Date: Tue, 21 Feb 2012 02:32:46 +0000 (UTC) From: Oleksandr Tymoshenko <gonzo@FreeBSD.org> To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r231956 - in projects/armv6/sys: arm/ti arm/ti/omap4 dev/mmc Message-ID: <201202210232.q1L2Wkxc078437@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
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@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@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 ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201202210232.q1L2Wkxc078437>