From owner-svn-src-head@FreeBSD.ORG Sat May 26 21:07:16 2012 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id 704AE106566B; Sat, 26 May 2012 21:07:16 +0000 (UTC) (envelope-from raj@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 5A86E8FC12; Sat, 26 May 2012 21:07:16 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q4QL7GeU006361; Sat, 26 May 2012 21:07:16 GMT (envelope-from raj@svn.freebsd.org) Received: (from raj@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q4QL7Gt0006359; Sat, 26 May 2012 21:07:16 GMT (envelope-from raj@svn.freebsd.org) Message-Id: <201205262107.q4QL7Gt0006359@svn.freebsd.org> From: Rafal Jaworowski Date: Sat, 26 May 2012 21:07:16 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r236121 - head/sys/powerpc/mpc85xx X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 26 May 2012 21:07:16 -0000 Author: raj Date: Sat May 26 21:07:15 2012 New Revision: 236121 URL: http://svn.freebsd.org/changeset/base/236121 Log: Import eSDHC driver for Freescale integrated controller. Obtained from: Freescale, Semihalf Written by: Michal Dubiel Added: head/sys/powerpc/mpc85xx/fsl_sdhc.c (contents, props changed) head/sys/powerpc/mpc85xx/fsl_sdhc.h (contents, props changed) Added: head/sys/powerpc/mpc85xx/fsl_sdhc.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/powerpc/mpc85xx/fsl_sdhc.c Sat May 26 21:07:15 2012 (r236121) @@ -0,0 +1,1306 @@ +/*- + * Copyright (c) 2011-2012 Semihalf + * 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 THE 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 THE 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 Freescale integrated eSDHC controller. + * Limitations: + * - No support for multi-block transfers. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "opt_platform.h" + +#include "mmcbr_if.h" + +#include "fsl_sdhc.h" + +#define DEBUG +#undef DEBUG +#ifdef DEBUG +#define DPRINTF(fmt, arg...) printf("DEBUG %s(): " fmt, __FUNCTION__, ##arg) +#else +#define DPRINTF(fmt, arg...) +#endif + + +/***************************************************************************** + * Register the driver + *****************************************************************************/ +/* Forward declarations */ +static int fsl_sdhc_probe(device_t); +static int fsl_sdhc_attach(device_t); +static int fsl_sdhc_detach(device_t); + +static int fsl_sdhc_read_ivar(device_t, device_t, int, uintptr_t *); +static int fsl_sdhc_write_ivar(device_t, device_t, int, uintptr_t); + +static int fsl_sdhc_update_ios(device_t, device_t); +static int fsl_sdhc_request(device_t, device_t, struct mmc_request *); +static int fsl_sdhc_get_ro(device_t, device_t); +static int fsl_sdhc_acquire_host(device_t, device_t); +static int fsl_sdhc_release_host(device_t, device_t); + +static device_method_t fsl_sdhc_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, fsl_sdhc_probe), + DEVMETHOD(device_attach, fsl_sdhc_attach), + DEVMETHOD(device_detach, fsl_sdhc_detach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, fsl_sdhc_read_ivar), + DEVMETHOD(bus_write_ivar, fsl_sdhc_write_ivar), + + /* OFW bus interface */ + DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), + DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), + DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), + DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), + DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), + + /* mmcbr_if */ + DEVMETHOD(mmcbr_update_ios, fsl_sdhc_update_ios), + DEVMETHOD(mmcbr_request, fsl_sdhc_request), + DEVMETHOD(mmcbr_get_ro, fsl_sdhc_get_ro), + DEVMETHOD(mmcbr_acquire_host, fsl_sdhc_acquire_host), + DEVMETHOD(mmcbr_release_host, fsl_sdhc_release_host), + + {0, 0}, +}; + +/* kobj_class definition */ +static driver_t fsl_sdhc_driver = { + "sdhci", + fsl_sdhc_methods, + sizeof(struct fsl_sdhc_softc) +}; + +static devclass_t fsl_sdhc_devclass; + +DRIVER_MODULE(sdhci, simplebus, fsl_sdhc_driver, fsl_sdhc_devclass, 0, 0); + + +/***************************************************************************** + * Private methods + *****************************************************************************/ +static inline int +read4(struct fsl_sdhc_softc *sc, unsigned int offset) +{ + + return bus_space_read_4(sc->bst, sc->bsh, offset); +} + +static inline void +write4(struct fsl_sdhc_softc *sc, unsigned int offset, int value) +{ + + bus_space_write_4(sc->bst, sc->bsh, offset, value); +} + +static inline void +set_bit(struct fsl_sdhc_softc *sc, uint32_t offset, uint32_t mask) +{ + uint32_t x = read4(sc, offset); + + write4(sc, offset, x | mask); +} + +static inline void +clear_bit(struct fsl_sdhc_softc *sc, uint32_t offset, uint32_t mask) +{ + uint32_t x = read4(sc, offset); + + write4(sc, offset, x & ~mask); +} + +static int +wait_for_bit_clear(struct fsl_sdhc_softc *sc, enum sdhc_reg_off reg, + uint32_t bit) +{ + uint32_t timeout = 10; + uint32_t stat; + + stat = read4(sc, reg); + while (stat & bit) { + if (timeout == 0) { + return (-1); + } + --timeout; + DELAY(1000); + stat = read4(sc, reg); + } + + return (0); +} + +static int +wait_for_free_line(struct fsl_sdhc_softc *sc, enum sdhc_line line) +{ + uint32_t timeout = 100; + uint32_t stat; + + stat = read4(sc, SDHC_PRSSTAT); + while (stat & line) { + if (timeout == 0) { + return (-1); + } + --timeout; + DELAY(1000); + stat = read4(sc, SDHC_PRSSTAT); + } + + return (0); +} + +static uint32_t +get_platform_clock(struct fsl_sdhc_softc *sc) +{ + device_t self, parent; + phandle_t node; + uint32_t clock; + + self = sc->self; + node = ofw_bus_get_node(self); + + /* Get sdhci node properties */ + if((OF_getprop(node, "clock-frequency", (void *)&clock, + sizeof(clock)) <= 0) || (clock == 0)) { + + /* + * Trying to get clock from parent device (soc) if correct + * clock cannot be acquired from sdhci node. + */ + parent = device_get_parent(self); + node = ofw_bus_get_node(parent); + + /* Get soc properties */ + if ((OF_getprop(node, "bus-frequency", (void *)&clock, + sizeof(clock)) <= 0) || (clock == 0)) { + device_printf(self,"Cannot acquire correct sdhci " + "frequency from DTS.\n"); + + return (0); + } + } + + DPRINTF("Acquired clock: %d from DTS\n", clock); + + return (clock); +} + +/** + * Set clock driving card. + * @param sc + * @param clock Desired clock frequency in Hz + */ +static void +set_clock(struct fsl_sdhc_softc *sc, uint32_t clock) +{ + uint32_t base_clock; + uint32_t divisor, prescaler = 1; + uint32_t round = 0; + + if (clock == sc->slot.clock) + return; + + if (clock == 0) { + clear_bit(sc, SDHC_SYSCTL, MASK_CLOCK_CONTROL | SYSCTL_PEREN | + SYSCTL_HCKEN | SYSCTL_IPGEN); + return; + } + + base_clock = sc->platform_clock; + round = base_clock & 0x2; + base_clock >>= 2; + base_clock += round; + round = 0; + + /* SD specification 1.1 doesn't allow frequences above 50 MHz */ + if (clock > FSL_SDHC_MAX_CLOCK) + clock = FSL_SDHC_MAX_CLOCK; + + /* + * divisor = ceil(base_clock / clock) + * TODO: Reconsider symmetric rounding here instead of ceiling. + */ + divisor = (base_clock + clock - 1) / clock; + + while (divisor > 16) { + round = divisor & 0x1; + divisor >>= 1; + + prescaler <<= 1; + } + divisor += round - 1; + + /* Turn off the clock. */ + clear_bit(sc, SDHC_SYSCTL, MASK_CLOCK_CONTROL); + + /* Write clock settings. */ + set_bit(sc, SDHC_SYSCTL, (prescaler << SHIFT_SDCLKFS) | + (divisor << SHIFT_DVS)); + + /* + * Turn on clocks. + * TODO: This actually disables clock automatic gating off feature of + * the controller which eventually should be enabled but as for now + * it prevents controller from generating card insertion/removal + * interrupts correctly. + */ + set_bit(sc, SDHC_SYSCTL, SYSCTL_PEREN | SYSCTL_HCKEN | SYSCTL_IPGEN); + + sc->slot.clock = clock; + + DPRINTF("given clock = %d, computed clock = %d\n", clock, + (base_clock / prescaler) / (divisor + 1)); +} + +static inline void +send_80_clock_ticks(struct fsl_sdhc_softc *sc) +{ + int err; + + err = wait_for_free_line(sc, SDHC_CMD_LINE | SDHC_DAT_LINE); + if (err != 0) { + device_printf(sc->self, "Can't acquire data/cmd lines\n"); + return; + } + + set_bit(sc, SDHC_SYSCTL, SYSCTL_INITA); + err = wait_for_bit_clear(sc, SDHC_SYSCTL, SYSCTL_INITA); + if (err != 0) { + device_printf(sc->self, "Can't send 80 clocks to the card.\n"); + } +} + +static void +set_bus_width(struct fsl_sdhc_softc *sc, enum mmc_bus_width width) +{ + + DPRINTF("setting bus width to %d\n", width); + switch (width) { + case bus_width_1: + set_bit(sc, SDHC_PROCTL, DTW_1); + break; + case bus_width_4: + set_bit(sc, SDHC_PROCTL, DTW_4); + break; + case bus_width_8: + set_bit(sc, SDHC_PROCTL, DTW_8); + break; + default: + device_printf(sc->self, "Unsupported bus width\n"); + } +} + +static void +reset_controller_all(struct fsl_sdhc_softc *sc) +{ + uint32_t count = 5; + + set_bit(sc, SDHC_SYSCTL, SYSCTL_RSTA); + while (read4(sc, SDHC_SYSCTL) & SYSCTL_RSTA) { + DELAY(FSL_SDHC_RESET_DELAY); + --count; + if (count == 0) { + device_printf(sc->self, + "Can't reset the controller\n"); + return; + } + } +} + +static void +reset_controller_dat_cmd(struct fsl_sdhc_softc *sc) +{ + int err; + + set_bit(sc, SDHC_SYSCTL, SYSCTL_RSTD | SYSCTL_RSTC); + err = wait_for_bit_clear(sc, SDHC_SYSCTL, SYSCTL_RSTD | SYSCTL_RSTC); + if (err != 0) { + device_printf(sc->self, "Can't reset data & command part!\n"); + return; + } +} + +static void +init_controller(struct fsl_sdhc_softc *sc) +{ + + /* Enable interrupts. */ +#ifdef FSL_SDHC_NO_DMA + write4(sc, SDHC_IRQSTATEN, MASK_IRQ_ALL & ~IRQ_DINT & ~IRQ_DMAE); + write4(sc, SDHC_IRQSIGEN, MASK_IRQ_ALL & ~IRQ_DINT & ~IRQ_DMAE); +#else + write4(sc, SDHC_IRQSTATEN, MASK_IRQ_ALL & ~IRQ_BRR & ~IRQ_BWR); + write4(sc, SDHC_IRQSIGEN, MASK_IRQ_ALL & ~IRQ_BRR & ~IRQ_BWR); + + /* Write DMA address */ + write4(sc, SDHC_DSADDR, sc->dma_phys); + + /* Enable snooping and fix for AHB2MAG bypass. */ + write4(sc, SDHC_DCR, DCR_SNOOP | DCR_AHB2MAG_BYPASS); +#endif + /* Set data timeout. */ + set_bit(sc, SDHC_SYSCTL, 0xe << SHIFT_DTOCV); + + /* Set water-mark levels (FIFO buffer size). */ + write4(sc, SDHC_WML, (FSL_SDHC_FIFO_BUF_WORDS << 16) | + FSL_SDHC_FIFO_BUF_WORDS); +} + +static void +init_mmc_host_struct(struct fsl_sdhc_softc *sc) +{ + struct mmc_host *host = &sc->mmc_host; + + /* Clear host structure. */ + bzero(host, sizeof(struct mmc_host)); + + /* Calculate minimum and maximum operating frequencies. */ + host->f_min = sc->platform_clock / FSL_SDHC_MAX_DIV; + host->f_max = FSL_SDHC_MAX_CLOCK; + + /* Set operation conditions (voltage). */ + host->host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; + + /* Set additional host controller capabilities. */ + host->caps = MMC_CAP_4_BIT_DATA; + + /* Set mode. */ + host->mode = mode_sd; +} + +static void +card_detect_task(void *arg, int pending) +{ + struct fsl_sdhc_softc *sc = (struct fsl_sdhc_softc *)arg; + int err; + int insert; + + insert = read4(sc, SDHC_PRSSTAT) & PRSSTAT_CINS; + + mtx_lock(&sc->mtx); + + if (insert) { + if (sc->child != NULL) { + mtx_unlock(&sc->mtx); + return; + } + + sc->child = device_add_child(sc->self, "mmc", -1); + if (sc->child == NULL) { + device_printf(sc->self, "Couldn't add MMC bus!\n"); + mtx_unlock(&sc->mtx); + return; + } + + /* Initialize MMC bus host structure. */ + init_mmc_host_struct(sc); + device_set_ivars(sc->child, &sc->mmc_host); + + } else { + if (sc->child == NULL) { + mtx_unlock(&sc->mtx); + return; + } + } + + mtx_unlock(&sc->mtx); + + if (insert) { + if ((err = device_probe_and_attach(sc->child)) != 0) { + device_printf(sc->self, "MMC bus failed on probe " + "and attach! error %d\n", err); + device_delete_child(sc->self, sc->child); + sc->child = NULL; + } + } else { + if (device_delete_child(sc->self, sc->child) != 0) + device_printf(sc->self, "Could not delete MMC bus!\n"); + sc->child = NULL; + } +} + +static void +card_detect_delay(void *arg) +{ + struct fsl_sdhc_softc *sc = arg; + + taskqueue_enqueue(taskqueue_swi_giant, &sc->card_detect_task); +} + +static void +finalize_request(struct fsl_sdhc_softc *sc) +{ + + DPRINTF("finishing request %x\n", sc->request); + + sc->request->done(sc->request); + sc->request = NULL; +} + +/** + * Read response from card. + * @todo Implement Auto-CMD responses being held in R3 for multi-block xfers. + * @param sc + */ +static void +get_response(struct fsl_sdhc_softc *sc) +{ + struct mmc_command *cmd = sc->request->cmd; + int i; + uint32_t val; + uint8_t ext = 0; + + if (cmd->flags & MMC_RSP_136) { + /* CRC is stripped, need to shift one byte left. */ + for (i = 0; i < 4; i++) { + val = read4(sc, SDHC_CMDRSP0 + i * 4); + cmd->resp[3 - i] = (val << 8) + ext; + ext = val >> 24; + } + } else { + cmd->resp[0] = read4(sc, SDHC_CMDRSP0); + } +} + +#ifdef FSL_SDHC_NO_DMA +/** + * Read all content of a fifo buffer. + * @warning Assumes data buffer is 32-bit aligned. + * @param sc + */ +static void +read_block_pio(struct fsl_sdhc_softc *sc) +{ + struct mmc_data *data = sc->request->cmd->data; + size_t left = min(FSL_SDHC_FIFO_BUF_SIZE, data->len); + uint8_t *buf = data->data; + uint32_t word; + + buf += sc->data_offset; + bus_space_read_multi_4(sc->bst, sc->bsh, SDHC_DATPORT, (uint32_t *)buf, + left >> 2); + + sc->data_offset += left; + + /* Handle 32-bit unaligned size case. */ + left &= 0x3; + if (left > 0) { + buf = (uint8_t *)data->data + (sc->data_offset & ~0x3); + word = read4(sc, SDHC_DATPORT); + while (left > 0) { + *(buf++) = word; + word >>= 8; + --left; + } + } +} + +/** + * Write a fifo buffer. + * @warning Assumes data buffer size is 32-bit aligned. + * @param sc + */ +static void +write_block_pio(struct fsl_sdhc_softc *sc) +{ + struct mmc_data *data = sc->request->cmd->data; + size_t left = min(FSL_SDHC_FIFO_BUF_SIZE, data->len); + uint8_t *buf = data->data; + uint32_t word = 0; + + DPRINTF("sc->data_offset %d\n", sc->data_offset); + + buf += sc->data_offset; + bus_space_write_multi_4(sc->bst, sc->bsh, SDHC_DATPORT, (uint32_t *)buf, + left >> 2); + + sc->data_offset += left; + + /* Handle 32-bit unaligned size case. */ + left &= 0x3; + if (left > 0) { + buf = (uint8_t *)data->data + (sc->data_offset & ~0x3); + while (left > 0) { + word += *(buf++); + word <<= 8; + --left; + } + write4(sc, SDHC_DATPORT, word); + } +} + +static void +pio_read_transfer(struct fsl_sdhc_softc *sc) +{ + + while (read4(sc, SDHC_PRSSTAT) & PRSSTAT_BREN) { + read_block_pio(sc); + + /* + * TODO: should we check here whether data_offset >= data->len? + */ + } +} + +static void +pio_write_transfer(struct fsl_sdhc_softc *sc) +{ + + while (read4(sc, SDHC_PRSSTAT) & PRSSTAT_BWEN) { + write_block_pio(sc); + + /* + * TODO: should we check here whether data_offset >= data->len? + */ + } +} +#endif /* FSL_SDHC_USE_DMA */ + +static inline void +handle_command_intr(struct fsl_sdhc_softc *sc, uint32_t irq_stat) +{ + struct mmc_command *cmd = sc->request->cmd; + + /* Handle errors. */ + if (irq_stat & IRQ_CTOE) { + cmd->error = MMC_ERR_TIMEOUT; + } else if (irq_stat & IRQ_CCE) { + cmd->error = MMC_ERR_BADCRC; + } else if (irq_stat & (IRQ_CEBE | IRQ_CIE)) { + cmd->error = MMC_ERR_FIFO; + } + + if (cmd->error) { + device_printf(sc->self, "Error interrupt occured\n"); + reset_controller_dat_cmd(sc); + return; + } + + if (sc->command_done) + return; + + if (irq_stat & IRQ_CC) { + sc->command_done = 1; + + if (cmd->flags & MMC_RSP_PRESENT) + get_response(sc); + } +} + +static inline void +handle_data_intr(struct fsl_sdhc_softc *sc, uint32_t irq_stat) +{ + struct mmc_command *cmd = sc->request->cmd; + + /* Handle errors. */ + if (irq_stat & IRQ_DTOE) { + cmd->error = MMC_ERR_TIMEOUT; + } else if (irq_stat & (IRQ_DCE | IRQ_DEBE)) { + cmd->error = MMC_ERR_BADCRC; + } else if (irq_stat & IRQ_ERROR_DATA_MASK) { + cmd->error = MMC_ERR_FAILED; + } + + if (cmd->error) { + device_printf(sc->self, "Error interrupt occured\n"); + sc->data_done = 1; + reset_controller_dat_cmd(sc); + return; + } + + if (sc->data_done) + return; + +#ifdef FSL_SDHC_NO_DMA + if (irq_stat & IRQ_BRR) { + pio_read_transfer(sc); + } + + if (irq_stat & IRQ_BWR) { + pio_write_transfer(sc); + } +#else + if (irq_stat & IRQ_DINT) { + struct mmc_data *data = sc->request->cmd->data; + + /* Synchronize DMA. */ + if (data->flags & MMC_DATA_READ) { + bus_dmamap_sync(sc->dma_tag, sc->dma_map, + BUS_DMASYNC_POSTREAD); + memcpy(data->data, sc->dma_mem, data->len); + } else { + bus_dmamap_sync(sc->dma_tag, sc->dma_map, + BUS_DMASYNC_POSTWRITE); + } + + /* + * TODO: For multiple block transfers, address of dma memory + * in DSADDR register should be set to the beginning of the + * segment here. Also offset to data pointer should be handled. + */ + } +#endif + + if (irq_stat & IRQ_TC) + sc->data_done = 1; +} + +static void +interrupt_handler(void *arg) +{ + struct fsl_sdhc_softc *sc = (struct fsl_sdhc_softc *)arg; + uint32_t irq_stat; + + mtx_lock(&sc->mtx); + + irq_stat = read4(sc, SDHC_IRQSTAT); + + /* Card interrupt. */ + if (irq_stat & IRQ_CINT) { + DPRINTF("Card interrupt recievied\n"); + + } + + /* Card insertion interrupt. */ + if (irq_stat & IRQ_CINS) { + clear_bit(sc, SDHC_IRQSIGEN, IRQ_CINS); + clear_bit(sc, SDHC_IRQSTATEN, IRQ_CINS); + set_bit(sc, SDHC_IRQSIGEN, IRQ_CRM); + set_bit(sc, SDHC_IRQSTATEN, IRQ_CRM); + + callout_reset(&sc->card_detect_callout, hz / 2, + card_detect_delay, sc); + } + + /* Card removal interrupt. */ + if (irq_stat & IRQ_CRM) { + clear_bit(sc, SDHC_IRQSIGEN, IRQ_CRM); + clear_bit(sc, SDHC_IRQSTATEN, IRQ_CRM); + set_bit(sc, SDHC_IRQSIGEN, IRQ_CINS); + set_bit(sc, SDHC_IRQSTATEN, IRQ_CINS); + + callout_stop(&sc->card_detect_callout); + taskqueue_enqueue(taskqueue_swi_giant, &sc->card_detect_task); + } + + /* Handle request interrupts. */ + if (sc->request) { + handle_command_intr(sc, irq_stat); + handle_data_intr(sc, irq_stat); + + /* + * Finalize request when transfer is done successfully + * or was interrupted due to error. + */ + if ((sc->data_done && sc->command_done) || + (sc->request->cmd->error)) + finalize_request(sc); + } + + /* Clear status register. */ + write4(sc, SDHC_IRQSTAT, irq_stat); + + mtx_unlock(&sc->mtx); +} + +#ifndef FSL_SDHC_NO_DMA +static void +dma_get_phys_addr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + + if (error != 0) + return; + + /* Get first segment's physical address. */ + *(bus_addr_t *)arg = segs->ds_addr; +} + +static int +init_dma(struct fsl_sdhc_softc *sc) +{ + device_t self = sc->self; + int err; + + err = bus_dma_tag_create(bus_get_dma_tag(self), + FSL_SDHC_DMA_BLOCK_SIZE, 0, BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, FSL_SDHC_DMA_BLOCK_SIZE, 1, + FSL_SDHC_DMA_BLOCK_SIZE, BUS_DMA_ALLOCNOW, NULL, NULL, + &sc->dma_tag); + + if (err) { + device_printf(self, "Could not create DMA tag!\n"); + return (-1); + } + + err = bus_dmamem_alloc(sc->dma_tag, (void **)&(sc->dma_mem), + BUS_DMA_NOWAIT | BUS_DMA_NOCACHE, &sc->dma_map); + if (err) { + device_printf(self, "Could not allocate DMA memory!\n"); + goto fail1; + } + + err = bus_dmamap_load(sc->dma_tag, sc->dma_map, (void *)sc->dma_mem, + FSL_SDHC_DMA_BLOCK_SIZE, dma_get_phys_addr, &sc->dma_phys, 0); + if (err) { + device_printf(self, "Could not load DMA map!\n"); + goto fail2; + } + + return (0); + +fail2: + bus_dmamem_free(sc->dma_tag, sc->dma_mem, sc->dma_map); +fail1: + bus_dma_tag_destroy(sc->dma_tag); + + return (-1); +} +#endif /* FSL_SDHC_NO_DMA */ + +static uint32_t +set_xfertyp_register(const struct mmc_command *cmd) +{ + uint32_t xfertyp = 0; + + /* Set command index. */ + xfertyp |= cmd->opcode << CMDINX_SHIFT; + + /* Set command type. */ + if (cmd->opcode == MMC_STOP_TRANSMISSION) + xfertyp |= CMDTYP_ABORT; + + /* Set data preset select. */ + if (cmd->data) { + xfertyp |= XFERTYP_DPSEL; + + /* Set transfer direction. */ + if (cmd->data->flags & MMC_DATA_READ) + xfertyp |= XFERTYP_DTDSEL; + } + + /* Set command index check. */ + if (cmd->flags & MMC_RSP_OPCODE) + xfertyp |= XFERTYP_CICEN; + + /* Set command CRC check. */ + if (cmd->flags & MMC_RSP_CRC) + xfertyp |= XFERTYP_CCCEN; + + /* Set response type */ + if (!(cmd->flags & MMC_RSP_PRESENT)) + xfertyp |= RSPTYP_NONE; + else if (cmd->flags & MMC_RSP_136) + xfertyp |= RSPTYP_136; + else if (cmd->flags & MMC_RSP_BUSY) + xfertyp |= RSPTYP_48_BUSY; + else + xfertyp |= RSPTYP_48; + +#ifndef FSL_SDHC_NO_DMA + /* Enable DMA */ + xfertyp |= XFERTYP_DMAEN; +#endif + + return (xfertyp); +} + +static uint32_t +set_blkattr_register(const struct mmc_data *data) +{ + + if (data->len <= FSL_SDHC_MAX_BLOCK_SIZE) { + /* One block transfer. */ + return (BLKATTR_BLOCK_COUNT(1) | ((data->len) & + BLKATTR_BLKSZE)); + } + + /* TODO: Write code here for multi-block transfers. */ + return (0); +} + +/** + * Initiate data transfer. Interrupt handler will finalize it. + * @todo Implement multi-block transfers. + * @param sc + * @param cmd + */ +static int +start_data(struct fsl_sdhc_softc *sc, struct mmc_data *data) +{ + uint32_t reg; + + if ((uint32_t)data->data & 0x3) { + device_printf(sc->self, "32-bit unaligned data pointer in " + "request\n"); + return (-1); + } + + sc->data_done = 0; + +#ifdef FSL_SDHC_NO_DMA + sc->data_ptr = data->data; + sc->data_offset = 0; +#else + /* Write DMA address register. */ + write4(sc, SDHC_DSADDR, sc->dma_phys); + + /* Synchronize DMA. */ + if (data->flags & MMC_DATA_READ) { + bus_dmamap_sync(sc->dma_tag, sc->dma_map, + BUS_DMASYNC_PREREAD); + } else { + memcpy(sc->dma_mem, data->data, data->len); + bus_dmamap_sync(sc->dma_tag, sc->dma_map, + BUS_DMASYNC_PREWRITE); + } +#endif + /* Set block size and count. */ + reg = set_blkattr_register(data); + if (reg == 0) { + device_printf(sc->self, "Requested unsupported multi-block " + "transfer.\n"); + return (-1); + } + write4(sc, SDHC_BLKATTR, reg); + + return (0); +} + +static int +start_command(struct fsl_sdhc_softc *sc, struct mmc_command *cmd) +{ + struct mmc_request *req = sc->request; + uint32_t mask; + uint32_t xfertyp; + int err; + + DPRINTF("opcode %d, flags 0x%08x\n", cmd->opcode, cmd->flags); + DPRINTF("PRSSTAT = 0x%08x\n", read4(sc, SDHC_PRSSTAT)); + + sc->command_done = 0; + + cmd->error = MMC_ERR_NONE; + + /* TODO: should we check here for card presence and clock settings? */ + + /* Always wait for free CMD line. */ + mask = SDHC_CMD_LINE; + /* Wait for free DAT if we have data or busy signal. */ + if (cmd->data || (cmd->flags & MMC_RSP_BUSY)) + mask |= SDHC_DAT_LINE; + /* We shouldn't wait for DAT for stop commands. */ + if (cmd == req->stop) + mask &= ~SDHC_DAT_LINE; + err = wait_for_free_line(sc, mask); + if (err != 0) { + device_printf(sc->self, "Controller never released inhibit " + "bit(s).\n"); + reset_controller_dat_cmd(sc); + cmd->error = MMC_ERR_FAILED; + sc->request = NULL; + req->done(req); + return (-1); + } + + xfertyp = set_xfertyp_register(cmd); + + if (cmd->data != NULL) { + err = start_data(sc, cmd->data); + if (err != 0) { + device_printf(sc->self, + "Data transfer request failed\n"); + reset_controller_dat_cmd(sc); + cmd->error = MMC_ERR_FAILED; + sc->request = NULL; + req->done(req); + return (-1); + } + } + + write4(sc, SDHC_CMDARG, cmd->arg); + write4(sc, SDHC_XFERTYP, xfertyp); + + DPRINTF("XFERTYP = 0x%08x\n", xfertyp); + DPRINTF("CMDARG = 0x%08x\n", cmd->arg); + + return (0); +} + +#ifdef DEBUG +static void +dump_registers(struct fsl_sdhc_softc *sc) +{ + printf("PRSSTAT = 0x%08x\n", read4(sc, SDHC_PRSSTAT)); + printf("PROCTL = 0x%08x\n", read4(sc, SDHC_PROCTL)); + printf("PMUXCR = 0x%08x\n", ccsr_read4(OCP85XX_PMUXCR)); + printf("HOSTCAPBLT = 0x%08x\n", read4(sc, SDHC_HOSTCAPBLT)); + printf("IRQSTAT = 0x%08x\n", read4(sc, SDHC_IRQSTAT)); + printf("IRQSTATEN = 0x%08x\n", read4(sc, SDHC_IRQSTATEN)); + printf("IRQSIGEN = 0x%08x\n", read4(sc, SDHC_IRQSIGEN)); + printf("WML = 0x%08x\n", read4(sc, SDHC_WML)); + printf("DSADDR = 0x%08x\n", read4(sc, SDHC_DSADDR)); + printf("XFERTYP = 0x%08x\n", read4(sc, SDHC_XFERTYP)); + printf("ECMCR = 0x%08x\n", ccsr_read4(OCP85XX_ECMCR)); + printf("DCR = 0x%08x\n", read4(sc, SDHC_DCR)); +} *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***