diff options
Diffstat (limited to 'sys/arch/armv7/exynos/exesdhc.c')
-rw-r--r-- | sys/arch/armv7/exynos/exesdhc.c | 990 |
1 files changed, 990 insertions, 0 deletions
diff --git a/sys/arch/armv7/exynos/exesdhc.c b/sys/arch/armv7/exynos/exesdhc.c new file mode 100644 index 00000000000..2a48896cfab --- /dev/null +++ b/sys/arch/armv7/exynos/exesdhc.c @@ -0,0 +1,990 @@ +/* $OpenBSD: exesdhc.c,v 1.1 2015/01/26 02:48:24 bmercer Exp $ */ +/* + * Copyright (c) 2009 Dale Rahn <drahn@openbsd.org> + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Copyright (c) 2012-2013 Patrick Wildt <patrick@blueri.se> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* i.MX SD/MMC support derived from /sys/dev/sdmmc/sdhc.c */ + + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/malloc.h> +#include <sys/systm.h> +#include <machine/bus.h> +#include <machine/fdt.h> + +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmcvar.h> + +#include <armv7/armv7/armv7var.h> +#include <armv7/exynos/exclockvar.h> +#include <armv7/exynos/exgpiovar.h> + +/* registers */ +#define SDHC_DS_ADDR 0x00 +#define SDHC_BLK_ATT 0x04 +#define SDHC_CMD_ARG 0x08 +#define SDHC_CMD_XFR_TYP 0x0c +#define SDHC_CMD_RSP0 0x10 +#define SDHC_CMD_RSP1 0x14 +#define SDHC_CMD_RSP2 0x18 +#define SDHC_CMD_RSP3 0x1c +#define SDHC_DATA_BUFF_ACC_PORT 0x20 +#define SDHC_PRES_STATE 0x24 +#define SDHC_PROT_CTRL 0x28 +#define SDHC_SYS_CTRL 0x2c +#define SDHC_INT_STATUS 0x30 +#define SDHC_INT_STATUS_EN 0x34 +#define SDHC_INT_SIGNAL_EN 0x38 +#define SDHC_AUTOCMD12_ERR_STATUS 0x3c +#define SDHC_HOST_CTRL_CAP 0x40 +#define SDHC_WTMK_LVL 0x44 +#define SDHC_MIX_CTRL 0x48 +#define SDHC_FORCE_EVENT 0x50 +#define SDHC_ADMA_ERR_STATUS 0x54 +#define SDHC_ADMA_SYS_ADDR 0x58 +#define SDHC_DLL_CTRL 0x60 +#define SDHC_DLL_STATUS 0x64 +#define SDHC_CLK_TUNE_CTRL_STATUS 0x68 +#define SDHC_VEND_SPEC 0xc0 +#define SDHC_MMC_BOOT 0xc4 +#define SDHC_VEND_SPEC2 0xc8 +#define SDHC_HOST_CTRL_VER 0xfc + +/* bits and bytes */ +#define SDHC_BLK_ATT_BLKCNT_MAX 0xffff +#define SDHC_BLK_ATT_BLKCNT_SHIFT 16 +#define SDHC_BLK_ATT_BLKSIZE_SHIFT 0 +#define SDHC_CMD_XFR_TYP_CMDINDX_SHIFT 24 +#define SDHC_CMD_XFR_TYP_CMDINDX_SHIFT_MASK (0x3f << SDHC_CMD_XFR_TYP_CMDINDX_SHIFT) +#define SDHC_CMD_XFR_TYP_CMDTYP_SHIFT 22 +#define SDHC_CMD_XFR_TYP_DPSEL_SHIFT 21 +#define SDHC_CMD_XFR_TYP_DPSEL (1 << SDHC_CMD_XFR_TYP_DPSEL_SHIFT) +#define SDHC_CMD_XFR_TYP_CICEN_SHIFT 20 +#define SDHC_CMD_XFR_TYP_CICEN (1 << SDHC_CMD_XFR_TYP_CICEN_SHIFT) +#define SDHC_CMD_XFR_TYP_CCCEN_SHIFT 19 +#define SDHC_CMD_XFR_TYP_CCCEN (1 << SDHC_CMD_XFR_TYP_CCCEN_SHIFT) +#define SDHC_CMD_XFR_TYP_RSPTYP_SHIFT 16 +#define SDHC_CMD_XFR_TYP_RSP_NONE (0x0 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT) +#define SDHC_CMD_XFR_TYP_RSP136 (0x1 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT) +#define SDHC_CMD_XFR_TYP_RSP48 (0x2 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT) +#define SDHC_CMD_XFR_TYP_RSP48B (0x3 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT) +#define SDHC_PRES_STATE_WPSPL (1 << 19) +#define SDHC_PRES_STATE_BREN (1 << 11) +#define SDHC_PRES_STATE_BWEN (1 << 10) +#define SDHC_PRES_STATE_SDSTB (1 << 3) +#define SDHC_PRES_STATE_DLA (1 << 2) +#define SDHC_PRES_STATE_CDIHB (1 << 1) +#define SDHC_PRES_STATE_CIHB (1 << 0) +#define SDHC_SYS_CTRL_RSTA (1 << 24) +#define SDHC_SYS_CTRL_RSTC (1 << 25) +#define SDHC_SYS_CTRL_RSTD (1 << 26) +#define SDHC_SYS_CTRL_CLOCK_MASK (0xfff << 4) +#define SDHC_SYS_CTRL_CLOCK_DIV_SHIFT 4 +#define SDHC_SYS_CTRL_CLOCK_PRE_SHIFT 8 +#define SDHC_SYS_CTRL_DTOCV_SHIFT 16 +#define SDHC_INT_STATUS_CC (1 << 0) +#define SDHC_INT_STATUS_TC (1 << 1) +#define SDHC_INT_STATUS_BGE (1 << 2) +#define SDHC_INT_STATUS_DINT (1 << 3) +#define SDHC_INT_STATUS_BWR (1 << 4) +#define SDHC_INT_STATUS_BRR (1 << 5) +#define SDHC_INT_STATUS_CINS (1 << 6) +#define SDHC_INT_STATUS_CRM (1 << 7) +#define SDHC_INT_STATUS_CINT (1 << 8) +#define SDHC_INT_STATUS_CTOE (1 << 16) +#define SDHC_INT_STATUS_CCE (1 << 17) +#define SDHC_INT_STATUS_CEBE (1 << 18) +#define SDHC_INT_STATUS_CIC (1 << 19) +#define SDHC_INT_STATUS_DTOE (1 << 20) +#define SDHC_INT_STATUS_DCE (1 << 21) +#define SDHC_INT_STATUS_DEBE (1 << 22) +#define SDHC_INT_STATUS_DMAE (1 << 28) +#define SDHC_INT_STATUS_CMD_ERR (SDHC_INT_STATUS_CIC | SDHC_INT_STATUS_CEBE | SDHC_INT_STATUS_CCE) +#define SDHC_INT_STATUS_ERR (SDHC_INT_STATUS_CTOE | SDHC_INT_STATUS_CCE | SDHC_INT_STATUS_CEBE | \ + SDHC_INT_STATUS_CIC | SDHC_INT_STATUS_DTOE | SDHC_INT_STATUS_DCE | \ + SDHC_INT_STATUS_DEBE | SDHC_INT_STATUS_DMAE) +#define SDHC_MIX_CTRL_DMAEN (1 << 0) +#define SDHC_MIX_CTRL_BCEN (1 << 1) +#define SDHC_MIX_CTRL_AC12EN (1 << 2) +#define SDHC_MIX_CTRL_DTDSEL (1 << 4) +#define SDHC_MIX_CTRL_MSBSEL (1 << 5) +#define SDHC_PROT_CTRL_DMASEL_SDMA_MASK (0x3 << 8) +#define SDHC_HOST_CTRL_CAP_MBL_SHIFT 16 +#define SDHC_HOST_CTRL_CAP_MBL_MASK 0x7 +#define SDHC_HOST_CTRL_CAP_VS33 (1 << 24) +#define SDHC_HOST_CTRL_CAP_VS30 (1 << 25) +#define SDHC_HOST_CTRL_CAP_VS18 (1 << 26) +#define SDHC_VEND_SPEC_FRC_SDCLK_ON (1 << 8) +#define SDHC_WTMK_LVL_RD_WML_SHIFT 0 +#define SDHC_WTMK_LVL_WR_WML_SHIFT 16 + +#define SDHC_COMMAND_TIMEOUT hz +#define SDHC_BUFFER_TIMEOUT hz +#define SDHC_TRANSFER_TIMEOUT hz + +int exesdhc_match(struct device *parent, void *v, void *aux); +void exesdhc_attach(struct device *parent, struct device *self, void *args); + +#include <machine/bus.h> + +struct exesdhc_softc { + struct device sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + void *sc_ih; /* Interrupt handler */ + u_int sc_flags; + + int unit; /* unit id */ + struct device *sdmmc; /* generic SD/MMC device */ + int clockbit; /* clock control bit */ + u_int clkbase; /* base clock frequency in KHz */ + int maxblklen; /* maximum block length */ + int flags; /* flags for this host */ + uint32_t ocr; /* OCR value from capabilities */ +// u_int8_t regs[14]; /* host controller state */ + uint32_t intr_status; /* soft interrupt status */ + uint32_t intr_error_status; /* */ +}; + + +/* Host controller functions called by the attachment driver. */ +int exesdhc_host_found(struct exesdhc_softc *, bus_space_tag_t, + bus_space_handle_t, bus_size_t, int); +void exesdhc_power(int, void *); +void exesdhc_shutdown(void *); +int exesdhc_intr(void *); + +/* RESET MODES */ +#define MMC_RESET_DAT 1 +#define MMC_RESET_CMD 2 +#define MMC_RESET_ALL (MMC_RESET_CMD|MMC_RESET_DAT) + +#define HDEVNAME(sc) ((sc)->sc_dev.dv_xname) + +/* flag values */ +#define SHF_USE_DMA 0x0001 + +/* SDHC should only be accessed with 4 byte reads or writes. */ +#define HREAD4(sc, reg) \ + (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) +#define HWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define HSET4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) +#define HCLR4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) + +int exesdhc_host_reset(sdmmc_chipset_handle_t); +uint32_t exesdhc_host_ocr(sdmmc_chipset_handle_t); +int exesdhc_host_maxblklen(sdmmc_chipset_handle_t); +int exesdhc_card_detect(sdmmc_chipset_handle_t); +int exesdhc_bus_power(sdmmc_chipset_handle_t, uint32_t); +int exesdhc_bus_clock(sdmmc_chipset_handle_t, int); +void exesdhc_card_intr_mask(sdmmc_chipset_handle_t, int); +void exesdhc_card_intr_ack(sdmmc_chipset_handle_t); +void exesdhc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *); +int exesdhc_start_command(struct exesdhc_softc *, struct sdmmc_command *); +int exesdhc_wait_state(struct exesdhc_softc *, uint32_t, uint32_t); +int exesdhc_soft_reset(struct exesdhc_softc *, int); +int exesdhc_wait_intr(struct exesdhc_softc *, int, int); +void exesdhc_transfer_data(struct exesdhc_softc *, struct sdmmc_command *); +void exesdhc_read_data(struct exesdhc_softc *, u_char *, int); +void exesdhc_write_data(struct exesdhc_softc *, u_char *, int); + +//#define SDHC_DEBUG +#ifdef SDHC_DEBUG +int exesdhcdebug = 20; +#define DPRINTF(n,s) do { if ((n) <= exesdhcdebug) printf s; } while (0) +#else +#define DPRINTF(n,s) do {} while(0) +#endif + +struct sdmmc_chip_functions exesdhc_functions = { + /* host controller reset */ + exesdhc_host_reset, + /* host controller capabilities */ + exesdhc_host_ocr, + exesdhc_host_maxblklen, + /* card detection */ + exesdhc_card_detect, + /* bus power and clock frequency */ + exesdhc_bus_power, + exesdhc_bus_clock, + /* command execution */ + exesdhc_exec_command, + /* card interrupt */ + exesdhc_card_intr_mask, + exesdhc_card_intr_ack +}; + +struct cfdriver exesdhc_cd = { + NULL, "exesdhc", DV_DULL +}; + +struct cfattach exesdhc_ca = { + sizeof(struct exesdhc_softc), NULL, exesdhc_attach +}; +struct cfattach exesdhc_fdt_ca = { + sizeof(struct exesdhc_softc), exesdhc_match, exesdhc_attach +}; + +int +exesdhc_match(struct device *parent, void *v, void *aux) +{ + struct armv7_attach_args *aa = aux; + + if (fdt_node_compatible("samsung,exynos5250-dw-mshc", aa->aa_node)) + return 1; + + return 0; +} + +void +exesdhc_attach(struct device *parent, struct device *self, void *args) +{ + struct exesdhc_softc *sc = (struct exesdhc_softc *) self; + struct armv7_attach_args *aa = args; + struct fdt_memory mem; + struct sdmmcbus_attach_args saa; + int error = 1, irq; + uint32_t caps; + + sc->sc_iot = aa->aa_iot; + if (aa->aa_node) { + static int unit = 0; + uint32_t ints[3]; + + sc->unit = unit++; + + if (fdt_get_memory_address(aa->aa_node, 0, &mem)) + panic("%s: could not extract memory data from FDT", + __func__); + + /* TODO: Add interrupt FDT API. */ + if (fdt_node_property_ints(aa->aa_node, "interrupts", + ints, 3) != 3) + panic("%s: could not extract interrupt data from FDT", + __func__); + + irq = ints[1]; + } else { + irq = aa->aa_dev->irq[0]; + mem.addr = aa->aa_dev->mem[0].addr; + mem.size = aa->aa_dev->mem[0].size; + } + + if (bus_space_map(sc->sc_iot, mem.addr, mem.size, 0, &sc->sc_ioh)) + panic("%s: bus_space_map failed!", __func__); + + printf("\n"); + + /* XXX DMA channels? */ + + sc->sc_ih = arm_intr_establish(irq, IPL_SDMMC, + exesdhc_intr, sc, sc->sc_dev.dv_xname); + + /* + * Reset the host controller and enable interrupts. + */ + if (exesdhc_host_reset(sc)) + goto err; + + /* Determine host capabilities. */ + caps = HREAD4(sc, SDHC_HOST_CTRL_CAP); + + /* + * Determine the base clock frequency. (2.2.24) + */ + //sc->clkbase = exccm_get_usdhx(aa->aa_dev->unit + 1); + sc->clkbase = 0; + + /* + * Determine SD bus voltage levels supported by the controller. + */ + if (caps & SDHC_HOST_CTRL_CAP_VS18) + SET(sc->ocr, MMC_OCR_1_7V_1_8V | MMC_OCR_1_8V_1_9V); + if (caps & SDHC_HOST_CTRL_CAP_VS30) + SET(sc->ocr, MMC_OCR_2_9V_3_0V | MMC_OCR_3_0V_3_1V); + if (caps & SDHC_HOST_CTRL_CAP_VS33) + SET(sc->ocr, MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V); + + /* + * Determine max block size. + */ + switch ((caps >> SDHC_HOST_CTRL_CAP_MBL_SHIFT) + & SDHC_HOST_CTRL_CAP_MBL_MASK) { + case 0: + sc->maxblklen = 512; + break; + case 1: + sc->maxblklen = 1024; + break; + case 2: + sc->maxblklen = 2048; + break; + case 3: + sc->maxblklen = 4096; + break; + default: + sc->maxblklen = 512; + printf("invalid capability blocksize in capa %08x," + " trying 512\n", caps); + } + + /* somewhere this blksize might be used instead of the device's */ + sc->maxblklen = 512; + + /* + * Attach the generic SD/MMC bus driver. (The bus driver must + * not invoke any chipset functions before it is attached.) + */ + + bzero(&saa, sizeof(saa)); + saa.saa_busname = "sdmmc"; + saa.sct = &exesdhc_functions; + saa.sch = sc; + + sc->sdmmc = config_found(&sc->sc_dev, &saa, NULL); + if (sc->sdmmc == NULL) { + error = 0; + goto err; + } + + return; + +err: + return; +} + + +/* + * Power hook established by or called from attachment driver. + */ +void +exesdhc_power(int why, void *arg) +{ +} + +/* + * Shutdown hook established by or called from attachment driver. + */ +void +exesdhc_shutdown(void *arg) +{ + struct exesdhc_softc *sc = arg; + + /* XXX chip locks up if we don't disable it before reboot. */ + (void)exesdhc_host_reset(sc); +} + +/* + * Reset the host controller. Called during initialization, when + * cards are removed, upon resume, and during error recovery. + */ +int +exesdhc_host_reset(sdmmc_chipset_handle_t sch) +{ + struct exesdhc_softc *sc = sch; + u_int32_t imask; + int error; + int s; + + s = splsdmmc(); + + /* Disable all interrupts. */ + HWRITE4(sc, SDHC_INT_STATUS_EN, 0); + HWRITE4(sc, SDHC_INT_SIGNAL_EN, 0); + + /* + * Reset the entire host controller and wait up to 100ms for + * the controller to clear the reset bit. + */ + if ((error = exesdhc_soft_reset(sc, SDHC_SYS_CTRL_RSTA)) != 0) { + splx(s); + return (error); + } + + /* Set data timeout counter value to max for now. */ + HSET4(sc, SDHC_SYS_CTRL, 0xe << SDHC_SYS_CTRL_DTOCV_SHIFT); + + /* Enable interrupts. */ + imask = SDHC_INT_STATUS_CC | SDHC_INT_STATUS_TC | + SDHC_INT_STATUS_BGE | +#ifdef SDHC_DMA + SHDC_INT_STATUS_DINT; +#else + SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR; +#endif + + imask |= SDHC_INT_STATUS_CTOE | SDHC_INT_STATUS_CCE | + SDHC_INT_STATUS_CEBE | SDHC_INT_STATUS_CIC | + SDHC_INT_STATUS_DTOE | SDHC_INT_STATUS_DCE | + SDHC_INT_STATUS_DEBE | SDHC_INT_STATUS_DMAE; + + HWRITE4(sc, SDHC_INT_STATUS_EN, imask); + HWRITE4(sc, SDHC_INT_SIGNAL_EN, imask); + + // Use no or simple DMA + HWRITE4(sc, SDHC_PROT_CTRL, + HREAD4(sc, SDHC_PROT_CTRL) & ~SDHC_PROT_CTRL_DMASEL_SDMA_MASK); + + splx(s); + return 0; +} + +uint32_t +exesdhc_host_ocr(sdmmc_chipset_handle_t sch) +{ + struct exesdhc_softc *sc = sch; + return sc->ocr; +} + +int +exesdhc_host_maxblklen(sdmmc_chipset_handle_t sch) +{ + struct exesdhc_softc *sc = sch; + return sc->maxblklen; +} + +/* + * Return non-zero if the card is currently inserted. + */ +int +exesdhc_card_detect(sdmmc_chipset_handle_t sch) +{ + struct exesdhc_softc *sc = sch; + int gpio; + + switch (board_id) + { + case BOARD_ID_EXYNOS5_CHROMEBOOK: + switch (sc->unit) { + case 2: + gpio = 6*32 + 0; + break; + case 3: + gpio = 1*32 + 6; + break; + default: + return 0; + } + return exgpio_get_bit(gpio) ? 0 : 1; + default: + return 1; + } +} + +/* + * Set or change SD bus voltage and enable or disable SD bus power. + * Return zero on success. + */ +int +exesdhc_bus_power(sdmmc_chipset_handle_t sch, uint32_t ocr) +{ + return 0; +} + +/* + * Set or change SDCLK frequency or disable the SD clock. + * Return zero on success. + */ +int +exesdhc_bus_clock(sdmmc_chipset_handle_t sch, int freq) +{ + struct exesdhc_softc *sc = sch; + int div, pre_div, cur_freq, s; + int error = 0; + + s = splsdmmc(); + + if (sc->clkbase / 16 > freq) { + for (pre_div = 2; pre_div < 256; pre_div *= 2) + if ((sc->clkbase / pre_div) <= (freq * 16)) + break; + } else + pre_div = 2; + + if (sc->clkbase == freq) + pre_div = 1; + + for (div = 1; div <= 16; div++) + if ((sc->clkbase / (div * pre_div)) <= freq) + break; + + div -= 1; + pre_div >>= 1; + + cur_freq = sc->clkbase / (pre_div * 2) / (div + 1); + + /* disable force CLK ouput active */ + HCLR4(sc, SDHC_VEND_SPEC, SDHC_VEND_SPEC_FRC_SDCLK_ON); + + /* wait while clock is unstable */ + if ((error = exesdhc_wait_state(sc, SDHC_PRES_STATE_SDSTB, SDHC_PRES_STATE_SDSTB)) != 0) + goto ret; + + HCLR4(sc, SDHC_SYS_CTRL, SDHC_SYS_CTRL_CLOCK_MASK); + HSET4(sc, SDHC_SYS_CTRL, (div << SDHC_SYS_CTRL_CLOCK_DIV_SHIFT) | (pre_div << SDHC_SYS_CTRL_CLOCK_PRE_SHIFT)); + + /* wait while clock is unstable */ + if ((error = exesdhc_wait_state(sc, SDHC_PRES_STATE_SDSTB, SDHC_PRES_STATE_SDSTB)) != 0) + goto ret; + +ret: + splx(s); + return error; +} + +void +exesdhc_card_intr_mask(sdmmc_chipset_handle_t sch, int enable) +{ + printf("exesdhc_card_intr_mask\n"); + /* - this is SDIO card interrupt */ + struct exesdhc_softc *sc = sch; + + if (enable) { + HSET4(sc, SDHC_INT_STATUS_EN, SDHC_INT_STATUS_CINT); + HSET4(sc, SDHC_INT_SIGNAL_EN, SDHC_INT_STATUS_CINT); + } else { + HCLR4(sc, SDHC_INT_STATUS_EN, SDHC_INT_STATUS_CINT); + HCLR4(sc, SDHC_INT_SIGNAL_EN, SDHC_INT_STATUS_CINT); + } +} + +void +exesdhc_card_intr_ack(sdmmc_chipset_handle_t sch) +{ + printf("exesdhc_card_intr_ack\n"); + struct exesdhc_softc *sc = sch; + + HWRITE4(sc, SDHC_INT_STATUS, SDHC_INT_STATUS_CINT); +} + +int +exesdhc_wait_state(struct exesdhc_softc *sc, uint32_t mask, uint32_t value) +{ + uint32_t state; + int timeout; + state = HREAD4(sc, SDHC_PRES_STATE); + DPRINTF(3,("%s: wait_state %x %x %x)\n", HDEVNAME(sc), + mask, value, state)); + for (timeout = 1000; timeout > 0; timeout--) { + if (((state = HREAD4(sc, SDHC_PRES_STATE)) & mask) == value) + return 0; + delay(10); + } + DPRINTF(0,("%s: timeout waiting for %x\n", HDEVNAME(sc), + value, state)); + return ETIMEDOUT; +} + +void +exesdhc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd) +{ + struct exesdhc_softc *sc = sch; + int error; + + /* + * Start the command, or mark `cmd' as failed and return. + */ + error = exesdhc_start_command(sc, cmd); + if (error != 0) { + cmd->c_error = error; + SET(cmd->c_flags, SCF_ITSDONE); + return; + } + + /* + * Wait until the command phase is done, or until the command + * is marked done for any other reason. + */ + if (!exesdhc_wait_intr(sc, SDHC_INT_STATUS_CC, SDHC_COMMAND_TIMEOUT)) { + cmd->c_error = ETIMEDOUT; + SET(cmd->c_flags, SCF_ITSDONE); + return; + } + + /* + * The host controller removes bits [0:7] from the response + * data (CRC) and we pass the data up unchanged to the bus + * driver (without padding). + */ + if (cmd->c_error == 0 && ISSET(cmd->c_flags, SCF_RSP_PRESENT)) { + if (ISSET(cmd->c_flags, SCF_RSP_136)) { + cmd->c_resp[0] = HREAD4(sc, SDHC_CMD_RSP0); + cmd->c_resp[1] = HREAD4(sc, SDHC_CMD_RSP1); + cmd->c_resp[2] = HREAD4(sc, SDHC_CMD_RSP2); + cmd->c_resp[3] = HREAD4(sc, SDHC_CMD_RSP3); + +#ifdef SDHC_DEBUG + printf("resp[0] 0x%08x\nresp[1] 0x%08x\nresp[2] 0x%08x\nresp[3] 0x%08x\n", cmd->c_resp[0], cmd->c_resp[1], cmd->c_resp[2], cmd->c_resp[3]); +#endif + } else { + cmd->c_resp[0] = HREAD4(sc, SDHC_CMD_RSP0); +#ifdef SDHC_DEBUG + printf("resp[0] 0x%08x\n", cmd->c_resp[0]); +#endif + } + } + + /* + * If the command has data to transfer in any direction, + * execute the transfer now. + */ + if (cmd->c_error == 0 && cmd->c_data) + exesdhc_transfer_data(sc, cmd); + + DPRINTF(1,("%s: cmd %u done (flags=%#x error=%d)\n", + HDEVNAME(sc), cmd->c_opcode, cmd->c_flags, cmd->c_error)); + SET(cmd->c_flags, SCF_ITSDONE); +} + +int +exesdhc_start_command(struct exesdhc_softc *sc, struct sdmmc_command *cmd) +{ + u_int32_t blksize = 0; + u_int32_t blkcount = 0; + u_int32_t command; + int error; + int s; + + DPRINTF(1,("%s: start cmd %u arg=%#x data=%#x dlen=%d flags=%#x " + "proc=\"%s\"\n", HDEVNAME(sc), cmd->c_opcode, cmd->c_arg, + cmd->c_data, cmd->c_datalen, cmd->c_flags, curproc ? + curproc->p_comm : "")); + + /* + * The maximum block length for commands should be the minimum + * of the host buffer size and the card buffer size. (1.7.2) + */ + + /* Fragment the data into proper blocks. */ + if (cmd->c_datalen > 0) { + blksize = MIN(cmd->c_datalen, cmd->c_blklen); + blkcount = cmd->c_datalen / blksize; + if (cmd->c_datalen % blksize > 0) { + /* XXX: Split this command. (1.7.4) */ + printf("%s: data not a multiple of %d bytes\n", + HDEVNAME(sc), blksize); + return EINVAL; + } + } + + /* Check limit imposed by 9-bit block count. (1.7.2) */ + if (blkcount > SDHC_BLK_ATT_BLKCNT_MAX) { + printf("%s: too much data\n", HDEVNAME(sc)); + return EINVAL; + } + + /* setup for PIO, check for write protection */ + if (!ISSET(cmd->c_flags, SCF_CMD_READ)) { + if (!(HREAD4(sc, SDHC_PRES_STATE) & SDHC_PRES_STATE_WPSPL)) { + printf("%s: card is write protected\n", + HDEVNAME(sc)); + return EINVAL; + } + } + +#ifdef SDHC_DMA + /* set watermark level */ + uint32_t wml = blksize / sizeof(uint32_t); + if (ISSET(cmd->c_flags, SCF_CMD_READ)) { + if (wml > 16) + wml = 16; + HWRITE4(sc, SDHC_WTMK_LVL, wml << SDHC_WTMK_LVL_RD_WML_SHIFT); + } else { + if (wml > 128) + wml = 128; + HWRITE4(sc, SDHC_WTMK_LVL, wml << SDHC_WTMK_LVL_WR_WML_SHIFT); + } +#endif + + /* Prepare transfer mode register value. (2.2.5) */ + command = 0; + + if (ISSET(cmd->c_flags, SCF_CMD_READ)) + command |= SDHC_MIX_CTRL_DTDSEL; + if (blkcount > 0) { + command |= SDHC_MIX_CTRL_BCEN; +#ifdef SDHC_DMA + command |= SDHC_MIX_CTRL_DMAEN; +#endif + if (blkcount > 1) { + command |= SDHC_MIX_CTRL_MSBSEL; + command |= SDHC_MIX_CTRL_AC12EN; + } + } + + command |= (cmd->c_opcode << SDHC_CMD_XFR_TYP_CMDINDX_SHIFT) & + SDHC_CMD_XFR_TYP_CMDINDX_SHIFT_MASK; + + if (ISSET(cmd->c_flags, SCF_RSP_CRC)) + command |= SDHC_CMD_XFR_TYP_CCCEN; + if (ISSET(cmd->c_flags, SCF_RSP_IDX)) + command |= SDHC_CMD_XFR_TYP_CICEN; + if (cmd->c_data != NULL) + command |= SDHC_CMD_XFR_TYP_DPSEL; + + if (!ISSET(cmd->c_flags, SCF_RSP_PRESENT)) + command |= SDHC_CMD_XFR_TYP_RSP_NONE; + else if (ISSET(cmd->c_flags, SCF_RSP_136)) + command |= SDHC_CMD_XFR_TYP_RSP136; + else if (ISSET(cmd->c_flags, SCF_RSP_BSY)) + command |= SDHC_CMD_XFR_TYP_RSP48B; + else + command |= SDHC_CMD_XFR_TYP_RSP48; + + /* Wait until command and data inhibit bits are clear. (1.5) */ + if ((error = exesdhc_wait_state(sc, SDHC_PRES_STATE_CIHB, 0)) != 0) + return error; + + s = splsdmmc(); + + /* + * Start a CPU data transfer. Writing to the high order byte + * of the SDHC_COMMAND register triggers the SD command. (1.5) + */ +#ifdef SDHC_DMA + if (cmd->c_data) + HWRITE4(sc, SDHC_DS_ADDR, (uint32_t)cmd->c_data); +#endif + HWRITE4(sc, SDHC_BLK_ATT, blkcount << SDHC_BLK_ATT_BLKCNT_SHIFT | + blksize << SDHC_BLK_ATT_BLKSIZE_SHIFT); + HWRITE4(sc, SDHC_CMD_ARG, cmd->c_arg); + HWRITE4(sc, SDHC_MIX_CTRL, + (HREAD4(sc, SDHC_MIX_CTRL) & (0xf << 22)) | (command & 0xffff)); + HWRITE4(sc, SDHC_CMD_XFR_TYP, command); + + splx(s); + return 0; +} + +void +exesdhc_transfer_data(struct exesdhc_softc *sc, struct sdmmc_command *cmd) +{ +#ifndef SDHC_DMA + u_char *datap = cmd->c_data; + int i; +#endif + int datalen; + int mask; + int error; + + mask = ISSET(cmd->c_flags, SCF_CMD_READ) ? + SDHC_PRES_STATE_BREN : SDHC_PRES_STATE_BWEN; + error = 0; + datalen = cmd->c_datalen; + + DPRINTF(1,("%s: resp=%#x datalen=%d\n", HDEVNAME(sc), + MMC_R1(cmd->c_resp), datalen)); + +#ifndef SDHC_DMA + while (datalen > 0) { + if (!exesdhc_wait_intr(sc, SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR, + SDHC_BUFFER_TIMEOUT)) { + error = ETIMEDOUT; + break; + } + + if ((error = exesdhc_wait_state(sc, mask, mask)) != 0) + break; + + /* FIXME: wait a bit, else it fails */ + delay(100); + i = MIN(datalen, cmd->c_blklen); + if (ISSET(cmd->c_flags, SCF_CMD_READ)) + exesdhc_read_data(sc, datap, i); + else + exesdhc_write_data(sc, datap, i); + + datap += i; + datalen -= i; + } +#endif + + if (error == 0 && !exesdhc_wait_intr(sc, SDHC_INT_STATUS_TC, + SDHC_TRANSFER_TIMEOUT)) + error = ETIMEDOUT; + + if (error != 0) + cmd->c_error = error; + SET(cmd->c_flags, SCF_ITSDONE); + + DPRINTF(1,("%s: data transfer done (error=%d)\n", + HDEVNAME(sc), cmd->c_error)); +} + +void +exesdhc_read_data(struct exesdhc_softc *sc, u_char *datap, int datalen) +{ + while (datalen > 3) { + *(uint32_t *)datap = HREAD4(sc, SDHC_DATA_BUFF_ACC_PORT); + datap += 4; + datalen -= 4; + } + if (datalen > 0) { + uint32_t rv = HREAD4(sc, SDHC_DATA_BUFF_ACC_PORT); + do { + *datap++ = rv & 0xff; + rv = rv >> 8; + } while (--datalen > 0); + } +} + +void +exesdhc_write_data(struct exesdhc_softc *sc, u_char *datap, int datalen) +{ + while (datalen > 3) { + DPRINTF(3,("%08x\n", *(uint32_t *)datap)); + HWRITE4(sc, SDHC_DATA_BUFF_ACC_PORT, *((uint32_t *)datap)); + datap += 4; + datalen -= 4; + } + if (datalen > 0) { + uint32_t rv = *datap++; + if (datalen > 1) + rv |= *datap++ << 8; + if (datalen > 2) + rv |= *datap++ << 16; + DPRINTF(3,("rv %08x\n", rv)); + HWRITE4(sc, SDHC_DATA_BUFF_ACC_PORT, rv); + } +} + +/* Prepare for another command. */ +int +exesdhc_soft_reset(struct exesdhc_softc *sc, int mask) +{ + int timo; + + DPRINTF(1,("%s: software reset reg=%#x\n", HDEVNAME(sc), mask)); + + /* disable force CLK ouput active */ + HCLR4(sc, SDHC_VEND_SPEC, SDHC_VEND_SPEC_FRC_SDCLK_ON); + + /* reset */ + HSET4(sc, SDHC_SYS_CTRL, mask); + delay(10); + + for (timo = 1000; timo > 0; timo--) { + if (!ISSET(HREAD4(sc, SDHC_SYS_CTRL), mask)) + break; + delay(10); + } + if (timo == 0) { + DPRINTF(1,("%s: timeout reg=%#x\n", HDEVNAME(sc), + HREAD4(sc, SDHC_SYS_CTRL))); + return ETIMEDOUT; + } + + return 0; +} + +int +exesdhc_wait_intr(struct exesdhc_softc *sc, int mask, int timo) +{ + int status; + int s; + + mask |= SDHC_INT_STATUS_ERR; + s = splsdmmc(); + + /* enable interrupts for brr and bwr */ + if (mask & (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR)) + HSET4(sc, SDHC_INT_SIGNAL_EN, (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR)); + + status = sc->intr_status & mask; + while (status == 0) { + if (tsleep(&sc->intr_status, PWAIT, "hcintr", timo) + == EWOULDBLOCK) { + status |= SDHC_INT_STATUS_ERR; + break; + } + status = sc->intr_status & mask; + } + sc->intr_status &= ~status; + DPRINTF(2,("%s: intr status %#x error %#x\n", HDEVNAME(sc), status, + sc->intr_error_status)); + + /* Command timeout has higher priority than command complete. */ + if (ISSET(status, SDHC_INT_STATUS_ERR)) { + sc->intr_error_status = 0; + (void)exesdhc_soft_reset(sc, SDHC_SYS_CTRL_RSTC | SDHC_SYS_CTRL_RSTD); + status = 0; + } + + splx(s); + return status; +} + +/* + * Established by attachment driver at interrupt priority IPL_SDMMC. + */ +int +exesdhc_intr(void *arg) +{ + struct exesdhc_softc *sc = arg; + + u_int32_t status; + + /* Find out which interrupts are pending. */ + status = HREAD4(sc, SDHC_INT_STATUS); + +#ifndef SDHC_DMA + /* disable interrupts for brr and bwr, else we get flooded */ + if (status & (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR)) + HCLR4(sc, SDHC_INT_SIGNAL_EN, (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR)); +#endif + + /* Acknowledge the interrupts we are about to handle. */ + HWRITE4(sc, SDHC_INT_STATUS, status); + DPRINTF(2,("%s: interrupt status=0x%08x\n", HDEVNAME(sc), + status, status)); + + /* + * Service error interrupts. + */ + if (ISSET(status, SDHC_INT_STATUS_CMD_ERR | + SDHC_INT_STATUS_CTOE | SDHC_INT_STATUS_DTOE)) { + sc->intr_status |= status; + sc->intr_error_status |= status & 0xffff0000; + wakeup(&sc->intr_status); + } + + /* + * Wake up the blocking process to service command + * related interrupt(s). + */ + if (ISSET(status, SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR | + SDHC_INT_STATUS_TC | SDHC_INT_STATUS_CC)) { + sc->intr_status |= status; + wakeup(&sc->intr_status); + } + + /* + * Service SD card interrupts. + */ + if (ISSET(status, SDHC_INT_STATUS_CINT)) { + DPRINTF(0,("%s: card interrupt\n", HDEVNAME(sc))); + HCLR4(sc, SDHC_INT_STATUS, SDHC_INT_STATUS_CINT); + sdmmc_card_intr(sc->sdmmc); + } + return 1; +} |