/* $OpenBSD: sximmc.c,v 1.15 2024/05/13 01:15:50 jsg Exp $ */ /* $NetBSD: awin_mmc.c,v 1.23 2015/11/14 10:32:40 bouyer Exp $ */ /*- * Copyright (c) 2014 Jared D. McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR 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 //#define SXIMMC_DEBUG #define SXIMMC_GCTRL 0x0000 #define SXIMMC_CLKCR 0x0004 #define SXIMMC_TIMEOUT 0x0008 #define SXIMMC_WIDTH 0x000C #define SXIMMC_BLKSZ 0x0010 #define SXIMMC_BYTECNT 0x0014 #define SXIMMC_CMD 0x0018 #define SXIMMC_ARG 0x001C #define SXIMMC_RESP0 0x0020 #define SXIMMC_RESP1 0x0024 #define SXIMMC_RESP2 0x0028 #define SXIMMC_RESP3 0x002C #define SXIMMC_IMASK 0x0030 #define SXIMMC_MINT 0x0034 #define SXIMMC_RINT 0x0038 #define SXIMMC_STATUS 0x003C #define SXIMMC_FTRGLEVEL 0x0040 #define SXIMMC_FUNCSEL 0x0044 #define SXIMMC_CBCR 0x0048 #define SXIMMC_BBCR 0x004C #define SXIMMC_DBGC 0x0050 #define SXIMMC_A12A 0x0058 /* A80 */ #define SXIMMC_HWRST 0x0078 /* A80 */ #define SXIMMC_DMAC 0x0080 #define SXIMMC_DLBA 0x0084 #define SXIMMC_IDST 0x0088 #define SXIMMC_IDIE 0x008C #define SXIMMC_CHDA 0x0090 #define SXIMMC_CBDA 0x0094 #define SXIMMC_FIFO_A10 0x0100 #define SXIMMC_FIFO_A31 0x0200 #define SXIMMC_GCTRL_ACCESS_BY_AHB (1U << 31) #define SXIMMC_GCTRL_WAIT_MEM_ACCESS_DONE (1U << 30) #define SXIMMC_GCTRL_DDR_MODE (1U << 10) #define SXIMMC_GCTRL_DEBOUNCEEN (1U << 8) #define SXIMMC_GCTRL_DMAEN (1U << 5) #define SXIMMC_GCTRL_INTEN (1U << 4) #define SXIMMC_GCTRL_DMARESET (1U << 2) #define SXIMMC_GCTRL_FIFORESET (1U << 1) #define SXIMMC_GCTRL_SOFTRESET (1U << 0) #define SXIMMC_GCTRL_RESET \ (SXIMMC_GCTRL_SOFTRESET | SXIMMC_GCTRL_FIFORESET | \ SXIMMC_GCTRL_DMARESET) #define SXIMMC_CLKCR_LOWPOWERON (1U << 17) #define SXIMMC_CLKCR_CARDCLKON (1U << 16) #define SXIMMC_CLKCR_DIV 0x0000ffff #define SXIMMC_WIDTH_1 0 #define SXIMMC_WIDTH_4 1 #define SXIMMC_WIDTH_8 2 #define SXIMMC_CMD_START (1U << 31) #define SXIMMC_CMD_USE_HOLD_REG (1U << 29) #define SXIMMC_CMD_VOL_SWITCH (1U << 28) #define SXIMMC_CMD_BOOT_ABORT (1U << 27) #define SXIMMC_CMD_BOOT_ACK_EXP (1U << 26) #define SXIMMC_CMD_ALT_BOOT_OPT (1U << 25) #define SXIMMC_CMD_ENBOOT (1U << 24) #define SXIMMC_CMD_CCS_EXP (1U << 23) #define SXIMMC_CMD_RD_CEATA_DEV (1U << 22) #define SXIMMC_CMD_UPCLK_ONLY (1U << 21) #define SXIMMC_CMD_SEND_INIT_SEQ (1U << 15) #define SXIMMC_CMD_STOP_ABORT_CMD (1U << 14) #define SXIMMC_CMD_WAIT_PRE_OVER (1U << 13) #define SXIMMC_CMD_SEND_AUTO_STOP (1U << 12) #define SXIMMC_CMD_SEQMOD (1U << 11) #define SXIMMC_CMD_WRITE (1U << 10) #define SXIMMC_CMD_DATA_EXP (1U << 9) #define SXIMMC_CMD_CHECK_RSP_CRC (1U << 8) #define SXIMMC_CMD_LONG_RSP (1U << 7) #define SXIMMC_CMD_RSP_EXP (1U << 6) #define SXIMMC_INT_CARD_REMOVE (1U << 31) #define SXIMMC_INT_CARD_INSERT (1U << 30) #define SXIMMC_INT_SDIO_INT (1U << 16) #define SXIMMC_INT_END_BIT_ERR (1U << 15) #define SXIMMC_INT_AUTO_CMD_DONE (1U << 14) #define SXIMMC_INT_START_BIT_ERR (1U << 13) #define SXIMMC_INT_HW_LOCKED (1U << 12) #define SXIMMC_INT_FIFO_RUN_ERR (1U << 11) #define SXIMMC_INT_VOL_CHG_DONE (1U << 10) #define SXIMMC_INT_DATA_STARVE (1U << 10) #define SXIMMC_INT_BOOT_START (1U << 9) #define SXIMMC_INT_DATA_TIMEOUT (1U << 9) #define SXIMMC_INT_ACK_RCV (1U << 8) #define SXIMMC_INT_RESP_TIMEOUT (1U << 8) #define SXIMMC_INT_DATA_CRC_ERR (1U << 7) #define SXIMMC_INT_RESP_CRC_ERR (1U << 6) #define SXIMMC_INT_RX_DATA_REQ (1U << 5) #define SXIMMC_INT_TX_DATA_REQ (1U << 4) #define SXIMMC_INT_DATA_OVER (1U << 3) #define SXIMMC_INT_CMD_DONE (1U << 2) #define SXIMMC_INT_RESP_ERR (1U << 1) #define SXIMMC_INT_ERROR \ (SXIMMC_INT_RESP_ERR | SXIMMC_INT_RESP_CRC_ERR | \ SXIMMC_INT_DATA_CRC_ERR | SXIMMC_INT_RESP_TIMEOUT | \ SXIMMC_INT_FIFO_RUN_ERR | SXIMMC_INT_HW_LOCKED | \ SXIMMC_INT_START_BIT_ERR | SXIMMC_INT_END_BIT_ERR) #define SXIMMC_STATUS_DMAREQ (1U << 31) #define SXIMMC_STATUS_DATA_FSM_BUSY (1U << 10) #define SXIMMC_STATUS_CARD_DATA_BUSY (1U << 9) #define SXIMMC_STATUS_CARD_PRESENT (1U << 8) #define SXIMMC_STATUS_FIFO_FULL (1U << 3) #define SXIMMC_STATUS_FIFO_EMPTY (1U << 2) #define SXIMMC_STATUS_TXWL_FLAG (1U << 1) #define SXIMMC_STATUS_RXWL_FLAG (1U << 0) #define SXIMMC_FUNCSEL_CEATA_DEV_INTEN (1U << 10) #define SXIMMC_FUNCSEL_SEND_AUTO_STOP_CCSD (1U << 9) #define SXIMMC_FUNCSEL_SEND_CCSD (1U << 8) #define SXIMMC_FUNCSEL_ABT_RD_DATA (1U << 2) #define SXIMMC_FUNCSEL_SDIO_RD_WAIT (1U << 1) #define SXIMMC_FUNCSEL_SEND_IRQ_RSP (1U << 0) #define SXIMMC_DMAC_REFETCH_DES (1U << 31) #define SXIMMC_DMAC_IDMA_ON (1U << 7) #define SXIMMC_DMAC_FIX_BURST (1U << 1) #define SXIMMC_DMAC_SOFTRESET (1U << 0) #define SXIMMC_IDST_HOST_ABT (1U << 10) #define SXIMMC_IDST_ABNORMAL_INT_SUM (1U << 9) #define SXIMMC_IDST_NORMAL_INT_SUM (1U << 8) #define SXIMMC_IDST_CARD_ERR_SUM (1U << 5) #define SXIMMC_IDST_DES_INVALID (1U << 4) #define SXIMMC_IDST_FATAL_BUS_ERR (1U << 2) #define SXIMMC_IDST_RECEIVE_INT (1U << 1) #define SXIMMC_IDST_TRANSMIT_INT (1U << 0) #define SXIMMC_IDST_ERROR \ (SXIMMC_IDST_ABNORMAL_INT_SUM | SXIMMC_IDST_CARD_ERR_SUM | \ SXIMMC_IDST_DES_INVALID | SXIMMC_IDST_FATAL_BUS_ERR) #define SXIMMC_IDST_COMPLETE \ (SXIMMC_IDST_RECEIVE_INT | SXIMMC_IDST_TRANSMIT_INT) struct sximmc_idma_descriptor { uint32_t dma_config; #define SXIMMC_IDMA_CONFIG_DIC (1U << 1) #define SXIMMC_IDMA_CONFIG_LD (1U << 2) #define SXIMMC_IDMA_CONFIG_FD (1U << 3) #define SXIMMC_IDMA_CONFIG_CH (1U << 4) #define SXIMMC_IDMA_CONFIG_ER (1U << 5) #define SXIMMC_IDMA_CONFIG_CES (1U << 30) #define SXIMMC_IDMA_CONFIG_OWN (1U << 31) uint32_t dma_buf_size; uint32_t dma_buf_addr; uint32_t dma_next; } __packed; #define SXIMMC_NDESC 32 #define SXIMMC_DMA_FTRGLEVEL_A20 0x20070008 #define SXIMMC_DMA_FTRGLEVEL_A80 0x200f0010 int sximmc_match(struct device *, void *, void *); void sximmc_attach(struct device *, struct device *, void *); int sximmc_intr(void *); int sximmc_host_reset(sdmmc_chipset_handle_t); uint32_t sximmc_host_ocr(sdmmc_chipset_handle_t); int sximmc_host_maxblklen(sdmmc_chipset_handle_t); int sximmc_card_detect(sdmmc_chipset_handle_t); int sximmc_bus_power(sdmmc_chipset_handle_t, uint32_t); int sximmc_bus_clock(sdmmc_chipset_handle_t, int, int); int sximmc_bus_width(sdmmc_chipset_handle_t, int); void sximmc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *); void sximmc_card_intr_mask(sdmmc_chipset_handle_t, int); void sximmc_card_intr_ack(sdmmc_chipset_handle_t); void sximmc_pwrseq_pre(uint32_t); void sximmc_pwrseq_post(uint32_t); struct sdmmc_chip_functions sximmc_chip_functions = { .host_reset = sximmc_host_reset, .host_ocr = sximmc_host_ocr, .host_maxblklen = sximmc_host_maxblklen, .card_detect = sximmc_card_detect, .bus_power = sximmc_bus_power, .bus_clock = sximmc_bus_clock, .bus_width = sximmc_bus_width, .exec_command = sximmc_exec_command, .card_intr_mask = sximmc_card_intr_mask, .card_intr_ack = sximmc_card_intr_ack, }; struct sximmc_softc { struct device sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; bus_space_handle_t sc_clk_bsh; bus_dma_tag_t sc_dmat; int sc_node; int sc_use_dma; void *sc_ih; struct device *sc_sdmmc_dev; uint32_t sc_fifo_reg; uint32_t sc_dma_ftrglevel; bus_dma_segment_t sc_idma_segs[1]; int sc_idma_nsegs; bus_size_t sc_idma_size; bus_dmamap_t sc_idma_map; int sc_idma_ndesc; char *sc_idma_desc; int sc_idma_shift; uint32_t sc_intr_rint; uint32_t sc_intr_mint; uint32_t sc_idma_idst; uint32_t sc_gpio[4]; uint32_t sc_vmmc; uint32_t sc_vqmmc; uint32_t sc_pwrseq; uint32_t sc_vdd; }; struct cfdriver sximmc_cd = { NULL, "sximmc", DV_DULL }; const struct cfattach sximmc_ca = { sizeof(struct sximmc_softc), sximmc_match, sximmc_attach }; #define MMC_WRITE(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) #define MMC_READ(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) int sximmc_set_clock(struct sximmc_softc *sc, u_int); int sximmc_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return (OF_is_compatible(faa->fa_node, "allwinner,sun4i-a10-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun5i-a13-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun7i-a20-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun9i-a80-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun20i-d1-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a64-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a64-emmc") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a100-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a100-emmc")); } int sximmc_idma_setup(struct sximmc_softc *sc) { int error; sc->sc_idma_ndesc = SXIMMC_NDESC; sc->sc_idma_size = sizeof(struct sximmc_idma_descriptor) * sc->sc_idma_ndesc; error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_idma_size, 0, sc->sc_idma_size, sc->sc_idma_segs, 1, &sc->sc_idma_nsegs, BUS_DMA_WAITOK); if (error) return error; error = bus_dmamem_map(sc->sc_dmat, sc->sc_idma_segs, sc->sc_idma_nsegs, sc->sc_idma_size, &sc->sc_idma_desc, BUS_DMA_WAITOK); if (error) goto free; error = bus_dmamap_create(sc->sc_dmat, sc->sc_idma_size, 1, sc->sc_idma_size, 0, BUS_DMA_WAITOK, &sc->sc_idma_map); if (error) goto unmap; error = bus_dmamap_load(sc->sc_dmat, sc->sc_idma_map, sc->sc_idma_desc, sc->sc_idma_size, NULL, BUS_DMA_WAITOK); if (error) goto destroy; return 0; destroy: bus_dmamap_destroy(sc->sc_dmat, sc->sc_idma_map); unmap: bus_dmamem_unmap(sc->sc_dmat, sc->sc_idma_desc, sc->sc_idma_size); free: bus_dmamem_free(sc->sc_dmat, sc->sc_idma_segs, sc->sc_idma_nsegs); return error; } void sximmc_attach(struct device *parent, struct device *self, void *aux) { struct sximmc_softc *sc = (struct sximmc_softc *)self; struct fdt_attach_args *faa = aux; struct sdmmcbus_attach_args saa; int node, width; if (faa->fa_nreg < 1) return; sc->sc_node = faa->fa_node; sc->sc_bst = faa->fa_iot; sc->sc_dmat = faa->fa_dmat; if (bus_space_map(sc->sc_bst, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_bsh)) { printf(": can't map registers\n"); return; } sc->sc_use_dma = 1; printf("\n"); pinctrl_byname(faa->fa_node, "default"); /* enable clock */ clock_enable(faa->fa_node, NULL); delay(5000); reset_deassert_all(faa->fa_node); /* * The FIFO register is in a different location on the * Allwinner A31 and later generations. Unfortunately the * compatible string wasn't changed, so we need to look at the * root node to pick the right register. * * XXX Should we always use DMA (like Linux does) to avoid * this issue? */ node = OF_finddevice("/"); if (OF_is_compatible(node, "allwinner,sun4i-a10") || OF_is_compatible(node, "allwinner,sun5i-a10s") || OF_is_compatible(node, "allwinner,sun5i-a13") || OF_is_compatible(node, "allwinner,sun7i-a20")) sc->sc_fifo_reg = SXIMMC_FIFO_A10; else sc->sc_fifo_reg = SXIMMC_FIFO_A31; if (OF_is_compatible(sc->sc_node, "allwinner,sun9i-a80-mmc")) sc->sc_dma_ftrglevel = SXIMMC_DMA_FTRGLEVEL_A80; else sc->sc_dma_ftrglevel = SXIMMC_DMA_FTRGLEVEL_A20; if (OF_is_compatible(faa->fa_node, "allwinner,sun20i-d1-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a100-mmc") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a100-emmc")) sc->sc_idma_shift = 2; if (sc->sc_use_dma) { if (sximmc_idma_setup(sc) != 0) { printf("%s: failed to setup DMA\n", self->dv_xname); return; } } OF_getpropintarray(sc->sc_node, "cd-gpios", sc->sc_gpio, sizeof(sc->sc_gpio)); gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_INPUT); sc->sc_vmmc = OF_getpropint(sc->sc_node, "vmmc-supply", 0); sc->sc_vqmmc = OF_getpropint(sc->sc_node, "vqmmc-supply", 0); sc->sc_pwrseq = OF_getpropint(sc->sc_node, "mmc-pwrseq", 0); sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO, sximmc_intr, sc, sc->sc_dev.dv_xname); if (sc->sc_ih == NULL) { printf(": can't establish interrupt\n"); return; } sximmc_bus_width(sc, 1); sximmc_set_clock(sc, 400); sximmc_host_reset(sc); memset(&saa, 0, sizeof(saa)); saa.saa_busname = "sdmmc"; saa.sct = &sximmc_chip_functions; saa.sch = sc; #if 0 saa.saa_clkmin = 400; saa.saa_clkmax = awin_chip_id() == AWIN_CHIP_ID_A80 ? 48000 : 50000; #endif saa.caps = SMC_CAPS_SD_HIGHSPEED | SMC_CAPS_MMC_HIGHSPEED; width = OF_getpropint(sc->sc_node, "bus-width", 1); if (width >= 8) saa.caps |= SMC_CAPS_8BIT_MODE; if (width >= 4) saa.caps |= SMC_CAPS_4BIT_MODE; if (sc->sc_use_dma) { saa.dmat = sc->sc_dmat; saa.caps |= SMC_CAPS_DMA; } if (OF_is_compatible(sc->sc_node, "allwinner,sun4i-a10-mmc") || OF_is_compatible(sc->sc_node, "allwinner,sun20i-d1-mmc") || OF_is_compatible(sc->sc_node, "allwinner,sun50i-a64-emmc") || OF_is_compatible(sc->sc_node, "allwinner,sun50i-a100-emmc")) { saa.max_seg = 0x2000; } else { saa.max_seg = 0x10000; } sc->sc_sdmmc_dev = config_found(self, &saa, NULL); } int sximmc_set_clock(struct sximmc_softc *sc, u_int freq) { if (freq > 0) { if (clock_set_frequency(sc->sc_node, "mmc", freq * 1000)) return EIO; clock_enable(sc->sc_node, "mmc"); delay(20000); } else clock_disable(sc->sc_node, "mmc"); return 0; } int sximmc_intr(void *priv) { struct sximmc_softc *sc = priv; uint32_t idst, rint, mint; idst = MMC_READ(sc, SXIMMC_IDST); rint = MMC_READ(sc, SXIMMC_RINT); mint = MMC_READ(sc, SXIMMC_MINT); if (!idst && !rint && !mint) return 0; MMC_WRITE(sc, SXIMMC_IDST, idst); MMC_WRITE(sc, SXIMMC_RINT, rint); MMC_WRITE(sc, SXIMMC_MINT, mint); #ifdef SXIMMC_DEBUG printf("%s: mmc intr idst=%08X rint=%08X mint=%08X\n", sc->sc_dev.dv_xname, idst, rint, mint); #endif if (idst) { sc->sc_idma_idst |= idst; wakeup(&sc->sc_idma_idst); } if (rint) { sc->sc_intr_rint |= rint; wakeup(&sc->sc_intr_rint); if (rint & SXIMMC_INT_SDIO_INT) { uint32_t imask; imask = MMC_READ(sc, SXIMMC_IMASK); imask &= ~SXIMMC_INT_SDIO_INT; MMC_WRITE(sc, SXIMMC_IMASK, imask); sdmmc_card_intr(sc->sc_sdmmc_dev); } } return 1; } void sximmc_card_intr_mask(sdmmc_chipset_handle_t sch, int enable) { struct sximmc_softc *sc = sch; uint32_t imask; imask = MMC_READ(sc, SXIMMC_IMASK); if (enable) imask |= SXIMMC_INT_SDIO_INT; else imask &= ~SXIMMC_INT_SDIO_INT; MMC_WRITE(sc, SXIMMC_IMASK, imask); } void sximmc_card_intr_ack(sdmmc_chipset_handle_t sch) { struct sximmc_softc *sc = sch; uint32_t imask; MMC_WRITE(sc, SXIMMC_RINT, SXIMMC_INT_SDIO_INT); imask = MMC_READ(sc, SXIMMC_IMASK); imask |= SXIMMC_INT_SDIO_INT; MMC_WRITE(sc, SXIMMC_IMASK, imask); } int sximmc_wait_rint(struct sximmc_softc *sc, uint32_t mask, int timeout) { int retry; int error; splassert(IPL_BIO); if (sc->sc_intr_rint & mask) return 0; retry = sc->sc_use_dma ? (timeout / hz) : 10000; while (retry > 0) { if (sc->sc_use_dma) { error = tsleep_nsec(&sc->sc_intr_rint, PWAIT, "rint", SEC_TO_NSEC(1)); if (error && error != EWOULDBLOCK) return error; if (sc->sc_intr_rint & mask) return 0; } else { sc->sc_intr_rint |= MMC_READ(sc, SXIMMC_RINT); if (sc->sc_intr_rint & mask) return 0; delay(1000); } --retry; } return ETIMEDOUT; } void sximmc_led(struct sximmc_softc *sc, int on) { } int sximmc_host_reset(sdmmc_chipset_handle_t sch) { struct sximmc_softc *sc = sch; int retry = 1000; #if 0 if (awin_chip_id() == AWIN_CHIP_ID_A80) { if (sc->sc_mmc_port == 2 || sc->sc_mmc_port == 3) { MMC_WRITE(sc, SXIMMC_HWRST, 0); delay(10); MMC_WRITE(sc, SXIMMC_HWRST, 1); delay(300); } } #endif MMC_WRITE(sc, SXIMMC_GCTRL, MMC_READ(sc, SXIMMC_GCTRL) | SXIMMC_GCTRL_RESET); while (--retry > 0) { if (!(MMC_READ(sc, SXIMMC_GCTRL) & SXIMMC_GCTRL_RESET)) break; delay(100); } #ifdef SXIMMC_DEBUG if (retry == 0) printf("%s: host reset failed\n", sc->sc_dev.dv_xname); else printf("%s: host reset succeeded\n", sc->sc_dev.dv_xname); #endif /* Allow access to the FIFO by the CPU. */ MMC_WRITE(sc, SXIMMC_GCTRL, MMC_READ(sc, SXIMMC_GCTRL) | SXIMMC_GCTRL_ACCESS_BY_AHB); MMC_WRITE(sc, SXIMMC_TIMEOUT, 0xffffffff); MMC_WRITE(sc, SXIMMC_IMASK, SXIMMC_INT_CMD_DONE | SXIMMC_INT_ERROR | SXIMMC_INT_DATA_OVER | SXIMMC_INT_AUTO_CMD_DONE); MMC_WRITE(sc, SXIMMC_GCTRL, MMC_READ(sc, SXIMMC_GCTRL) | SXIMMC_GCTRL_INTEN); return 0; } uint32_t sximmc_host_ocr(sdmmc_chipset_handle_t sch) { #if 0 return MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V | MMC_OCR_HCS; #else return MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V; #endif } int sximmc_host_maxblklen(sdmmc_chipset_handle_t sch) { #if 0 return 8192; #else return 512; #endif } int sximmc_card_detect(sdmmc_chipset_handle_t sch) { struct sximmc_softc *sc = sch; int inverted, val; /* XXX treat broken-cd as non-removable */ if (OF_getproplen(sc->sc_node, "non-removable") == 0 || OF_getproplen(sc->sc_node, "broken-cd") == 0) return 1; val = gpio_controller_get_pin(sc->sc_gpio); inverted = (OF_getproplen(sc->sc_node, "cd-inverted") == 0); return inverted ? !val : val; } int sximmc_bus_power(sdmmc_chipset_handle_t sch, uint32_t ocr) { struct sximmc_softc *sc = sch; uint32_t vdd = 0; if (ISSET(ocr, MMC_OCR_3_2V_3_3V|MMC_OCR_3_3V_3_4V)) vdd = 3300000; if (sc->sc_vdd == 0 && vdd > 0) sximmc_pwrseq_pre(sc->sc_pwrseq); /* enable mmc power */ if (sc->sc_vmmc && vdd > 0) regulator_enable(sc->sc_vmmc); if (sc->sc_vqmmc && vdd > 0) regulator_enable(sc->sc_vqmmc); delay(10000); if (sc->sc_vdd == 0 && vdd > 0) sximmc_pwrseq_post(sc->sc_pwrseq); sc->sc_vdd = vdd; return 0; } int sximmc_update_clock(struct sximmc_softc *sc) { uint32_t cmd; int retry; #ifdef SXIMMC_DEBUG printf("%s: update clock\n", sc->sc_dev.dv_xname); #endif cmd = SXIMMC_CMD_START | SXIMMC_CMD_UPCLK_ONLY | SXIMMC_CMD_WAIT_PRE_OVER; MMC_WRITE(sc, SXIMMC_CMD, cmd); retry = 0xfffff; while (--retry > 0) { if (!(MMC_READ(sc, SXIMMC_CMD) & SXIMMC_CMD_START)) break; delay(10); } if (retry == 0) { printf("%s: timeout updating clock\n", sc->sc_dev.dv_xname); #ifdef SXIMMC_DEBUG printf("GCTRL: 0x%08x\n", MMC_READ(sc, SXIMMC_GCTRL)); printf("CLKCR: 0x%08x\n", MMC_READ(sc, SXIMMC_CLKCR)); printf("TIMEOUT: 0x%08x\n", MMC_READ(sc, SXIMMC_TIMEOUT)); printf("WIDTH: 0x%08x\n", MMC_READ(sc, SXIMMC_WIDTH)); printf("CMD: 0x%08x\n", MMC_READ(sc, SXIMMC_CMD)); printf("MINT: 0x%08x\n", MMC_READ(sc, SXIMMC_MINT)); printf("RINT: 0x%08x\n", MMC_READ(sc, SXIMMC_RINT)); printf("STATUS: 0x%08x\n", MMC_READ(sc, SXIMMC_STATUS)); #endif return ETIMEDOUT; } return 0; } int sximmc_bus_clock(sdmmc_chipset_handle_t sch, int freq, int timing) { struct sximmc_softc *sc = sch; uint32_t clkcr; clkcr = MMC_READ(sc, SXIMMC_CLKCR); if (clkcr & SXIMMC_CLKCR_CARDCLKON) { clkcr &= ~SXIMMC_CLKCR_CARDCLKON; MMC_WRITE(sc, SXIMMC_CLKCR, clkcr); if (sximmc_update_clock(sc) != 0) return 1; } if (freq) { clkcr &= ~SXIMMC_CLKCR_DIV; MMC_WRITE(sc, SXIMMC_CLKCR, clkcr); if (sximmc_update_clock(sc) != 0) return 1; if (sximmc_set_clock(sc, freq) != 0) return 1; clkcr |= SXIMMC_CLKCR_CARDCLKON; MMC_WRITE(sc, SXIMMC_CLKCR, clkcr); if (sximmc_update_clock(sc) != 0) return 1; } return 0; } int sximmc_bus_width(sdmmc_chipset_handle_t sch, int width) { struct sximmc_softc *sc = sch; #ifdef SXIMMC_DEBUG printf("%s: width = %d\n", sc->sc_dev.dv_xname, width); #endif switch (width) { case 1: MMC_WRITE(sc, SXIMMC_WIDTH, SXIMMC_WIDTH_1); break; case 4: MMC_WRITE(sc, SXIMMC_WIDTH, SXIMMC_WIDTH_4); break; case 8: MMC_WRITE(sc, SXIMMC_WIDTH, SXIMMC_WIDTH_8); break; default: return 1; } return 0; } int sximmc_pio_wait(struct sximmc_softc *sc, struct sdmmc_command *cmd) { int retry = 0xfffff; uint32_t bit = (cmd->c_flags & SCF_CMD_READ) ? SXIMMC_STATUS_FIFO_EMPTY : SXIMMC_STATUS_FIFO_FULL; while (--retry > 0) { uint32_t status = MMC_READ(sc, SXIMMC_STATUS); if (!(status & bit)) return 0; delay(10); } return ETIMEDOUT; } int sximmc_pio_transfer(struct sximmc_softc *sc, struct sdmmc_command *cmd) { u_char *datap = cmd->c_data; int datalen = cmd->c_resid; while (datalen > 3) { if (sximmc_pio_wait(sc, cmd)) return ETIMEDOUT; if (cmd->c_flags & SCF_CMD_READ) { *(uint32_t *)datap = MMC_READ(sc, sc->sc_fifo_reg); } else { MMC_WRITE(sc, sc->sc_fifo_reg, *(uint32_t *)datap); } datap += 4; datalen -= 4; } if (datalen > 0 && cmd->c_flags & SCF_CMD_READ) { uint32_t rv = MMC_READ(sc, sc->sc_fifo_reg); do { *datap++ = rv & 0xff; rv = rv >> 8; } while(--datalen > 0); } else if (datalen > 0) { uint32_t rv = *datap++; if (datalen > 1) rv |= *datap++ << 8; if (datalen > 2) rv |= *datap++ << 16; MMC_WRITE(sc, sc->sc_fifo_reg, rv); } return 0; } int sximmc_dma_prepare(struct sximmc_softc *sc, struct sdmmc_command *cmd) { struct sximmc_idma_descriptor *dma = (void *)sc->sc_idma_desc; bus_addr_t desc_paddr = sc->sc_idma_map->dm_segs[0].ds_addr; uint32_t val; int seg; if (sc->sc_idma_ndesc < cmd->c_dmamap->dm_nsegs) { printf("%s: not enough descriptors for %d byte transfer!\n", sc->sc_dev.dv_xname, cmd->c_datalen); return EIO; } for (seg = 0; seg < cmd->c_dmamap->dm_nsegs; seg++) { bus_addr_t paddr = cmd->c_dmamap->dm_segs[seg].ds_addr; bus_size_t len = cmd->c_dmamap->dm_segs[seg].ds_len; desc_paddr += sizeof(struct sximmc_idma_descriptor); dma[seg].dma_buf_size = htole32(len); dma[seg].dma_buf_addr = htole32(paddr >> sc->sc_idma_shift); dma[seg].dma_config = htole32(SXIMMC_IDMA_CONFIG_CH | SXIMMC_IDMA_CONFIG_OWN); if (seg == 0) { dma[seg].dma_config |= htole32(SXIMMC_IDMA_CONFIG_FD); } if (seg == cmd->c_dmamap->dm_nsegs - 1) { dma[seg].dma_config |= htole32(SXIMMC_IDMA_CONFIG_LD); dma[seg].dma_config |= htole32(SXIMMC_IDMA_CONFIG_ER); dma[seg].dma_next = 0; } else { dma[seg].dma_config |= htole32(SXIMMC_IDMA_CONFIG_DIC); dma[seg].dma_next = htole32(desc_paddr >> sc->sc_idma_shift); } } bus_dmamap_sync(sc->sc_dmat, sc->sc_idma_map, 0, sc->sc_idma_size, BUS_DMASYNC_PREWRITE); sc->sc_idma_idst = 0; val = MMC_READ(sc, SXIMMC_GCTRL); val |= SXIMMC_GCTRL_DMAEN; val |= SXIMMC_GCTRL_INTEN; MMC_WRITE(sc, SXIMMC_GCTRL, val); val |= SXIMMC_GCTRL_DMARESET; MMC_WRITE(sc, SXIMMC_GCTRL, val); MMC_WRITE(sc, SXIMMC_DMAC, SXIMMC_DMAC_SOFTRESET); MMC_WRITE(sc, SXIMMC_DMAC, SXIMMC_DMAC_IDMA_ON|SXIMMC_DMAC_FIX_BURST); val = MMC_READ(sc, SXIMMC_IDIE); val &= ~(SXIMMC_IDST_RECEIVE_INT|SXIMMC_IDST_TRANSMIT_INT); if (cmd->c_flags & SCF_CMD_READ) val |= SXIMMC_IDST_RECEIVE_INT; else val |= SXIMMC_IDST_TRANSMIT_INT; MMC_WRITE(sc, SXIMMC_IDIE, val); MMC_WRITE(sc, SXIMMC_DLBA, sc->sc_idma_map->dm_segs[0].ds_addr >> sc->sc_idma_shift); MMC_WRITE(sc, SXIMMC_FTRGLEVEL, sc->sc_dma_ftrglevel); return 0; } void sximmc_dma_complete(struct sximmc_softc *sc) { bus_dmamap_sync(sc->sc_dmat, sc->sc_idma_map, 0, sc->sc_idma_size, BUS_DMASYNC_POSTWRITE); } void sximmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd) { struct sximmc_softc *sc = sch; uint32_t cmdval = SXIMMC_CMD_START; int retry; int s; #ifdef SXIMMC_DEBUG printf("%s: opcode %d flags 0x%x data %p datalen %d blklen %d\n", sc->sc_dev.dv_xname, cmd->c_opcode, cmd->c_flags, cmd->c_data, cmd->c_datalen, cmd->c_blklen); #endif s = splbio(); if (cmd->c_opcode == 0) cmdval |= SXIMMC_CMD_SEND_INIT_SEQ; if (cmd->c_flags & SCF_RSP_PRESENT) cmdval |= SXIMMC_CMD_RSP_EXP; if (cmd->c_flags & SCF_RSP_136) cmdval |= SXIMMC_CMD_LONG_RSP; if (cmd->c_flags & SCF_RSP_CRC) cmdval |= SXIMMC_CMD_CHECK_RSP_CRC; if (cmd->c_datalen > 0) { uint16_t blksize; uint16_t blkcount; cmdval |= SXIMMC_CMD_DATA_EXP | SXIMMC_CMD_WAIT_PRE_OVER; if (!ISSET(cmd->c_flags, SCF_CMD_READ)) { cmdval |= SXIMMC_CMD_WRITE; } blksize = MIN(cmd->c_datalen, cmd->c_blklen); blkcount = cmd->c_datalen / blksize; if (blkcount > 1 && cmd->c_opcode != SD_IO_RW_EXTENDED) { cmdval |= SXIMMC_CMD_SEND_AUTO_STOP; } MMC_WRITE(sc, SXIMMC_BLKSZ, blksize); MMC_WRITE(sc, SXIMMC_BYTECNT, blkcount * blksize); } sc->sc_intr_rint = 0; #if 0 if (awin_chip_id() == AWIN_CHIP_ID_A80) { MMC_WRITE(sc, SXIMMC_A12A, (cmdval & SXIMMC_CMD_SEND_AUTO_STOP) ? 0 : 0xffff); } #endif MMC_WRITE(sc, SXIMMC_ARG, cmd->c_arg); #ifdef SXIMMC_DEBUG printf("%s: cmdval = %08x\n", sc->sc_dev.dv_xname, cmdval); #endif if (cmd->c_datalen == 0) { MMC_WRITE(sc, SXIMMC_CMD, cmdval | cmd->c_opcode); } else { cmd->c_resid = cmd->c_datalen; sximmc_led(sc, 0); if (cmd->c_dmamap && sc->sc_use_dma) { cmd->c_error = sximmc_dma_prepare(sc, cmd); MMC_WRITE(sc, SXIMMC_CMD, cmdval | cmd->c_opcode); if (cmd->c_error == 0) { cmd->c_error = tsleep_nsec(&sc->sc_idma_idst, PWAIT, "idma", SEC_TO_NSEC(10)); } sximmc_dma_complete(sc); if (sc->sc_idma_idst & SXIMMC_IDST_ERROR) { cmd->c_error = EIO; } else if (!(sc->sc_idma_idst & SXIMMC_IDST_COMPLETE)) { cmd->c_error = ETIMEDOUT; } } else { splx(s); MMC_WRITE(sc, SXIMMC_CMD, cmdval | cmd->c_opcode); cmd->c_error = sximmc_pio_transfer(sc, cmd); s = splbio(); } sximmc_led(sc, 1); if (cmd->c_error) { #ifdef SXIMMC_DEBUG printf("%s: xfer failed, error %d\n", sc->sc_dev.dv_xname, cmd->c_error); #endif goto done; } } cmd->c_error = sximmc_wait_rint(sc, SXIMMC_INT_ERROR|SXIMMC_INT_CMD_DONE, hz * 10); if (cmd->c_error == 0 && (sc->sc_intr_rint & SXIMMC_INT_ERROR)) { if (sc->sc_intr_rint & SXIMMC_INT_RESP_TIMEOUT) { cmd->c_error = ETIMEDOUT; } else { cmd->c_error = EIO; } } if (cmd->c_error) { #ifdef SXIMMC_DEBUG printf("%s: cmd failed, error %d\n", sc->sc_dev.dv_xname, cmd->c_error); #endif goto done; } if (cmd->c_datalen > 0) { cmd->c_error = sximmc_wait_rint(sc, SXIMMC_INT_ERROR| SXIMMC_INT_AUTO_CMD_DONE| SXIMMC_INT_DATA_OVER, hz*10); if (cmd->c_error == 0 && (sc->sc_intr_rint & SXIMMC_INT_ERROR)) { cmd->c_error = ETIMEDOUT; } if (cmd->c_error) { #ifdef SXIMMC_DEBUG printf("%s: data timeout, rint = %08x\n", sc->sc_dev.dv_xname, sc->sc_intr_rint); #endif cmd->c_error = ETIMEDOUT; goto done; } } if (cmd->c_flags & SCF_RSP_PRESENT) { if (cmd->c_flags & SCF_RSP_136) { cmd->c_resp[0] = MMC_READ(sc, SXIMMC_RESP0); cmd->c_resp[1] = MMC_READ(sc, SXIMMC_RESP1); cmd->c_resp[2] = MMC_READ(sc, SXIMMC_RESP2); cmd->c_resp[3] = MMC_READ(sc, SXIMMC_RESP3); if (cmd->c_flags & SCF_RSP_CRC) { cmd->c_resp[0] = (cmd->c_resp[0] >> 8) | (cmd->c_resp[1] << 24); cmd->c_resp[1] = (cmd->c_resp[1] >> 8) | (cmd->c_resp[2] << 24); cmd->c_resp[2] = (cmd->c_resp[2] >> 8) | (cmd->c_resp[3] << 24); cmd->c_resp[3] = (cmd->c_resp[3] >> 8); } } else { cmd->c_resp[0] = MMC_READ(sc, SXIMMC_RESP0); } } done: cmd->c_flags |= SCF_ITSDONE; splx(s); if (cmd->c_error) { #ifdef SXIMMC_DEBUG printf("%s: i/o error %d\n", sc->sc_dev.dv_xname, cmd->c_error); #endif MMC_WRITE(sc, SXIMMC_GCTRL, MMC_READ(sc, SXIMMC_GCTRL) | SXIMMC_GCTRL_DMARESET | SXIMMC_GCTRL_FIFORESET); for (retry = 0; retry < 1000; retry++) { if (!(MMC_READ(sc, SXIMMC_GCTRL) & SXIMMC_GCTRL_RESET)) break; delay(10); } sximmc_host_reset(sc); sximmc_update_clock(sc); } if (!cmd->c_dmamap || !sc->sc_use_dma) { MMC_WRITE(sc, SXIMMC_GCTRL, MMC_READ(sc, SXIMMC_GCTRL) | SXIMMC_GCTRL_FIFORESET); } } void sximmc_pwrseq_pre(uint32_t phandle) { uint32_t *gpios, *gpio; int node; int len; node = OF_getnodebyphandle(phandle); if (node == 0) return; if (!OF_is_compatible(node, "mmc-pwrseq-simple")) return; pinctrl_byname(node, "default"); clock_enable(node, "ext_clock"); len = OF_getproplen(node, "reset-gpios"); if (len <= 0) return; gpios = malloc(len, M_TEMP, M_WAITOK); OF_getpropintarray(node, "reset-gpios", gpios, len); gpio = gpios; while (gpio && gpio < gpios + (len / sizeof(uint32_t))) { gpio_controller_config_pin(gpio, GPIO_CONFIG_OUTPUT); gpio_controller_set_pin(gpio, 1); gpio = gpio_controller_next_pin(gpio); } free(gpios, M_TEMP, len); } void sximmc_pwrseq_post(uint32_t phandle) { uint32_t *gpios, *gpio; int node; int len; node = OF_getnodebyphandle(phandle); if (node == 0) return; if (!OF_is_compatible(node, "mmc-pwrseq-simple")) return; len = OF_getproplen(node, "reset-gpios"); if (len <= 0) return; gpios = malloc(len, M_TEMP, M_WAITOK); OF_getpropintarray(node, "reset-gpios", gpios, len); gpio = gpios; while (gpio && gpio < gpios + (len / sizeof(uint32_t))) { gpio_controller_set_pin(gpio, 0); gpio = gpio_controller_next_pin(gpio); } free(gpios, M_TEMP, len); }