#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.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 <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 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
#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_DTW_MASK (0x3 << 1)
#define SDHC_PROT_CTRL_DTW_4BIT (1 << 1)
#define SDHC_PROT_CTRL_DTW_8BIT (1 << 2)
#define SDHC_PROT_CTRL_DMASEL_MASK (0x3 << 8)
#define SDHC_PROT_CTRL_DMASEL_SDMA (0x0 << 8)
#define SDHC_PROT_CTRL_DMASEL_ADMA1 (0x1 << 8)
#define SDHC_PROT_CTRL_DMASEL_ADMA2 (0x2 << 8)
#define SDHC_HOST_CTRL_CAP_MBL_SHIFT 16
#define SDHC_HOST_CTRL_CAP_MBL_MASK 0x7
#define SDHC_HOST_CTRL_CAP_ADMAS (1 << 20)
#define SDHC_HOST_CTRL_CAP_HSS (1 << 21)
#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_RD_BRST_LEN_SHIFT 8
#define SDHC_WTMK_LVL_WR_WML_SHIFT 16
#define SDHC_WTMK_LVL_WR_BRST_LEN_SHIFT 24
#define SDHC_COMMAND_TIMEOUT 1
#define SDHC_BUFFER_TIMEOUT 1
#define SDHC_TRANSFER_TIMEOUT 1
#define SDHC_DMA_TIMEOUT 3
#define SDHC_ADMA2_VALID (1 << 0)
#define SDHC_ADMA2_END (1 << 1)
#define SDHC_ADMA2_INT (1 << 2)
#define SDHC_ADMA2_ACT (3 << 4)
#define SDHC_ADMA2_ACT_NOP (0 << 4)
#define SDHC_ADMA2_ACT_TRANS (2 << 4)
#define SDHC_ADMA2_ACT_LINK (3 << 4)
struct sdhc_adma2_descriptor32 {
uint16_t attribute;
uint16_t length;
uint32_t address;
} __packed;
int imxesdhc_match(struct device *, void *, void *);
void imxesdhc_attach(struct device *, struct device *, void *);
struct imxesdhc_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_dma_tag_t sc_dmat;
void *sc_ih;
int sc_node;
uint32_t sc_gpio[3];
uint32_t sc_vmmc;
uint32_t sc_pwrseq;
uint32_t sc_vdd;
int sc_cookies[SDMMC_MAX_FUNCTIONS];
u_int sc_flags;
struct device *sdmmc;
int clockbit;
u_int clkbase;
int maxblklen;
int flags;
uint32_t ocr;
uint32_t intr_status;
uint32_t intr_error_status;
bus_dmamap_t adma_map;
bus_dma_segment_t adma_segs[1];
caddr_t adma2;
};
int imxesdhc_intr(void *);
void imxesdhc_clock_enable(uint32_t);
void imxesdhc_pwrseq_pre(uint32_t);
void imxesdhc_pwrseq_post(uint32_t);
#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)
#define SHF_USE_DMA 0x0001
#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 imxesdhc_host_reset(sdmmc_chipset_handle_t);
uint32_t imxesdhc_host_ocr(sdmmc_chipset_handle_t);
int imxesdhc_host_maxblklen(sdmmc_chipset_handle_t);
int imxesdhc_card_detect(sdmmc_chipset_handle_t);
int imxesdhc_bus_power(sdmmc_chipset_handle_t, uint32_t);
int imxesdhc_bus_clock(sdmmc_chipset_handle_t, int, int);
int imxesdhc_bus_width(sdmmc_chipset_handle_t, int);
void imxesdhc_card_intr_mask(sdmmc_chipset_handle_t, int);
void imxesdhc_card_intr_ack(sdmmc_chipset_handle_t);
void imxesdhc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *);
int imxesdhc_start_command(struct imxesdhc_softc *, struct sdmmc_command *);
int imxesdhc_wait_state(struct imxesdhc_softc *, uint32_t, uint32_t);
int imxesdhc_soft_reset(struct imxesdhc_softc *, int);
int imxesdhc_wait_intr(struct imxesdhc_softc *, int, int);
void imxesdhc_transfer_data(struct imxesdhc_softc *, struct sdmmc_command *);
void imxesdhc_read_data(struct imxesdhc_softc *, u_char *, int);
void imxesdhc_write_data(struct imxesdhc_softc *, u_char *, int);
#ifdef SDHC_DEBUG
int imxesdhcdebug = 20;
#define DPRINTF(n,s) do { if ((n) <= imxesdhcdebug) printf s; } while (0)
#else
#define DPRINTF(n,s) do {} while(0)
#endif
struct sdmmc_chip_functions imxesdhc_functions = {
imxesdhc_host_reset,
imxesdhc_host_ocr,
imxesdhc_host_maxblklen,
imxesdhc_card_detect,
imxesdhc_bus_power,
imxesdhc_bus_clock,
imxesdhc_bus_width,
imxesdhc_exec_command,
imxesdhc_card_intr_mask,
imxesdhc_card_intr_ack
};
struct cfdriver imxesdhc_cd = {
NULL, "imxesdhc", DV_DULL
};
const struct cfattach imxesdhc_ca = {
sizeof(struct imxesdhc_softc), imxesdhc_match, imxesdhc_attach
};
int
imxesdhc_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "fsl,imx6q-usdhc") ||
OF_is_compatible(faa->fa_node, "fsl,imx6sl-usdhc") ||
OF_is_compatible(faa->fa_node, "fsl,imx6sx-usdhc") ||
OF_is_compatible(faa->fa_node, "fsl,imx7d-usdhc");
}
void
imxesdhc_attach(struct device *parent, struct device *self, void *aux)
{
struct imxesdhc_softc *sc = (struct imxesdhc_softc *) self;
struct fdt_attach_args *faa = aux;
struct sdmmcbus_attach_args saa;
int error = 1, node, reg;
uint32_t caps;
uint32_t width;
if (faa->fa_nreg < 1)
return;
sc->sc_node = faa->fa_node;
sc->sc_dmat = faa->fa_dmat;
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh))
panic("imxesdhc_attach: bus_space_map failed!");
printf("\n");
pinctrl_byname(faa->fa_node, "default");
clock_set_assigned(faa->fa_node);
clock_enable_all(faa->fa_node);
sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_SDMMC,
imxesdhc_intr, sc, sc->sc_dev.dv_xname);
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_pwrseq = OF_getpropint(sc->sc_node, "mmc-pwrseq", 0);
if (imxesdhc_host_reset(sc))
return;
caps = HREAD4(sc, SDHC_HOST_CTRL_CAP);
if (OF_is_compatible(sc->sc_node, "fsl,imx6sl-usdhc") ||
OF_is_compatible(sc->sc_node, "fsl,imx6sx-usdhc") ||
OF_is_compatible(sc->sc_node, "fsl,imx7d-usdhc"))
caps &= 0xffff0000;
if (ISSET(caps, SDHC_HOST_CTRL_CAP_ADMAS))
SET(sc->flags, SHF_USE_DMA);
sc->clkbase = clock_get_frequency(faa->fa_node, "per") / 1000;
printf("%s: %d MHz base clock\n", DEVNAME(sc), sc->clkbase / 1000);
if (caps & SDHC_HOST_CTRL_CAP_VS18)
SET(sc->ocr, MMC_OCR_1_65V_1_95V);
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);
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);
}
sc->maxblklen = 512;
if (ISSET(sc->flags, SHF_USE_DMA)) {
int rseg;
error = bus_dmamem_alloc(sc->sc_dmat, PAGE_SIZE, PAGE_SIZE,
PAGE_SIZE, sc->adma_segs, 1, &rseg,
BUS_DMA_WAITOK | BUS_DMA_ZERO);
if (error)
goto adma_done;
error = bus_dmamem_map(sc->sc_dmat, sc->adma_segs, rseg,
PAGE_SIZE, &sc->adma2, BUS_DMA_WAITOK | BUS_DMA_COHERENT);
if (error) {
bus_dmamem_free(sc->sc_dmat, sc->adma_segs, rseg);
goto adma_done;
}
error = bus_dmamap_create(sc->sc_dmat, PAGE_SIZE, 1, PAGE_SIZE,
0, BUS_DMA_WAITOK, &sc->adma_map);
if (error) {
bus_dmamem_unmap(sc->sc_dmat, sc->adma2, PAGE_SIZE);
bus_dmamem_free(sc->sc_dmat, sc->adma_segs, rseg);
goto adma_done;
}
error = bus_dmamap_load(sc->sc_dmat, sc->adma_map,
sc->adma2, PAGE_SIZE, NULL,
BUS_DMA_WAITOK | BUS_DMA_WRITE);
if (error) {
bus_dmamap_destroy(sc->sc_dmat, sc->adma_map);
bus_dmamem_unmap(sc->sc_dmat, sc->adma2, PAGE_SIZE);
bus_dmamem_free(sc->sc_dmat, sc->adma_segs, rseg);
goto adma_done;
}
adma_done:
if (error) {
printf("%s: can't allocate DMA descriptor table\n",
DEVNAME(sc));
CLR(sc->flags, SHF_USE_DMA);
}
}
bzero(&saa, sizeof(saa));
saa.saa_busname = "sdmmc";
saa.sct = &imxesdhc_functions;
saa.sch = sc;
saa.dmat = sc->sc_dmat;
if (ISSET(sc->flags, SHF_USE_DMA)) {
saa.caps |= SMC_CAPS_DMA;
saa.max_seg = 65535;
}
if (caps & SDHC_HOST_CTRL_CAP_HSS)
saa.caps |= SMC_CAPS_MMC_HIGHSPEED | SMC_CAPS_SD_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;
for (node = OF_child(sc->sc_node); node; node = OF_peer(node)) {
reg = OF_getpropint(node, "reg", -1);
if (reg < 0 || reg >= SDMMC_MAX_FUNCTIONS)
continue;
sc->sc_cookies[reg] = node;
saa.cookies[reg] = &sc->sc_cookies[reg];
}
sc->sdmmc = config_found(&sc->sc_dev, &saa, NULL);
if (sc->sdmmc == NULL) {
error = 0;
return;
}
}
void
imxesdhc_clock_enable(uint32_t phandle)
{
uint32_t gpios[3];
int node;
node = OF_getnodebyphandle(phandle);
if (node == 0)
return;
if (!OF_is_compatible(node, "gpio-gate-clock"))
return;
pinctrl_byname(node, "default");
OF_getpropintarray(node, "enable-gpios", gpios, sizeof(gpios));
gpio_controller_config_pin(&gpios[0], GPIO_CONFIG_OUTPUT);
gpio_controller_set_pin(&gpios[0], 1);
}
void
imxesdhc_pwrseq_pre(uint32_t phandle)
{
uint32_t *gpios, *gpio;
uint32_t clocks;
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");
clocks = OF_getpropint(node, "clocks", 0);
if (clocks)
imxesdhc_clock_enable(clocks);
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
imxesdhc_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);
}
int
imxesdhc_host_reset(sdmmc_chipset_handle_t sch)
{
struct imxesdhc_softc *sc = sch;
u_int32_t imask;
int error;
int s;
s = splsdmmc();
HWRITE4(sc, SDHC_INT_STATUS_EN, 0);
HWRITE4(sc, SDHC_INT_SIGNAL_EN, 0);
if ((error = imxesdhc_soft_reset(sc, SDHC_SYS_CTRL_RSTA)) != 0) {
splx(s);
return (error);
}
HSET4(sc, SDHC_SYS_CTRL, 0xe << SDHC_SYS_CTRL_DTOCV_SHIFT);
imask = SDHC_INT_STATUS_CC | SDHC_INT_STATUS_TC |
SDHC_INT_STATUS_BGE | SDHC_INT_STATUS_DINT |
SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR;
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);
HCLR4(sc, SDHC_PROT_CTRL, SDHC_PROT_CTRL_DMASEL_MASK);
HCLR4(sc, SDHC_PROT_CTRL, SDHC_PROT_CTRL_DTW_MASK);
HWRITE4(sc, SDHC_WTMK_LVL,
(64 << SDHC_WTMK_LVL_RD_WML_SHIFT) |
(16 << SDHC_WTMK_LVL_RD_BRST_LEN_SHIFT) |
(64 << SDHC_WTMK_LVL_WR_WML_SHIFT) |
(16 << SDHC_WTMK_LVL_WR_BRST_LEN_SHIFT));
splx(s);
return 0;
}
uint32_t
imxesdhc_host_ocr(sdmmc_chipset_handle_t sch)
{
struct imxesdhc_softc *sc = sch;
return sc->ocr;
}
int
imxesdhc_host_maxblklen(sdmmc_chipset_handle_t sch)
{
struct imxesdhc_softc *sc = sch;
return sc->maxblklen;
}
int
imxesdhc_card_detect(sdmmc_chipset_handle_t sch)
{
struct imxesdhc_softc *sc = sch;
if (OF_getproplen(sc->sc_node, "non-removable") == 0)
return 1;
return gpio_controller_get_pin(sc->sc_gpio);
}
int
imxesdhc_bus_power(sdmmc_chipset_handle_t sch, uint32_t ocr)
{
struct imxesdhc_softc *sc = sch;
uint32_t vdd = 0;
ocr &= sc->ocr;
if (ISSET(ocr, MMC_OCR_3_2V_3_3V|MMC_OCR_3_3V_3_4V))
vdd = 3300000;
else if (ISSET(ocr, MMC_OCR_2_9V_3_0V|MMC_OCR_3_0V_3_1V))
vdd = 3000000;
else if (ISSET(ocr, MMC_OCR_1_65V_1_95V))
vdd = 1800000;
if (sc->sc_vdd == 0 && vdd > 0)
imxesdhc_pwrseq_pre(sc->sc_pwrseq);
if (sc->sc_vmmc && vdd > 0)
regulator_enable(sc->sc_vmmc);
if (sc->sc_vdd == 0 && vdd > 0)
imxesdhc_pwrseq_post(sc->sc_pwrseq);
sc->sc_vdd = vdd;
return 0;
}
int
imxesdhc_bus_clock(sdmmc_chipset_handle_t sch, int freq, int timing)
{
struct imxesdhc_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);
HCLR4(sc, SDHC_VEND_SPEC, SDHC_VEND_SPEC_FRC_SDCLK_ON);
if ((error = imxesdhc_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));
if ((error = imxesdhc_wait_state(sc,
SDHC_PRES_STATE_SDSTB, SDHC_PRES_STATE_SDSTB)) != 0)
goto ret;
ret:
splx(s);
return error;
}
int
imxesdhc_bus_width(sdmmc_chipset_handle_t sch, int width)
{
struct imxesdhc_softc *sc = sch;
uint32_t reg;
int s;
if (width != 1 && width != 4 && width != 8)
return (1);
s = splsdmmc();
reg = HREAD4(sc, SDHC_PROT_CTRL) & ~SDHC_PROT_CTRL_DTW_MASK;
if (width == 4)
reg |= SDHC_PROT_CTRL_DTW_4BIT;
else if (width == 8)
reg |= SDHC_PROT_CTRL_DTW_8BIT;
HWRITE4(sc, SDHC_PROT_CTRL, reg);
splx(s);
return (0);
}
void
imxesdhc_card_intr_mask(sdmmc_chipset_handle_t sch, int enable)
{
struct imxesdhc_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
imxesdhc_card_intr_ack(sdmmc_chipset_handle_t sch)
{
struct imxesdhc_softc *sc = sch;
HSET4(sc, SDHC_INT_STATUS_EN, SDHC_INT_STATUS_CINT);
}
int
imxesdhc_wait_state(struct imxesdhc_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, state %x\n",
HDEVNAME(sc), value, state));
return ETIMEDOUT;
}
void
imxesdhc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
{
struct imxesdhc_softc *sc = sch;
int error;
error = imxesdhc_start_command(sc, cmd);
if (error != 0) {
cmd->c_error = error;
SET(cmd->c_flags, SCF_ITSDONE);
return;
}
if (!imxesdhc_wait_intr(sc, SDHC_INT_STATUS_CC, SDHC_COMMAND_TIMEOUT)) {
cmd->c_error = ETIMEDOUT;
SET(cmd->c_flags, SCF_ITSDONE);
return;
}
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\n"
"resp[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 (cmd->c_error == 0 && cmd->c_data)
imxesdhc_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
imxesdhc_start_command(struct imxesdhc_softc *sc, struct sdmmc_command *cmd)
{
struct sdhc_adma2_descriptor32 *desc = (void *)sc->adma2;
u_int32_t blksize = 0;
u_int32_t blkcount = 0;
u_int32_t command;
int error;
int seg;
int s;
DPRINTF(1,("%s: start cmd %u arg=%#x data=%p dlen=%d flags=%#x\n",
HDEVNAME(sc), cmd->c_opcode, cmd->c_arg, cmd->c_data,
cmd->c_datalen, cmd->c_flags));
if (cmd->c_datalen > 0) {
blksize = MIN(cmd->c_datalen, cmd->c_blklen);
blkcount = cmd->c_datalen / blksize;
if (cmd->c_datalen % blksize > 0) {
printf("%s: data not a multiple of %d bytes\n",
HDEVNAME(sc), blksize);
return EINVAL;
}
}
if (blkcount > SDHC_BLK_ATT_BLKCNT_MAX) {
printf("%s: too much data\n", HDEVNAME(sc));
return EINVAL;
}
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;
}
}
command = 0;
if (ISSET(cmd->c_flags, SCF_CMD_READ))
command |= SDHC_MIX_CTRL_DTDSEL;
if (blkcount > 0) {
command |= SDHC_MIX_CTRL_BCEN;
if (blkcount > 1) {
command |= SDHC_MIX_CTRL_MSBSEL;
if (cmd->c_opcode != SD_IO_RW_EXTENDED)
command |= SDHC_MIX_CTRL_AC12EN;
}
}
if (cmd->c_dmamap && cmd->c_datalen > 0 &&
ISSET(sc->flags, SHF_USE_DMA))
command |= SDHC_MIX_CTRL_DMAEN;
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;
if ((error = imxesdhc_wait_state(sc, SDHC_PRES_STATE_CIHB, 0)) != 0)
return error;
s = splsdmmc();
if (cmd->c_dmamap && ISSET(sc->flags, SHF_USE_DMA)) {
for (seg = 0; seg < cmd->c_dmamap->dm_nsegs; seg++) {
bus_addr_t paddr =
cmd->c_dmamap->dm_segs[seg].ds_addr;
uint16_t len =
cmd->c_dmamap->dm_segs[seg].ds_len == 65536 ?
0 : cmd->c_dmamap->dm_segs[seg].ds_len;
uint16_t attr;
attr = SDHC_ADMA2_VALID | SDHC_ADMA2_ACT_TRANS;
if (seg == cmd->c_dmamap->dm_nsegs - 1)
attr |= SDHC_ADMA2_END;
desc[seg].attribute = htole16(attr);
desc[seg].length = htole16(len);
desc[seg].address = htole32(paddr);
}
desc[cmd->c_dmamap->dm_nsegs].attribute = htole16(0);
bus_dmamap_sync(sc->sc_dmat, sc->adma_map, 0, PAGE_SIZE,
BUS_DMASYNC_PREWRITE);
HCLR4(sc, SDHC_PROT_CTRL, SDHC_PROT_CTRL_DMASEL_MASK);
HSET4(sc, SDHC_PROT_CTRL, SDHC_PROT_CTRL_DMASEL_ADMA2);
HWRITE4(sc, SDHC_ADMA_SYS_ADDR,
sc->adma_map->dm_segs[0].ds_addr);
} else
HCLR4(sc, SDHC_PROT_CTRL, SDHC_PROT_CTRL_DMASEL_MASK);
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
imxesdhc_transfer_data(struct imxesdhc_softc *sc, struct sdmmc_command *cmd)
{
u_char *datap = cmd->c_data;
int i, datalen;
int mask;
int error;
if (cmd->c_dmamap) {
int status;
error = 0;
for (;;) {
status = imxesdhc_wait_intr(sc,
SDHC_INT_STATUS_DINT|SDHC_INT_STATUS_TC,
SDHC_DMA_TIMEOUT);
if (status & SDHC_INT_STATUS_TC)
break;
if (!status) {
error = ETIMEDOUT;
break;
}
}
bus_dmamap_sync(sc->sc_dmat, sc->adma_map, 0, PAGE_SIZE,
BUS_DMASYNC_POSTWRITE);
goto done;
}
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));
while (datalen > 0) {
if (!imxesdhc_wait_intr(sc,
SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR,
SDHC_BUFFER_TIMEOUT)) {
error = ETIMEDOUT;
break;
}
if ((error = imxesdhc_wait_state(sc, mask, mask)) != 0)
break;
delay(100);
i = MIN(datalen, cmd->c_blklen);
if (ISSET(cmd->c_flags, SCF_CMD_READ))
imxesdhc_read_data(sc, datap, i);
else
imxesdhc_write_data(sc, datap, i);
datap += i;
datalen -= i;
}
if (error == 0 && !imxesdhc_wait_intr(sc, SDHC_INT_STATUS_TC,
SDHC_TRANSFER_TIMEOUT))
error = ETIMEDOUT;
done:
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
imxesdhc_read_data(struct imxesdhc_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
imxesdhc_write_data(struct imxesdhc_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);
}
}
int
imxesdhc_soft_reset(struct imxesdhc_softc *sc, int mask)
{
int timo;
DPRINTF(1,("%s: software reset reg=%#x\n", HDEVNAME(sc), mask));
HCLR4(sc, SDHC_VEND_SPEC, SDHC_VEND_SPEC_FRC_SDCLK_ON);
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
imxesdhc_wait_intr(struct imxesdhc_softc *sc, int mask, int secs)
{
int status;
int s;
mask |= SDHC_INT_STATUS_ERR;
s = splsdmmc();
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_nsec(&sc->intr_status, PWAIT, "hcintr",
SEC_TO_NSEC(secs)) == 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));
if (ISSET(status, SDHC_INT_STATUS_ERR)) {
sc->intr_error_status = 0;
(void)imxesdhc_soft_reset(sc,
SDHC_SYS_CTRL_RSTC | SDHC_SYS_CTRL_RSTD);
status = 0;
}
splx(s);
return status;
}
int
imxesdhc_intr(void *arg)
{
struct imxesdhc_softc *sc = arg;
u_int32_t status;
status = HREAD4(sc, SDHC_INT_STATUS);
if (status & (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR))
HCLR4(sc, SDHC_INT_SIGNAL_EN,
(SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR));
HWRITE4(sc, SDHC_INT_STATUS, status);
DPRINTF(2,("%s: interrupt status=0x%08x\n", HDEVNAME(sc), status));
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);
}
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);
}
if (ISSET(status, SDHC_INT_STATUS_CINT)) {
DPRINTF(0,("%s: card interrupt\n", HDEVNAME(sc)));
HCLR4(sc, SDHC_INT_STATUS_EN, SDHC_INT_STATUS_CINT);
sdmmc_card_intr(sc->sdmmc);
}
return 1;
}