summaryrefslogtreecommitdiffstats
path: root/drivers/mci/pxamci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mci/pxamci.c')
-rw-r--r--drivers/mci/pxamci.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/drivers/mci/pxamci.c b/drivers/mci/pxamci.c
new file mode 100644
index 0000000000..75b61f07b8
--- /dev/null
+++ b/drivers/mci/pxamci.c
@@ -0,0 +1,369 @@
+/*
+ * PXA MCI driver
+ *
+ * Copyright (C) 2011 Robert Jarzmik
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Insprired by linux kernel driver
+ */
+
+#include <common.h>
+#include <errno.h>
+#include <io.h>
+#include <gpio.h>
+#include <clock.h>
+#include <init.h>
+#include <mci.h>
+
+#include <mach/clock.h>
+#include <mach/mci_pxa2xx.h>
+#include <mach/pxa-regs.h>
+#include "pxamci.h"
+
+#define DRIVER_NAME "pxa-mmc"
+
+static void clk_enable(void)
+{
+ CKEN |= CKEN_MMC;
+}
+
+static int pxamci_set_power(struct pxamci_host *host, int on)
+{
+ mci_dbg("on=%d\n", on);
+ if (host->pdata && host->pdata->gpio_power > 0)
+ gpio_set_value(host->pdata->gpio_power,
+ !!on ^ host->pdata->gpio_power_invert);
+ else if (host->pdata && host->pdata->setpower)
+ host->pdata->setpower(&host->mci, on);
+ return 0;
+}
+
+static void pxamci_start_clock(struct pxamci_host *host)
+{
+ mmc_writel(START_CLOCK, MMC_STRPCL);
+}
+
+static void pxamci_stop_clock(struct pxamci_host *host)
+{
+ uint64_t start = get_time_ns();
+ unsigned stat;
+
+ stat = mmc_readl(MMC_STAT);
+ if (stat & STAT_CLK_EN)
+ writel(STOP_CLOCK, host->base + MMC_STRPCL);
+ while (!is_timeout(start, 10 * MSECOND) && stat & STAT_CLK_EN)
+ stat = mmc_readl(MMC_STAT);
+
+ if (stat & STAT_CLK_EN)
+ mci_err("unable to stop clock\n");
+}
+
+static void pxamci_setup_data(struct pxamci_host *host, struct mci_data *data)
+{
+ static const unsigned int timeout = 100000000; /* 10ms */
+
+ mci_dbg("nbblocks=%d, blocksize=%d\n", data->blocks, data->blocksize);
+ mmc_writel(data->blocks, MMC_NOB);
+ mmc_writel(data->blocksize, MMC_BLKLEN);
+ mmc_writel((timeout + 255) / 256, MMC_RDTO);
+}
+
+static int pxamci_read_data(struct pxamci_host *host, unsigned char *dst,
+ unsigned len)
+{
+ int trf_len, trf_len1, trf_len4, ret = 0;
+ uint64_t start;
+ u32 *dst4;
+
+ mci_dbg("dst=%p, len=%u\n", dst, len);
+ while (!ret && len > 0) {
+ trf_len = min_t(int, len, MMC_FIFO_LENGTH);
+
+ for (start = get_time_ns(), ret = -ETIMEDOUT;
+ ret && !is_timeout(start, 10 * MSECOND);)
+ if (mmc_readl(MMC_I_REG) & RXFIFO_RD_REQ)
+ ret = 0;
+ trf_len1 = trf_len % 4;
+ trf_len4 = trf_len / 4;
+ for (dst4 = (u32 *)dst; !ret && trf_len4 > 0; trf_len4--)
+ *dst4++ = mmc_readl(MMC_RXFIFO);
+ for (dst = (u8 *)dst4; !ret && trf_len1 > 0; trf_len1--)
+ *dst++ = mmc_readb(MMC_RXFIFO);
+ len -= trf_len;
+ }
+
+ if (!ret)
+ for (start = get_time_ns(), ret = -ETIMEDOUT;
+ ret && !is_timeout(start, 10 * MSECOND);)
+ if (mmc_readl(MMC_STAT) & STAT_DATA_TRAN_DONE)
+ ret = 0;
+ mci_dbg("ret=%d, remain=%d, stat=%x, mmc_i_reg=%x\n",
+ ret, len, mmc_readl(MMC_STAT), mmc_readl(MMC_I_REG));
+ return ret;
+}
+
+static int pxamci_write_data(struct pxamci_host *host, const unsigned char *src,
+ unsigned len)
+{
+ uint64_t start;
+ int trf_len, partial = 0, ret = 0;
+ unsigned stat;
+
+ mci_dbg("src=%p, len=%u\n", src, len);
+ while (!ret && len > 0) {
+ trf_len = min_t(int, len, MMC_FIFO_LENGTH);
+ partial = trf_len < MMC_FIFO_LENGTH;
+
+ for (start = get_time_ns(), ret = -ETIMEDOUT;
+ ret && !is_timeout(start, 10 * MSECOND);)
+ if (mmc_readl(MMC_I_REG) & TXFIFO_WR_REQ)
+ ret = 0;
+ for (; !ret && trf_len > 0; trf_len--, len--)
+ mmc_writeb(*src++, MMC_TXFIFO);
+ if (partial)
+ mmc_writeb(BUF_PART_FULL, MMC_PRTBUF);
+ }
+
+ if (!ret)
+ for (start = get_time_ns(), ret = -ETIMEDOUT;
+ ret && !is_timeout(start, 100 * MSECOND);) {
+ stat = mmc_readl(MMC_STAT);
+ stat &= STAT_DATA_TRAN_DONE | STAT_PRG_DONE;
+ if (stat == (STAT_DATA_TRAN_DONE | STAT_PRG_DONE))
+ ret = 0;
+ }
+ mci_dbg("ret=%d, remain=%d, stat=%x, mmc_i_reg=%x\n",
+ ret, len, mmc_readl(MMC_STAT), mmc_readl(MMC_I_REG));
+ return ret;
+}
+
+static int pxamci_transfer_data(struct pxamci_host *host,
+ struct mci_data *data)
+{
+ int nbbytes = data->blocks * data->blocksize;
+ int ret;
+ unsigned err_mask = STAT_CRC_READ_ERROR | STAT_CRC_WRITE_ERROR |
+ STAT_READ_TIME_OUT;
+
+ if (data->flags & MMC_DATA_WRITE)
+ ret = pxamci_write_data(host, data->src, nbbytes);
+ else
+ ret = pxamci_read_data(host, data->dest, nbbytes);
+
+ if (!ret && (mmc_readl(MMC_STAT) & err_mask))
+ ret = -EILSEQ;
+ return ret;
+}
+
+static void pxamci_start_cmd(struct pxamci_host *host, struct mci_cmd *cmd,
+ unsigned int cmdat)
+{
+ mci_dbg("cmd=(idx=%d,type=%d,clkrt=%d)\n", cmd->cmdidx, cmd->resp_type,
+ host->clkrt);
+ if (cmd->resp_type & MMC_RSP_BUSY)
+ cmdat |= CMDAT_BUSY;
+
+ switch (cmd->resp_type) {
+ /* r1, r1b, r6, r7 */
+ case MMC_RSP_R1:
+ case MMC_RSP_R1b:
+ cmdat |= CMDAT_RESP_SHORT;
+ break;
+ case MMC_RSP_R2:
+ cmdat |= CMDAT_RESP_R2;
+ break;
+ case MMC_RSP_R3:
+ cmdat |= CMDAT_RESP_R3;
+ break;
+ default:
+ break;
+ }
+
+ mmc_writel(cmd->cmdidx, MMC_CMD);
+ mmc_writel(cmd->cmdarg >> 16, MMC_ARGH);
+ mmc_writel(cmd->cmdarg & 0xffff, MMC_ARGL);
+ mmc_writel(host->clkrt, MMC_CLKRT);
+ pxamci_start_clock(host);
+ mmc_writel(cmdat, MMC_CMDAT);
+}
+
+static int pxamci_cmd_response(struct pxamci_host *host, struct mci_cmd *cmd)
+{
+ unsigned v, stat;
+ int i;
+
+ /*
+ * Did I mention this is Sick. We always need to
+ * discard the upper 8 bits of the first 16-bit word.
+ */
+ v = mmc_readl(MMC_RES) & 0xffff;
+ for (i = 0; i < 4; i++) {
+ u32 w1 = mmc_readl(MMC_RES) & 0xffff;
+ u32 w2 = mmc_readl(MMC_RES) & 0xffff;
+ cmd->response[i] = v << 24 | w1 << 8 | w2 >> 8;
+ v = w2;
+ }
+
+ stat = mmc_readl(MMC_STAT);
+ if (stat & STAT_TIME_OUT_RESPONSE)
+ return -ETIMEDOUT;
+ if (stat & STAT_RES_CRC_ERR && cmd->resp_type & MMC_RSP_CRC) {
+ /*
+ * workaround for erratum #42:
+ * Intel PXA27x Family Processor Specification Update Rev 001
+ * A bogus CRC error can appear if the msb of a 136 bit
+ * response is a one.
+ */
+ if (cpu_is_pxa27x() && cmd->resp_type & MMC_RSP_136 &&
+ cmd->response[0] & 0x80000000)
+ pr_debug("ignoring CRC from command %d - *risky*\n",
+ cmd->cmdidx);
+ else
+ return -EILSEQ;
+ }
+
+ return 0;
+}
+
+static int pxamci_mmccmd(struct pxamci_host *host, struct mci_cmd *cmd,
+ struct mci_data *data, unsigned int cmddat)
+{
+ int ret = 0;
+ uint64_t start;
+
+ pxamci_start_cmd(host, cmd, cmddat);
+ for (start = get_time_ns(), ret = -ETIMEDOUT;
+ ret && !is_timeout(start, 10 * MSECOND);)
+ if (mmc_readl(MMC_STAT) & STAT_END_CMD_RES)
+ ret = 0;
+
+ if (!ret && data)
+ ret = pxamci_transfer_data(host, data);
+
+ if (!ret)
+ ret = pxamci_cmd_response(host, cmd);
+ return ret;
+}
+
+static int pxamci_request(struct mci_host *mci, struct mci_cmd *cmd,
+ struct mci_data *data)
+{
+ struct pxamci_host *host = to_pxamci(mci);
+ unsigned int cmdat;
+ int ret;
+
+ pxamci_stop_clock(host);
+
+ cmdat = host->cmdat;
+ host->cmdat &= ~CMDAT_INIT;
+
+ if (data) {
+ pxamci_setup_data(host, data);
+
+ cmdat &= ~CMDAT_BUSY;
+ cmdat |= CMDAT_DATAEN;
+ if (data->flags & MMC_DATA_WRITE)
+ cmdat |= CMDAT_WRITE;
+ }
+
+ ret = pxamci_mmccmd(host, cmd, data, cmdat);
+ return ret;
+}
+
+static void pxamci_set_ios(struct mci_host *mci, struct device_d *dev,
+ unsigned bus_width, unsigned clock)
+{
+ struct pxamci_host *host = to_pxamci(mci);
+ unsigned int clk_in = pxa_get_mmcclk();
+ int fact;
+
+ mci_dbg("bus_width=%d, clock=%u\n", bus_width, clock);
+ if (clock)
+ fact = min_t(int, clk_in / clock, 1 << 6);
+ else
+ fact = 1 << 6;
+ fact = max_t(int, fact, 1);
+
+ /*
+ * We calculate clkrt here, and will write it on the next command
+ * MMC card clock = mmcclk / (2 ^ clkrt)
+ */
+ /* to handle (19.5MHz, 26MHz) */
+ host->clkrt = fls(fact) - 1;
+
+ if (bus_width == 4)
+ host->cmdat |= CMDAT_SD_4DAT;
+ else
+ host->cmdat &= ~CMDAT_SD_4DAT;
+ host->cmdat |= CMDAT_INIT;
+
+ clk_enable();
+ pxamci_set_power(host, 1);
+}
+
+static int pxamci_init(struct mci_host *mci, struct device_d *dev)
+{
+ struct pxamci_host *host = to_pxamci(mci);
+
+ if (host->pdata && host->pdata->init)
+ return host->pdata->init(mci, dev);
+ return 0;
+}
+
+static int pxamci_probe(struct device_d *dev)
+{
+ struct pxamci_host *host;
+ int gpio_power = -1;
+
+ host = xzalloc(sizeof(*host));
+ host->base = dev_request_mem_region(dev, 0);
+
+ host->mci.init = pxamci_init;
+ host->mci.send_cmd = pxamci_request;
+ host->mci.set_ios = pxamci_set_ios;
+ host->mci.host_caps = MMC_MODE_4BIT;
+ host->mci.hw_dev = dev;
+ host->mci.voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ /*
+ * Calculate minimum clock rate, rounding up.
+ */
+ host->mci.f_min = pxa_get_mmcclk() >> 6;
+ host->mci.f_max = pxa_get_mmcclk();
+
+ /*
+ * Ensure that the host controller is shut down, and setup
+ * with our defaults.
+ */
+ pxamci_stop_clock(host);
+ mmc_writel(0, MMC_SPI);
+ mmc_writel(64, MMC_RESTO);
+ mmc_writel(0, MMC_I_MASK);
+
+ host->pdata = dev->platform_data;
+ if (host->pdata)
+ gpio_power = host->pdata->gpio_power;
+
+ if (gpio_power > 0)
+ gpio_direction_output(gpio_power,
+ host->pdata->gpio_power_invert);
+
+ mci_register(&host->mci);
+ return 0;
+}
+
+static struct driver_d pxamci_driver = {
+ .name = DRIVER_NAME,
+ .probe = pxamci_probe,
+};
+
+static int __init pxamci_init_driver(void)
+{
+ register_driver(&pxamci_driver);
+ return 0;
+}
+
+device_initcall(pxamci_init_driver);