#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/malloc.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 <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/ofw_pinctrl.h>
#include <dev/ofw/ofw_regulator.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
#define SXIMMC_HWRST 0x0078
#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");
clock_enable(faa->fa_node, NULL);
delay(5000);
reset_deassert_all(faa->fa_node);
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
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;
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);
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);
}