Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 10 Mar 2012 21:13:17 -0500
From:      Patrick Kelsey <kelsey@ieee.org>
To:        freebsd-mips@freebsd.org
Subject:   [PATCH] MMC/SD SPI-mode driver
Message-ID:  <CAD44qMW5jyno5BB%2BQg0Tg_wZA3opK%2BYVUzXHO69Y27ZKtDm7Ew@mail.gmail.com>

next in thread | raw e-mail | index | archive | help

[-- Attachment #1 --]
There are two .diff files attached to this message.  The first is an
MMC/SD SPI-mode driver (mmcspi) that implements the existing mmc/sd
bridge interface and talks to the spibus interface (the latter with a
few modifications).  The second is the changes to the ar71xx_spi
driver that I made to support the mmcspi driver.  I developed these
patches using a MikroTik RB450G and tested it with 31 different
microSD cards.

Change details for mmcspi.diff:

Existing mmc/sd stack:
- Added concept of bus type (bus_type_sd vs. bus_type_spi)
- Fixed some typos
- Fixed some math errors in csd decode
- Fixed bad lookup table
- Fixed bad arg to mmc_send_app_op_cond() during probe
- Added setting of block length on card
- Added detection (and skipping) of password-locked cards
- Avoid card select and bus timing commands when bus type is bus_type_spi
- Added new command flag MMC_CMD_IS_APP to annotate ACMD_x commands

Existing spibus interface:
- Incorporated Luiz Otavio O Souza's acquire/release/write patch
- Added configuration methods
- Added flag to allow transactions with chip select high (needed for
  proper sd card init)


Change details for ar71xx_spi.diff
- Incorporated Luiz Otavio O Souza's patch implementing his changes to spibus_if
- Added configuration methods and clock setting support


-Patrick

[-- Attachment #2 --]
Index: sys/arm/at91/at91_mci.c
===================================================================
--- sys/arm/at91/at91_mci.c	(revision 231894)
+++ sys/arm/at91/at91_mci.c	(working copy)
@@ -659,6 +659,9 @@
 	switch (which) {
 	default:
 		return (EINVAL);
+	case MMCBR_IVAR_BUS_TYPE:
+		*(int *)result = bus_type_sd;
+		break;
 	case MMCBR_IVAR_BUS_MODE:
 		*(int *)result = sc->host.ios.bus_mode;
 		break;
@@ -742,6 +745,7 @@
 		sc->host.ios.vdd = value;
 		break;
 	/* These are read-only */
+	case MMCBR_IVAR_BUS_TYPE:
 	case MMCBR_IVAR_CAPS:
 	case MMCBR_IVAR_HOST_OCR:
 	case MMCBR_IVAR_F_MIN:
Index: sys/conf/files
===================================================================
--- sys/conf/files	(revision 231894)
+++ sys/conf/files	(working copy)
@@ -1459,6 +1459,7 @@
 dev/mmc/mmcbr_if.m		standard
 dev/mmc/mmcbus_if.m		standard
 dev/mmc/mmcsd.c			optional mmcsd
+dev/mmc/mmcspi.c		optional mmcspi
 dev/mn/if_mn.c			optional mn pci
 dev/mps/mps.c			optional mps
 dev/mps/mps_config.c		optional mps
Index: sys/modules/mmcspi/Makefile
===================================================================
--- sys/modules/mmcspi/Makefile	(revision 0)
+++ sys/modules/mmcspi/Makefile	(revision 0)
@@ -0,0 +1,7 @@
+
+.PATH: ${.CURDIR}/../../dev/mmc
+
+KMOD=	mmcspi
+SRCS=	mmcspi.c mmcbr_if.h spibus_if.h device_if.h bus_if.h
+
+.include <bsd.kmod.mk>
Index: sys/dev/mmc/mmcbrvar.h
===================================================================
--- sys/dev/mmc/mmcbrvar.h	(revision 231894)
+++ sys/dev/mmc/mmcbrvar.h	(working copy)
@@ -59,6 +59,7 @@
 #include "mmcbr_if.h"
 
 enum mmcbr_device_ivars {
+    MMCBR_IVAR_BUS_TYPE,
     MMCBR_IVAR_BUS_MODE,
     MMCBR_IVAR_BUS_WIDTH,
     MMCBR_IVAR_CHIP_SELECT,
@@ -82,6 +83,7 @@
 #define MMCBR_ACCESSOR(var, ivar, type)					\
 	__BUS_ACCESSOR(mmcbr, var, MMCBR, ivar, type)
 
+MMCBR_ACCESSOR(bus_type, BUS_TYPE, int)
 MMCBR_ACCESSOR(bus_mode, BUS_MODE, int)
 MMCBR_ACCESSOR(bus_width, BUS_WIDTH, int)
 MMCBR_ACCESSOR(chip_select, CHIP_SELECT, int)
Index: sys/dev/mmc/mmcreg.h
===================================================================
--- sys/dev/mmc/mmcreg.h	(revision 231894)
+++ sys/dev/mmc/mmcreg.h	(working copy)
@@ -78,6 +78,7 @@
 #define	MMC_CMD_BC	(2ul << 5)	/* Broadcast command, no response */
 #define	MMC_CMD_BCR	(3ul << 5)	/* Broadcast command with response */
 #define	MMC_CMD_MASK	(3ul << 5)
+#define	MMC_CMD_IS_APP  (1ul << 8)	/* Command is being sent as an APP command */
 
 /* Possible response types defined in the standard: */
 #define	MMC_RSP_NONE	(0)
Index: sys/dev/mmc/bridge.h
===================================================================
--- sys/dev/mmc/bridge.h	(revision 231894)
+++ sys/dev/mmc/bridge.h	(working copy)
@@ -100,6 +100,10 @@
 	cs_dontcare = 0, cs_high, cs_low
 };
 
+enum mmc_bus_type {
+	bus_type_sd = 0, bus_type_spi
+};
+
 enum mmc_bus_width {
 	bus_width_1 = 0, bus_width_4 = 2, bus_width_8 = 3
 };
@@ -111,6 +115,7 @@
 struct mmc_ios {
 	uint32_t	clock;	/* Speed of the clock in Hz to move data */
 	enum mmc_vdd	vdd;	/* Voltage to apply to the power pins/ */
+	enum mmc_bus_type bus_type;
 	enum mmc_bus_mode bus_mode;
 	enum mmc_chip_select chip_select;
 	enum mmc_bus_width bus_width;
Index: sys/dev/mmc/mmcspi.c
===================================================================
--- sys/dev/mmc/mmcspi.c	(revision 0)
+++ sys/dev/mmc/mmcspi.c	(revision 0)
@@ -0,0 +1,2559 @@
+/*-
+ * Copyright (c) 2012 Patrick Kelsey.  All rights reserved.
+ *
+ * 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
+ * the 31 microSD cards listed at the end of this file.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+
+#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 "mmcbr_if.h"
+#include "spibus_if.h"
+
+#define MMCSPI_RETRIES 3 /* number of retries for internally originated commands */
+#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 response tokens */
+#define MMCSPI_DR_VALUE 0x01  /* value used to search for data response 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
+
+
+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;	/* mask to use when checking for R1 errors */
+	unsigned char		use_crc;	/* do crc checking for this command */
+	unsigned char		rsp_type;	/* type of response to 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;	/* length of response to this command */
+	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;       /* indicates whether host has been acquired */
+	struct mmc_host 	host;		/* host parameters */
+	struct mtx		mtx;		/* slot mutex */
+	uint8_t			last_ocr[MMCSPI_OCR_LEN]; /* last 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;	/* whether crc checking is enabled on the card */
+	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;		/* spi bus device this bridge is on */
+	struct mmcspi_slot	slot;		/* state for the single supported slot */
+	unsigned int		use_crc;	/* whether to enable command CRC checking */
+};
+
+
+/* device interface */
+static int mmcspi_probe(device_t dev);
+static int mmcspi_attach(device_t dev);
+static int mmcspi_detach(device_t dev);
+static int mmcspi_suspend(device_t dev);
+static int mmcspi_resume(device_t dev);
+
+/* bus interface */
+static int mmcspi_read_ivar(device_t bus, device_t child, int which,
+			    uintptr_t *result);
+static int mmcspi_write_ivar(device_t bus, device_t child, int which,
+			     uintptr_t value);
+
+/* mmcbr interface */
+static int mmcspi_update_ios(device_t brdev, device_t reqdev);
+static int mmcspi_request(device_t brdev, device_t reqdev,
+			  struct mmc_request *req);
+static int mmcspi_get_ro(device_t brdev, device_t reqdev);
+static int mmcspi_acquire_host(device_t brdev, device_t reqdev);
+static int mmcspi_release_host(device_t brdev, device_t reqdev);
+
+/* supporting cast */
+static void mmcspi_slot_init(device_t brdev, struct mmcspi_slot *slot);
+static void mmcspi_slot_fini(device_t brdev, struct mmcspi_slot *slot);
+
+static void mmcspi_card_add(struct mmcspi_slot *slot);
+static void mmcspi_card_delete(struct mmcspi_slot *slot);
+
+static unsigned int mmcspi_do_spi_read(device_t dev, uint8_t *data,
+				       unsigned int len);
+static unsigned int mmcspi_do_spi_write(device_t dev, uint8_t *cmd,
+					unsigned int cmdlen, uint8_t *data,
+					unsigned int datalen);
+
+static unsigned int mmcspi_wait_for_not_busy(device_t dev);
+static unsigned int mmcspi_shift_copy(uint8_t *dest, uint8_t *src,
+				      unsigned int src_len, unsigned int shift);
+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);
+
+static unsigned int mmcspi_set_up_command(device_t dev,
+					  struct mmcspi_command *mmcspi_cmd,
+					  struct mmc_command *mmc_cmd);
+static unsigned int mmcspi_send_cmd(device_t dev, struct mmcspi_command *cmd,
+				    uint8_t *rspbuf);
+
+static unsigned int mmcspi_read_block(device_t dev, uint8_t *data,
+				      unsigned int len, unsigned int check_crc16,
+				      unsigned int check_crc7);
+static unsigned int mmcspi_send_stop(device_t dev, unsigned int retries);
+static unsigned int mmcspi_read_phase(device_t dev, struct mmcspi_command *cmd);
+static unsigned int mmcspi_write_block(device_t dev, uint8_t *data,
+				       unsigned int is_multi,
+				       unsigned char use_crc, uint8_t *status);
+static unsigned int mmcspi_write_phase(device_t dev, struct mmcspi_command *cmd);
+static unsigned int mmcspi_translate_response(device_t dev,
+					      struct mmcspi_command *cmd,
+					      uint8_t *rspbuf);
+static unsigned int mmcspi_get_ocr(device_t dev, uint8_t *ocrbuf);
+static unsigned int mmcspi_set_crc_on_off(device_t dev, unsigned int crc_on);
+static unsigned int mmcspi_update_crc_setting(device_t dev, unsigned int crc_on);
+
+static uint8_t update_crc7(uint8_t crc, uint8_t *buf, unsigned int len);
+static uint16_t update_crc16(uint16_t crc, uint8_t *buf, unsigned int len);
+static void init_crc7tab(void);
+static void init_crc16tab(void);
+
+static int mmcspi_modevent_handler(module_t mod, int what, void *arg);
+
+#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 and 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 int
+mmcspi_probe(device_t dev)
+{
+	TRACE_ENTER(dev);
+	
+	device_set_desc(dev, "mmc spi-mode bridge");
+
+	TRACE_EXIT(dev);
+	return (0);
+}
+
+
+static void
+mmcspi_slot_init(device_t brdev, struct mmcspi_slot *slot)
+{
+	TRACE_ENTER(brdev);
+
+	struct mmcspi_softc *sc = device_get_softc(brdev);
+	struct spi_config cfg;
+
+	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;
+
+	SPIBUS_ACQUIRE_BUS(sc->busdev, sc->dev);
+
+	cfg.clock_hz = 0;
+	SPIBUS_SET_CONFIG(sc->busdev, &cfg);
+
+	SPIBUS_GET_CONFIG(sc->busdev, &cfg);
+	slot->host.f_max = cfg.clock_hz;
+
+	if (slot->host.f_max > 25000000) {
+		slot->host.caps |= MMC_CAP_HSPEED;
+	}
+
+	SPIBUS_RELEASE_BUS(sc->busdev, sc->dev);
+
+	/* 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 = slot->sc->dev;
+
+	TRACE_ENTER(brdev);
+
+	MMCSPI_LOCK_SLOT(slot);
+	slot->dev = device_add_child(brdev, "mmc", -1);
+	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 = slot->sc->dev;
+	device_t 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_attach(device_t dev)
+{
+	struct mmcspi_softc *sc = device_get_softc(dev);
+	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
+	struct sysctl_oid *tree = device_get_sysctl_tree(dev);
+	struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree);
+
+	TRACE_ENTER(dev);
+
+	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)
+{
+	TRACE_ENTER(dev);
+
+  	struct mmcspi_softc *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)
+{
+	TRACE_ENTER(bus);
+
+	struct mmcspi_slot *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_CAPS:
+		*result = slot->host.caps;
+		break;
+	case MMCBR_IVAR_TIMING:
+		*result = slot->host.ios.timing;
+		break;
+	case MMCBR_IVAR_MAX_DATA:
+		*result = 64*1024;  /* seems reasonable, not dictated by anything */
+		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)
+{
+	TRACE_ENTER(bus);
+
+	struct mmcspi_slot *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_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 int
+mmcspi_update_ios(device_t brdev, device_t reqdev)
+{
+	TRACE_ENTER(brdev);
+	struct mmcspi_softc *sc = device_get_softc(brdev);
+	struct mmcspi_slot *slot = device_get_ivars(reqdev);
+	struct spi_config cfg;
+	struct spi_command spi_cmd;
+
+	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.
+		 */
+
+		/* Use 100 kHz for maximum compatibility. */
+		cfg.clock_hz = 100000;
+		SPIBUS_SET_CONFIG(sc->busdev, &cfg);
+		SPIBUS_GET_CONFIG(sc->busdev, &cfg);
+		slot->host.ios.clock = cfg.clock_hz;
+
+		spi_cmd.tx_cmd = onesbuf;
+		spi_cmd.rx_cmd = junkbuf;
+		spi_cmd.tx_cmd_sz = spi_cmd.rx_cmd_sz = 10;
+		spi_cmd.tx_data = spi_cmd.rx_data = NULL;
+		spi_cmd.tx_data_sz = spi_cmd.rx_data_sz = 0;
+		spi_cmd.flags = SPI_SKIP_CHIP_SELECT | SPI_CHIP_SELECT_HIGH;
+
+		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;
+	}
+	
+
+	/* 
+	 * A clock value of zero means set the clock line low, which is the
+	 * normal spi idle state.
+	 */
+	if (slot->host.ios.clock > 0) {
+		cfg.clock_hz = slot->host.ios.clock;
+		SPIBUS_SET_CONFIG(sc->busdev, &cfg);
+			
+		/* retrieve the possibly adjusteed downward setting */
+		SPIBUS_GET_CONFIG(sc->busdev, &cfg);
+		slot->host.ios.clock = cfg.clock_hz;
+	}
+
+	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_do_spi_read(device_t dev, uint8_t *data, unsigned int len)
+{
+	TRACE_ENTER(dev);
+	
+	struct mmcspi_softc *sc = device_get_softc(dev);
+	struct spi_command spi_cmd;
+	int err;
+
+	spi_cmd.tx_cmd = onesbuf;
+	spi_cmd.rx_cmd = data;
+	spi_cmd.tx_cmd_sz = spi_cmd.rx_cmd_sz = len;
+	spi_cmd.tx_data = NULL;
+	spi_cmd.rx_data = NULL;
+	spi_cmd.tx_data_sz = spi_cmd.rx_data_sz = 0;
+	spi_cmd.flags = SPI_SKIP_CHIP_SELECT;
+
+	err = SPIBUS_TRANSFER(sc->busdev, sc->dev, &spi_cmd);
+
+	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)
+{
+	TRACE_ENTER(dev);
+	
+	struct mmcspi_softc *sc = device_get_softc(dev);
+	struct spi_command spi_cmd;
+	int err;
+
+	spi_cmd.tx_cmd = cmd;
+	spi_cmd.rx_cmd = junkbuf;
+	spi_cmd.tx_cmd_sz = spi_cmd.rx_cmd_sz = cmdlen;
+	spi_cmd.tx_data = data;
+	spi_cmd.rx_data = junkbuf;
+	spi_cmd.tx_data_sz = spi_cmd.rx_data_sz = datalen;
+	spi_cmd.flags = SPI_SKIP_CHIP_SELECT;
+
+	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)
+{
+	TRACE_ENTER(dev);
+	
+	int i;
+	unsigned int err;
+	unsigned int busy_length = 0;
+	uint8_t pollbuf[MMCSPI_POLL_LEN];
+	struct bintime start, elapsed;
+
+	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 for longer than 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 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)
+{
+	int i;
+	unsigned int err;
+	unsigned int offset;
+	unsigned int shift = 0;
+	unsigned int remaining;
+	boolean_t found;
+	uint8_t pollbuf[2 * MMCSPI_MAX_RSP_LEN];
+	struct bintime start, elapsed;
+
+	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 response 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.
+				 */
+				uint16_t search_space =
+				    ((uint16_t)pollbuf[i] << 8) | pollbuf[i + 1];
+				uint16_t search_mask = (uint16_t)mask << 8;
+				uint16_t 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 match 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 reponse 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 = device_get_softc(dev);
+	struct mmcspi_slot *slot = &sc->slot;
+	uint32_t opcode = mmc_cmd->opcode;
+	uint32_t arg = mmc_cmd->arg;
+	uint32_t flags = mmc_cmd->flags;
+	uint32_t retries = mmc_cmd->retries;
+	unsigned char rsp_type;
+	unsigned char rsp_len;
+	unsigned int ldata_len = 0;
+	unsigned int use_crc = slot->crc_enabled;
+
+	if (flags & MMC_CMD_IS_APP) {
+		switch (opcode) {
+		case ACMD_SD_STATUS:
+			rsp_type = MMCSPI_RSP_R2;
+			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;
+			break;
+		case ACMD_SD_SEND_OP_COND:
+			/* only HCS bit is valid in spi mode */
+			arg &= 0x40000000;
+			rsp_type = MMCSPI_RSP_R1;
+			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;
+			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:
+		case MMCSPI_CRC_ON_OFF:
+			rsp_type = MMCSPI_RSP_R1;
+			break;
+
+		case MMC_SEND_CSD:
+		case MMC_SEND_CID:
+			arg = 0; /* no rca in spi mode */
+			rsp_type = MMCSPI_RSP_R1;
+			ldata_len = 16;
+			break;
+
+		case MMC_APP_CMD:
+			arg = 0; /* no rca in spi mode */
+			rsp_type = MMCSPI_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;
+			break;
+
+		case MMC_ALL_SEND_CID:
+			/* handle MMC_ALL_SEND_CID as MMC_SEND_CID */
+			opcode = MMC_SEND_CID;
+			rsp_type = MMCSPI_RSP_R1;
+			ldata_len = 16;
+			break;
+
+		case MMC_SEND_STATUS:
+			arg = 0; /* no rca in spi mode */
+			rsp_type = MMCSPI_RSP_R2;
+			break;
+
+
+		case MMCSPI_READ_OCR:
+			rsp_type = MMCSPI_RSP_R3;
+			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;
+			break;
+
+		case SD_SEND_IF_COND:
+			use_crc = 1;
+			rsp_type = MMCSPI_RSP_R7;
+			break;
+
+		default:
+			TRACE(dev, ERROR, "Invalid command 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;
+
+	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)
+{
+	TRACE_ENTER(dev);
+
+	unsigned int err;
+	uint32_t opcode = cmd->opcode;
+	uint32_t arg = cmd->arg;
+	uint8_t txbuf[8];
+	uint8_t crc;
+
+	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)
+{
+	TRACE_ENTER(dev);
+	
+	int i;
+	unsigned int err;
+	unsigned int non_token_bytes;
+	unsigned int data_captured;
+	unsigned int crc_captured;
+	unsigned int pollbufpos;
+	unsigned int crc16_mismatch = 0;
+	uint16_t crc16, computed_crc16;
+	uint8_t crc7, computed_crc7;
+	uint8_t pollbuf[MMCSPI_POLL_LEN];
+	uint8_t crcbuf[MMCSPI_DATA_CRC_LEN];
+	struct bintime start;
+	struct bintime elapsed;
+
+	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
+	 * both require a passing crc-7 check and allow a passing crc-7
+	 * check to override a failing crc-16 check when crc-7 checking is
+	 * 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)
+{
+	TRACE_ENTER(dev);
+	int i;
+	unsigned int err;
+	struct mmcspi_command stop;
+	struct mmc_command mmc_stop;
+	uint8_t stop_response;
+
+	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)
+{
+	TRACE_ENTER(dev);
+
+	struct mmc_data *data = cmd->data;
+	uint8_t *data8;
+	unsigned int data_offset;
+	unsigned int num_blocks;
+	unsigned int len;
+	unsigned int err;
+	int i;
+
+	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)
+{
+	TRACE_ENTER(dev);
+	
+	unsigned int err;
+	uint16_t crc;
+	uint8_t response_token;
+	uint8_t txbuf[max(MMCSPI_POLL_LEN, 2)];
+
+	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)
+{
+	TRACE_ENTER(dev);
+
+	struct mmc_data *data = cmd->data;
+	uint8_t *data8;
+	unsigned int data_offset;
+	unsigned int num_blocks;
+	unsigned int err;
+	int i;
+	uint8_t token[2];
+	uint8_t status;
+
+	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)
+{
+	TRACE_ENTER(dev);
+
+	struct mmc_command *mmc_cmd = cmd->mmc_cmd;
+	uint32_t mmc_rsp_type = MMC_RSP(cmd->flags);
+	uint8_t *ldata = cmd->ldata.data;
+
+	TRACE(dev, ACTION, "translating SPI rsp %u to SD rsp %u\n",
+	      cmd->rsp_type, mmc_rsp_type);
+
+	if ((MMC_RSP_R1 == mmc_rsp_type) ||
+	    (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 (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 (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 (MMC_RSP_R7 == mmc_rsp_type) {
+		/* Note MMC_RSP_R6 and MMC_RSP_R7 are numerically equal, so
+		   this handles both cases. */
+
+		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 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 (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)
+{
+	TRACE_ENTER(dev);
+
+	struct mmc_command mmc_cmd;
+	struct mmcspi_command cmd;
+	unsigned int err;
+	uint8_t r1_status;
+	uint8_t rspbuf[MMCSPI_MAX_RSP_LEN];
+
+	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)
+{
+	TRACE_ENTER(dev);
+	struct mmcspi_softc *sc = device_get_softc(dev);
+	struct mmcspi_slot *slot = &sc->slot;
+	unsigned int err;
+	int i;
+
+	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)
+{
+	TRACE_ENTER(brdev);
+
+	struct mmcspi_softc *sc = device_get_softc(brdev);
+	struct mmcspi_slot *slot = device_get_ivars(reqdev);
+	struct spi_config cfg;
+	int err = 0;
+
+	MMCSPI_LOCK_SLOT(slot);
+	while (slot->bus_busy) {
+		mtx_sleep(slot, &slot->mtx, 0, "mmcspiah", 0);
+	}
+	slot->bus_busy++;
+	MMCSPI_UNLOCK_SLOT(slot);
+
+	SPIBUS_ACQUIRE_BUS(sc->busdev, sc->dev);
+
+	/* Restore the clock to the last setting, as it may have been
+	   changed by another spibus device. */
+	cfg.clock_hz = slot->host.ios.clock;
+	SPIBUS_SET_CONFIG(slot->sc->busdev, &cfg);
+
+	TRACE_EXIT(brdev);
+
+	return (err);
+}
+
+
+static int
+mmcspi_release_host(device_t brdev, device_t reqdev)
+{
+	TRACE_ENTER(brdev);
+
+	struct mmcspi_softc *sc = device_get_softc(brdev);
+	struct mmcspi_slot *slot = device_get_ivars(reqdev);
+
+	SPIBUS_RELEASE_BUS(sc->busdev, sc->dev);
+
+	MMCSPI_LOCK_SLOT(slot);
+	slot->bus_busy--;
+	MMCSPI_UNLOCK_SLOT(slot);
+
+	wakeup(slot);
+
+	TRACE_EXIT(brdev);
+
+	return (0);
+}
+
+
+static uint8_t
+update_crc7(uint8_t crc, uint8_t *buf, unsigned int len)
+{
+	int i;
+	uint8_t tmp;
+	
+	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)
+{
+
+	int i;
+	uint16_t tmp, c16;
+
+	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 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 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),
+
+	{0, 0},
+};
+
+
+static driver_t mmcspi_driver = {
+	"mmcspi",
+	mmcspi_methods,
+	sizeof(struct mmcspi_softc),
+};
+static devclass_t mmcspi_devclass;
+
+
+DRIVER_MODULE(mmcspi, spibus, mmcspi_driver, mmcspi_devclass, mmcspi_modevent_handler, 0);
+
+
+
+
+#if defined(MMCSPI_ENABLE_DEBUG_FUNCS)
+static void
+mmcspi_dump_data(device_t dev, const char *label, uint8_t *data,
+		 unsigned int len)
+{
+	TRACE_ENTER(dev);
+
+	unsigned int i, j;
+	unsigned int num_lines;
+	unsigned int residual;
+
+	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)
+{
+	TRACE_ENTER(dev);
+
+	unsigned int num_blocks = len / MMCSPI_DATA_BLOCK_LEN;
+	unsigned int residual = len - num_blocks * MMCSPI_DATA_BLOCK_LEN;
+	unsigned int i;
+
+	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
+
+
+/*
+ * These are the cards this code was tested with (initialization,
+ * identification, and single and multi block read and write with crc
+ * checking disabled and enabled), sorted by CID:
+ *
+ * Delkin Devices    2GB mid=0x02 oid=0x544d pnm="SA02G" prv=0.4 mdt=05.2010
+ * cid=02544d5341303247049c10012e00a500 csd=002e00325b5aa3a9ffffff800a800000
+ *
+ * Kingston          4GB mid=0x02 oid=0x544d pnm="SA04G" prv=0.9 mdt=12.2011
+ * cid=02544d53413034470927fec2e000bc00 csd=400e00325b5900001da77f800a400000
+ *
+ * PNY               8GB mid=0x02 oid=0x544d pnm="SA08G" prv=0.6 mdt=08.2011
+ * cid=02544d53413038470615a63ff500b800 csd=400e00325b5900003b4f7f800a400000
+ *
+ * Duracell          2GB mid=0x03 oid=0x5344 pnm="SU02G" prv=8.0 mdt=01.2011
+ * cid=0353445355303247800c8a671800b100 csd=002600325f5a83aefefbcfff92804000
+ *
+ * ADATA             4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=08.2011
+ * cid=03534453553034478002bc1eae00b800 csd=400e00325b5900001d8a7f800a404000
+ *
+ * Sandisk           4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=10.2010
+ * cid=0353445355303447800accba9e00aa00 csd=400e00325b5900001d8a7f800a404000
+ *
+ * PQI               4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=01.2011
+ * cid=0353445355303447800c981f7f00b100 csd=400e00325b5900001d8a7f800a404000
+ *
+ * Silicon Power     4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=11.2011
+ * cid=035344535530344780136f39a800bb00 csd=400e00325b5900001d8a7f800a404000
+ *
+ * Team Group        4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=12.2011
+ * cid=035344535530344780145d6e4500bc00 csd=400e00325b5900001d8a7f800a404000
+ *
+ * Fujifilm          4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=03.2011
+ * cid=035344535530344780214a790500b300 csd=400e00325b5900001d8a7f800a404000
+ *
+ * Patriot Memory    4GB mid=0x03 oid=0x5344 pnm="SU04G" prv=8.0 mdt=05.2011
+ * cid=035344535530344780808539c000b500 csd=400e00325b5900001d8a7f800a404000
+ *
+ * Sandisk          32GB mid=0x03 oid=0x5344 pnm="SU32G" prv=8.0 mdt=00.2000
+ * cid=03534453553332478061bbfd6500bb00 csd=400e00325b590000edc87f800a404000
+ *
+ * Sandisk          64GB mid=0x03 oid=0x5344 pnm="SU64G" prv=8.0 mdt=12.2011
+ * cid=035344535536344780718fc1a400bc00 csd=400e00325b590001dbd37f800a404000
+ *
+ * Dane-Elec         4GB mid=0x12 oid=0x3456 pnm="SMI  " prv=1.0 mdt=10.2011
+ * cid=123456534d4920201042020abd00ba00 csd=400e00325b5900001d6f7f800a400000
+ *
+ * G.SKILL           4GB mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000
+ * cid=1b534d30303030301008101f4100bc00 csd=400e00325b5900001d9b7f800a400000
+ *
+ * Transcend         2GB mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000
+ * cid=1b534d30303030301008d1f66900b300 csd=007fff325b5a83baf6dbdfff0e800000
+ *
+ * Samsung           2GB mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=10.2011
+ * cid=1b534d3030303030101f9ca96700ba00 csd=007fff325b5a83baf6dbdfff0e800000
+ *
+ * Transcend         2GB mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000
+ * cid=1b534d30303030301029ef1c2100b300 csd=007fff325b5a83baf6dbdfff0e800000
+ *
+ * Super Talent      1GB mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=02.2010
+ * cid=1b534d303030303010b1c68e2100a200 csd=007fff325f5983cb76dbdfff96400000
+ *
+ * Tribeca           1GB mid=0x1b oid=0x534d pnm="UD   " prv=1.0 mdt=09.2007
+ * cid=1b534d554420202010d3905704007900 csd=002f00325f5983ca6db7ff9f96400000
+ *
+ * Verbatim          2GB mid=0x27 oid=0x5048 pnm="SD02G" prv=3.0 mdt=08.2011
+ * cid=2750485344303247309d073a8f00b800 csd=005e00325b5aa3a9ffffff800a800000
+ *
+ * Maxell            4GB mid=0x27 oid=0x5048 pnm="SD04G" prv=3.0 mdt=10.2011
+ * cid=2750485344303447307c19a2f100ba00 csd=400e00325b5900001da77f800a400000
+ *
+ * Mushkin           4GB mid=0x27 oid=0x5048 pnm="SD04G" prv=3.0 mdt=08.2011
+ * cid=2750485344303447307cda40e800b800 csd=400e00325b5900001d6f7f800a400000
+ *
+ * Sony              4GB mid=0x27 oid=0x5048 pnm="SD04G" prv=3.0 mdt=09.2011
+ * cid=2750485344303447307ceadd4c00b900 csd=400e00325b5900001d6f7f800a400000
+ *
+ * Wintec           16GB mid=0x27 oid=0x5048 pnm="SD16G" prv=3.0 mdt=10.2011
+ * cid=2750485344313647307c2f8c9c00ba00 csd=400e00325b590000773f7f800a400000
+ *
+ * Lexar             4GB mid=0x28 oid=0x4245 pnm="LEXAR" prv=1.0 mdt=10.2011
+ * cid=2842454c4558415210be03d48100ba00 csd=400e00325b5900001ddd7f800a400000
+ *
+ * HP                2GB mid=0x28 oid=0x4245 pnm="SDU2G" prv=0.0 mdt=07.2011
+ * cid=2842455344553247000000144f00b700 csd=002f00325b5a83856db7ffbf16800000
+ *
+ * EP Memory         4GB mid=0x36 oid=0x414f pnm="MSD  " prv=1.0 mdt=07.2010
+ * cid=36414f4d5344202010000085b700a700 csd=400e00325b5900001dff7f800a400000
+ *
+ * RiDATA            4GB mid=0x73 oid=0x4247 pnm="NCard" prv=0.0 mdt=12.2011
+ * cid=7342474e43617264000000035200bc00 csd=400e00325b5900001d997f800a400000
+ *
+ * Pretec            8GB mid=0x73 oid=0x4247 pnm="NCard" prv=1.0 mdt=12.2011
+ * cid=7342474e43617264102600146400bc00 csd=400e00325b5900003bdd7f800a400000
+ *
+ * Transcend         8GB mid=0x74 oid=0x4a45 pnm="USD  " prv=1.0 mdt=00.2000
+ * cid=744a45555344202010051004a900bb00 csd=400e00325b5900003bbd7f800a400000
+ *
+ */
Index: sys/dev/mmc/mmc.c
===================================================================
--- sys/dev/mmc/mmc.c	(revision 231894)
+++ sys/dev/mmc/mmc.c	(working copy)
@@ -222,7 +222,7 @@
 	sc = device_get_softc(busdev);
 	MMC_LOCK(sc);
 	if (sc->owner)
-		panic("mmc: host bridge didn't seralize us.");
+		panic("mmc: host bridge didn't serialize us.");
 	sc->owner = dev;
 	MMC_UNLOCK(sc);
 
@@ -367,6 +367,8 @@
 	struct mmc_command appcmd;
 	int err = MMC_ERR_NONE, i;
 
+	cmd->flags |= MMC_CMD_IS_APP;
+
 	for (i = 0; i <= retries; i++) {
 		appcmd.opcode = MMC_APP_CMD;
 		appcmd.arg = rca << 16;
@@ -547,9 +549,14 @@
 {
 	int flags;
 
-	flags = (rca ? MMC_RSP_R1B : MMC_RSP_NONE) | MMC_CMD_AC;
-	return (mmc_wait_for_command(sc, MMC_SELECT_CARD, (uint32_t)rca << 16,
-	    flags, NULL, CMD_RETRIES));
+	if (bus_type_spi == mmcbr_get_bus_type(sc->dev)) {
+		/* card selection isn't required */
+		return (MMC_ERR_NONE);
+	} else {
+		flags = (rca ? MMC_RSP_R1B : MMC_RSP_NONE) | MMC_CMD_AC;
+		return (mmc_wait_for_command(sc, MMC_SELECT_CARD, (uint32_t)rca << 16,
+		    flags, NULL, CMD_RETRIES));
+	}
 }
 
 static int
@@ -646,6 +653,11 @@
 	uint8_t	value;
 	u_char switch_res[64];
 
+	if (bus_type_spi == mmcbr_get_bus_type(sc->dev)) {
+		/* bus timing can't be set on the card in spi mode */
+		return (MMC_ERR_NONE);
+	}
+
 	switch (timing) {
 	case bus_timing_normal:
 		value = 0;
@@ -792,7 +804,7 @@
 	1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
 };
 static const int mant[16] = {
-	10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80
+	0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80
 };
 static const int cur_min[8] = {
 	500, 1000, 5000, 10000, 25000, 35000, 60000, 100000
@@ -813,7 +825,7 @@
 	if (v == 0) {
 		m = mmc_get_bits(raw_csd, 128, 115, 4);
 		e = mmc_get_bits(raw_csd, 128, 112, 3);
-		csd->tacc = exp[e] * mant[m] + 9 / 10;
+		csd->tacc = (exp[e] * mant[m] + 9) / 10;
 		csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100;
 		m = mmc_get_bits(raw_csd, 128, 99, 4);
 		e = mmc_get_bits(raw_csd, 128, 96, 3);
@@ -841,7 +853,7 @@
 	} else if (v == 1) {
 		m = mmc_get_bits(raw_csd, 128, 115, 4);
 		e = mmc_get_bits(raw_csd, 128, 112, 3);
-		csd->tacc = exp[e] * mant[m] + 9 / 10;
+		csd->tacc = (exp[e] * mant[m] + 9) / 10;
 		csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100;
 		m = mmc_get_bits(raw_csd, 128, 99, 4);
 		e = mmc_get_bits(raw_csd, 128, 96, 3);
@@ -956,7 +968,7 @@
 }
 
 static int
-mmc_send_csd(struct mmc_softc *sc, uint16_t rca, uint32_t *rawcid)
+mmc_send_csd(struct mmc_softc *sc, uint16_t rca, uint32_t *rawcsd)
 {
 	struct mmc_command cmd;
 	int err;
@@ -966,7 +978,7 @@
 	cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
 	cmd.data = NULL;
 	err = mmc_wait_for_cmd(sc, &cmd, 0);
-	memcpy(rawcid, cmd.resp, 4 * sizeof(uint32_t));
+	memcpy(rawcsd, cmd.resp, 4 * sizeof(uint32_t));
 	return (err);
 }
 
@@ -1075,12 +1087,42 @@
 	return (err);
 }
 
+static int
+mmc_send_status(struct mmc_softc *sc, uint16_t rca, uint32_t *status)
+{
+	struct mmc_command cmd;
+	int err;
+
+	cmd.opcode = MMC_SEND_STATUS;
+	cmd.arg = rca << 16;
+	cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+	cmd.data = NULL;
+	err = mmc_wait_for_cmd(sc, &cmd, 0);
+	*status = cmd.resp[0];
+	return (err);
+}
+
+static int
+mmc_set_blocklen(struct mmc_softc *sc, uint32_t len)
+{
+	struct mmc_command cmd;
+	int err;
+
+	cmd.opcode = MMC_SET_BLOCKLEN;
+	cmd.arg = len;
+	cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+	cmd.data = NULL;
+	err = mmc_wait_for_cmd(sc, &cmd, 0);
+	return (err);
+}
+
+
 static void
 mmc_log_card(device_t dev, struct mmc_ivars *ivar, int newcard)
 {
 	device_printf(dev, "Card at relative address %d%s:\n",
 	    ivar->rca, newcard ? " added" : "");
-	device_printf(dev, " card: %s%s (0x%x/0x%x/\"%s\" rev %d.%d "
+	device_printf(dev, " card: %s%s (0x%02x/0x%04x/\"%s\" rev %d.%d "
 	    "m/d %02d.%04d s/n %08x)\n",
 	    ivar->mode == mode_sd ? "SD" : "MMC",
 	    ivar->high_cap ? " High Capacity" : "", 
@@ -1104,6 +1146,7 @@
 	struct mmc_ivars *ivar = NULL;
 	device_t *devlist;
 	int err, i, devcount, newcard;
+	uint32_t status;
 	uint32_t raw_cid[4];
 	uint32_t resp, sec_count;
 	device_t child;
@@ -1154,6 +1197,11 @@
 			ivar->rca = resp >> 16;
 			/* Get card CSD. */
 			mmc_send_csd(sc, ivar->rca, ivar->raw_csd);
+			if (bootverbose || mmc_debug) {
+				device_printf(sc->dev, "%sard detected (CSD %08x%08x%08x%08x)\n",
+					      newcard ? "New c" : "C",
+					      ivar->raw_csd[0], ivar->raw_csd[1], ivar->raw_csd[2], ivar->raw_csd[3]);
+			}
 			mmc_decode_csd_sd(ivar->raw_csd, &ivar->csd);
 			ivar->sec_count = ivar->csd.capacity / MMC_SECTOR_SIZE;
 			if (ivar->csd.csd_structure > 0)
@@ -1161,6 +1209,17 @@
 			ivar->tran_speed = ivar->csd.tran_speed;
 			ivar->erase_sector = ivar->csd.erase_sector * 
 			    ivar->csd.write_bl_len / MMC_SECTOR_SIZE;
+			
+			err = mmc_send_status(sc, ivar->rca, &status);
+			if (err != MMC_ERR_NONE) {
+				device_printf(sc->dev, "Error reading card status %d\n", err);
+				break;
+			}
+			if (status & R1_CARD_IS_LOCKED) {
+				device_printf(sc->dev, "Card is password protected, skipping.\n");
+				break;
+			}
+
 			/* Get card SCR. Card must be selected to fetch it. */
 			mmc_select_card(sc, ivar->rca);
 			mmc_app_send_scr(sc, ivar->rca, ivar->raw_scr);
@@ -1188,6 +1247,22 @@
 			if ((mmcbr_get_caps(sc->dev) & MMC_CAP_4_BIT_DATA) &&
 			    (ivar->scr.bus_widths & SD_SCR_BUS_WIDTH_4))
 				ivar->bus_width = bus_width_4;
+
+			/* 
+			 * Some cards that report maximum I/O block sizes
+			 * greater than 512 require the block length to be
+			 * set to 512, even though that is supposed to be
+			 * the default.  Examples:
+			 *
+			 * Transcend 2GB SDSC card, cid:
+			 * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000
+			 */
+			if ((MMC_SECTOR_SIZE != ivar->csd.read_bl_len) ||
+			    (MMC_SECTOR_SIZE != ivar->csd.write_bl_len)) {
+				mmc_set_blocklen(sc, MMC_SECTOR_SIZE); 
+			}
+
+
 			if (bootverbose || mmc_debug)
 				mmc_log_card(sc->dev, ivar, newcard);
 			if (newcard) {
@@ -1202,11 +1277,28 @@
 		mmc_set_relative_addr(sc, ivar->rca);
 		/* Get card CSD. */
 		mmc_send_csd(sc, ivar->rca, ivar->raw_csd);
+		if (bootverbose || mmc_debug) {
+			device_printf(sc->dev, "%sard detected (CSD %08x%08x%08x%08x)\n",
+			    newcard ? "New c" : "C",
+			    ivar->raw_csd[0], ivar->raw_csd[1], ivar->raw_csd[2], ivar->raw_csd[3]);
+		}
+
 		mmc_decode_csd_mmc(ivar->raw_csd, &ivar->csd);
 		ivar->sec_count = ivar->csd.capacity / MMC_SECTOR_SIZE;
 		ivar->tran_speed = ivar->csd.tran_speed;
 		ivar->erase_sector = ivar->csd.erase_sector * 
 		    ivar->csd.write_bl_len / MMC_SECTOR_SIZE;
+
+		err = mmc_send_status(sc, ivar->rca, &status);
+		if (err != MMC_ERR_NONE) {
+			device_printf(sc->dev, "Error reading card status %d\n", err);
+			break;
+		}
+		if (status & R1_CARD_IS_LOCKED) {
+			device_printf(sc->dev, "Card is password protected, skipping.\n");
+			break;
+		}
+
 		/* Only MMC >= 4.x cards support EXT_CSD. */
 		if (ivar->csd.spec_vers >= 4) {
 			/* Card must be selected to fetch EXT_CSD. */
@@ -1245,6 +1337,21 @@
 			ivar->bus_width = bus_width_1;
 			ivar->timing = bus_timing_normal;
 		}
+
+		/*
+		 * Some cards that report maximum I/O block sizes greater
+		 * than 512 require the block length to be set to 512, even
+		 * though that is supposed to be the default.  Examples:
+		 *
+		 * Transcend 2GB SDSC card, cid:
+		 * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000
+		 */
+		if ((MMC_SECTOR_SIZE != ivar->csd.read_bl_len) ||
+		    (MMC_SECTOR_SIZE != ivar->csd.write_bl_len)) {
+			mmc_set_blocklen(sc, MMC_SECTOR_SIZE); 
+		}
+
+
 		if (bootverbose || mmc_debug)
 			mmc_log_card(sc->dev, ivar, newcard);
 		if (newcard) {
@@ -1320,7 +1427,7 @@
 		err = mmc_send_if_cond(sc, 1);
 		if ((bootverbose || mmc_debug) && err == 0)
 			device_printf(sc->dev, "SD 2.0 interface conditions: OK\n");
-		if (mmc_send_app_op_cond(sc, err ? 0 : MMC_OCR_CCS, &ocr) !=
+		if (mmc_send_app_op_cond(sc, 0, &ocr) !=
 		    MMC_ERR_NONE) {
 			if (bootverbose || mmc_debug)
 				device_printf(sc->dev, "SD probe: failed\n");
@@ -1540,3 +1647,4 @@
 
 DRIVER_MODULE(mmc, at91_mci, mmc_driver, mmc_devclass, NULL, NULL);
 DRIVER_MODULE(mmc, sdhci, mmc_driver, mmc_devclass, NULL, NULL);
+DRIVER_MODULE(mmc, mmcspi, mmc_driver, mmc_devclass, NULL, NULL);
Index: sys/dev/sdhci/sdhci.c
===================================================================
--- sys/dev/sdhci/sdhci.c	(revision 231894)
+++ sys/dev/sdhci/sdhci.c	(working copy)
@@ -1442,6 +1442,9 @@
 	switch (which) {
 	default:
 		return (EINVAL);
+	case MMCBR_IVAR_BUS_TYPE:
+		*result = bus_type_sd;
+		break;
 	case MMCBR_IVAR_BUS_MODE:
 		*result = slot->host.ios.bus_mode;
 		break;
@@ -1534,6 +1537,7 @@
 	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:
Index: sys/dev/spibus/spi.h
===================================================================
--- sys/dev/spibus/spi.h	(revision 231894)
+++ sys/dev/spibus/spi.h	(working copy)
@@ -1,5 +1,9 @@
 /* $FreeBSD$ */
 
+#define	SPI_SKIP_CHIP_SELECT		0x0001 /* bus is already acquired */
+#define	SPI_CHIP_SELECT_HIGH		0x0002 /* set chip select high for
+						  duration of transfer */
+
 struct spi_command {
 	void	*tx_cmd;
 	uint32_t tx_cmd_sz;
@@ -9,4 +13,11 @@
 	uint32_t tx_data_sz;
 	void	*rx_data;
 	uint32_t rx_data_sz;
+	uint16_t flags;
 };
+
+struct spi_config {
+	unsigned int clock_hz;  /* sending a 0 to spibus_set_config
+				   requests the maximum possible
+				   frequency */
+};
Index: sys/dev/spibus/spibus.c
===================================================================
--- sys/dev/spibus/spibus.c	(revision 231894)
+++ sys/dev/spibus/spibus.c	(working copy)
@@ -22,6 +22,7 @@
 static int
 spibus_probe(device_t dev)
 {
+
 	device_set_desc(dev, "spibus bus");
 	return (0);
 }
@@ -60,6 +61,7 @@
 static int
 spibus_suspend(device_t dev)
 {
+
 	return (bus_generic_suspend(dev));
 }
 
@@ -67,6 +69,7 @@
 int
 spibus_resume(device_t dev)
 {
+
 	return (bus_generic_resume(dev));
 }
 
@@ -107,6 +110,7 @@
 spibus_child_pnpinfo_str(device_t bus, device_t child, char *buf,
     size_t buflen)
 {
+
 	*buf = '\0';
 	return (0);
 }
@@ -155,12 +159,48 @@
 	resource_int_value(dname, dunit, "cs", &devi->cs);
 }
 
+static void
+spibus_acquire_bus_impl(device_t dev, device_t child)
+{
+
+	SPIBUS_ACQUIRE_BUS(device_get_parent(dev), child);
+}
+
+static void
+spibus_release_bus_impl(device_t dev, device_t child)
+{
+
+	SPIBUS_RELEASE_BUS(device_get_parent(dev), child);
+}
+
 static int
 spibus_transfer_impl(device_t dev, device_t child, struct spi_command *cmd)
 {
+
 	return (SPIBUS_TRANSFER(device_get_parent(dev), child, cmd));
 }
 
+static int
+spibus_write_impl(device_t dev, uint32_t reg, uint32_t data)
+{
+
+	return (SPIBUS_WRITE(device_get_parent(dev), reg, data));
+}
+
+static int
+spibus_get_config_impl(device_t dev, struct spi_config *cfg)
+{
+
+	return (SPIBUS_GET_CONFIG(device_get_parent(dev), cfg));
+}
+
+static int
+spibus_set_config_impl(device_t dev, struct spi_config *cfg)
+{
+
+	return (SPIBUS_SET_CONFIG(device_get_parent(dev), cfg));
+}
+
 static device_method_t spibus_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe,		spibus_probe),
@@ -180,7 +220,12 @@
 	DEVMETHOD(bus_hinted_child,	spibus_hinted_child),
 
 	/* spibus interface */
+	DEVMETHOD(spibus_acquire_bus,	spibus_acquire_bus_impl),
+	DEVMETHOD(spibus_release_bus,	spibus_release_bus_impl),
 	DEVMETHOD(spibus_transfer,	spibus_transfer_impl),
+	DEVMETHOD(spibus_write,		spibus_write_impl),
+	DEVMETHOD(spibus_get_config,	spibus_get_config_impl),
+	DEVMETHOD(spibus_set_config,	spibus_set_config_impl),
 
 	DEVMETHOD_END
 };
Index: sys/dev/spibus/spibus_if.m
===================================================================
--- sys/dev/spibus/spibus_if.m	(revision 231894)
+++ sys/dev/spibus/spibus_if.m	(working copy)
@@ -32,6 +32,22 @@
 INTERFACE spibus;
 
 #
+# Acquire bus and select the device
+#
+METHOD void acquire_bus {
+	device_t dev;
+	device_t child;
+};
+
+#
+# Release bus and deselect the device
+#
+METHOD void release_bus {
+	device_t dev;
+	device_t child;
+};
+
+#
 # Do a spi command
 #
 METHOD int transfer {
@@ -39,3 +55,28 @@
 	device_t child;
 	struct spi_command *cmd;
 };
+
+#
+# Write a command to spibus
+#
+METHOD int write {
+	device_t dev;
+	uint32_t reg;
+	uint32_t data;
+};
+
+#
+# Get spibus configuration
+#
+METHOD int get_config {
+	device_t dev;
+	struct spi_config *cfg;
+};
+
+#
+# Set spibus configuration
+#
+METHOD int set_config {
+	device_t dev;
+	struct spi_config *cfg;
+};

[-- Attachment #3 --]
Index: sys/mips/atheros/ar71xx_spi.c
===================================================================
--- sys/mips/atheros/ar71xx_spi.c	(revision 231894)
+++ sys/mips/atheros/ar71xx_spi.c	(working copy)
@@ -35,7 +35,9 @@
 #include <sys/interrupt.h>
 #include <sys/malloc.h>
 #include <sys/kernel.h>
+#include <sys/lock.h>
 #include <sys/module.h>
+#include <sys/mutex.h>
 #include <sys/rman.h>
 
 #include <vm/vm.h>
@@ -51,6 +53,7 @@
 #include "spibus_if.h"
 
 #include <mips/atheros/ar71xxreg.h>
+#include <mips/atheros/ar71xx_cpudef.h>
 
 #undef AR71XX_SPI_DEBUG
 #ifdef AR71XX_SPI_DEBUG
@@ -59,6 +62,7 @@
 #define dprintf(x, arg...)
 #endif
 
+
 /*
  * register space access macros
  */
@@ -76,18 +80,125 @@
 
 struct ar71xx_spi_softc {
 	device_t		sc_dev;
+	device_t		sc_owner;
+	struct mtx		sc_mtx;
 	struct resource		*sc_mem_res;
 	uint32_t		sc_reg_ctrl;
+	struct spi_config	sc_config;
+	int			sc_pre_delay;
+	int			sc_post_delay;
 };
 
+#define	AR71XX_SPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define	AR71XX_SPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define	AR71XX_SPI_LOCK_INIT(_sc) \
+	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \
+	    "ar71xx_spi", MTX_DEF)
+#define	AR71XX_SPI_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx);
+#define	AR71XX_SPI_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED);
+#define	AR71XX_SPI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
+
 static int
 ar71xx_spi_probe(device_t dev)
 {
+
 	device_set_desc(dev, "AR71XX SPI");
 	return (0);
 }
 
+
+/* 
+ * Configure the hardware clock to achieve the requested frequency without
+ * going over.  If the requested frequency is less than the minimum possible
+ * hardware frequency, software delays will be used to meet the target.
+ */
 static int
+ar71xx_spi_set_clock(struct ar71xx_spi_softc *sc, unsigned int hz)
+{
+	unsigned int div;
+	unsigned int actual_divisor;
+	unsigned int actual_hz;
+	unsigned int addl_period_us;
+	uint64_t temp64;
+
+	if (0 == hz) {
+		/* maximum frequency possible */
+		div = 0;
+	} else {
+		/* 
+		 * Figure the divider value that would achieve the closest
+		 * frequency to that requested without going over.
+		 */
+		div = (ar71xx_ahb_freq() - 1) / (2 * hz);
+	}
+
+	
+	/* Avoid chip bug at 0 == div */
+	if (0 == div) {
+		div = 1;
+	}
+
+	if (div > SPI_CTRL_CLOCK_DIVIDER_MAX) {
+		div = SPI_CTRL_CLOCK_DIVIDER_MAX;
+	}
+
+	actual_divisor = 2 * (div + 1);
+
+	/* What the hardware will generate, rounded up. */
+	actual_hz = (ar71xx_ahb_freq() + actual_divisor - 1) / actual_divisor;
+	
+	if (0 == hz) {
+		hz = actual_hz;
+	}
+
+	/* 
+	 * At requested clock rates below the hardware minimum, figure out
+	 * what delays need to be inserted when feeding bits to the hardware
+	 * in order to stay under the target.
+	 */
+	if (actual_hz > hz) {
+
+		/* 
+		 * Compute the additional number of microseconds we need in
+		 * the clock period.  In the spirit of not exceeding the
+		 * target frequency, round what we need up, round what the
+		 * hardware gives us down.
+		 */
+		addl_period_us = (1000000 + hz - 1) / hz + 1000000 / actual_hz;
+
+		sc->sc_pre_delay = addl_period_us / 2;
+		sc->sc_post_delay = addl_period_us - sc->sc_pre_delay;
+
+		/* 
+		 * A high resolution, rounded up inversion of sum of
+		 * hardware and software period components, so one can be
+		 * confident a requested hardware maximum clock rate is not
+		 * being exceeded.
+		 */
+		temp64 = 1000000ull * actual_divisor  + 
+		    (uint64_t)addl_period_us * (uint64_t)ar71xx_ahb_freq();
+
+		sc->sc_config.clock_hz = 
+		    (1000000ull * (uint64_t)ar71xx_ahb_freq() + temp64 - 1) /
+		    temp64;
+	} else {
+		sc->sc_pre_delay = 0;
+		sc->sc_post_delay = 0;
+		sc->sc_config.clock_hz = ar71xx_ahb_freq() / actual_divisor;
+	}
+
+#if 0
+	device_printf(sc->sc_dev, "Requested hz=%u reg=0x%02x pre=%u post=%u\n",
+		      hz, div,
+		      sc->sc_pre_delay, sc->sc_post_delay);
+#endif
+
+	SPI_WRITE(sc, AR71XX_SPI_CTRL, SPI_CTRL_REMAP_DISABLE | div);	
+
+	return (0);
+}
+
+static int
 ar71xx_spi_attach(device_t dev)
 {
 	struct ar71xx_spi_softc *sc = device_get_softc(dev);
@@ -102,12 +213,20 @@
 		return (ENXIO);
 	}
 
+	AR71XX_SPI_LOCK_INIT(sc);
 
 	SPI_WRITE(sc, AR71XX_SPI_FS, 1);
-	sc->sc_reg_ctrl  = SPI_READ(sc, AR71XX_SPI_CTRL);
-	SPI_WRITE(sc, AR71XX_SPI_CTRL, 0x43);
-	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CSMASK);
+	sc->sc_reg_ctrl = SPI_READ(sc, AR71XX_SPI_CTRL);
+	
+	/*
+	 * The following is equivalent to AR71XX_SPI_CTRL = 0x43, which has
+	 * been the historical default.
+	 */
+	ar71xx_spi_set_clock(sc, (ar71xx_ahb_freq() - 1) / 6);
 
+	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CS(0));
+
+	device_printf(dev, "attaching at spibus0\n");
 	device_add_child(dev, "spibus", 0);
 	return (bus_generic_attach(dev));
 }
@@ -115,49 +234,97 @@
 static void
 ar71xx_spi_chip_activate(struct ar71xx_spi_softc *sc, int cs)
 {
-	uint32_t ioctrl = SPI_IO_CTRL_CSMASK;
+
 	/*
-	 * Put respective CSx to low
+	 * Activate the respective device (we have three CS pins allowing
+	 * 7 muxed devices on bus).
+	 * At any given moment only one device should be selected on bus. 
+	 * if cs is set to 0 it will be the same as SPI_IO_CTRL_CSMASK
+	 * (0x7 - all pins high - no device selected).
 	 */
-	ioctrl &= ~(SPI_IO_CTRL_CS0 << cs);
-
-	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, ioctrl);
+	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CS(cs));
 }
 
 static void
 ar71xx_spi_chip_deactivate(struct ar71xx_spi_softc *sc, int cs)
 {
+
 	/*
-	 * Put all CSx to high
+	 * Deactivate all devices, put all CSx to high
 	 */
-	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CSMASK);
+	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CS(0));
 }
 
+static void
+ar71xx_spi_acquire_bus(device_t dev, device_t child)
+{
+	struct spibus_ivar *devi = SPIBUS_IVAR(child);
+	struct ar71xx_spi_softc *sc;
+
+	sc = device_get_softc(dev);
+	AR71XX_SPI_ASSERT_UNLOCKED(sc);
+
+	AR71XX_SPI_LOCK(sc);
+	if (sc->sc_owner)
+		panic("ar71xx_spi: cannot serialize the access to device.");
+
+	sc->sc_owner = child;
+	ar71xx_spi_chip_activate(sc, devi->cs);
+}
+
+static void
+ar71xx_spi_release_bus(device_t dev, device_t child)
+{
+	struct spibus_ivar *devi = SPIBUS_IVAR(child);
+	struct ar71xx_spi_softc *sc;
+
+	sc = device_get_softc(dev);
+	AR71XX_SPI_ASSERT_LOCKED(sc);
+
+	if (!sc->sc_owner)
+		panic("ar71xx_spi: releasing unowned bus.");
+	if (sc->sc_owner != child)
+		panic("ar71xx_spi: you don't own the bus. game over.");
+
+	ar71xx_spi_chip_deactivate(sc, devi->cs);
+	sc->sc_owner = NULL;
+	AR71XX_SPI_UNLOCK(sc);
+}
+
 static uint8_t
 ar71xx_spi_txrx(struct ar71xx_spi_softc *sc, int cs, uint8_t data)
 {
 	int bit;
-	/* CS0 */
-	uint32_t ioctrl = SPI_IO_CTRL_CSMASK;
+	uint32_t ioctrl;
+
+	AR71XX_SPI_ASSERT_LOCKED(sc);
+
 	/*
-	 * low-level for selected CS
+	 * activate the selected CS
 	 */
-	ioctrl &= ~(SPI_IO_CTRL_CS0 << cs);
+	ioctrl = SPI_IO_CTRL_CS(cs);
 
-	uint32_t iod, rds;
+	uint32_t iod = 0, rds;
 	for (bit = 7; bit >=0; bit--) {
 		if (data & (1 << bit))
 			iod = ioctrl | SPI_IO_CTRL_DO;
 		else
 			iod = ioctrl & ~SPI_IO_CTRL_DO;
+
 		SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, iod);
+		if (sc->sc_pre_delay) {
+			DELAY(sc->sc_pre_delay);
+		}
 		SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, iod | SPI_IO_CTRL_CLK);
+		if (sc->sc_post_delay) {
+			DELAY(sc->sc_post_delay);
+		}
 	}
 
 	/*
 	 * Provide falling edge for connected device by clear clock bit.
 	 */
-	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, iod);
+	SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, iod);	
 	rds = SPI_READ(sc, AR71XX_SPI_RDS);
 
 	return (rds & 0xff);
@@ -169,12 +336,19 @@
 	struct ar71xx_spi_softc *sc;
 	uint8_t *buf_in, *buf_out;
 	struct spibus_ivar *devi = SPIBUS_IVAR(child);
+	int cs = devi->cs;
 	int i;
 
 	sc = device_get_softc(dev);
 
-	ar71xx_spi_chip_activate(sc, devi->cs);
+	if ((cmd->flags & SPI_SKIP_CHIP_SELECT) == 0)
+		ar71xx_spi_acquire_bus(dev, child);
 
+	if (cmd->flags & SPI_CHIP_SELECT_HIGH) {
+		ar71xx_spi_chip_deactivate(sc, devi->cs);
+		cs = 0;
+	}
+
 	KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, 
 	    ("TX/RX command sizes should be equal"));
 	KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, 
@@ -186,7 +360,7 @@
 	buf_out = (uint8_t *)cmd->tx_cmd;
 	buf_in = (uint8_t *)cmd->rx_cmd;
 	for (i = 0; i < cmd->tx_cmd_sz; i++)
-		buf_in[i] = ar71xx_spi_txrx(sc, devi->cs, buf_out[i]);
+		buf_in[i] = ar71xx_spi_txrx(sc, cs, buf_out[i]);
 
 	/*
 	 * Receive/transmit data (depends on  command)
@@ -194,14 +368,53 @@
 	buf_out = (uint8_t *)cmd->tx_data;
 	buf_in = (uint8_t *)cmd->rx_data;
 	for (i = 0; i < cmd->tx_data_sz; i++)
-		buf_in[i] = ar71xx_spi_txrx(sc, devi->cs, buf_out[i]);
+		buf_in[i] = ar71xx_spi_txrx(sc, cs, buf_out[i]);
 
-	ar71xx_spi_chip_deactivate(sc, devi->cs);
+	if ((cmd->flags & SPI_SKIP_CHIP_SELECT) == 0) {
+		ar71xx_spi_release_bus(dev, child);
+	} else if (cmd->flags & SPI_CHIP_SELECT_HIGH) {
+		ar71xx_spi_chip_activate(sc, devi->cs);
+	}
 
 	return (0);
 }
 
 static int
+ar71xx_spi_write(device_t dev, uint32_t reg, uint32_t data)
+{
+	struct ar71xx_spi_softc *sc = device_get_softc(dev);
+
+	AR71XX_SPI_ASSERT_LOCKED(sc);
+	SPI_WRITE(sc, reg, data);
+	return (0);
+}
+
+static int
+ar71xx_spi_set_config(device_t dev, struct spi_config *cfg)
+{
+	struct ar71xx_spi_softc *sc = device_get_softc(dev);
+
+	AR71XX_SPI_ASSERT_LOCKED(sc);
+	if (ar71xx_spi_set_clock(sc, cfg->clock_hz)) {
+		return (ENXIO);
+	}
+
+	return (0);
+}
+
+static int
+ar71xx_spi_get_config(device_t dev, struct spi_config *cfg)
+{
+	struct ar71xx_spi_softc *sc = device_get_softc(dev);
+
+	AR71XX_SPI_ASSERT_LOCKED(sc);
+
+	*cfg = sc->sc_config;
+
+	return (0);
+}
+
+static int
 ar71xx_spi_detach(device_t dev)
 {
 	struct ar71xx_spi_softc *sc = device_get_softc(dev);
@@ -209,6 +422,8 @@
 	SPI_WRITE(sc, AR71XX_SPI_CTRL, sc->sc_reg_ctrl);
 	SPI_WRITE(sc, AR71XX_SPI_FS, 0);
 
+	AR71XX_SPI_LOCK_DESTROY(sc);
+
 	if (sc->sc_mem_res)
 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
 
@@ -221,7 +436,12 @@
 	DEVMETHOD(device_attach,	ar71xx_spi_attach),
 	DEVMETHOD(device_detach,	ar71xx_spi_detach),
 
+	DEVMETHOD(spibus_acquire_bus,	ar71xx_spi_acquire_bus),
+	DEVMETHOD(spibus_release_bus,	ar71xx_spi_release_bus),
 	DEVMETHOD(spibus_transfer,	ar71xx_spi_transfer),
+	DEVMETHOD(spibus_write,		ar71xx_spi_write),
+	DEVMETHOD(spibus_get_config,	ar71xx_spi_get_config),
+	DEVMETHOD(spibus_set_config,	ar71xx_spi_set_config),
 
 	{0, 0}
 };
Index: sys/mips/atheros/ar71xxreg.h
===================================================================
--- sys/mips/atheros/ar71xxreg.h	(revision 231894)
+++ sys/mips/atheros/ar71xxreg.h	(working copy)
@@ -476,11 +483,12 @@
 #define		AR71XX_SPI_CTRL		0x04
 #define			SPI_CTRL_REMAP_DISABLE		(1 << 6)
 #define			SPI_CTRL_CLOCK_DIVIDER_MASK	((1 << 6) - 1)
+#define  		SPI_CTRL_CLOCK_DIVIDER_MAX	SPI_CTRL_CLOCK_DIVIDER_MASK
 #define		AR71XX_SPI_IO_CTRL	0x08
 #define			SPI_IO_CTRL_CS2			(1 << 18)
 #define			SPI_IO_CTRL_CS1			(1 << 17)
 #define			SPI_IO_CTRL_CS0			(1 << 16)
-#define			SPI_IO_CTRL_CSMASK		(7 << 16)
+#define			SPI_IO_CTRL_CS(cs)		((7 - cs) << 16)
 #define			SPI_IO_CTRL_CLK			(1 << 8)
 #define			SPI_IO_CTRL_DO			1
 #define		AR71XX_SPI_RDS		0x0C

Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAD44qMW5jyno5BB%2BQg0Tg_wZA3opK%2BYVUzXHO69Y27ZKtDm7Ew>