diff options
author | Marek Vasut <marek.vasut@gmail.com> | 2011-11-02 00:29:27 +0000 |
---|---|---|
committer | Andy Fleming <afleming@freescale.com> | 2011-11-03 02:14:58 -0500 |
commit | 07133f2e7b671866bcf91ef6926c187c392c7aa1 (patch) | |
tree | df5ce537d29da15c104d2ba99a9c79ccf1e7bec1 /drivers | |
parent | ce5207e191c59b3135303fd03b98dd2ac3701ba2 (diff) | |
download | u-boot-imx-07133f2e7b671866bcf91ef6926c187c392c7aa1.zip u-boot-imx-07133f2e7b671866bcf91ef6926c187c392c7aa1.tar.gz u-boot-imx-07133f2e7b671866bcf91ef6926c187c392c7aa1.tar.bz2 |
PXA: Add MMC driver using the generic MMC framework
Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/pxa_mmc_gen.c | 442 |
2 files changed, 443 insertions, 0 deletions
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6e94860..9f9db75 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -38,6 +38,7 @@ COBJS-$(CONFIG_MV_SDHCI) += mv_sdhci.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o COBJS-$(CONFIG_OMAP_HSMMC) += omap_hsmmc.o COBJS-$(CONFIG_PXA_MMC) += pxa_mmc.o +COBJS-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o COBJS-$(CONFIG_S5P_MMC) += s5p_mmc.o COBJS-$(CONFIG_SDHCI) += sdhci.o COBJS-$(CONFIG_SH_MMCIF) += sh_mmcif.o diff --git a/drivers/mmc/pxa_mmc_gen.c b/drivers/mmc/pxa_mmc_gen.c new file mode 100644 index 0000000..28e37b4 --- /dev/null +++ b/drivers/mmc/pxa_mmc_gen.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com> + * + * Loosely based on the old code and Linux's PXA MMC driver + * + * 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 <config.h> +#include <common.h> +#include <malloc.h> + +#include <mmc.h> +#include <asm/errno.h> +#include <asm/arch/hardware.h> +#include <asm/arch/regs-mmc.h> +#include <asm/io.h> + +/* PXAMMC Generic default config for various CPUs */ +#if defined(CONFIG_PXA250) +#define PXAMMC_FIFO_SIZE 1 +#define PXAMMC_MIN_SPEED 312500 +#define PXAMMC_MAX_SPEED 20000000 +#define PXAMMC_HOST_CAPS (0) +#elif defined(CONFIG_PXA27X) +#define PXAMMC_CRC_SKIP +#define PXAMMC_FIFO_SIZE 32 +#define PXAMMC_MIN_SPEED 304000 +#define PXAMMC_MAX_SPEED 19500000 +#define PXAMMC_HOST_CAPS (MMC_MODE_4BIT) +#elif defined(CONFIG_CPU_MONAHANS) +#define PXAMMC_FIFO_SIZE 32 +#define PXAMMC_MIN_SPEED 304000 +#define PXAMMC_MAX_SPEED 26000000 +#define PXAMMC_HOST_CAPS (MMC_MODE_4BIT | MMC_MODE_HS) +#else +#error "This CPU isn't supported by PXA MMC!" +#endif + +#define MMC_STAT_ERRORS \ + (MMC_STAT_RES_CRC_ERROR | MMC_STAT_SPI_READ_ERROR_TOKEN | \ + MMC_STAT_CRC_READ_ERROR | MMC_STAT_TIME_OUT_RESPONSE | \ + MMC_STAT_READ_TIME_OUT | MMC_STAT_CRC_WRITE_ERROR) + +/* 1 millisecond (in wait cycles below it's 100 x 10uS waits) */ +#define PXA_MMC_TIMEOUT 100 + +struct pxa_mmc_priv { + struct pxa_mmc_regs *regs; +}; + +/* Wait for bit to be set */ +static int pxa_mmc_wait(struct mmc *mmc, uint32_t mask) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + unsigned int timeout = PXA_MMC_TIMEOUT; + + /* Wait for bit to be set */ + while (--timeout) { + if (readl(®s->stat) & mask) + break; + udelay(10); + } + + if (!timeout) + return -ETIMEDOUT; + + return 0; +} + +static int pxa_mmc_stop_clock(struct mmc *mmc) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + unsigned int timeout = PXA_MMC_TIMEOUT; + + /* If the clock aren't running, exit */ + if (!(readl(®s->stat) & MMC_STAT_CLK_EN)) + return 0; + + /* Tell the controller to turn off the clock */ + writel(MMC_STRPCL_STOP_CLK, ®s->strpcl); + + /* Wait until the clock are off */ + while (--timeout) { + if (!(readl(®s->stat) & MMC_STAT_CLK_EN)) + break; + udelay(10); + } + + /* The clock refused to stop, scream and die a painful death */ + if (!timeout) + return -ETIMEDOUT; + + /* The clock stopped correctly */ + return 0; +} + +static int pxa_mmc_start_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + uint32_t cmdat) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + int ret; + + /* The card can send a "busy" response */ + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= MMC_CMDAT_BUSY; + + /* Inform the controller about response type */ + switch (cmd->resp_type) { + case MMC_RSP_R1: + case MMC_RSP_R1b: + cmdat |= MMC_CMDAT_R1; + break; + case MMC_RSP_R2: + cmdat |= MMC_CMDAT_R2; + break; + case MMC_RSP_R3: + cmdat |= MMC_CMDAT_R3; + break; + default: + break; + } + + /* Load command and it's arguments into the controller */ + writel(cmd->cmdidx, ®s->cmd); + writel(cmd->cmdarg >> 16, ®s->argh); + writel(cmd->cmdarg & 0xffff, ®s->argl); + writel(cmdat, ®s->cmdat); + + /* Start the controller clock and wait until they are started */ + writel(MMC_STRPCL_START_CLK, ®s->strpcl); + + ret = pxa_mmc_wait(mmc, MMC_STAT_CLK_EN); + if (ret) + return ret; + + /* Correct and happy end */ + return 0; +} + +static int pxa_mmc_cmd_done(struct mmc *mmc, struct mmc_cmd *cmd) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + uint32_t a, b, c; + int i; + int stat; + + /* Read the controller status */ + stat = readl(®s->stat); + + /* + * Linux says: + * Did I mention this is Sick. We always need to + * discard the upper 8 bits of the first 16-bit word. + */ + a = readl(®s->res) & 0xffff; + for (i = 0; i < 4; i++) { + b = readl(®s->res) & 0xffff; + c = readl(®s->res) & 0xffff; + cmd->response[i] = (a << 24) | (b << 8) | (c >> 8); + a = c; + } + + /* The command response didn't arrive */ + if (stat & MMC_STAT_TIME_OUT_RESPONSE) + return -ETIMEDOUT; + else if (stat & MMC_STAT_RES_CRC_ERROR && cmd->flags & MMC_RSP_CRC) { +#ifdef PXAMMC_CRC_SKIP + if (cmd->flags & MMC_RSP_136 && cmd->response[0] & (1 << 31)) + printf("Ignoring CRC, this may be dangerous!\n"); + else +#endif + return -EILSEQ; + } + + /* The command response was successfully read */ + return 0; +} + +static int pxa_mmc_do_read_xfer(struct mmc *mmc, struct mmc_data *data) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + uint32_t len; + uint32_t *buf = (uint32_t *)data->dest; + int size; + int ret; + + len = data->blocks * data->blocksize; + + while (len) { + /* The controller has data ready */ + if (readl(®s->i_reg) & MMC_I_REG_RXFIFO_RD_REQ) { + size = min(len, PXAMMC_FIFO_SIZE); + len -= size; + size /= 4; + + /* Read data into the buffer */ + while (size--) + *buf++ = readl(®s->rxfifo); + + } + + if (readl(®s->stat) & MMC_STAT_ERRORS) + return -EIO; + } + + /* Wait for the transmission-done interrupt */ + ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE); + if (ret) + return ret; + + return 0; +} + +static int pxa_mmc_do_write_xfer(struct mmc *mmc, struct mmc_data *data) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + uint32_t len; + uint32_t *buf = (uint32_t *)data->src; + int size; + int ret; + + len = data->blocks * data->blocksize; + + while (len) { + /* The controller is ready to receive data */ + if (readl(®s->i_reg) & MMC_I_REG_TXFIFO_WR_REQ) { + size = min(len, PXAMMC_FIFO_SIZE); + len -= size; + size /= 4; + + while (size--) + writel(*buf++, ®s->txfifo); + + if (min(len, PXAMMC_FIFO_SIZE) < 32) + writel(MMC_PRTBUF_BUF_PART_FULL, ®s->prtbuf); + } + + if (readl(®s->stat) & MMC_STAT_ERRORS) + return -EIO; + } + + /* Wait for the transmission-done interrupt */ + ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE); + if (ret) + return ret; + + /* Wait until the data are really written to the card */ + ret = pxa_mmc_wait(mmc, MMC_STAT_PRG_DONE); + if (ret) + return ret; + + return 0; +} + +static int pxa_mmc_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + uint32_t cmdat = 0; + int ret; + + /* Stop the controller */ + ret = pxa_mmc_stop_clock(mmc); + if (ret) + return ret; + + /* If we're doing data transfer, configure the controller accordingly */ + if (data) { + writel(data->blocks, ®s->nob); + writel(data->blocksize, ®s->blklen); + /* This delay can be optimized, but stick with max value */ + writel(0xffff, ®s->rdto); + cmdat |= MMC_CMDAT_DATA_EN; + if (data->flags & MMC_DATA_WRITE) + cmdat |= MMC_CMDAT_WRITE; + } + + /* Run in 4bit mode if the card can do it */ + if (mmc->bus_width == 4) + cmdat |= MMC_CMDAT_SD_4DAT; + + /* Execute the command */ + ret = pxa_mmc_start_cmd(mmc, cmd, cmdat); + if (ret) + return ret; + + /* Wait until the command completes */ + ret = pxa_mmc_wait(mmc, MMC_STAT_END_CMD_RES); + if (ret) + return ret; + + /* Read back the result */ + ret = pxa_mmc_cmd_done(mmc, cmd); + if (ret) + return ret; + + /* In case there was a data transfer scheduled, do it */ + if (data) { + if (data->flags & MMC_DATA_WRITE) + pxa_mmc_do_write_xfer(mmc, data); + else + pxa_mmc_do_read_xfer(mmc, data); + } + + return 0; +} + +static void pxa_mmc_set_ios(struct mmc *mmc) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + uint32_t tmp; + uint32_t pxa_mmc_clock; + + if (!mmc->clock) { + pxa_mmc_stop_clock(mmc); + return; + } + + /* PXA3xx can do 26MHz with special settings. */ + if (mmc->clock == 26000000) { + writel(0x7, ®s->clkrt); + return; + } + + /* Set clock to the card the usual way. */ + pxa_mmc_clock = 0; + tmp = mmc->f_max / mmc->clock; + tmp += tmp % 2; + + while (tmp > 1) { + pxa_mmc_clock++; + tmp >>= 1; + } + + writel(pxa_mmc_clock, ®s->clkrt); +} + +static int pxa_mmc_init(struct mmc *mmc) +{ + struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + + /* Make sure the clock are stopped */ + pxa_mmc_stop_clock(mmc); + + /* Turn off SPI mode */ + writel(0, ®s->spi); + + /* Set up maximum timeout to wait for command response */ + writel(MMC_RES_TO_MAX_MASK, ®s->resto); + + /* Mask all interrupts */ + writel(~(MMC_I_MASK_TXFIFO_WR_REQ | MMC_I_MASK_RXFIFO_RD_REQ), + ®s->i_mask); + return 0; +} + +int pxa_mmc_register(int card_index) +{ + struct mmc *mmc; + struct pxa_mmc_priv *priv; + uint32_t reg; + int ret = -ENOMEM; + + mmc = malloc(sizeof(struct mmc)); + if (!mmc) + goto err0; + + priv = malloc(sizeof(struct pxa_mmc_priv)); + if (!priv) + goto err1; + + switch (card_index) { + case 0: + priv->regs = (struct pxa_mmc_regs *)MMC0_BASE; + break; + case 1: + priv->regs = (struct pxa_mmc_regs *)MMC1_BASE; + break; + default: + printf("PXA MMC: Invalid MMC controller ID (card_index = %d)\n", + card_index); + goto err2; + } + + mmc->priv = priv; + + sprintf(mmc->name, "PXA MMC"); + mmc->send_cmd = pxa_mmc_request; + mmc->set_ios = pxa_mmc_set_ios; + mmc->init = pxa_mmc_init; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = PXAMMC_MAX_SPEED; + mmc->f_min = PXAMMC_MIN_SPEED; + mmc->host_caps = PXAMMC_HOST_CAPS; + + mmc->b_max = 0; + +#ifndef CONFIG_CPU_MONAHANS /* PXA2xx */ + reg = readl(CKEN); + reg |= CKEN12_MMC; + writel(reg, CKEN); +#else /* PXA3xx */ + reg = readl(CKENA); + reg |= CKENA_12_MMC0 | CKENA_13_MMC1; + writel(reg, CKENA); +#endif + + mmc_register(mmc); + + return 0; + +err2: + free(priv); +err1: + free(mmc); +err0: + return ret; +} |