summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorRobert Jarzmik <robert.jarzmik@free.fr>2011-12-07 22:47:59 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2011-12-08 10:22:58 +0100
commiteae6d3beb787a7b0f8576a5e5e1441cce3f96fa2 (patch)
tree5aa9a6b1b7565572e8b28185796e9ede254e45f4 /drivers
parent25de86661a3692051ddf875e645406b3c9b6417e (diff)
downloadbarebox-eae6d3beb787a7b0f8576a5e5e1441cce3f96fa2.tar.gz
barebox-eae6d3beb787a7b0f8576a5e5e1441cce3f96fa2.tar.xz
drivers/mci: add PXA host controller
Add a simple PIO based host controller for MMC and SD cards on PXA SoCs. Reads and writes are available, and no usage is made of DMA or IRQs. SPI mode is not supported yet. Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mci/Kconfig7
-rw-r--r--drivers/mci/Makefile1
-rw-r--r--drivers/mci/pxamci.c359
-rw-r--r--drivers/mci/pxamci.h99
4 files changed, 466 insertions, 0 deletions
diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
index 6ed21cd2a4..8279e5d7ce 100644
--- a/drivers/mci/Kconfig
+++ b/drivers/mci/Kconfig
@@ -72,6 +72,13 @@ config MCI_OMAP_HSMMC
Enable this entry to add support to read and write SD cards on a
OMAP4 based system.
+config MCI_PXA
+ bool "PXA"
+ depends on ARCH_PXA
+ help
+ Enable this entry to add support to read and write SD cards on a
+ XScale PXA25x / PXA27x based system.
+
config MCI_ATMEL
bool "ATMEL (AT91)"
depends on ARCH_AT91
diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile
index d7482dc8a2..0fc31af744 100644
--- a/drivers/mci/Makefile
+++ b/drivers/mci/Makefile
@@ -4,5 +4,6 @@ obj-$(CONFIG_MCI_S3C) += s3c.o
obj-$(CONFIG_MCI_IMX) += imx.o
obj-$(CONFIG_MCI_IMX_ESDHC) += imx-esdhc.o
obj-$(CONFIG_MCI_OMAP_HSMMC) += omap_hsmmc.o
+obj-$(CONFIG_MCI_PXA) += pxamci.o
obj-$(CONFIG_MCI_ATMEL) += atmel_mci.o
obj-$(CONFIG_MCI_SPI) += mci_spi.o
diff --git a/drivers/mci/pxamci.c b/drivers/mci/pxamci.c
new file mode 100644
index 0000000000..558116f946
--- /dev/null
+++ b/drivers/mci/pxamci.c
@@ -0,0 +1,359 @@
+/*
+ * 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, ret = 0;
+ uint64_t start;
+
+ 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;
+ for (; !ret && trf_len > 0; trf_len--, len--)
+ *dst++ = mmc_readb(MMC_RXFIFO);
+ }
+
+ 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, 10 * 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)\n", cmd->cmdidx, cmd->resp_type);
+ 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();
+ unsigned long fact;
+
+ mci_dbg("bus_width=%d, clock=%ud\n", bus_width, clock);
+ fact = min_t(int, clock / clk_in, 1);
+ fact = max_t(int, fact, 1 << 6);
+
+ /*
+ * 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);
diff --git a/drivers/mci/pxamci.h b/drivers/mci/pxamci.h
new file mode 100644
index 0000000000..18d12a3038
--- /dev/null
+++ b/drivers/mci/pxamci.h
@@ -0,0 +1,99 @@
+/*
+ * 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
+ */
+
+#define MMC_FIFO_LENGTH 32
+
+#define MMC_STRPCL 0x0000
+#define STOP_CLOCK (1 << 0)
+#define START_CLOCK (2 << 0)
+
+#define MMC_STAT 0x0004
+#define STAT_END_CMD_RES (1 << 13)
+#define STAT_PRG_DONE (1 << 12)
+#define STAT_DATA_TRAN_DONE (1 << 11)
+#define STAT_CLK_EN (1 << 8)
+#define STAT_RECV_FIFO_FULL (1 << 7)
+#define STAT_XMIT_FIFO_EMPTY (1 << 6)
+#define STAT_RES_CRC_ERR (1 << 5)
+#define STAT_SPI_READ_ERROR_TOKEN (1 << 4)
+#define STAT_CRC_READ_ERROR (1 << 3)
+#define STAT_CRC_WRITE_ERROR (1 << 2)
+#define STAT_TIME_OUT_RESPONSE (1 << 1)
+#define STAT_READ_TIME_OUT (1 << 0)
+
+#define MMC_CLKRT 0x0008 /* 3 bit */
+
+#define MMC_SPI 0x000c
+#define SPI_CS_ADDRESS (1 << 3)
+#define SPI_CS_EN (1 << 2)
+#define CRC_ON (1 << 1)
+#define SPI_EN (1 << 0)
+
+#define MMC_CMDAT 0x0010
+#define CMDAT_SDIO_INT_EN (1 << 11)
+#define CMDAT_SD_4DAT (1 << 8)
+#define CMDAT_DMAEN (1 << 7)
+#define CMDAT_INIT (1 << 6)
+#define CMDAT_BUSY (1 << 5)
+#define CMDAT_STREAM (1 << 4) /* 1 = stream */
+#define CMDAT_WRITE (1 << 3) /* 1 = write */
+#define CMDAT_DATAEN (1 << 2)
+#define CMDAT_RESP_NONE (0 << 0)
+#define CMDAT_RESP_SHORT (1 << 0)
+#define CMDAT_RESP_R2 (2 << 0)
+#define CMDAT_RESP_R3 (3 << 0)
+
+#define MMC_RESTO 0x0014 /* 7 bit */
+#define MMC_RDTO 0x0018 /* 16 bit */
+#define MMC_BLKLEN 0x001c /* 10 bit */
+#define MMC_NOB 0x0020 /* 16 bit */
+
+#define MMC_PRTBUF 0x0024
+#define BUF_PART_FULL (1 << 0)
+
+#define MMC_I_REG 0x002c
+#define TXFIFO_WR_REQ (1 << 6)
+#define RXFIFO_RD_REQ (1 << 5)
+#define CLK_IS_OFF (1 << 4)
+#define STOP_CMD (1 << 3)
+#define END_CMD_RES (1 << 2)
+#define PRG_DONE (1 << 1)
+#define DATA_TRAN_DONE (1 << 0)
+
+#define MMC_I_MASK 0x0028
+#define MMC_CMD 0x0030
+#define MMC_ARGH 0x0034 /* 16 bit */
+#define MMC_ARGL 0x0038 /* 16 bit */
+#define MMC_RES 0x003c /* 16 bit */
+#define MMC_RXFIFO 0x0040 /* 8 bit */
+#define MMC_TXFIFO 0x0044 /* 8 bit */
+
+struct pxamci_host {
+ struct mci_host mci;
+ void __iomem *base;
+ struct pxamci_platform_data *pdata;
+
+ unsigned int cmdat;
+ int clkrt;
+};
+
+#define to_pxamci(mci) container_of(mci, struct pxamci_host, mci)
+
+#define mmc_readb(reg) readb(host->base + (reg))
+#define mmc_readl(reg) readl(host->base + (reg))
+#define mmc_writeb(val, reg) writeb((val), host->base + (reg))
+#define mmc_writel(val, reg) writel((val), host->base + (reg))
+
+#define mci_dbg(fmt, arg...) \
+ dev_dbg(host->mci.hw_dev, "%s: " fmt, __func__, ## arg)
+#define mci_err(fmt, arg...) \
+ dev_err(host->mci.hw_dev, "%s: " fmt, __func__, ## arg)