diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2016-05-01 16:04:40 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2016-05-01 16:04:40 +0000 |
commit | 6bd1ebc4f6372213e2d48e527b38f1d9c3c14f0e (patch) | |
tree | 80ea831bd0078578a4716851e31069b80e26e830 /sys/dev/sdmmc | |
parent | 9350001d745387d778c568a6f38cc76cb9f0beb8 (diff) |
Add support for changing the bus width to the sdmmc subsystem and the sdhc(4)
controller. Use this to switch SD cards to a 4-bit bus if they support it.
ok deraadt@, jsg@
Diffstat (limited to 'sys/dev/sdmmc')
-rw-r--r-- | sys/dev/sdmmc/sdhc.c | 35 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc.c | 34 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmc_mem.c | 123 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcchip.h | 5 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcreg.h | 26 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcvar.h | 8 |
6 files changed, 192 insertions, 39 deletions
diff --git a/sys/dev/sdmmc/sdhc.c b/sys/dev/sdmmc/sdhc.c index a85044b8475..dc0350a729b 100644 --- a/sys/dev/sdmmc/sdhc.c +++ b/sys/dev/sdmmc/sdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sdhc.c,v 1.45 2016/04/30 13:33:35 kettenis Exp $ */ +/* $OpenBSD: sdhc.c,v 1.46 2016/05/01 16:04:39 kettenis Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -87,6 +87,7 @@ 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_bus_width(sdmmc_chipset_handle_t, int); void sdhc_card_intr_mask(sdmmc_chipset_handle_t, int); void sdhc_card_intr_ack(sdmmc_chipset_handle_t); void sdhc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *); @@ -117,6 +118,7 @@ struct sdmmc_chip_functions sdhc_functions = { /* bus power and clock frequency */ sdhc_bus_power, sdhc_bus_clock, + sdhc_bus_width, /* command execution */ sdhc_exec_command, /* card interrupt */ @@ -289,6 +291,7 @@ sdhc_host_found(struct sdhc_softc *sc, bus_space_tag_t iot, saa.saa_busname = "sdmmc"; saa.sct = &sdhc_functions; saa.sch = hp; + saa.caps = SMC_CAPS_4BIT_MODE; saa.dmat = sc->sc_dmat; if (ISSET(hp->flags, SHF_USE_DMA)) saa.caps |= SMC_CAPS_DMA; @@ -599,6 +602,36 @@ ret: return error; } +int +sdhc_bus_width(sdmmc_chipset_handle_t sch, int width) +{ + struct sdhc_host *hp = (struct sdhc_host *)sch; + int reg; + int s; + + if (width != 1 && width != 4 && width != 8) + return 1; + + s = splsdmmc(); + + reg = HREAD1(hp, SDHC_HOST_CTL); + reg &= ~SDHC_4BIT_MODE; + if (SDHC_SPEC_VERSION(hp->version) >= SDHC_SPEC_V3) { + reg &= ~SDHC_8BIT_MODE; + } + if (width == 4) { + reg |= SDHC_4BIT_MODE; + } else if (width == 8) { + KASSERT(SDHC_SPEC_VERSION(hp->version) >= SDHC_SPEC_V3); + reg |= SDHC_8BIT_MODE; + } + HWRITE1(hp, SDHC_HOST_CTL, reg); + + splx(s); + + return 0; +} + void sdhc_card_intr_mask(sdmmc_chipset_handle_t sch, int enable) { diff --git a/sys/dev/sdmmc/sdmmc.c b/sys/dev/sdmmc/sdmmc.c index 4ba2b65e1e3..72b793e1d7a 100644 --- a/sys/dev/sdmmc/sdmmc.c +++ b/sys/dev/sdmmc/sdmmc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sdmmc.c,v 1.40 2016/04/30 11:32:23 kettenis Exp $ */ +/* $OpenBSD: sdmmc.c,v 1.41 2016/05/01 16:04:39 kettenis Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -60,7 +60,6 @@ int sdmmc_enable(struct sdmmc_softc *); void sdmmc_disable(struct sdmmc_softc *); int sdmmc_scan(struct sdmmc_softc *); int sdmmc_init(struct sdmmc_softc *); -int sdmmc_set_bus_width(struct sdmmc_function *); #ifdef SDMMC_IOCTL int sdmmc_ioctl(struct device *, u_long, caddr_t); #endif @@ -700,37 +699,6 @@ sdmmc_set_relative_addr(struct sdmmc_softc *sc, return 0; } -/* - * Switch card and host to the maximum supported bus width. - */ -int -sdmmc_set_bus_width(struct sdmmc_function *sf) -{ - struct sdmmc_softc *sc = sf->sc; - struct sdmmc_command cmd; - int error; - - rw_enter_write(&sc->sc_lock); - - if (!ISSET(sc->sc_flags, SMF_SD_MODE)) { - rw_exit(&sc->sc_lock); - return EOPNOTSUPP; - } - - if ((error = sdmmc_select_card(sc, sf)) != 0) { - rw_exit(&sc->sc_lock); - 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; - error = sdmmc_app_command(sc, &cmd); - rw_exit(&sc->sc_lock); - return error; -} - int sdmmc_select_card(struct sdmmc_softc *sc, struct sdmmc_function *sf) { diff --git a/sys/dev/sdmmc/sdmmc_mem.c b/sys/dev/sdmmc/sdmmc_mem.c index 188d3392d5b..9344e3b8ec8 100644 --- a/sys/dev/sdmmc/sdmmc_mem.c +++ b/sys/dev/sdmmc/sdmmc_mem.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sdmmc_mem.c,v 1.23 2016/04/30 11:32:23 kettenis Exp $ */ +/* $OpenBSD: sdmmc_mem.c,v 1.24 2016/05/01 16:04:39 kettenis Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -37,7 +37,12 @@ void sdmmc_print_cid(struct sdmmc_cid *); 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_function *); +int sdmmc_mem_send_scr(struct sdmmc_softc *, uint32_t *); +int sdmmc_mem_decode_scr(struct sdmmc_softc *, uint32_t *, + struct sdmmc_function *); + int sdmmc_mem_send_cxd_data(struct sdmmc_softc *, int, void *, size_t); +int sdmmc_set_bus_width(struct sdmmc_function *, int); int sdmmc_mem_mmc_switch(struct sdmmc_function *, uint8_t, uint8_t, uint8_t); int sdmmc_mem_sd_init(struct sdmmc_softc *, struct sdmmc_function *); @@ -339,6 +344,70 @@ sdmmc_print_cid(struct sdmmc_cid *cid) #endif int +sdmmc_mem_send_scr(struct sdmmc_softc *sc, uint32_t *scr) +{ + struct sdmmc_command cmd; + void *ptr = NULL; + int datalen = 8; + int error = 0; + + ptr = malloc(datalen, M_DEVBUF, M_NOWAIT | M_ZERO); + if (ptr == NULL) + goto out; + + memset(&cmd, 0, sizeof(cmd)); + cmd.c_data = ptr; + cmd.c_datalen = datalen; + cmd.c_blklen = datalen; + cmd.c_arg = 0; + cmd.c_flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1; + cmd.c_opcode = SD_APP_SEND_SCR; + + error = sdmmc_app_command(sc, &cmd); + if (error == 0) { + memcpy(scr, ptr, datalen); + } + +out: + if (ptr != NULL) + free(ptr, M_DEVBUF, datalen); + + return error; +} + +int +sdmmc_mem_decode_scr(struct sdmmc_softc *sc, uint32_t *raw_scr, + struct sdmmc_function *sf) +{ + sdmmc_response resp; + int ver; + + memset(resp, 0, sizeof(resp)); + /* + * Change the raw SCR to a response. + */ + resp[0] = be32toh(raw_scr[1]) >> 8; // LSW + resp[1] = be32toh(raw_scr[0]); // MSW + resp[0] |= (resp[1] & 0xff) << 24; + resp[1] >>= 8; + + ver = SCR_STRUCTURE(resp); + sf->scr.sd_spec = SCR_SD_SPEC(resp); + sf->scr.bus_width = SCR_SD_BUS_WIDTHS(resp); + + DPRINTF(("%s: %s: %08x%08x ver=%d, spec=%d, bus width=%d\n", + DEVNAME(sc), __func__, resp[1], resp[0], + ver, sf->scr.sd_spec, sf->scr.bus_width)); + + if (ver != 0) { + DPRINTF(("%s: unknown SCR structure version: %d\n", + DEVNAME(sc), ver)); + return EINVAL; + } + return 0; +} + +int sdmmc_mem_send_cxd_data(struct sdmmc_softc *sc, int opcode, void *data, size_t datalen) { @@ -376,6 +445,36 @@ out: } int +sdmmc_set_bus_width(struct sdmmc_function *sf, int width) +{ + struct sdmmc_softc *sc = sf->sc; + struct sdmmc_command cmd; + int error; + + memset(&cmd, 0, sizeof(cmd)); + cmd.c_opcode = SD_APP_SET_BUS_WIDTH; + cmd.c_flags = SCF_RSP_R1 | SCF_CMD_AC; + + switch (width) { + case 1: + cmd.c_arg = SD_ARG_BUS_WIDTH_1; + break; + + case 4: + cmd.c_arg = SD_ARG_BUS_WIDTH_4; + break; + + default: + return EINVAL; + } + + error = sdmmc_app_command(sc, &cmd); + if (error == 0) + error = sdmmc_chip_bus_width(sc->sct, sc->sch, width); + return error; +} + +int sdmmc_mem_mmc_switch(struct sdmmc_function *sf, uint8_t set, uint8_t index, uint8_t value) { @@ -416,7 +515,27 @@ sdmmc_mem_init(struct sdmmc_softc *sc, struct sdmmc_function *sf) int sdmmc_mem_sd_init(struct sdmmc_softc *sc, struct sdmmc_function *sf) { - /* XXX */ + uint32_t raw_scr[2]; + int error; + + error = sdmmc_mem_send_scr(sc, raw_scr); + if (error) { + printf("%s: SD_SEND_SCR send failed\n", DEVNAME(sc)); + return error; + } + error = sdmmc_mem_decode_scr(sc, raw_scr, sf); + if (error) + return error; + + if (ISSET(sc->sc_caps, SMC_CAPS_4BIT_MODE) && + ISSET(sf->scr.bus_width, SCR_SD_BUS_WIDTHS_4BIT)) { + DPRINTF(("%s: change bus width\n", DEVNAME(sc))); + error = sdmmc_set_bus_width(sf, 4); + if (error) { + printf("%s: can't change bus width\n", DEVNAME(sc)); + return error; + } + } return 0; } diff --git a/sys/dev/sdmmc/sdmmcchip.h b/sys/dev/sdmmc/sdmmcchip.h index 4b7eedd5296..20d5e3aa59b 100644 --- a/sys/dev/sdmmc/sdmmcchip.h +++ b/sys/dev/sdmmc/sdmmcchip.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sdmmcchip.h,v 1.6 2016/04/30 11:32:23 kettenis Exp $ */ +/* $OpenBSD: sdmmcchip.h,v 1.7 2016/05/01 16:04:39 kettenis Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -37,6 +37,7 @@ struct sdmmc_chip_functions { /* bus power and clock frequency */ int (*bus_power)(sdmmc_chipset_handle_t, u_int32_t); int (*bus_clock)(sdmmc_chipset_handle_t, int); + int (*bus_width)(sdmmc_chipset_handle_t, int); /* command execution */ void (*exec_command)(sdmmc_chipset_handle_t, struct sdmmc_command *); @@ -61,6 +62,8 @@ struct sdmmc_chip_functions { ((tag)->bus_power((handle), (ocr))) #define sdmmc_chip_bus_clock(tag, handle, freq) \ ((tag)->bus_clock((handle), (freq))) +#define sdmmc_chip_bus_width(tag, handle, width) \ + ((tag)->bus_width((handle), (width))) /* command execution */ #define sdmmc_chip_exec_command(tag, handle, cmdp) \ ((tag)->exec_command((handle), (cmdp))) diff --git a/sys/dev/sdmmc/sdmmcreg.h b/sys/dev/sdmmc/sdmmcreg.h index 57f61b125d9..7036217cc69 100644 --- a/sys/dev/sdmmc/sdmmcreg.h +++ b/sys/dev/sdmmc/sdmmcreg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sdmmcreg.h,v 1.8 2016/01/10 14:11:43 kettenis Exp $ */ +/* $OpenBSD: sdmmcreg.h,v 1.9 2016/05/01 16:04:39 kettenis Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -45,6 +45,7 @@ /* SD application commands */ /* response type */ #define SD_APP_SET_BUS_WIDTH 6 /* R1 */ #define SD_APP_OP_COND 41 /* R3 */ +#define SD_APP_SEND_SCR 51 /* R1 */ /* OCR bits */ #define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */ @@ -236,6 +237,29 @@ #define SD_CID_PSN(resp) MMC_RSP_BITS((resp), 24, 32) #define SD_CID_MDT(resp) MMC_RSP_BITS((resp), 8, 12) +/* SCR (SD Configuration Register) */ +#define SCR_STRUCTURE(scr) MMC_RSP_BITS((scr), 60, 4) +#define SCR_STRUCTURE_VER_1_0 0 /* Version 1.0 */ +#define SCR_SD_SPEC(scr) MMC_RSP_BITS((scr), 56, 4) +#define SCR_SD_SPEC_VER_1_0 0 /* Version 1.0 and 1.01 */ +#define SCR_SD_SPEC_VER_1_10 1 /* Version 1.10 */ +#define SCR_SD_SPEC_VER_2 2 /* Version 2.00 or Version 3.0X */ +#define SCR_DATA_STAT_AFTER_ERASE(scr) MMC_RSP_BITS((scr), 55, 1) +#define SCR_SD_SECURITY(scr) MMC_RSP_BITS((scr), 52, 3) +#define SCR_SD_SECURITY_NONE 0 /* no security */ +#define SCR_SD_SECURITY_1_0 1 /* security protocol 1.0 */ +#define SCR_SD_SECURITY_1_0_2 2 /* security protocol 1.0 */ +#define SCR_SD_BUS_WIDTHS(scr) MMC_RSP_BITS((scr), 48, 4) +#define SCR_SD_BUS_WIDTHS_1BIT (1 << 0) /* 1bit (DAT0) */ +#define SCR_SD_BUS_WIDTHS_4BIT (1 << 2) /* 4bit (DAT0-3) */ +#define SCR_SD_SPEC3(scr) MMC_RSP_BITS((scr), 47, 1) +#define SCR_EX_SECURITY(scr) MMC_RSP_BITS((scr), 43, 4) +#define SCR_SD_SPEC4(scr) MMC_RSP_BITS((scr), 42, 1) +#define SCR_RESERVED(scr) MMC_RSP_BITS((scr), 34, 8) +#define SCR_CMD_SUPPORT_CMD23(scr) MMC_RSP_BITS((scr), 33, 1) +#define SCR_CMD_SUPPORT_CMD20(scr) MMC_RSP_BITS((scr), 32, 1) +#define SCR_RESERVED2(scr) MMC_RSP_BITS((scr), 0, 32) + /* Might be slow, but it should work on big and little endian systems. */ #define MMC_RSP_BITS(resp, start, len) __bitfield((resp), (start)-8, (len)) static __inline int diff --git a/sys/dev/sdmmc/sdmmcvar.h b/sys/dev/sdmmc/sdmmcvar.h index d2d929ed6da..e2867aa23f2 100644 --- a/sys/dev/sdmmc/sdmmcvar.h +++ b/sys/dev/sdmmc/sdmmcvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sdmmcvar.h,v 1.23 2016/04/30 11:32:23 kettenis Exp $ */ +/* $OpenBSD: sdmmcvar.h,v 1.24 2016/05/01 16:04:39 kettenis Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -48,6 +48,11 @@ struct sdmmc_cid { int mdt; /* manufacturing date */ }; +struct sdmmc_scr { + int sd_spec; + int bus_width; +}; + typedef u_int32_t sdmmc_response[4]; struct sdmmc_softc; @@ -149,6 +154,7 @@ struct sdmmc_function { struct sdmmc_csd csd; /* decoded CSD value */ struct sdmmc_cid cid; /* decoded CID value */ sdmmc_response raw_cid; /* temp. storage for decoding */ + struct sdmmc_scr scr; /* decoded SCR value */ }; /* |