/* $NetBSD: $ */

/*-
  * Copyright (c) 2013 UPMC/LIP6
  * All rights reserved.
  * This software is distributed under the following condiions
  * compliant with the NetBSD foundation policy.
  *
  * 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 THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION 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.
  */

/* driver for the VHDL vci controller */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: machdep.c,v 1.223 2008/07/02 17:28:56 ad Exp $");

#include <sys/param.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/kauth.h>
#include <sys/kthread.h>
#include <sys/condvar.h>

#include <dev/sdmmc/sdmmcvar.h>
#include <dev/sdmmc/sdmmcchip.h>

#include <uvm/uvm.h>

#include <machine/autoconf.h>
#include <machine/bus.h>

#undef VCI_DEBUG
#ifdef VCI_DEBUG
#define DPRINTF(m, x) {if (vcidebug_mask & (m)) printf x;}
#define VCIDEBUG_FUNC	0x1
#define VCIDEBUG_XFER	0x2
#define VCIDEBUG_XFER2	0x4
#define VCIDEBUG_INIT	0x8
static int vcidebug_mask = VCIDEBUG_FUNC|VCIDEBUG_XFER|VCIDEBUG_XFER2|VCIDEBUG_INIT;
#else
#define DPRINTF(m, x)
#endif

/* VCI device registers */
#define SPI_DATA_TXRX(i)	(0x0 + (i))
#define SPI_DATA_RXTX_MASK	(0xFF   ) /* Mask for the an RX/TX value   */

#define SPI_CTRL		0x4
#define SPI_CTRL_DMA_ERR	(1 << 17) /* DMA error                     */
#define SPI_CTRL_DMA_BSY	(1 << 16) /* DMA in progress               */
#define SPI_CTRL_CPOL		(1 << 15) /* Clock polarity                */
#define SPI_CTRL_CPHA		(1 << 14) /* Clock phase                   */
#define SPI_CTRL_IE_EN		(1 << 12) /* Interrupt Enable              */
#define SPI_CTRL_LSB_EN		(1 << 11) /* LSB are sent first            */
#define SPI_CTRL_GO_BSY		(1 << 8 ) /* Start the transfer            */
#define SPI_CTRL_CHAR_LEN_MASK	(0xFF   ) /* Bits transmited in 1 transfer */

/* depretacted flags, only used by the vci_oc_spi_controller */
#define SPI_CTRL_TXN_EN		(1 << 10) /* MOSI is changed on neg edge   */
#define SPI_CTRL_RXN_EN		(1 << 9 ) /* MISO is latched on neg edge   */


#define SPI_DIVIDER		0x5

#define SPI_SS			0x6

#define SPI_DMA_BASE			0x7
#define SPI_DMA_BASEH			0x8
#define SPI_DMA_COUNT			0x9

#define SPI_DMA_COUNT_READ	(1 << 0) /* operation is a read (else write) */

#define SPI_DEVICE_REG_READ(sc, reg) \
	bus_space_read_4((sc)->sc_tag, (sc)->sc_handle, (reg) * 4)
#define SPI_DEVICE_REG_WRITE(sc, reg, val) \
	bus_space_write_4((sc)->sc_tag, (sc)->sc_handle, (reg) * 4, (val))

#define SDCARD_COMMAND_TIMEOUT 100
#define MMC_TIME_OVER(sc)  ((sc)->sc_busclk /  16) /* about 1s */

static int  vcispi_match(device_t, cfdata_t, void *);
static void vcispi_attach(device_t, device_t, void *);
static void vcispi_io_thread(void *);

static int      vcispi_host_reset(sdmmc_chipset_handle_t);
static uint32_t vcispi_host_ocr(sdmmc_chipset_handle_t);
static int      vcispi_host_maxblklen(sdmmc_chipset_handle_t);
static int      vcispi_card_detect(sdmmc_chipset_handle_t);
static int      vcispi_write_protect(sdmmc_chipset_handle_t);
static int      vcispi_bus_power(sdmmc_chipset_handle_t, uint32_t);
static int      vcispi_bus_clock(sdmmc_chipset_handle_t, int);
static int      vcispi_bus_width(sdmmc_chipset_handle_t, int);
static void     vcispi_exec_command(sdmmc_chipset_handle_t,
		    struct sdmmc_command *);

static struct sdmmc_chip_functions vcispi_sd_functions = {
	.host_reset	= vcispi_host_reset,
        .host_ocr	= vcispi_host_ocr,
        .host_maxblklen	= vcispi_host_maxblklen,
        .card_detect	= vcispi_card_detect,
        .write_protect	= vcispi_write_protect,
        .bus_power	= vcispi_bus_power,
        .bus_clock	= vcispi_bus_clock,
        .bus_width	= vcispi_bus_width,
        .exec_command	= vcispi_exec_command,
        .card_enable_intr = NULL,
        .card_intr_ack	= NULL,
};

static void	vcispi_spi_initialize(sdmmc_chipset_handle_t);

static struct sdmmc_spi_chip_functions vcispi_spi_chip_functions = {
	.initialize	= vcispi_spi_initialize,
};

struct vcispi_softc {
	device_t sc_dev;
	bus_space_tag_t sc_tag;
	bus_space_handle_t sc_handle;
	irq_t sc_ih;
	bus_dma_tag_t sc_dmat;
	bus_dmamap_t  sc_dmamap;
	uint32_t sc_div_freq; /* input freq in hz */
	uint32_t sc_busclk; /* bus freq in hz */
	kmutex_t sc_mtx;
	kcondvar_t sc_io_cv;
	kcondvar_t sc_intr_cv;
	kcondvar_t sc_done_cv;
	lwp_t	   *sc_lwp;
	struct sdmmc_command *sc_sdmmc_cmd;
	uint32_t sc_ctrl;
	volatile uint32_t sc_ctrl_intr;
	device_t sc_sdmmc;
};

static void vcispi_gen_tick(struct vcispi_softc *, int);
static uint8_t vcispi_receive_char(struct vcispi_softc *);
static uint8_t vcispi_receive_char_nowait(struct vcispi_softc *);
static void vcispi_put_tx(struct vcispi_softc *sc, uint8_t);
static void vcispi_wait(struct vcispi_softc *sc);
static void vcispi_cmd_cfgread(struct vcispi_softc *, struct sdmmc_command *);
static void vcispi_cmd_read(struct vcispi_softc *, struct sdmmc_command *);
static void vcispi_cmd_write(struct vcispi_softc *, struct sdmmc_command *);
static int  vcispi_intr(void *);


CFATTACH_DECL_NEW(vcispi, sizeof(struct vcispi_softc),
    vcispi_match, vcispi_attach, NULL, NULL);

int
vcispi_match(device_t parent, cfdata_t match, void *aux)
{
	struct tsardevs_attach_args *tsd = aux;
	if (strcmp(tsd->tsd_devtype, "vci:ioc") == 0)
		return 1;
	if (strcmp(tsd->tsd_devtype, "vci:spi") == 0)
		return 1;
	return 0;
}

void
vcispi_attach(device_t parent, device_t self, void *aux)
{
	struct vcispi_softc *sc = device_private(self);
	struct tsardevs_attach_args *tsd = aux;
	const struct boot_fdt_prop *prop;
	int error;
	struct sdmmcbus_attach_args ssaa = {
		.saa_busname = "sdmmc",
		.saa_sct = &vcispi_sd_functions,
		.saa_spi_sct = &vcispi_spi_chip_functions,
		.saa_sch =  sc,
		.saa_dmat = NULL,
		.saa_clkmin = 200, /* 200Khz */
		.saa_clkmax = 10000, /* 10Mhz */
		.saa_caps = SMC_CAPS_SPI_MODE
			  | SMC_CAPS_SINGLE_ONLY
			  | SMC_CAPS_POLL_CARD_DET,
	};

	aprint_normal(": SPI controller");

	sc->sc_dev = self;
	sc->sc_tag = tsd->tsd_tag;
	sc->sc_dmat = NULL;

	prop = fdt_find_prop(tsd->tsd_node->fdt_node_data, "dma");
	if (prop != NULL && be32toh(prop->prop_value[0]) == 1) {
		sc->sc_dmat = tsd->tsd_dmatag;
		aprint_normal(" with DMA");
	}
	if (fdt_get_clock_freq(tsd->tsd_node, &sc->sc_div_freq)) {
		char buf[8];
		humanize_number(buf, 8, sc->sc_div_freq, "Hz", 1000);
		aprint_verbose(", %s input clock", buf);
	} else {
		sc->sc_div_freq = curcpu()->ci_cpu_freq;
	}
	aprint_normal("\n");

	mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_SDMMC);
	cv_init(&sc->sc_io_cv, "spiio");
	cv_init(&sc->sc_intr_cv, "spiintr");
	cv_init(&sc->sc_done_cv, "spiiocmpl");

	/* map the device */
	if (bus_space_map(sc->sc_tag,
	    tsd->tsd_reg[0].reg_addr,tsd->tsd_reg[0].reg_size, 0,
	    &sc->sc_handle)) {
			aprint_error_dev(self, "couldn't map registers\n");
			return;
	}

	if (sc->sc_dmat != NULL) {
		char buf[40];
		error = bus_dmamap_create(sc->sc_dmat, MAXPHYS, 1, MAXPHYS,
		    0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_dmamap);
		if (error) {
			aprint_error_dev(self, "can't create DMA map (%d)\n",
			    error);
			return;
		}
		sc->sc_ih = intr_establish(tsd->tsd_irq, IPL_SDMMC,
		    device_xname(self), buf, vcispi_intr, sc, NULL);
		if (sc->sc_ih == NULL) {
			aprint_error_dev(self, "can't establish interrupt\n");
			return;
		} else {
			aprint_normal_dev(self, "interrupting at %s\n", buf);
		}
	}
	sc->sc_ctrl = SPI_CTRL_TXN_EN | 8 /* char len */;

	DPRINTF(VCIDEBUG_INIT, ("init: ctrl 0x%x\n", SPI_DEVICE_REG_READ(sc, SPI_CTRL)));
	SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl);
	vcispi_bus_clock(sc, 200);

	if (kthread_create(PRI_BIO, KTHREAD_MPSAFE, NULL,
	    vcispi_io_thread, sc, &sc->sc_lwp, "%s", device_xname(self))) {
		aprint_error_dev(self, "couldn't create task thread\n");
		return;
	}
	sc->sc_sdmmc = config_found(sc->sc_dev, &ssaa, NULL);
	return;
}

static void
vcispi_io_thread(void *arg)
{
        struct vcispi_softc *sc = (struct vcispi_softc *)arg;
	struct sdmmc_command *cmd;
	int i;
	int rsp;
	int retrycount;

	mutex_enter(&sc->sc_mtx);
	for(;;) {
		while (sc->sc_sdmmc_cmd == NULL)
			cv_wait(&sc->sc_io_cv, &sc->sc_mtx);
		mutex_exit(&sc->sc_mtx);

		cmd = sc->sc_sdmmc_cmd;
		retrycount = 0;
		DPRINTF(VCIDEBUG_FUNC|VCIDEBUG_XFER,
		    ("vcispi_io_thread %d 0x%x len %d\n", cmd->c_opcode,
		    cmd->c_arg, cmd->c_datalen));
retry:
		SPI_DEVICE_REG_WRITE(sc, SPI_SS, 1);
		vcispi_put_tx(sc, 0xff);
		vcispi_put_tx(sc, 0x40 | (cmd->c_opcode & 0x3f));
		vcispi_put_tx(sc, (cmd->c_arg >> 24) & 0xff);
		vcispi_put_tx(sc, (cmd->c_arg >> 16) & 0xff);
		vcispi_put_tx(sc, (cmd->c_arg >> 8) & 0xff);
		vcispi_put_tx(sc, (cmd->c_arg >> 0) & 0xff);
		vcispi_put_tx(sc, (cmd->c_opcode == MMC_GO_IDLE_STATE) ? 0x95 :
		    (cmd->c_opcode == SD_SEND_IF_COND) ? 0x87 : 0); /* CRC */

		for (i = 0; i < SDCARD_COMMAND_TIMEOUT; i++) {
			rsp = vcispi_receive_char(sc);
			if (!(rsp & 0x80))
				break;
		}
		if (i == SDCARD_COMMAND_TIMEOUT) {
			DPRINTF(VCIDEBUG_XFER, ("rsp timeout (0x%x)\n", rsp));
			cmd->c_error = ETIMEDOUT;
			goto done;
		}

		if (ISSET(cmd->c_flags, SCF_RSP_SPI_S2)) {
			rsp |= ((uint16_t) vcispi_receive_char(sc)) << 8;
		} else if (ISSET(cmd->c_flags, SCF_RSP_SPI_B4)) {
			cmd->c_resp[1] =
			    ((uint32_t) vcispi_receive_char(sc)) << 24;
			cmd->c_resp[1] |=
			    ((uint32_t) vcispi_receive_char(sc)) << 16;
			cmd->c_resp[1] |=
			    ((uint32_t) vcispi_receive_char(sc)) << 8;
			cmd->c_resp[1] |=
			     (uint32_t) vcispi_receive_char(sc);
			DPRINTF(VCIDEBUG_XFER2,
			   ("R3 resp: %#x\n", cmd->c_resp[1]));
		}
		cmd->c_resp[0] = rsp;
		if (rsp != 0 && rsp != R1_SPI_IDLE) {
			aprint_error_dev(sc->sc_dev,
			    "response error %#x for cmd %d arg 0x%x",
			    rsp, cmd->c_opcode, cmd->c_arg);
			if (++retrycount < 5) {
				SPI_DEVICE_REG_WRITE(sc, SPI_SS, 0);
				vcispi_put_tx(sc, 0xff);
				aprint_error(", retrying\n");
				goto retry;
			}
			aprint_error("\n");
			cmd->c_error = EIO;
			goto done;
		}
		DPRINTF(VCIDEBUG_XFER2, ("R1 resp: %#x\n", rsp));

		if (cmd->c_datalen > 0) {
			if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
				/* XXX: swap in this place? */
				if (cmd->c_opcode == MMC_SEND_CID ||
				    cmd->c_opcode == MMC_SEND_CSD) {
					sdmmc_response res;
					uint32_t *p = cmd->c_data;

					vcispi_cmd_cfgread(sc, cmd);
					res[0] = be32toh(p[3]);
					res[1] = be32toh(p[2]);
					res[2] = be32toh(p[1]);
					res[3] = be32toh(p[0]);
					memcpy(p, &res, sizeof(res));
				} else {
					vcispi_cmd_read(sc, cmd);
				}
			} else {
				vcispi_cmd_write(sc, cmd);
			}
		} else {
			vcispi_put_tx(sc, 0xff);
			vcispi_wait(sc);
		}

done:
		SPI_DEVICE_REG_WRITE(sc, SPI_SS, 0);
		mutex_enter(&sc->sc_mtx);
		SET(cmd->c_flags, SCF_ITSDONE);
		cv_broadcast(&sc->sc_done_cv);
		sc->sc_sdmmc_cmd = NULL;
		DPRINTF(VCIDEBUG_XFER,
		  ("%s: cmd %d done (flags=%#x error=%d)\n",
		  device_xname(sc->sc_dev), cmd->c_opcode,
		  cmd->c_flags, cmd->c_error));
	}
}


static int
vcispi_host_reset(sdmmc_chipset_handle_t v)
{
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_host_reset\n"));
	return 0; /* XXX */
}

static uint32_t
vcispi_host_ocr(sdmmc_chipset_handle_t v)
{
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_host_ocr\n"));
	return (MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V);
}

static int
vcispi_host_maxblklen(sdmmc_chipset_handle_t v)
{
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_host_maxblklen\n"));

	return MAXPHYS;
}

static int
vcispi_card_detect(sdmmc_chipset_handle_t v)
{
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_card_detect\n"));

	return 1; /* XXX */
}

static int
vcispi_write_protect(sdmmc_chipset_handle_t v)
{
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_write_protect\n"));

	return 0; /* XXX */
}

static int
vcispi_bus_power(sdmmc_chipset_handle_t v, uint32_t p)
{
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_bus_power(%d)\n", p));

	return 0;
}

static int
vcispi_bus_clock(sdmmc_chipset_handle_t v, int clk)
{
	struct vcispi_softc *sc = v;
	DPRINTF(VCIDEBUG_FUNC|VCIDEBUG_INIT, ("vcispi_bus_clock(%d)\n", clk));
	int div;

	clk *= 1000;
	sc->sc_busclk = clk;

	if (clk == 0) {
		div = 0xffffffff;
		sc->sc_busclk = 1;
	} else {
		div = (sc->sc_div_freq / (clk * 2)) - 1;
		if (div < 0)
			div = 0;
		sc->sc_busclk = sc->sc_div_freq / (div + 1) / 2;
	}
	mutex_enter(&sc->sc_mtx);
	while (sc->sc_sdmmc_cmd != NULL)
		cv_wait(&sc->sc_done_cv, &sc->sc_mtx);
	DPRINTF(VCIDEBUG_FUNC|VCIDEBUG_INIT, ("bus clock %d/%d, divider %d\n",
	    clk, sc->sc_busclk, div));
	SPI_DEVICE_REG_WRITE(sc, SPI_DIVIDER, div);
	mutex_exit(&sc->sc_mtx);
	return 0;
}

static int
vcispi_bus_width(sdmmc_chipset_handle_t v, int w)
{
	struct vcispi_softc *sc = v;
	DPRINTF(VCIDEBUG_FUNC, ("vcispi_bus_width(%d)\n", w));

	switch(w) {
	case 1:
		break;
	default:
		aprint_debug_dev(sc->sc_dev, "unsupported bus width %d\n", w);
		return 1;
	}
	return 0;
}

static void
vcispi_exec_command(sdmmc_chipset_handle_t v, struct sdmmc_command *cmd)
{
	struct vcispi_softc *sc = v;

	mutex_enter(&sc->sc_mtx);
	while (sc->sc_sdmmc_cmd != NULL)
		cv_wait(&sc->sc_done_cv, &sc->sc_mtx);
	KASSERT(sc->sc_sdmmc_cmd == NULL);
	sc->sc_sdmmc_cmd = cmd;
	cv_broadcast(&sc->sc_io_cv);
	while (!ISSET(cmd->c_flags, SCF_ITSDONE))
		cv_wait(&sc->sc_done_cv, &sc->sc_mtx);
	mutex_exit(&sc->sc_mtx);
}

static void
vcispi_spi_initialize(sdmmc_chipset_handle_t v)
{
	struct vcispi_softc *sc = v;
	DPRINTF(VCIDEBUG_FUNC|VCIDEBUG_XFER, ("vcispi_spi_initialize"));

	mutex_enter(&sc->sc_mtx);
	vcispi_gen_tick(sc, 10);
	mutex_exit(&sc->sc_mtx);
	DPRINTF(VCIDEBUG_FUNC|VCIDEBUG_XFER, (" done\n"));
}

static void
vcispi_gen_tick(struct vcispi_softc *sc, int ticks)
{
	for (; ticks > 0; ticks--) {
		vcispi_put_tx(sc, 0xFF);
	}
}

static uint8_t
vcispi_receive_char(struct vcispi_softc *sc)
{
	vcispi_gen_tick(sc, 1);
	vcispi_wait(sc);
	return SPI_DEVICE_REG_READ(sc, SPI_DATA_TXRX(0));
}

static uint8_t
vcispi_receive_char_nowait(struct vcispi_softc *sc)
{
	SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(0), 0xff);
	SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl | SPI_CTRL_GO_BSY);
	vcispi_wait(sc);
	return SPI_DEVICE_REG_READ(sc, SPI_DATA_TXRX(0));
}

static void
vcispi_put_tx(struct vcispi_softc *sc, uint8_t data)
{
	DPRINTF(VCIDEBUG_XFER2, (" vcispi_put_tx(0x%x)\n", data));
	vcispi_wait(sc);
	SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(0), data);
	SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl | SPI_CTRL_GO_BSY);
}

static void
vcispi_wait(struct vcispi_softc *sc)
{
	uint32_t reg;
	DPRINTF(VCIDEBUG_XFER2, (" vcispi_wait"));
	do {
		reg = SPI_DEVICE_REG_READ(sc, SPI_CTRL);
		DPRINTF(VCIDEBUG_XFER2, (" reg 0x%x", reg));
	} while((reg & (SPI_CTRL_GO_BSY|SPI_CTRL_DMA_BSY)) != 0);
	if (reg & SPI_CTRL_DMA_ERR)
		aprint_error_dev(sc->sc_dev, ": DMA error (0x%x)\n", reg);
	DPRINTF(VCIDEBUG_XFER2, ("\n"));
}

static void
vcispi_cmd_cfgread(struct vcispi_softc *sc, struct sdmmc_command *cmd)
{
	u_char *data = cmd->c_data;
	int timo;
	int c;
	int i;

	/* wait data token */
	c = vcispi_receive_char_nowait(sc);
	for (timo = MMC_TIME_OVER(sc); timo > 0; timo--) {
		if (c != 0xff)
			break;
		c = vcispi_receive_char_nowait(sc);
	}
	if (timo == 0) {
		aprint_error_dev(sc->sc_dev, "cfg read timeout\n");
		cmd->c_error = ETIMEDOUT;
		return;
	}
	if (c != 0xfe) {
		aprint_error_dev(sc->sc_dev, "cfg read error (data=%#x)\n", c);
		cmd->c_error = EIO;
		return;
	}

	/* data read */
	data[0] = '\0';
	for (i = 1; i < cmd->c_datalen; i++) {
		data[i] = vcispi_receive_char_nowait(sc);
	}

	(void) vcispi_receive_char_nowait(sc);
	(void) vcispi_receive_char_nowait(sc);
	vcispi_put_tx(sc, 0xff);
	vcispi_wait(sc);
}

static void
vcispi_cmd_read(struct vcispi_softc *sc, struct sdmmc_command *cmd)
{
	uint32_t *data = (uint32_t *)cmd->c_data;
	uint32_t *data8 = cmd->c_data;
	int timo;
	int c;
	int i;

	/* wait data token */
	c = vcispi_receive_char_nowait(sc);
	for (timo = MMC_TIME_OVER(sc); timo > 0; timo--) {
		if (c != 0xff)
			break;
		c = vcispi_receive_char_nowait(sc);
	}
	if (timo == 0) {
		aprint_error_dev(sc->sc_dev, "read timeout\n");
		cmd->c_error = ETIMEDOUT;
		return;
	}
	if (c != 0xfe) {
		aprint_error_dev(sc->sc_dev, "read error (data=%#x)\n", c);
		cmd->c_error = EIO;
		return;
	}
	if (sc->sc_dmat != NULL &&
	    ((vaddr_t)data & 0x3f) == 0 &&
	    (cmd->c_datalen & 0x3f) == 0) {
		cmd->c_error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap,
		    data, cmd->c_datalen, NULL,
		    BUS_DMA_NOWAIT | BUS_DMA_STREAMING | BUS_DMA_READ);
		if (cmd->c_error) {
			aprint_error_dev(sc->sc_dev,
			    "bus_dmamap_load failed (%d)\n", cmd->c_error);
			return;
		}
		bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, cmd->c_datalen,
		    BUS_DMASYNC_PREREAD);
		SPI_DEVICE_REG_WRITE(sc, SPI_CTRL,
		    sc->sc_ctrl | SPI_CTRL_IE_EN);
		SPI_DEVICE_REG_WRITE(sc, SPI_DMA_BASE,
		    sc->sc_dmamap->dm_segs[0].ds_addr & 0xffffffff);
		SPI_DEVICE_REG_WRITE(sc, SPI_DMA_BASEH,
		    (sc->sc_dmamap->dm_segs[0].ds_addr >> 32) & 0xffffffff);
		SPI_DEVICE_REG_WRITE(sc, SPI_DMA_COUNT,
		    cmd->c_datalen | SPI_DMA_COUNT_READ);
		mutex_enter(&sc->sc_mtx);
		sc->sc_ctrl_intr = SPI_DEVICE_REG_READ(sc, SPI_CTRL);
		while ((sc->sc_ctrl_intr & (SPI_CTRL_GO_BSY|SPI_CTRL_DMA_BSY))
		    != 0) {
			cv_wait(&sc->sc_intr_cv, &sc->sc_mtx);
		}
		mutex_exit(&sc->sc_mtx);
		bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, cmd->c_datalen,
		    BUS_DMASYNC_POSTREAD);
		bus_dmamap_unload(sc->sc_dmat, sc->sc_dmamap);
		i = cmd->c_datalen;
	} else {
		/* switch to 128 bits words */
		sc->sc_ctrl = (sc->sc_ctrl & ~SPI_CTRL_CHAR_LEN_MASK) | 128;
		SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl);
		/* data read */
		for (i = 0; i + 3 < cmd->c_datalen / 4; i += 4) {
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(0), 0xffffffff);
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(1), 0xffffffff);
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(2), 0xffffffff);
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(3), 0xffffffff);
			SPI_DEVICE_REG_WRITE(sc,
			    SPI_CTRL, sc->sc_ctrl | SPI_CTRL_GO_BSY);
			vcispi_wait(sc);
			data[i  ] = be32toh(
			    SPI_DEVICE_REG_READ(sc, SPI_DATA_TXRX(3)));
			data[i+1] = be32toh(
			    SPI_DEVICE_REG_READ(sc, SPI_DATA_TXRX(2)));
			data[i+2] = be32toh(
			    SPI_DEVICE_REG_READ(sc, SPI_DATA_TXRX(1)));
			data[i+3] = be32toh(
			    SPI_DEVICE_REG_READ(sc, SPI_DATA_TXRX(0)));
		}
		i = i * 4;
	}
	/* switch  back to 8 bits reads */
	sc->sc_ctrl = (sc->sc_ctrl & ~SPI_CTRL_CHAR_LEN_MASK) | 8;
	SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl);
	/* and complete reads */
	KASSERT(i <= cmd->c_datalen);
	for (; i < cmd->c_datalen; i++) {
		data8[i] = vcispi_receive_char_nowait(sc);
	}
	/* ignore CRC */
	(void) vcispi_receive_char_nowait(sc);
	(void) vcispi_receive_char_nowait(sc);
}

static void
vcispi_cmd_write(struct vcispi_softc *sc, struct sdmmc_command *cmd)
{
	uint32_t *data = (u_int32_t *)cmd->c_data;
	int i;
	int status;

	KASSERT((cmd->c_datalen % 16) == 0);
	vcispi_put_tx(sc, 0xfe);

	if (sc->sc_dmat != NULL &&
	    ((vaddr_t)data & 0x3f) == 0 &&
	    (cmd->c_datalen & 0x3f) == 0) {
		KASSERT(cmd->c_datalen == 512);
		cmd->c_error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap,
		    data, cmd->c_datalen, NULL,
		    BUS_DMA_NOWAIT | BUS_DMA_STREAMING | BUS_DMA_WRITE);
		if (cmd->c_error) {
			aprint_error_dev(sc->sc_dev,
			    "bus_dmamap_load failed (%d)\n", cmd->c_error);
			return;
		}
		bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, cmd->c_datalen,
		    BUS_DMASYNC_PREWRITE);
		vcispi_wait(sc);
		SPI_DEVICE_REG_WRITE(sc, SPI_CTRL,
		    sc->sc_ctrl | SPI_CTRL_IE_EN);
		SPI_DEVICE_REG_WRITE(sc, SPI_DMA_BASE,
		    sc->sc_dmamap->dm_segs[0].ds_addr & 0xffffffff);
		SPI_DEVICE_REG_WRITE(sc, SPI_DMA_BASEH,
		    (sc->sc_dmamap->dm_segs[0].ds_addr >> 32) & 0xffffffff);
		KASSERT((cmd->c_datalen & SPI_DMA_COUNT_READ) == 0);
		SPI_DEVICE_REG_WRITE(sc, SPI_DMA_COUNT, cmd->c_datalen);
		mutex_enter(&sc->sc_mtx);
		sc->sc_ctrl_intr = SPI_DEVICE_REG_READ(sc, SPI_CTRL);
		while ((sc->sc_ctrl_intr & (SPI_CTRL_GO_BSY|SPI_CTRL_DMA_BSY))
		    != 0) {
			cv_wait(&sc->sc_intr_cv, &sc->sc_mtx);
		}
		mutex_exit(&sc->sc_mtx);
		bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, cmd->c_datalen,
		    BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(sc->sc_dmat, sc->sc_dmamap);
		i = cmd->c_datalen;
	} else {
		/* switch to 128 bits words */
		vcispi_wait(sc);
		sc->sc_ctrl = (sc->sc_ctrl & ~SPI_CTRL_CHAR_LEN_MASK) | 128;
		SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl);
		/* data write */
		for (i = 0; i < cmd->c_datalen / 4; i += 4) {
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(0),
			    htobe32(data[i+3]));
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(1),
			    htobe32(data[i+2]));
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(2),
			    htobe32(data[i+1]));
			SPI_DEVICE_REG_WRITE(sc, SPI_DATA_TXRX(3),
			    htobe32(data[i  ]));
			SPI_DEVICE_REG_WRITE(sc,
			    SPI_CTRL, sc->sc_ctrl | SPI_CTRL_GO_BSY);
			vcispi_wait(sc);
		}
	}
	/* switch  back to 8 bits reads */
	sc->sc_ctrl = (sc->sc_ctrl & ~SPI_CTRL_CHAR_LEN_MASK) | 8;
	SPI_DEVICE_REG_WRITE(sc, SPI_CTRL, sc->sc_ctrl);

	/* dummy CRC */
	vcispi_put_tx(sc, 0);
	vcispi_put_tx(sc, 0);
	/* read status */
	status = vcispi_receive_char(sc);
	DPRINTF(VCIDEBUG_XFER2, ("write status 0x%x\n", status));
	if ((status & 0x1f) != 0x5) {
		aprint_error_dev(sc->sc_dev, ": write failed: 0x%x\n",
		    status);
		cmd->c_error = EIO;
	}
	/* wait for write to complete */
	for (i= 0; i < 500000; i++) { /* 0.4s timeout at 10Mhz */
		status = vcispi_receive_char(sc);
		if (status != 0) {
			/* write completed */
			return;
		}
	}
	/* write time out */
	aprint_error_dev(sc->sc_dev, ": write timeout, status 0x%x\n", status);
	cmd->c_error = ETIMEDOUT;
}

static int
vcispi_intr(void * v)
{
	struct vcispi_softc *sc = v;
	mutex_enter(&sc->sc_mtx);
	sc->sc_ctrl_intr = SPI_DEVICE_REG_READ(sc, SPI_CTRL);
	cv_broadcast(&sc->sc_intr_cv);
	mutex_exit(&sc->sc_mtx);
	return 1;
}
