diff options
Diffstat (limited to 'rtemsbsd/sys/dev/dw_mmc/dw_mmc.c')
-rw-r--r-- | rtemsbsd/sys/dev/dw_mmc/dw_mmc.c | 1106 |
1 files changed, 1106 insertions, 0 deletions
diff --git a/rtemsbsd/sys/dev/dw_mmc/dw_mmc.c b/rtemsbsd/sys/dev/dw_mmc/dw_mmc.c new file mode 100644 index 00000000..681bf95a --- /dev/null +++ b/rtemsbsd/sys/dev/dw_mmc/dw_mmc.c @@ -0,0 +1,1106 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2006 Bernd Walter. All rights reserved. + * Copyright (c) 2006 M. Warner Losh. All rights reserved. + * Copyright (c) 2010 Greg Ansley. All rights reserved. + * Copyright (c) 2014 embedded brains GmbH. 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. + */ + +#include <rtems/bsd/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/sysctl.h> +#include <sys/time.h> + +#include <machine/bus.h> +#include <machine/cpu.h> +#include <machine/cpufunc.h> +#include <machine/resource.h> + +#include <dev/dw_mmc/dw_mmcreg.h> + +#include <dev/mmc/bridge.h> +#include <dev/mmc/mmcreg.h> +#include <dev/mmc/mmcbrvar.h> + +#include <rtems/bsd/local/mmcbr_if.h> + +#include <rtems/irq-extension.h> + +#include <bsp.h> + +#ifdef LIBBSP_ARM_ALTERA_CYCLONE_V_BSP_H + +#define DW_MMC_ALTERA_CYCLONE_V + +#include <bsp/socal/hps.h> +#include <bsp/socal/socal.h> +#include <bsp/socal/alt_sysmgr.h> +#include <bsp/alt_clock_manager.h> +#include <bsp/irq.h> + +#endif /* DW_MMC_ALTERA_CYCLONE_V */ + +struct dw_mmc_softc { + device_t dev; + struct mtx sc_mtx; + struct mtx bus_mtx; + bus_space_handle_t bushandle; + uint32_t biu_clock; + uint32_t ciu_clock; + uint32_t card_clock; + struct mmc_host host; + uint32_t cmdr_flags; + volatile struct dw_mmc_des *des; + rtems_id task_id; +}; + +#define DW_MMC_MAX_DES_COUNT 32 + +#define DW_MMC_MAX_DMA_TRANSFER_BYTES \ + (DW_MMC_MAX_DES_COUNT * 2 * DW_MMC_DES1_MAX_BS) + +static inline uint32_t +RD4(struct dw_mmc_softc *sc, bus_size_t off) +{ + return (bus_space_read_4(0, sc->bushandle, off)); +} + +static inline void +WR4(struct dw_mmc_softc *sc, bus_size_t off, uint32_t val) +{ + bus_space_write_4(0, sc->bushandle, off, val); +} + +/* bus entry points */ +static int dw_mmc_probe(device_t dev); +static int dw_mmc_attach(device_t dev); +static int dw_mmc_detach(device_t dev); +static void dw_mmc_intr(void *); + +static void +DW_MMC_LOCK(struct dw_mmc_softc *sc) +{ + mtx_lock(&sc->sc_mtx); + sc->task_id = rtems_task_self(); +} + +#define DW_MMC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define DW_MMC_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ + "dw_mmc", MTX_DEF) + +#define DW_MMC_BUS_LOCK(_sc) mtx_lock(&(_sc)->bus_mtx) +#define DW_MMC_BUS_UNLOCK(_sc) mtx_unlock(&(_sc)->bus_mtx) +#define DW_MMC_BUS_LOCK_INIT(_sc) \ + mtx_init(&_sc->bus_mtx, device_get_nameunit(_sc->dev), \ + "dw_mmc", MTX_DEF) + +static int +dw_mmc_poll_reset_completion(struct dw_mmc_softc *sc, uint32_t ctrl_resets) +{ + rtems_interval timeout = rtems_clock_tick_later_usec(250000); + + do { + if ((RD4(sc, DW_MMC_CTRL) & ctrl_resets) == 0) { + return 0; + } + } while (rtems_clock_tick_before(timeout)); + + return EBUSY; +} + +static uint32_t +dw_mmc_poll_intsts(struct dw_mmc_softc *sc, uint32_t mask) +{ + uint32_t ret_intsts = 0; + + while (1) { + uint32_t intsts = RD4(sc, DW_MMC_RINTSTS); + + if ((intsts & DW_MMC_INT_ERROR) != 0) { + WR4(sc, DW_MMC_RINTSTS, intsts); + ret_intsts = intsts; + break; + } + + if ((intsts & mask) != 0) { + WR4(sc, DW_MMC_RINTSTS, intsts & mask); + break; + } + } + + return ret_intsts; +} + +static void +dw_mmc_wait_for_interrupt(struct dw_mmc_softc *sc, uint32_t intmask) +{ + rtems_status_code rs; + + WR4(sc, DW_MMC_INTMASK, intmask); + + rs = rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); + BSD_ASSERT(rs == RTEMS_SUCCESSFUL); +} + +static int +dw_mmc_init(struct dw_mmc_softc *sc) +{ + uint32_t ctrl; + uint32_t fifoth; + int err; + + err = dw_mmc_poll_reset_completion(sc, DW_MMC_CTRL_RESET); + if (err != 0) { + return err; + } + + sc->card_clock = UINT32_MAX; + + /* Clear interrupt status */ + WR4(sc, DW_MMC_RINTSTS, 0xffffffff); + + /* Disable all interrupts */ + WR4(sc, DW_MMC_INTMASK, 0x0); + + /* Enable interrupts in general */ + ctrl = RD4(sc, DW_MMC_CTRL); + ctrl |= DW_MMC_CTRL_INT_ENABLE; + WR4(sc, DW_MMC_CTRL, ctrl); + + /* Set data and response timeout to maximum values */ + WR4(sc, DW_MMC_TMOUT, 0xffffffff); + + /* Set debounce value to 25ms */ + WR4(sc, DW_MMC_DEBNCE, (sc->biu_clock / 1000) * 25); + + /* Set FIFO watermarks */ + fifoth = RD4(sc, DW_MMC_FIFOTH); + fifoth &= ~(DW_MMC_FIFOTH_RX_WMARK_MSK | DW_MMC_FIFOTH_TX_WMARK_MSK); + fifoth |= DW_MMC_FIFOTH_RX_WMARK(511) | DW_MMC_FIFOTH_TX_WMARK(512); + WR4(sc, DW_MMC_FIFOTH, fifoth); + + /* Set DMA descriptor */ + WR4(sc, DW_MMC_DBADDR, (uint32_t) sc->des); + + return 0; +} + +static void +dw_mmc_fini(struct dw_mmc_softc *sc) +{ + WR4(sc, DW_MMC_CTRL, DW_MMC_CTRL_FIFO_RESET | DW_MMC_CTRL_RESET); +} + +static int +dw_mmc_probe(device_t dev) +{ + + device_set_desc(dev, "DesignWare Mobile Storage Host"); + return (0); +} + +static int +dw_mmc_platform_init(struct dw_mmc_softc *sc) +{ +#ifdef DW_MMC_ALTERA_CYCLONE_V + size_t des_size = DW_MMC_MAX_DES_COUNT * sizeof(*sc->des); + ALT_STATUS_CODE as; + + /* Module base address */ + sc->bushandle = (bus_space_handle_t) ALT_SDMMC_ADDR; + + /* BIU clock */ + as = alt_clk_freq_get(ALT_CLK_L4_MP, &sc->biu_clock); + BSD_ASSERT(as == ALT_E_SUCCESS); + + /* CIU clock */ + as = alt_clk_clock_enable(ALT_CLK_SDMMC); + BSD_ASSERT(as == ALT_E_SUCCESS); + as = alt_clk_freq_get(ALT_CLK_SDMMC, &sc->ciu_clock); + BSD_ASSERT(as == ALT_E_SUCCESS); + sc->ciu_clock /= 4; + + sc->des = rtems_cache_coherent_allocate(des_size, 0, 0); + if (sc->des == NULL) { + return (ENOMEM); + } + memset(__DEVOLATILE(void *, sc->des), 0, des_size); +#endif + + return (0); +} + +static void +dw_mmc_platform_install_intr(struct dw_mmc_softc *sc) +{ + rtems_vector_number irq = +#ifdef DW_MMC_ALTERA_CYCLONE_V + ALT_INT_INTERRUPT_SDMMC_IRQ; +#else + UINT32_MAX; +#endif + rtems_status_code rs; + + /* + * Activate the interrupt + */ + rs = rtems_interrupt_handler_install(irq, "DW MMC", + RTEMS_INTERRUPT_SHARED, dw_mmc_intr, sc); + BSD_ASSERT(rs == RTEMS_SUCCESSFUL); +} + +static bool +dw_mmc_platform_set_clock(struct dw_mmc_softc *sc, uint32_t card_clock) +{ + bool use_hold_reg; + +#ifdef DW_MMC_ALTERA_CYCLONE_V + uint32_t drvsel; + uint32_t smplsel; + uint32_t ctl; + ALT_STATUS_CODE as; + + /* FIXME: Values taken from U-Boot, not clear how they are determined */ + if (card_clock > 25000000) { + drvsel = 3; + smplsel = 7; + } else { + drvsel = 3; + smplsel = 0; + } + + use_hold_reg = drvsel != 0; + + as = alt_clk_clock_disable(ALT_CLK_SDMMC); + BSD_ASSERT(as == ALT_E_SUCCESS); + + ctl = alt_read_word(ALT_SYSMGR_SDMMC_CTL_ADDR); + ctl &= ALT_SYSMGR_SDMMC_CTL_DRVSEL_CLR_MSK + & ALT_SYSMGR_SDMMC_CTL_SMPLSEL_CLR_MSK; + ctl |= ALT_SYSMGR_SDMMC_CTL_DRVSEL_SET(drvsel) + | ALT_SYSMGR_SDMMC_CTL_SMPLSEL_SET(smplsel); + alt_write_word(ALT_SYSMGR_SDMMC_CTL_ADDR, ctl); + + as = alt_clk_clock_enable(ALT_CLK_SDMMC); + BSD_ASSERT(as == ALT_E_SUCCESS); +#else + use_hold_reg = false; +#endif + + return use_hold_reg; +} + +static void +dw_mmc_platform_fini(struct dw_mmc_softc *sc) +{ +#ifdef DW_MMC_ALTERA_CYCLONE_V + rtems_cache_coherent_free(__DEVOLATILE(void *, sc->des)); +#endif +} + +static int +dw_mmc_attach(device_t dev) +{ + struct dw_mmc_softc *sc = device_get_softc(dev); + int err; + + sc->dev = dev; + + err = dw_mmc_platform_init(sc); + if (err != 0) { + return (err); + } + + dw_mmc_fini(sc); + err = dw_mmc_init(sc); + if (err != 0) { + dw_mmc_platform_fini(sc); + + return (err); + } + + DW_MMC_LOCK_INIT(sc); + DW_MMC_BUS_LOCK_INIT(sc); + + dw_mmc_platform_install_intr(sc); + + sc->host.f_min = 400000; + sc->host.f_max = (int) sc->ciu_clock; + if (sc->host.f_max > 50000000) + sc->host.f_max = 50000000; /* Limit to 50MHz */ + + sc->host.host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; + + /* FIXME: MMC_CAP_8_BIT_DATA for eSDIO? */ + sc->host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; + + device_add_child(dev, "mmc", 0); + device_set_ivars(dev, &sc->host); + err = bus_generic_attach(dev); + + return (err); +} + +static int +dw_mmc_detach(device_t dev) +{ + struct dw_mmc_softc *sc = device_get_softc(dev); + + dw_mmc_fini(sc); + + /* FIXME: Implement */ + BSD_ASSERT(0); + + return (EBUSY); +} + +static int +dw_mmc_cmd_wait(struct dw_mmc_softc *sc) +{ + rtems_interval timeout = rtems_clock_tick_later_usec(250000); + + do { + if ((RD4(sc, DW_MMC_CMD) & DW_MMC_CMD_START) == 0) { + return 0; + } + } while (rtems_clock_tick_before(timeout)); + + return EBUSY; +} + +static void +dw_mmc_cmd_start(struct dw_mmc_softc *sc, uint32_t cmd, uint32_t cmdarg) +{ + WR4(sc, DW_MMC_CMDARG, cmdarg); + cmd |= DW_MMC_CMD_START; + WR4(sc, DW_MMC_CMD, cmd); +} + +static int +dw_mmc_cmd_update_clock(struct dw_mmc_softc *sc) +{ + dw_mmc_cmd_start(sc, + DW_MMC_CMD_UPDATE_CLK | DW_MMC_CMD_PRV_DATA_WAIT, 0); + + return dw_mmc_cmd_wait(sc); +} + +static int +dw_mmc_set_clock(struct dw_mmc_softc *sc, uint32_t card_clock) +{ + uint32_t clkdiv; + int err; + + if (sc->card_clock == card_clock) { + return 0; + } + + sc->card_clock = card_clock; + + /* Disable card clock */ + WR4(sc, DW_MMC_CLKENA, 0); + + err = dw_mmc_cmd_update_clock(sc); + if (err != 0) { + return err; + } + + if (card_clock == 0) { + return 0; + } + + if (dw_mmc_platform_set_clock(sc, card_clock)) { + sc->cmdr_flags |= DW_MMC_CMD_USE_HOLD_REG; + } else { + sc->cmdr_flags &= ~DW_MMC_CMD_USE_HOLD_REG; + } + + if (card_clock == sc->ciu_clock) { + clkdiv = 0; + } else { + uint32_t s = 2 * card_clock; + + clkdiv = (sc->ciu_clock + s - 1) / s; + } + + WR4(sc, DW_MMC_CLKDIV, clkdiv); + WR4(sc, DW_MMC_CLKSRC, 0); + + err = dw_mmc_cmd_update_clock(sc); + if (err != 0) { + return err; + } + + /* Enable card clock */ + WR4(sc, DW_MMC_CLKENA, DW_MMC_CLKEN_ENABLE); + + return dw_mmc_cmd_update_clock(sc); +} + +static int +dw_mmc_update_ios(device_t brdev, device_t reqdev) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + struct mmc_host *host; + struct mmc_ios *ios; + uint32_t ctype; + int err; + + DW_MMC_LOCK(sc); + + host = &sc->host; + ios = &host->ios; + + err = dw_mmc_set_clock(sc, (uint32_t) ios->clock); + if (err != 0) { + return (err); + } + + if (ios->power_mode == power_off) { + WR4(sc, DW_MMC_PWREN, 0); + } else { + sc->cmdr_flags |= DW_MMC_CMD_SEND_INIT; + WR4(sc, DW_MMC_PWREN, DW_MMC_PWREN_ENABLE); + } + + switch (ios->bus_width) { + default: + BSD_ASSERT(ios->bus_width == bus_width_1); + ctype = DW_MMC_CTYPE_1BIT; + break; + case bus_width_4: + ctype = DW_MMC_CTYPE_4BIT; + break; + case bus_width_8: + ctype = DW_MMC_CTYPE_8BIT; + break; + } + + WR4(sc, DW_MMC_CTYPE, ctype); + + DW_MMC_UNLOCK(sc); + + return (0); +} + +static int +dw_mmc_fifo_and_dma_reset(struct dw_mmc_softc *sc) +{ + uint32_t ctrl_resets = DW_MMC_CTRL_FIFO_RESET | DW_MMC_CTRL_DMA_RESET; + uint32_t ctrl = RD4(sc, DW_MMC_CTRL); + + ctrl |= ctrl_resets; + + WR4(sc, DW_MMC_CTRL, ctrl); + + return dw_mmc_poll_reset_completion(sc, ctrl_resets); +} + +static int +dw_mmc_cmd_read_response(struct dw_mmc_softc *sc, struct mmc_command *cmd, + uint32_t intsts) +{ + if ((intsts & DW_MMC_INT_RTO) != 0) { + return MMC_ERR_TIMEOUT; + } else if ((intsts & DW_MMC_INT_RCRC) != 0 + && (cmd->flags & MMC_RSP_CRC) != 0) { + return MMC_ERR_BADCRC; + } else if ((intsts & DW_MMC_INT_RE) != 0) { + return MMC_ERR_FAILED; + } + + if ((cmd->flags & MMC_RSP_PRESENT) != 0) { + uint32_t *resp = &cmd->resp[0]; + + if ((cmd->flags & MMC_RSP_136) != 0) { + resp[3] = RD4(sc, DW_MMC_RESP0); + resp[2] = RD4(sc, DW_MMC_RESP1); + resp[1] = RD4(sc, DW_MMC_RESP2); + resp[0] = RD4(sc, DW_MMC_RESP3); + } else { + resp[0] = RD4(sc, DW_MMC_RESP0); + } + } + + return MMC_ERR_NONE; +} + +static uint32_t +dw_mmc_cmd_data_read(struct dw_mmc_softc *sc, struct mmc_data *data, + uint32_t *data32, size_t count_bytes) +{ + uint32_t intsts = 0; + + while (count_bytes > 0) { + uint32_t status; + size_t available_words; + size_t dangling_bytes = 0; + size_t i; + + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_RXDR + | DW_MMC_INT_DTO | DW_MMC_INT_HTO); + + if (intsts != 0) { + return intsts; + } + + status = RD4(sc, DW_MMC_STATUS); + available_words = DW_MMC_STATUS_GET_FIFO_CNT(status); + + if (available_words * DW_MMC_FIFO_WIDTH > count_bytes) { + dangling_bytes = count_bytes % DW_MMC_FIFO_WIDTH; + --available_words; + } + + for (i = 0; i < available_words; i++) { + data32[i] = RD4(sc, DW_MMC_DATA); + } + + data32 += available_words; + count_bytes -= available_words * DW_MMC_FIFO_WIDTH; + + if (dangling_bytes != 0) { + uint32_t tmp = RD4(sc, DW_MMC_DATA); + + memcpy(data32, &tmp, dangling_bytes); + BSD_ASSERT(count_bytes == dangling_bytes); + count_bytes = 0; + } + } + + if ((data->flags & MMC_DATA_MULTI) != 0) { + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_ACD); + } + + return intsts; +} + +static uint32_t +dw_mmc_cmd_data_write(struct dw_mmc_softc *sc, struct mmc_data *data, + uint32_t *data32, size_t count_bytes) +{ + uint32_t intsts; + + while (count_bytes > 0) { + uint32_t status; + size_t pending_words = count_bytes / DW_MMC_FIFO_WIDTH; + size_t free_words; + size_t dangling_bytes; + size_t words_to_write; + size_t i; + + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_TXDR + | DW_MMC_INT_HTO); + + if (intsts != 0) { + return intsts; + } + + status = RD4(sc, DW_MMC_STATUS); + free_words = DW_MMC_FIFO_DEPTH - DW_MMC_STATUS_GET_FIFO_CNT(status); + + if (pending_words >= free_words) { + words_to_write = free_words; + dangling_bytes = 0; + } else { + words_to_write = pending_words; + dangling_bytes = count_bytes % DW_MMC_FIFO_WIDTH; + } + + for (i = 0; i < words_to_write; i++) { + WR4(sc, DW_MMC_DATA, data32[i]); + } + + data32 += words_to_write; + count_bytes -= words_to_write * DW_MMC_FIFO_WIDTH; + + if (dangling_bytes != 0) { + uint32_t tmp = 0; + + memcpy(&tmp, &data32[0], dangling_bytes); + WR4(sc, DW_MMC_DATA, tmp); + BSD_ASSERT(count_bytes == dangling_bytes); + count_bytes = 0; + } + } + + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_DTO); + + if ((data->flags & MMC_DATA_MULTI) != 0 && intsts == 0) { + dw_mmc_poll_intsts(sc, DW_MMC_INT_ACD); + } + + return intsts; +} + +static uint32_t +dw_mmc_cmd_data_transfer(struct dw_mmc_softc *sc, struct mmc_data *data, + size_t done_bytes, bool use_dma) +{ + uint32_t *data32 = (uint32_t *) ((char *) data->data + done_bytes); + bool do_write = (data->flags & MMC_DATA_WRITE) != 0; + uint32_t intsts; + + if (use_dma) { + dw_mmc_wait_for_interrupt(sc, DW_MMC_INT_DTO); + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_DTO); + + if ((data->flags & MMC_DATA_MULTI) != 0 && intsts == 0) { + dw_mmc_poll_intsts(sc, DW_MMC_INT_ACD); + } + + if (!do_write) { + rtems_cache_invalidate_multiple_data_lines(data->data, + data->len); + } + } else { + size_t count_bytes = data->len - done_bytes; + + if (do_write) { + intsts = dw_mmc_cmd_data_write(sc, data, data32, count_bytes); + } else { + intsts = dw_mmc_cmd_data_read(sc, data, data32, count_bytes); + } + } + + return intsts; +} + +static int +dw_mmc_cmd_data_finish(struct dw_mmc_softc *sc, uint32_t intsts) +{ + int mmc_err = MMC_ERR_NONE; + + if ((intsts & DW_MMC_INT_ERROR) != 0) { + if ((intsts & DW_MMC_INT_DCRC) != 0) { + mmc_err = MMC_ERR_BADCRC; + } else if ((intsts & DW_MMC_INT_EBE) != 0) { + mmc_err = MMC_ERR_FAILED; + } else if ((intsts & DW_MMC_INT_DRTO) != 0) { + mmc_err = MMC_ERR_TIMEOUT; + } else { + mmc_err = MMC_ERR_FAILED; + } + } + + return mmc_err; +} + +static int +dw_mmc_cmd_done(struct dw_mmc_softc *sc, struct mmc_command *cmd, + struct mmc_data *data, size_t done_bytes, bool use_dma) +{ + uint32_t intsts; + int mmc_err; + + dw_mmc_wait_for_interrupt(sc, DW_MMC_INT_CMD_DONE); + + intsts = RD4(sc, DW_MMC_RINTSTS); + WR4(sc, DW_MMC_RINTSTS, + intsts & (DW_MMC_INT_ERROR | DW_MMC_INT_CMD_DONE)); + + mmc_err = dw_mmc_cmd_read_response(sc, cmd, intsts); + if (mmc_err != 0) { + return mmc_err; + } + + if (data != NULL) { + intsts = dw_mmc_cmd_data_transfer(sc, data, done_bytes, use_dma); + mmc_err = dw_mmc_cmd_data_finish(sc, intsts); + } + + return mmc_err; +} + +static size_t +dw_mmc_fill_fifo(struct dw_mmc_softc *sc, struct mmc_data *data) +{ + uint32_t *data32 = data->data; + size_t count_bytes = data->len; + size_t count_words = 0; + size_t dangling_bytes; + size_t i; + + if (count_bytes >= DW_MMC_FIFO_DEPTH * DW_MMC_FIFO_WIDTH) { + count_words = DW_MMC_FIFO_DEPTH; + dangling_bytes = 0; + } else { + count_words = count_bytes / DW_MMC_FIFO_WIDTH; + dangling_bytes = count_bytes % DW_MMC_FIFO_WIDTH; + } + + for (i = 0; i < count_words; ++i) { + WR4(sc, DW_MMC_DATA, data32[i]); + } + + if (dangling_bytes) { + uint32_t tmp = 0; + + memcpy(&tmp, &data32[i], dangling_bytes); + WR4(sc, DW_MMC_DATA, tmp); + } + + return count_words * DW_MMC_FIFO_WIDTH + dangling_bytes; +} + +static bool dw_mmc_dma_can_use(const struct mmc_data *data) +{ + uintptr_t cache_line = 32; + + return data->len >= cache_line + && ((data->len | (uintptr_t) data->data) & (cache_line - 1)) == 0; +} + +static void +dw_mmc_dma_setup(struct dw_mmc_softc *sc, struct mmc_data *data) +{ + volatile struct dw_mmc_des *des = sc->des; + uint32_t buf = (uint32_t) data->data; + size_t count_bytes = data->len; + uint32_t fs = DW_MMC_DES0_FS; + size_t s = 2 * DW_MMC_DES1_MAX_BS; + size_t n = (count_bytes + s - 1) / s; + size_t m = count_bytes % s; + size_t i; + + for (i = 0; i < n - 1; ++i) { + des[i].des1 = DW_MMC_DES1_BS1(DW_MMC_DES1_MAX_BS) + | DW_MMC_DES1_BS2(DW_MMC_DES1_MAX_BS); + des[i].des2 = buf; + buf += DW_MMC_DES1_MAX_BS; + des[i].des3 = buf; + buf += DW_MMC_DES1_MAX_BS; + des[i].des0 = DW_MMC_DES0_OWN | fs; + fs = 0; + } + + if (m > DW_MMC_DES1_MAX_BS) { + des[i].des1 = DW_MMC_DES1_BS1(DW_MMC_DES1_MAX_BS) + | DW_MMC_DES1_BS2(m - DW_MMC_DES1_MAX_BS); + des[i].des2 = buf; + buf += DW_MMC_DES1_MAX_BS; + des[i].des3 = buf; + } else { + des[i].des1 = DW_MMC_DES1_BS1(m); + des[i].des2 = buf; + des[i].des3 = 0; + } + + des[i].des0 = DW_MMC_DES0_OWN | DW_MMC_DES0_ER | fs | DW_MMC_DES0_LD; + +#ifdef __arm__ + _ARM_Data_synchronization_barrier(); +#else + /* TODO */ +#endif +} + + +static void +dw_mmc_cmd_do(struct dw_mmc_softc *sc, struct mmc_request *req, + struct mmc_command *cmd) +{ + size_t done_bytes = 0; + bool use_dma = false; + struct mmc_data *data; + uint32_t cmdr; + + data = cmd->data; + cmdr = cmd->opcode; + + if (cmd->opcode == MMC_STOP_TRANSMISSION) { + cmdr |= DW_MMC_CMD_SEND_STOP; + } else { + cmdr |= DW_MMC_CMD_PRV_DATA_WAIT; + } + + cmdr |= sc->cmdr_flags; + sc->cmdr_flags &= ~DW_MMC_CMD_SEND_INIT; + + if (MMC_RSP(cmd->flags) != MMC_RSP_NONE) { + cmdr |= DW_MMC_CMD_RESP_EXP; + + if ((cmd->flags & MMC_RSP_136) != 0) { + cmdr |= DW_MMC_CMD_RESP_LONG; + } + } + + if ((cmd->flags & MMC_RSP_CRC) != 0) { + cmdr |= DW_MMC_CMD_RESP_CRC; + } + + if (data != NULL) { + size_t count_bytes = data->len; + uint32_t ctrl; + int mmc_err; + + cmdr |= DW_MMC_CMD_DATA_EXP; + + if ((data->flags & MMC_DATA_MULTI) != 0) { + cmdr |= DW_MMC_CMD_SEND_STOP; + } + + mmc_err = dw_mmc_fifo_and_dma_reset(sc); + if (mmc_err != 0) { + cmd->error = mmc_err; + return; + } + + use_dma = dw_mmc_dma_can_use(data); + + ctrl = RD4(sc, DW_MMC_CTRL); + + if (use_dma) { + ctrl |= DW_MMC_CTRL_DMA_ENABLE; + } else { + ctrl &= ~DW_MMC_CTRL_DMA_ENABLE; + } + + WR4(sc, DW_MMC_CTRL, ctrl); + WR4(sc, DW_MMC_BLKSIZ, MIN(count_bytes, MMC_SECTOR_SIZE)); + WR4(sc, DW_MMC_BYTCNT, count_bytes); + + if ((data->flags & MMC_DATA_WRITE) != 0) { + cmdr |= DW_MMC_CMD_DATA_WR; + + if (use_dma) { + rtems_cache_flush_multiple_data_lines(data->data, + count_bytes); + } else { + done_bytes = dw_mmc_fill_fifo(sc, data); + } + } else if (use_dma) { + rtems_cache_invalidate_multiple_data_lines(data->data, + count_bytes); + } + + if (use_dma) { + if (count_bytes > DW_MMC_MAX_DMA_TRANSFER_BYTES) { + cmd->error = MMC_ERR_INVALID; + return; + } + + dw_mmc_dma_setup(sc, data); + } + } + + dw_mmc_cmd_start(sc, cmdr, cmd->arg); + + if (use_dma) { + WR4(sc, DW_MMC_PLDMND, 0); + } + + cmd->error = dw_mmc_cmd_done(sc, cmd, data, done_bytes, use_dma); +} + +static int +dw_mmc_request(device_t brdev, device_t reqdev, struct mmc_request *req) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + + DW_MMC_LOCK(sc); + dw_mmc_cmd_do(sc, req, req->cmd); + DW_MMC_UNLOCK(sc); + + (*req->done)(req); + + return (0); +} + +static int +dw_mmc_get_ro(device_t brdev, device_t reqdev) +{ + return (0); +} + +static int +dw_mmc_acquire_host(device_t brdev, device_t reqdev) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + + DW_MMC_BUS_LOCK(sc); + + return (0); +} + +static int +dw_mmc_release_host(device_t brdev, device_t reqdev) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + + DW_MMC_BUS_UNLOCK(sc); + + return (0); +} + +static void +dw_mmc_intr(void *arg) +{ + struct dw_mmc_softc *sc = (struct dw_mmc_softc *) arg; + rtems_status_code rs; + + WR4(sc, DW_MMC_INTMASK, 0); + + rs = rtems_event_transient_send(sc->task_id); + BSD_ASSERT(rs == RTEMS_SUCCESSFUL); +} + +static int +dw_mmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) +{ + struct dw_mmc_softc *sc = device_get_softc(bus); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + *(int *)result = sc->host.ios.bus_mode; + break; + case MMCBR_IVAR_BUS_WIDTH: + *(int *)result = sc->host.ios.bus_width; + break; + case MMCBR_IVAR_CHIP_SELECT: + *(int *)result = sc->host.ios.chip_select; + break; + case MMCBR_IVAR_CLOCK: + *(int *)result = sc->host.ios.clock; + break; + case MMCBR_IVAR_F_MIN: + *(int *)result = sc->host.f_min; + break; + case MMCBR_IVAR_F_MAX: + *(int *)result = sc->host.f_max; + break; + case MMCBR_IVAR_HOST_OCR: + *(int *)result = sc->host.host_ocr; + break; + case MMCBR_IVAR_MODE: + *(int *)result = sc->host.mode; + break; + case MMCBR_IVAR_OCR: + *(int *)result = sc->host.ocr; + break; + case MMCBR_IVAR_POWER_MODE: + *(int *)result = sc->host.ios.power_mode; + break; + case MMCBR_IVAR_VDD: + *(int *)result = sc->host.ios.vdd; + break; + case MMCBR_IVAR_CAPS: + *(int *)result = sc->host.caps; + break; + case MMCBR_IVAR_MAX_DATA: + *(int *)result = 1; + break; + } + return (0); +} + +static int +dw_mmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) +{ + struct dw_mmc_softc *sc = device_get_softc(bus); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + sc->host.ios.bus_mode = value; + break; + case MMCBR_IVAR_BUS_WIDTH: + sc->host.ios.bus_width = value; + break; + case MMCBR_IVAR_CHIP_SELECT: + sc->host.ios.chip_select = value; + break; + case MMCBR_IVAR_CLOCK: + sc->host.ios.clock = value; + break; + case MMCBR_IVAR_MODE: + sc->host.mode = value; + break; + case MMCBR_IVAR_OCR: + sc->host.ocr = value; + break; + case MMCBR_IVAR_POWER_MODE: + sc->host.ios.power_mode = value; + break; + case MMCBR_IVAR_VDD: + sc->host.ios.vdd = value; + break; + /* These are read-only */ + case MMCBR_IVAR_CAPS: + case MMCBR_IVAR_HOST_OCR: + case MMCBR_IVAR_F_MIN: + case MMCBR_IVAR_F_MAX: + case MMCBR_IVAR_MAX_DATA: + return (EINVAL); + } + return (0); +} + +static device_method_t dw_mmc_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, dw_mmc_probe), + DEVMETHOD(device_attach, dw_mmc_attach), + DEVMETHOD(device_detach, dw_mmc_detach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, dw_mmc_read_ivar), + DEVMETHOD(bus_write_ivar, dw_mmc_write_ivar), + + /* mmcbr_if */ + DEVMETHOD(mmcbr_update_ios, dw_mmc_update_ios), + DEVMETHOD(mmcbr_request, dw_mmc_request), + DEVMETHOD(mmcbr_get_ro, dw_mmc_get_ro), + DEVMETHOD(mmcbr_acquire_host, dw_mmc_acquire_host), + DEVMETHOD(mmcbr_release_host, dw_mmc_release_host), + + DEVMETHOD_END +}; + +static driver_t dw_mmc_driver = { + "dw_mmc", + dw_mmc_methods, + sizeof(struct dw_mmc_softc) +}; + +static devclass_t dw_mmc_devclass; + +DRIVER_MODULE(dw_mmc, nexus, dw_mmc_driver, dw_mmc_devclass, NULL, NULL); |