diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2016-08-15 21:03:28 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2016-08-15 21:03:28 +0000 |
commit | d0481db6a55a0c22307f2bf12dcc23093933bf94 (patch) | |
tree | f551c2513ab02b74fb1317aa2b9f5cf1852d084e /sys | |
parent | d0519b184d4d386f57c8148aada19c3a875a8c79 (diff) |
First stab at porting the awinmmc(4) driver from NetBSD. The driver will
be known as sximmc(4) and is somewhat functional. Still needs proper clock
support and some further cleanup.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/arch/armv7/sunxi/files.sunxi | 6 | ||||
-rw-r--r-- | sys/arch/armv7/sunxi/sximmc.c | 1074 |
2 files changed, 1079 insertions, 1 deletions
diff --git a/sys/arch/armv7/sunxi/files.sunxi b/sys/arch/armv7/sunxi/files.sunxi index 063faddef3c..1326514b6d5 100644 --- a/sys/arch/armv7/sunxi/files.sunxi +++ b/sys/arch/armv7/sunxi/files.sunxi @@ -1,4 +1,4 @@ -# $OpenBSD: files.sunxi,v 1.11 2016/08/13 22:07:01 kettenis Exp $ +# $OpenBSD: files.sunxi,v 1.12 2016/08/15 21:03:27 kettenis Exp $ define sunxi {} device sunxi: sunxi @@ -36,6 +36,10 @@ device sxiahci: scsi, atascsi attach sxiahci at fdt file arch/armv7/sunxi/sxiahci.c sxiahci +device sximmc: sdmmcbus +attach sximmc at fdt +file arch/armv7/sunxi/sximmc.c sximmc + #attach ohci at sunxi with sxiohci #file arch/armv7/sunxi/sxiohci.c sxiohci diff --git a/sys/arch/armv7/sunxi/sximmc.c b/sys/arch/armv7/sunxi/sximmc.c new file mode 100644 index 00000000000..fde1d8362a2 --- /dev/null +++ b/sys/arch/armv7/sunxi/sximmc.c @@ -0,0 +1,1074 @@ +/* $OpenBSD: sximmc.c,v 1.1 2016/08/15 21:03:27 kettenis Exp $ */ +/* $NetBSD: awin_mmc.c,v 1.23 2015/11/14 10:32:40 bouyer Exp $ */ + +/*- + * Copyright (c) 2014 Jared D. McNeill <jmcneill@invisible.ca> + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kernel.h> + +#include <machine/intr.h> +#include <machine/bus.h> +#include <machine/fdt.h> + +#include <dev/sdmmc/sdmmcvar.h> +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmc_ioreg.h> + +#include <armv7/sunxi/sxiccmuvar.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_gpio.h> +#include <dev/ofw/ofw_pinctrl.h> +#include <dev/ofw/fdt.h> + +#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 0x0100 + +#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_write_protect(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 *); + +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, +}; + +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; + + uint32_t sc_idma_xferlen; + 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; + + uint32_t sc_intr_rint; + uint32_t sc_intr_mint; + uint32_t sc_idma_idst; + + uint32_t sc_gpio[4]; +}; + +struct cfdriver sximmc_cd = { + NULL, "sximmc", DV_DULL +}; + +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; + + if (OF_is_compatible(faa->fa_node, "allwinner,sun4i-a10-mmc")) + return 1; + if (OF_is_compatible(faa->fa_node, "allwinner,sun5i-a13-mmc")) + return 1; + if (OF_is_compatible(faa->fa_node, "allwinner,sun9i-a80-mmc")) + return 1; + + return 0; +} + +int +sximmc_idma_setup(struct sximmc_softc *sc) +{ + int error; + + if (OF_is_compatible(sc->sc_node, "allwinner,sun4i-a10-mmc")) { + sc->sc_idma_xferlen = 0x2000; + } else { + sc->sc_idma_xferlen = 0x10000; + } + + 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 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 */ + sxiccmu_enablemodule(CCMU_SDMMC0); + delay(5000); + +#if 0 + if (awin_chip_id() == AWIN_CHIP_ID_A80) { + bus_space_subregion(sc->sc_bst, aio->aio_ccm_bsh, + AWIN_A80_CCU_SCLK_SDMMC0_CLK_REG + (loc->loc_port * 4), 4, + &sc->sc_clk_bsh); + awin_reg_set_clear(aio->aio_core_bst, aio->aio_ccm_bsh, + AWIN_A80_CCU_SCLK_BUS_CLK_GATING0_REG, + AWIN_A80_CCU_SCLK_BUS_CLK_GATING0_SD, 0); + awin_reg_set_clear(aio->aio_core_bst, aio->aio_ccm_bsh, + AWIN_A80_CCU_SCLK_BUS_SOFT_RST0_REG, + AWIN_A80_CCU_SCLK_BUS_SOFT_RST0_SD, 0); + awin_reg_set_clear(aio->aio_core_bst, aio->aio_core_bsh, + AWIN_A80_SDMMC_COMM_OFFSET + (loc->loc_port * 4), + AWIN_A80_SDMMC_COMM_SDC_RESET_SW | + AWIN_A80_SDMMC_COMM_SDC_CLOCK_SW, 0); + delay(1000); + } else { + bus_space_subregion(sc->sc_bst, aio->aio_ccm_bsh, + AWIN_SD0_CLK_REG + (loc->loc_port * 4), 4, &sc->sc_clk_bsh); + + awin_pll6_enable(); + + awin_reg_set_clear(aio->aio_core_bst, aio->aio_ccm_bsh, + AWIN_AHB_GATING0_REG, + AWIN_AHB_GATING0_SDMMC0 << loc->loc_port, 0); + if (awin_chip_id() == AWIN_CHIP_ID_A31) { + awin_reg_set_clear(aio->aio_core_bst, aio->aio_ccm_bsh, + AWIN_A31_AHB_RESET0_REG, + AWIN_A31_AHB_RESET0_SD0_RST << loc->loc_port, 0); + } + } +#endif + +#if 0 + switch (awin_chip_id()) { + case AWIN_CHIP_ID_A80: + sc->sc_fifo_reg = AWIN_A31_MMC_FIFO; + sc->sc_dma_ftrglevel = SXIMMC_DMA_FTRGLEVEL_A80; + break; + case AWIN_CHIP_ID_A31: + sc->sc_fifo_reg = AWIN_A31_MMC_FIFO; + sc->sc_dma_ftrglevel = SXIMMC_DMA_FTRGLEVEL_A20; + break; + default: + sc->sc_fifo_reg = SXIMMC_FIFO; + sc->sc_dma_ftrglevel = SXIMMC_DMA_FTRGLEVEL_A20; + break; + } +#else + sc->sc_fifo_reg = SXIMMC_FIFO; + sc->sc_dma_ftrglevel = SXIMMC_DMA_FTRGLEVEL_A20; +#endif + + 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_ih = arm_intr_establish_fdt(faa->fa_node, IPL_BIO, + sximmc_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(": can't to establish interrupt\n"); + return; + } + + sximmc_host_reset(sc); + sximmc_bus_width(sc, 1); + sximmc_set_clock(sc, 400); + + 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; + } + + sc->sc_sdmmc_dev = config_found(self, &saa, NULL); +} + +int +sximmc_set_clock(struct sximmc_softc *sc, u_int freq) +{ +#if 0 + uint32_t odly, sdly, clksrc, n, m, clk; + u_int osc24m_freq = AWIN_REF_FREQ / 1000; + u_int pll_freq; + + if (awin_chip_id() == AWIN_CHIP_ID_A80) { + pll_freq = awin_periph0_get_rate() / 1000; + } else { + pll_freq = awin_pll6_get_rate() / 1000; + } + +#ifdef SXIMMC_DEBUG + printf("%s: freq = %d, pll_freq = %d\n", sc->sc_dev.dv_xname, + freq, pll_freq); +#endif + + if (freq <= 400) { + odly = 0; + sdly = 0; + clksrc = AWIN_SD_CLK_SRC_SEL_OSC24M; + n = 2; + if (freq > 0) + m = ((osc24m_freq / (1 << n)) / freq) - 1; + else + m = 15; + } else if (freq <= 25000) { + odly = 0; + sdly = 5; + clksrc = AWIN_SD_CLK_SRC_SEL_PLL6; + n = awin_chip_id() == AWIN_CHIP_ID_A80 ? 2 : 0; + m = ((pll_freq / freq) / (1 << n)) - 1; + } else if (freq <= 50000) { + odly = awin_chip_id() == AWIN_CHIP_ID_A80 ? 5 : 3; + sdly = awin_chip_id() == AWIN_CHIP_ID_A80 ? 4 : 5; + clksrc = AWIN_SD_CLK_SRC_SEL_PLL6; + n = awin_chip_id() == AWIN_CHIP_ID_A80 ? 2 : 0; + m = ((pll_freq / freq) / (1 << n)) - 1; + } else { + /* UHS speeds not implemented yet */ + return EIO; + } + + clk = bus_space_read_4(sc->sc_bst, sc->sc_clk_bsh, 0); + clk &= ~AWIN_SD_CLK_SRC_SEL; + clk |= __SHIFTIN(clksrc, AWIN_SD_CLK_SRC_SEL); + clk &= ~AWIN_SD_CLK_DIV_RATIO_N; + clk |= __SHIFTIN(n, AWIN_SD_CLK_DIV_RATIO_N); + clk &= ~AWIN_SD_CLK_DIV_RATIO_M; + clk |= __SHIFTIN(m, AWIN_SD_CLK_DIV_RATIO_M); + clk &= ~AWIN_SD_CLK_OUTPUT_PHASE_CTR; + clk |= __SHIFTIN(odly, AWIN_SD_CLK_OUTPUT_PHASE_CTR); + clk &= ~AWIN_SD_CLK_PHASE_CTR; + clk |= __SHIFTIN(sdly, AWIN_SD_CLK_PHASE_CTR); + clk |= AWIN_PLL_CFG_ENABLE; + bus_space_write_4(sc->sc_bst, sc->sc_clk_bsh, 0, clk); + delay(20000); +#endif + + 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); + } + + return 1; +} + +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(&sc->sc_intr_rint, PWAIT, "rint", hz); + 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; + +#ifdef SXIMMC_DEBUG + printf("%s: host reset\n", sc->sc_dev.dv_xname); +#endif + +#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); + } + + 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) +{ + return 8192; +} + +int +sximmc_card_detect(sdmmc_chipset_handle_t sch) +{ + struct sximmc_softc *sc = sch; + int inverted, val; + + if (OF_getproplen(sc->sc_node, "non-removable") == 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) +{ + 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) +{ + uint32_t *datap = (uint32_t *)cmd->c_data; + int i; + + for (i = 0; i < (cmd->c_resid >> 2); i++) { + if (sximmc_pio_wait(sc, cmd)) + return ETIMEDOUT; + if (cmd->c_flags & SCF_CMD_READ) { + datap[i] = MMC_READ(sc, sc->sc_fifo_reg); + } else { + MMC_WRITE(sc, sc->sc_fifo_reg, datap[i]); + } + } + + 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; + bus_size_t off; + int desc, resid, seg; + uint32_t val; + + desc = 0; + 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; + resid = min(len, cmd->c_resid); + off = 0; + while (resid > 0) { + if (desc == sc->sc_idma_ndesc) + break; + len = min(sc->sc_idma_xferlen, resid); + dma[desc].dma_buf_size = htole32(len); + dma[desc].dma_buf_addr = htole32(paddr + off); + dma[desc].dma_config = htole32(SXIMMC_IDMA_CONFIG_CH | + SXIMMC_IDMA_CONFIG_OWN); + cmd->c_resid -= len; + resid -= len; + off += len; + if (desc == 0) { + dma[desc].dma_config |= + htole32(SXIMMC_IDMA_CONFIG_FD); + } + if (cmd->c_resid == 0) { + dma[desc].dma_config |= + htole32(SXIMMC_IDMA_CONFIG_LD); + dma[desc].dma_config |= + htole32(SXIMMC_IDMA_CONFIG_ER); + dma[desc].dma_next = 0; + } else { + dma[desc].dma_config |= + htole32(SXIMMC_IDMA_CONFIG_DIC); + dma[desc].dma_next = htole32( + desc_paddr + ((desc+1) * + sizeof(struct sximmc_idma_descriptor))); + } + ++desc; + } + } + if (desc == sc->sc_idma_ndesc) { + printf("%s: not enough descriptors for %d byte transfer!\n", + sc->sc_dev.dv_xname, cmd->c_datalen); + return EIO; + } + + 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, desc_paddr); + 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) { + unsigned int nblks; + + cmdval |= SXIMMC_CMD_DATA_EXP | SXIMMC_CMD_WAIT_PRE_OVER; + if (!ISSET(cmd->c_flags, SCF_CMD_READ)) { + cmdval |= SXIMMC_CMD_WRITE; + } + + nblks = cmd->c_datalen / cmd->c_blklen; + if (nblks == 0 || (cmd->c_datalen % cmd->c_blklen) != 0) + ++nblks; + + if (nblks > 1) { + cmdval |= SXIMMC_CMD_SEND_AUTO_STOP; + } + + MMC_WRITE(sc, SXIMMC_BLKSZ, cmd->c_blklen); + MMC_WRITE(sc, SXIMMC_BYTECNT, nblks * cmd->c_blklen); + } + + 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(&sc->sc_idma_idst, + PWAIT, "idma", hz*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_update_clock(sc); + } + + if (!cmd->c_dmamap || !sc->sc_use_dma) { + MMC_WRITE(sc, SXIMMC_GCTRL, + MMC_READ(sc, SXIMMC_GCTRL) | SXIMMC_GCTRL_FIFORESET); + } +} |