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