/*- * Copyright (c) 2012-2025 Patrick Kelsey. All rights reserved. * Copyright (c) 2025 Ruslan Bukin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Portions of this software may have been developed with reference to * the SD Simplified Specification. The following disclaimer may apply: * * The following conditions apply to the release of the simplified * specification ("Simplified Specification") by the SD Card Association and * the SD Group. The Simplified Specification is a subset of the complete SD * Specification which is owned by the SD Card Association and the SD * Group. This Simplified Specification is provided on a non-confidential * basis subject to the disclaimers below. Any implementation of the * Simplified Specification may require a license from the SD Card * Association, SD Group, SD-3C LLC or other third parties. * * Disclaimers: * * The information contained in the Simplified Specification is presented only * as a standard specification for SD Cards and SD Host/Ancillary products and * is provided "AS-IS" without any representations or warranties of any * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD * Card Association for any damages, any infringements of patents or other * right of the SD Group, SD-3C LLC, the SD Card Association or any third * parties, which may result from its use. No license is granted by * implication, estoppel or otherwise under any patent or other rights of the * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing * herein shall be construed as an obligation by the SD Group, the SD-3C LLC * or the SD Card Association to disclose or distribute any technical * information, know-how or other confidential information to any third party. * * * CRC routines adapted from public domain code written by Lammert Bies. * * * This is an implementation of mmcbr that communicates with SD/MMC cards in * SPI mode via spibus_if. In order to minimize changes to the existing * MMC/SD stack (and allow for maximal reuse of the same), the behavior of * the SD-bus command set is emulated as much as possible, where required. * * The SPI bus ownership behavior is to acquire the SPI bus for the entire * duration that the MMC host is acquired. * * CRC checking is enabled by default, but can be disabled at runtime * per-card via sysctl (e.g. sysctl dev.mmcspi.0.use_crc=0). * * Considered, but not implemented: * - Card presence detection * - Card power control * - Detection of lock switch state on cards that have them * - Yielding the CPU during long card busy cycles * * Originally developed and tested using a MicroTik RouterBOARD RB450G and * 31 microSD cards available circa 2012. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mmcbr_if.h" #include "spibus_if.h" #define MMCSPI_RETRIES 3 #define MMCSPI_TIMEOUT_SEC 3 #define MMCSPI_MAX_RSP_LEN 5 /* max length of an Rn response */ #define MMCSPI_OCR_LEN 4 #define MMCSPI_DATA_BLOCK_LEN 512 #define MMCSPI_DATA_CRC_LEN 2 #define MMCSPI_POLL_LEN 8 /* amount to read when searching */ #define MMCSPI_R1_MASK 0x80 /* mask used to search for R1 tokens */ #define MMCSPI_R1_VALUE 0x00 /* value used to search for R1 tokens */ #define MMCSPI_DR_MASK 0x11 /* mask used to search for data resp tokens */ #define MMCSPI_DR_VALUE 0x01 /* value used to search for data resp tokens */ #define MMCSPI_DR_ERR_MASK 0x0e #define MMCSPI_DR_ERR_NONE 0x04 #define MMCSPI_DR_ERR_CRC 0x0a #define MMCSPI_DR_ERR_WRITE 0x0c #define MMCSPI_TOKEN_SB 0xfe /* start block token for read single, read multi, and write single */ #define MMCSPI_TOKEN_SB_WM 0xfc /* start block token for write multi */ #define MMCSPI_TOKEN_ST 0xfd /* stop transmission token */ #define MMCSPI_IS_DE_TOKEN(x) (0 == ((x) & 0xf0)) /* detector for data error token */ #define MMCSPI_R1_IDLE 0x01 #define MMCSPI_R1_ERASE_RST 0x02 #define MMCSPI_R1_ILL_CMD 0x04 #define MMCSPI_R1_CRC_ERR 0x08 #define MMCSPI_R1_ERASE_ERR 0x10 #define MMCSPI_R1_ADDR_ERR 0x20 #define MMCSPI_R1_PARAM_ERR 0x40 #define MMCSPI_R1_ERR_MASK (MMCSPI_R1_PARAM_ERR | MMCSPI_R1_ADDR_ERR | \ MMCSPI_R1_ERASE_ERR | MMCSPI_R1_CRC_ERR | \ MMCSPI_R1_ILL_CMD) #define MMCSPI_R2_LOCKED 0x01 #define MMCSPI_R2_WP_ER_LCK 0x02 #define MMCSPI_R2_ERR 0x04 #define MMCSPI_R2_CC_ERR 0x08 #define MMCSPI_R2_ECC_FAIL 0x10 #define MMCSPI_R2_WP_VIOLATE 0x20 #define MMCSPI_R2_ERASE_PARAM 0x40 #define MMCSPI_R2_OOR_CSD_OW 0x80 /* commands that only apply to the SPI interface */ #define MMCSPI_READ_OCR 58 #define MMCSPI_CRC_ON_OFF 59 static struct ofw_compat_data compat_data[] = { { "mmc-spi-slot", 1 }, { NULL, 0 } }; struct mmcspi_command { struct mmc_command *mmc_cmd; /* command passed from mmc layer */ uint32_t opcode; /* possibly translated opcode */ uint32_t arg; /* possibly translated arg */ uint32_t flags; /* possibly translated flags */ uint32_t retries; /* possibly translated retry count */ struct mmc_data *data; /* possibly redirected data segment */ unsigned int error_mask; /* R1 errors check mask */ unsigned char use_crc; /* do crc checking for this command */ unsigned char rsp_type; /* SPI response type of this command */ #define MMCSPI_RSP_R1 0 #define MMCSPI_RSP_R1B 1 #define MMCSPI_RSP_R2 2 #define MMCSPI_RSP_R3 3 #define MMCSPI_RSP_R7 4 unsigned char rsp_len; /* response len of this command */ unsigned char mmc_rsp_type; /* MMC reponse type to translate to */ #define MMCSPI_TO_MMC_RSP_NONE 0 #define MMCSPI_TO_MMC_RSP_R1 1 #define MMCSPI_TO_MMC_RSP_R1B 2 #define MMCSPI_TO_MMC_RSP_R2 3 #define MMCSPI_TO_MMC_RSP_R3 4 #define MMCSPI_TO_MMC_RSP_R6 5 #define MMCSPI_TO_MMC_RSP_R7 6 struct mmc_data ldata; /* local read data */ }; struct mmcspi_slot { struct mmcspi_softc *sc; /* back pointer to parent bridge */ device_t dev; /* mmc device for slot */ boolean_t bus_busy; /* host has been acquired */ struct mmc_host host; /* host parameters */ struct mtx mtx; /* slot mutex */ uint8_t last_ocr[MMCSPI_OCR_LEN]; /* ocr retrieved after CMD8 */ uint32_t last_opcode; /* last opcode requested by mmc layer */ uint32_t last_flags; /* last flags requested by mmc layer */ unsigned int crc_enabled; /* crc checking is enabled */ unsigned int crc_init_done; /* whether the initial crc setting has been sent to the card */ #define MMCSPI_MAX_LDATA_LEN 16 uint8_t ldata_buf[MMCSPI_MAX_LDATA_LEN]; }; struct mmcspi_softc { device_t dev; /* this mmc bridge device */ device_t busdev; struct mmcspi_slot slot; unsigned int use_crc; /* command CRC checking */ }; #if defined(MMCSPI_ENABLE_DEBUG_FUNCS) static void mmcspi_dump_data(device_t dev, const char *label, uint8_t *data, unsigned int len); static void mmcspi_dump_spi_bus(device_t dev, unsigned int len); #endif #define MMCSPI_LOCK_SLOT(_slot) mtx_lock(&(_slot)->mtx) #define MMCSPI_UNLOCK_SLOT(_slot) mtx_unlock(&(_slot)->mtx) #define MMCSPI_SLOT_LOCK_INIT(_slot) mtx_init(&(_slot)->mtx, \ "SD slot mtx", "mmcspi", MTX_DEF) #define MMCSPI_SLOT_LOCK_DESTROY(_slot) mtx_destroy(&(_slot)->mtx); #define MMCSPI_ASSERT_SLOT_LOCKED(_slot) mtx_assert(&(_slot)->mtx, \ MA_OWNED); #define MMCSPI_ASSERT_SLOT_UNLOCKED(_slot) mtx_assert(&(_slot)->mtx, \ MA_NOTOWNED); #define TRACE_ZONE_ENABLED(zone) (trace_zone_mask & TRACE_ZONE_##zone) #define TRACE_ENTER(dev) \ if (TRACE_ZONE_ENABLED(ENTER)) { \ device_printf(dev, "%s: enter\n", __func__); \ } #define TRACE_EXIT(dev) \ if (TRACE_ZONE_ENABLED(EXIT)) { \ device_printf(dev, "%s: exit\n", __func__); \ } #define TRACE(dev, zone, ...) \ if (TRACE_ZONE_ENABLED(zone)) { \ device_printf(dev, __VA_ARGS__); \ } #define TRACE_ZONE_ENTER (1ul << 0) /* function entrance */ #define TRACE_ZONE_EXIT (1ul << 1) /* function exit */ #define TRACE_ZONE_ACTION (1ul << 2) /* for narrating major actions taken */ #define TRACE_ZONE_RESULT (1ul << 3) /* for narrating results of actions */ #define TRACE_ZONE_ERROR (1ul << 4) /* for reporting errors */ #define TRACE_ZONE_DATA (1ul << 5) /* for dumping bus data */ #define TRACE_ZONE_DETAILS (1ul << 6) /* for narrating minor actions/results */ #define TRACE_ZONE_NONE 0 #define TRACE_ZONE_ALL 0xffffffff #define CRC7_INITIAL 0x00 #define CRC16_INITIAL 0x0000 SYSCTL_NODE(_hw, OID_AUTO, mmcspi, CTLFLAG_RD, 0, "mmcspi driver"); static unsigned int trace_zone_mask = TRACE_ZONE_ERROR; static uint8_t crc7tab[256]; static uint16_t crc16tab[256]; static uint8_t onesbuf[MMCSPI_DATA_BLOCK_LEN]; /* for driving the tx line when receiving */ static uint8_t junkbuf[MMCSPI_DATA_BLOCK_LEN]; /* for receiving data when transmitting */ static uint8_t update_crc7(uint8_t crc, uint8_t *buf, unsigned int len) { uint8_t tmp; int i; for (i = 0; i < len; i++) { tmp = (crc << 1) ^ buf[i]; crc = crc7tab[tmp]; } return (crc); } static uint16_t update_crc16(uint16_t crc, uint8_t *buf, unsigned int len) { uint16_t tmp, c16; int i; for (i = 0; i < len; i++) { c16 = 0x00ff & (uint16_t)buf[i]; tmp = (crc >> 8) ^ c16; crc = (crc << 8) ^ crc16tab[tmp]; } return (crc); } static void init_crc7tab(void) { #define P_CRC7 0x89 int i, j; uint8_t crc, c; for (i = 0; i < 256; i++) { c = (uint8_t)i; crc = (c & 0x80) ? c ^ P_CRC7 : c; for (j=1; j<8; j++) { crc = crc << 1; if (crc & 0x80) crc = crc ^ P_CRC7; } crc7tab[i] = crc; } } static void init_crc16tab(void) { #define P_CCITT 0x1021 int i, j; uint16_t crc, c; for (i = 0; i < 256; i++) { crc = 0; c = ((uint16_t) i) << 8; for (j=0; j<8; j++) { if ((crc ^ c) & 0x8000) crc = ( crc << 1 ) ^ P_CCITT; else crc = crc << 1; c = c << 1; } crc16tab[i] = crc; } } static void mmcspi_slot_init(device_t brdev, struct mmcspi_slot *slot) { struct mmcspi_softc *sc; TRACE_ENTER(brdev); sc = device_get_softc(brdev); slot->sc = sc; slot->dev = NULL; /* will get real value when card is added */ slot->bus_busy = false; slot->host.f_min = 100000; /* this should be as low as we need to go for any card */ slot->host.caps = 0; /* SPI mode requires 3.3V operation */ slot->host.host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; MMCSPI_SLOT_LOCK_INIT(slot); TRACE_EXIT(brdev); } static void mmcspi_slot_fini(device_t brdev, struct mmcspi_slot *slot) { TRACE_ENTER(brdev); MMCSPI_SLOT_LOCK_DESTROY(slot); TRACE_EXIT(brdev); } static void mmcspi_card_add(struct mmcspi_slot *slot) { device_t brdev; device_t child; brdev = slot->sc->dev; TRACE_ENTER(brdev); child = device_add_child(brdev, "mmc", DEVICE_UNIT_ANY); MMCSPI_LOCK_SLOT(slot); slot->dev = child; device_set_ivars(slot->dev, slot); MMCSPI_UNLOCK_SLOT(slot); device_probe_and_attach(slot->dev); TRACE_EXIT(brdev); } static void mmcspi_card_delete(struct mmcspi_slot *slot) { device_t brdev; device_t dev; brdev = slot->sc->dev; TRACE_ENTER(brdev); MMCSPI_LOCK_SLOT(slot); dev = slot->dev; slot->dev = NULL; MMCSPI_UNLOCK_SLOT(slot); device_delete_child(brdev, dev); TRACE_EXIT(brdev); } static int mmcspi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "MMC SPI mode controller"); return (BUS_PROBE_DEFAULT); } static int mmcspi_attach(device_t dev) { struct mmcspi_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; struct sysctl_oid_list *child; TRACE_ENTER(dev); sc = device_get_softc(dev); ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); child = SYSCTL_CHILDREN(tree); sc->dev = dev; sc->busdev = device_get_parent(dev); sc->use_crc = 1; SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "use_crc", CTLFLAG_RW, &sc->use_crc, sizeof(sc->use_crc), "Enable/disable crc checking"); SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "trace_mask", CTLFLAG_RW, &trace_zone_mask, sizeof(trace_zone_mask), "Bitmask for adjusting " "trace messages"); mmcspi_slot_init(dev, &sc->slot); /* XXX trigger this from card insert detection */ mmcspi_card_add(&sc->slot); TRACE_EXIT(dev); return (0); } static int mmcspi_detach(device_t dev) { struct mmcspi_softc *sc; TRACE_ENTER(dev); sc = device_get_softc(dev); /* XXX trigger this from card removal detection */ mmcspi_card_delete(&sc->slot); mmcspi_slot_fini(dev, &sc->slot); TRACE_EXIT(dev); return (0); } static int mmcspi_suspend(device_t dev) { int err; TRACE_ENTER(dev); err = bus_generic_suspend(dev); if (err) { TRACE_EXIT(dev); return (err); } TRACE_EXIT(dev); return (0); } static int mmcspi_resume(device_t dev) { int err; TRACE_ENTER(dev); err = bus_generic_resume(dev); if (err) { TRACE_EXIT(dev); return (err); } TRACE_EXIT(dev); return (0); } static int mmcspi_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct mmcspi_slot *slot; TRACE_ENTER(bus); slot = device_get_ivars(child); switch (which) { case MMCBR_IVAR_BUS_TYPE: *result = bus_type_spi; break; case MMCBR_IVAR_BUS_MODE: *result = slot->host.ios.bus_mode; break; case MMCBR_IVAR_BUS_WIDTH: *result = slot->host.ios.bus_width; break; case MMCBR_IVAR_CHIP_SELECT: *result = slot->host.ios.chip_select; break; case MMCBR_IVAR_CLOCK: *result = slot->host.ios.clock; break; case MMCBR_IVAR_F_MIN: *result = slot->host.f_min; break; case MMCBR_IVAR_F_MAX: *result = slot->host.f_max; break; case MMCBR_IVAR_HOST_OCR: *result = slot->host.host_ocr; break; case MMCBR_IVAR_MODE: *result = slot->host.mode; break; case MMCBR_IVAR_OCR: *result = slot->host.ocr; break; case MMCBR_IVAR_POWER_MODE: *result = slot->host.ios.power_mode; break; case MMCBR_IVAR_VDD: *result = slot->host.ios.vdd; break; case MMCBR_IVAR_VCCQ: *result = slot->host.ios.vccq; break; case MMCBR_IVAR_CAPS: *result = slot->host.caps; break; case MMCBR_IVAR_TIMING: *result = slot->host.ios.timing; break; case MMCBR_IVAR_MAX_DATA: /* seems reasonable, not dictated by anything */ *result = 64 * 1024; break; default: return (EINVAL); } TRACE_EXIT(bus); return (0); } static int mmcspi_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct mmcspi_slot *slot; TRACE_ENTER(bus); slot = device_get_ivars(child); switch (which) { default: return (EINVAL); case MMCBR_IVAR_BUS_MODE: slot->host.ios.bus_mode = value; break; case MMCBR_IVAR_BUS_WIDTH: slot->host.ios.bus_width = value; break; case MMCBR_IVAR_CLOCK: slot->host.ios.clock = value; break; case MMCBR_IVAR_CHIP_SELECT: slot->host.ios.chip_select = value; break; case MMCBR_IVAR_MODE: slot->host.mode = value; break; case MMCBR_IVAR_OCR: slot->host.ocr = value; break; case MMCBR_IVAR_POWER_MODE: slot->host.ios.power_mode = value; break; case MMCBR_IVAR_VDD: slot->host.ios.vdd = value; break; case MMCBR_IVAR_VCCQ: slot->host.ios.vccq = value; break; case MMCBR_IVAR_TIMING: slot->host.ios.timing = value; break; case MMCBR_IVAR_BUS_TYPE: case MMCBR_IVAR_CAPS: case MMCBR_IVAR_HOST_OCR: case MMCBR_IVAR_F_MIN: case MMCBR_IVAR_F_MAX: case MMCBR_IVAR_MAX_DATA: return (EINVAL); } TRACE_EXIT(bus); return (0); } static unsigned int mmcspi_do_spi_read(device_t dev, uint8_t *data, unsigned int len) { struct spi_command spi_cmd; struct mmcspi_softc *sc; int err; TRACE_ENTER(dev); sc = device_get_softc(dev); spi_cmd.tx_cmd = onesbuf; spi_cmd.rx_cmd = data; spi_cmd.tx_cmd_sz = len; spi_cmd.rx_cmd_sz = len; spi_cmd.tx_data = NULL; spi_cmd.rx_data = NULL; spi_cmd.tx_data_sz = 0; spi_cmd.rx_data_sz = 0; err = SPIBUS_TRANSFER(sc->busdev, sc->dev, &spi_cmd); #ifdef DEBUG_RX int i; if (err == 0) { printf("rx val: "); for (i = 0; i < len; i++) printf("%x ", data[i]); printf("\n"); } #endif TRACE_EXIT(dev); return (err ? MMC_ERR_FAILED : MMC_ERR_NONE); } static unsigned int mmcspi_do_spi_write(device_t dev, uint8_t *cmd, unsigned int cmdlen, uint8_t *data, unsigned int datalen) { struct mmcspi_softc *sc; struct spi_command spi_cmd; int err; TRACE_ENTER(dev); sc = device_get_softc(dev); spi_cmd.tx_cmd = cmd; spi_cmd.rx_cmd = junkbuf; spi_cmd.tx_cmd_sz = cmdlen; spi_cmd.rx_cmd_sz = cmdlen; spi_cmd.tx_data = data; spi_cmd.rx_data = junkbuf; spi_cmd.tx_data_sz = datalen; spi_cmd.rx_data_sz = datalen; err = SPIBUS_TRANSFER(sc->busdev, sc->dev, &spi_cmd); TRACE_EXIT(dev); return (err ? MMC_ERR_FAILED : MMC_ERR_NONE); } static unsigned int mmcspi_wait_for_not_busy(device_t dev) { unsigned int busy_length; uint8_t pollbuf[MMCSPI_POLL_LEN]; struct bintime start, elapsed; unsigned int err; int i; busy_length = 0; TRACE_ENTER(dev); TRACE(dev, ACTION, "waiting for not busy\n"); getbintime(&start); do { TRACE(dev, DETAILS, "looking for end of busy\n"); err = mmcspi_do_spi_read(dev, pollbuf, MMCSPI_POLL_LEN); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "spi read failed\n"); TRACE_EXIT(dev); return (err); } for (i = 0; i < MMCSPI_POLL_LEN; i++) { if (pollbuf[i] != 0x00) { TRACE(dev, DETAILS, "end of busy found at %d\n", i); break; } busy_length++; } getbintime(&elapsed); bintime_sub(&elapsed, &start); if (elapsed.sec > MMCSPI_TIMEOUT_SEC) { TRACE(dev, ERROR, "card busy timeout\n"); return (MMC_ERR_TIMEOUT); } } while (MMCSPI_POLL_LEN == i); TRACE(dev, RESULT, "busy for %u byte slots\n", busy_length); TRACE_EXIT(dev); return (MMC_ERR_NONE); } static int mmcspi_update_ios(device_t brdev, device_t reqdev) { struct mmcspi_softc *sc; struct mmcspi_slot *slot; struct spi_command spi_cmd; TRACE_ENTER(brdev); sc = device_get_softc(brdev); slot = device_get_ivars(reqdev); if (power_up == slot->host.ios.power_mode) { /* * This sequence provides the initialization steps required * by the spec after card power is applied, but before any * commands are issued. These operations are harmless if * applied at any other time (after a warm reset, for * example). */ /* * XXX Power-on portion of implementation of card power * control should go here. Should probably include a power * off first to ensure card is fully reset from any previous * state. */ /* * Make sure power to card has ramped up. The spec requires * power to ramp up in 35ms or less. */ DELAY(35000); /* * Provide at least 74 clocks with CS and MOSI high that the * spec requires after card power stabilizes. */ spi_cmd.tx_cmd = onesbuf; spi_cmd.tx_cmd_sz = 10; spi_cmd.rx_cmd = junkbuf; spi_cmd.rx_cmd_sz = 10; spi_cmd.tx_data = NULL; spi_cmd.rx_data = NULL; spi_cmd.tx_data_sz = 0; spi_cmd.rx_data_sz = 0; SPIBUS_TRANSFER(sc->busdev, sc->dev, &spi_cmd); /* * Perhaps this was a warm reset and the card is in the * middle of a long operation. */ mmcspi_wait_for_not_busy(brdev); slot->last_opcode = 0xffffffff; slot->last_flags = 0; memset(slot->last_ocr, 0, MMCSPI_OCR_LEN); slot->crc_enabled = 0; slot->crc_init_done = 0; } if (power_off == slot->host.ios.power_mode) { /* * XXX Power-off portion of implementation of card power * control should go here. */ } TRACE_EXIT(brdev); return (0); } static unsigned int mmcspi_shift_copy(uint8_t *dest, uint8_t *src, unsigned int dest_len, unsigned int shift) { unsigned int i; if (0 == shift) memcpy(dest, src, dest_len); else { for (i = 0; i < dest_len; i++) { dest[i] = (src[i] << shift) | (src[i + 1] >> (8 - shift)); } } return (dest_len); } static unsigned int mmcspi_get_response_token(device_t dev, uint8_t mask, uint8_t value, unsigned int len, unsigned int has_busy, uint8_t *rspbuf) { uint8_t pollbuf[2 * MMCSPI_MAX_RSP_LEN]; struct bintime start, elapsed; boolean_t found; unsigned int err; unsigned int offset; unsigned int shift = 0; unsigned int remaining; uint16_t search_space; uint16_t search_mask; uint16_t search_value; int i; TRACE_ENTER(dev); /* * This loop searches data clocked out of the card for a response * token matching the given mask and value. It will locate tokens * that are not byte-aligned, as some cards send non-byte-aligned * response tokens in some situations. For example, the following * card consistently sends an unaligned response token to the stop * command used to terminate multi-block reads: * * Transcend 2GB SDSC card, cid: * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000 */ offset = 0; found = false; getbintime(&start); do { TRACE(dev, DETAILS, "looking for response token with " "mask 0x%02x, value 0x%02x\n", mask, value); err = mmcspi_do_spi_read(dev, &pollbuf[offset], len); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "spi read of resp token failed\n"); TRACE_EXIT(dev); return (err); } for (i = 0; i < len + offset; i++) { if ((pollbuf[i] & mask) == value) { TRACE(dev, DETAILS, "response token found at " "%d (0x%02x)\n", i, pollbuf[i]); shift = 0; found = true; break; } else if (i < len + offset - 1) { /* * Not the last byte in the buffer, so check * for a non-aligned response. */ search_space = ((uint16_t)pollbuf[i] << 8) | pollbuf[i + 1]; search_mask = (uint16_t)mask << 8; search_value = (uint16_t)value << 8; TRACE(dev, DETAILS, "search: space=0x%04x " " mask=0x%04x val=0x%04x\n", search_space, search_mask, search_value); for (shift = 1; shift < 8; shift++) { search_space <<= 1; if ((search_space & search_mask) == search_value) { found = true; TRACE(dev, DETAILS, "Found mat" "ch at shift %u\n", shift); break; } } if (shift < 8) break; } else { /* * Move the last byte to the first position * and go 'round again. */ pollbuf[0] = pollbuf[i]; } } if (!found) { offset = 1; getbintime(&elapsed); bintime_sub(&elapsed, &start); if (elapsed.sec > MMCSPI_TIMEOUT_SEC) { TRACE(dev, ERROR, "timeout while looking for " "response token\n"); return (MMC_ERR_TIMEOUT); } } } while (!found); /* * Note that if i == 0 and offset == 1, shift is always greater than * zero. */ remaining = i - offset + (shift ? 1 : 0); TRACE(dev, DETAILS, "len=%u i=%u rem=%u shift=%u\n", len, i, remaining, shift); if (remaining) { err = mmcspi_do_spi_read(dev, &pollbuf[len + offset], remaining); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "spi read of remainder of response " "token failed\n"); TRACE_EXIT(dev); return (err); } } mmcspi_shift_copy(rspbuf, &pollbuf[i], len, shift); if (TRACE_ZONE_ENABLED(RESULT)) { TRACE(dev, RESULT, "response ="); for (i = 0; i < len; i++) printf(" 0x%02x", rspbuf[i]); printf("\n"); } if (has_busy) { err = mmcspi_wait_for_not_busy(dev); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } } TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_set_up_command(device_t dev, struct mmcspi_command *mmcspi_cmd, struct mmc_command *mmc_cmd) { struct mmcspi_softc *sc; struct mmcspi_slot *slot; uint32_t opcode; uint32_t arg; uint32_t flags; uint32_t retries; unsigned char rsp_type; unsigned char rsp_len; unsigned char mmc_rsp_type; unsigned int ldata_len = 0; unsigned int use_crc; sc = device_get_softc(dev); slot = &sc->slot; use_crc = slot->crc_enabled; opcode = mmc_cmd->opcode; arg = mmc_cmd->arg; flags = mmc_cmd->flags; retries = mmc_cmd->retries; if (flags & MMC_CMD_IS_APP) { switch (opcode) { case ACMD_SD_STATUS: rsp_type = MMCSPI_RSP_R2; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R1; break; case ACMD_SEND_NUM_WR_BLOCKS: case ACMD_SET_WR_BLK_ERASE_COUNT: case ACMD_SET_CLR_CARD_DETECT: case ACMD_SEND_SCR: rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R1; break; case ACMD_SD_SEND_OP_COND: /* only HCS bit is valid in spi mode */ arg &= 0x40000000; rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R3; break; default: TRACE(dev, ERROR, "Invalid app command opcode %u\n", opcode); return (MMC_ERR_INVALID); } } else { switch (opcode) { case MMC_GO_IDLE_STATE: use_crc = 1; rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_NONE; break; case MMC_SEND_OP_COND: case MMC_SWITCH_FUNC: /* also SD_SWITCH_FUNC */ case MMC_SET_BLOCKLEN: case MMC_READ_SINGLE_BLOCK: case MMC_READ_MULTIPLE_BLOCK: case MMC_WRITE_BLOCK: case MMC_WRITE_MULTIPLE_BLOCK: case MMC_PROGRAM_CSD: case MMC_SEND_WRITE_PROT: case SD_ERASE_WR_BLK_START: case SD_ERASE_WR_BLK_END: case MMC_LOCK_UNLOCK: case MMC_GEN_CMD: rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R1; break; case MMCSPI_CRC_ON_OFF: rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_NONE; break; case MMC_SEND_CSD: case MMC_SEND_CID: arg = 0; /* no rca in spi mode */ rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R2; ldata_len = 16; break; case MMC_APP_CMD: arg = 0; /* no rca in spi mode */ rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R1; break; case MMC_STOP_TRANSMISSION: case MMC_SET_WRITE_PROT: case MMC_CLR_WRITE_PROT: case MMC_ERASE: rsp_type = MMCSPI_RSP_R1B; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R1B; break; case MMC_ALL_SEND_CID: /* handle MMC_ALL_SEND_CID as MMC_SEND_CID */ opcode = MMC_SEND_CID; rsp_type = MMCSPI_RSP_R1; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R2; ldata_len = 16; break; case MMC_SEND_STATUS: arg = 0; /* no rca in spi mode */ rsp_type = MMCSPI_RSP_R2; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R1; break; case MMCSPI_READ_OCR: rsp_type = MMCSPI_RSP_R3; mmc_rsp_type = MMCSPI_TO_MMC_RSP_NONE; break; case SD_SEND_RELATIVE_ADDR: /* * Handle SD_SEND_RELATIVE_ADDR as MMC_SEND_STATUS - * the rca returned to the caller will always be 0. */ opcode = MMC_SEND_STATUS; rsp_type = MMCSPI_RSP_R2; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R6; break; case SD_SEND_IF_COND: use_crc = 1; rsp_type = MMCSPI_RSP_R7; mmc_rsp_type = MMCSPI_TO_MMC_RSP_R7; break; default: TRACE(dev, ERROR, "Invalid cmd opcode %u\n", opcode); return (MMC_ERR_INVALID); } } switch (rsp_type) { case MMCSPI_RSP_R1: case MMCSPI_RSP_R1B: rsp_len = 1; break; case MMCSPI_RSP_R2: rsp_len = 2; break; case MMCSPI_RSP_R3: case MMCSPI_RSP_R7: rsp_len = 5; break; default: TRACE(dev, ERROR, "Unknown response type %u\n", rsp_type); return (MMC_ERR_INVALID); } mmcspi_cmd->mmc_cmd = mmc_cmd; mmcspi_cmd->opcode = opcode; mmcspi_cmd->arg = arg; mmcspi_cmd->flags = flags; mmcspi_cmd->retries = retries; mmcspi_cmd->use_crc = use_crc; mmcspi_cmd->error_mask = MMCSPI_R1_ERR_MASK; if (!mmcspi_cmd->use_crc) mmcspi_cmd->error_mask &= ~MMCSPI_R1_CRC_ERR; mmcspi_cmd->rsp_type = rsp_type; mmcspi_cmd->rsp_len = rsp_len; mmcspi_cmd->mmc_rsp_type = mmc_rsp_type; memset(&mmcspi_cmd->ldata, 0, sizeof(struct mmc_data)); mmcspi_cmd->ldata.len = ldata_len; if (ldata_len) { mmcspi_cmd->ldata.data = sc->slot.ldata_buf; mmcspi_cmd->ldata.flags = MMC_DATA_READ; mmcspi_cmd->data = &mmcspi_cmd->ldata; } else mmcspi_cmd->data = mmc_cmd->data; return (MMC_ERR_NONE); } static unsigned int mmcspi_send_cmd(device_t dev, struct mmcspi_command *cmd, uint8_t *rspbuf) { unsigned int err; uint32_t opcode; uint32_t arg; uint8_t txbuf[8]; uint8_t crc; TRACE_ENTER(dev); opcode = cmd->opcode; arg = cmd->arg; TRACE(dev, ACTION, "sending %sMD%u(0x%08x)\n", cmd->flags & MMC_CMD_IS_APP ? "AC": "C", opcode, arg); /* * Sending this byte ahead of each command prevents some cards from * responding with unaligned data, and doesn't bother the others. * Examples: * * Sandisk 32GB SDHC card, cid: * mid=0x03 oid=0x5344 pnm="SU32G" prv=8.0 mdt=00.2000 */ txbuf[0] = 0xff; txbuf[1] = 0x40 | (opcode & 0x3f); txbuf[2] = arg >> 24; txbuf[3] = (arg >> 16) & 0xff; txbuf[4] = (arg >> 8) & 0xff; txbuf[5] = arg & 0xff; if (cmd->use_crc) crc = update_crc7(CRC7_INITIAL, &txbuf[1], 5); else crc = 0; txbuf[6] = (crc << 1) | 0x01; /* * Some cards have garbage on the bus in the first byte slot after * the last command byte. This seems to be common with the stop * command. Clocking out an extra byte with the command will * result in that data not being searched for the response token, * which is ok, because no cards respond that fast. */ txbuf[7] = 0xff; err = mmcspi_do_spi_write(dev, txbuf, sizeof(txbuf), NULL, 0); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "spi write of command failed\n"); TRACE_EXIT(dev); return (err); } TRACE(dev, DETAILS, "rx cmd bytes 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", junkbuf[0], junkbuf[1], junkbuf[2], junkbuf[3], junkbuf[4], junkbuf[5] ); TRACE(dev, DETAILS, "skipped response byte is 0x%02x\n", junkbuf[6]); err = mmcspi_get_response_token(dev, MMCSPI_R1_MASK, MMCSPI_R1_VALUE, cmd->rsp_len, MMCSPI_RSP_R1B == cmd->rsp_type, rspbuf); if (MMC_ERR_NONE == err) { if (rspbuf[0] & cmd->error_mask & MMCSPI_R1_CRC_ERR) err = MMC_ERR_BADCRC; else if (rspbuf[0] & cmd->error_mask) err = MMC_ERR_INVALID; } TRACE_EXIT(dev); return (err); } static unsigned int mmcspi_read_block(device_t dev, uint8_t *data, unsigned int len, unsigned int check_crc16, unsigned int check_crc7) { struct bintime start; struct bintime elapsed; unsigned int non_token_bytes; unsigned int data_captured; unsigned int crc_captured; unsigned int pollbufpos; unsigned int crc16_mismatch; unsigned int err; uint16_t crc16, computed_crc16; uint8_t crc7, computed_crc7; uint8_t pollbuf[MMCSPI_POLL_LEN]; uint8_t crcbuf[MMCSPI_DATA_CRC_LEN]; int i; crc16_mismatch = 0; TRACE_ENTER(dev); TRACE(dev, ACTION, "read block(%u)\n", len); /* * With this approach, we could pointlessly read up to * (MMCSPI_POLL_LEN - 3 - len) bytes from the spi bus, but only in * the odd situation where MMCSPI_POLL_LEN is greater than len + 3. */ getbintime(&start); do { TRACE(dev, DETAILS, "looking for read token\n"); err = mmcspi_do_spi_read(dev, pollbuf, MMCSPI_POLL_LEN); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "token read on spi failed\n"); TRACE_EXIT(dev); return (err); } for (i = 0; i < MMCSPI_POLL_LEN; i++) { if (MMCSPI_TOKEN_SB == pollbuf[i]) { TRACE(dev, RESULT, "found start block token at %d\n", i); break; } else if (MMCSPI_IS_DE_TOKEN(pollbuf[i])) { TRACE(dev, ERROR, "found data error token at %d\n", i); TRACE_EXIT(dev); return (MMC_ERR_FAILED); } } getbintime(&elapsed); bintime_sub(&elapsed, &start); if (elapsed.sec > MMCSPI_TIMEOUT_SEC) { TRACE(dev, ERROR, "timeout while looking for read " "token\n"); return (MMC_ERR_TIMEOUT); } } while (MMCSPI_POLL_LEN == i); /* copy any data captured in tail of poll buf to data buf */ non_token_bytes = MMCSPI_POLL_LEN - i - 1; data_captured = min(non_token_bytes, len); crc_captured = non_token_bytes - data_captured; pollbufpos = i + 1; TRACE(dev, DETAILS, "data bytes captured in pollbuf = %u\n", data_captured); memcpy(data, &pollbuf[pollbufpos], data_captured); pollbufpos += data_captured; TRACE(dev, DETAILS, "data bytes to read = %u, crc_captured = %u\n", len - data_captured, crc_captured); /* get any remaining data from the spi bus */ if (data_captured < len) { err = mmcspi_do_spi_read(dev, &data[data_captured], len - data_captured); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "spi read of remainder of block failed\n"); TRACE_EXIT(dev); return (err); } } /* copy any crc captured in the poll buf to the crc buf */ memcpy(crcbuf, &pollbuf[pollbufpos], crc_captured); /* get any remaining crc */ if (crc_captured < MMCSPI_DATA_CRC_LEN) { TRACE(dev, DETAILS, "crc bytes to read = %u\n", MMCSPI_DATA_CRC_LEN - crc_captured); err = mmcspi_do_spi_read(dev, &crcbuf[crc_captured], MMCSPI_DATA_CRC_LEN - crc_captured); if (MMC_ERR_NONE != err) { TRACE(dev, ERROR, "spi read of crc failed\n"); TRACE_EXIT(dev); return (err); } } /* * The following crc checking code is deliberately structured to * allow a passing crc-7 check to override a failing crc-16 check * when both are enabled. */ if (check_crc16) { crc16 = ((uint16_t)crcbuf[0] << 8) | crcbuf[1]; computed_crc16 = update_crc16(CRC16_INITIAL, data, len); TRACE(dev, RESULT, "sent_crc16=0x%04x computed_crc16=0x%04x\n", crc16, computed_crc16); if (computed_crc16 != crc16) { crc16_mismatch = 1; TRACE(dev, ERROR, "crc16 mismatch, should be 0x%04x, " " is 0x%04x\n", crc16, computed_crc16); if (!check_crc7) { TRACE_EXIT(dev); return (MMC_ERR_BADCRC); } } } if (check_crc7) { if (crc16_mismatch) { /* * Let the user know something else is being checked * after announcing an error above. */ TRACE(dev, ERROR, "checking crc7\n"); } crc7 = data[len - 1] >> 1; computed_crc7 = update_crc7(CRC7_INITIAL, data, len - 1); TRACE(dev, RESULT, "sent_crc7=0x%02x computed_crc7=0x%02x\n", crc7, computed_crc7); if (computed_crc7 != crc7) { TRACE(dev, ERROR, "crc7 mismatch, should be 0x%02x, is 0x%02x\n", crc7, computed_crc7); TRACE_EXIT(dev); return (MMC_ERR_BADCRC); } } TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_send_stop(device_t dev, unsigned int retries) { struct mmcspi_command stop; struct mmc_command mmc_stop; uint8_t stop_response; unsigned int err; int i; TRACE_ENTER(dev); memset(&mmc_stop, 0, sizeof(mmc_stop)); mmc_stop.opcode = MMC_STOP_TRANSMISSION; mmc_stop.flags = MMC_RSP_R1B | MMC_CMD_AC; err = mmcspi_set_up_command(dev, &stop, &mmc_stop); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } /* * Retry stop commands that fail due to bad crc here because having * the caller retry the entire read/write command due to such a * failure is pointlessly expensive. */ for (i = 0; i <= retries; i++) { TRACE(dev, ACTION, "sending stop message\n"); err = mmcspi_send_cmd(dev, &stop, &stop_response); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } TRACE(dev, RESULT, "stop response=0x%02x\n", stop_response); /* retry on crc error */ if (stop_response & stop.error_mask & MMCSPI_R1_CRC_ERR) { continue; } } if (stop_response & stop.error_mask) { TRACE_EXIT(dev); /* * Don't return MMC_ERR_BADCRC here, even if * MMCSPI_R1_CRC_ERR is set, because that would trigger the * caller's retry-on-crc-error mechanism, effectively * squaring the maximum number of retries of the stop * command. */ return (MMC_ERR_FAILED); } TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_read_phase(device_t dev, struct mmcspi_command *cmd) { struct mmc_data *data; unsigned int data_offset; unsigned int num_blocks; unsigned int len; unsigned int err; uint8_t *data8; int i; TRACE_ENTER(dev); data = cmd->data; data8 = (uint8_t *)data->data; data_offset = 0; if (data->len < MMCSPI_DATA_BLOCK_LEN) { num_blocks = 1; len = data->len; } else { num_blocks = data->len / MMCSPI_DATA_BLOCK_LEN; len = MMCSPI_DATA_BLOCK_LEN; } for (i = 0; i < num_blocks; i++) { /* * The CID and CSD data blocks contain both a trailing crc-7 * inside the data block and the standard crc-16 following * the data block, so both are checked when use_crc is true. * * When crc checking has been enabled via CMD59, some cards * send CID and CSD data blocks with correct crc-7 values * but incorrect crc-16 values. read_block will accept * those responses as valid as long as the crc-7 is correct. * * Examples: * * Super Talent 1GB SDSC card, cid: * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=02.2010 */ err = mmcspi_read_block(dev, &data8[data_offset], len, cmd->use_crc, cmd->use_crc && ((MMC_SEND_CID == cmd->opcode) || (MMC_SEND_CSD == cmd->opcode))); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } data_offset += MMCSPI_DATA_BLOCK_LEN; } /* multi-block read commands require a stop */ if (num_blocks > 1) { err = mmcspi_send_stop(dev, cmd->retries); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } } TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_write_block(device_t dev, uint8_t *data, unsigned int is_multi, unsigned char use_crc, uint8_t *status) { uint8_t txbuf[max(MMCSPI_POLL_LEN, 2)]; uint8_t response_token; unsigned int err; uint16_t crc; TRACE_ENTER(dev); if (use_crc) crc = update_crc16(CRC16_INITIAL, data, MMCSPI_DATA_BLOCK_LEN); else crc = 0; TRACE(dev, ACTION, "write block(512) crc=0x%04x\n", crc); txbuf[0] = is_multi ? MMCSPI_TOKEN_SB_WM : MMCSPI_TOKEN_SB; err = mmcspi_do_spi_write(dev, txbuf, 1, data, MMCSPI_DATA_BLOCK_LEN); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } txbuf[0] = crc >> 8; txbuf[1] = crc & 0xff; err = mmcspi_do_spi_write(dev, txbuf, 2, NULL, 0); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } err = mmcspi_get_response_token(dev, MMCSPI_DR_MASK, MMCSPI_DR_VALUE, 1, 1, &response_token); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } *status = response_token & MMCSPI_DR_ERR_MASK; TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_write_phase(device_t dev, struct mmcspi_command *cmd) { struct mmc_data *data; unsigned int data_offset; unsigned int num_blocks; unsigned int err; uint8_t *data8; uint8_t token[2]; uint8_t status; int i; TRACE_ENTER(dev); data = cmd->data; data8 = (uint8_t *)data->data; data_offset = 0; num_blocks = data->len / MMCSPI_DATA_BLOCK_LEN; for (i = 0; i < num_blocks; i++) { err = mmcspi_write_block(dev, &data8[data_offset], num_blocks > 1, cmd->use_crc, &status); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } if (MMCSPI_DR_ERR_NONE != status) { if (num_blocks > 1) { /* * Ignore any failure reported for the stop * command, as the return status for the * write phase will be whatever error was * indicated in the data respone token. */ mmcspi_send_stop(dev, cmd->retries); } /* * A CRC error can't be ignored here, even if crc * use is disabled, as there is no way to simply * carry on when a data error token has been sent. */ if (MMCSPI_DR_ERR_CRC == status) { TRACE_EXIT(dev); return (MMC_ERR_BADCRC); } else { TRACE_EXIT(dev); return (MMC_ERR_FAILED); } } data_offset += MMCSPI_DATA_BLOCK_LEN; } /* successful multi-block write commands require a stop token */ if (num_blocks > 1) { TRACE(dev, ACTION, "Sending stop token\n"); /* * Most/all cards are a bit sluggish in assserting busy * after receipt of the STOP_TRAN token. Clocking out an * extra byte here provides a byte of dead time before * looking for not busy, avoiding a premature not-busy * determination with such cards. */ token[0] = MMCSPI_TOKEN_ST; token[1] = 0xff; err = mmcspi_do_spi_write(dev, token, sizeof(token), NULL, 0); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } err = mmcspi_wait_for_not_busy(dev); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } } TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_translate_response(device_t dev, struct mmcspi_command *cmd, uint8_t *rspbuf) { struct mmc_command *mmc_cmd; uint32_t mmc_rsp_type; uint8_t *ldata; mmc_cmd = cmd->mmc_cmd; mmc_rsp_type = cmd->mmc_rsp_type; ldata = cmd->ldata.data; TRACE_ENTER(dev); TRACE(dev, ACTION, "translating SPI rsp %u to SD rsp %u\n", cmd->rsp_type, mmc_rsp_type); if ((MMCSPI_TO_MMC_RSP_R1 == mmc_rsp_type) || (MMCSPI_TO_MMC_RSP_R1B == mmc_rsp_type)) { TRACE(dev, ACTION, "translating SPI-R1/2 to SD-R1\n"); if ((MMCSPI_RSP_R1 == cmd->rsp_type) || (MMCSPI_RSP_R1B == cmd->rsp_type) || (MMCSPI_RSP_R2 == cmd->rsp_type)) { mmc_cmd->resp[0] = 0; if (rspbuf[0] & MMCSPI_R1_PARAM_ERR) mmc_cmd->resp[0] |= R1_OUT_OF_RANGE; if (rspbuf[0] & MMCSPI_R1_ADDR_ERR) mmc_cmd->resp[0] |= R1_ADDRESS_ERROR; if (rspbuf[0] & MMCSPI_R1_ERASE_ERR) mmc_cmd->resp[0] |= R1_ERASE_SEQ_ERROR; if (rspbuf[0] & MMCSPI_R1_CRC_ERR) mmc_cmd->resp[0] |= R1_COM_CRC_ERROR; if (rspbuf[0] & MMCSPI_R1_ILL_CMD) mmc_cmd->resp[0] |= R1_ILLEGAL_COMMAND; if (rspbuf[0] & MMCSPI_R1_ERASE_RST) mmc_cmd->resp[0] |= R1_ERASE_RESET; if (rspbuf[0] & MMCSPI_R1_IDLE) mmc_cmd->resp[0] |= (uint32_t)R1_STATE_IDLE << 9; else mmc_cmd->resp[0] |= (uint32_t)R1_STATE_READY << 9; /* When MMC_CMD_IS_APP is sent, emulate R1_APP_CMD SD-bus status bit. */ if (!(cmd->flags & MMC_CMD_IS_APP) && (MMC_APP_CMD == cmd->opcode)) mmc_cmd->resp[0] |= R1_APP_CMD; if (MMCSPI_RSP_R2 == cmd->rsp_type) { if (rspbuf[1] & MMCSPI_R2_OOR_CSD_OW) mmc_cmd->resp[0] |= R1_OUT_OF_RANGE | R1_CSD_OVERWRITE; if (rspbuf[1] & MMCSPI_R2_ERASE_PARAM) mmc_cmd->resp[0] |= R1_ERASE_PARAM; if (rspbuf[1] & MMCSPI_R2_WP_VIOLATE) mmc_cmd->resp[0] |= R1_WP_VIOLATION; if (rspbuf[1] & MMCSPI_R2_ECC_FAIL) mmc_cmd->resp[0] |= R1_CARD_ECC_FAILED; if (rspbuf[1] & MMCSPI_R2_CC_ERR) mmc_cmd->resp[0] |= R1_CC_ERROR; if (rspbuf[1] & MMCSPI_R2_ERR) mmc_cmd->resp[0] |= R1_ERROR; if (rspbuf[1] & MMCSPI_R2_WP_ER_LCK) mmc_cmd->resp[0] |= R1_LOCK_UNLOCK_FAILED | R1_WP_ERASE_SKIP; if (rspbuf[1] & MMCSPI_R2_LOCKED) mmc_cmd->resp[0] |= R1_CARD_IS_LOCKED; } } else return (MMC_ERR_INVALID); } else if (MMCSPI_TO_MMC_RSP_R2 == mmc_rsp_type) { if (16 == cmd->ldata.len) { TRACE(dev, ACTION, "translating SPI-R1/ldata(16) " "to SD-R2\n"); /* ldata contains bits 127:0 of the spi response */ mmc_cmd->resp[0] = (uint32_t)ldata[0] << 24 | (uint32_t)ldata[1] << 16 | (uint32_t)ldata[2] << 8 | (uint32_t)ldata[3]; mmc_cmd->resp[1] = (uint32_t)ldata[4] << 24 | (uint32_t)ldata[5] << 16 | (uint32_t)ldata[6] << 8 | (uint32_t)ldata[7]; mmc_cmd->resp[2] = (uint32_t)ldata[8] << 24 | (uint32_t)ldata[9] << 16 | (uint32_t)ldata[10] << 8 | (uint32_t)ldata[11]; mmc_cmd->resp[3] = (uint32_t)ldata[12] << 24 | (uint32_t)ldata[13] << 16 | (uint32_t)ldata[14] << 8; } else return (MMC_ERR_INVALID); } else if (MMCSPI_TO_MMC_RSP_R3 == mmc_rsp_type) { if (MMCSPI_RSP_R3 == cmd->rsp_type) { TRACE(dev, ACTION, "translating SPI-R3 to SD-R3\n"); /* rspbuf contains a 40-bit spi-R3 from the MMCSPI_READ_OCR response, of which bits 31:0 are the OCR value */ /* spi response bits 31:0 mapped to sdhc register bits 31:0 */ mmc_cmd->resp[0] = (uint32_t)rspbuf[1] << 24 | (uint32_t)rspbuf[2] << 16 | (uint32_t)rspbuf[3] << 8 | (uint32_t)rspbuf[4]; /* Clear card busy bit (indicating busy) if the SPI-R1 idle bit is set. */ if (rspbuf[0] & MMCSPI_R1_IDLE) { mmc_cmd->resp[0] &= ~MMC_OCR_CARD_BUSY; } else { mmc_cmd->resp[0] |= MMC_OCR_CARD_BUSY; } TRACE(dev, DETAILS, "ocr=0x%08x\n", mmc_cmd->resp[0]); } else return (MMC_ERR_INVALID); } else if (MMCSPI_TO_MMC_RSP_R6 == mmc_rsp_type) { if (MMCSPI_RSP_R2 == cmd->rsp_type) { TRACE(dev, ACTION, "translating SPI-R2 to SD-R6\n"); /* rca returned will always be zero */ mmc_cmd->resp[0] = 0; if (rspbuf[0] & MMCSPI_R1_CRC_ERR) mmc_cmd->resp[0] |= 0x8000; if (rspbuf[0] & MMCSPI_R1_ILL_CMD) mmc_cmd->resp[0] |= 0x4000; if (rspbuf[1] & MMCSPI_R2_ERR) mmc_cmd->resp[0] |= 0x2000; if (rspbuf[0] & MMCSPI_R1_IDLE) mmc_cmd->resp[0] |= (uint32_t)R1_STATE_IDLE << 9; else mmc_cmd->resp[0] |= (uint32_t)R1_STATE_READY << 9; } else return (MMC_ERR_INVALID); } else if (MMCSPI_TO_MMC_RSP_R7 == mmc_rsp_type) { if (MMCSPI_RSP_R7 == cmd->rsp_type) { TRACE(dev, ACTION, "translating SPI-R7 to SD-R7\n"); /* rsp buf contains a 40-bit spi-R7, of which bits 11:0 need to be transferred */ /* spi response bits 11:0 mapped to sdhc register bits 11:0 */ mmc_cmd->resp[0] = (uint32_t)(rspbuf[3] & 0xf) << 8 | (uint32_t)rspbuf[4]; } else return (MMC_ERR_INVALID); } else if (MMCSPI_TO_MMC_RSP_NONE != mmc_rsp_type) return (MMC_ERR_INVALID); TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_get_ocr(device_t dev, uint8_t *ocrbuf) { struct mmc_command mmc_cmd; struct mmcspi_command cmd; unsigned int err; uint8_t r1_status; uint8_t rspbuf[MMCSPI_MAX_RSP_LEN]; TRACE_ENTER(dev); memset(&mmc_cmd, 0, sizeof(struct mmc_command)); mmc_cmd.opcode = MMCSPI_READ_OCR; mmc_cmd.flags = MMC_RSP_R3 | MMC_CMD_AC; err = mmcspi_set_up_command(dev, &cmd, &mmc_cmd); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } err = mmcspi_send_cmd(dev, &cmd, rspbuf); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } r1_status = rspbuf[0] & cmd.error_mask; if (r1_status) { if (r1_status & MMCSPI_R1_CRC_ERR) err = MMC_ERR_BADCRC; else err = MMC_ERR_INVALID; TRACE_EXIT(dev); return (err); } memcpy(ocrbuf, &rspbuf[1], MMCSPI_OCR_LEN); TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_set_crc_on_off(device_t dev, unsigned int crc_on) { struct mmc_command mmc_cmd; struct mmcspi_command cmd; unsigned int err; uint8_t r1_status; uint8_t rspbuf[MMCSPI_MAX_RSP_LEN]; TRACE_ENTER(dev); memset(&mmc_cmd, 0, sizeof(struct mmc_command)); mmc_cmd.opcode = MMCSPI_CRC_ON_OFF; mmc_cmd.arg = crc_on ? 1 : 0; mmc_cmd.flags = MMC_RSP_NONE | MMC_CMD_AC; err = mmcspi_set_up_command(dev, &cmd, &mmc_cmd); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } err = mmcspi_send_cmd(dev, &cmd, rspbuf); if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } r1_status = rspbuf[0] & cmd.error_mask; if (r1_status) { if (r1_status & MMCSPI_R1_CRC_ERR) err = MMC_ERR_BADCRC; else err = MMC_ERR_INVALID; TRACE_EXIT(dev); return (err); } TRACE_EXIT(dev); return (MMC_ERR_NONE); } static unsigned int mmcspi_update_crc_setting(device_t dev, unsigned int crc_on) { struct mmcspi_softc *sc; struct mmcspi_slot *slot; unsigned int err; int i; TRACE_ENTER(dev); sc = device_get_softc(dev); slot = &sc->slot; for (i = 0; i <= MMCSPI_RETRIES; i++) { err = mmcspi_set_crc_on_off(dev, crc_on); if (MMC_ERR_BADCRC != err) break; } if (MMC_ERR_NONE != err) { TRACE_EXIT(dev); return (err); } if (crc_on) slot->crc_enabled = 1; else slot->crc_enabled = 0; TRACE_EXIT(dev); return (MMC_ERR_NONE); } static int mmcspi_request(device_t brdev, device_t reqdev, struct mmc_request *req) { TRACE_ENTER(brdev); struct mmcspi_softc *sc = device_get_softc(brdev); struct mmcspi_slot *slot = &sc->slot; struct mmcspi_command cmd; struct mmc_command *mmc_cmd = req->cmd; struct mmc_data *data; unsigned int err; unsigned int use_crc_sample; int i, j; uint32_t opcode; uint32_t flags; uint32_t last_opcode; uint32_t last_flags; uint8_t rspbuf[MMCSPI_MAX_RSP_LEN]; #define IS_CMD(code, cmd, flags) \ (!((flags) & MMC_CMD_IS_APP) && ((code) == (cmd))) #define IS_ACMD(code, cmd, flags) \ (((flags) & MMC_CMD_IS_APP) && ((code) == (cmd))) if (power_on != slot->host.ios.power_mode) return (MMC_ERR_INVALID); /* * Sample use_crc sysctl and adjust card setting if required and * appropriate. */ use_crc_sample = sc->use_crc; if (slot->crc_init_done && (use_crc_sample != slot->crc_enabled)) { err = mmcspi_update_crc_setting(brdev, use_crc_sample); if (MMC_ERR_NONE != err) goto out; slot->crc_init_done = 1; } err = mmcspi_set_up_command(brdev, &cmd, mmc_cmd); if (MMC_ERR_NONE != err) goto out; opcode = cmd.opcode; flags = cmd.flags; data = cmd.data; last_opcode = slot->last_opcode; last_flags = slot->last_flags; /* enforce restrictions on request parameters */ if (data) { /* * All writes must be a multiple of the block length. All * reads greater than the block length must be a multiple of * the block length. */ if ((data->len % MMCSPI_DATA_BLOCK_LEN) && !((data->flags & MMC_DATA_READ) && (data->len < MMCSPI_DATA_BLOCK_LEN))) { TRACE(brdev, ERROR, "requested data phase not a multiple of %u\n", MMCSPI_DATA_BLOCK_LEN); err = MMC_ERR_INVALID; goto out; } if (((data->flags & MMC_DATA_READ) && (data->flags & MMC_DATA_WRITE)) || (data->flags & MMC_DATA_STREAM)) { TRACE(brdev, ERROR, "illegal data phase flags 0x%02x\n", data->flags); err = MMC_ERR_INVALID; goto out; } } for (i = 0; i <= cmd.retries; i++) { /* * On the next command following a CMD8, collect the OCR and * save it off for use in the next ACMD41. */ if (IS_CMD(SD_SEND_IF_COND, last_opcode, last_flags)) { err = mmcspi_get_ocr(brdev, slot->last_ocr); if (MMC_ERR_NONE != err) { if (MMC_ERR_BADCRC == err) continue; goto out; } } err = mmcspi_send_cmd(brdev, &cmd, rspbuf); if (MMC_ERR_NONE != err) { if (MMC_ERR_BADCRC == err) continue; goto out; } if (data) { if (data->flags & MMC_DATA_READ) err = mmcspi_read_phase(brdev, &cmd); else /* MMC_DATA_WRITE */ err = mmcspi_write_phase(brdev, &cmd); if (MMC_ERR_NONE != err) { if (MMC_ERR_BADCRC == err) continue; goto out; } } break; } if (MMC_ERR_NONE != err) goto out; /* * If this was an ACMD_SD_SEND_OP_COND or MMC_SEND_OP_COND, we need * to return an OCR value in the result. If the response from the * card indicates it is still in the IDLE state, supply the OCR * value obtained after the last CMD8, otherwise issue an * MMCSPI_READ_OCR to get the current value, which will have a valid * CCS bit. * * This dance is required under this emulation approach because the * spec stipulates that no other commands should be sent while * ACMD_SD_SEND_OP_COND is being used to poll for the end of the * IDLE state, and some cards do enforce that requirement. */ if (IS_ACMD(ACMD_SD_SEND_OP_COND, opcode, flags) || IS_CMD(MMC_SEND_OP_COND, opcode, flags)) { if (rspbuf[0] & MMCSPI_R1_IDLE) memcpy(&rspbuf[1], slot->last_ocr, MMCSPI_OCR_LEN); else { /* * Some cards won't accept the MMCSPI_CRC_ON_OFF * command until initialization is complete. * * Examples: * * Super Talent 1GB SDSC card, cid: * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=02.2010 */ if (!slot->crc_init_done) { err = mmcspi_update_crc_setting(brdev, sc->use_crc); if (MMC_ERR_NONE != err) goto out; slot->crc_init_done = 1; } for (j = 0; j <= cmd.retries; j++) { /* * Note that in this case, we pass on the R1 * from READ_OCR. */ err = mmcspi_get_ocr(brdev, rspbuf); if (MMC_ERR_NONE != err) { if (MMC_ERR_BADCRC == err) continue; goto out; } } if (MMC_ERR_NONE != err) goto out; } /* adjust the SPI response type to include the OCR */ cmd.rsp_type = MMCSPI_RSP_R3; } err = mmcspi_translate_response(brdev, &cmd, rspbuf); if (MMC_ERR_NONE != err) goto out; out: slot->last_opcode = mmc_cmd->opcode; slot->last_flags = mmc_cmd->flags; mmc_cmd->error = err; if (req->done) req->done(req); TRACE_EXIT(brdev); return (err); } static int mmcspi_get_ro(device_t brdev, device_t reqdev) { TRACE_ENTER(brdev); TRACE_EXIT(brdev); /* XXX no support for this currently */ return (0); } static int mmcspi_acquire_host(device_t brdev, device_t reqdev) { struct mmcspi_slot *slot; int err; TRACE_ENTER(brdev); err = 0; slot = device_get_ivars(reqdev); MMCSPI_LOCK_SLOT(slot); while (slot->bus_busy) mtx_sleep(slot, &slot->mtx, 0, "mmcspiah", 0); slot->bus_busy++; MMCSPI_UNLOCK_SLOT(slot); TRACE_EXIT(brdev); return (err); } static int mmcspi_release_host(device_t brdev, device_t reqdev) { struct mmcspi_slot *slot; TRACE_ENTER(brdev); slot = device_get_ivars(reqdev); MMCSPI_LOCK_SLOT(slot); slot->bus_busy--; MMCSPI_UNLOCK_SLOT(slot); wakeup(slot); TRACE_EXIT(brdev); return (0); } static int mmcspi_modevent_handler(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: init_crc7tab(); init_crc16tab(); memset(onesbuf, 0xff, sizeof(onesbuf)); break; default: return (EOPNOTSUPP); } return (0); } static int mmcspi_switch_vccq(device_t bus, device_t child) { return (0); } #if defined(MMCSPI_ENABLE_DEBUG_FUNCS) static void mmcspi_dump_data(device_t dev, const char *label, uint8_t *data, unsigned int len) { unsigned int i, j; unsigned int num_lines; unsigned int residual; TRACE_ENTER(dev); num_lines = len / 16; residual = len - 16 * num_lines; for(i = 0; i < num_lines; i++) { device_printf(dev, "%s:", label); for(j = 0; j < 16; j++) printf(" %02x", data[i * 16 + j]); printf("\n"); } if (residual) { device_printf(dev, "%s:", label); for(j = 0; j < residual; j++) printf(" %02x", data[num_lines * 16 + j]); printf("\n"); } TRACE_EXIT(dev); } static void mmcspi_dump_spi_bus(device_t dev, unsigned int len) { unsigned int num_blocks; unsigned int residual; unsigned int i; TRACE_ENTER(dev); num_blocks = len / MMCSPI_DATA_BLOCK_LEN; residual = len - num_blocks * MMCSPI_DATA_BLOCK_LEN; for (i = 0; i < num_blocks; i++) { if (MMC_ERR_NONE != mmcspi_do_spi_read(dev, junkbuf, MMCSPI_DATA_BLOCK_LEN)) { device_printf(dev, "spi read failed\n"); return; } mmcspi_dump_data(dev, "bus_data", junkbuf, MMCSPI_DATA_BLOCK_LEN); } if (residual) { if (MMC_ERR_NONE != mmcspi_do_spi_read(dev, junkbuf, residual)) { device_printf(dev, "spi read failed\n"); return; } mmcspi_dump_data(dev, "bus_data", junkbuf, residual); } TRACE_EXIT(dev); } #endif static device_method_t mmcspi_methods[] = { /* device_if */ DEVMETHOD(device_probe, mmcspi_probe), DEVMETHOD(device_attach, mmcspi_attach), DEVMETHOD(device_detach, mmcspi_detach), DEVMETHOD(device_suspend, mmcspi_suspend), DEVMETHOD(device_resume, mmcspi_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, mmcspi_read_ivar), DEVMETHOD(bus_write_ivar, mmcspi_write_ivar), /* mmcbr_if */ DEVMETHOD(mmcbr_update_ios, mmcspi_update_ios), DEVMETHOD(mmcbr_request, mmcspi_request), DEVMETHOD(mmcbr_get_ro, mmcspi_get_ro), DEVMETHOD(mmcbr_acquire_host, mmcspi_acquire_host), DEVMETHOD(mmcbr_release_host, mmcspi_release_host), DEVMETHOD(mmcbr_switch_vccq, mmcspi_switch_vccq), {0, 0}, }; static driver_t mmcspi_driver = { "mmcspi", mmcspi_methods, sizeof(struct mmcspi_softc), }; DRIVER_MODULE(mmcspi, spibus, mmcspi_driver, mmcspi_modevent_handler, NULL); MODULE_DEPEND(mmcspi, spibus, 1, 1, 1); MMC_DECLARE_BRIDGE(mmcspi); #ifdef FDT SPIBUS_FDT_PNP_INFO(compat_data); #endif