#include /*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LIBBSP_ARM_ALTERA_CYCLONE_V_BSP_H #define DW_MMC_ALTERA_CYCLONE_V #include #include #include #include #include #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; int bus_busy; 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) 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 struct ofw_compat_data compat_data[] = { {"altr,socfpga-dw-mshc", 1}, {NULL, 0}, }; static int dw_mmc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); 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_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_LOCK(sc); while (sc->bus_busy) msleep(sc, &sc->sc_mtx, PZERO, "dw_mmc: acquire host", 0); sc->bus_busy = 1; DW_MMC_UNLOCK(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_LOCK(sc); sc->bus_busy = 0; wakeup(sc); DW_MMC_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_TIMING: *result = sc->host.ios.timing; 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; case MMCBR_IVAR_TIMING: sc->host.ios.timing = 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, simplebus, dw_mmc_driver, dw_mmc_devclass, NULL, NULL); DRIVER_MODULE(mmc, dw_mmc, mmc_driver, mmc_devclass, NULL, NULL); MODULE_DEPEND(dw_mmc, mmc, 1, 1, 1);