RE: [RFC 1/2] MMC/SD Controller driver for OMAP2430

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi All,

In response to the RFC posted by Khasim to support MMC/SD controller on OMAP2430, I am reposting the patches after fixing the
comments provided
by Russell King and Pierre Ossman. This patch also works for OMAP3430.

The following type of cards were tested.
- MMC/SD cards, SDHC card, MMCmobile card(at 1.8V).
- Hot plug of the cards.

TBD
- Multi-slot support.

FYI,
Below are the archive links to the comments provided by Russell King and Pierre Ossman.

http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2007-July/041172.html
http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2007-August/041312.html

http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2007-August/041313.html
This one had only two comments to change the usage of mdelay to msleep in the mmc board file. I have done the same and not reposting
that again
on arm linux kernel mailing list. 

Note: I am only posting the controller driver patch to arm linux kernel mailing list. Rest of the supporting omap patches will be
posted on linux omap list.

Kindly let me know if there are any further issues. I would be glad to work on it.

Thanks,
Madhu



-----Original Message-----
From: linux-omap-open-source-bounces@xxxxxxxxxxxxxx [mailto:linux-omap-open-source-bounces@xxxxxxxxxxxxxx] On Behalf Of Syed
Mohammed, Khasim
Sent: Thursday, July 26, 2007 5:47 AM
To: linux-arm-kernel@xxxxxxxxxxxxxxxxxxxxxx; Pierre Ossman; linux-omap-open-source@xxxxxxxxxxxxxx
Subject: [RFC 1/2] MMC/SD Controller driver for OMAP2430

Hello:

I am hereby presenting MMC/SD controller driver for OMAP 2430. It has been validated for SD/MMC (3v) cards on 2430 (ES 2.1) SDP
V4.0.

TBD:
 - Multi-slot support
 - Card detection is untested.
 - 1.8v cards are not tested.

It would be very kind of you to review this code and pass your comments.

FYI, I have posted complete series (other patches specific to OMAP tree) on linux-omap mailing list. 

Thanks & Regards,
Khasim

=============================================================================

diff -purN linux-omap/drivers/mmc/host/omap_hsmmc.c lin_for_mmc/drivers/mmc/host/omap_hsmmc.c
--- linux-omap/drivers/mmc/host/omap_hsmmc.c	1969-12-31 18:00:00.000000000 -0600
+++ lin_for_mmc/drivers/mmc/host/omap_hsmmc.c	2007-07-25 18:36:32.000000000 -0500
@@ -0,0 +1,1050 @@
+/*
+ * drivers/mmc/host/omap_hsmmc.c
+ *
+ * Driver for OMAP2430 MMC controller.
+ *
+ * Copyright (C) 2007 Texas Instruments.
+ *
+ * Authors:
+ *	Syed Mohammed Khasim	<x0khasim@xxxxxx>
+ *	Madhusudhan		<madhu.cr@xxxxxx>
+ *	Mohit Jalori		<mjalori@xxxxxx>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/mach-types.h>
+#include <asm/hardware.h>
+#include <asm/arch/board.h>
+#include <asm/arch/mmc.h>
+#include <asm/arch/cpu.h>
+#include <asm/semaphore.h>
+
+#ifdef CONFIG_PM
+#include <linux/notifier.h>
+#include <linux/pm.h>
+#endif
+
+/* OMAP HSMMC Host Controller Registers */
+#define OMAP_HSMMC_SYSCONFIG	0x0010
+#define OMAP_HSMMC_CON		0x002C
+#define OMAP_HSMMC_BLK		0x0104
+#define OMAP_HSMMC_ARG		0x0108
+#define OMAP_HSMMC_CMD		0x010C
+#define OMAP_HSMMC_RSP10	0x0110
+#define OMAP_HSMMC_RSP32	0x0114
+#define OMAP_HSMMC_RSP54	0x0118
+#define OMAP_HSMMC_RSP76	0x011C
+#define OMAP_HSMMC_DATA		0x0120
+#define OMAP_HSMMC_HCTL		0x0128
+#define OMAP_HSMMC_SYSCTL	0x012C
+#define OMAP_HSMMC_STAT		0x0130
+#define OMAP_HSMMC_IE		0x0134
+#define OMAP_HSMMC_ISE		0x0138
+#define OMAP_HSMMC_CAPA		0x0140
+
+#define VS18			(1<<26)
+#define VS30			(1<<25)
+#define SDVS18			(0x5<<9)
+#define SDVS30			(0x6<<9)
+#define SDVSCLR			0xFFFFF1FF
+#define SDVSDET			0x00000400
+#define AUTOIDLE		0x1
+#define SDBP			(1<<8)
+#define DTO			0xe
+#define ICE			0x1
+#define ICS			0x2
+#define CEN			(1<<2)
+#define CLKD_MASK		0x0000FFC0
+#define INT_EN_MASK		0x307F0033
+#define INIT_STREAM		(1<<1)
+#define DP_SELECT		(1<<21)
+#define DDIR			(1<<4)
+#define DMA_EN			0x1
+#define MSBS			1<<5
+#define BCE			1<<1
+#define FOUR_BIT		1 << 1
+#define CC			0x1
+#define TC			0x02
+#define OD			0x1
+#define ERR			(1 << 15)
+#define CMD_TIMEOUT		(1 << 16)
+#define DATA_TIMEOUT		(1 << 20)
+#define CMD_CRC			(1 << 17)
+#define DATA_CRC		(1 << 21)
+#define CARD_ERR		(1 << 28)
+#define STAT_CLEAR		0xFFFFFFFF
+#define INIT_STREAM_CMD		0x00000000
+
+#define OMAP_MMC1_DEVID		1
+#define OMAP_MMC2_DEVID		2
+#define OMAP_MMC_DATADIR_NONE	0
+#define OMAP_MMC_DATADIR_READ	1
+#define OMAP_MMC_DATADIR_WRITE	2
+#define MMC1_ACTIVE_OVERWRITE	(1<<31)
+#define MMC_TIMEOUT_MS		20
+#define OMAP_MMC_MASTER_CLOCK	96000000
+#define DRIVER_NAME		"mmci-omap"
+#define mmc_slot(host)		(host->pdata->slots[host->slot_id])
+
+/*
+ * MMC Host controller read/write API's
+ */
+#define OMAP_HSMMC_READ(base, reg)	\
+	__raw_readl((base) + OMAP_HSMMC_##reg)
+
+#define OMAP_HSMMC_WRITE(base, reg, val) \
+	__raw_writel((val), (base) + OMAP_HSMMC_##reg)
+
+struct mmc_omap_host {
+	struct	device		*dev;
+	struct	mmc_host	*mmc;
+	struct	mmc_request	*mrq;
+	struct	mmc_command	*cmd;
+	struct	mmc_data	*data;
+	struct	resource	*mem_res;
+	struct	clk		*fclk, *iclk, *dbclk;
+	struct	semaphore	sem;
+	struct	work_struct	mmc_carddetect_work;
+	void	__iomem		*base;
+	void			*mapbase;
+	unsigned int		id;
+	unsigned int		dma_len;
+	unsigned int		dma_dir;
+	unsigned char		bus_mode;
+	unsigned char		datadir;
+	u32			*buffer;
+	u32			bytesleft;
+	int			suspended;
+	int			irq;
+	int			carddetect;
+	int			use_dma, dma_ch;
+	int			initstr;
+	int			slot_id;
+
+	struct	omap_mmc_platform_data	*pdata;
+};
+
+/*
+ * Stop clock to the card
+ */
+static void omap_mmc_stop_clock(struct mmc_omap_host *host)
+{
+	OMAP_HSMMC_WRITE(host->base, SYSCTL,
+		OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
+	if ((OMAP_HSMMC_READ(host->base, SYSCTL) & CEN) != 0x0)
+		dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped");
+}
+
+/*
+ * Send init stream sequence to card
+ * before sending IDLE command
+ */
+static void send_init_stream(struct mmc_omap_host *host)
+{
+	int reg = 0;
+	typeof(jiffies) timeout;
+
+	disable_irq(host->irq);
+	OMAP_HSMMC_WRITE(host->base, CON,
+		OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM);
+	OMAP_HSMMC_WRITE(host->base, CMD, INIT_STREAM_CMD);
+
+	timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+	while ((reg != CC) && time_before(jiffies, timeout)) {
+		reg = OMAP_HSMMC_READ(host->base, STAT) & CC;
+	}
+
+	OMAP_HSMMC_WRITE(host->base, CON,
+		OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM);
+	enable_irq(host->irq);
+}
+
+/*
+ * Configure the Responce type
+ */
+static void
+mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+	int  cmdreg = 0, resptype = 0, cmdtype = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n",
+			mmc_hostname(host->mmc), cmd->opcode, cmd->arg);
+	host->cmd = cmd;
+
+	/*
+	 * Clear status bits and enable interrupts
+	 */
+	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+	OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
+	OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R1:
+	case MMC_RSP_R3:
+	case MMC_RSP_R1B:
+		/* resp 1, 1b, 3 */
+		resptype = 2;
+		break;
+	case MMC_RSP_R2:
+		resptype = 1;
+		break;
+	default:
+		break;
+	}
+
+	cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base,HCTL) & ~FOUR_BIT);
+
+	if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4)
+		OMAP_HSMMC_WRITE(host->base, HCTL,
+				OMAP_HSMMC_READ(host->base, HCTL)
+						| FOUR_BIT);
+	else if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_1)
+		OMAP_HSMMC_WRITE(host->base, HCTL,
+				OMAP_HSMMC_READ(host->base, HCTL)
+						& ~FOUR_BIT);
+
+	if (cmd->opcode == MMC_READ_SINGLE_BLOCK ||
+			cmd->opcode == MMC_READ_MULTIPLE_BLOCK ||
+			cmd->opcode == SD_APP_SEND_SCR	||
+			((cmd->opcode == MMC_SEND_EXT_CSD) && cmd->arg == 0))
+		cmdreg |= DP_SELECT | DDIR | MSBS | BCE;
+
+	else if (cmd->opcode == MMC_WRITE_BLOCK ||
+			cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) {
+		cmdreg |= DP_SELECT | MSBS | BCE;
+		cmdreg &= ~(DDIR);
+	}
+
+	if (host->use_dma)
+		cmdreg |= DMA_EN;
+
+	if (cmd->opcode == MMC_GO_IDLE_STATE || cmd->opcode == MMC_SEND_OP_COND
+		|| cmd->opcode == MMC_ALL_SEND_CID)
+		OMAP_HSMMC_WRITE(host->base, CON,
+				OMAP_HSMMC_READ(host->base, CON) | OD);
+
+	if (cmd->opcode == MMC_GO_IDLE_STATE)
+		if (host->initstr == 0) {
+			send_init_stream(host);
+			host->initstr = 1;
+		}
+
+	OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
+	OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
+}
+
+/*
+ * Notify the transder complete to MMC core
+ */
+static void
+mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
+{
+	host->data = NULL;
+
+	if (host->use_dma && host->dma_ch != -1) {
+		dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
+			host->dma_dir);
+	}
+
+	host->datadir = OMAP_MMC_DATADIR_NONE;
+
+	if (data->error == MMC_ERR_NONE)
+		data->bytes_xfered += data->blocks * (data->blksz);
+
+	if (!data->stop) {
+		host->mrq = NULL;
+		mmc_request_done(host->mmc, data->mrq);
+		return;
+	}
+	mmc_omap_start_command(host, data->stop);
+}
+
+/*
+ * Notify the core about command completion
+ */
+static void
+mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+	host->cmd = NULL;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		if (cmd->flags & MMC_RSP_136) {
+			/* response type 2 */
+			cmd->resp[3] = OMAP_HSMMC_READ(host->base, RSP10);
+			cmd->resp[2] = OMAP_HSMMC_READ(host->base, RSP32);
+			cmd->resp[1] = OMAP_HSMMC_READ(host->base, RSP54);
+			cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP76);
+		} else {
+			/* response types 1, 1b, 3, 4, 5, 6 */
+			cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10);
+		}
+	}
+	if (host->data == NULL || cmd->error != MMC_ERR_NONE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: End request, err %x\n",
+				mmc_hostname(host->mmc), cmd->error);
+		host->mrq = NULL;
+		mmc_request_done(host->mmc, cmd->mrq);
+	}
+}
+
+/*
+ * DMA clean up for command errors
+ */
+static void mmc_dma_cleanup(struct mmc_omap_host *host)
+{
+	host->data->error |= MMC_ERR_TIMEOUT;
+
+	if (host->use_dma && host->dma_ch != -1) {
+		dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len,
+			host->dma_dir);
+		omap_free_dma(host->dma_ch);
+		host->dma_ch = -1;
+		up(&host->sem);
+	}
+	host->data = NULL;
+	host->datadir = OMAP_MMC_DATADIR_NONE;
+}
+
+/*
+ * MMC controller IRQ handler
+ */
+static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
+{
+	struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id;
+	int end_cmd = 0, end_trans = 0, status;
+	typeof(jiffies) timeout;
+
+	if (host->cmd == NULL && host->data == NULL) {
+		OMAP_HSMMC_WRITE(host->base, STAT,
+				OMAP_HSMMC_READ(host->base, STAT));
+		return IRQ_HANDLED;
+	}
+
+	if (host->cmd) {
+		if (host->cmd->opcode == MMC_SELECT_CARD
+			|| host->cmd->opcode == MMC_SWITCH) {
+			timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+			while (time_before(jiffies, timeout)) {
+				if ((OMAP_HSMMC_READ(host->base, STAT)
+					& CC) == CC)
+					break;
+			}
+		}
+	}
+
+	status = OMAP_HSMMC_READ(host->base, STAT);
+	dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
+
+	if (status & (ERR)) {
+		if (status & (CMD_TIMEOUT) ||
+			status & (CMD_CRC)) {
+			if (host->cmd) {
+				if (status & (CMD_TIMEOUT))
+					host->cmd->error |= MMC_ERR_TIMEOUT;
+				else
+					host->cmd->error |= MMC_ERR_BADCRC;
+				end_cmd = 1;
+			}
+			if (host->data)
+				mmc_dma_cleanup(host);
+		}
+		if (status & (DATA_TIMEOUT) ||
+			status & (DATA_CRC)) {
+			if (host->data) {
+				if (status & (DATA_TIMEOUT))
+					mmc_dma_cleanup(host);
+				else
+					host->data->error |= MMC_ERR_BADCRC;
+				end_trans = 1;
+			}
+		}
+		if (status & CARD_ERR) {
+			if (host->cmd) {
+				host->cmd->error |= MMC_ERR_FAILED;
+				end_cmd = 1;
+			}
+			if (host->data) {
+				host->data->error |= MMC_ERR_FAILED;
+				end_trans = 1;
+			}
+		}
+	}
+
+	OMAP_HSMMC_WRITE(host->base, STAT, status);
+	if (end_cmd || (status & CC))
+		mmc_omap_cmd_done(host, host->cmd);
+
+	if (end_trans || (status & TC))
+		if (host->mmc->mode == MMC_MODE_MMC
+			|| host->mmc->mode == MMC_MODE_SD)
+			mmc_omap_xfer_done(host, host->data);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Swith MMC operating voltage
+ */
+static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
+{
+	u32 reg_val = 0;
+	int ret = 0;
+
+	/* Disable the clocks */
+	clk_disable(host->fclk);
+	clk_disable(host->iclk);
+	clk_disable(host->dbclk);
+
+	/* Turn the power off */
+	ret = mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+	if (ret != 0)
+		goto err;
+
+	/* Turn the power ON with given VDD 1.8 or 3.0v */
+	ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd);
+	if (ret != 0)
+		goto err;
+
+	clk_enable(host->fclk);
+	clk_enable(host->iclk);
+	clk_enable(host->dbclk);
+
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR);
+	reg_val = OMAP_HSMMC_READ(host->base, HCTL);
+
+	if (((1 << vdd) == MMC_VDD_33_34) || ((1 << vdd) == MMC_VDD_33_34)) {
+		host->initstr = 0;
+		reg_val |= SDVS30;
+	}
+	if ((1 << vdd) == MMC_VDD_165_195)
+		reg_val |= SDVS18;
+
+	OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
+
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+	return 0;
+err:
+	dev_dbg(mmc_dev(host->mmc), "Unable to switch operating voltage \n");
+	return -1;
+}
+
+/*
+ * Work Item to notify the core about card insertion/removal
+ */
+static void mmc_omap_detect(struct work_struct *work)
+{
+	u16 vdd = 0;
+	struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+						mmc_carddetect_work);
+
+	if (host->carddetect) {
+		if (!(OMAP_HSMMC_READ(host->base, HCTL)	& SDVSDET)) {
+			vdd = fls(host->mmc->ocr_avail) - 1;
+			if (omap_mmc_switch_opcond(host, vdd) != 0)
+				host->mmc->ios.vdd = vdd;
+			mmc_detect_change(host->mmc, (HZ * 200) / 1000);
+		}
+	} else {
+		mmc_detect_change(host->mmc, (HZ * 50) / 1000);
+	}
+}
+
+/*
+ * ISR for handling card insertion and removal
+ */
+void omap_mmc_notify_card_detect(struct device *dev, int slot, int detected)
+{
+	struct mmc_omap_host *host = dev_get_drvdata(dev);
+
+	host->carddetect = detected;
+	schedule_work(&host->mmc_carddetect_work);
+}
+
+/*
+ * DMA call back function
+ */
+static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
+{
+	struct mmc_omap_host *host = (struct mmc_omap_host *)data;
+
+	if (ch_status & (1 << 11))
+		dev_dbg(mmc_dev(host->mmc), "MISALIGNED_ADRS_ERR\n");
+
+	if (host->dma_ch < 0)
+		return;
+
+	omap_free_dma(host->dma_ch);
+	host->dma_ch = -1;
+	up(&host->sem);
+}
+
+/*
+ * Configure dma src and destination parameters
+ */
+static int mmc_omap_config_dma_param(int sync_dir, struct mmc_omap_host *host,
+				     struct mmc_data *data)
+{
+	if (sync_dir == 0) {
+		omap_set_dma_dest_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_CONSTANT,
+			(dma_addr_t) (host-> mapbase + OMAP_HSMMC_DATA), 0, 0);
+		omap_set_dma_src_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_POST_INC,
+			    sg_dma_address(&data-> sg[0]), 0, 0);
+	} else {
+		omap_set_dma_src_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_CONSTANT,
+			(dma_addr_t) (host->mapbase +OMAP_HSMMC_DATA), 0, 0);
+		omap_set_dma_dest_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_POST_INC,
+				sg_dma_address(&data->sg[0]), 0, 0);
+	}
+	return 0;
+}
+
+/*
+ * Rotine to configure and start DMA for the MMC card
+ */
+static int
+mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
+{
+	int sync_dev, sync_dir = 0;
+	int dma_ch = 0, ret = 0;
+	struct mmc_data *data = req->data;
+
+	/*
+	 * If for some reason the DMA transfer is still active,
+	 * we wait for timeout period and free the dma
+	 */
+	if (host->dma_ch != -1) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(100);
+		if (down_trylock(&host->sem)) {
+			omap_free_dma(host->dma_ch);
+			host->dma_ch = -1;
+			up(&host->sem);
+			return 1;
+		}
+	} else {
+		if (down_trylock(&host->sem)) {
+			BUG();
+		}
+	}
+	if (!(data->flags & MMC_DATA_WRITE)) {
+		host->dma_dir = DMA_FROM_DEVICE;
+		if (host->id == OMAP_MMC1_DEVID)
+			sync_dev = OMAP24XX_DMA_MMC1_RX;
+		else
+			sync_dev = OMAP24XX_DMA_MMC2_RX;
+	} else {
+		host->dma_dir = DMA_TO_DEVICE;
+		if (host->id == OMAP_MMC1_DEVID)
+			sync_dev = OMAP24XX_DMA_MMC1_TX;
+		else
+			sync_dev = OMAP24XX_DMA_MMC2_TX;
+	}
+
+	ret = omap_request_dma(sync_dev, "MMC/SD", mmc_omap_dma_cb,
+							host, &dma_ch);
+	if (ret != 0) {
+		dev_dbg(mmc_dev(host->mmc),
+			"%s: omap_request_dma() failed with %d\n",
+			mmc_hostname(host->mmc), ret);
+		return ret;
+	}
+
+	host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+					data->sg_len, host->dma_dir);
+	host->dma_ch = dma_ch;
+
+	if (!(data->flags & MMC_DATA_WRITE))
+		mmc_omap_config_dma_param(1, host, data);
+	else
+		mmc_omap_config_dma_param(0, host, data);
+
+	omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32,
+		(data->blksz / 4), data->blocks, OMAP_DMA_SYNC_FRAME,
+							sync_dev, sync_dir);
+	omap_start_dma(dma_ch);
+	return 0;
+}
+
+/*
+ * Configure block leangth for MMC/SD cards and intiate the transfer.
+ */
+static int
+mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
+{
+	int ret = 0;
+	host->data = req->data;
+
+	if (req->data == NULL) {
+		host->datadir = OMAP_MMC_DATADIR_NONE;
+		OMAP_HSMMC_WRITE(host->base, BLK, 0);
+		return 0;
+	}
+
+	OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
+					| (req->data->blocks << 16) );
+
+	host->datadir = (req->data-> flags & MMC_DATA_WRITE) ?
+			OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
+
+	if (host->use_dma) {
+		ret = mmc_omap_start_dma_transfer(host, req);
+		if (ret != 0) {
+			dev_dbg(mmc_dev(host->mmc), "MMC start dma failure\n");
+			return ret;
+		} else {
+			host->buffer = NULL;
+			host->bytesleft = 0;
+		}
+	} else {
+		host->buffer =	(u32 *) (page_address(req->data->sg->page) +
+							req->data->sg->offset);
+		host->bytesleft = req->data->blocks * (req->data->blksz);
+		host->dma_ch = -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Request function. for read/write operation
+ */
+static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct mmc_omap_host *host = mmc_priv(mmc);
+
+	WARN_ON(host->mrq != NULL);
+	host->mrq = req;
+
+	/* Reset MMC Controller's Data FSM */
+	if (req->cmd->opcode == MMC_GO_IDLE_STATE) {
+		OMAP_HSMMC_WRITE(host->base, SYSCTL,
+				OMAP_HSMMC_READ(host->base, SYSCTL) | 1 << 26);
+		while (OMAP_HSMMC_READ(host->base, SYSCTL) & (1 << 26)) ;
+	}
+
+	if (req->cmd->opcode == SD_APP_SEND_SCR
+		|| req->cmd->opcode == MMC_SEND_EXT_CSD)
+		mmc->ios.bus_width = MMC_BUS_WIDTH_1;
+
+	if (mmc_omap_prepare_data(host, req))
+		dev_dbg(mmc_dev(host->mmc),
+			"MMC host %s failed to initiate data transfer\n",
+			mmc_hostname(host->mmc));
+	mmc_omap_start_command(host, req->cmd);
+}
+
+
+/*
+ * Configuring clock values.
+ */
+/* Routine to configure clock values. Exposed API to core */
+static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct mmc_omap_host *host = mmc_priv(mmc);
+	u16 dsor = 0;
+	unsigned long regval;
+	typeof(jiffies) timeout;
+
+	switch (ios->power_mode) {
+	case MMC_POWER_OFF:
+		mmc_slot(host).set_power(host->dev,host->slot_id, 0, 0);
+		break;
+	case MMC_POWER_UP:
+		mmc_slot(host).set_power(host->dev,host->slot_id, 1, ios->vdd);
+		break;
+	}
+
+	if (host->id == OMAP_MMC1_DEVID) {
+		if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
+				((ios->vdd == 7) || (ios->vdd == 8))) {
+				/* vdd is 1.8v */
+				if (omap_mmc_switch_opcond(host, ios->vdd) != 0)
+					dev_dbg(mmc_dev(host->mmc),
+						"Switch operation failed\n");
+				host->initstr = 0;
+		}
+	}
+
+	if (ios->clock) {
+		dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
+		if (dsor < 1)
+			dsor = 1;
+
+		if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
+			dsor++;
+
+		if (dsor > 250)
+			dsor = 250;
+	}
+
+	omap_mmc_stop_clock(host);
+	regval = OMAP_HSMMC_READ(host->base, SYSCTL);
+	regval = regval & ~(CLKD_MASK);
+	regval = regval | (dsor << 6) | (DTO << 16);
+	OMAP_HSMMC_WRITE(host->base, SYSCTL, regval);
+	OMAP_HSMMC_WRITE(host->base, SYSCTL,
+			OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
+
+	/* Wait till the ICS bit is set */
+	timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+	while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2
+		&& time_before(jiffies, timeout)) ;
+
+	OMAP_HSMMC_WRITE(host->base, SYSCTL,
+			OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
+}
+
+static struct mmc_host_ops mmc_omap_ops = {
+	.request = omap_mmc_request,
+	.set_ios = omap_mmc_set_ios,
+};
+
+static int __init omap_mmc_probe(struct platform_device *pdev)
+{
+	struct omap_mmc_platform_data *pdata = pdev->dev.platform_data;
+	struct mmc_host *mmc;
+	struct mmc_omap_host *host = NULL;
+	struct resource *res;
+	int ret = 0, irq;
+
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "Platform Data is missing\n");
+		return -ENXIO;
+	}
+
+	if (pdata->nr_slots == 0) {
+		dev_err(&pdev->dev, "No Slots\n");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	irq = platform_get_irq(pdev, 0);
+	if (res == NULL || irq < 0)
+		return -ENXIO;
+
+	res = request_mem_region(res->start, res->end - res->start + 1,
+							pdev->name);
+	if (res == NULL)
+		return -EBUSY;
+
+	mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev);
+	if (!mmc) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	host		= mmc_priv(mmc);
+	host->mmc	= mmc;
+	host->pdata	= pdata;
+	host->use_dma	= 1;
+	host->dma_ch	= -1;
+	host->initstr	= 0;
+	host->mem_res	= res;
+	host->irq	= irq;
+	host->id	= pdev->id;
+	host->slot_id	= pdev->id - 1;
+	host->mapbase	= (void *)host->mem_res->start;
+	host->base	= (void __iomem *)IO_ADDRESS(host->mapbase);
+	mmc->ops	= &mmc_omap_ops;
+	mmc->f_min	= 400000;
+	mmc->f_max	= 52000000;
+
+	sema_init(&host->sem, 1);
+
+	host->iclk = clk_get(&pdev->dev, "mmchs_ick");
+	if (IS_ERR(host->iclk)) {
+		ret = PTR_ERR(host->iclk);
+		host->iclk = NULL;
+		goto err;
+	}
+	host->fclk = clk_get(&pdev->dev, "mmchs_fck");
+	if (IS_ERR(host->fclk)) {
+		ret = PTR_ERR(host->fclk);
+		host->fclk = NULL;
+		clk_put(host->iclk);
+		goto err;
+	}
+	host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
+
+	/*
+	 * MMC can still work without debounce clock.
+	 */
+	if (IS_ERR(host->dbclk))
+		dev_dbg(mmc_dev(host->mmc), "Failed to get debounce clock \n");
+
+	if (clk_enable(host->fclk) != 0) {
+		goto err;
+	}
+	if (clk_enable(host->iclk) != 0) {
+		clk_disable(host->fclk);
+		clk_put(host->fclk);
+		goto err;
+	}
+	if (clk_enable(host->dbclk) != 0)
+		dev_dbg(mmc_dev(host->mmc), "Enabling debounce clk failed\n");
+
+	omap_writel(omap_readl(CONTROL_DEVCONF1) | MMC1_ACTIVE_OVERWRITE,
+						CONTROL_DEVCONF1);
+	mmc->ocr_avail = mmc_slot(host).ocr_mask;
+	mmc->caps |= MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK;
+	if (pdata->wire4)
+		mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) | SDVS30);
+
+	OMAP_HSMMC_WRITE(host->base, CAPA,OMAP_HSMMC_READ(host->base,
+							CAPA) | VS30 | VS18);
+
+	/* Set the controller to AUTO IDLE mode */
+	OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
+			OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
+
+	/* Set SD bus power bit */
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+	/* Request IRQ for MMC operations */
+	ret = request_irq(host->irq, mmc_omap_irq, 0, pdev->name, host);
+	if (ret) {
+		dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ");
+		goto irq_err;
+	}
+
+	INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
+	if(pdata->init != NULL) {
+		if (pdata->init(&pdev->dev) != 0) {
+			free_irq(host->irq, host);
+			goto irq_err;
+		}
+	}
+
+	OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
+	OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
+
+	platform_set_drvdata(pdev, host);
+	mmc_add_host(mmc);
+
+	return 0;
+
+err:
+	dev_dbg(mmc_dev(host->mmc), "Probe Fail\n");
+	if (host)
+		mmc_free_host(mmc);
+	return ret;
+
+irq_err:
+	dev_dbg(mmc_dev(host->mmc), "Unable to configure MMC IRQs");
+	clk_disable(host->fclk);
+	clk_disable(host->iclk);
+	clk_disable(host->dbclk);
+
+	clk_put(host->fclk);
+	clk_put(host->iclk);
+	clk_put(host->dbclk);
+
+	if (host)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static int omap_mmc_remove(struct platform_device *pdev)
+{
+	struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+	if (host) {
+		host->pdata->cleanup(&pdev->dev);
+		free_irq(host->irq, host);
+		flush_scheduled_work();
+
+		clk_disable(host->fclk);
+		clk_disable(host->iclk);
+		clk_disable(host->dbclk);
+		clk_put(host->fclk);
+		clk_put(host->iclk);
+		clk_put(host->dbclk);
+
+		mmc_free_host(host->mmc);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	int ret = 0;
+	struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+	if (host && host->suspended)
+		return 0;
+
+	if (host) {
+		ret = mmc_suspend_host(host->mmc, state);
+		if (ret == 0) {
+			host->suspended = 1;
+
+		OMAP_HSMMC_WRITE(host->base, ISE, 0);
+		OMAP_HSMMC_WRITE(host->base, IE, 0);
+
+		ret = host->pdata->suspend(&pdev->dev, host->slot_id);
+		if (ret)
+			dev_dbg(mmc_dev(host->mmc),
+				"Unable to handle MMC board level suspend\n");
+
+		if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
+			OMAP_HSMMC_WRITE(host->base,HCTL,
+					OMAP_HSMMC_READ(host->base, HCTL)
+					& SDVSCLR);
+			OMAP_HSMMC_WRITE(host->base,HCTL,
+					OMAP_HSMMC_READ(host->base, HCTL)
+					|SDVS30);
+			OMAP_HSMMC_WRITE(host->base,HCTL,
+					OMAP_HSMMC_READ(host->base, HCTL)
+					| SDBP);
+		}
+		clk_disable(host->fclk);
+		clk_disable(host->iclk);
+		clk_disable(host->dbclk);
+
+		if (host->id == OMAP_MMC1_DEVID) {
+			omap_writel(omap_readl(CONTROL_DEVCONF1)
+					& ~MMC1_ACTIVE_OVERWRITE,
+					CONTROL_DEVCONF1);
+		}
+
+		/* Power off slot power */
+		ret = mmc_slot(host).set_power(host->dev,host->slot_id, 0, 0);
+		if (ret != 0)
+			dev_dbg(mmc_dev(host->mmc), "Unable to disable power to MMC\n");
+			host->initstr = 0;
+		}
+	}
+	return ret;
+}
+
+/* Routine to resume the MMC device */
+static int omap_mmc_resume(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+	if (host && !host->suspended)
+		return 0;
+
+	if (host) {
+		if (host->id == OMAP_MMC1_DEVID) {
+			omap_writel(omap_readl(CONTROL_DEVCONF1)
+						| MMC1_ACTIVE_OVERWRITE,
+						CONTROL_DEVCONF1);
+		}
+
+		mmc_slot(host).set_power(host->dev, host->slot_id, 1,
+							host->mmc->ios.vdd);
+		if (ret != 0) {
+			dev_dbg(mmc_dev(host->mmc), "Enabling power failed\n");
+			return ret;
+		}
+
+		if (clk_enable(host->fclk) != 0)
+			goto clk_en_err;
+
+		if (clk_enable(host->iclk) != 0) {
+			clk_disable(host->fclk);
+			clk_put(host->fclk);
+			goto clk_en_err;
+		}
+
+		if (clk_enable(host->dbclk) != 0)
+			dev_dbg(mmc_dev(host->mmc),
+					"Enabling debounce clk failed\n");
+
+		ret = host->pdata->resume(&pdev->dev, host->slot_id);
+		if (ret)
+			dev_dbg(mmc_dev(host->mmc),
+					"Unmask interrupt failed\n");
+
+		/* Notify the core to resume the host */
+		ret = mmc_resume_host(host->mmc);
+		if (ret == 0)
+			host->suspended = 0;
+		msleep_interruptible(1);
+	}
+
+	return ret;
+
+clk_en_err:
+	dev_dbg(mmc_dev(host->mmc),
+		"Failed to enable MMC clocks during resume\n");
+	return -1;
+}
+
+#else
+#define omap_mmc_suspend	NULL
+#define omap_mmc_resume		NULL
+#endif
+
+static struct platform_driver omap_mmc_driver = {
+	.probe	= omap_mmc_probe,
+	.remove	= omap_mmc_remove,
+	.suspend= omap_mmc_suspend,
+	.resume	= omap_mmc_resume,
+	.driver	= {
+		   .name = DRIVER_NAME,
+	},
+};
+
+static int __init omap_mmc_init(void)
+{
+	/* Register the MMC driver */
+	if (platform_driver_register(&omap_mmc_driver)) {
+		printk(KERN_ERR ": Failed to register MMC driver\n");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit omap_mmc_cleanup(void)
+{
+	/* Unregister MMC driver */
+	platform_driver_unregister(&omap_mmc_driver);
+}
+
+module_init(omap_mmc_init);
+module_exit(omap_mmc_cleanup);
+
+MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS(DRIVER_NAME);
+MODULE_AUTHOR("Texas Instruments Inc");
_______________________________________________
Linux-omap-open-source mailing list
Linux-omap-open-source@xxxxxxxxxxxxxx
http://linux.omap.com/mailman/listinfo/linux-omap-open-source

-
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux