diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2010-10-09 11:41:42 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2010-10-11 13:10:44 +0200 |
commit | 84c7dc4df268b72c26488d9698c1e6cff08341d9 (patch) | |
tree | 9ed3866a9ab6ffeb9b865ba652a5561793fd4c08 /drivers/mci/imx-esdhc.c | |
parent | e1cd8358d51cbffb6cbd178353c5c820c3c7dec6 (diff) | |
download | barebox-84c7dc4df268b72c26488d9698c1e6cff08341d9.tar.gz barebox-84c7dc4df268b72c26488d9698c1e6cff08341d9.tar.xz |
mci: Add i.MX esdhc support
This adds a driver for the esdhc controller found on Freescale
i.MX25/35/51 SoCs.
This code is based on the U-Boot driver.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mci/imx-esdhc.c')
-rw-r--r-- | drivers/mci/imx-esdhc.c | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/drivers/mci/imx-esdhc.c b/drivers/mci/imx-esdhc.c new file mode 100644 index 0000000000..63cd059f6d --- /dev/null +++ b/drivers/mci/imx-esdhc.c @@ -0,0 +1,512 @@ +/* + * Copyright 2007,2010 Freescale Semiconductor, Inc + * Andy Fleming + * + * Based vaguely on the pxa mmc code: + * (C) Copyright 2003 + * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net + * + * 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 <config.h> +#include <common.h> +#include <driver.h> +#include <init.h> +#include <malloc.h> +#include <mci.h> +#include <clock.h> +#include <asm/io.h> +#include <asm/mmu.h> +#include <mach/clock.h> + +#include "imx-esdhc.h" + +struct fsl_esdhc { + u32 dsaddr; + u32 blkattr; + u32 cmdarg; + u32 xfertyp; + u32 cmdrsp0; + u32 cmdrsp1; + u32 cmdrsp2; + u32 cmdrsp3; + u32 datport; + u32 prsstat; + u32 proctl; + u32 sysctl; + u32 irqstat; + u32 irqstaten; + u32 irqsigen; + u32 autoc12err; + u32 hostcapblt; + u32 wml; + char reserved1[8]; + u32 fevt; + char reserved2[168]; + u32 hostver; + char reserved3[780]; + u32 scr; +}; + +struct fsl_esdhc_host { + struct mci_host mci; + struct fsl_esdhc *regs; + u32 no_snoop; + unsigned long cur_clock; + struct device_d *dev; +}; + +#define to_fsl_esdhc(mci) container_of(mci, struct fsl_esdhc_host, mci) + +/* Return the XFERTYP flags for a given command and data packet */ +u32 esdhc_xfertyp(struct mci_cmd *cmd, struct mci_data *data) +{ + u32 xfertyp = 0; + + if (data) { + xfertyp |= XFERTYP_DPSEL; +#ifndef CONFIG_MCI_IMX_ESDHC_PIO + xfertyp |= XFERTYP_DMAEN; +#endif + if (data->blocks > 1) { + xfertyp |= XFERTYP_MSBSEL; + xfertyp |= XFERTYP_BCEN; + } + + if (data->flags & MMC_DATA_READ) + xfertyp |= XFERTYP_DTDSEL; + } + + if (cmd->resp_type & MMC_RSP_CRC) + xfertyp |= XFERTYP_CCCEN; + if (cmd->resp_type & MMC_RSP_OPCODE) + xfertyp |= XFERTYP_CICEN; + if (cmd->resp_type & MMC_RSP_136) + xfertyp |= XFERTYP_RSPTYP_136; + else if (cmd->resp_type & MMC_RSP_BUSY) + xfertyp |= XFERTYP_RSPTYP_48_BUSY; + else if (cmd->resp_type & MMC_RSP_PRESENT) + xfertyp |= XFERTYP_RSPTYP_48; + + return XFERTYP_CMD(cmd->cmdidx) | xfertyp; +} + +#ifdef CONFIG_MCI_IMX_ESDHC_PIO +/* + * PIO Read/Write Mode reduce the performace as DMA is not used in this mode. + */ +static void +esdhc_pio_read_write(struct mci_host *mci, struct mci_data *data) +{ + struct fsl_esdhc_host *host = to_fsl_esdhc(mci); + struct fsl_esdhc *regs = host->regs; + u32 blocks; + char *buffer; + u32 databuf; + u32 size; + u32 irqstat; + u32 timeout; + + if (data->flags & MMC_DATA_READ) { + blocks = data->blocks; + buffer = data->dest; + while (blocks) { + timeout = PIO_TIMEOUT; + size = data->blocksize; + irqstat = esdhc_read32(®s->irqstat); + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BREN) + && --timeout); + if (timeout <= 0) { + printf("\nData Read Failed in PIO Mode."); + return; + } + while (size && (!(irqstat & IRQSTAT_TC))) { + udelay(100); /* Wait before last byte transfer complete */ + irqstat = esdhc_read32(®s->irqstat); + databuf = esdhc_read32(®s->datport); + *((u32 *)buffer) = databuf; + buffer += 4; + size -= 4; + } + blocks--; + } + } else { + blocks = data->blocks; + buffer = (char *)data->src; + while (blocks) { + timeout = PIO_TIMEOUT; + size = data->blocksize; + irqstat = esdhc_read32(®s->irqstat); + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BWEN) + && --timeout); + if (timeout <= 0) { + printf("\nData Write Failed in PIO Mode."); + return; + } + while (size && (!(irqstat & IRQSTAT_TC))) { + udelay(100); /* Wait before last byte transfer complete */ + databuf = *((u32 *)buffer); + buffer += 4; + size -= 4; + irqstat = esdhc_read32(®s->irqstat); + esdhc_write32(®s->datport, databuf); + } + blocks--; + } + } +} +#endif + +static int esdhc_setup_data(struct mci_host *mci, struct mci_data *data) +{ + struct fsl_esdhc_host *host = to_fsl_esdhc(mci); + struct fsl_esdhc *regs = host->regs; +#ifndef CONFIG_MCI_IMX_ESDHC_PIO + u32 wml_value; + + wml_value = data->blocksize/4; + + if (data->flags & MMC_DATA_READ) { + if (wml_value > 0x10) + wml_value = 0x10; + + esdhc_clrsetbits32(®s->wml, WML_RD_WML_MASK, wml_value); + esdhc_write32(®s->dsaddr, (u32)data->dest); + } else { + if (wml_value > 0x80) + wml_value = 0x80; + if ((esdhc_read32(®s->prsstat) & PRSSTAT_WPSPL) == 0) { + printf("\nThe SD card is locked. Can not write to a locked card.\n\n"); + return -ETIMEDOUT; + } + + esdhc_clrsetbits32(®s->wml, WML_WR_WML_MASK, + wml_value << 16); + esdhc_write32(®s->dsaddr, (u32)data->src); + } +#else /* CONFIG_MCI_IMX_ESDHC_PIO */ + if (!(data->flags & MMC_DATA_READ)) { + if ((esdhc_read32(®s->prsstat) & PRSSTAT_WPSPL) == 0) { + printf("\nThe SD card is locked. " + "Can not write to a locked card.\n\n"); + return -ETIMEDOUT; + } + esdhc_write32(®s->dsaddr, (u32)data->src); + } else + esdhc_write32(®s->dsaddr, (u32)data->dest); +#endif /* CONFIG_MCI_IMX_ESDHC_PIO */ + + esdhc_write32(®s->blkattr, data->blocks << 16 | data->blocksize); + + return 0; +} + + +/* + * Sends a command out on the bus. Takes the mci pointer, + * a command pointer, and an optional data pointer. + */ +static int +esdhc_send_cmd(struct mci_host *mci, struct mci_cmd *cmd, struct mci_data *data) +{ + u32 xfertyp; + u32 irqstat; + struct fsl_esdhc_host *host = to_fsl_esdhc(mci); + struct fsl_esdhc *regs = host->regs; + + esdhc_write32(®s->irqstat, -1); + + /* Wait for the bus to be idle */ + while ((esdhc_read32(®s->prsstat) & PRSSTAT_CICHB) || + (esdhc_read32(®s->prsstat) & PRSSTAT_CIDHB)) + ; + + while (esdhc_read32(®s->prsstat) & PRSSTAT_DLA) + ; + + /* Wait at least 8 SD clock cycles before the next command */ + /* + * Note: This is way more than 8 cycles, but 1ms seems to + * resolve timing issues with some cards + */ + udelay(1000); + + /* Set up for a data transfer if we have one */ + if (data) { + int err; + + err = esdhc_setup_data(mci, data); + if(err) + return err; + if (data->flags & MMC_DATA_WRITE) { + dma_flush_range((unsigned long)data->src, + (unsigned long)(data->src + 512)); + } else + dma_clean_range((unsigned long)data->src, + (unsigned long)(data->src + 512)); + + } + + /* Figure out the transfer arguments */ + xfertyp = esdhc_xfertyp(cmd, data); + + /* Send the command */ + esdhc_write32(®s->cmdarg, cmd->cmdarg); + esdhc_write32(®s->xfertyp, xfertyp); + + /* Wait for the command to complete */ + while (!(esdhc_read32(®s->irqstat) & IRQSTAT_CC)) + ; + + irqstat = esdhc_read32(®s->irqstat); + esdhc_write32(®s->irqstat, irqstat); + + if (irqstat & CMD_ERR) + return -EIO; + + if (irqstat & IRQSTAT_CTOE) + return -ETIMEDOUT; + + /* Copy the response to the response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + u32 cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0; + + cmdrsp3 = esdhc_read32(®s->cmdrsp3); + cmdrsp2 = esdhc_read32(®s->cmdrsp2); + cmdrsp1 = esdhc_read32(®s->cmdrsp1); + cmdrsp0 = esdhc_read32(®s->cmdrsp0); + cmd->response[0] = (cmdrsp3 << 8) | (cmdrsp2 >> 24); + cmd->response[1] = (cmdrsp2 << 8) | (cmdrsp1 >> 24); + cmd->response[2] = (cmdrsp1 << 8) | (cmdrsp0 >> 24); + cmd->response[3] = (cmdrsp0 << 8); + } else + cmd->response[0] = esdhc_read32(®s->cmdrsp0); + + /* Wait until all of the blocks are transferred */ + if (data) { +#ifdef CONFIG_MCI_IMX_ESDHC_PIO + esdhc_pio_read_write(mci, data); +#else + do { + irqstat = esdhc_read32(®s->irqstat); + + if (irqstat & DATA_ERR) + return -EIO; + + if (irqstat & IRQSTAT_DTOE) + return -ETIMEDOUT; + } while (!(irqstat & IRQSTAT_TC) && + (esdhc_read32(®s->prsstat) & PRSSTAT_DLA)); + + if (data->flags & MMC_DATA_READ) { + dma_inv_range((unsigned long)data->dest, + (unsigned long)(data->dest + 512)); + } +#endif + } + + esdhc_write32(®s->irqstat, -1); + + return 0; +} + +void set_sysctl(struct mci_host *mci, u32 clock) +{ + int div, pre_div; + struct fsl_esdhc_host *host = to_fsl_esdhc(mci); + struct fsl_esdhc *regs = host->regs; + int sdhc_clk = imx_get_mmcclk(); + u32 clk; + + if (clock < mci->f_min) + clock = mci->f_min; + + pre_div = 0; + + for (pre_div = 1; pre_div < 256; pre_div <<= 1) { + if (sdhc_clk / pre_div < clock * 16) + break; + }; + + div = sdhc_clk / pre_div / clock; + + if (sdhc_clk / pre_div / div > clock) + div++; + + host->cur_clock = sdhc_clk / pre_div / div; + + div -= 1; + pre_div >>= 1; + + dev_dbg(host->dev, "set clock: wanted: %d got: %d\n", clock, host->cur_clock); + dev_dbg(host->dev, "pre_div: %d div: %d\n", pre_div, div); + + clk = (pre_div << 8) | (div << 4); + + esdhc_clrbits32(®s->sysctl, SYSCTL_CKEN); + + esdhc_clrsetbits32(®s->sysctl, SYSCTL_CLOCK_MASK, clk); + + udelay(10000); + + clk = SYSCTL_PEREN | SYSCTL_CKEN; + + esdhc_setbits32(®s->sysctl, clk); +} + +static void esdhc_set_ios(struct mci_host *mci, struct device_d *dev, + unsigned bus_width, unsigned clock) +{ + struct fsl_esdhc_host *host = to_fsl_esdhc(mci); + struct fsl_esdhc *regs = host->regs; + + /* Set the clock speed */ + set_sysctl(mci, clock); + + /* Set the bus width */ + esdhc_clrbits32(®s->proctl, PROCTL_DTW_4 | PROCTL_DTW_8); + + if (bus_width == 4) + esdhc_setbits32(®s->proctl, PROCTL_DTW_4); + else if (bus_width == 8) + esdhc_setbits32(®s->proctl, PROCTL_DTW_8); + +} + +static int esdhc_init(struct mci_host *mci, struct device_d *dev) +{ + struct fsl_esdhc_host *host = to_fsl_esdhc(mci); + struct fsl_esdhc *regs = host->regs; + int timeout = 1000; + int ret = 0; + + /* Enable cache snooping */ + if (host && !host->no_snoop) + esdhc_write32(®s->scr, 0x00000040); + + /* Reset the entire host controller */ + esdhc_write32(®s->sysctl, SYSCTL_RSTA); + + /* Wait until the controller is available */ + while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA) && --timeout) + udelay(1000); + + esdhc_write32(®s->sysctl, SYSCTL_HCKEN | SYSCTL_IPGEN); + + /* Set the initial clock speed */ + set_sysctl(mci, 400000); + + /* Disable the BRR and BWR bits in IRQSTAT */ + esdhc_clrbits32(®s->irqstaten, IRQSTATEN_BRR | IRQSTATEN_BWR); + + /* Put the PROCTL reg back to the default */ + esdhc_write32(®s->proctl, PROCTL_INIT); + + /* Set timout to the maximum value */ + esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, 14 << 16); + + return ret; +} + +static int esdhc_reset(struct fsl_esdhc *regs) +{ + uint64_t start; + + /* reset the controller */ + esdhc_write32(®s->sysctl, SYSCTL_RSTA); + + start = get_time_ns(); + /* hardware clears the bit when it is done */ + while (1) { + if (!(esdhc_read32(®s->sysctl) & SYSCTL_RSTA)) + break; + if (is_timeout(start, 100 * MSECOND)) { + printf("MMC/SD: Reset never completed.\n"); + return -EIO; + } + } + + return 0; +} + +static int fsl_esdhc_probe(struct device_d *dev) +{ + struct fsl_esdhc_host *host; + struct mci_host *mci; + u32 caps; + int ret; + + host = xzalloc(sizeof(*host)); + mci = &host->mci; + + host->dev = dev; + host->regs = (struct fsl_esdhc *)dev->map_base; + + /* First reset the eSDHC controller */ + ret = esdhc_reset(host->regs); + if (ret) { + free(host); + return ret; + } + + caps = esdhc_read32(&host->regs->hostcapblt); + + if (caps & ESDHC_HOSTCAPBLT_VS18) + mci->voltages |= MMC_VDD_165_195; + if (caps & ESDHC_HOSTCAPBLT_VS30) + mci->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & ESDHC_HOSTCAPBLT_VS33) + mci->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + + mci->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; + + if (caps & ESDHC_HOSTCAPBLT_HSS) + mci->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + + host->mci.send_cmd = esdhc_send_cmd; + host->mci.set_ios = esdhc_set_ios; + host->mci.init = esdhc_init; + host->mci.host_caps = MMC_MODE_4BIT; + + host->mci.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + host->mci.f_min = imx_get_mmcclk() >> 12; + if (host->mci.f_min < 200000) + host->mci.f_min = 200000; + host->mci.f_max = imx_get_mmcclk(); + + mci_register(&host->mci); + + return 0; +} + +static struct driver_d fsl_esdhc_driver = { + .name = "imx-esdhc", + .probe = fsl_esdhc_probe, +}; + +static int fsl_esdhc_init_driver(void) +{ + register_driver(&fsl_esdhc_driver); + return 0; +} + +device_initcall(fsl_esdhc_init_driver); + |