/* * Copyright (C) 2011 Andes Technology Corporation * Macpaul Lin, Andes Technology Corporation * * See file CREDITS for list of people who contributed to this * project. * * 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, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include #include #include #include #include /* * supported mmc hosts * setting the number CONFIG_FTSDC010_NUMBER in your configuration file. */ static struct mmc ftsdc010_dev[CONFIG_FTSDC010_NUMBER]; static struct mmc_host ftsdc010_host[CONFIG_FTSDC010_NUMBER]; static struct ftsdc010_mmc *ftsdc010_get_base_mmc(int dev_index) { return (struct ftsdc010_mmc *)CONFIG_FTSDC010_BASE + dev_index; } #ifdef DEBUG static void ftsdc010_dump_reg(struct mmc_host *host) { debug("cmd: %08x\n", readl(&host->reg->cmd)); debug("argu: %08x\n", readl(&host->reg->argu)); debug("rsp0: %08x\n", readl(&host->reg->rsp0)); debug("rsp1: %08x\n", readl(&host->reg->rsp1)); debug("rsp2: %08x\n", readl(&host->reg->rsp2)); debug("rsp3: %08x\n", readl(&host->reg->rsp3)); debug("rsp_cmd: %08x\n", readl(&host->reg->rsp_cmd)); debug("dcr: %08x\n", readl(&host->reg->dcr)); debug("dtr: %08x\n", readl(&host->reg->dtr)); debug("dlr: %08x\n", readl(&host->reg->dlr)); debug("status: %08x\n", readl(&host->reg->status)); debug("clr: %08x\n", readl(&host->reg->clr)); debug("int_mask: %08x\n", readl(&host->reg->int_mask)); debug("pcr: %08x\n", readl(&host->reg->pcr)); debug("ccr: %08x\n", readl(&host->reg->ccr)); debug("bwr: %08x\n", readl(&host->reg->bwr)); debug("dwr: %08x\n", readl(&host->reg->dwr)); debug("feature: %08x\n", readl(&host->reg->feature)); debug("rev: %08x\n", readl(&host->reg->rev)); } #endif static unsigned int enable_imask(struct ftsdc010_mmc *reg, unsigned int imask) { unsigned int newmask; newmask = readl(®->int_mask); newmask |= imask; writel(newmask, ®->int_mask); return newmask; } static void ftsdc010_pio_read(struct mmc_host *host, char *buf, unsigned int size) { unsigned int fifo; unsigned int fifo_words; unsigned int *ptr; unsigned int status; unsigned int retry = 0; /* get_data_buffer */ ptr = (unsigned int *)buf; while (size) { status = readl(&host->reg->status); debug("%s: size: %08x\n", __func__, size); if (status & FTSDC010_STATUS_FIFO_ORUN) { debug("%s: FIFO OVERRUN: sta: %08x\n", __func__, status); fifo = host->fifo_len > size ? size : host->fifo_len; size -= fifo; fifo_words = fifo >> 2; while (fifo_words--) *ptr++ = readl(&host->reg->dwr); /* * for adding some delays for SD card to put * data into FIFO again */ udelay(4*FTSDC010_DELAY_UNIT); #ifdef CONFIG_FTSDC010_SDIO /* sdio allow non-power-of-2 blksz */ if (fifo & 3) { unsigned int n = fifo & 3; unsigned int data = readl(&host->reg->dwr); unsigned char *p = (unsigned char *)ptr; while (n--) { *p++ = data; data >>= 8; } } #endif } else { udelay(1); if (++retry >= FTSDC010_PIO_RETRY) { debug("%s: PIO_RETRY timeout\n", __func__); return; } } } } static void ftsdc010_pio_write(struct mmc_host *host, const char *buf, unsigned int size) { unsigned int fifo; unsigned int *ptr; unsigned int status; unsigned int retry = 0; /* get data buffer */ ptr = (unsigned int *)buf; while (size) { status = readl(&host->reg->status); if (status & FTSDC010_STATUS_FIFO_URUN) { fifo = host->fifo_len > size ? size : host->fifo_len; size -= fifo; fifo = (fifo + 3) >> 2; while (fifo--) { writel(*ptr, &host->reg->dwr); ptr++; } } else { udelay(1); if (++retry >= FTSDC010_PIO_RETRY) { debug("%s: PIO_RETRY timeout\n", __func__); return; } } } } static int ftsdc010_check_rsp(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { struct mmc_host *host = mmc->priv; unsigned int sta, clear; sta = readl(&host->reg->status); debug("%s: sta: %08x cmd %d\n", __func__, sta, cmd->cmdidx); /* check RSP TIMEOUT or FAIL */ if (sta & FTSDC010_STATUS_RSP_TIMEOUT) { /* RSP TIMEOUT */ debug("%s: RSP timeout: sta: %08x\n", __func__, sta); clear |= FTSDC010_CLR_RSP_TIMEOUT; writel(clear, &host->reg->clr); return TIMEOUT; } else if (sta & FTSDC010_STATUS_RSP_CRC_FAIL) { /* clear response fail bit */ debug("%s: RSP CRC FAIL: sta: %08x\n", __func__, sta); clear |= FTSDC010_CLR_RSP_CRC_FAIL; writel(clear, &host->reg->clr); return COMM_ERR; } else if (sta & FTSDC010_STATUS_RSP_CRC_OK) { /* clear response CRC OK bit */ clear |= FTSDC010_CLR_RSP_CRC_OK; } writel(clear, &host->reg->clr); return 0; } static int ftsdc010_check_data(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { struct mmc_host *host = mmc->priv; unsigned int sta, clear; sta = readl(&host->reg->status); debug("%s: sta: %08x cmd %d\n", __func__, sta, cmd->cmdidx); /* check DATA TIMEOUT or FAIL */ if (data) { /* Transfer Complete */ if (sta & FTSDC010_STATUS_DATA_END) clear |= FTSDC010_STATUS_DATA_END; /* Data CRC_OK */ if (sta & FTSDC010_STATUS_DATA_CRC_OK) clear |= FTSDC010_STATUS_DATA_CRC_OK; /* DATA TIMEOUT or DATA CRC FAIL */ if (sta & FTSDC010_STATUS_DATA_TIMEOUT) { /* DATA TIMEOUT */ debug("%s: DATA TIMEOUT: sta: %08x\n", __func__, sta); clear |= FTSDC010_STATUS_DATA_TIMEOUT; writel(clear, &host->reg->clr); return TIMEOUT; } else if (sta & FTSDC010_STATUS_DATA_CRC_FAIL) { /* DATA CRC FAIL */ debug("%s: DATA CRC FAIL: sta: %08x\n", __func__, sta); clear |= FTSDC010_STATUS_DATA_CRC_FAIL; writel(clear, &host->reg->clr); return COMM_ERR; } writel(clear, &host->reg->clr); } return 0; } static int ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { struct mmc_host *host = mmc->priv; #ifdef CONFIG_FTSDC010_SDIO unsigned int scon; #endif unsigned int ccon; unsigned int mask, tmpmask; unsigned int ret; unsigned int sta, i; ret = 0; if (data) mask = FTSDC010_INT_MASK_RSP_TIMEOUT; else if (cmd->resp_type & MMC_RSP_PRESENT) mask = FTSDC010_INT_MASK_RSP_TIMEOUT; else mask = FTSDC010_INT_MASK_CMD_SEND; /* write argu reg */ debug("%s: argu: %08x\n", __func__, host->reg->argu); writel(cmd->cmdarg, &host->reg->argu); /* setup commnad */ ccon = FTSDC010_CMD_IDX(cmd->cmdidx); /* setup command flags */ ccon |= FTSDC010_CMD_CMD_EN; /* * This hardware didn't support specific commands for mapping * MMC_RSP_BUSY and MMC_RSP_OPCODE. Hence we don't deal with it. */ if (cmd->resp_type & MMC_RSP_PRESENT) { ccon |= FTSDC010_CMD_NEED_RSP; mask |= FTSDC010_INT_MASK_RSP_CRC_OK | FTSDC010_INT_MASK_RSP_CRC_FAIL; } if (cmd->resp_type & MMC_RSP_136) ccon |= FTSDC010_CMD_LONG_RSP; /* In Linux driver, MMC_CMD_APP_CMD is checked in last_opcode */ if (host->last_opcode == MMC_CMD_APP_CMD) ccon |= FTSDC010_CMD_APP_CMD; #ifdef CONFIG_FTSDC010_SDIO scon = readl(&host->reg->sdio_ctrl1); if (host->card_type == MMC_TYPE_SDIO) scon |= FTSDC010_SDIO_CTRL1_SDIO_ENABLE; else scon &= ~FTSDC010_SDIO_CTRL1_SDIO_ENABLE; writel(scon, &host->reg->sdio_ctrl1); #endif /* record last opcode for specifing the command type to hardware */ host->last_opcode = cmd->cmdidx; /* write int_mask reg */ tmpmask = readl(&host->reg->int_mask); tmpmask |= mask; writel(tmpmask, &host->reg->int_mask); /* write cmd reg */ debug("%s: ccon: %08x\n", __func__, ccon); writel(ccon, &host->reg->cmd); /* check CMD_SEND */ for (i = 0; i < FTSDC010_CMD_RETRY; i++) { /* * If we read status register too fast * will lead hardware error and the RSP_TIMEOUT * flag will be raised incorrectly. */ udelay(16*FTSDC010_DELAY_UNIT); sta = readl(&host->reg->status); /* Command Complete */ /* * Note: * Do not clear FTSDC010_CLR_CMD_SEND flag. * (by writing FTSDC010_CLR_CMD_SEND bit to clear register) * It will make the driver becomes very slow. * If the operation hasn't been finished, hardware will * clear this bit automatically. * In origin, the driver will clear this flag if there is * no data need to be read. */ if (sta & FTSDC010_STATUS_CMD_SEND) break; } if (i > FTSDC010_CMD_RETRY) { printf("%s: send command timeout\n", __func__); return TIMEOUT; } /* check rsp status */ ret = ftsdc010_check_rsp(mmc, cmd, data); if (ret) return ret; /* read response if we have RSP_OK */ if (ccon & FTSDC010_CMD_LONG_RSP) { cmd->response[0] = readl(&host->reg->rsp3); cmd->response[1] = readl(&host->reg->rsp2); cmd->response[2] = readl(&host->reg->rsp1); cmd->response[3] = readl(&host->reg->rsp0); } else { cmd->response[0] = readl(&host->reg->rsp0); } /* read/write data */ if (data && (data->flags & MMC_DATA_READ)) { ftsdc010_pio_read(host, data->dest, data->blocksize * data->blocks); } else if (data && (data->flags & MMC_DATA_WRITE)) { ftsdc010_pio_write(host, data->src, data->blocksize * data->blocks); } /* check data status */ if (data) { ret = ftsdc010_check_data(mmc, cmd, data); if (ret) return ret; } udelay(FTSDC010_DELAY_UNIT); return ret; } static unsigned int cal_blksz(unsigned int blksz) { unsigned int blksztwo = 0; while (blksz >>= 1) blksztwo++; return blksztwo; } static int ftsdc010_setup_data(struct mmc *mmc, struct mmc_data *data) { struct mmc_host *host = mmc->priv; unsigned int dcon, newmask; /* configure data transfer paramter */ if (!data) return 0; if (((data->blocksize - 1) & data->blocksize) != 0) { printf("%s: can't do non-power-of 2 sized block transfers" " (blksz %d)\n", __func__, data->blocksize); return -1; } /* * We cannot deal with unaligned blocks with more than * one block being transfered. */ if ((data->blocksize <= 2) && (data->blocks > 1)) { printf("%s: can't do non-word sized block transfers" " (blksz %d)\n", __func__, data->blocksize); return -1; } /* data length */ dcon = data->blocksize * data->blocks; writel(dcon, &host->reg->dlr); /* write data control */ dcon = cal_blksz(data->blocksize); /* add to IMASK register */ newmask = (FTSDC010_STATUS_RSP_CRC_FAIL | FTSDC010_STATUS_DATA_TIMEOUT); /* * enable UNDERRUN will trigger interrupt immediatedly * So setup it when rsp is received successfully */ if (data->flags & MMC_DATA_WRITE) { dcon |= FTSDC010_DCR_DATA_WRITE; } else { dcon &= ~FTSDC010_DCR_DATA_WRITE; newmask |= FTSDC010_STATUS_FIFO_ORUN; } enable_imask(host->reg, newmask); #ifdef CONFIG_FTSDC010_SDIO /* always reset fifo since last transfer may fail */ dcon |= FTSDC010_DCR_FIFO_RST; if (data->blocks > 1) dcon |= FTSDC010_SDIO_CTRL1_SDIO_BLK_MODE; #endif /* enable data transfer which will be pended until cmd is send */ dcon |= FTSDC010_DCR_DATA_EN; writel(dcon, &host->reg->dcr); return 0; } static int ftsdc010_send_request(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { int ret; if (data) { ret = ftsdc010_setup_data(mmc, data); if (ret) { printf("%s: setup data error\n", __func__); return -1; } if ((data->flags & MMC_DATA_BOTH_DIR) == MMC_DATA_BOTH_DIR) { printf("%s: data is both direction\n", __func__); return -1; } } /* Send command */ ret = ftsdc010_send_cmd(mmc, cmd, data); return ret; } static int ftsdc010_card_detect(struct mmc *mmc) { struct mmc_host *host = mmc->priv; unsigned int sta; sta = readl(&host->reg->status); debug("%s: card status: %08x\n", __func__, sta); return (sta & FTSDC010_STATUS_CARD_DETECT) ? 0 : 1; } static int ftsdc010_request(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { int ret; if (ftsdc010_card_detect(mmc) == 0) { printf("%s: no medium present\n", __func__); return -1; } else { ret = ftsdc010_send_request(mmc, cmd, data); return ret; } } static void ftsdc010_set_clk(struct mmc *mmc) { struct mmc_host *host = mmc->priv; unsigned char clk_div; unsigned int real_rate; unsigned int clock; debug("%s: mmc_set_clock: %x\n", __func__, mmc->clock); clock = readl(&host->reg->ccr); if (mmc->clock == 0) { real_rate = 0; clock |= FTSDC010_CCR_CLK_DIS; } else { debug("%s, mmc->clock: %08x, origin clock: %08x\n", __func__, mmc->clock, clock); for (clk_div = 0; clk_div <= 127; clk_div++) { real_rate = (CONFIG_SYS_CLK_FREQ / 2) / (2 * (clk_div + 1)); if (real_rate <= mmc->clock) break; } debug("%s: computed real_rate: %x, clk_div: %x\n", __func__, real_rate, clk_div); if (clk_div > 127) debug("%s: no match clock rate, %x\n", __func__, mmc->clock); clock = (clock & ~FTSDC010_CCR_CLK_DIV(0x7f)) | FTSDC010_CCR_CLK_DIV(clk_div); clock &= ~FTSDC010_CCR_CLK_DIS; } debug("%s, set clock: %08x\n", __func__, clock); writel(clock, &host->reg->ccr); } static void ftsdc010_set_ios(struct mmc *mmc) { struct mmc_host *host = mmc->priv; unsigned int power; unsigned long val; unsigned int bus_width; debug("%s: bus_width: %x, clock: %d\n", __func__, mmc->bus_width, mmc->clock); /* set pcr: power on */ power = readl(&host->reg->pcr); power |= FTSDC010_PCR_POWER_ON; writel(power, &host->reg->pcr); if (mmc->clock) ftsdc010_set_clk(mmc); /* set bwr: bus width reg */ bus_width = readl(&host->reg->bwr); bus_width &= ~(FTSDC010_BWR_WIDE_8_BUS | FTSDC010_BWR_WIDE_4_BUS | FTSDC010_BWR_SINGLE_BUS); if (mmc->bus_width == 8) bus_width |= FTSDC010_BWR_WIDE_8_BUS; else if (mmc->bus_width == 4) bus_width |= FTSDC010_BWR_WIDE_4_BUS; else bus_width |= FTSDC010_BWR_SINGLE_BUS; writel(bus_width, &host->reg->bwr); /* set fifo depth */ val = readl(&host->reg->feature); host->fifo_len = FTSDC010_FEATURE_FIFO_DEPTH(val) * 4; /* 4 bytes */ /* set data timeout register */ val = -1; writel(val, &host->reg->dtr); } static void ftsdc010_reset(struct mmc_host *host) { unsigned int timeout; unsigned int sta; /* Do SDC_RST: Software reset for all register */ writel(FTSDC010_CMD_SDC_RST, &host->reg->cmd); host->clock = 0; /* this hardware has no reset finish flag to read */ /* wait 100ms maximum */ timeout = 100; /* hw clears the bit when it's done */ while (readl(&host->reg->dtr) != 0) { if (timeout == 0) { printf("%s: reset timeout error\n", __func__); return; } timeout--; udelay(10*FTSDC010_DELAY_UNIT); } sta = readl(&host->reg->status); if (sta & FTSDC010_STATUS_CARD_CHANGE) writel(FTSDC010_CLR_CARD_CHANGE, &host->reg->clr); } static int ftsdc010_core_init(struct mmc *mmc) { struct mmc_host *host = mmc->priv; unsigned int mask; unsigned int major, minor, revision; /* get hardware version */ host->version = readl(&host->reg->rev); major = FTSDC010_REV_MAJOR(host->version); minor = FTSDC010_REV_MINOR(host->version); revision = FTSDC010_REV_REVISION(host->version); printf("ftsdc010 hardware ver: %d_%d_r%d\n", major, minor, revision); /* Interrupt MASK register init - mask all */ writel(0x0, &host->reg->int_mask); mask = FTSDC010_INT_MASK_CMD_SEND | FTSDC010_INT_MASK_DATA_END | FTSDC010_INT_MASK_CARD_CHANGE; #ifdef CONFIG_FTSDC010_SDIO mask |= FTSDC010_INT_MASK_CP_READY | FTSDC010_INT_MASK_CP_BUF_READY | FTSDC010_INT_MASK_PLAIN_TEXT_READY | FTSDC010_INT_MASK_SDIO_IRPT; #endif writel(mask, &host->reg->int_mask); return 0; } int ftsdc010_mmc_init(int dev_index) { struct mmc *mmc; struct mmc_host *host; mmc = &ftsdc010_dev[dev_index]; sprintf(mmc->name, "FTSDC010 SD/MMC"); mmc->priv = &ftsdc010_host[dev_index]; mmc->send_cmd = ftsdc010_request; mmc->set_ios = ftsdc010_set_ios; mmc->init = ftsdc010_core_init; mmc->getcd = NULL; mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; mmc->f_min = CONFIG_SYS_CLK_FREQ / 2 / (2*128); mmc->f_max = CONFIG_SYS_CLK_FREQ / 2 / 2; ftsdc010_host[dev_index].clock = 0; ftsdc010_host[dev_index].reg = ftsdc010_get_base_mmc(dev_index); mmc_register(mmc); /* reset mmc */ host = (struct mmc_host *)mmc->priv; ftsdc010_reset(host); return 0; }