svn commit: r231956 - in projects/armv6/sys: arm/ti arm/ti/omap4 dev/mmc

Oleksandr Tymoshenko gonzo at FreeBSD.org
Tue Feb 21 02:32:47 UTC 2012


Author: gonzo
Date: Tue Feb 21 02:32:46 2012
New Revision: 231956
URL: http://svn.freebsd.org/changeset/base/231956

Log:
  Add MMC driver by Ben R. Gray slightly cleaned-up and FDT-ized by me
  
  Submitted by:	Ben Gray <ben.r.gray at gmail.com>

Added:
  projects/armv6/sys/arm/ti/omap_mmc.c
  projects/armv6/sys/arm/ti/omap_mmc.h
Modified:
  projects/armv6/sys/arm/ti/omap4/files.omap4
  projects/armv6/sys/dev/mmc/mmc.c

Modified: projects/armv6/sys/arm/ti/omap4/files.omap4
==============================================================================
--- projects/armv6/sys/arm/ti/omap4/files.omap4	Tue Feb 21 02:30:19 2012	(r231955)
+++ projects/armv6/sys/arm/ti/omap4/files.omap4	Tue Feb 21 02:32:46 2012	(r231956)
@@ -23,6 +23,7 @@ arm/ti/omap_gpio.c				optional	gpio
 arm/ti/usb/omap_ehci.c				optional	usb ehci
 arm/ti/omap_dma.c				optional	omap_dma
 arm/ti/ti_i2c.c					optional	ti_i2c
+arm/ti/omap_mmc.c				optional	mmc
 
 arm/ti/omap4/omap4_prcm_clks.c			standard
 arm/ti/omap4/omap4_scm_padconf.c		standard

Added: projects/armv6/sys/arm/ti/omap_mmc.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/armv6/sys/arm/ti/omap_mmc.c	Tue Feb 21 02:32:46 2012	(r231956)
@@ -0,0 +1,1724 @@
+/*-
+ * Copyright (c) 2011
+ *	Ben Gray <ben.r.gray at gmail.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/**
+ * Driver for the MMC/SD/SDIO module on the TI OMAP series of SoCs.
+ *
+ * This driver is heavily based on the SD/MMC driver for the AT91 (at91_mci.c).
+ *
+ * It's important to realise that the MMC state machine is already in the kernel
+ * and this driver only exposes the specific interfaces of the controller.
+ *
+ * This driver is still very much a work in progress, I've verified that basic
+ * sector reading can be performed. But I've yet to test it with a file system
+ * or even writing.  In addition I've only tested the driver with an SD card,
+ * I've no idea if MMC cards work.
+ *
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bio.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/kthread.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+#include <sys/time.h>
+#include <sys/timetc.h>
+#include <sys/gpio.h>
+
+#include <machine/bus.h>
+#include <machine/cpu.h>
+#include <machine/cpufunc.h>
+#include <machine/resource.h>
+#include <machine/frame.h>
+#include <machine/intr.h>
+
+#include <dev/mmc/bridge.h>
+#include <dev/mmc/mmcreg.h>
+#include <dev/mmc/mmcbrvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include "gpio_if.h"
+
+#include "mmcbr_if.h"
+#include "mmcbus_if.h"
+
+#include <arm/ti/omap_dma.h>
+#include <arm/ti/omap_mmc.h>
+#include <arm/ti/ti_cpuid.h>
+#include <arm/ti/ti_prcm.h>
+
+#ifdef TWL_SUPPORTED
+#include <arm/omap/twl/twl.h>
+#include <arm/omap/twl/twl_vreg.h>
+#endif
+
+#ifdef DEBUG
+#define omap_mmc_dbg(sc, fmt, args...) \
+	device_printf((sc)->sc_dev, fmt, ## args);
+#else
+#define omap_mmc_dbg(sc, fmt, args...)
+#endif
+
+/**
+ *	Structure that stores the driver context
+ */
+struct omap_mmc_softc {
+	device_t		sc_dev;
+	struct resource*	sc_irq_res;
+	struct resource*	sc_mem_res;
+
+	void*			sc_irq_h;
+
+	bus_dma_tag_t		sc_dmatag;
+	bus_dmamap_t		sc_dmamap;
+	int			sc_dmamapped;
+
+	unsigned int		sc_dmach_rd;
+	unsigned int		sc_dmach_wr;
+
+	device_t		sc_gpio_dev;
+	int			sc_wp_gpio_pin;  /* GPIO pin for MMC write protect */
+
+#ifdef TWL_SUPPORTED
+	device_t		sc_vreg_dev;
+	const char*		sc_vreg_name;
+#endif
+
+	struct mtx		sc_mtx;
+
+	struct mmc_host		host;
+	struct mmc_request*	req;
+	struct mmc_command*	curcmd;
+
+	int			flags;
+#define CMD_STARTED     1
+#define STOP_STARTED    2
+
+	int			bus_busy;  /* TODO: Needed ? */
+
+	void*			sc_cmd_data_vaddr;
+	int			sc_cmd_data_len;
+
+	/* The offset applied to each of the register base addresses, OMAP4
+	 * register sets are offset 0x100 from the OMAP3 series.
+	 */
+	unsigned long		sc_reg_off;
+
+	/* The physical address of the MMCHS_DATA register, used for the DMA xfers */
+	unsigned long		sc_data_reg_paddr;
+
+	/* The reference clock frequency */
+	unsigned int		sc_ref_freq;
+
+	enum mmc_power_mode	sc_cur_power_mode;
+};
+
+/**
+ *	Macros for driver mutex locking
+ */
+#define OMAP_MMC_LOCK(_sc)              mtx_lock(&(_sc)->sc_mtx)
+#define	OMAP_MMC_UNLOCK(_sc)            mtx_unlock(&(_sc)->sc_mtx)
+#define OMAP_MMC_LOCK_INIT(_sc) \
+	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \
+	         "omap_mmc", MTX_DEF)
+#define OMAP_MMC_LOCK_DESTROY(_sc)      mtx_destroy(&_sc->sc_mtx);
+#define OMAP_MMC_ASSERT_LOCKED(_sc)     mtx_assert(&_sc->sc_mtx, MA_OWNED);
+#define OMAP_MMC_ASSERT_UNLOCKED(_sc)   mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
+
+static void omap_mmc_start(struct omap_mmc_softc *sc);
+
+/**
+ *	omap_mmc_read_4 - reads a 32-bit value from a register
+ *	omap_mmc_write_4 - writes a 32-bit value to a register
+ *	@sc: pointer to the driver context
+ *	@off: register offset to read from
+ *	@val: the value to write into the register
+ *
+ *	LOCKING:
+ *	None
+ *
+ *	RETURNS:
+ *	The 32-bit value read from the register
+ */
+static inline uint32_t
+omap_mmc_read_4(struct omap_mmc_softc *sc, bus_size_t off)
+{
+	return bus_read_4(sc->sc_mem_res, (sc->sc_reg_off + off));
+}
+
+static inline void
+omap_mmc_write_4(struct omap_mmc_softc *sc, bus_size_t off, uint32_t val)
+{
+	bus_write_4(sc->sc_mem_res, (sc->sc_reg_off + off), val);
+}
+
+/**
+ *	omap_mmc_reset_controller -
+ *	@arg: caller supplied arg
+ *	@segs: array of segments (although in our case should only be one)
+ *	@nsegs: number of segments (in our case should be 1)
+ *	@error:
+ *
+ *
+ *
+ */
+static void
+omap_mmc_reset_controller(struct omap_mmc_softc *sc, uint32_t bit)
+{
+	unsigned long attempts;
+	uint32_t sysctl;
+
+	omap_mmc_dbg(sc, "reseting controller - bit 0x%08x\n", bit);
+
+	sysctl = omap_mmc_read_4(sc, MMCHS_SYSCTL);
+	omap_mmc_write_4(sc, MMCHS_SYSCTL, sysctl | bit);
+
+	if ((ti_chip() == CHIP_OMAP_4) && (ti_revision() > OMAP4430_REV_ES1_0)) {
+		/* OMAP4 ES2 and greater has an updated reset logic.
+		 * Monitor a 0->1 transition first
+		 */
+		attempts = 10000;
+		while (!(omap_mmc_read_4(sc, MMCHS_SYSCTL) & bit) && (attempts-- > 0))
+			continue;
+	}
+
+	attempts = 10000;
+	while ((omap_mmc_read_4(sc, MMCHS_SYSCTL) & bit) && (attempts-- > 0))
+		continue;
+
+	if (omap_mmc_read_4(sc, MMCHS_SYSCTL) & bit)
+		device_printf(sc->sc_dev, "Error - Timeout waiting on controller reset\n");
+}
+
+/**
+ *	omap_mmc_getaddr - called by the DMA function to simply return the phys addr
+ *	@arg: caller supplied arg
+ *	@segs: array of segments (although in our case should only be one)
+ *	@nsegs: number of segments (in our case should be 1)
+ *	@error:
+ *
+ *	This function is called by bus_dmamap_load() after it has compiled an array
+ *	of segments, each segment is a phsyical chunk of memory. However in our case
+ *	we should only have one segment, because we don't (yet?) support DMA scatter
+ *	gather. To ensure we only have one segment, the DMA tag was created by
+ *	bus_dma_tag_create() (called from omap_mmc_attach) with nsegments set to 1.
+ *
+ */
+static void
+omap_mmc_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
+{
+	if (error != 0)
+		return;
+
+	*(bus_addr_t *)arg = segs[0].ds_addr;
+}
+
+/**
+ *	omap_mmc_dma_intr - interrupt handler for DMA events triggered by the controller
+ *	@ch: the dma channel number
+ *	@status: bit field of the status bytes
+ *	@data: callback data, in this case a pointer to the controller struct
+ *
+ *
+ *	LOCKING:
+ *	Called from interrupt context
+ *
+ */
+static void
+omap_mmc_dma_intr(unsigned int ch, uint32_t status, void *data)
+{
+	/* Ignore for now ... we don't need this interrupt as we already have the
+	 * interrupt from the MMC controller.
+	 */
+}
+
+/**
+ *	omap_mmc_intr_xfer_compl - called if a 'transfer complete' IRQ was received
+ *	@sc: pointer to the driver context
+ *	@cmd: the command that was sent previously
+ *
+ *	This function is simply responsible for syncing up the DMA buffer.
+ *
+ *	LOCKING:
+ *	Called from interrupt context
+ *
+ *	RETURNS:
+ *	Return value indicates if the transaction is complete, not done = 0, done != 0
+ */
+static int
+omap_mmc_intr_xfer_compl(struct omap_mmc_softc *sc, struct mmc_command *cmd)
+{
+	uint32_t cmd_reg;
+
+	/* Read command register to test whether this command was a read or write. */
+	cmd_reg = omap_mmc_read_4(sc, MMCHS_CMD);
+
+	/* Sync-up the DMA buffer so the caller can access the new memory */
+	if (cmd_reg & MMCHS_CMD_DDIR) {
+		bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, BUS_DMASYNC_POSTREAD);
+		bus_dmamap_unload(sc->sc_dmatag, sc->sc_dmamap);
+	}
+	else {
+		bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, BUS_DMASYNC_POSTWRITE);
+		bus_dmamap_unload(sc->sc_dmatag, sc->sc_dmamap);
+	}
+	sc->sc_dmamapped--;
+
+	/* Debugging dump of the data received */
+#if 0
+	{
+		int i;
+		uint8_t *p = (uint8_t*) sc->sc_cmd_data_vaddr;
+		for (i=0; i<sc->sc_cmd_data_len; i++) {
+			if ((i % 16) == 0)
+				printf("\n0x%04x : ", i);
+			printf("%02X ", *p++);
+		}
+		printf("\n");
+	}
+#endif
+
+	/* We are done, transfer complete */
+	return 1;
+}
+
+/**
+ *	omap_mmc_intr_cmd_compl - called if a 'command complete' IRQ was received
+ *	@sc: pointer to the driver context
+ *	@cmd: the command that was sent previously
+ *
+ *
+ *	LOCKING:
+ *	Called from interrupt context
+ *
+ *	RETURNS:
+ *	Return value indicates if the transaction is complete, not done = 0, done != 0
+ */
+static int
+omap_mmc_intr_cmd_compl(struct omap_mmc_softc *sc, struct mmc_command *cmd)
+{
+	uint32_t cmd_reg;
+
+	/* Copy the response into the request struct ... if a response was
+	 * expected */
+	if (cmd != NULL && (cmd->flags & MMC_RSP_PRESENT)) {
+		if (cmd->flags & MMC_RSP_136) {
+			cmd->resp[3] = omap_mmc_read_4(sc, MMCHS_RSP10);
+			cmd->resp[2] = omap_mmc_read_4(sc, MMCHS_RSP32);
+			cmd->resp[1] = omap_mmc_read_4(sc, MMCHS_RSP54);
+			cmd->resp[0] = omap_mmc_read_4(sc, MMCHS_RSP76);
+		} else {
+			cmd->resp[0] = omap_mmc_read_4(sc, MMCHS_RSP10);
+		}
+	}
+
+	/* Check if the command was expecting some data transfer, if not
+	 * we are done. */
+	cmd_reg = omap_mmc_read_4(sc, MMCHS_CMD);
+	return ((cmd_reg & MMCHS_CMD_DP) == 0);
+}
+
+/**
+ *	omap_mmc_intr_error - handles error interrupts
+ *	@sc: pointer to the driver context
+ *	@cmd: the command that was sent previously
+ *	@stat_reg: the value that was in the status register
+ *
+ *
+ *	LOCKING:
+ *	Called from interrupt context
+ *
+ *	RETURNS:
+ *	Return value indicates if the transaction is complete, not done = 0, done != 0
+ */
+static int
+omap_mmc_intr_error(struct omap_mmc_softc *sc, struct mmc_command *cmd,
+					 uint32_t stat_reg)
+{
+	omap_mmc_dbg(sc, "error in xfer - stat 0x%08x\n", stat_reg);
+
+	/* Ignore CRC errors on CMD2 and ACMD47, per relevant standards */
+	if ((stat_reg & MMCHS_STAT_CCRC) && (cmd->opcode == MMC_SEND_OP_COND ||
+	    cmd->opcode == ACMD_SD_SEND_OP_COND))
+		cmd->error = MMC_ERR_NONE;
+	else if (stat_reg & (MMCHS_STAT_CTO | MMCHS_STAT_DTO))
+		cmd->error = MMC_ERR_TIMEOUT;
+	else if (stat_reg & (MMCHS_STAT_CCRC | MMCHS_STAT_DCRC))
+		cmd->error = MMC_ERR_BADCRC;
+	else
+		cmd->error = MMC_ERR_FAILED;
+
+	/* If a dma transaction we should also stop the dma transfer */
+	if (omap_mmc_read_4(sc, MMCHS_CMD) & MMCHS_CMD_DE) {
+
+		/* Abort the DMA transfer (DDIR bit tells direction) */
+		if (omap_mmc_read_4(sc, MMCHS_CMD) & MMCHS_CMD_DDIR)
+			omap_dma_stop_xfer(sc->sc_dmach_rd);
+		else
+			omap_dma_stop_xfer(sc->sc_dmach_wr);
+
+		/* If an error occure abort the DMA operation and free the dma map */
+		if ((sc->sc_dmamapped > 0) && (cmd->error != MMC_ERR_NONE)) {
+			bus_dmamap_unload(sc->sc_dmatag, sc->sc_dmamap);
+			sc->sc_dmamapped--;
+		}
+	}
+
+	/* Command error occured? ... if so issue a soft reset for the cmd fsm */
+	if (stat_reg & (MMCHS_STAT_CCRC | MMCHS_STAT_CTO)) {
+		omap_mmc_reset_controller(sc, MMCHS_SYSCTL_SRC);
+	}
+
+	/* Data error occured? ... if so issue a soft reset for the data line */
+	if (stat_reg & (MMCHS_STAT_DEB | MMCHS_STAT_DCRC | MMCHS_STAT_DTO)) {
+		omap_mmc_reset_controller(sc, MMCHS_SYSCTL_SRD);
+	}
+
+	/* On any error the command is cancelled ... so we are done */
+	return 1;
+}
+
+/**
+ *	omap_mmc_intr - interrupt handler for MMC/SD/SDIO controller
+ *	@arg: pointer to the driver context
+ *
+ *	Interrupt handler for the MMC/SD/SDIO controller, responsible for handling
+ *	the IRQ and clearing the status flags.
+ *
+ *	LOCKING:
+ *	Called from interrupt context
+ *
+ *	RETURNS:
+ *	nothing
+ */
+static void
+omap_mmc_intr(void *arg)
+{
+	struct omap_mmc_softc *sc = (struct omap_mmc_softc *) arg;
+	uint32_t stat_reg;
+	int done = 0;
+
+	OMAP_MMC_LOCK(sc);
+
+	stat_reg = omap_mmc_read_4(sc, MMCHS_STAT)
+	    & (omap_mmc_read_4(sc, MMCHS_IE) | MMCHS_STAT_ERRI);
+
+	if (sc->curcmd == NULL) {
+		device_printf(sc->sc_dev, "Error: current cmd NULL, already done?\n");
+		omap_mmc_write_4(sc, MMCHS_STAT, stat_reg);
+		OMAP_MMC_UNLOCK(sc);
+		return;
+	}
+
+	if (stat_reg & MMCHS_STAT_ERRI) {
+		/* An error has been tripped in the status register */
+		done = omap_mmc_intr_error(sc, sc->curcmd, stat_reg);
+
+	} else {
+
+		/* NOTE: This implementation could be a bit inefficent, I don't think
+		 * it is necessary to handle both the 'command complete' and 'transfer
+		 * complete' for data transfers ... presumably just transfer complete
+		 * is enough.
+		 */
+
+		/* No error */
+		sc->curcmd->error = MMC_ERR_NONE;
+
+		/* Check if the command completed */
+		if (stat_reg & MMCHS_STAT_CC) {
+			done = omap_mmc_intr_cmd_compl(sc, sc->curcmd);
+		}
+
+		/* Check if the transfer has completed */
+		if (stat_reg & MMCHS_STAT_TC) {
+			done = omap_mmc_intr_xfer_compl(sc, sc->curcmd);
+		}
+
+	}
+
+	/* Clear all the interrupt status bits by writing the value back */
+	omap_mmc_write_4(sc, MMCHS_STAT, stat_reg);
+
+	/* This may mark the command as done if there is no stop request */
+	/* TODO: This is a bit ugly, needs fix-up */
+	if (done) {
+		omap_mmc_start(sc);
+	}
+
+	OMAP_MMC_UNLOCK(sc);
+}
+
+/**
+ *	omap_mmc_start_cmd - starts the given command
+ *	@sc: pointer to the driver context
+ *	@cmd: the command to start
+ *
+ *	The call tree for this function is
+ *		- omap_mmc_start_cmd
+ *			- omap_mmc_start
+ *				- omap_mmc_request
+ *
+ *	LOCKING:
+ *	Caller should be holding the OMAP_MMC lock.
+ *
+ *	RETURNS:
+ *	nothing
+ */
+static void
+omap_mmc_start_cmd(struct omap_mmc_softc *sc, struct mmc_command *cmd)
+{
+	uint32_t cmd_reg, con_reg, ise_reg;
+	struct mmc_data *data;
+	struct mmc_request *req;
+	void *vaddr;
+	bus_addr_t paddr;
+	uint32_t pktsize;
+
+	sc->curcmd = cmd;
+	data = cmd->data;
+	req = cmd->mrq;
+
+	/* Ensure the STR and MIT bits are cleared, these are only used for special
+	 * command types.
+	 */
+	con_reg = omap_mmc_read_4(sc, MMCHS_CON);
+	con_reg &= ~(MMCHS_CON_STR | MMCHS_CON_MIT);
+
+	/* Load the command into bits 29:24 of the CMD register */
+	cmd_reg = (uint32_t)(cmd->opcode & 0x3F) << 24;
+
+	/* Set the default set of interrupts */
+	ise_reg = (MMCHS_STAT_CERR | MMCHS_STAT_CTO | MMCHS_STAT_CC | MMCHS_STAT_CEB);
+
+	/* Enable CRC checking if requested */
+	if (cmd->flags & MMC_RSP_CRC)
+		ise_reg |= MMCHS_STAT_CCRC;
+
+	/* Enable reply index checking if the response supports it */
+	if (cmd->flags & MMC_RSP_OPCODE)
+		ise_reg |= MMCHS_STAT_CIE;
+
+	/* Set the expected response length */
+	if (MMC_RSP(cmd->flags) == MMC_RSP_NONE) {
+		cmd_reg |= MMCHS_CMD_RSP_TYPE_NO;
+	} else {
+		if (cmd->flags & MMC_RSP_136)
+			cmd_reg |= MMCHS_CMD_RSP_TYPE_136;
+		else if (cmd->flags & MMC_RSP_BUSY)
+			cmd_reg |= MMCHS_CMD_RSP_TYPE_48_BSY;
+		else
+			cmd_reg |= MMCHS_CMD_RSP_TYPE_48;
+
+		/* Enable command index/crc checks if necessary expected */
+		if (cmd->flags & MMC_RSP_CRC)
+			cmd_reg |= MMCHS_CMD_CCCE;
+		if (cmd->flags & MMC_RSP_OPCODE)
+			cmd_reg |= MMCHS_CMD_CICE;
+	}
+
+	/* Set the bits for the special commands CMD12 (MMC_STOP_TRANSMISSION) and
+	 * CMD52 (SD_IO_RW_DIRECT) */
+	if (cmd->opcode == MMC_STOP_TRANSMISSION)
+		cmd_reg |= MMCHS_CMD_CMD_TYPE_IO_ABORT;
+
+	/* Check if there is any data to write */
+	if (data == NULL) {
+		/* Clear the block count */
+		omap_mmc_write_4(sc, MMCHS_BLK, 0);
+
+		/* The no data case is fairly simple */
+		omap_mmc_write_4(sc, MMCHS_CON, con_reg);
+		omap_mmc_write_4(sc, MMCHS_IE, ise_reg);
+		omap_mmc_write_4(sc, MMCHS_ISE, ise_reg);
+		omap_mmc_write_4(sc, MMCHS_ARG, cmd->arg);
+		omap_mmc_write_4(sc, MMCHS_CMD, cmd_reg);
+		return;
+	}
+
+	/* Indicate that data is present */
+	cmd_reg |= MMCHS_CMD_DP | MMCHS_CMD_MSBS | MMCHS_CMD_BCE;
+
+	/* Indicate a read operation */
+	if (data->flags & MMC_DATA_READ)
+		cmd_reg |= MMCHS_CMD_DDIR;
+
+	/* Streaming mode */
+	if (data->flags & MMC_DATA_STREAM) {
+		con_reg |= MMCHS_CON_STR;
+	}
+
+	/* Multi-block mode */
+	if (data->flags & MMC_DATA_MULTI) {
+		cmd_reg |= MMCHS_CMD_MSBS;
+	}
+
+	/* Enable extra interrupt sources for the transfer */
+	ise_reg |= (MMCHS_STAT_TC | MMCHS_STAT_DTO | MMCHS_STAT_DEB | MMCHS_STAT_CEB);
+	if (cmd->flags & MMC_RSP_CRC)
+		ise_reg |= MMCHS_STAT_DCRC;
+
+	/* Enable the DMA transfer bit */
+	cmd_reg |= MMCHS_CMD_DE;
+
+	/* Set the block size and block count */
+	omap_mmc_write_4(sc, MMCHS_BLK, (1 << 16) | data->len);
+
+	/* Setup the DMA stuff */
+	if (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) {
+
+		vaddr = data->data;
+		data->xfer_len = 0;
+
+		/* Map the buffer buf into bus space using the dmamap map. */
+		if (bus_dmamap_load(sc->sc_dmatag, sc->sc_dmamap, vaddr, data->len,
+		    omap_mmc_getaddr, &paddr, 0) != 0) {
+
+			if (req->cmd->flags & STOP_STARTED)
+				req->stop->error = MMC_ERR_NO_MEMORY;
+			else
+				req->cmd->error = MMC_ERR_NO_MEMORY;
+			sc->req = NULL;
+			sc->curcmd = NULL;
+			req->done(req);
+			return;
+		}
+
+		/* Calculate the packet size, the max packet size is 512 bytes
+		 * (or 128 32-bit elements).
+		 */
+		pktsize = min((data->len / 4), (512 / 4));
+
+		/* Sync the DMA buffer and setup the DMA controller */
+		if (data->flags & MMC_DATA_READ) {
+			bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, BUS_DMASYNC_PREREAD);
+			omap_dma_start_xfer_packet(sc->sc_dmach_rd, sc->sc_data_reg_paddr,
+			    paddr, 1, (data->len / 4), pktsize);
+		} else {
+			bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, BUS_DMASYNC_PREWRITE);
+			omap_dma_start_xfer_packet(sc->sc_dmach_wr, paddr,
+			    sc->sc_data_reg_paddr, 1, (data->len / 4), pktsize);
+		}
+
+		/* Increase the mapped count */
+		sc->sc_dmamapped++;
+
+		sc->sc_cmd_data_vaddr = vaddr;
+		sc->sc_cmd_data_len = data->len;
+	}
+
+	/* Finally kick off the command */
+	omap_mmc_write_4(sc, MMCHS_CON, con_reg);
+	omap_mmc_write_4(sc, MMCHS_IE, ise_reg);
+	omap_mmc_write_4(sc, MMCHS_ISE, ise_reg);
+	omap_mmc_write_4(sc, MMCHS_ARG, cmd->arg);
+	omap_mmc_write_4(sc, MMCHS_CMD, cmd_reg);
+
+	/* and we're done */
+}
+
+/**
+ *	omap_mmc_start - starts a request stored in the driver context
+ *	@sc: pointer to the driver context
+ *
+ *	This function is called by omap_mmc_request() in response to a read/write
+ *	request from the MMC core module.
+ *
+ *	LOCKING:
+ *	Caller should be holding the OMAP_MMC lock.
+ *
+ *	RETURNS:
+ *	nothing
+ */
+static void
+omap_mmc_start(struct omap_mmc_softc *sc)
+{
+	struct mmc_request *req;
+
+	/* Sanity check we have a request */
+	req = sc->req;
+	if (req == NULL)
+		return;
+
+	/* assert locked */
+	if (!(sc->flags & CMD_STARTED)) {
+		sc->flags |= CMD_STARTED;
+		omap_mmc_start_cmd(sc, req->cmd);
+		return;
+	}
+
+	if (!(sc->flags & STOP_STARTED) && req->stop) {
+		sc->flags |= STOP_STARTED;
+		omap_mmc_start_cmd(sc, req->stop);
+		return;
+	}
+
+	/* We must be done -- bad idea to do this while locked? */
+	sc->req = NULL;
+	sc->curcmd = NULL;
+	req->done(req);
+}
+
+/**
+ *	omap_mmc_request - entry point for all read/write/cmd requests
+ *	@brdev: mmc bridge device handle
+ *	@reqdev: the device doing the requesting ?
+ *	@req: the action requested
+ *
+ *	LOCKING:
+ *	None, internally takes the OMAP_MMC lock.
+ *
+ *	RETURNS:
+ *	0 on success
+ *	EBUSY if the driver is already performing a request
+ */
+static int
+omap_mmc_request(device_t brdev, device_t reqdev, struct mmc_request *req)
+{
+	struct omap_mmc_softc *sc = device_get_softc(brdev);
+
+	OMAP_MMC_LOCK(sc);
+
+	/*
+	 * XXX do we want to be able to queue up multiple commands?
+	 * XXX sounds like a good idea, but all protocols are sync, so
+	 * XXX maybe the idea is naive...
+	 */
+	if (sc->req != NULL) {
+		OMAP_MMC_UNLOCK(sc);
+		return (EBUSY);
+	}
+
+	/* Store the request and start the command */
+	sc->req = req;
+	sc->flags = 0;
+	omap_mmc_start(sc);
+
+	OMAP_MMC_UNLOCK(sc);
+
+	return (0);
+}
+
+/**
+ *	omap_mmc_get_ro - returns the status of the read-only setting
+ *	@brdev: mmc bridge device handle
+ *	@reqdev: device doing the request
+ *
+ *	This function is relies on hint'ed values to determine which GPIO is used
+ *	to determine if the write protect is enabled. On the BeagleBoard the pin
+ *	is GPIO_23.
+ *
+ *	LOCKING:
+ *	-
+ *
+ *	RETURNS:
+ *	0 if not read-only
+ *	1 if read only
+ */
+static int
+omap_mmc_get_ro(device_t brdev, device_t reqdev)
+{
+	struct omap_mmc_softc *sc = device_get_softc(brdev);
+	unsigned int readonly = 0;
+
+	OMAP_MMC_LOCK(sc);
+
+	if ((sc->sc_wp_gpio_pin != -1) && (sc->sc_gpio_dev != NULL)) {
+		if (GPIO_PIN_GET(sc->sc_gpio_dev, sc->sc_wp_gpio_pin, &readonly) != 0)
+			readonly = 0;
+		else
+			readonly = (readonly == 0) ? 0 : 1;
+	}
+
+	OMAP_MMC_UNLOCK(sc);
+
+	return (readonly);
+}
+
+/**
+ *	omap_mmc_send_init_stream - sets bus/controller settings
+ *	@brdev: mmc bridge device handle
+ *	@reqdev: device doing the request
+ *
+ *	Send init stream sequence to card before sending IDLE command
+ *
+ *	LOCKING:
+ *
+ *
+ *	RETURNS:
+ *	0 if function succeeded
+ */
+static void
+omap_mmc_send_init_stream(struct omap_mmc_softc *sc)
+{
+	unsigned long timeout;
+	uint32_t ie, ise, con;
+
+	omap_mmc_dbg(sc, "Performing init sequence\n");
+
+	/* Prior to issuing any command, the MMCHS controller has to execute a
+	 * special INIT procedure. The MMCHS controller has to generate a clock
+	 * during 1ms. During the INIT procedure, the MMCHS controller generates 80
+	 * clock periods. In order to keep the 1ms gap, the MMCHS controller should
+	 * be configured to generate a clock whose frequency is smaller or equal to
+	 * 80 KHz. If the MMCHS controller divider bitfield width doesn't allow to
+	 * choose big values, the MMCHS controller driver should perform the INIT
+	 * procedure twice or three times. Twice is generally enough.
+	 *
+	 * The INIt procedure is executed by setting MMCHS1.MMCHS_CON[1] INIT
+	 * bitfield to 1 and by sending a dummy command, writing 0x00000000 in
+	 * MMCHS1.MMCHS_CMD register.
+	 */
+
+	/* Disable interrupt status events but enable interrupt generation.
+	 * This doesn't seem right to me, but if the interrupt generation is not
+	 * enabled the CC bit doesn't seem to be set in the STAT register.
+	 */
+
+	/* Enable interrupt generation */
+	ie = omap_mmc_read_4(sc, MMCHS_IE);
+	omap_mmc_write_4(sc, MMCHS_IE, 0x307F0033);
+
+	/* Disable generation of status events (stops interrupt triggering) */
+	ise = omap_mmc_read_4(sc, MMCHS_ISE);
+	omap_mmc_write_4(sc, MMCHS_ISE, 0);
+
+	/* Set the initialise stream bit */
+	con = omap_mmc_read_4(sc, MMCHS_CON);
+	con |= MMCHS_CON_INIT;
+	omap_mmc_write_4(sc, MMCHS_CON, con);
+
+	/* Write a dummy command 0x00 */
+	omap_mmc_write_4(sc, MMCHS_CMD, 0x00000000);
+
+	/* Loop waiting for the command to finish */
+	timeout = hz;
+	do {
+		pause("MMCINIT", 1);
+		if (timeout-- == 0) {
+			device_printf(sc->sc_dev, "Error: first stream init timed out\n");
+			break;
+		}
+	} while (!(omap_mmc_read_4(sc, MMCHS_STAT) & MMCHS_STAT_CC));
+
+	/* Clear the command complete status bit */
+	omap_mmc_write_4(sc, MMCHS_STAT, MMCHS_STAT_CC);
+
+	/* Write another dummy command 0x00 */
+	omap_mmc_write_4(sc, MMCHS_CMD, 0x00000000);
+
+	/* Loop waiting for the second command to finish */
+	timeout = hz;
+	do {
+		pause("MMCINIT", 1);
+		if (timeout-- == 0) {
+			device_printf(sc->sc_dev, "Error: second stream init timed out\n");
+			break;
+		}
+	} while (!(omap_mmc_read_4(sc, MMCHS_STAT) & MMCHS_STAT_CC));
+
+	/* Clear the stream init bit */
+	con &= ~MMCHS_CON_INIT;
+	omap_mmc_write_4(sc, MMCHS_CON, con);
+
+	/* Clear the status register, then restore the IE and ISE registers */
+	omap_mmc_write_4(sc, MMCHS_STAT, 0xffffffff);
+	omap_mmc_read_4(sc, MMCHS_STAT);
+
+	omap_mmc_write_4(sc, MMCHS_ISE, ise);
+	omap_mmc_write_4(sc, MMCHS_IE, ie);
+}
+
+/**
+ *	omap_mmc_update_ios - sets bus/controller settings
+ *	@brdev: mmc bridge device handle
+ *	@reqdev: device doing the request
+ *
+ *	Called to set the bus and controller settings that need to be applied to
+ *	the actual HW.  Currently this function just sets the bus width and the
+ *	clock speed.
+ *
+ *	LOCKING:
+ *
+ *
+ *	RETURNS:
+ *	0 if function succeeded
+ */
+static int
+omap_mmc_update_ios(device_t brdev, device_t reqdev)
+{
+	struct omap_mmc_softc *sc;
+	struct mmc_host *host;
+	struct mmc_ios *ios;
+	uint32_t clkdiv;
+	uint32_t hctl_reg;
+	uint32_t con_reg;
+	uint32_t sysctl_reg;
+	uint16_t mv;
+	unsigned long timeout;
+	int do_card_init = 0;
+
+	sc = device_get_softc(brdev);
+	host = &sc->host;
+	ios = &host->ios;
+
+	/* Read the initial values of the registers */
+	hctl_reg = omap_mmc_read_4(sc, MMCHS_HCTL);
+	con_reg = omap_mmc_read_4(sc, MMCHS_CON);
+
+	/* Set the bus width */
+	switch (ios->bus_width) {
+		case bus_width_1:
+			hctl_reg &= ~MMCHS_HCTL_DTW;
+			con_reg &= ~MMCHS_CON_DW8;
+			break;
+		case bus_width_4:
+			hctl_reg |= MMCHS_HCTL_DTW;
+			con_reg &= ~MMCHS_CON_DW8;
+			break;
+		case bus_width_8:
+			con_reg |= MMCHS_CON_DW8;
+			break;
+	}
+
+	/* Finally write all these settings back to the registers */
+	omap_mmc_write_4(sc, MMCHS_HCTL, hctl_reg);
+	omap_mmc_write_4(sc, MMCHS_CON, con_reg);
+
+	/* Check if we need to change the external voltage regulator */
+	if (sc->sc_cur_power_mode != ios->power_mode) {
+
+		if (ios->power_mode == power_up) {
+
+			/* Set the power level */
+			hctl_reg = omap_mmc_read_4(sc, MMCHS_HCTL);
+			hctl_reg &= ~(MMCHS_HCTL_SDVS_MASK | MMCHS_HCTL_SDBP);
+
+			if ((ios->vdd == -1) || (ios->vdd >= vdd_240)) {
+				mv = 3000;
+				hctl_reg |= MMCHS_HCTL_SDVS_V30;
+			} else {
+				mv = 1800;
+				hctl_reg |= MMCHS_HCTL_SDVS_V18;
+			}
+
+			omap_mmc_write_4(sc, MMCHS_HCTL, hctl_reg);
+
+#ifdef TWL_SUPPORTED
+			/* Set the desired voltage on the regulator */
+			if (sc->sc_vreg_dev && sc->sc_vreg_name)
+				twl_vreg_set_voltage(sc->sc_vreg_dev, sc->sc_vreg_name, mv);
+#endif
+
+			/* Enable the bus power */
+			omap_mmc_write_4(sc, MMCHS_HCTL, (hctl_reg | MMCHS_HCTL_SDBP));
+			timeout = hz;
+			while (!(omap_mmc_read_4(sc, MMCHS_HCTL) & MMCHS_HCTL_SDBP)) {
+				if (timeout-- == 0)
+					break;
+				pause("MMC_PWRON", 1);
+			}
+
+		} else if (ios->power_mode == power_off) {
+			/* Disable the bus power */
+			hctl_reg = omap_mmc_read_4(sc, MMCHS_HCTL);
+			omap_mmc_write_4(sc, MMCHS_HCTL, (hctl_reg & ~MMCHS_HCTL_SDBP));
+
+#ifdef TWL_SUPPORTED
+			/* Turn the power off on the voltage regulator */
+			if (sc->sc_vreg_dev && sc->sc_vreg_name)
+				twl_vreg_set_voltage(sc->sc_vreg_dev, sc->sc_vreg_name, 0);
+#endif
+
+		} else if (ios->power_mode == power_on) {
+			/* Force a card re-initialisation sequence */
+			do_card_init = 1;
+		}
+
+		/* Save the new power state */
+		sc->sc_cur_power_mode = ios->power_mode;
+	}
+
+	/* need the MMCHS_SYSCTL register */
+	sysctl_reg = omap_mmc_read_4(sc, MMCHS_SYSCTL);
+
+	/* Just in case this hasn't been setup before, set the timeout to the default */
+	sysctl_reg &= ~MMCHS_SYSCTL_DTO_MASK;
+	sysctl_reg |= MMCHS_SYSCTL_DTO(0xe);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-projects mailing list