Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 9 Apr 2025 11:23:26 GMT
From:      Ruslan Bukin <br@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 253c83058deb - main - mmc: SPI-mode support for SD cards.
Message-ID:  <202504091123.539BNQRJ065932@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by br:

URL: https://cgit.FreeBSD.org/src/commit/?id=253c83058deb5ff77dc0a3b60bf7c1d10f9ef5e8

commit 253c83058deb5ff77dc0a3b60bf7c1d10f9ef5e8
Author:     Ruslan Bukin <br@FreeBSD.org>
AuthorDate: 2025-04-09 10:23:48 +0000
Commit:     Ruslan Bukin <br@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 <br@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 ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202504091123.539BNQRJ065932>