mirror of
https://git.rtems.org/rtems-libbsd/
synced 2025-05-14 04:09:26 +08:00

Do not overwrite the device ivars used by the simplebus. Use unspecified unit number for child device.
1143 lines
25 KiB
C
1143 lines
25 KiB
C
#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 <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/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.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;
|
|
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 void
|
|
dw_mmc_configure_dma(struct dw_mmc_softc *sc)
|
|
{
|
|
|
|
WR4(sc, DW_MMC_BMOD, DW_MMC_BMOD_DE | DW_MMC_BMOD_FB);
|
|
}
|
|
|
|
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_DMA_RESET |
|
|
DW_MMC_CTRL_FIFO_RESET | DW_MMC_CTRL_RESET);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
sc->card_clock = UINT32_MAX;
|
|
|
|
dw_mmc_configure_dma(sc);
|
|
|
|
/* 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_DMA_RESET | DW_MMC_CTRL_FIFO_RESET |
|
|
DW_MMC_CTRL_RESET);
|
|
wmb();
|
|
WR4(sc, DW_MMC_BMOD, DW_MMC_BMOD_SWR);
|
|
}
|
|
|
|
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", -1);
|
|
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);
|
|
int err;
|
|
|
|
ctrl &= ~DW_MMC_CTRL_DMA_ENABLE;
|
|
ctrl |= ctrl_resets;
|
|
|
|
WR4(sc, DW_MMC_CTRL, ctrl);
|
|
|
|
err = dw_mmc_poll_reset_completion(sc, ctrl_resets);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
WR4(sc, DW_MMC_BMOD, DW_MMC_BMOD_SWR);
|
|
return (0);
|
|
}
|
|
|
|
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;
|
|
wmb();
|
|
}
|
|
|
|
|
|
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;
|
|
WR4(sc, DW_MMC_CTRL, ctrl);
|
|
wmb();
|
|
dw_mmc_configure_dma(sc);
|
|
}
|
|
|
|
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);
|