The branch main has been updated by br: URL: https://cgit.FreeBSD.org/src/commit/?id=253c83058deb5ff77dc0a3b60bf7c1d10f9ef5e8
commit 253c83058deb5ff77dc0a3b60bf7c1d10f9ef5e8 Author: Ruslan Bukin <b...@freebsd.org> AuthorDate: 2025-04-09 10:23:48 +0000 Commit: Ruslan Bukin <b...@freebsd.org> CommitDate: 2025-04-09 11:21:05 +0000 mmc: SPI-mode support for SD cards. Introduce SPI-mode support which allows an SD card to communicate with a host system using SPI protocol, as described in the SD Card Specification. This feature is useful for low-end, FPGA or RISC-V research systems when a SoC is limited in terms of peripherals available (e.g. lack of a dedicated MMC controller in hardware). Examples of such systems include Codasip, lowRISC and CVA6. Project timeline: 2007: Warner first discussed SPI operational mode in his MMC presentation: https://people.freebsd.org/~imp/bsdcan2007.pdf 2012: Patrick Kelsey engineered the support. 2025: Ruslan cleaned up, tested on Codasip X730 platform (RISC-V FPGA) and put the patch to review. 2025: Patrick Kelsey reviewed the patch and aligned with the current MMC code. Reviewed by: pkelsey Sponsored by: UKRI Differential Revision: https://reviews.freebsd.org/D49248 --- sys/conf/files | 1 + sys/dev/mmc/mmcspi.c | 2378 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2379 insertions(+) diff --git a/sys/conf/files b/sys/conf/files index 2f4b7126a9cd..1372c6b1b5b9 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2427,6 +2427,7 @@ dev/mmc/mmc.c optional mmc !mmccam dev/mmc/mmcbr_if.m standard dev/mmc/mmcbus_if.m standard dev/mmc/mmcsd.c optional mmcsd !mmccam +dev/mmc/mmcspi.c optional mmcsd spibus !mmccam dev/mmc/mmc_fdt_helpers.c optional mmc regulator clk fdt | mmccam regulator clk fdt dev/mmc/mmc_helpers.c optional mmc gpio regulator clk | mmccam gpio regulator clk dev/mmc/mmc_pwrseq.c optional mmc clk regulator fdt | mmccam clk regulator fdt diff --git a/sys/dev/mmc/mmcspi.c b/sys/dev/mmc/mmcspi.c new file mode 100644 index 000000000000..d18f7aeb8cc0 --- /dev/null +++ b/sys/dev/mmc/mmcspi.c @@ -0,0 +1,2378 @@ +/*- + * Copyright (c) 2012-2025 Patrick Kelsey. All rights reserved. + * Copyright (c) 2025 Ruslan Bukin <b...@bsdpad.com> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/resource.h> +#include <sys/sysctl.h> + +#include <dev/mmc/bridge.h> +#include <dev/mmc/mmcreg.h> +#include <dev/mmc/mmcbrvar.h> +#include <dev/spibus/spi.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#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. *** 1447 LINES SKIPPED ***