diff options
Diffstat (limited to 'sys/dev/sdmmc')
-rw-r--r-- | sys/dev/sdmmc/files.sdmmc | 11 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdhc.c | 1065 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdhcreg.h | 173 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdhcvar.h | 39 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc.c | 556 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc_io.c | 140 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc_mem.c | 243 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc_scsi.c | 238 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc_scsi.h | 39 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcchip.h | 78 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcreg.h | 232 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcvar.h | 127 |
12 files changed, 2941 insertions, 0 deletions
diff --git a/sys/dev/sdmmc/files.sdmmc b/sys/dev/sdmmc/files.sdmmc new file mode 100644 index 00000000000..6462e6e0a77 --- /dev/null +++ b/sys/dev/sdmmc/files.sdmmc @@ -0,0 +1,11 @@ +# $OpenBSD: files.sdmmc,v 1.1 2006/05/28 17:21:14 uwe Exp $ +# +# Config file and device description for machine-independent SD/MMC code. +# Included by ports that need it. + +device sdmmc: scsi +attach sdmmc at sdmmcbus +file dev/sdmmc/sdmmc.c sdmmc +file dev/sdmmc/sdmmc_io.c sdmmc +file dev/sdmmc/sdmmc_mem.c sdmmc +file dev/sdmmc/sdmmc_scsi.c sdmmc diff --git a/sys/dev/sdmmc/sdhc.c b/sys/dev/sdmmc/sdhc.c new file mode 100644 index 00000000000..ea800debd1a --- /dev/null +++ b/sys/dev/sdmmc/sdhc.c @@ -0,0 +1,1065 @@ +/* $OpenBSD: sdhc.c,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +/* + * SD Host Controller driver based on the SD Host Controller Standard + * Simplified Specification Version 1.00 (www.sdcard.com). + */ + +#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 <sys/timeout.h> + +#include <dev/sdmmc/sdhcreg.h> +#include <dev/sdmmc/sdhcvar.h> +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmcreg.h> +#include <dev/sdmmc/sdmmcvar.h> + +#undef SDHC_DEBUG + +#define SDHC_COMMAND_TIMEOUT (hz*2) /* 2 seconds */ +#define SDHC_DATA_TIMEOUT (hz*2) /* 2 seconds */ + +struct sdhc_host { + struct sdhc_softc *sc; /* host controller device */ + struct device *sdmmc; /* generic SD/MMC device */ + bus_space_tag_t iot; /* host register set tag */ + bus_space_handle_t ioh; /* host register set handle */ + u_int clkbase; /* base clock frequency in KHz */ + int maxblklen; /* maximum block length */ + int flags; /* flags for this host */ + u_int32_t ocr; /* OCR value from capabilities */ + struct proc *event_thread; /* event processing thread */ + struct sdmmc_command *cmd; /* current command or NULL */ + struct timeout cmd_to; /* command timeout */ +}; + +#define HDEVNAME(hp) ((hp)->sdmmc->dv_xname) + +/* flag values */ +#define SHF_USE_DMA 0x0001 +#define SHF_CARD_PRESENT 0x0002 +#define SHF_CARD_ATTACHED 0x0004 + +#define HREAD1(hp, reg) \ + (bus_space_read_1((hp)->iot, (hp)->ioh, (reg))) +#define HREAD2(hp, reg) \ + (bus_space_read_2((hp)->iot, (hp)->ioh, (reg))) +#define HREAD4(hp, reg) \ + (bus_space_read_4((hp)->iot, (hp)->ioh, (reg))) +#define HWRITE1(hp, reg, val) \ + bus_space_write_1((hp)->iot, (hp)->ioh, (reg), (val)) +#define HWRITE2(hp, reg, val) \ + bus_space_write_2((hp)->iot, (hp)->ioh, (reg), (val)) +#define HWRITE4(hp, reg, val) \ + bus_space_write_4((hp)->iot, (hp)->ioh, (reg), (val)) +#define HCLR1(hp, reg, bits) \ + HWRITE1((hp), (reg), HREAD1((hp), (reg)) & ~(bits)) +#define HCLR2(hp, reg, bits) \ + HWRITE2((hp), (reg), HREAD2((hp), (reg)) & ~(bits)) +#define HSET1(hp, reg, bits) \ + HWRITE1((hp), (reg), HREAD1((hp), (reg)) | (bits)) +#define HSET2(hp, reg, bits) \ + HWRITE2((hp), (reg), HREAD2((hp), (reg)) | (bits)) + +void sdhc_create_event_thread(void *); +void sdhc_event_thread(void *); +void sdhc_event_process(struct sdhc_host *); + +int sdhc_host_reset(sdmmc_chipset_handle_t); +u_int32_t sdhc_host_ocr(sdmmc_chipset_handle_t); +int sdhc_host_maxblklen(sdmmc_chipset_handle_t); +int sdhc_card_detect(sdmmc_chipset_handle_t); +int sdhc_bus_power(sdmmc_chipset_handle_t, u_int32_t); +int sdhc_bus_clock(sdmmc_chipset_handle_t, int); +int sdhc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *); +int sdhc_wait_state(struct sdhc_host *, u_int32_t, u_int32_t); +int sdhc_start_command(struct sdhc_host *, struct sdmmc_command *); +int sdhc_wait_command(struct sdhc_host *, int); +int sdhc_finish_command(struct sdhc_host *); +void sdhc_transfer_data(struct sdhc_host *); +void sdhc_read_data(struct sdhc_host *, u_char *, int); +void sdhc_write_data(struct sdhc_host *, u_char *, int); +void sdhc_command_timeout(void *); + +#ifdef SDHC_DEBUG +void sdhc_dump_regs(struct sdhc_host *); +#define DPRINTF(s) printf s +#else +#define DPRINTF(s) /**/ +#endif + +struct sdmmc_chip_functions sdhc_functions = { + /* host controller reset */ + sdhc_host_reset, + /* host controller capabilities */ + sdhc_host_ocr, + sdhc_host_maxblklen, + /* card detection */ + sdhc_card_detect, + /* bus power and clock frequency */ + sdhc_bus_power, + sdhc_bus_clock, + /* command execution */ + sdhc_exec_command +}; + +struct cfdriver sdhc_cd = { + NULL, "sdhc", DV_DULL +}; + +/* + * Called by attachment driver. For each SD card slot there is one SD + * host controller standard register set. (1.3) + */ +int +sdhc_host_found(struct sdhc_softc *sc, bus_space_tag_t iot, + bus_space_handle_t ioh, bus_size_t iosize, int usedma) +{ + struct sdmmcbus_attach_args saa; + struct sdhc_host *hp; + u_int32_t caps; +#ifdef SDHC_DEBUG + u_int16_t version; + + version = bus_space_read_2(iot, ioh, SDHC_HOST_CTL_VERSION); + printf("%s: SD Host Specification/Vendor Version ", + sc->sc_dev.dv_xname); + switch(SDHC_SPEC_VERSION(version)) { + case 0x00: + printf("1.0/%u\n", SDHC_VENDOR_VERSION(version)); + break; + default: + printf(">1.0/%u\n", SDHC_VENDOR_VERSION(version)); + break; + } +#endif + + /* Allocate one more host structure. */ + sc->sc_nhosts++; + MALLOC(hp, struct sdhc_host *, sizeof(struct sdhc_host) * + sc->sc_nhosts, M_DEVBUF, M_WAITOK); + if (sc->sc_host != NULL) { + bcopy(sc->sc_host, hp, sizeof(struct sdhc_host) * + (sc->sc_nhosts-1)); + FREE(sc->sc_host, M_DEVBUF); + } + sc->sc_host = hp; + + /* Fill in the new host structure. */ + hp = &sc->sc_host[sc->sc_nhosts-1]; + bzero(hp, sizeof(struct sdhc_host)); + hp->sc = sc; + hp->iot = iot; + hp->ioh = ioh; + timeout_set(&hp->cmd_to, sdhc_command_timeout, hp); + + /* + * Reset the host controller and enable interrupts. + */ + if (sdhc_host_reset(hp) != 0) { + printf("%s: host reset failed\n", sc->sc_dev.dv_xname); + goto err; + } + + /* Determine host capabilities. */ + caps = HREAD4(hp, SDHC_CAPABILITIES); + + /* Use DMA if the host system and the controller support it. */ + if (usedma && ISSET(caps, SDHC_DMA_SUPPORT)) + SET(hp->flags, SHF_USE_DMA); + + /* + * Determine the base clock frequency. (2.2.24) + */ + if (SDHC_BASE_FREQ_KHZ(caps) != 0) + hp->clkbase = SDHC_BASE_FREQ_KHZ(caps); + if (hp->clkbase == 0) { + /* The attachment driver must tell us. */ + printf("%s: base clock frequency unknown\n", + sc->sc_dev.dv_xname); + goto err; + } else if (hp->clkbase < 10000 || hp->clkbase > 63000) { + /* SDHC 1.0 supports only 10-63 Mhz. */ + printf("%s: base clock frequency out of range: %u MHz\n", + sc->sc_dev.dv_xname, hp->clkbase); + goto err; + } + + /* + * XXX Set the data timeout counter value according to + * capabilities. (2.2.15) + */ + + /* + * Determine SD bus voltage levels supported by the controller. + */ + if (ISSET(caps, SDHC_VOLTAGE_SUPP_1_8V)) + SET(hp->ocr, MMC_OCR_1_7V_1_8V | MMC_OCR_1_8V_1_9V); + if (ISSET(caps, SDHC_VOLTAGE_SUPP_3_0V)) + SET(hp->ocr, MMC_OCR_2_9V_3_0V | MMC_OCR_3_0V_3_1V); + if (ISSET(caps, SDHC_VOLTAGE_SUPP_3_3V)) + SET(hp->ocr, MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V); + + /* + * Determine the maximum block length supported by the host + * controller. (2.2.24) + */ + switch((caps >> SDHC_MAX_BLK_LEN_SHIFT) & SDHC_MAX_BLK_LEN_MASK) { + case SDHC_MAX_BLK_LEN_512: + hp->maxblklen = 512; + break; + case SDHC_MAX_BLK_LEN_1024: + hp->maxblklen = 1024; + break; + case SDHC_MAX_BLK_LEN_2048: + hp->maxblklen = 2048; + break; + default: + hp->maxblklen = 1; + break; + } + + /* + * 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 = &sdhc_functions; + saa.sch = hp; + + hp->sdmmc = config_found(&sc->sc_dev, &saa, NULL); + if (hp->sdmmc == NULL) { + printf("%s: can't attach bus\n", sc->sc_dev.dv_xname); + goto err; + } + + /* + * Create the event thread that will attach and detach cards + * and perform other lengthy operations. + */ +#ifdef DO_CONFIG_PENDING + config_pending_incr(); +#endif + kthread_create_deferred(sdhc_create_event_thread, hp); + + return 0; + +err: + /* XXX: Leaking one sdhc_host structure here. */ + sc->sc_nhosts--; + return 1; +} + +void +sdhc_create_event_thread(void *arg) +{ + struct sdhc_host *hp = arg; + + /* If there's a card, attach it. */ + sdhc_event_process(hp); + + if (kthread_create(sdhc_event_thread, hp, &hp->event_thread, + HDEVNAME(hp)) != 0) + printf("%s: can't create event thread\n", HDEVNAME(hp)); + +#ifdef DO_CONFIG_PENDING + config_pending_decr(); +#endif +} + +void +sdhc_event_thread(void *arg) +{ + struct sdhc_host *hp = arg; + + for (;;) { + //DPRINTF(("%s: tsleep sdhcev\n", HDEVNAME(hp))); + (void)tsleep((caddr_t)hp, PWAIT, "sdhcev", 0); + sdhc_event_process(hp); + } +} + +void +sdhc_event_process(struct sdhc_host *hp) +{ + //DPRINTF(("%s: event process\n", HDEVNAME(hp))); + + /* If there's a card, attach it, if it went away, detach it. */ + if (sdhc_card_detect(hp)) { + if (!ISSET(hp->flags, SHF_CARD_PRESENT)) { + SET(hp->flags, SHF_CARD_PRESENT); + if (sdmmc_card_attach(hp->sdmmc) == 0) + SET(hp->flags, SHF_CARD_ATTACHED); + } + } else { + /* XXX If a command was in progress, abort it. */ + int s = splsdmmc(); + if (hp->cmd != NULL) { + timeout_del(&hp->cmd_to); + printf("%s: interrupted command %u\n", + HDEVNAME(hp), hp->cmd->c_opcode); + hp->cmd = NULL; + } + splx(s); + + if (ISSET(hp->flags, SHF_CARD_PRESENT)) { + CLR(hp->flags, SHF_CARD_PRESENT); + if (ISSET(hp->flags, SHF_CARD_ATTACHED)) { + sdmmc_card_detach(hp->sdmmc, DETACH_FORCE); + CLR(hp->flags, SHF_CARD_ATTACHED); + } + } + } +} + +/* + * Power hook established by or called from attachment driver. + */ +void +sdhc_power(int why, void *arg) +{ + /* struct sdhc_softc *sc = arg; */ + + switch(why) { + case PWR_STANDBY: + case PWR_SUSPEND: + /* XXX suspend or detach cards */ + break; + case PWR_RESUME: + /* XXX resume or reattach cards */ + break; + } +} + +/* + * Shutdown hook established by or called from attachment driver. + */ +void +sdhc_shutdown(void *arg) +{ + struct sdhc_softc *sc = arg; + struct sdhc_host *hp; + int i; + + /* XXX chip locks up if we don't disable it before reboot. */ + for (i = 0; i < sc->sc_nhosts; i++) { + hp = &sc->sc_host[i]; + sdhc_host_reset(hp); + } +} + +/* + * Reset the host controller. Called during initialization, when + * cards are removed and during error recovery. + */ +int +sdhc_host_reset(sdmmc_chipset_handle_t sch) +{ + struct sdhc_host *hp = sch; + u_int16_t imask; + int error = 0; + int timo; + int s; + + s = splsdmmc(); + + /* Disable all interrupts. */ + HWRITE2(hp, SDHC_NINTR_SIGNAL_EN, 0); + + /* + * Reset the entire host controller and wait up to 100ms. + */ + HWRITE1(hp, SDHC_SOFTWARE_RESET, SDHC_RESET_MASK); + for (timo = 10; timo > 0; timo--) { + if (!ISSET(HREAD1(hp, SDHC_SOFTWARE_RESET), SDHC_RESET_MASK)) + break; + sdmmc_delay(10000); + } + if (timo == 0) { + HWRITE1(hp, SDHC_SOFTWARE_RESET, 0); + error = ETIMEDOUT; + } + + /* Set data timeout counter value to max for now. */ + HWRITE1(hp, SDHC_TIMEOUT_CTL, SDHC_TIMEOUT_MAX); + + /* Enable interrupts. */ + imask = SDHC_CARD_REMOVAL | SDHC_CARD_INSERTION | + SDHC_BUFFER_READ_READY | SDHC_BUFFER_WRITE_READY | + SDHC_DMA_INTERRUPT | SDHC_BLOCK_GAP_EVENT | + SDHC_TRANSFER_COMPLETE | SDHC_COMMAND_COMPLETE; + HWRITE2(hp, SDHC_NINTR_STATUS_EN, imask); + HWRITE2(hp, SDHC_EINTR_STATUS_EN, SDHC_EINTR_STATUS_MASK); + HWRITE2(hp, SDHC_NINTR_SIGNAL_EN, imask); + HWRITE2(hp, SDHC_EINTR_SIGNAL_EN, SDHC_EINTR_SIGNAL_MASK); + + splx(s); + return error; +} + +u_int32_t +sdhc_host_ocr(sdmmc_chipset_handle_t sch) +{ + struct sdhc_host *hp = sch; + return hp->ocr; +} + +int +sdhc_host_maxblklen(sdmmc_chipset_handle_t sch) +{ + struct sdhc_host *hp = sch; + return hp->maxblklen; +} + +/* + * Return non-zero if the card is currently inserted. + */ +int +sdhc_card_detect(sdmmc_chipset_handle_t sch) +{ + struct sdhc_host *hp = sch; + return ISSET(HREAD4(hp, SDHC_PRESENT_STATE), SDHC_CARD_INSERTED) ? + 1 : 0; +} + +/* + * Set or change SD bus voltage and enable or disable SD bus power. + * Return zero on success. + */ +int +sdhc_bus_power(sdmmc_chipset_handle_t sch, u_int32_t ocr) +{ + struct sdhc_host *hp = sch; + u_int8_t vdd; + int s; + + s = splsdmmc(); + + /* + * Disable bus power before voltage change. + */ + HWRITE1(hp, SDHC_POWER_CTL, 0); + + /* If power is disabled, reset the host and return now. */ + if (ocr == 0) { + splx(s); + if (sdhc_host_reset(hp) != 0) + printf("%s: host reset failed\n", HDEVNAME(hp)); + return 0; + } + + /* + * Select the maximum voltage according to capabilities. + */ + ocr &= hp->ocr; + if (ISSET(ocr, MMC_OCR_3_2V_3_3V|MMC_OCR_3_3V_3_4V)) + vdd = SDHC_VOLTAGE_3_3V; + else if (ISSET(ocr, MMC_OCR_2_9V_3_0V|MMC_OCR_3_0V_3_1V)) + vdd = SDHC_VOLTAGE_3_0V; + else if (ISSET(ocr, MMC_OCR_1_7V_1_8V|MMC_OCR_1_8V_1_9V)) + vdd = SDHC_VOLTAGE_1_8V; + else { + /* Unsupported voltage level requested. */ + splx(s); + return EINVAL; + } + + /* + * Enable bus power. Wait at least 1 ms (or 74 clocks) plus + * voltage ramp until power rises. + */ + HWRITE1(hp, SDHC_POWER_CTL, (vdd << SDHC_VOLTAGE_SHIFT) | + SDHC_BUS_POWER); + sdmmc_delay(10000); + + /* + * The host system may not power the bus due to battery low, + * etc. In that case, the host controller should clear the + * bus power bit. + */ + if (!ISSET(HREAD1(hp, SDHC_POWER_CTL), SDHC_BUS_POWER)) { + splx(s); + return ENXIO; + } + + splx(s); + return 0; +} + +/* + * Return the smallest possible base clock frequency divisor value + * for the CLOCK_CTL register to produce `freq' (KHz). + */ +static int +sdhc_clock_divisor(struct sdhc_host *hp, u_int freq) +{ + int div; + + for (div = 1; div <= 256; div *= 2) + if ((hp->clkbase / div) <= freq) + return (div / 2); + /* No divisor found. */ + return -1; +} + +/* + * Set or change SDCLK frequency or disable the SD clock. + * Return zero on success. + */ +int +sdhc_bus_clock(sdmmc_chipset_handle_t sch, int freq) +{ + struct sdhc_host *hp = sch; + int s; + int div; + int timo; + int error = 0; + + s = splsdmmc(); + +#ifdef DIAGNOSTIC + /* Must not stop the clock if commands are in progress. */ + if (ISSET(HREAD4(hp, SDHC_PRESENT_STATE), SDHC_CMD_INHIBIT_MASK) && + sdhc_card_detect(hp)) + printf("sdhc_sdclk_frequency_select: command in progress\n"); +#endif + + /* + * Stop SD clock before changing the frequency. + */ + HWRITE2(hp, SDHC_CLOCK_CTL, 0); + if (freq == SDMMC_SDCLK_OFF) + goto ret; + + /* + * Set the minimum base clock frequency divisor. + */ + if ((div = sdhc_clock_divisor(hp, freq)) < 0) { + /* Invalid base clock frequency or `freq' value. */ + error = EINVAL; + goto ret; + } + HWRITE2(hp, SDHC_CLOCK_CTL, div << SDHC_SDCLK_DIV_SHIFT); + + /* + * Start internal clock. Wait 10ms for stabilization. + */ + HSET2(hp, SDHC_CLOCK_CTL, SDHC_INTCLK_ENABLE); + for (timo = 1000; timo > 0; timo--) { + if (ISSET(HREAD2(hp, SDHC_CLOCK_CTL), SDHC_INTCLK_STABLE)) + break; + sdmmc_delay(10); + } + if (timo == 0) { + error = ETIMEDOUT; + goto ret; + } + + /* + * Enable SD clock. + */ + HSET2(hp, SDHC_CLOCK_CTL, SDHC_SDCLK_ENABLE); + +ret: + splx(s); + return error; +} + +/* + * Send a command and data to the card and return the command response + * and data from the card. + * + * If no callback function is specified, execute the command + * synchronously; otherwise, return immediately and call the function + * from the event thread after the command has completed. + */ +int +sdhc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd) +{ + struct sdhc_host *hp = sch; + int error; + + if (hp->cmd != NULL) + return EBUSY; + + error = sdhc_start_command(hp, cmd); + if (error != 0) + goto err; + if (cmd->c_done != NULL) + /* Execute this command asynchronously. */ + return error; + + error = sdhc_wait_command(hp, SCF_DONE|SCF_CMD_DONE); + if (error != 0) + goto err; + return sdhc_finish_command(hp); + err: + cmd->c_error = error; + SET(cmd->c_flags, SCF_DONE); + return sdhc_finish_command(hp); +} + +int +sdhc_wait_state(struct sdhc_host *hp, u_int32_t mask, u_int32_t value) +{ + u_int32_t state; + int timeout; + + for (timeout = 10; timeout > 0; timeout--) { + if (((state = HREAD4(hp, SDHC_PRESENT_STATE)) & mask) + == value) + return 0; + sdmmc_delay(10000); + } + DPRINTF(("%s: timeout waiting for %x (state=%b)\n", HDEVNAME(hp), + value, state, SDHC_PRESENT_STATE_BITS)); + return ETIMEDOUT; +} + +int +sdhc_start_command(struct sdhc_host *hp, struct sdmmc_command *cmd) +{ + u_int16_t blksize = 0; + u_int16_t blkcount = 0; + u_int16_t mode; + u_int16_t command; + int error; + int s; + + DPRINTF(("%s: start cmd %u\n", HDEVNAME(hp), cmd->c_opcode)); + hp->cmd = cmd; + + /* If the card went away, finish the command immediately. */ + if (!ISSET(hp->flags, SHF_CARD_PRESENT)) + return ETIMEDOUT; + + /* + * 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 = 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(hp), blksize); + return EINVAL; + } + } + + /* Check limit imposed by 9-bit block count. (1.7.2) */ + if (blkcount > SDHC_BLOCK_COUNT_MAX) { + printf("%s: too much data\n", HDEVNAME(hp)); + return EINVAL; + } + + /* Prepare transfer mode register value. (2.2.5) */ + mode = 0; + if (ISSET(cmd->c_flags, SCF_CMD_READ)) + mode |= SDHC_READ_MODE; + if (blkcount > 0) { + mode |= SDHC_BLOCK_COUNT_ENABLE; + if (blkcount > 1) { + mode |= SDHC_MULTI_BLOCK_MODE; + mode |= SDHC_AUTO_CMD12_ENABLE; + } + } +#ifdef notyet + if (ISSET(hp->flags, SHF_USE_DMA)) + mode |= SDHC_DMA_ENABLE; +#endif + + /* + * Prepare command register value. (2.2.6) + */ + command = (cmd->c_opcode & SDHC_COMMAND_INDEX_MASK) << + SDHC_COMMAND_INDEX_SHIFT; + + if (ISSET(cmd->c_flags, SCF_RSP_CRC)) + command |= SDHC_CRC_CHECK_ENABLE; + if (ISSET(cmd->c_flags, SCF_RSP_IDX)) + command |= SDHC_INDEX_CHECK_ENABLE; + if (cmd->c_data != NULL) + command |= SDHC_DATA_PRESENT_SELECT; + + if (!ISSET(cmd->c_flags, SCF_RSP_PRESENT)) + command |= SDHC_NO_RESPONSE; + else if (ISSET(cmd->c_flags, SCF_RSP_136)) + command |= SDHC_RESP_LEN_136; + else if (ISSET(cmd->c_flags, SCF_RSP_BSY)) + command |= SDHC_RESP_LEN_48_CHK_BUSY; + else + command |= SDHC_RESP_LEN_48; + + /* Wait until command and data inhibit bits are clear. (1.5) */ + if ((error = sdhc_wait_state(hp, SDHC_CMD_INHIBIT_MASK, 0)) != 0) + return error; + + s = splsdmmc(); + + /* Alert the user not to remove the card. */ + HSET1(hp, SDHC_HOST_CTL, SDHC_LED_ON); + + /* XXX: Set DMA start address if SHF_USE_DMA is set. */ + + /* + * Start a CPU data transfer. Writing to the high order byte + * of the SDHC_COMMAND register triggers the SD command. (1.5) + */ + HWRITE2(hp, SDHC_BLOCK_SIZE, blksize); + HWRITE2(hp, SDHC_BLOCK_COUNT, blkcount); + HWRITE4(hp, SDHC_ARGUMENT, cmd->c_arg); + HWRITE2(hp, SDHC_TRANSFER_MODE, mode); + HWRITE2(hp, SDHC_COMMAND, command); + + /* + * Start a software timeout. In the unlikely event that the + * controller's own timeout detection mechanism fails we will + * abort the transfer in software. + */ + timeout_add(&hp->cmd_to, SDHC_COMMAND_TIMEOUT); + + splx(s); + return 0; +} + +int +sdhc_wait_command(struct sdhc_host *hp, int flags) +{ + int s; + + for (;;) { + /* Return if the command was aborted. */ + if (hp->cmd == NULL) + return EIO; + + s = splsdmmc(); + + /* Return if the command has reached the awaited state. */ + if (ISSET(hp->cmd->c_flags, flags)) { + splx(s); + return 0; + } + + DPRINTF(("%s: tsleep sdhccmd (flags=%#x)\n", + HDEVNAME(hp), flags)); + (void)tsleep((caddr_t)hp, PWAIT, "sdhccmd", 0); + + /* Process card events. */ + sdhc_event_process(hp); + + splx(s); + } +} + +int +sdhc_finish_command(struct sdhc_host *hp) +{ + struct sdmmc_command *cmd = hp->cmd; + int error; + + if (cmd == NULL) { + DPRINTF(("%s: finish NULL cmd\n", HDEVNAME(hp))); + return 0; + } + + /* Cancel command timeout. */ + timeout_del(&hp->cmd_to); + + DPRINTF(("%s: finish cmd %u (flags=%#x error=%d)\n", + HDEVNAME(hp), cmd->c_opcode, cmd->c_flags, cmd->c_error)); + + /* + * 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)) { + u_char *p = (u_char *)cmd->c_resp; + int i; + + for (i = 0; i < 15; i++) + *p++ = HREAD1(hp, SDHC_RESPONSE + i); + } else + cmd->c_resp[0] = HREAD4(hp, SDHC_RESPONSE); + } + + if (cmd->c_error == 0 && cmd->c_data != NULL) { + timeout_add(&hp->cmd_to, SDHC_DATA_TIMEOUT); + sdhc_transfer_data(hp); + } + + /* Turn off the LED. */ + HCLR1(hp, SDHC_HOST_CTL, SDHC_LED_ON); + + error = cmd->c_error; + hp->cmd = NULL; + + SET(cmd->c_flags, SCF_DONE); + DPRINTF(("%s: cmd %u done (flags=%#x error=%d)\n", + HDEVNAME(hp), cmd->c_opcode, cmd->c_flags, error)); + if (cmd->c_done != NULL) + cmd->c_done(hp->sdmmc, cmd); + return error; +} + +void +sdhc_transfer_data(struct sdhc_host *hp) +{ + struct sdmmc_command *cmd = hp->cmd; + u_char *datap = cmd->c_data; + int i, datalen; + int mask; + int error; + + mask = ISSET(cmd->c_flags, SCF_CMD_READ) ? + SDHC_BUFFER_READ_ENABLE : SDHC_BUFFER_WRITE_ENABLE; + error = 0; + datalen = cmd->c_datalen; + + DPRINTF(("%s: resp=%#x ", HDEVNAME(hp), MMC_R1(cmd->c_resp))); + DPRINTF(("datalen %u\n", datalen)); + while (datalen > 0) { + error = sdhc_wait_command(hp, SCF_DONE|SCF_BUF_READY); + if (error != 0) + break; + + error = sdhc_wait_state(hp, mask, mask); + if (error != 0) + break; + + i = MIN(datalen, cmd->c_blklen); + if (ISSET(cmd->c_flags, SCF_CMD_READ)) + sdhc_read_data(hp, datap, i); + else + sdhc_write_data(hp, datap, i); + + datap += i; + datalen -= i; + CLR(cmd->c_flags, SCF_BUF_READY); + } + + if (error == 0) + error = sdhc_wait_command(hp, SCF_DONE|SCF_XFR_DONE); + + timeout_del(&hp->cmd_to); + + if (cmd->c_error == 0) { + cmd->c_error = error; + SET(cmd->c_flags, SCF_DONE); + } + + DPRINTF(("%s: data transfer done (error=%d)\n", + HDEVNAME(hp), cmd->c_error)); +} + +void +sdhc_read_data(struct sdhc_host *hp, u_char *datap, int datalen) +{ + while (datalen > 0) { + if (datalen > 3) { + *((u_int32_t *)datap)++ = HREAD4(hp, SDHC_DATA); + datalen -= 4; + } else if (datalen > 1) { + *((u_int16_t *)datap)++ = HREAD2(hp, SDHC_DATA); + datalen -= 2; + } else { + *datap++ = HREAD1(hp, SDHC_DATA); + datalen -= 1; + } + } +} + +void +sdhc_write_data(struct sdhc_host *hp, u_char *datap, int datalen) +{ + while (datalen > 0) { + if (datalen > 3) { + HWRITE4(hp, SDHC_DATA, *(u_int32_t *)datap); + datap += 4; + datalen -= 4; + } else if (datalen > 1) { + HWRITE2(hp, SDHC_DATA, *(u_int16_t *)datap); + datap += 2; + datalen -= 2; + } else { + HWRITE1(hp, SDHC_DATA, *datap); + datap++; + datalen -= 1; + } + } +} + +void +sdhc_command_timeout(void *arg) +{ + struct sdhc_host *hp = arg; + struct sdmmc_command *cmd = hp->cmd; + int s; + + if (cmd == NULL) + return; + + s = splsdmmc(); + if (!ISSET(cmd->c_flags, SCF_DONE)) { + DPRINTF(("%s: timeout cmd %u, resetting...\n", + HDEVNAME(hp), cmd->c_opcode)); + cmd->c_error = ETIMEDOUT; + SET(cmd->c_flags, SCF_DONE); + HWRITE1(hp, SDHC_SOFTWARE_RESET, SDHC_RESET_DAT| + SDHC_RESET_CMD); + timeout_add(&hp->cmd_to, hz/2); + } else { + DPRINTF(("%s: timeout cmd %u, resetting...done\n", + HDEVNAME(hp), cmd->c_opcode)); + wakeup(hp); + } + splx(s); +} + +/* + * Established by attachment driver at interrupt priority IPL_SDMMC. + */ +int +sdhc_intr(void *arg) +{ + struct sdhc_softc *sc = arg; + int done = 0; + int host; + + /* We got an interrupt, but we don't know from which slot. */ + for (host = 0; host < sc->sc_nhosts; host++) { + struct sdhc_host *hp = &sc->sc_host[host]; + u_int16_t status; + + if (hp == NULL) + continue; + + status = HREAD2(hp, SDHC_NINTR_STATUS); + if (!ISSET(status, SDHC_NINTR_STATUS_MASK)) + continue; + + /* Clear interrupts we are about to handle. */ + HWRITE2(hp, SDHC_NINTR_STATUS, status); +#ifdef SDHC_DEBUG + printf("%s: interrupt status=%b\n", HDEVNAME(hp), + status, SDHC_NINTR_STATUS_BITS); +#endif + + /* + * Wake up the event thread to service the interrupt(s). + */ + if (ISSET(status, SDHC_BUFFER_READ_READY| + SDHC_BUFFER_WRITE_READY)) { + if (hp->cmd != NULL && + !ISSET(hp->cmd->c_flags, SCF_DONE)) { + SET(hp->cmd->c_flags, SCF_BUF_READY); + wakeup(hp); + } + done++; + } + if (ISSET(status, SDHC_COMMAND_COMPLETE)) { + if (hp->cmd != NULL && + !ISSET(hp->cmd->c_flags, SCF_DONE)) { + SET(hp->cmd->c_flags, SCF_CMD_DONE); + wakeup(hp); + } + done++; + } + if (ISSET(status, SDHC_TRANSFER_COMPLETE)) { + if (hp->cmd != NULL && + !ISSET(hp->cmd->c_flags, SCF_DONE)) { + SET(hp->cmd->c_flags, SCF_XFR_DONE); + wakeup(hp); + } + done++; + } + if (ISSET(status, SDHC_CARD_REMOVAL|SDHC_CARD_INSERTION)) { + wakeup(hp); + done++; + } + + if (ISSET(status, SDHC_ERROR_INTERRUPT)) { + u_int16_t error; + + error = HREAD2(hp, SDHC_EINTR_STATUS); + HWRITE2(hp, SDHC_EINTR_STATUS, error); + + DPRINTF(("%s: error interrupt, status=%b\n", + HDEVNAME(hp), error, SDHC_EINTR_STATUS_BITS)); + + if (ISSET(error, SDHC_CMD_TIMEOUT_ERROR| + SDHC_DATA_TIMEOUT_ERROR) && hp->cmd != NULL && + !ISSET(hp->cmd->c_flags, SCF_DONE)) { + hp->cmd->c_error = ETIMEDOUT; + SET(hp->cmd->c_flags, SCF_DONE); + /* XXX can this reset be avoided? */ + HWRITE1(hp, SDHC_SOFTWARE_RESET, + SDHC_RESET_DAT|SDHC_RESET_CMD); + timeout_add(&hp->cmd_to, hz/2); + } + done++; + } + + if (ISSET(status, SDHC_CARD_INTERRUPT)) { + HCLR2(hp, SDHC_NINTR_STATUS_EN, SDHC_CARD_INTERRUPT); + /* XXX service card interrupt */ + printf("%s: card interrupt\n", HDEVNAME(hp)); + HSET2(hp, SDHC_NINTR_STATUS_EN, SDHC_CARD_INTERRUPT); + } + } + return done; +} + +#ifdef SDHC_DEBUG +void +sdhc_dump_regs(struct sdhc_host *hp) +{ + printf("0x%02x PRESENT_STATE: %b\n", SDHC_PRESENT_STATE, + HREAD4(hp, SDHC_PRESENT_STATE), SDHC_PRESENT_STATE_BITS); + printf("0x%02x POWER_CTL: %x\n", SDHC_POWER_CTL, + HREAD1(hp, SDHC_POWER_CTL)); + printf("0x%02x NINTR_STATUS: %x\n", SDHC_NINTR_STATUS, + HREAD2(hp, SDHC_NINTR_STATUS)); + printf("0x%02x EINTR_STATUS: %x\n", SDHC_EINTR_STATUS, + HREAD2(hp, SDHC_EINTR_STATUS)); + printf("0x%02x NINTR_STATUS_EN: %x\n", SDHC_NINTR_STATUS_EN, + HREAD2(hp, SDHC_NINTR_STATUS_EN)); + printf("0x%02x EINTR_STATUS_EN: %x\n", SDHC_EINTR_STATUS_EN, + HREAD2(hp, SDHC_EINTR_STATUS_EN)); + printf("0x%02x NINTR_SIGNAL_EN: %x\n", SDHC_NINTR_SIGNAL_EN, + HREAD2(hp, SDHC_NINTR_SIGNAL_EN)); + printf("0x%02x EINTR_SIGNAL_EN: %x\n", SDHC_EINTR_SIGNAL_EN, + HREAD2(hp, SDHC_EINTR_SIGNAL_EN)); + printf("0x%02x CAPABILITIES: %x\n", SDHC_CAPABILITIES, + HREAD4(hp, SDHC_CAPABILITIES)); + printf("0x%02x MAX_CAPABILITIES: %x\n", SDHC_MAX_CAPABILITIES, + HREAD4(hp, SDHC_MAX_CAPABILITIES)); +} +#endif diff --git a/sys/dev/sdmmc/sdhcreg.h b/sys/dev/sdmmc/sdhcreg.h new file mode 100644 index 00000000000..6b786e1472f --- /dev/null +++ b/sys/dev/sdmmc/sdhcreg.h @@ -0,0 +1,173 @@ +/* $OpenBSD: sdhcreg.h,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +#ifndef _SDHCREG_H_ +#define _SDHCREG_H_ + +/* PCI base address registers */ +#define SDHC_PCI_BAR_START PCI_MAPREG_START +#define SDHC_PCI_BAR_END PCI_MAPREG_END + +/* PCI interface classes */ +#define SDHC_PCI_INTERFACE_NO_DMA 0x00 +#define SDHC_PCI_INTERFACE_DMA 0x01 +#define SDHC_PCI_INTERFACE_VENDOR 0x02 + +/* Host standard register set */ +#define SDHC_DMA_ADDR 0x00 +#define SDHC_BLOCK_SIZE 0x04 +#define SDHC_BLOCK_COUNT 0x06 +#define SDHC_BLOCK_COUNT_MAX 512 +#define SDHC_ARGUMENT 0x08 +#define SDHC_TRANSFER_MODE 0x0c +#define SDHC_MULTI_BLOCK_MODE (1<<5) +#define SDHC_READ_MODE (1<<4) +#define SDHC_AUTO_CMD12_ENABLE (1<<2) +#define SDHC_BLOCK_COUNT_ENABLE (1<<1) +#define SDHC_DMA_ENABLE (1<<0) +#define SDHC_COMMAND 0x0e +#define SDHC_COMMAND_INDEX_SHIFT 8 +#define SDHC_COMMAND_INDEX_MASK 0x3f +#define SDHC_COMMAND_TYPE_ABORT (3<<6) +#define SDHC_COMMAND_TYPE_RESUME (2<<6) +#define SDHC_COMMAND_TYPE_SUSPEND (1<<6) +#define SDHC_COMMAND_TYPE_NORMAL (0<<6) +#define SDHC_DATA_PRESENT_SELECT (1<<5) +#define SDHC_INDEX_CHECK_ENABLE (1<<4) +#define SDHC_CRC_CHECK_ENABLE (1<<3) +#define SDHC_NO_RESPONSE (0<<0) +#define SDHC_RESP_LEN_136 (1<<0) +#define SDHC_RESP_LEN_48 (2<<0) +#define SDHC_RESP_LEN_48_CHK_BUSY (3<<0) +#define SDHC_RESPONSE 0x10 /* - 0x1f */ +#define SDHC_DATA 0x20 +#define SDHC_PRESENT_STATE 0x24 +#define SDHC_CARD_INSERTED (1<<16) +#define SDHC_BUFFER_READ_ENABLE (1<<11) +#define SDHC_BUFFER_WRITE_ENABLE (1<<10) +#define SDHC_CMD_INHIBIT_DAT (1<<1) +#define SDHC_CMD_INHIBIT_CMD (1<<0) +#define SDHC_CMD_INHIBIT_MASK 0x0003 +#define SDHC_HOST_CTL 0x28 +#define SDHC_HIGH_SPEED (1<<2) +#define SDHC_4BIT_MODE (1<<1) +#define SDHC_LED_ON (1<<0) +#define SDHC_POWER_CTL 0x29 +#define SDHC_VOLTAGE_SHIFT 1 +#define SDHC_VOLTAGE_MASK 0x07 +#define SDHC_VOLTAGE_3_3V 0x07 +#define SDHC_VOLTAGE_3_0V 0x06 +#define SDHC_VOLTAGE_1_8V 0x05 +#define SDHC_BUS_POWER (1<<0) +#define SDHC_BLOCK_GAP_CTL 0x2a +#define SDHC_WAKEUP_CTL 0x2b +#define SDHC_CLOCK_CTL 0x2c +#define SDHC_SDCLK_DIV_SHIFT 8 +#define SDHC_SDCLK_DIV_MASK 0xff +#define SDHC_SDCLK_ENABLE (1<<2) +#define SDHC_INTCLK_STABLE (1<<1) +#define SDHC_INTCLK_ENABLE (1<<0) +#define SDHC_TIMEOUT_CTL 0x2e +#define SDHC_TIMEOUT_MAX 0x0e +#define SDHC_SOFTWARE_RESET 0x2f +#define SDHC_RESET_MASK 0x5 +#define SDHC_RESET_DAT (1<<2) +#define SDHC_RESET_CMD (1<<1) +#define SDHC_RESET_ALL (1<<0) +#define SDHC_NINTR_STATUS 0x30 +#define SDHC_ERROR_INTERRUPT (1<<15) +#define SDHC_CARD_INTERRUPT (1<<8) +#define SDHC_CARD_REMOVAL (1<<7) +#define SDHC_CARD_INSERTION (1<<6) +#define SDHC_BUFFER_READ_READY (1<<5) +#define SDHC_BUFFER_WRITE_READY (1<<4) +#define SDHC_DMA_INTERRUPT (1<<3) +#define SDHC_BLOCK_GAP_EVENT (1<<2) +#define SDHC_TRANSFER_COMPLETE (1<<1) +#define SDHC_COMMAND_COMPLETE (1<<0) +#define SDHC_NINTR_STATUS_MASK 0x81ff +#define SDHC_EINTR_STATUS 0x32 +#define SDHC_AUTO_CMD12_ERROR (1<<8) +#define SDHC_CURRENT_LIMIT_ERROR (1<<7) +#define SDHC_DATA_END_BIT_ERROR (1<<6) +#define SDHC_DATA_CRC_ERROR (1<<5) +#define SDHC_DATA_TIMEOUT_ERROR (1<<4) +#define SDHC_CMD_INDEX_ERROR (1<<3) +#define SDHC_CMD_END_BIT_ERROR (1<<2) +#define SDHC_CMD_CRC_ERROR (1<<1) +#define SDHC_CMD_TIMEOUT_ERROR (1<<0) +#define SDHC_EINTR_STATUS_MASK 0x01ff /* excluding vendor signals */ +#define SDHC_NINTR_STATUS_EN 0x34 +#define SDHC_EINTR_STATUS_EN 0x36 +#define SDHC_NINTR_SIGNAL_EN 0x38 +#define SDHC_NINTR_SIGNAL_MASK 0x01ff +#define SDHC_EINTR_SIGNAL_EN 0x3a +#define SDHC_EINTR_SIGNAL_MASK 0x01ff /* excluding vendor signals */ +#define SDHC_CMD12_ERROR_STATUS 0x3c +#define SDHC_CAPABILITIES 0x40 +#define SDHC_VOLTAGE_SUPP_1_8V (1<<26) +#define SDHC_VOLTAGE_SUPP_3_0V (1<<25) +#define SDHC_VOLTAGE_SUPP_3_3V (1<<24) +#define SDHC_DMA_SUPPORT (1<<22) +#define SDHC_HIGH_SPEED_SUPP (1<<21) +#define SDHC_MAX_BLK_LEN_512 0 +#define SDHC_MAX_BLK_LEN_1024 1 +#define SDHC_MAX_BLK_LEN_2048 2 +#define SDHC_MAX_BLK_LEN_SHIFT 16 +#define SDHC_MAX_BLK_LEN_MASK 0x3 +#define SDHC_BASE_FREQ_SHIFT 8 +#define SDHC_BASE_FREQ_MASK 0x2f +#define SDHC_TIMEOUT_FREQ_UNIT (1<<7) /* 0=KHz, 1=MHz */ +#define SDHC_TIMEOUT_FREQ_SHIFT 0 +#define SDHC_TIMEOUT_FREQ_MASK 0x1f +#define SDHC_MAX_CAPABILITIES 0x48 +#define SDHC_SLOT_INTR_STATUS 0xfc +#define SDHC_HOST_CTL_VERSION 0xfe +#define SDHC_SPEC_VERS_SHIFT 0 +#define SDHC_SPEC_VERS_MASK 0xff +#define SDHC_VENDOR_VERS_SHIFT 8 +#define SDHC_VENDOR_VERS_MASK 0xff + +/* SDHC_CAPABILITIES decoding */ +#define SDHC_BASE_FREQ_KHZ(cap) \ + ((((cap) >> SDHC_BASE_FREQ_SHIFT) & SDHC_BASE_FREQ_MASK) * 1000) +#define SDHC_TIMEOUT_FREQ(cap) \ + (((cap) >> SDHC_TIMEOUT_FREQ_SHIFT) & SDHC_TIMEOUT_FREQ_MASK) +#define SDHC_TIMEOUT_FREQ_KHZ(cap) \ + (((cap) & SDHC_TIMEOUT_FREQ_UNIT) ? \ + SDHC_TIMEOUT_FREQ(cap) * 1000: \ + SDHC_TIMEOUT_FREQ(cap)) + +/* SDHC_HOST_CTL_VERSION decoding */ +#define SDHC_SPEC_VERSION(hcv) \ + (((hcv) >> SDHC_SPEC_VERS_SHIFT) & SDHC_SPEC_VERS_MASK) +#define SDHC_VENDOR_VERSION(hcv) \ + (((hcv) >> SDHC_VENDOR_VERS_SHIFT) & SDHC_VENDOR_VERS_MASK) + +#define SDHC_PRESENT_STATE_BITS \ + "\20\31CL\30D3L\27D2L\26D1L\25D0L\24WPS\23CD\22CSS\21CI" \ + "\14BRE\13BWE\12RTA\11WTA\3DLA\2CID\1CIC" +#define SDHC_NINTR_STATUS_BITS \ + "\20\20ERROR\11CARD\10REMOVAL\7INSERTION\6READ\5WRITE" \ + "\4DMA\3GAP\2XFER\1CMD" +#define SDHC_EINTR_STATUS_BITS \ + "\20\11ACMD12\10CL\7DEB\6DCRC\5DT\4CI\3CEB\2CCRC\1CT" +#define SDHC_CAPABILITIES_BITS \ + "\20\33Vdd1.8V\32Vdd3.0V\31Vdd3.3V\30SUSPEND\27DMA\26HIGHSPEED" + +#endif diff --git a/sys/dev/sdmmc/sdhcvar.h b/sys/dev/sdmmc/sdhcvar.h new file mode 100644 index 00000000000..c39fb0dd955 --- /dev/null +++ b/sys/dev/sdmmc/sdhcvar.h @@ -0,0 +1,39 @@ +/* $OpenBSD: sdhcvar.h,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +#ifndef _SDHCVAR_H_ +#define _SDHCVAR_H_ + +#include <machine/bus.h> + +struct sdhc_host; + +struct sdhc_softc { + struct device sc_dev; + struct sdhc_host *sc_host; + int sc_nhosts; +}; + +/* Host controller functions called by the attachment driver. */ +int sdhc_host_found(struct sdhc_softc *, bus_space_tag_t, + bus_space_handle_t, bus_size_t, int); +void sdhc_power(int, void *); +void sdhc_shutdown(void *); +int sdhc_intr(void *); + +#endif diff --git a/sys/dev/sdmmc/sdmmc.c b/sys/dev/sdmmc/sdmmc.c new file mode 100644 index 00000000000..5caa81e624c --- /dev/null +++ b/sys/dev/sdmmc/sdmmc.c @@ -0,0 +1,556 @@ +/* $OpenBSD: sdmmc.c,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +/* + * Host controller independent SD/MMC bus driver based on information + * from SanDisk SD Card Product Manual Revision 2.2 (SanDisk), SDIO + * Simple Specification Version 1.0 (SDIO) and the Linux "mmc" driver. + */ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/systm.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +#include <dev/sdmmc/sdmmc_scsi.h> +#include <dev/sdmmc/sdmmcchip.h> +#ifdef notyet +#include <dev/sdmmc/sdmmcdevs.h> +#endif +#include <dev/sdmmc/sdmmcreg.h> +#include <dev/sdmmc/sdmmcvar.h> + +#undef SDMMC_DEBUG + +int sdmmc_match(struct device *, void *, void *); +void sdmmc_attach(struct device *, struct device *, void *); + +int sdmmc_set_relative_addr(struct sdmmc_softc *, struct sdmmc_card *); +int sdmmc_set_bus_width(struct sdmmc_softc *, struct sdmmc_card *); +int sdmmc_enable(struct sdmmc_softc *); +void sdmmc_disable(struct sdmmc_softc *); +int sdmmc_decode_csd(struct sdmmc_softc *, sdmmc_response, + struct sdmmc_card *); +int sdmmc_decode_cid(struct sdmmc_softc *, sdmmc_response, + struct sdmmc_card *); +void sdmmc_print_cid(struct sdmmc_cid *); +void sdmmc_identify_all(struct sdmmc_softc *); + +#ifdef SDMMC_DEBUG +#define DPRINTF(s) printf s +#else +#define DPRINTF(s) /**/ +#endif + +struct cfattach sdmmc_ca = { + sizeof(struct sdmmc_softc), sdmmc_match, sdmmc_attach +}; + +struct cfdriver sdmmc_cd = { + NULL, "sdmmc", DV_DULL +}; + +int +sdmmc_match(struct device *parent, void *match, void *aux) +{ + struct cfdata *cf = match; + struct sdmmcbus_attach_args *saa = aux; + + return strcmp(saa->saa_busname, cf->cf_driver->cd_name) == 0; +} + +void +sdmmc_attach(struct device *parent, struct device *self, void *aux) +{ + struct sdmmc_softc *sc = (struct sdmmc_softc *)self; + struct sdmmcbus_attach_args *saa = aux; + + printf("\n"); + + sc->sct = saa->sct; + sc->sch = saa->sch; + + SIMPLEQ_INIT(&sc->cs_head); +} + +/* + * Called from the host driver when a card, or a stack of cards are + * inserted. Return zero if any card drivers have been attached. + */ +int +sdmmc_card_attach(struct device *dev) +{ + struct sdmmc_softc *sc = (struct sdmmc_softc *)dev; + struct sdmmc_card *cs; + + DPRINTF(("%s: attach card\n", SDMMCDEVNAME(sc))); + + /* Power up the card (or card stack). */ + if (sdmmc_enable(sc) != 0) { + printf("%s: can't enable card\n", SDMMCDEVNAME(sc)); + return 1; + } + + /* Scan for cards and allocate a card structure for each. */ + sdmmc_identify_all(sc); + + /* There should be at least one card now; otherwise, bail out. */ + if (SIMPLEQ_EMPTY(&sc->cs_head)) { + printf("%s: can't identify card\n", SDMMCDEVNAME(sc)); + sdmmc_disable(sc); + return 1; + } + + /* Initialize all identified cards. */ + SIMPLEQ_FOREACH(cs, &sc->cs_head, cs_list) { + /* Boost the bus width. */ + (void)sdmmc_set_bus_width(sc, cs); /* XXX */ + + if (ISSET(sc->sc_flags, SMF_MEM_MODE) && + sdmmc_mem_init(sc, cs) != 0) + printf("%s: init failed\n", SDMMCDEVNAME(sc)); + } + + /* XXX attach SDIO driver(s) */ + + /* Attach SCSI emulation for memory cards. */ + if (ISSET(sc->sc_flags, SMF_MEM_MODE)) + sdmmc_scsi_attach(sc); + return 0; +} + +/* + * Called from host driver with DETACH_* flags from <sys/device.h> + * when cards are gone. + */ +void +sdmmc_card_detach(struct device *dev, int flags) +{ + struct sdmmc_softc *sc = (struct sdmmc_softc *)dev; + struct sdmmc_card *cs, *csnext; + + DPRINTF(("%s: detach card\n", SDMMCDEVNAME(sc))); + + /* Power down. */ + sdmmc_disable(sc); + + /* Detach SCSI emulation. */ + if (ISSET(sc->sc_flags, SMF_MEM_MODE)) + sdmmc_scsi_detach(sc); + + /* XXX detach SDIO driver(s) */ + + /* Free all card structures. */ + for (cs = SIMPLEQ_FIRST(&sc->cs_head); cs != NULL; cs = csnext) { + csnext = SIMPLEQ_NEXT(cs, cs_list); + FREE(cs, M_DEVBUF); + } + SIMPLEQ_INIT(&sc->cs_head); +} + +int +sdmmc_enable(struct sdmmc_softc *sc) +{ + u_int32_t host_ocr; + int error; + + /* + * Calculate the equivalent of the card OCR from the host + * capabilities and select the maximum supported bus voltage. + */ + host_ocr = sdmmc_chip_host_ocr(sc->sct, sc->sch); + error = sdmmc_chip_bus_power(sc->sct, sc->sch, host_ocr); + if (error != 0) { + printf("%s: can't supply bus power\n", SDMMCDEVNAME(sc)); + goto err; + } + + /* + * Select the minimum clock frequency. + */ + error = sdmmc_chip_bus_clock(sc->sct, sc->sch, SDMMC_SDCLK_400KHZ); + if (error != 0) { + printf("%s: can't supply clock\n", SDMMCDEVNAME(sc)); + goto err; + } + + /* Initialize SD I/O card function(s). */ + if ((error = sdmmc_io_enable(sc)) != 0) + goto err; + + /* Initialize SD/MMC memory card(s). */ + if ((error = sdmmc_mem_enable(sc)) != 0) + goto err; + + /* XXX */ + if (ISSET(sc->sc_flags, SMF_SD_MODE)) + (void)sdmmc_chip_bus_clock(sc->sct, sc->sch, + SDMMC_SDCLK_25MHZ); + + err: + if (error != 0) + sdmmc_disable(sc); + return error; +} + +void +sdmmc_disable(struct sdmmc_softc *sc) +{ + /* XXX complete commands if card is still present. */ + + /* Deselect all cards. */ + (void)sdmmc_select_card(sc, NULL); + + /* Turn off bus power and clock. */ + (void)sdmmc_chip_bus_clock(sc->sct, sc->sch, SDMMC_SDCLK_OFF); + (void)sdmmc_chip_bus_power(sc->sct, sc->sch, 0); +} + +void +sdmmc_delay(u_int usecs) +{ + int ticks = usecs / (1000000 / hz); + + if (ticks > 0) + (void)tsleep(&sdmmc_delay, PWAIT, "sdwait", ticks); + else + delay(usecs); +} + +/* + * Set the lowest bus voltage supported by the card and the host. + */ +int +sdmmc_set_bus_power(struct sdmmc_softc *sc, u_int32_t host_ocr, + u_int32_t card_ocr) +{ + u_int32_t bit; + + /* Mask off unsupported voltage levels and select the lowest. */ + DPRINTF(("%s: host_ocr=%x ", SDMMCDEVNAME(sc), host_ocr)); + host_ocr &= card_ocr; + for (bit = 4; bit < 23; bit++) { + if (ISSET(host_ocr, 1<<bit)) { + host_ocr &= 3<<bit; + break; + } + } + DPRINTF(("card_ocr=%x new_ocr=%x\n", card_ocr, host_ocr)); + + if (host_ocr == 0 || + sdmmc_chip_bus_power(sc->sct, sc->sch, host_ocr) != 0) + return 1; + return 0; +} + +/* + * Read the CSD and CID from all cards and assign each card a unique + * relative card address (RCA). + */ +void +sdmmc_identify_all(struct sdmmc_softc *sc) +{ + struct sdmmc_command cmd; + struct sdmmc_card *cs; + u_int16_t next_rca; + int error; + int i; + + /* + * CMD2 is a broadcast command understood by SD cards and MMC + * cards. All cards begin to respond to the command, but back + * off if another card drives the CMD line to a different level. + * Only one card will get its entire response through. That + * card remains silent once it has been assigned a RCA. + */ + for (i = 0; i < 100; i++) { + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_ALL_SEND_CID; + cmd.c_flags = SCF_CMD_BCR | SCF_RSP_R2; + + error = sdmmc_mmc_command(sc, &cmd); + if (error == ETIMEDOUT) { + /* No more cards there. */ + break; + } else if (error != 0) { + DPRINTF(("%s: can't read CID\n", SDMMCDEVNAME(sc))); + break; + } + + /* In MMC mode, find the next available RCA. */ + next_rca = 0; + if (!ISSET(sc->sc_flags, SMF_SD_MODE)) + SIMPLEQ_FOREACH(cs, &sc->cs_head, cs_list) + next_rca++; + + /* Allocate a card structure. */ + MALLOC(cs, struct sdmmc_card *, sizeof *cs, M_DEVBUF, + M_WAITOK); + bzero(cs, sizeof *cs); + cs->rca = next_rca; + + /* + * Remember the CID returned in the CMD2 response for + * later decoding. + */ + bcopy(cmd.c_resp, cs->raw_cid, sizeof cs->raw_cid); + + /* + * Silence the card by assigning it a unique RCA, or + * querying it for its RCA in case of SD. + */ + if (sdmmc_set_relative_addr(sc, cs) != 0) { + printf("%s: can't set RCA\n", SDMMCDEVNAME(sc)); + FREE(cs, M_DEVBUF); + break; + } + + SIMPLEQ_INSERT_TAIL(&sc->cs_head, cs, cs_list); + } + + /* + * All cards are either inactive or awaiting further commands. + * Read the CSDs and decode the raw CID for each card. + */ + SIMPLEQ_FOREACH(cs, &sc->cs_head, cs_list) { + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_SEND_CSD; + cmd.c_arg = MMC_ARG_RCA(cs->rca); + cmd.c_flags = SCF_CMD_AC | SCF_RSP_R2; + + if (sdmmc_mmc_command(sc, &cmd) != 0) { + SET(cs->flags, SDMMCF_CARD_ERROR); + continue; + } + + if (sdmmc_decode_csd(sc, cmd.c_resp, cs) != 0 || + sdmmc_decode_cid(sc, cs->raw_cid, cs) != 0) { + SET(cs->flags, SDMMCF_CARD_ERROR); + continue; + } + +#ifdef SDMMC_DEBUG + printf("%s: CID: ", SDMMCDEVNAME(sc)); + sdmmc_print_cid(&cs->cid); +#endif + } +} + +int +sdmmc_app_command(struct sdmmc_softc *sc, struct sdmmc_command *cmd) +{ + struct sdmmc_command acmd; + int error; + + bzero(&acmd, sizeof acmd); + acmd.c_opcode = MMC_APP_CMD; + acmd.c_arg = 0; + acmd.c_flags = SCF_CMD_AC | SCF_RSP_R1; + + error = sdmmc_mmc_command(sc, &acmd); + if (error != 0) + return error; + + if (!ISSET(MMC_R1(acmd.c_resp), MMC_R1_APP_CMD)) + /* Card does not support application commands. */ + return ENODEV; + + return sdmmc_mmc_command(sc, cmd); +} + +int +sdmmc_mmc_command(struct sdmmc_softc *sc, struct sdmmc_command *cmd) +{ + return sdmmc_chip_exec_command(sc->sct, sc->sch, cmd); +} + +/* + * Send the "GO IDLE STATE" command. + */ +void +sdmmc_go_idle_state(struct sdmmc_softc *sc) +{ + struct sdmmc_command cmd; + + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_GO_IDLE_STATE; + cmd.c_flags = SCF_CMD_BC | SCF_RSP_R0; + + (void)sdmmc_mmc_command(sc, &cmd); +} + +/* + * Retrieve (SD) or set (MMC) the relative card address (RCA). + */ +int +sdmmc_set_relative_addr(struct sdmmc_softc *sc, struct sdmmc_card *cs) +{ + struct sdmmc_command cmd; + + bzero(&cmd, sizeof cmd); + + if (ISSET(sc->sc_flags, SMF_SD_MODE)) { + cmd.c_opcode = SD_SEND_RELATIVE_ADDR; + cmd.c_flags = SCF_CMD_BCR | SCF_RSP_R6; + } else { + cmd.c_opcode = MMC_SET_RELATIVE_ADDR; + cmd.c_arg = MMC_ARG_RCA(cs->rca); + cmd.c_flags = SCF_CMD_AC | SCF_RSP_R1; + } + + if (sdmmc_mmc_command(sc, &cmd) != 0) + return 1; + + if (ISSET(sc->sc_flags, SMF_SD_MODE)) + cs->rca = SD_R6_RCA(cmd.c_resp); + return 0; +} + +/* + * Set the maximum supported bus width. + */ +int +sdmmc_set_bus_width(struct sdmmc_softc *sc, struct sdmmc_card *cs) +{ + struct sdmmc_command cmd; + int error; + + if (!ISSET(sc->sc_flags, SMF_SD_MODE)) + return EOPNOTSUPP; + + if ((error = sdmmc_select_card(sc, cs)) != 0) + return error; + + bzero(&cmd, sizeof cmd); + cmd.c_opcode = SD_APP_SET_BUS_WIDTH; + cmd.c_arg = SD_ARG_BUS_WIDTH_4; + cmd.c_flags = SCF_CMD_AC | SCF_RSP_R1; + + return sdmmc_app_command(sc, &cmd); +} + +int +sdmmc_select_card(struct sdmmc_softc *sc, struct sdmmc_card *cs) +{ + struct sdmmc_command cmd; + int error; + + if (sc->sc_card == cs) + return 0; + + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_SELECT_CARD; + cmd.c_arg = cs == NULL ? 0 : MMC_ARG_RCA(cs->rca); + cmd.c_flags = SCF_CMD_AC | (cs == NULL ? SCF_RSP_R0 : SCF_RSP_R1); + error = sdmmc_mmc_command(sc, &cmd); + if (error == 0 || cs == NULL) + sc->sc_card = cs; + return error; +} + +int +sdmmc_decode_csd(struct sdmmc_softc *sc, sdmmc_response resp, + struct sdmmc_card *cs) +{ + struct sdmmc_csd *csd = &cs->csd; + + if (ISSET(sc->sc_flags, SMF_SD_MODE)) { + /* + * CSD version 1.0 corresponds to SD system + * specification version 1.0 - 1.10. (SanDisk, 3.5.3) + */ + csd->csdver = SD_CSD_CSDVER(resp); + if (csd->csdver != SD_CSD_CSDVER_1_0) { + printf("%s: unknown SD CSD structure version 0x%x\n", + SDMMCDEVNAME(sc), csd->csdver); + return 1; + } + + csd->capacity = SD_CSD_CAPACITY(resp); + csd->read_bl_len = SD_CSD_READ_BL_LEN(resp); + } else { + csd->csdver = MMC_CSD_CSDVER(resp); + if (csd->csdver != MMC_CSD_CSDVER_1_0 && + csd->csdver != MMC_CSD_CSDVER_2_0) { + printf("%s: unknown MMC CSD structure version 0x%x\n", + SDMMCDEVNAME(sc), csd->csdver); + return 1; + } + + csd->mmcver = MMC_CSD_MMCVER(resp); + csd->capacity = MMC_CSD_CAPACITY(resp); + csd->read_bl_len = MMC_CSD_READ_BL_LEN(resp); + csd->sector_size = 1 << csd->read_bl_len; + } + csd->sector_size = MIN(1 << csd->read_bl_len, + sdmmc_chip_host_maxblklen(sc->sct, sc->sch)); + return 0; +} + +int +sdmmc_decode_cid(struct sdmmc_softc *sc, sdmmc_response resp, + struct sdmmc_card *cs) +{ + struct sdmmc_cid *cid = &cs->cid; + + if (ISSET(sc->sc_flags, SMF_SD_MODE)) { + cid->mid = SD_CID_MID(resp); + cid->oid = SD_CID_OID(resp); + SD_CID_PNM_CPY(resp, cid->pnm); + cid->rev = SD_CID_REV(resp); + cid->psn = SD_CID_PSN(resp); + cid->mdt = SD_CID_MDT(resp); + } else { + switch(cs->csd.mmcver) { + case MMC_CSD_MMCVER_1_0: + case MMC_CSD_MMCVER_1_4: + cid->mid = MMC_CID_MID_V1(resp); + MMC_CID_PNM_V1_CPY(resp, cid->pnm); + cid->rev = MMC_CID_REV_V1(resp); + cid->psn = MMC_CID_PSN_V1(resp); + cid->mdt = MMC_CID_MDT_V1(resp); + break; + case MMC_CSD_MMCVER_2_0: + case MMC_CSD_MMCVER_3_1: + case MMC_CSD_MMCVER_4_0: + cid->mid = MMC_CID_MID_V2(resp); + cid->oid = MMC_CID_OID_V2(resp); + MMC_CID_PNM_V2_CPY(resp, cid->pnm); + cid->psn = MMC_CID_PSN_V2(resp); + break; + default: + printf("%s: unknown MMC version %d\n", + SDMMCDEVNAME(sc), cs->csd.mmcver); + return 1; + } + } + return 0; +} + +void +sdmmc_print_cid(struct sdmmc_cid *cid) +{ + printf("mid=0x%02x oid=0x%04x pnm=\"%s\" rev=0x%02x psn=0x%08x" + " mdt=%03x\n", cid->mid, cid->oid, cid->pnm, cid->rev, cid->psn, + cid->mdt); +} diff --git a/sys/dev/sdmmc/sdmmc_io.c b/sys/dev/sdmmc/sdmmc_io.c new file mode 100644 index 00000000000..3adad9bfdaf --- /dev/null +++ b/sys/dev/sdmmc/sdmmc_io.c @@ -0,0 +1,140 @@ +/* $OpenBSD: sdmmc_io.c,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +/* Routines for SD I/O cards. */ + +#include <sys/param.h> +#include <sys/systm.h> + +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmcreg.h> +#include <dev/sdmmc/sdmmcvar.h> + +#undef SDMMC_DEBUG + +#ifdef SDMMC_DEBUG +#define DPRINTF(s) printf s +#else +#define DPRINTF(s) /**/ +#endif + +/* + * Initialize SD I/O card functions (before memory cards). The host + * system and controller must support card interrupts in order to use + * I/O functions. + */ +int +sdmmc_io_enable(struct sdmmc_softc *sc) +{ + u_int32_t host_ocr; + u_int32_t card_ocr; + + /* Set host mode to SD "combo" card. */ + SET(sc->sc_flags, SMF_SD_MODE|SMF_IO_MODE|SMF_MEM_MODE); + + /* Reset I/O functions (*must* do that before CMD5). */ + sdmmc_io_reset(sc); + + /* + * Read the I/O OCR value, determine the number of I/O + * functions and whether memory is also present (a "combo + * card") by issuing CMD5. SD memory-only and MMC cards + * do not respond to CMD5. + */ + if (sdmmc_io_send_op_cond(sc, 0, &card_ocr) != 0) { + /* No SDIO card; switch to SD memory-only mode. */ + CLR(sc->sc_flags, SMF_IO_MODE); + return 0; + } + + if (SD_IO_OCR_NF(card_ocr) == 0) { + /* No I/O functions. */ + DPRINTF(("%s: no I/O functions\n", SDMMCDEVNAME(sc))); + return 0; + } + + /* Set the lowest voltage supported by the card and host. */ + host_ocr = sdmmc_chip_host_ocr(sc->sct, sc->sch); + if (sdmmc_set_bus_power(sc, host_ocr, card_ocr) != 0) { + printf("%s: can't supply voltage requested by card\n", + SDMMCDEVNAME(sc)); + return 1; + } + + /* Reset I/O functions (again). */ + sdmmc_io_reset(sc); + + /* Send the new OCR value until all cards are ready. */ + if (sdmmc_io_send_op_cond(sc, host_ocr, NULL) != 0) { + printf("%s: can't send I/O OCR\n", SDMMCDEVNAME(sc)); + return 1; + } + return 0; +} + +/* + * Send the "I/O RESET" command. + */ +void +sdmmc_io_reset(struct sdmmc_softc *sc) +{ + struct sdmmc_command cmd; + + bzero(&cmd, sizeof cmd); + cmd.c_opcode = SD_IO_RESET; + cmd.c_flags = SCF_CMD_BC | SCF_RSP_R0; + + (void)sdmmc_mmc_command(sc, &cmd); +} + +/* + * Get or set the card's I/O OCR value (SDIO). + */ +int +sdmmc_io_send_op_cond(struct sdmmc_softc *sc, u_int32_t ocr, u_int32_t *ocrp) +{ + struct sdmmc_command cmd; + int error; + int i; + + /* + * If we change the OCR value, retry the command until the OCR + * we receive in response has the "CARD BUSY" bit set, meaning + * that all cards are ready for card identification. + */ + for (i = 0; i < 100; i++) { + bzero(&cmd, sizeof cmd); + cmd.c_opcode = SD_IO_SEND_OP_COND; + cmd.c_arg = ocr; + cmd.c_flags = SCF_CMD_BCR | SCF_RSP_R4; + + error = sdmmc_mmc_command(sc, &cmd); + if (error != 0) + break; + if (ISSET(MMC_R4(cmd.c_resp), SD_IO_OCR_MEM_READY) || + ocr == 0) + break; + error = ETIMEDOUT; + sdmmc_delay(10000); + } + if (error == 0) + printf("ocr: %x\n", MMC_R4(cmd.c_resp)); + if (error == 0 && ocrp != NULL) + *ocrp = MMC_R4(cmd.c_resp); + return error; +} diff --git a/sys/dev/sdmmc/sdmmc_mem.c b/sys/dev/sdmmc/sdmmc_mem.c new file mode 100644 index 00000000000..c10e6018566 --- /dev/null +++ b/sys/dev/sdmmc/sdmmc_mem.c @@ -0,0 +1,243 @@ +/* $OpenBSD: sdmmc_mem.c,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +/* Routines for SD/MMC memory cards. */ + +#include <sys/param.h> +#include <sys/systm.h> + +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmcreg.h> +#include <dev/sdmmc/sdmmcvar.h> + +#undef SDMMC_DEBUG + +#ifdef SDMMC_DEBUG +#define DPRINTF(s) printf s +#else +#define DPRINTF(s) /**/ +#endif + +int sdmmc_mem_send_op_cond(struct sdmmc_softc *, u_int32_t, u_int32_t *); +int sdmmc_mem_set_blocklen(struct sdmmc_softc *, struct sdmmc_card *); + +/* + * Initialize SD/MMC memory cards and memory in SDIO "combo" cards. + */ +int +sdmmc_mem_enable(struct sdmmc_softc *sc) +{ + u_int32_t host_ocr; + u_int32_t card_ocr; + + /* Set host mode to SD "combo" card or SD memory-only. */ + SET(sc->sc_flags, SMF_SD_MODE|SMF_MEM_MODE); + + /* Reset memory (*must* do that before CMD55 or CMD1). */ + sdmmc_go_idle_state(sc); + + /* + * Read the SD/MMC memory OCR value by issuing CMD55 followed + * by ACMD41 to read the OCR value from memory-only SD cards. + * MMC cards will not respond to CMD55 or ACMD41 and this is + * how we distinguish them from SD cards. + */ + mmc_mode: + if (sdmmc_mem_send_op_cond(sc, 0, &card_ocr) != 0) { + DPRINTF(("flags %x\n", sc->sc_flags)); + if (ISSET(sc->sc_flags, SMF_SD_MODE) && + !ISSET(sc->sc_flags, SMF_IO_MODE)) { + /* Not a SD card, switch to MMC mode. */ + CLR(sc->sc_flags, SMF_SD_MODE); + goto mmc_mode; + } + if (!ISSET(sc->sc_flags, SMF_SD_MODE)) { + printf("%s: can't read memory OCR\n", + SDMMCDEVNAME(sc)); + return 1; + } else { + /* Not a "combo" card. */ + CLR(sc->sc_flags, SMF_MEM_MODE); + return 0; + } + } + + /* Set the lowest voltage supported by the card and host. */ + host_ocr = sdmmc_chip_host_ocr(sc->sct, sc->sch); + if (sdmmc_set_bus_power(sc, host_ocr, card_ocr) != 0) { + printf("%s: can't supply voltage requested by card\n", + SDMMCDEVNAME(sc)); + return 1; + } + + /* Tell the card(s) to enter the idle state (again). */ + sdmmc_go_idle_state(sc); + + /* Send the new OCR value until all cards are ready. */ + if (sdmmc_mem_send_op_cond(sc, host_ocr, NULL) != 0) { + printf("%s: can't send memory OCR\n", SDMMCDEVNAME(sc)); + return 1; + } + return 0; +} + +/* + * Initialize a SD/MMC memory card. + */ +int +sdmmc_mem_init(struct sdmmc_softc *sc, struct sdmmc_card *cs) +{ + if (sdmmc_select_card(sc, cs) != 0 || + sdmmc_mem_set_blocklen(sc, cs) != 0) + return 1; + return 0; +} + +/* + * Get or set the card's memory OCR value (SD or MMC). + */ +int +sdmmc_mem_send_op_cond(struct sdmmc_softc *sc, u_int32_t ocr, + u_int32_t *ocrp) +{ + struct sdmmc_command cmd; + int error; + int i; + + /* + * If we change the OCR value, retry the command until the OCR + * we receive in response has the "CARD BUSY" bit set, meaning + * that all cards are ready for card identification. + */ + for (i = 0; i < 100; i++) { + bzero(&cmd, sizeof cmd); + cmd.c_arg = ocr; + cmd.c_flags = SCF_CMD_BCR | SCF_RSP_R3; + + if (ISSET(sc->sc_flags, SMF_SD_MODE)) { + cmd.c_opcode = SD_APP_OP_COND; + error = sdmmc_app_command(sc, &cmd); + } else { + cmd.c_opcode = MMC_SEND_OP_COND; + error = sdmmc_mmc_command(sc, &cmd); + } + if (error != 0) + break; + if (ISSET(MMC_R3(cmd.c_resp), MMC_OCR_MEM_READY) || + ocr == 0) + break; + error = ETIMEDOUT; + sdmmc_delay(10000); + } + if (error == 0 && ocrp != NULL) + *ocrp = MMC_R3(cmd.c_resp); + return error; +} + +/* + * Set the read block length appropriately for this card, according to + * the card CSD register value. + */ +int +sdmmc_mem_set_blocklen(struct sdmmc_softc *sc, struct sdmmc_card *cs) +{ + struct sdmmc_command cmd; + + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_SET_BLOCKLEN; + cmd.c_arg = cs->csd.sector_size; + cmd.c_flags = SCF_CMD_AC | SCF_RSP_R1; + DPRINTF(("%s: read_bl_len=%d sector_size=%d\n", SDMMCDEVNAME(sc), + 1 << cs->csd.read_bl_len, cs->csd.sector_size)); + + return sdmmc_mmc_command(sc, &cmd); +} + +int +sdmmc_mem_read_block(struct sdmmc_softc *sc, struct sdmmc_card *cs, + int blkno, u_char *data, size_t datalen) +{ + struct sdmmc_command cmd; + int error; + + if ((error = sdmmc_select_card(sc, cs)) != 0) + return error; + + bzero(&cmd, sizeof cmd); + cmd.c_data = data; + cmd.c_datalen = datalen; + cmd.c_blklen = cs->csd.sector_size; + cmd.c_opcode = (datalen / cmd.c_blklen) > 1 ? + MMC_READ_BLOCK_MULTIPLE : MMC_READ_BLOCK_SINGLE; + cmd.c_arg = blkno << 9; + cmd.c_flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1; + + error = sdmmc_mmc_command(sc, &cmd); + if (error != 0) + return error; + + do { + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_SEND_STATUS; + cmd.c_arg = MMC_ARG_RCA(cs->rca); + cmd.c_flags = SCF_CMD_AC | SCF_RSP_R1; + error = sdmmc_mmc_command(sc, &cmd); + if (error != 0) + break; + /* XXX time out */ + } while (!ISSET(MMC_R1(cmd.c_resp), MMC_R1_READY_FOR_DATA)); + + return error; +} + +int +sdmmc_mem_write_block(struct sdmmc_softc *sc, struct sdmmc_card *cs, + int blkno, u_char *data, size_t datalen) +{ + struct sdmmc_command cmd; + int error; + + if ((error = sdmmc_select_card(sc, cs)) != 0) + return error; + + bzero(&cmd, sizeof cmd); + cmd.c_data = data; + cmd.c_datalen = datalen; + cmd.c_blklen = cs->csd.sector_size; + cmd.c_opcode = (datalen / cmd.c_blklen) > 1 ? + MMC_WRITE_BLOCK_MULTIPLE : MMC_WRITE_BLOCK_SINGLE; + cmd.c_arg = blkno << 9; + cmd.c_flags = SCF_CMD_ADTC | SCF_RSP_R1; + + error = sdmmc_mmc_command(sc, &cmd); + if (error != 0) + return error; + + do { + bzero(&cmd, sizeof cmd); + cmd.c_opcode = MMC_SEND_STATUS; + cmd.c_arg = MMC_ARG_RCA(cs->rca); + cmd.c_flags = SCF_CMD_AC | SCF_RSP_R1; + error = sdmmc_mmc_command(sc, &cmd); + if (error != 0) + break; + /* XXX time out */ + } while (!ISSET(MMC_R1(cmd.c_resp), MMC_R1_READY_FOR_DATA)); + + return error; +} diff --git a/sys/dev/sdmmc/sdmmc_scsi.c b/sys/dev/sdmmc/sdmmc_scsi.c new file mode 100644 index 00000000000..e32f27bb52b --- /dev/null +++ b/sys/dev/sdmmc/sdmmc_scsi.c @@ -0,0 +1,238 @@ +/* $OpenBSD: sdmmc_scsi.c,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +/* A SCSI bus emulation to access SD/MMC memory cards. */ + +#include <sys/param.h> +#include <sys/buf.h> +#include <sys/malloc.h> +#include <sys/systm.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsi_disk.h> +#include <scsi/scsiconf.h> + +#include <dev/sdmmc/sdmmc_scsi.h> +#include <dev/sdmmc/sdmmcvar.h> + +#undef SDMMC_DEBUG + +#define SDMMC_SCSIID_HOST 0x00 +#define SDMMC_SCSIID_MAX 0x0f + +int sdmmc_scsi_cmd(struct scsi_xfer *); +int sdmmc_start_xs(struct sdmmc_softc *, struct scsi_xfer *); +int sdmmc_done_xs(struct sdmmc_softc *, struct scsi_xfer *); +void sdmmc_complete(struct sdmmc_softc *, struct scsi_xfer *); +void sdmmc_scsi_minphys(struct buf *); + +#ifdef SDMMC_DEBUG +#define DPRINTF(s) printf s +#else +#define DPRINTF(s) /**/ +#endif + +void +sdmmc_scsi_attach(struct sdmmc_softc *sc) +{ + struct sdmmc_scsi_softc *scbus; + struct sdmmc_card *cs; + + MALLOC(scbus, struct sdmmc_scsi_softc *, + sizeof *scbus, M_DEVBUF, M_WAITOK); + bzero(scbus, sizeof *scbus); + + MALLOC(scbus->sc_tgt, struct sdmmc_scsi_target *, + sizeof(*scbus->sc_tgt) * (SDMMC_SCSIID_MAX+1), + M_DEVBUF, M_WAITOK); + bzero(scbus->sc_tgt, sizeof(*scbus->sc_tgt) * (SDMMC_SCSIID_MAX+1)); + + /* + * Each card that sent us a CID in the identification stage + * gets a SCSI ID > 0, whether it is a memory card or not. + */ + scbus->sc_ntargets = 1; + SIMPLEQ_FOREACH(cs, &sc->cs_head, cs_list) { + if (scbus->sc_ntargets >= SDMMC_SCSIID_MAX+1) + break; + scbus->sc_tgt[scbus->sc_ntargets].card = cs; + scbus->sc_ntargets++; + } + + sc->sc_scsibus = scbus; + + scbus->sc_adapter.scsi_cmd = sdmmc_scsi_cmd; + scbus->sc_adapter.scsi_minphys = sdmmc_scsi_minphys; + + scbus->sc_link.adapter_target = SDMMC_SCSIID_HOST; + scbus->sc_link.adapter_buswidth = scbus->sc_ntargets; + scbus->sc_link.adapter_softc = sc; + scbus->sc_link.luns = 1; + scbus->sc_link.openings = 1; + scbus->sc_link.adapter = &scbus->sc_adapter; + + scbus->sc_child = config_found(&sc->sc_dev, &scbus->sc_link, + scsiprint); +} + +void +sdmmc_scsi_detach(struct sdmmc_softc *sc) +{ + struct sdmmc_scsi_softc *scbus; + + scbus = sc->sc_scsibus; + + if (scbus->sc_child != NULL) + config_detach(scbus->sc_child, DETACH_FORCE); + + if (scbus->sc_tgt != NULL) + FREE(scbus->sc_tgt, M_DEVBUF); + + FREE(scbus, M_DEVBUF); + sc->sc_scsibus = NULL; +} + +int +sdmmc_scsi_cmd(struct scsi_xfer *xs) +{ + struct scsi_link *link = xs->sc_link; + struct sdmmc_softc *sc = link->adapter_softc; + struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; + struct sdmmc_scsi_target *tgt = &scbus->sc_tgt[link->target]; + struct scsi_inquiry_data inq; + struct scsi_read_cap_data rcd; + + if (link->target >= scbus->sc_ntargets || tgt->card == NULL || + link->lun != 0) { + DPRINTF(("%s: sdmmc_scsi_cmd: no target %d\n", + SDMMCDEVNAME(sc), link->target)); + /* XXX should be XS_SENSE and sense filled out */ + xs->error = XS_DRIVER_STUFFUP; + xs->flags |= ITSDONE; + scsi_done(xs); + return COMPLETE; + } + + DPRINTF(("%s: sdmmc_scsi_cmd: target=%d xs=%p cmd=%#x " + "datalen=%d (poll=%d)\n", SDMMCDEVNAME(sc), link->target, + xs, xs->cmd->opcode, xs->datalen, xs->flags & SCSI_POLL)); + + xs->error = XS_NOERROR; + + switch (xs->cmd->opcode) { + case READ_COMMAND: + case READ_BIG: + case WRITE_COMMAND: + case WRITE_BIG: + /* Deal with I/O outside the switch. */ + break; + + case INQUIRY: + bzero(&inq, sizeof inq); + inq.device = T_DIRECT; + inq.version = 2; + inq.response_format = 2; + inq.additional_length = 32; + strlcpy(inq.vendor, "SD/MMC ", sizeof(inq.vendor)); + snprintf(inq.product, sizeof(inq.product), + "Drive #%02d", link->target); + strlcpy(inq.revision, " ", sizeof(inq.revision)); + bcopy(&inq, xs->data, MIN(xs->datalen, sizeof inq)); + scsi_done(xs); + return COMPLETE; + + case TEST_UNIT_READY: + case START_STOP: + case SYNCHRONIZE_CACHE: + return COMPLETE; + + case READ_CAPACITY: + bzero(&rcd, sizeof rcd); + _lto4b(tgt->card->csd.capacity - 1, rcd.addr); + _lto4b(tgt->card->csd.sector_size, rcd.length); + bcopy(&rcd, xs->data, MIN(xs->datalen, sizeof rcd)); + scsi_done(xs); + return COMPLETE; + + + default: + DPRINTF(("%s: unsupported scsi command %#x\n", + SDMMCDEVNAME(sc), xs->cmd->opcode)); + xs->error = XS_DRIVER_STUFFUP; + scsi_done(xs); + return COMPLETE; + } + + /* XXX check bounds */ + + return sdmmc_start_xs(sc, xs); +} + +int +sdmmc_start_xs(struct sdmmc_softc *sc, struct scsi_xfer *xs) +{ + sdmmc_complete(sc, xs); + return COMPLETE; +} + +void +sdmmc_complete(struct sdmmc_softc *sc, struct scsi_xfer *xs) +{ + struct scsi_link *link = xs->sc_link; + struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; + struct sdmmc_scsi_target *tgt = &scbus->sc_tgt[link->target]; + struct scsi_rw *rw; + struct scsi_rw_big *rwb; + u_int32_t blockno; + u_int32_t blockcnt; + int error; + + /* A read or write operation. */ + /* XXX move to some sort of "scsi emulation layer". */ + if (xs->cmdlen == 6) { + rw = (struct scsi_rw *)xs->cmd; + blockno = _3btol(rw->addr) & (SRW_TOPADDR << 16 | 0xffff); + blockcnt = rw->length ? rw->length : 0x100; + } else { + rwb = (struct scsi_rw_big *)xs->cmd; + blockno = _4btol(rwb->addr); + blockcnt = _2btol(rwb->length); + } + + if (ISSET(xs->flags, SCSI_DATA_IN)) + error = sdmmc_mem_read_block(sc, tgt->card, blockno, + xs->data, blockcnt * DEV_BSIZE); + else + error = sdmmc_mem_write_block(sc, tgt->card, blockno, + xs->data, blockcnt * DEV_BSIZE); + if (error != 0) + xs->error = XS_DRIVER_STUFFUP; + + xs->flags |= ITSDONE; + xs->resid = 0; + scsi_done(xs); +} + +void +sdmmc_scsi_minphys(struct buf *bp) +{ + /* XXX limit to max. transfer size supported by card/host? */ + if (bp->b_bcount > DEV_BSIZE) + bp->b_bcount = DEV_BSIZE; + minphys(bp); +} diff --git a/sys/dev/sdmmc/sdmmc_scsi.h b/sys/dev/sdmmc/sdmmc_scsi.h new file mode 100644 index 00000000000..4dd12e32953 --- /dev/null +++ b/sys/dev/sdmmc/sdmmc_scsi.h @@ -0,0 +1,39 @@ +/* $OpenBSD: sdmmc_scsi.h,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +#ifndef _SDMMC_SCSI_H_ +#define _SDMMC_SCSI_H_ + +struct sdmmc_softc; + +struct sdmmc_scsi_target { + struct sdmmc_card *card; +}; + +struct sdmmc_scsi_softc { + struct scsi_adapter sc_adapter; + struct scsi_link sc_link; + struct device *sc_child; + struct sdmmc_scsi_target *sc_tgt; + int sc_ntargets; +}; + +void sdmmc_scsi_attach(struct sdmmc_softc *); +void sdmmc_scsi_detach(struct sdmmc_softc *); + +#endif diff --git a/sys/dev/sdmmc/sdmmcchip.h b/sys/dev/sdmmc/sdmmcchip.h new file mode 100644 index 00000000000..2ac3881dd32 --- /dev/null +++ b/sys/dev/sdmmc/sdmmcchip.h @@ -0,0 +1,78 @@ +/* $OpenBSD: sdmmcchip.h,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +#ifndef _SDMMC_CHIP_H_ +#define _SDMMC_CHIP_H_ + +struct sdmmc_command; + +typedef struct sdmmc_chip_functions *sdmmc_chipset_tag_t; +typedef void *sdmmc_chipset_handle_t; + +struct sdmmc_chip_functions { + /* host controller reset */ + int (*host_reset)(sdmmc_chipset_handle_t); + /* host capabilities */ + u_int32_t (*host_ocr)(sdmmc_chipset_handle_t); + int (*host_maxblklen)(sdmmc_chipset_handle_t); + /* card detection */ + int (*card_detect)(sdmmc_chipset_handle_t); + /* bus power and clock frequency */ + int (*bus_power)(sdmmc_chipset_handle_t, u_int32_t); + int (*bus_clock)(sdmmc_chipset_handle_t, int); + /* command execution */ + int (*exec_command)(sdmmc_chipset_handle_t, + struct sdmmc_command *); +}; + +/* host controller reset */ +#define sdmmc_chip_host_reset(tag, handle) \ + ((tag)->host_reset((handle))) +/* host capabilities */ +#define sdmmc_chip_host_ocr(tag, handle) \ + ((tag)->host_ocr((handle))) +#define sdmmc_chip_host_maxblklen(tag, handle) \ + ((tag)->host_maxblklen((handle))) +/* card detection */ +#define sdmmc_chip_card_detect(tag, handle) \ + ((tag)->card_detect((handle))) +/* bus power and clock frequency */ +#define sdmmc_chip_bus_power(tag, handle, ocr) \ + ((tag)->bus_power((handle), (ocr))) +#define sdmmc_chip_bus_clock(tag, handle, freq) \ + ((tag)->bus_clock((handle), (freq))) +/* command execution */ +#define sdmmc_chip_exec_command(tag, handle, cmdp) \ + ((tag)->exec_command((handle), (cmdp))) + +/* clock frequencies for sdmmc_chip_bus_clock() */ +#define SDMMC_SDCLK_OFF 0 +#define SDMMC_SDCLK_400KHZ 400 +#define SDMMC_SDCLK_25MHZ 25000 + +struct sdmmcbus_attach_args { + const char *saa_busname; + sdmmc_chipset_tag_t sct; + sdmmc_chipset_handle_t sch; +}; + +/* host controller calls to sdmmc */ +int sdmmc_card_attach(struct device *); +void sdmmc_card_detach(struct device *, int); + +#endif diff --git a/sys/dev/sdmmc/sdmmcreg.h b/sys/dev/sdmmc/sdmmcreg.h new file mode 100644 index 00000000000..5f0f8c34a38 --- /dev/null +++ b/sys/dev/sdmmc/sdmmcreg.h @@ -0,0 +1,232 @@ +/* $OpenBSD: sdmmcreg.h,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +#ifndef _SDMMCREG_H_ +#define _SDMMCREG_H_ + +/* MMC commands */ /* response type */ +#define MMC_GO_IDLE_STATE 0 /* R0 */ +#define MMC_SEND_OP_COND 1 /* R3 */ +#define MMC_ALL_SEND_CID 2 /* R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* R1 */ +#define MMC_SELECT_CARD 7 /* R1 */ +#define MMC_SEND_CSD 9 /* R2 */ +#define MMC_SEND_STATUS 13 /* R1 */ +#define MMC_SET_BLOCKLEN 16 /* R1 */ +#define MMC_READ_BLOCK_SINGLE 17 /* R1 */ +#define MMC_READ_BLOCK_MULTIPLE 18 /* R1 */ +#define MMC_SET_BLOCK_COUNT 23 /* R1 */ +#define MMC_WRITE_BLOCK_SINGLE 24 /* R1 */ +#define MMC_WRITE_BLOCK_MULTIPLE 25 /* R1 */ +#define MMC_APP_CMD 55 /* R1 */ + +/* SD commands */ /* response type */ +#define SD_SEND_RELATIVE_ADDR 3 /* R6 */ +#define SD_IO_SEND_OP_COND 5 /* R4 */ +#define SD_IO_RESET 52 /* R0 */ + +/* SD application commands */ /* response type */ +#define SD_APP_SET_BUS_WIDTH 6 /* R1 */ +#define SD_APP_OP_COND 41 /* R3 */ + +/* OCR bits */ +#define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */ +#define MMC_OCR_3_5V_3_6V (1<<23) +#define MMC_OCR_3_4V_3_5V (1<<22) +#define MMC_OCR_3_3V_3_4V (1<<21) +#define MMC_OCR_3_2V_3_3V (1<<20) +#define MMC_OCR_3_1V_3_2V (1<<19) +#define MMC_OCR_3_0V_3_1V (1<<18) +#define MMC_OCR_2_9V_3_0V (1<<17) +#define MMC_OCR_2_8V_2_9V (1<<16) +#define MMC_OCR_2_7V_2_8V (1<<15) +#define MMC_OCR_2_6V_2_7V (1<<14) +#define MMC_OCR_2_5V_2_6V (1<<13) +#define MMC_OCR_2_4V_2_5V (1<<12) +#define MMC_OCR_2_3V_2_4V (1<<11) +#define MMC_OCR_2_2V_2_3V (1<<10) +#define MMC_OCR_2_1V_2_2V (1<<9) +#define MMC_OCR_2_0V_2_1V (1<<8) +#define MMC_OCR_1_9V_2_0V (1<<7) +#define MMC_OCR_1_8V_1_9V (1<<6) +#define MMC_OCR_1_7V_1_8V (1<<5) +#define MMC_OCR_1_6V_1_7V (1<<4) + +/* R1 response type bits */ +#define MMC_R1_READY_FOR_DATA (1<<8) /* ready for next transfer */ +#define MMC_R1_APP_CMD (1<<5) /* app. commands supported */ + +/* RCA argument and response */ +#define MMC_ARG_RCA(rca) ((rca) << 16) +#define SD_R6_RCA(resp) ((resp)[0] >> 16) + +/* bus width argument */ +#define SD_ARG_BUS_WIDTH_1 0 +#define SD_ARG_BUS_WIDTH_4 2 + +/* 48-bit response decoding (32 bits w/o CRC) */ +#define MMC_R1(resp) ((resp)[0]) +#define MMC_R3(resp) ((resp)[0]) +#define MMC_R4(resp) ((resp)[0]) + +/* MMC R2 response (CSD) */ +#define MMC_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2) +#define MMC_CSD_CSDVER_1_0 1 +#define MMC_CSD_CSDVER_2_0 2 +#define MMC_CSD_MMCVER(resp) MMC_RSP_BITS((resp), 122, 4) +#define MMC_CSD_MMCVER_1_0 0 /* MMC 1.0 - 1.2 */ +#define MMC_CSD_MMCVER_1_4 1 /* MMC 1.4 */ +#define MMC_CSD_MMCVER_2_0 2 /* MMC 2.0 - 2.2 */ +#define MMC_CSD_MMCVER_3_1 3 /* MMC 3.1 - 3.3 */ +#define MMC_CSD_MMCVER_4_0 4 /* MMC 4 */ +#define MMC_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4) +#define MMC_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12) +#define MMC_CSD_CAPACITY(resp) ((MMC_CSD_C_SIZE((resp))+1) << \ + (MMC_CSD_C_SIZE_MULT((resp))+2)) +#define MMC_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3) + +/* MMC v1 R2 response (CID) */ +#define MMC_CID_MID_V1(resp) MMC_RSP_BITS((resp), 104, 24) +#define MMC_CID_PNM_V1_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \ + (pnm)[6] = MMC_RSP_BITS((resp), 48, 8); \ + (pnm)[7] = '\0'; \ + } while (0) +#define MMC_CID_REV_V1(resp) MMC_RSP_BITS((resp), 40, 8) +#define MMC_CID_PSN_V1(resp) MMC_RSP_BITS((resp), 16, 24) +#define MMC_CID_MDT_V1(resp) MMC_RSP_BITS((resp), 8, 8) + +/* MMC v2 R2 response (CID) */ +#define MMC_CID_MID_V2(resp) MMC_RSP_BITS((resp), 120, 8) +#define MMC_CID_OID_V2(resp) MMC_RSP_BITS((resp), 104, 16) +#define MMC_CID_PNM_V2_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \ + (pnm)[6] = '\0'; \ + } while (0) +#define MMC_CID_PSN_V2(resp) MMC_RSP_BITS((resp), 16, 32) + +/* SD R2 response (CSD) */ +#define SD_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2) +#define SD_CSD_CSDVER_1_0 0 +#define SD_CSD_TAAC(resp) MMC_RSP_BITS((resp), 112, 8) +#define SD_CSD_TAAC_1_5_MSEC 0x26 +#define SD_CSD_NSAC(resp) MMC_RSP_BITS((resp), 104, 8) +#define SD_CSD_SPEED(resp) MMC_RSP_BITS((resp), 96, 8) +#define SD_CSD_SPEED_25_MHZ 0x32 +#define SD_CSD_SPEED_50_MHZ 0x5a +#define SD_CSD_CCC(resp) MMC_RSP_BITS((resp), 84, 12) +#define SD_CSD_CCC_ALL 0x5f5 +#define SD_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4) +#define SD_CSD_READ_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 79, 1) +#define SD_CSD_WRITE_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 78, 1) +#define SD_CSD_READ_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 77, 1) +#define SD_CSD_DSR_IMP(resp) MMC_RSP_BITS((resp), 76, 1) +#define SD_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12) +#define SD_CSD_CAPACITY(resp) ((SD_CSD_C_SIZE((resp))+1) << \ + (SD_CSD_C_SIZE_MULT((resp))+2)) +#define SD_CSD_VDD_R_CURR_MIN(resp) MMC_RSP_BITS((resp), 59, 3) +#define SD_CSD_VDD_R_CURR_MAX(resp) MMC_RSP_BITS((resp), 56, 3) +#define SD_CSD_VDD_W_CURR_MIN(resp) MMC_RSP_BITS((resp), 53, 3) +#define SD_CSD_VDD_W_CURR_MAX(resp) MMC_RSP_BITS((resp), 50, 3) +#define SD_CSD_VDD_RW_CURR_100mA 0x7 +#define SD_CSD_VDD_RW_CURR_80mA 0x6 +#define SD_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3) +#define SD_CSD_ERASE_BLK_EN(resp) MMC_RSP_BITS((resp), 46, 1) +#define SD_CSD_SECTOR_SIZE(resp) MMC_RSP_BITS((resp), 39, 7) /* +1 */ +#define SD_CSD_WP_GRP_SIZE(resp) MMC_RSP_BITS((resp), 32, 7) /* +1 */ +#define SD_CSD_WP_GRP_ENABLE(resp) MMC_RSP_BITS((resp), 31, 1) +#define SD_CSD_R2W_FACTOR(resp) MMC_RSP_BITS((resp), 26, 3) +#define SD_CSD_WRITE_BL_LEN(resp) MMC_RSP_BITS((resp), 22, 4) +#define SD_CSD_RW_BL_LEN_2G 0xa +#define SD_CSD_RW_BL_LEN_1G 0x9 +#define SD_CSD_WRITE_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 21, 1) +#define SD_CSD_FILE_FORMAT_GRP(resp) MMC_RSP_BITS((resp), 15, 1) +#define SD_CSD_COPY(resp) MMC_RSP_BITS((resp), 14, 1) +#define SD_CSD_PERM_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 13, 1) +#define SD_CSD_TMP_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 12, 1) +#define SD_CSD_FILE_FORMAT(resp) MMC_RSP_BITS((resp), 10, 2) + +/* SD R2 response (CID) */ +#define SD_CID_MID(resp) MMC_RSP_BITS((resp), 120, 8) +#define SD_CID_OID(resp) MMC_RSP_BITS((resp), 104, 16) +#define SD_CID_PNM_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = '\0'; \ + } while (0) +#define SD_CID_REV(resp) MMC_RSP_BITS((resp), 56, 8) +#define SD_CID_PSN(resp) MMC_RSP_BITS((resp), 24, 32) +#define SD_CID_MDT(resp) MMC_RSP_BITS((resp), 8, 12) + +/* SD R4 response (IO OCR) */ +#define SD_IO_OCR_MEM_READY (1<<31) /* all memory cards ready */ +#define SD_IO_OCR_MP /* memory present flag */ +#define SD_IO_OCR_NF_SHIFT 28 /* number of functions */ +#define SD_IO_OCR_NF_MASK 0x3 /* 7 functions max. */ +#define SD_IO_OCR_NF(ocr) (((ocr) >> SD_IO_OCR_NF_SHIFT) & \ + SD_IO_OCR_NF_MASK) + +/* XXX slow but should work on big and little endian systems. */ +#define MMC_RSP_BITS(resp, start, len) __bitfield((resp), (start)-8, (len)) +static __inline int +__bitfield(u_int32_t *src, int start, int len) +{ + u_int8_t *sp; + u_int32_t dst, mask; + int shift, bs, bc; + + if (start < 0 || len < 0 || len > 32) + return 0; + + dst = 0; + mask = len % 32 ? UINT_MAX >> (32 - (len % 32)) : UINT_MAX; + shift = 0; + + while (len > 0) { + sp = (u_int8_t *)src + start / 8; + bs = start % 8; + bc = 8 - bs; + if (bc > len) + bc = len; + dst |= (*sp++ >> bs) << shift; + shift += bc; + start += bc; + len -= bc; + } + + dst &= mask; + return (int)dst; +} + +#endif diff --git a/sys/dev/sdmmc/sdmmcvar.h b/sys/dev/sdmmc/sdmmcvar.h new file mode 100644 index 00000000000..16ea091b88e --- /dev/null +++ b/sys/dev/sdmmc/sdmmcvar.h @@ -0,0 +1,127 @@ +/* $OpenBSD: sdmmcvar.h,v 1.1 2006/05/28 17:21:14 uwe Exp $ */ + +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * + * 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. + */ + +#ifndef _SDMMCVAR_H_ +#define _SDMMCVAR_H_ + +#include <sys/queue.h> +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmcreg.h> + +struct sdmmc_csd { + int csdver; /* CSD structure format */ + int mmcver; /* MMC version (for CID format) */ + int capacity; /* total number of sectors */ + int sector_size; /* sector size in bytes */ + int read_bl_len; /* block length for reads */ + /* ... */ +}; + +struct sdmmc_cid { + int mid; /* manufacturer identification number */ + int oid; /* OEM/product identification number */ + char pnm[8]; /* product name (MMC v1 has the longest) */ + int rev; /* product revision */ + int psn; /* product serial number */ + int mdt; /* manufacturing date */ +}; + +struct sdmmc_command; + +typedef u_int32_t sdmmc_response[4]; +typedef void (*sdmmc_callback)(struct device *, struct sdmmc_command *); + +struct sdmmc_command { + u_int16_t c_opcode; /* SD or MMC command index */ + u_int32_t c_arg; /* SD/MMC command argument */ + sdmmc_response c_resp; /* response buffer */ + void *c_data; /* buffer to send or read into */ + int c_datalen; /* length of data buffer */ + int c_blklen; /* block length */ + int c_flags; /* see below */ +#define SCF_DONE 0x0001 /* command is finished */ +#define SCF_BUF_READY 0x0002 /* buffer ready int occurred */ +#define SCF_CMD_DONE 0x0004 /* cmd complete int occurred */ +#define SCF_XFR_DONE 0x0008 /* transfer complete int occurred */ +#define SCF_CMD_AC 0x0000 +#define SCF_CMD_ADTC 0x0010 +#define SCF_CMD_BC 0x0020 +#define SCF_CMD_BCR 0x0030 +#define SCF_CMD_READ 0x0040 /* read command (data expected) */ +#define SCF_RSP_BSY 0x0100 +#define SCF_RSP_136 0x0200 +#define SCF_RSP_CRC 0x0400 +#define SCF_RSP_IDX 0x0800 +#define SCF_RSP_PRESENT 0x1000 +/* response types */ +#define SCF_RSP_R0 0 /* none */ +#define SCF_RSP_R1 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +#define SCF_RSP_R2 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_136) +#define SCF_RSP_R3 (SCF_RSP_PRESENT) +#define SCF_RSP_R4 (SCF_RSP_PRESENT) +#define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC) + sdmmc_callback c_done; /* callback function */ + int c_error; /* errno value on completion */ +}; + +struct sdmmc_card { + u_int16_t rca; + int flags; +#define SDMMCF_CARD_ERROR 0x0010 /* card in error state */ + sdmmc_response raw_cid; + struct sdmmc_cid cid; + struct sdmmc_csd csd; + SIMPLEQ_ENTRY(sdmmc_card) cs_list; +}; + +struct sdmmc_softc { + struct device sc_dev; /* base device */ +#define SDMMCDEVNAME(sc) ((sc)->sc_dev.dv_xname) + sdmmc_chipset_tag_t sct; /* host controller chipset tag */ + sdmmc_chipset_handle_t sch; /* host controller chipset handle */ + int sc_flags; +#define SMF_SD_MODE 0x0001 /* host in SD mode (MMC otherwise) */ +#define SMF_IO_MODE 0x0002 /* host in I/O mode (SD only) */ +#define SMF_MEM_MODE 0x0004 /* host in memory mode (SD or MMC) */ + SIMPLEQ_HEAD(, sdmmc_card) cs_head; + struct sdmmc_card *sc_card; /* selected card */ + void *sc_scsibus; /* SCSI bus emulation softc */ +}; + +#define IPL_SDMMC IPL_BIO +#define splsdmmc() splbio() + +void sdmmc_delay(u_int); +int sdmmc_set_bus_power(struct sdmmc_softc *, u_int32_t, u_int32_t); +int sdmmc_mmc_command(struct sdmmc_softc *, struct sdmmc_command *); +int sdmmc_app_command(struct sdmmc_softc *, struct sdmmc_command *); +void sdmmc_go_idle_state(struct sdmmc_softc *); +int sdmmc_select_card(struct sdmmc_softc *, struct sdmmc_card *); + +int sdmmc_io_enable(struct sdmmc_softc *); +void sdmmc_io_reset(struct sdmmc_softc *); +int sdmmc_io_send_op_cond(struct sdmmc_softc *, u_int32_t, u_int32_t *); + +int sdmmc_mem_enable(struct sdmmc_softc *); +int sdmmc_mem_init(struct sdmmc_softc *, struct sdmmc_card *); +int sdmmc_mem_read_block(struct sdmmc_softc *, struct sdmmc_card *, + int, u_char *, size_t); +int sdmmc_mem_write_block(struct sdmmc_softc *, struct sdmmc_card *, + int, u_char *, size_t); + +#endif |