diff options
Diffstat (limited to 'drivers/mmc/sh_mmcif.c')
-rw-r--r-- | drivers/mmc/sh_mmcif.c | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/drivers/mmc/sh_mmcif.c b/drivers/mmc/sh_mmcif.c new file mode 100644 index 0000000..567e2cb --- /dev/null +++ b/drivers/mmc/sh_mmcif.c @@ -0,0 +1,608 @@ +/* + * MMCIF driver. + * + * Copyright (C) 2011 Renesas Solutions Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + */ + +#include <config.h> +#include <common.h> +#include <watchdog.h> +#include <command.h> +#include <mmc.h> +#include <malloc.h> +#include <asm/errno.h> +#include <asm/io.h> +#include "sh_mmcif.h" + +#define DRIVER_NAME "sh_mmcif" + +static void *mmc_priv(struct mmc *mmc) +{ + return (void *)mmc->priv; +} + +static int sh_mmcif_intr(void *dev_id) +{ + struct sh_mmcif_host *host = dev_id; + u32 state = 0; + + state = sh_mmcif_read(&host->regs->ce_int); + state &= sh_mmcif_read(&host->regs->ce_int_mask); + + if (state & INT_RBSYE) { + sh_mmcif_write(~(INT_RBSYE | INT_CRSPE), &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MRBSYE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_CRSPE) { + sh_mmcif_write(~INT_CRSPE, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MCRSPE, &host->regs->ce_int_mask); + /* one more interrupt (INT_RBSYE) */ + if (sh_mmcif_read(&host->regs->ce_cmd_set) & CMD_SET_RBSY) + return -EAGAIN; + goto end; + } else if (state & INT_BUFREN) { + sh_mmcif_write(~INT_BUFREN, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MBUFREN, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_BUFWEN) { + sh_mmcif_write(~INT_BUFWEN, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MBUFWEN, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_CMD12DRE) { + sh_mmcif_write(~(INT_CMD12DRE | INT_CMD12RBE | INT_CMD12CRE | + INT_BUFRE), &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MCMD12DRE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_BUFRE) { + sh_mmcif_write(~INT_BUFRE, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MBUFRE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_DTRANE) { + sh_mmcif_write(~INT_DTRANE, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MDTRANE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_CMD12RBE) { + sh_mmcif_write(~(INT_CMD12RBE | INT_CMD12CRE), + &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MCMD12RBE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_ERR_STS) { + /* err interrupts */ + sh_mmcif_write(~state, &host->regs->ce_int); + sh_mmcif_bitclr(state, &host->regs->ce_int_mask); + goto err; + } else + return -EAGAIN; + +err: + host->sd_error = 1; + debug("%s: int err state = %08x\n", DRIVER_NAME, state); +end: + host->wait_int = 1; + return 0; +} + +static int mmcif_wait_interrupt_flag(struct sh_mmcif_host *host) +{ + int timeout = 10000000; + + while (1) { + timeout--; + if (timeout < 0) { + printf("timeout\n"); + return 0; + } + + if (!sh_mmcif_intr(host)) + break; + + udelay(1); /* 1 usec */ + } + + return 1; /* Return value: NOT 0 = complete waiting */ +} + +static void sh_mmcif_clock_control(struct sh_mmcif_host *host, unsigned int clk) +{ + int i; + + sh_mmcif_bitclr(CLK_ENABLE, &host->regs->ce_clk_ctrl); + sh_mmcif_bitclr(CLK_CLEAR, &host->regs->ce_clk_ctrl); + + if (!clk) + return; + if (clk == CLKDEV_EMMC_DATA) { + sh_mmcif_bitset(CLK_PCLK, &host->regs->ce_clk_ctrl); + } else { + for (i = 1; (unsigned int)host->clk / (1 << i) >= clk; i++) + ; + sh_mmcif_bitset((i - 1) << 16, &host->regs->ce_clk_ctrl); + } + sh_mmcif_bitset(CLK_ENABLE, &host->regs->ce_clk_ctrl); +} + +static void sh_mmcif_sync_reset(struct sh_mmcif_host *host) +{ + u32 tmp; + + tmp = sh_mmcif_read(&host->regs->ce_clk_ctrl) & (CLK_ENABLE | + CLK_CLEAR); + + sh_mmcif_write(SOFT_RST_ON, &host->regs->ce_version); + sh_mmcif_write(SOFT_RST_OFF, &host->regs->ce_version); + sh_mmcif_bitset(tmp | SRSPTO_256 | SRBSYTO_29 | SRWDTO_29 | SCCSTO_29, + &host->regs->ce_clk_ctrl); + /* byte swap on */ + sh_mmcif_bitset(BUF_ACC_ATYP, &host->regs->ce_buf_acc); +} + +static int sh_mmcif_error_manage(struct sh_mmcif_host *host) +{ + u32 state1, state2; + int ret, timeout = 10000000; + + host->sd_error = 0; + host->wait_int = 0; + + state1 = sh_mmcif_read(&host->regs->ce_host_sts1); + state2 = sh_mmcif_read(&host->regs->ce_host_sts2); + debug("%s: ERR HOST_STS1 = %08x\n", \ + DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts1)); + debug("%s: ERR HOST_STS2 = %08x\n", \ + DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts2)); + + if (state1 & STS1_CMDSEQ) { + debug("%s: Forced end of command sequence\n", DRIVER_NAME); + sh_mmcif_bitset(CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl); + sh_mmcif_bitset(~CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl); + while (1) { + timeout--; + if (timeout < 0) { + printf(DRIVER_NAME": Forceed end of " \ + "command sequence timeout err\n"); + return -EILSEQ; + } + if (!(sh_mmcif_read(&host->regs->ce_host_sts1) + & STS1_CMDSEQ)) + break; + } + sh_mmcif_sync_reset(host); + return -EILSEQ; + } + + if (state2 & STS2_CRC_ERR) + ret = -EILSEQ; + else if (state2 & STS2_TIMEOUT_ERR) + ret = TIMEOUT; + else + ret = -EILSEQ; + return ret; +} + +static int sh_mmcif_single_read(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 blocksize, i; + unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + + /* buf read enable */ + sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask); + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + blocksize = (BLOCK_SIZE_MASK & + sh_mmcif_read(&host->regs->ce_block_set)) + 3; + for (i = 0; i < blocksize / 4; i++) + *p++ = sh_mmcif_read(&host->regs->ce_data); + + /* buffer read end */ + sh_mmcif_bitset(MASK_MBUFRE, &host->regs->ce_int_mask); + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_mmcif_multi_read(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 blocksize, i, j; + unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set); + for (j = 0; j < data->blocks; j++) { + sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask); + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + for (i = 0; i < blocksize / 4; i++) + *p++ = sh_mmcif_read(&host->regs->ce_data); + + WATCHDOG_RESET(); + } + return 0; +} + +static int sh_mmcif_single_write(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 blocksize, i; + const unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + blocksize = (BLOCK_SIZE_MASK & + sh_mmcif_read(&host->regs->ce_block_set)) + 3; + for (i = 0; i < blocksize / 4; i++) + sh_mmcif_write(*p++, &host->regs->ce_data); + + /* buffer write end */ + sh_mmcif_bitset(MASK_MDTRANE, &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_mmcif_multi_write(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 i, j, blocksize; + const unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set); + for (j = 0; j < data->blocks; j++) { + sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + for (i = 0; i < blocksize / 4; i++) + sh_mmcif_write(*p++, &host->regs->ce_data); + + WATCHDOG_RESET(); + } + return 0; +} + +static void sh_mmcif_get_response(struct sh_mmcif_host *host, + struct mmc_cmd *cmd) +{ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp3); + cmd->response[1] = sh_mmcif_read(&host->regs->ce_resp2); + cmd->response[2] = sh_mmcif_read(&host->regs->ce_resp1); + cmd->response[3] = sh_mmcif_read(&host->regs->ce_resp0); + debug(" RESP %08x, %08x, %08x, %08x\n", cmd->response[0], + cmd->response[1], cmd->response[2], cmd->response[3]); + } else { + cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp0); + } +} + +static void sh_mmcif_get_cmd12response(struct sh_mmcif_host *host, + struct mmc_cmd *cmd) +{ + cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp_cmd12); +} + +static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host, + struct mmc_data *data, struct mmc_cmd *cmd) +{ + u32 tmp = 0; + u32 opc = cmd->cmdidx; + + /* Response Type check */ + switch (cmd->resp_type) { + case MMC_RSP_NONE: + tmp |= CMD_SET_RTYP_NO; + break; + case MMC_RSP_R1: + case MMC_RSP_R1b: + case MMC_RSP_R3: + tmp |= CMD_SET_RTYP_6B; + break; + case MMC_RSP_R2: + tmp |= CMD_SET_RTYP_17B; + break; + default: + printf(DRIVER_NAME": Not support type response.\n"); + break; + } + + /* RBSY */ + if (opc == MMC_CMD_SWITCH) + tmp |= CMD_SET_RBSY; + + /* WDAT / DATW */ + if (host->data) { + tmp |= CMD_SET_WDAT; + switch (host->bus_width) { + case MMC_BUS_WIDTH_1: + tmp |= CMD_SET_DATW_1; + break; + case MMC_BUS_WIDTH_4: + tmp |= CMD_SET_DATW_4; + break; + case MMC_BUS_WIDTH_8: + tmp |= CMD_SET_DATW_8; + break; + default: + printf(DRIVER_NAME": Not support bus width.\n"); + break; + } + } + /* DWEN */ + if (opc == MMC_CMD_WRITE_SINGLE_BLOCK || + opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) + tmp |= CMD_SET_DWEN; + /* CMLTE/CMD12EN */ + if (opc == MMC_CMD_READ_MULTIPLE_BLOCK || + opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) { + tmp |= CMD_SET_CMLTE | CMD_SET_CMD12EN; + sh_mmcif_bitset(data->blocks << 16, &host->regs->ce_block_set); + } + /* RIDXC[1:0] check bits */ + if (opc == MMC_CMD_SEND_OP_COND || opc == MMC_CMD_ALL_SEND_CID || + opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID) + tmp |= CMD_SET_RIDXC_BITS; + /* RCRC7C[1:0] check bits */ + if (opc == MMC_CMD_SEND_OP_COND) + tmp |= CMD_SET_CRC7C_BITS; + /* RCRC7C[1:0] internal CRC7 */ + if (opc == MMC_CMD_ALL_SEND_CID || + opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID) + tmp |= CMD_SET_CRC7C_INTERNAL; + + return opc = ((opc << 24) | tmp); +} + +static u32 sh_mmcif_data_trans(struct sh_mmcif_host *host, + struct mmc_data *data, u16 opc) +{ + u32 ret; + + switch (opc) { + case MMC_CMD_READ_MULTIPLE_BLOCK: + ret = sh_mmcif_multi_read(host, data); + break; + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + ret = sh_mmcif_multi_write(host, data); + break; + case MMC_CMD_WRITE_SINGLE_BLOCK: + ret = sh_mmcif_single_write(host, data); + break; + case MMC_CMD_READ_SINGLE_BLOCK: + case MMC_CMD_SEND_EXT_CSD: + ret = sh_mmcif_single_read(host, data); + break; + default: + printf(DRIVER_NAME": NOT SUPPORT CMD = d'%08d\n", opc); + ret = -EINVAL; + break; + } + return ret; +} + +static int sh_mmcif_start_cmd(struct sh_mmcif_host *host, + struct mmc_data *data, struct mmc_cmd *cmd) +{ + long time; + int ret = 0, mask = 0; + u32 opc = cmd->cmdidx; + + if (opc == MMC_CMD_STOP_TRANSMISSION) { + /* MMCIF sends the STOP command automatically */ + if (host->last_cmd == MMC_CMD_READ_MULTIPLE_BLOCK) + sh_mmcif_bitset(MASK_MCMD12DRE, + &host->regs->ce_int_mask); + else + sh_mmcif_bitset(MASK_MCMD12RBE, + &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + sh_mmcif_get_cmd12response(host, cmd); + return 0; + } + if (opc == MMC_CMD_SWITCH) + mask = MASK_MRBSYE; + else + mask = MASK_MCRSPE; + + mask |= MASK_MCMDVIO | MASK_MBUFVIO | MASK_MWDATERR | + MASK_MRDATERR | MASK_MRIDXERR | MASK_MRSPERR | + MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO | + MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO; + + if (host->data) { + sh_mmcif_write(0, &host->regs->ce_block_set); + sh_mmcif_write(data->blocksize, &host->regs->ce_block_set); + } + opc = sh_mmcif_set_cmd(host, data, cmd); + + sh_mmcif_write(INT_START_MAGIC, &host->regs->ce_int); + sh_mmcif_write(mask, &host->regs->ce_int_mask); + + debug("CMD%d ARG:%08x\n", cmd->cmdidx, cmd->cmdarg); + /* set arg */ + sh_mmcif_write(cmd->cmdarg, &host->regs->ce_arg); + host->wait_int = 0; + /* set cmd */ + sh_mmcif_write(opc, &host->regs->ce_cmd_set); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0) + return sh_mmcif_error_manage(host); + + if (host->sd_error) { + switch (cmd->cmdidx) { + case MMC_CMD_ALL_SEND_CID: + case MMC_CMD_SELECT_CARD: + case MMC_CMD_APP_CMD: + ret = TIMEOUT; + break; + default: + printf(DRIVER_NAME": Cmd(d'%d) err\n", cmd->cmdidx); + ret = sh_mmcif_error_manage(host); + break; + } + host->sd_error = 0; + host->wait_int = 0; + return ret; + } + + /* if no response */ + if (!(opc & 0x00C00000)) + return 0; + + if (host->wait_int == 1) { + sh_mmcif_get_response(host, cmd); + host->wait_int = 0; + } + if (host->data) + ret = sh_mmcif_data_trans(host, data, cmd->cmdidx); + host->last_cmd = cmd->cmdidx; + + return ret; +} + +static int sh_mmcif_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sh_mmcif_host *host = mmc_priv(mmc); + int ret; + + WATCHDOG_RESET(); + + switch (cmd->cmdidx) { + case MMC_CMD_APP_CMD: + return TIMEOUT; + case MMC_CMD_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */ + if (data) + /* ext_csd */ + break; + else + /* send_if_cond cmd (not support) */ + return TIMEOUT; + default: + break; + } + host->sd_error = 0; + host->data = data; + ret = sh_mmcif_start_cmd(host, data, cmd); + host->data = NULL; + + return ret; +} + +static void sh_mmcif_set_ios(struct mmc *mmc) +{ + struct sh_mmcif_host *host = mmc_priv(mmc); + + if (mmc->clock) + sh_mmcif_clock_control(host, mmc->clock); + + if (mmc->bus_width == 8) + host->bus_width = MMC_BUS_WIDTH_8; + else if (mmc->bus_width == 4) + host->bus_width = MMC_BUS_WIDTH_4; + else + host->bus_width = MMC_BUS_WIDTH_1; + + debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width); +} + +static int sh_mmcif_init(struct mmc *mmc) +{ + struct sh_mmcif_host *host = mmc_priv(mmc); + + sh_mmcif_sync_reset(host); + sh_mmcif_write(MASK_ALL, &host->regs->ce_int_mask); + return 0; +} + +int mmcif_mmc_init(void) +{ + int ret = 0; + struct mmc *mmc; + struct sh_mmcif_host *host = NULL; + + mmc = malloc(sizeof(struct mmc)); + if (!mmc) + ret = -ENOMEM; + memset(mmc, 0, sizeof(*mmc)); + host = malloc(sizeof(struct sh_mmcif_host)); + if (!host) + ret = -ENOMEM; + memset(host, 0, sizeof(*host)); + + mmc->f_min = CLKDEV_MMC_INIT; + mmc->f_max = CLKDEV_EMMC_DATA; + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT | + MMC_MODE_8BIT; + memcpy(mmc->name, DRIVER_NAME, sizeof(DRIVER_NAME)); + mmc->send_cmd = sh_mmcif_request; + mmc->set_ios = sh_mmcif_set_ios; + mmc->init = sh_mmcif_init; + host->regs = (struct sh_mmcif_regs *)CONFIG_SH_MMCIF_ADDR; + host->clk = CONFIG_SH_MMCIF_CLK; + mmc->priv = host; + + mmc_register(mmc); + + return ret; +} |