From 5dbdb7da1cae6e58d8b1b47948c16bb7a6babd72 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 18 Mar 2016 22:14:16 +0800 Subject: drivers: add designware emmc driver Support Designware eMMC driver. It's based on both IO block and eMMC driver. Signed-off-by: Haojian Zhuang --- drivers/synopsys/emmc/dw_mmc.c | 424 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 drivers/synopsys/emmc/dw_mmc.c (limited to 'drivers') diff --git a/drivers/synopsys/emmc/dw_mmc.c b/drivers/synopsys/emmc/dw_mmc.c new file mode 100644 index 00000000..5b156837 --- /dev/null +++ b/drivers/synopsys/emmc/dw_mmc.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2016-2017, ARM Limited and Contributors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of ARM nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DWMMC_CTRL (0x00) +#define CTRL_IDMAC_EN (1 << 25) +#define CTRL_DMA_EN (1 << 5) +#define CTRL_INT_EN (1 << 4) +#define CTRL_DMA_RESET (1 << 2) +#define CTRL_FIFO_RESET (1 << 1) +#define CTRL_RESET (1 << 0) +#define CTRL_RESET_ALL (CTRL_DMA_RESET | CTRL_FIFO_RESET | \ + CTRL_RESET) + +#define DWMMC_PWREN (0x04) +#define DWMMC_CLKDIV (0x08) +#define DWMMC_CLKSRC (0x0c) +#define DWMMC_CLKENA (0x10) +#define DWMMC_TMOUT (0x14) +#define DWMMC_CTYPE (0x18) +#define CTYPE_8BIT (1 << 16) +#define CTYPE_4BIT (1) +#define CTYPE_1BIT (0) + +#define DWMMC_BLKSIZ (0x1c) +#define DWMMC_BYTCNT (0x20) +#define DWMMC_INTMASK (0x24) +#define INT_EBE (1 << 15) +#define INT_SBE (1 << 13) +#define INT_HLE (1 << 12) +#define INT_FRUN (1 << 11) +#define INT_DRT (1 << 9) +#define INT_RTO (1 << 8) +#define INT_DCRC (1 << 7) +#define INT_RCRC (1 << 6) +#define INT_RXDR (1 << 5) +#define INT_TXDR (1 << 4) +#define INT_DTO (1 << 3) +#define INT_CMD_DONE (1 << 2) +#define INT_RE (1 << 1) + +#define DWMMC_CMDARG (0x28) +#define DWMMC_CMD (0x2c) +#define CMD_START (1 << 31) +#define CMD_USE_HOLD_REG (1 << 29) /* 0 if SDR50/100 */ +#define CMD_UPDATE_CLK_ONLY (1 << 21) +#define CMD_SEND_INIT (1 << 15) +#define CMD_STOP_ABORT_CMD (1 << 14) +#define CMD_WAIT_PRVDATA_COMPLETE (1 << 13) +#define CMD_WRITE (1 << 10) +#define CMD_DATA_TRANS_EXPECT (1 << 9) +#define CMD_CHECK_RESP_CRC (1 << 8) +#define CMD_RESP_LEN (1 << 7) +#define CMD_RESP_EXPECT (1 << 6) +#define CMD(x) (x & 0x3f) + +#define DWMMC_RESP0 (0x30) +#define DWMMC_RESP1 (0x34) +#define DWMMC_RESP2 (0x38) +#define DWMMC_RESP3 (0x3c) +#define DWMMC_RINTSTS (0x44) +#define DWMMC_STATUS (0x48) +#define STATUS_DATA_BUSY (1 << 9) + +#define DWMMC_FIFOTH (0x4c) +#define FIFOTH_TWMARK(x) (x & 0xfff) +#define FIFOTH_RWMARK(x) ((x & 0x1ff) << 16) +#define FIFOTH_DMA_BURST_SIZE(x) ((x & 0x7) << 28) + +#define DWMMC_DEBNCE (0x64) +#define DWMMC_BMOD (0x80) +#define BMOD_ENABLE (1 << 7) +#define BMOD_FB (1 << 1) +#define BMOD_SWRESET (1 << 0) + +#define DWMMC_DBADDR (0x88) +#define DWMMC_IDSTS (0x8c) +#define DWMMC_IDINTEN (0x90) +#define DWMMC_CARDTHRCTL (0x100) +#define CARDTHRCTL_RD_THR(x) ((x & 0xfff) << 16) +#define CARDTHRCTL_RD_THR_EN (1 << 0) + +#define IDMAC_DES0_DIC (1 << 1) +#define IDMAC_DES0_LD (1 << 2) +#define IDMAC_DES0_FS (1 << 3) +#define IDMAC_DES0_CH (1 << 4) +#define IDMAC_DES0_ER (1 << 5) +#define IDMAC_DES0_CES (1 << 30) +#define IDMAC_DES0_OWN (1 << 31) +#define IDMAC_DES1_BS1(x) ((x) & 0x1fff) +#define IDMAC_DES2_BS2(x) (((x) & 0x1fff) << 13) + +#define DWMMC_DMA_MAX_BUFFER_SIZE (512 * 8) + +#define DWMMC_8BIT_MODE (1 << 6) + +#define TIMEOUT 100000 + +struct dw_idmac_desc { + unsigned int des0; + unsigned int des1; + unsigned int des2; + unsigned int des3; +}; + +static void dw_init(void); +static int dw_send_cmd(emmc_cmd_t *cmd); +static int dw_set_ios(int clk, int width); +static int dw_prepare(int lba, uintptr_t buf, size_t size); +static int dw_read(int lba, uintptr_t buf, size_t size); +static int dw_write(int lba, uintptr_t buf, size_t size); + +static const emmc_ops_t dw_mmc_ops = { + .init = dw_init, + .send_cmd = dw_send_cmd, + .set_ios = dw_set_ios, + .prepare = dw_prepare, + .read = dw_read, + .write = dw_write, +}; + +static dw_mmc_params_t dw_params; + +static void dw_update_clk(void) +{ + unsigned int data; + + mmio_write_32(dw_params.reg_base + DWMMC_CMD, + CMD_WAIT_PRVDATA_COMPLETE | CMD_UPDATE_CLK_ONLY | + CMD_START); + while (1) { + data = mmio_read_32(dw_params.reg_base + DWMMC_CMD); + if ((data & CMD_START) == 0) + break; + data = mmio_read_32(dw_params.reg_base + DWMMC_RINTSTS); + assert(data & INT_HLE); + } +} + +static void dw_set_clk(int clk) +{ + unsigned int data; + int div; + + assert(clk > 0); + + for (div = 1; div < 256; div++) { + if ((dw_params.clk_rate / (2 * div)) <= clk) { + break; + } + } + assert(div < 256); + + /* wait until controller is idle */ + do { + data = mmio_read_32(dw_params.reg_base + DWMMC_STATUS); + } while (data & STATUS_DATA_BUSY); + + /* disable clock before change clock rate */ + mmio_write_32(dw_params.reg_base + DWMMC_CLKENA, 0); + dw_update_clk(); + + mmio_write_32(dw_params.reg_base + DWMMC_CLKDIV, div); + dw_update_clk(); + + /* enable clock */ + mmio_write_32(dw_params.reg_base + DWMMC_CLKENA, 1); + mmio_write_32(dw_params.reg_base + DWMMC_CLKSRC, 0); + dw_update_clk(); +} + +static void dw_init(void) +{ + unsigned int data; + uintptr_t base; + + assert((dw_params.reg_base & EMMC_BLOCK_MASK) == 0); + + base = dw_params.reg_base; + mmio_write_32(base + DWMMC_PWREN, 1); + mmio_write_32(base + DWMMC_CTRL, CTRL_RESET_ALL); + do { + data = mmio_read_32(base + DWMMC_CTRL); + } while (data); + + /* enable DMA in CTRL */ + data = CTRL_INT_EN | CTRL_DMA_EN | CTRL_IDMAC_EN; + mmio_write_32(base + DWMMC_CTRL, data); + mmio_write_32(base + DWMMC_RINTSTS, ~0); + mmio_write_32(base + DWMMC_INTMASK, 0); + mmio_write_32(base + DWMMC_TMOUT, ~0); + mmio_write_32(base + DWMMC_IDINTEN, ~0); + mmio_write_32(base + DWMMC_BLKSIZ, EMMC_BLOCK_SIZE); + mmio_write_32(base + DWMMC_BYTCNT, 256 * 1024); + mmio_write_32(base + DWMMC_DEBNCE, 0x00ffffff); + mmio_write_32(base + DWMMC_BMOD, BMOD_SWRESET); + do { + data = mmio_read_32(base + DWMMC_BMOD); + } while (data & BMOD_SWRESET); + /* enable DMA in BMOD */ + data |= BMOD_ENABLE | BMOD_FB; + mmio_write_32(base + DWMMC_BMOD, data); + + udelay(100); + dw_set_clk(EMMC_BOOT_CLK_RATE); + udelay(100); +} + +static int dw_send_cmd(emmc_cmd_t *cmd) +{ + unsigned int op, data, err_mask; + uintptr_t base; + int timeout; + + assert(cmd); + + base = dw_params.reg_base; + + switch (cmd->cmd_idx) { + case EMMC_CMD0: + op = CMD_SEND_INIT; + break; + case EMMC_CMD12: + op = CMD_STOP_ABORT_CMD; + break; + case EMMC_CMD13: + op = CMD_WAIT_PRVDATA_COMPLETE; + break; + case EMMC_CMD8: + case EMMC_CMD17: + case EMMC_CMD18: + op = CMD_DATA_TRANS_EXPECT | CMD_WAIT_PRVDATA_COMPLETE; + break; + case EMMC_CMD24: + case EMMC_CMD25: + op = CMD_WRITE | CMD_DATA_TRANS_EXPECT | + CMD_WAIT_PRVDATA_COMPLETE; + break; + default: + op = 0; + break; + } + op |= CMD_USE_HOLD_REG | CMD_START; + switch (cmd->resp_type) { + case 0: + break; + case EMMC_RESPONSE_R2: + op |= CMD_RESP_EXPECT | CMD_CHECK_RESP_CRC | + CMD_RESP_LEN; + break; + case EMMC_RESPONSE_R3: + op |= CMD_RESP_EXPECT; + break; + default: + op |= CMD_RESP_EXPECT | CMD_CHECK_RESP_CRC; + break; + } + timeout = TIMEOUT; + do { + data = mmio_read_32(base + DWMMC_STATUS); + if (--timeout <= 0) + panic(); + } while (data & STATUS_DATA_BUSY); + + mmio_write_32(base + DWMMC_RINTSTS, ~0); + mmio_write_32(base + DWMMC_CMDARG, cmd->cmd_arg); + mmio_write_32(base + DWMMC_CMD, op | cmd->cmd_idx); + + err_mask = INT_EBE | INT_HLE | INT_RTO | INT_RCRC | INT_RE | + INT_DCRC | INT_DRT | INT_SBE; + timeout = TIMEOUT; + do { + udelay(500); + data = mmio_read_32(base + DWMMC_RINTSTS); + + if (data & err_mask) + return -EIO; + if (data & INT_DTO) + break; + if (--timeout == 0) { + ERROR("%s, RINTSTS:0x%x\n", __func__, data); + panic(); + } + } while (!(data & INT_CMD_DONE)); + + if (op & CMD_RESP_EXPECT) { + cmd->resp_data[0] = mmio_read_32(base + DWMMC_RESP0); + if (op & CMD_RESP_LEN) { + cmd->resp_data[1] = mmio_read_32(base + DWMMC_RESP1); + cmd->resp_data[2] = mmio_read_32(base + DWMMC_RESP2); + cmd->resp_data[3] = mmio_read_32(base + DWMMC_RESP3); + } + } + return 0; +} + +static int dw_set_ios(int clk, int width) +{ + switch (width) { + case EMMC_BUS_WIDTH_1: + mmio_write_32(dw_params.reg_base + DWMMC_CTYPE, CTYPE_1BIT); + break; + case EMMC_BUS_WIDTH_4: + mmio_write_32(dw_params.reg_base + DWMMC_CTYPE, CTYPE_4BIT); + break; + case EMMC_BUS_WIDTH_8: + mmio_write_32(dw_params.reg_base + DWMMC_CTYPE, CTYPE_8BIT); + break; + default: + assert(0); + } + dw_set_clk(clk); + return 0; +} + +static int dw_prepare(int lba, uintptr_t buf, size_t size) +{ + struct dw_idmac_desc *desc; + int desc_cnt, i, last; + uintptr_t base; + + assert(((buf & EMMC_BLOCK_MASK) == 0) && + ((size % EMMC_BLOCK_SIZE) == 0) && + (dw_params.desc_size > 0) && + ((dw_params.reg_base & EMMC_BLOCK_MASK) == 0) && + ((dw_params.desc_base & EMMC_BLOCK_MASK) == 0) && + ((dw_params.desc_size & EMMC_BLOCK_MASK) == 0)); + + desc_cnt = (size + DWMMC_DMA_MAX_BUFFER_SIZE - 1) / + DWMMC_DMA_MAX_BUFFER_SIZE; + assert(desc_cnt * sizeof(struct dw_idmac_desc) < dw_params.desc_size); + + base = dw_params.reg_base; + desc = (struct dw_idmac_desc *)dw_params.desc_base; + mmio_write_32(base + DWMMC_BYTCNT, size); + mmio_write_32(base + DWMMC_RINTSTS, ~0); + for (i = 0; i < desc_cnt; i++) { + desc[i].des0 = IDMAC_DES0_OWN | IDMAC_DES0_CH | IDMAC_DES0_DIC; + desc[i].des1 = IDMAC_DES1_BS1(DWMMC_DMA_MAX_BUFFER_SIZE); + desc[i].des2 = buf + DWMMC_DMA_MAX_BUFFER_SIZE * i; + desc[i].des3 = dw_params.desc_base + + (sizeof(struct dw_idmac_desc)) * (i + 1); + } + /* first descriptor */ + desc->des0 |= IDMAC_DES0_FS; + /* last descriptor */ + last = desc_cnt - 1; + (desc + last)->des0 |= IDMAC_DES0_LD; + (desc + last)->des0 &= ~(IDMAC_DES0_DIC | IDMAC_DES0_CH); + (desc + last)->des1 = IDMAC_DES1_BS1(size - (last * + DWMMC_DMA_MAX_BUFFER_SIZE)); + /* set next descriptor address as 0 */ + (desc + last)->des3 = 0; + + mmio_write_32(base + DWMMC_DBADDR, dw_params.desc_base); + clean_dcache_range(dw_params.desc_base, + desc_cnt * DWMMC_DMA_MAX_BUFFER_SIZE); + + return 0; +} + +static int dw_read(int lba, uintptr_t buf, size_t size) +{ + return 0; +} + +static int dw_write(int lba, uintptr_t buf, size_t size) +{ + return 0; +} + +void dw_mmc_init(dw_mmc_params_t *params) +{ + assert((params != 0) && + ((params->reg_base & EMMC_BLOCK_MASK) == 0) && + ((params->desc_base & EMMC_BLOCK_MASK) == 0) && + ((params->desc_size & EMMC_BLOCK_MASK) == 0) && + (params->desc_size > 0) && + (params->clk_rate > 0) && + ((params->bus_width == EMMC_BUS_WIDTH_1) || + (params->bus_width == EMMC_BUS_WIDTH_4) || + (params->bus_width == EMMC_BUS_WIDTH_8))); + + memcpy(&dw_params, params, sizeof(dw_mmc_params_t)); + emmc_init(&dw_mmc_ops, params->clk_rate, params->bus_width, + params->flags); +} -- cgit v1.2.3