[U-Boot] [PATCH v2.1] PXA: New MMC driver

Marek Vasut marek.vasut at gmail.com
Fri Aug 6 20:31:11 CEST 2010


The new driver is a complete rewrite. It uses the MMC framework and should
support both pxa2xx and pxa3xx.

Tested on:
- Palm Tungsten|C	PXA255
- Aeronix ZipitZ2	PXA270
- Marvell Zylonite 300	PXA300

This driver needs testing though.

Signed-off-by: Marek Vasut <marek.vasut at gmail.com>
---
 drivers/mmc/Makefile      |    1 +
 drivers/mmc/pxa_mmc.h     |   21 +++
 drivers/mmc/pxa_mmc_gen.c |  335 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 357 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/pxa_mmc_gen.c

diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 6fa04b8..01338a4 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -32,6 +32,7 @@ COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o
 COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o
 COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o
 COBJS-$(CONFIG_PXA_MMC) += pxa_mmc.o
+COBJS-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o
 
 COBJS	:= $(COBJS-y)
 SRCS	:= $(COBJS:.o=.c)
diff --git a/drivers/mmc/pxa_mmc.h b/drivers/mmc/pxa_mmc.h
index 6fa4268..a5b7351 100644
--- a/drivers/mmc/pxa_mmc.h
+++ b/drivers/mmc/pxa_mmc.h
@@ -135,4 +135,25 @@
 #define MMC_R1B_ADDR_ERR		0x2000
 #define MMC_R1B_PARAM_ERR		0x4000
 
+/* 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	"Unknown CPU for PXAMMC"
+#endif
+
 #endif /* __MMC_PXA_P_H__ */
diff --git a/drivers/mmc/pxa_mmc_gen.c b/drivers/mmc/pxa_mmc_gen.c
new file mode 100644
index 0000000..ea04e0a
--- /dev/null
+++ b/drivers/mmc/pxa_mmc_gen.c
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2010 Marek Vasut <marek.vasut at 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 "pxa_mmc.h"
+
+/* 1000uS (in wait cycles below it's 100 x 10uS waits) */
+#define	PXA_MMC_TIMEOUT	100
+
+static int pxa_mmc_wait(int mask)
+{
+	int timeout = PXA_MMC_TIMEOUT;
+
+	/* Wait until the clock are off */
+	while (!(MMC_STAT & mask) && --timeout)
+		udelay(10);
+
+	/* The clock refused to stop, scream and die a painful death */
+	if (!timeout)
+		return -ETIMEDOUT;
+
+}
+
+static int pxa_mmc_stop_clock(void)
+{
+	int timeout = PXA_MMC_TIMEOUT;
+
+	/* If the clock aren't running, exit */
+	if (!(MMC_STAT & MMC_STAT_CLK_EN))
+		return 0;
+
+	/* Tell the controller to turn off the clock */
+	MMC_STRPCL = MMC_STRPCL_STOP_CLK;
+
+	/* Wait until the clock are off */
+	while ((MMC_STAT & MMC_STAT_CLK_EN) && --timeout)
+		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_cmd *cmd, unsigned long cmdat)
+{
+	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 */
+	MMC_CMD = cmd->cmdidx;
+	MMC_ARGH = cmd->cmdarg >> 16;
+	MMC_ARGL = cmd->cmdarg & 0xffff;
+	MMC_CMDAT = cmdat;
+
+	/* Start the controller clock and wait until they are started */
+	MMC_STRPCL = MMC_STRPCL_START_CLK;
+
+	ret = pxa_mmc_wait(MMC_STAT_CLK_EN)
+	if (ret)
+		return ret;
+
+	/* Correct and happy end */
+	return 0;
+}
+
+static int pxa_mmc_cmd_done(struct mmc_cmd *cmd)
+{
+	unsigned long a, b, c;
+	int i;
+	int stat;
+
+	/* Read the controller status */
+	stat = MMC_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 = MMC_RES & 0xffff;
+	for (i = 0; i < 4; i++) {
+		b = MMC_RES & 0xffff;
+		c = MMC_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] & 0x80000000)
+			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_data *data)
+{
+	unsigned long len;
+	int i;
+	int ret;
+
+	len = data->blocks * data->blocksize;
+
+	while (len) {
+		/* The controller has data ready */
+		if (MMC_I_REG & MMC_I_REG_RXFIFO_RD_REQ) {
+			i = min(len, PXAMMC_FIFO_SIZE);
+
+			while (i--) {
+				*data->dest++ = *((volatile uchar *)&MMC_RXFIFO);
+				len--;
+			}
+		}
+
+		if (MMC_STAT & MMC_STAT_ERRORS)
+			return -EIO;
+	}
+
+	/* Wait for the transmission-done interrupt */
+	ret = pxa_mmc_wait(MMC_STAT_DATA_TRAN_DONE)
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int pxa_mmc_do_write_xfer(struct mmc_data *data)
+{
+	unsigned long len;
+	int i, bytes;
+	int ret;
+
+	len = data->blocks * data->blocksize;
+
+	while (len) {
+		/* The controller is ready to receive data */
+		if (MMC_I_REG & MMC_I_REG_TXFIFO_WR_REQ) {
+			bytes = min(len, PXAMMC_FIFO_SIZE);
+
+			for (i = 0; i < bytes; i++)
+				MMC_TXFIFO = *data->src++;
+
+			if (bytes < 32)
+				MMC_PRTBUF = MMC_PRTBUF_BUF_PART_FULL;
+
+			len -= bytes;
+		}
+
+		if (MMC_STAT & MMC_STAT_ERRORS)
+			return -EIO;
+	}
+
+	/* Wait for the transmission-done interrupt */
+	ret = pxa_mmc_wait(MMC_STAT_DATA_TRAN_DONE)
+	if (ret)
+		return ret;
+
+	/* Wait until the data are really written to the card */
+	ret = pxa_mmc_wait(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)
+{
+	unsigned long cmdat = 0;
+	int ret;
+
+	/* Stop the controller */
+	ret = pxa_mmc_stop_clock();
+	if (ret)
+		return ret;
+
+	/* If we're doing data transfer, configure the controller accordingly */
+	if (data) {
+		MMC_NOB = data->blocks;
+		MMC_BLKLEN = data->blocksize;
+		/* This delay can be optimized, but stick with max value */
+		MMC_RDTO = 0xffff;
+		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(cmd, cmdat);
+	if (ret)
+		return ret;
+
+	/* Wait until the command completes */
+	ret = pxa_mmc_wait(MMC_STAT_END_CMD_RES)
+	if (ret)
+		return ret;
+
+	/* Read back the result */
+	ret = pxa_mmc_cmd_done(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(data);
+		else
+			pxa_mmc_do_read_xfer(data);
+	}
+
+	return 0;
+}
+
+static void pxa_mmc_set_ios(struct mmc *mmc)
+{
+	unsigned long tmp;
+	unsigned long pxa_mmc_clock;
+
+	/* Set clock to the card */
+	if (mmc->clock) {
+		/* PXA3xx can do 26MHz with special settings */
+		if (mmc->clock == 26000000)
+			MMC_CLKRT = 0x7;
+		else {
+			pxa_mmc_clock = 0;
+			tmp = mmc->f_max / mmc->clock;
+			tmp += tmp % 2;
+			while (tmp > 1) {
+				pxa_mmc_clock++;
+				tmp >>= 1;
+			}
+			MMC_CLKRT = pxa_mmc_clock;
+		}
+	} else
+		pxa_mmc_stop_clock();
+}
+
+static int pxa_mmc_setup(struct mmc *mmc)
+{
+	/* Make sure the clock are stopped */
+	pxa_mmc_stop_clock();
+	/* Turn off SPI mode */
+	MMC_SPI = MMC_SPI_DISABLE;
+	/* Set up maximum timeout to wait for command response */
+	MMC_RESTO = MMC_RES_TO_MAX;
+	/* Mask all interrupts */
+	MMC_I_MASK = ~(MMC_I_MASK_TXFIFO_WR_REQ | MMC_I_MASK_RXFIFO_RD_REQ);
+	return 0;
+}
+
+int pxa_mmc_init(bd_t *bis)
+{
+	struct mmc *mmc;
+
+	mmc = malloc(sizeof(struct mmc));
+
+	if (!mmc)
+		return -ENOMEM;
+	sprintf(mmc->name, "PXA MMC");
+	mmc->send_cmd	= pxa_mmc_request;
+	mmc->set_ios	= pxa_mmc_set_ios;
+	mmc->init	= pxa_mmc_setup;
+
+	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_register(mmc);
+
+	return 0;
+}
+
+int cpu_mmc_init(bd_t *bis)
+{
+	return pxa_mmc_init(bis);
+}
-- 
1.7.1



More information about the U-Boot mailing list