/*	$OpenBSD: exesdhc.c,v 1.3 2015/05/30 02:17:36 jsg Exp $	*/
/*
 * Copyright (c) 2009 Dale Rahn <drahn@openbsd.org>
 * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org>
 * Copyright (c) 2012-2013 Patrick Wildt <patrick@blueri.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* i.MX SD/MMC support derived from /sys/dev/sdmmc/sdhc.c */

#include "fdt.h"

#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <machine/bus.h>
#if NFDT > 0
#include <machine/fdt.h>
#endif

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

#include <armv7/armv7/armv7var.h>
#include <armv7/exynos/exclockvar.h>
#include <armv7/exynos/exgpiovar.h>

/* registers */
#define SDHC_DS_ADDR			0x00
#define SDHC_BLK_ATT			0x04
#define SDHC_CMD_ARG			0x08
#define SDHC_CMD_XFR_TYP		0x0c
#define SDHC_CMD_RSP0			0x10
#define SDHC_CMD_RSP1			0x14
#define SDHC_CMD_RSP2			0x18
#define SDHC_CMD_RSP3			0x1c
#define SDHC_DATA_BUFF_ACC_PORT		0x20
#define SDHC_PRES_STATE			0x24
#define SDHC_PROT_CTRL			0x28
#define SDHC_SYS_CTRL			0x2c
#define SDHC_INT_STATUS			0x30
#define SDHC_INT_STATUS_EN		0x34
#define SDHC_INT_SIGNAL_EN		0x38
#define SDHC_AUTOCMD12_ERR_STATUS	0x3c
#define SDHC_HOST_CTRL_CAP		0x40
#define SDHC_WTMK_LVL			0x44
#define SDHC_MIX_CTRL			0x48
#define SDHC_FORCE_EVENT		0x50
#define SDHC_ADMA_ERR_STATUS		0x54
#define SDHC_ADMA_SYS_ADDR		0x58
#define SDHC_DLL_CTRL			0x60
#define SDHC_DLL_STATUS			0x64
#define SDHC_CLK_TUNE_CTRL_STATUS	0x68
#define SDHC_VEND_SPEC			0xc0
#define SDHC_MMC_BOOT			0xc4
#define SDHC_VEND_SPEC2			0xc8
#define SDHC_HOST_CTRL_VER		0xfc

/* bits and bytes */
#define SDHC_BLK_ATT_BLKCNT_MAX			0xffff
#define SDHC_BLK_ATT_BLKCNT_SHIFT		16
#define SDHC_BLK_ATT_BLKSIZE_SHIFT		0
#define SDHC_CMD_XFR_TYP_CMDINDX_SHIFT		24
#define SDHC_CMD_XFR_TYP_CMDINDX_SHIFT_MASK	(0x3f << SDHC_CMD_XFR_TYP_CMDINDX_SHIFT)
#define SDHC_CMD_XFR_TYP_CMDTYP_SHIFT		22
#define SDHC_CMD_XFR_TYP_DPSEL_SHIFT		21
#define SDHC_CMD_XFR_TYP_DPSEL			(1 << SDHC_CMD_XFR_TYP_DPSEL_SHIFT)
#define SDHC_CMD_XFR_TYP_CICEN_SHIFT		20
#define SDHC_CMD_XFR_TYP_CICEN			(1 << SDHC_CMD_XFR_TYP_CICEN_SHIFT)
#define SDHC_CMD_XFR_TYP_CCCEN_SHIFT		19
#define SDHC_CMD_XFR_TYP_CCCEN			(1 << SDHC_CMD_XFR_TYP_CCCEN_SHIFT)
#define SDHC_CMD_XFR_TYP_RSPTYP_SHIFT		16
#define SDHC_CMD_XFR_TYP_RSP_NONE		(0x0 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT)
#define SDHC_CMD_XFR_TYP_RSP136			(0x1 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT)
#define SDHC_CMD_XFR_TYP_RSP48			(0x2 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT)
#define SDHC_CMD_XFR_TYP_RSP48B			(0x3 << SDHC_CMD_XFR_TYP_RSPTYP_SHIFT)
#define SDHC_PRES_STATE_WPSPL			(1 << 19)
#define SDHC_PRES_STATE_BREN			(1 << 11)
#define SDHC_PRES_STATE_BWEN			(1 << 10)
#define SDHC_PRES_STATE_SDSTB			(1 << 3)
#define SDHC_PRES_STATE_DLA			(1 << 2)
#define SDHC_PRES_STATE_CDIHB			(1 << 1)
#define SDHC_PRES_STATE_CIHB			(1 << 0)
#define SDHC_SYS_CTRL_RSTA			(1 << 24)
#define SDHC_SYS_CTRL_RSTC			(1 << 25)
#define SDHC_SYS_CTRL_RSTD			(1 << 26)
#define SDHC_SYS_CTRL_CLOCK_MASK		(0xfff << 4)
#define SDHC_SYS_CTRL_CLOCK_DIV_SHIFT		4
#define SDHC_SYS_CTRL_CLOCK_PRE_SHIFT		8
#define SDHC_SYS_CTRL_DTOCV_SHIFT		16
#define SDHC_INT_STATUS_CC			(1 << 0)
#define SDHC_INT_STATUS_TC			(1 << 1)
#define SDHC_INT_STATUS_BGE			(1 << 2)
#define SDHC_INT_STATUS_DINT			(1 << 3)
#define SDHC_INT_STATUS_BWR			(1 << 4)
#define SDHC_INT_STATUS_BRR			(1 << 5)
#define SDHC_INT_STATUS_CINS			(1 << 6)
#define SDHC_INT_STATUS_CRM			(1 << 7)
#define SDHC_INT_STATUS_CINT			(1 << 8)
#define SDHC_INT_STATUS_CTOE			(1 << 16)
#define SDHC_INT_STATUS_CCE			(1 << 17)
#define SDHC_INT_STATUS_CEBE			(1 << 18)
#define SDHC_INT_STATUS_CIC			(1 << 19)
#define SDHC_INT_STATUS_DTOE			(1 << 20)
#define SDHC_INT_STATUS_DCE			(1 << 21)
#define SDHC_INT_STATUS_DEBE			(1 << 22)
#define SDHC_INT_STATUS_DMAE			(1 << 28)
#define SDHC_INT_STATUS_CMD_ERR			(SDHC_INT_STATUS_CIC | SDHC_INT_STATUS_CEBE | SDHC_INT_STATUS_CCE)
#define SDHC_INT_STATUS_ERR			(SDHC_INT_STATUS_CTOE | SDHC_INT_STATUS_CCE | SDHC_INT_STATUS_CEBE | \
						 SDHC_INT_STATUS_CIC | SDHC_INT_STATUS_DTOE | SDHC_INT_STATUS_DCE | \
						 SDHC_INT_STATUS_DEBE | SDHC_INT_STATUS_DMAE)
#define SDHC_MIX_CTRL_DMAEN			(1 << 0)
#define SDHC_MIX_CTRL_BCEN			(1 << 1)
#define SDHC_MIX_CTRL_AC12EN			(1 << 2)
#define SDHC_MIX_CTRL_DTDSEL			(1 << 4)
#define SDHC_MIX_CTRL_MSBSEL			(1 << 5)
#define SDHC_PROT_CTRL_DMASEL_SDMA_MASK		(0x3 << 8)
#define SDHC_HOST_CTRL_CAP_MBL_SHIFT		16
#define SDHC_HOST_CTRL_CAP_MBL_MASK		0x7
#define SDHC_HOST_CTRL_CAP_VS33			(1 << 24)
#define SDHC_HOST_CTRL_CAP_VS30			(1 << 25)
#define SDHC_HOST_CTRL_CAP_VS18			(1 << 26)
#define SDHC_VEND_SPEC_FRC_SDCLK_ON		(1 << 8)
#define SDHC_WTMK_LVL_RD_WML_SHIFT		0
#define SDHC_WTMK_LVL_WR_WML_SHIFT		16

#define SDHC_COMMAND_TIMEOUT	hz
#define SDHC_BUFFER_TIMEOUT	hz
#define SDHC_TRANSFER_TIMEOUT	hz

int exesdhc_match(struct device *parent, void *v, void *aux);
void exesdhc_attach(struct device *parent, struct device *self, void *args);

#include <machine/bus.h>

struct exesdhc_softc {
	struct device sc_dev;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;
	void			*sc_ih; /* Interrupt handler */
	u_int sc_flags;

	int unit;			/* unit id */
	struct device *sdmmc;		/* generic SD/MMC device */
	int clockbit;			/* clock control bit */
	u_int clkbase;			/* base clock frequency in KHz */
	int maxblklen;			/* maximum block length */
	int flags;			/* flags for this host */
	uint32_t ocr;			/* OCR value from capabilities */
//	u_int8_t regs[14];		/* host controller state */
	uint32_t intr_status;		/* soft interrupt status */
	uint32_t intr_error_status;	/*  */
};


/* Host controller functions called by the attachment driver. */
int	exesdhc_host_found(struct exesdhc_softc *, bus_space_tag_t,
	    bus_space_handle_t, bus_size_t, int);
void	exesdhc_power(int, void *);
void	exesdhc_shutdown(void *);
int	exesdhc_intr(void *);

/* RESET MODES */
#define MMC_RESET_DAT	1
#define MMC_RESET_CMD	2
#define MMC_RESET_ALL	(MMC_RESET_CMD|MMC_RESET_DAT)

#define HDEVNAME(sc)	((sc)->sc_dev.dv_xname)

/* flag values */
#define SHF_USE_DMA		0x0001

/* SDHC should only be accessed with 4 byte reads or writes. */
#define HREAD4(sc, reg)							\
	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
#define HWRITE4(sc, reg, val)						\
	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
#define HSET4(sc, reg, bits)						\
	HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
#define HCLR4(sc, reg, bits)						\
	HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))

int	exesdhc_host_reset(sdmmc_chipset_handle_t);
uint32_t exesdhc_host_ocr(sdmmc_chipset_handle_t);
int	exesdhc_host_maxblklen(sdmmc_chipset_handle_t);
int	exesdhc_card_detect(sdmmc_chipset_handle_t);
int	exesdhc_bus_power(sdmmc_chipset_handle_t, uint32_t);
int	exesdhc_bus_clock(sdmmc_chipset_handle_t, int);
void	exesdhc_card_intr_mask(sdmmc_chipset_handle_t, int);
void	exesdhc_card_intr_ack(sdmmc_chipset_handle_t);
void	exesdhc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *);
int	exesdhc_start_command(struct exesdhc_softc *, struct sdmmc_command *);
int	exesdhc_wait_state(struct exesdhc_softc *, uint32_t, uint32_t);
int	exesdhc_soft_reset(struct exesdhc_softc *, int);
int	exesdhc_wait_intr(struct exesdhc_softc *, int, int);
void	exesdhc_transfer_data(struct exesdhc_softc *, struct sdmmc_command *);
void	exesdhc_read_data(struct exesdhc_softc *, u_char *, int);
void	exesdhc_write_data(struct exesdhc_softc *, u_char *, int);

//#define SDHC_DEBUG
#ifdef SDHC_DEBUG
int exesdhcdebug = 20;
#define DPRINTF(n,s)	do { if ((n) <= exesdhcdebug) printf s; } while (0)
#else
#define DPRINTF(n,s)	do {} while(0)
#endif

struct sdmmc_chip_functions exesdhc_functions = {
	/* host controller reset */
	exesdhc_host_reset,
	/* host controller capabilities */
	exesdhc_host_ocr,
	exesdhc_host_maxblklen,
	/* card detection */
	exesdhc_card_detect,
	/* bus power and clock frequency */
	exesdhc_bus_power,
	exesdhc_bus_clock,
	/* command execution */
	exesdhc_exec_command,
	/* card interrupt */
	exesdhc_card_intr_mask,
	exesdhc_card_intr_ack
};

struct cfdriver exesdhc_cd = {
	NULL, "exesdhc", DV_DULL
};

struct cfattach exesdhc_ca = {
	sizeof(struct exesdhc_softc), NULL, exesdhc_attach
};
struct cfattach exesdhc_fdt_ca = {
	sizeof(struct exesdhc_softc), exesdhc_match, exesdhc_attach
};

int
exesdhc_match(struct device *parent, void *v, void *aux)
{
#if NFDT > 0
	struct armv7_attach_args *aa = aux;

	if (fdt_node_compatible("samsung,exynos5250-dw-mshc", aa->aa_node))
		return 1;
#endif

	return 0;
}

void
exesdhc_attach(struct device *parent, struct device *self, void *args)
{
	struct exesdhc_softc		*sc = (struct exesdhc_softc *) self;
	struct armv7_attach_args	*aa = args;
	struct sdmmcbus_attach_args	 saa;
	struct armv7mem			 mem;
	int				 error = 1, irq;
	uint32_t			 caps;

	sc->sc_iot = aa->aa_iot;
#if NFDT > 0
	if (aa->aa_node) {
		struct fdt_memory fdtmem;
		static int unit = 0;
		uint32_t ints[3];

		sc->unit = unit++;

		if (fdt_get_memory_address(aa->aa_node, 0, &fdtmem))
			panic("%s: could not extract memory data from FDT",
			    __func__);

		/* TODO: Add interrupt FDT API. */
		if (fdt_node_property_ints(aa->aa_node, "interrupts",
		    ints, 3) != 3)
			panic("%s: could not extract interrupt data from FDT",
			    __func__);

		mem.addr = fdtmem.addr;
		mem.size = fdtmem.size;

		irq = ints[1];
	} else
#endif
	{
		irq = aa->aa_dev->irq[0];
		mem.addr = aa->aa_dev->mem[0].addr;
		mem.size = aa->aa_dev->mem[0].size;
	}

	if (bus_space_map(sc->sc_iot, mem.addr, mem.size, 0, &sc->sc_ioh))
		panic("%s: bus_space_map failed!", __func__);

	printf("\n");

	/* XXX DMA channels? */

	sc->sc_ih = arm_intr_establish(irq, IPL_SDMMC,
	    exesdhc_intr, sc, sc->sc_dev.dv_xname);

	/*
	 * Reset the host controller and enable interrupts.
	 */
	if (exesdhc_host_reset(sc))
		goto err;

	/* Determine host capabilities. */
	caps = HREAD4(sc, SDHC_HOST_CTRL_CAP);

	/*
	 * Determine the base clock frequency. (2.2.24)
	 */
	//sc->clkbase = exccm_get_usdhx(aa->aa_dev->unit + 1);
	sc->clkbase = 0;

	/*
	 * Determine SD bus voltage levels supported by the controller.
	 */
	if (caps & SDHC_HOST_CTRL_CAP_VS18)
		SET(sc->ocr, MMC_OCR_1_7V_1_8V | MMC_OCR_1_8V_1_9V);
	if (caps & SDHC_HOST_CTRL_CAP_VS30)
		SET(sc->ocr, MMC_OCR_2_9V_3_0V | MMC_OCR_3_0V_3_1V);
	if (caps & SDHC_HOST_CTRL_CAP_VS33)
		SET(sc->ocr, MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V);

	/*
	 * Determine max block size.
	 */
	switch ((caps >> SDHC_HOST_CTRL_CAP_MBL_SHIFT)
	    & SDHC_HOST_CTRL_CAP_MBL_MASK) {
	case 0:
		sc->maxblklen = 512;
		break;
	case 1:
		sc->maxblklen = 1024;
		break;
	case 2:
		sc->maxblklen = 2048;
		break;
	case 3:
		sc->maxblklen = 4096;
		break;
	default:
		sc->maxblklen = 512;
		printf("invalid capability blocksize in capa %08x,"
		    " trying 512\n", caps);
	}

	/* somewhere this blksize might be used instead of the device's */
	sc->maxblklen = 512;

	/*
	 * Attach the generic SD/MMC bus driver.  (The bus driver must
	 * not invoke any chipset functions before it is attached.)
	 */

	bzero(&saa, sizeof(saa));
	saa.saa_busname = "sdmmc";
	saa.sct = &exesdhc_functions;
	saa.sch = sc;

	sc->sdmmc = config_found(&sc->sc_dev, &saa, NULL);
	if (sc->sdmmc == NULL) {
		error = 0;
		goto err;
	}
	
	return;

err:
	return;
}


/*
 * Power hook established by or called from attachment driver.
 */
void
exesdhc_power(int why, void *arg)
{
}

/*
 * Shutdown hook established by or called from attachment driver.
 */
void
exesdhc_shutdown(void *arg)
{
	struct exesdhc_softc *sc = arg;

	/* XXX chip locks up if we don't disable it before reboot. */
	(void)exesdhc_host_reset(sc);
}

/*
 * Reset the host controller.  Called during initialization, when
 * cards are removed, upon resume, and during error recovery.
 */
int
exesdhc_host_reset(sdmmc_chipset_handle_t sch)
{
	struct exesdhc_softc *sc = sch;
	u_int32_t imask;
	int error;
	int s;

	s = splsdmmc();

	/* Disable all interrupts. */
	HWRITE4(sc, SDHC_INT_STATUS_EN, 0);
	HWRITE4(sc, SDHC_INT_SIGNAL_EN, 0);

	/*
	 * Reset the entire host controller and wait up to 100ms for
	 * the controller to clear the reset bit.
	 */
	if ((error = exesdhc_soft_reset(sc, SDHC_SYS_CTRL_RSTA)) != 0) {
		splx(s);
		return (error);
	}

	/* Set data timeout counter value to max for now. */
	HSET4(sc, SDHC_SYS_CTRL, 0xe << SDHC_SYS_CTRL_DTOCV_SHIFT);

	/* Enable interrupts. */
	imask = SDHC_INT_STATUS_CC | SDHC_INT_STATUS_TC |
	    SDHC_INT_STATUS_BGE |
#ifdef SDHC_DMA
	    SHDC_INT_STATUS_DINT;
#else
	    SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR;
#endif

	imask |= SDHC_INT_STATUS_CTOE | SDHC_INT_STATUS_CCE |
	    SDHC_INT_STATUS_CEBE | SDHC_INT_STATUS_CIC |
	    SDHC_INT_STATUS_DTOE | SDHC_INT_STATUS_DCE |
	    SDHC_INT_STATUS_DEBE | SDHC_INT_STATUS_DMAE;

	HWRITE4(sc, SDHC_INT_STATUS_EN, imask);
	HWRITE4(sc, SDHC_INT_SIGNAL_EN, imask);

	// Use no or simple DMA
	HWRITE4(sc, SDHC_PROT_CTRL,
	    HREAD4(sc, SDHC_PROT_CTRL) & ~SDHC_PROT_CTRL_DMASEL_SDMA_MASK);

	splx(s);
	return 0;
}

uint32_t
exesdhc_host_ocr(sdmmc_chipset_handle_t sch)
{
	struct exesdhc_softc *sc = sch;
	return sc->ocr;
}

int
exesdhc_host_maxblklen(sdmmc_chipset_handle_t sch)
{
	struct exesdhc_softc *sc = sch;
	return sc->maxblklen;
}

/*
 * Return non-zero if the card is currently inserted.
 */
int
exesdhc_card_detect(sdmmc_chipset_handle_t sch)
{
	struct exesdhc_softc *sc = sch;
	int gpio;

	switch (board_id)
	{
	case BOARD_ID_EXYNOS5_CHROMEBOOK:
		switch (sc->unit) {
			case 2:
				gpio = 6*32 + 0;
				break;
			case 3:
				gpio = 1*32 + 6;
				break;
			default:
				return 0;
		}
		return exgpio_get_bit(gpio) ? 0 : 1;
	default:
		return 1;
	}
}

/*
 * Set or change SD bus voltage and enable or disable SD bus power.
 * Return zero on success.
 */
int
exesdhc_bus_power(sdmmc_chipset_handle_t sch, uint32_t ocr)
{
	return 0;
}

/*
 * Set or change SDCLK frequency or disable the SD clock.
 * Return zero on success.
 */
int
exesdhc_bus_clock(sdmmc_chipset_handle_t sch, int freq)
{
	struct exesdhc_softc *sc = sch;
	int div, pre_div, cur_freq, s;
	int error = 0;

	s = splsdmmc();

	if (sc->clkbase / 16 > freq) {
		for (pre_div = 2; pre_div < 256; pre_div *= 2)
			if ((sc->clkbase / pre_div) <= (freq * 16))
				break;
	} else
		pre_div = 2;

	if (sc->clkbase == freq)
		pre_div = 1;

	for (div = 1; div <= 16; div++)
		if ((sc->clkbase / (div * pre_div)) <= freq)
			break;

	div -= 1;
	pre_div >>= 1;

	cur_freq = sc->clkbase / (pre_div * 2) / (div + 1);

	/* disable force CLK ouput active */
	HCLR4(sc, SDHC_VEND_SPEC, SDHC_VEND_SPEC_FRC_SDCLK_ON);

	/* wait while clock is unstable */
	if ((error = exesdhc_wait_state(sc, SDHC_PRES_STATE_SDSTB, SDHC_PRES_STATE_SDSTB)) != 0)
		goto ret;

	HCLR4(sc, SDHC_SYS_CTRL, SDHC_SYS_CTRL_CLOCK_MASK);
	HSET4(sc, SDHC_SYS_CTRL, (div << SDHC_SYS_CTRL_CLOCK_DIV_SHIFT) | (pre_div << SDHC_SYS_CTRL_CLOCK_PRE_SHIFT));

	/* wait while clock is unstable */
	if ((error = exesdhc_wait_state(sc, SDHC_PRES_STATE_SDSTB, SDHC_PRES_STATE_SDSTB)) != 0)
		goto ret;

ret:
	splx(s);
	return error;
}

void
exesdhc_card_intr_mask(sdmmc_chipset_handle_t sch, int enable)
{
	printf("exesdhc_card_intr_mask\n");
	/* - this is SDIO card interrupt */
	struct exesdhc_softc *sc = sch;

	if (enable) {
		HSET4(sc, SDHC_INT_STATUS_EN, SDHC_INT_STATUS_CINT);
		HSET4(sc, SDHC_INT_SIGNAL_EN, SDHC_INT_STATUS_CINT);
	} else {
		HCLR4(sc, SDHC_INT_STATUS_EN, SDHC_INT_STATUS_CINT);
		HCLR4(sc, SDHC_INT_SIGNAL_EN, SDHC_INT_STATUS_CINT);
	}
}

void
exesdhc_card_intr_ack(sdmmc_chipset_handle_t sch)
{
	printf("exesdhc_card_intr_ack\n");
	struct exesdhc_softc *sc = sch;

	HWRITE4(sc, SDHC_INT_STATUS, SDHC_INT_STATUS_CINT);
}

int
exesdhc_wait_state(struct exesdhc_softc *sc, uint32_t mask, uint32_t value)
{
	uint32_t state;
	int timeout;
	state = HREAD4(sc, SDHC_PRES_STATE);
	DPRINTF(3,("%s: wait_state %x %x %x)\n", HDEVNAME(sc),
	    mask, value, state));
	for (timeout = 1000; timeout > 0; timeout--) {
		if (((state = HREAD4(sc, SDHC_PRES_STATE)) & mask) == value)
			return 0;
		delay(10);
	}
	DPRINTF(0,("%s: timeout waiting for %x, state %x\n", HDEVNAME(sc),
	    value, state));
	return ETIMEDOUT;
}

void
exesdhc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
{
	struct exesdhc_softc *sc = sch;
	int error;

	/*
	 * Start the command, or mark `cmd' as failed and return.
	 */
	error = exesdhc_start_command(sc, cmd);
	if (error != 0) {
		cmd->c_error = error;
		SET(cmd->c_flags, SCF_ITSDONE);
		return;
	}

	/*
	 * Wait until the command phase is done, or until the command
	 * is marked done for any other reason.
	 */
	if (!exesdhc_wait_intr(sc, SDHC_INT_STATUS_CC, SDHC_COMMAND_TIMEOUT)) {
		cmd->c_error = ETIMEDOUT;
		SET(cmd->c_flags, SCF_ITSDONE);
		return;
	}

	/*
	 * The host controller removes bits [0:7] from the response
	 * data (CRC) and we pass the data up unchanged to the bus
	 * driver (without padding).
	 */
	if (cmd->c_error == 0 && ISSET(cmd->c_flags, SCF_RSP_PRESENT)) {
		if (ISSET(cmd->c_flags, SCF_RSP_136)) {
			cmd->c_resp[0] = HREAD4(sc, SDHC_CMD_RSP0);
			cmd->c_resp[1] = HREAD4(sc, SDHC_CMD_RSP1);
			cmd->c_resp[2] = HREAD4(sc, SDHC_CMD_RSP2);
			cmd->c_resp[3] = HREAD4(sc, SDHC_CMD_RSP3);

#ifdef SDHC_DEBUG
			printf("resp[0] 0x%08x\nresp[1] 0x%08x\nresp[2] 0x%08x\nresp[3] 0x%08x\n", cmd->c_resp[0], cmd->c_resp[1], cmd->c_resp[2], cmd->c_resp[3]);
#endif
		} else  {
			cmd->c_resp[0] = HREAD4(sc, SDHC_CMD_RSP0);
#ifdef SDHC_DEBUG
			printf("resp[0] 0x%08x\n", cmd->c_resp[0]);
#endif
		}
	}

	/*
	 * If the command has data to transfer in any direction,
	 * execute the transfer now.
	 */
	if (cmd->c_error == 0 && cmd->c_data)
		exesdhc_transfer_data(sc, cmd);

	DPRINTF(1,("%s: cmd %u done (flags=%#x error=%d)\n",
	    HDEVNAME(sc), cmd->c_opcode, cmd->c_flags, cmd->c_error));
	SET(cmd->c_flags, SCF_ITSDONE);
}

int
exesdhc_start_command(struct exesdhc_softc *sc, struct sdmmc_command *cmd)
{
	u_int32_t blksize = 0;
	u_int32_t blkcount = 0;
	u_int32_t command;
	int error;
	int s;

	DPRINTF(1,("%s: start cmd %u arg=%#x data=%p dlen=%d flags=%#x "
	    "proc=\"%s\"\n", HDEVNAME(sc), cmd->c_opcode, cmd->c_arg,
	    cmd->c_data, cmd->c_datalen, cmd->c_flags, curproc ?
	    curproc->p_comm : ""));

	/*
	 * The maximum block length for commands should be the minimum
	 * of the host buffer size and the card buffer size. (1.7.2)
	 */

	/* Fragment the data into proper blocks. */
	if (cmd->c_datalen > 0) {
		blksize = MIN(cmd->c_datalen, cmd->c_blklen);
		blkcount = cmd->c_datalen / blksize;
		if (cmd->c_datalen % blksize > 0) {
			/* XXX: Split this command. (1.7.4) */
			printf("%s: data not a multiple of %d bytes\n",
			    HDEVNAME(sc), blksize);
			return EINVAL;
		}
	}

	/* Check limit imposed by 9-bit block count. (1.7.2) */
	if (blkcount > SDHC_BLK_ATT_BLKCNT_MAX) {
		printf("%s: too much data\n", HDEVNAME(sc));
		return EINVAL;
	}

	/* setup for PIO, check for write protection */
	if (!ISSET(cmd->c_flags, SCF_CMD_READ)) {
		if (!(HREAD4(sc, SDHC_PRES_STATE) & SDHC_PRES_STATE_WPSPL)) {
			printf("%s: card is write protected\n",
			    HDEVNAME(sc));
			return EINVAL;
		}
	}

#ifdef SDHC_DMA
	/* set watermark level */
	uint32_t wml = blksize / sizeof(uint32_t);
	if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
		if (wml > 16)
			wml = 16;
		HWRITE4(sc, SDHC_WTMK_LVL, wml << SDHC_WTMK_LVL_RD_WML_SHIFT);
	} else {
		if (wml > 128)
			wml = 128;
		HWRITE4(sc, SDHC_WTMK_LVL, wml << SDHC_WTMK_LVL_WR_WML_SHIFT);
	}
#endif

	/* Prepare transfer mode register value. (2.2.5) */
	command = 0;

	if (ISSET(cmd->c_flags, SCF_CMD_READ))
		command |= SDHC_MIX_CTRL_DTDSEL;
	if (blkcount > 0) {
		command |= SDHC_MIX_CTRL_BCEN;
#ifdef SDHC_DMA
		command |= SDHC_MIX_CTRL_DMAEN;
#endif
		if (blkcount > 1) {
			command |= SDHC_MIX_CTRL_MSBSEL;
			command |= SDHC_MIX_CTRL_AC12EN;
		}
	}

	command |= (cmd->c_opcode << SDHC_CMD_XFR_TYP_CMDINDX_SHIFT) &
	   SDHC_CMD_XFR_TYP_CMDINDX_SHIFT_MASK;

	if (ISSET(cmd->c_flags, SCF_RSP_CRC))
		command |= SDHC_CMD_XFR_TYP_CCCEN;
	if (ISSET(cmd->c_flags, SCF_RSP_IDX))
		command |= SDHC_CMD_XFR_TYP_CICEN;
	if (cmd->c_data != NULL)
		command |= SDHC_CMD_XFR_TYP_DPSEL;

	if (!ISSET(cmd->c_flags, SCF_RSP_PRESENT))
		command |= SDHC_CMD_XFR_TYP_RSP_NONE;
	else if (ISSET(cmd->c_flags, SCF_RSP_136))
		command |= SDHC_CMD_XFR_TYP_RSP136;
	else if (ISSET(cmd->c_flags, SCF_RSP_BSY))
		command |= SDHC_CMD_XFR_TYP_RSP48B;
	else
		command |= SDHC_CMD_XFR_TYP_RSP48;

	/* Wait until command and data inhibit bits are clear. (1.5) */
	if ((error = exesdhc_wait_state(sc, SDHC_PRES_STATE_CIHB, 0)) != 0)
		return error;

	s = splsdmmc();

	/*
	 * Start a CPU data transfer.  Writing to the high order byte
	 * of the SDHC_COMMAND register triggers the SD command. (1.5)
	 */
#ifdef SDHC_DMA
	if (cmd->c_data)
		HWRITE4(sc, SDHC_DS_ADDR, (uint32_t)cmd->c_data);
#endif
	HWRITE4(sc, SDHC_BLK_ATT, blkcount << SDHC_BLK_ATT_BLKCNT_SHIFT |
	    blksize << SDHC_BLK_ATT_BLKSIZE_SHIFT);
	HWRITE4(sc, SDHC_CMD_ARG, cmd->c_arg);
	HWRITE4(sc, SDHC_MIX_CTRL,
	    (HREAD4(sc, SDHC_MIX_CTRL) & (0xf << 22)) | (command & 0xffff));
	HWRITE4(sc, SDHC_CMD_XFR_TYP, command);

	splx(s);
	return 0;
}

void
exesdhc_transfer_data(struct exesdhc_softc *sc, struct sdmmc_command *cmd)
{
#ifndef SDHC_DMA
	u_char *datap = cmd->c_data;
	int i;
#endif
	int datalen;
	int mask;
	int error;

	mask = ISSET(cmd->c_flags, SCF_CMD_READ) ?
	    SDHC_PRES_STATE_BREN : SDHC_PRES_STATE_BWEN;
	error = 0;
	datalen = cmd->c_datalen;

	DPRINTF(1,("%s: resp=%#x datalen=%d\n", HDEVNAME(sc),
	    MMC_R1(cmd->c_resp), datalen));

#ifndef SDHC_DMA
	while (datalen > 0) {
		if (!exesdhc_wait_intr(sc, SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR,
		    SDHC_BUFFER_TIMEOUT)) {
			error = ETIMEDOUT;
			break;
		}

		if ((error = exesdhc_wait_state(sc, mask, mask)) != 0)
			break;

		/* FIXME: wait a bit, else it fails */
		delay(100);
		i = MIN(datalen, cmd->c_blklen);
		if (ISSET(cmd->c_flags, SCF_CMD_READ))
			exesdhc_read_data(sc, datap, i);
		else
			exesdhc_write_data(sc, datap, i);

		datap += i;
		datalen -= i;
	}
#endif

	if (error == 0 && !exesdhc_wait_intr(sc, SDHC_INT_STATUS_TC,
	    SDHC_TRANSFER_TIMEOUT))
		error = ETIMEDOUT;

	if (error != 0)
		cmd->c_error = error;
	SET(cmd->c_flags, SCF_ITSDONE);

	DPRINTF(1,("%s: data transfer done (error=%d)\n",
	    HDEVNAME(sc), cmd->c_error));
}

void
exesdhc_read_data(struct exesdhc_softc *sc, u_char *datap, int datalen)
{
	while (datalen > 3) {
		*(uint32_t *)datap = HREAD4(sc, SDHC_DATA_BUFF_ACC_PORT);
		datap += 4;
		datalen -= 4;
	}
	if (datalen > 0) {
		uint32_t rv = HREAD4(sc, SDHC_DATA_BUFF_ACC_PORT);
		do {
			*datap++ = rv & 0xff;
			rv = rv >> 8;
		} while (--datalen > 0);
	}
}

void
exesdhc_write_data(struct exesdhc_softc *sc, u_char *datap, int datalen)
{
	while (datalen > 3) {
		DPRINTF(3,("%08x\n", *(uint32_t *)datap));
		HWRITE4(sc, SDHC_DATA_BUFF_ACC_PORT, *((uint32_t *)datap));
		datap += 4;
		datalen -= 4;
	}
	if (datalen > 0) {
		uint32_t rv = *datap++;
		if (datalen > 1)
			rv |= *datap++ << 8;
		if (datalen > 2)
			rv |= *datap++ << 16;
		DPRINTF(3,("rv %08x\n", rv));
		HWRITE4(sc, SDHC_DATA_BUFF_ACC_PORT, rv);
	}
}

/* Prepare for another command. */
int
exesdhc_soft_reset(struct exesdhc_softc *sc, int mask)
{
	int timo;

	DPRINTF(1,("%s: software reset reg=%#x\n", HDEVNAME(sc), mask));

	/* disable force CLK ouput active */
	HCLR4(sc, SDHC_VEND_SPEC, SDHC_VEND_SPEC_FRC_SDCLK_ON);

	/* reset */
	HSET4(sc, SDHC_SYS_CTRL, mask);
	delay(10);

	for (timo = 1000; timo > 0; timo--) {
		if (!ISSET(HREAD4(sc, SDHC_SYS_CTRL), mask))
			break;
		delay(10);
	}
	if (timo == 0) {
		DPRINTF(1,("%s: timeout reg=%#x\n", HDEVNAME(sc),
		    HREAD4(sc, SDHC_SYS_CTRL)));
		return ETIMEDOUT;
	}

	return 0;
}

int
exesdhc_wait_intr(struct exesdhc_softc *sc, int mask, int timo)
{
	int status;
	int s;

	mask |= SDHC_INT_STATUS_ERR;
	s = splsdmmc();

	/* enable interrupts for brr and bwr */
	if (mask & (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR))
		HSET4(sc, SDHC_INT_SIGNAL_EN, (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR));

	status = sc->intr_status & mask;
	while (status == 0) {
		if (tsleep(&sc->intr_status, PWAIT, "hcintr", timo)
		    == EWOULDBLOCK) {
			status |= SDHC_INT_STATUS_ERR;
			break;
		}
		status = sc->intr_status & mask;
	}
	sc->intr_status &= ~status;
	DPRINTF(2,("%s: intr status %#x error %#x\n", HDEVNAME(sc), status,
	    sc->intr_error_status));

	/* Command timeout has higher priority than command complete. */
	if (ISSET(status, SDHC_INT_STATUS_ERR)) {
		sc->intr_error_status = 0;
		(void)exesdhc_soft_reset(sc, SDHC_SYS_CTRL_RSTC | SDHC_SYS_CTRL_RSTD);
		status = 0;
	}

	splx(s);
	return status;
}

/*
 * Established by attachment driver at interrupt priority IPL_SDMMC.
 */
int
exesdhc_intr(void *arg)
{
	struct exesdhc_softc *sc = arg;

	u_int32_t status;

	/* Find out which interrupts are pending. */
	status = HREAD4(sc, SDHC_INT_STATUS);

#ifndef SDHC_DMA
	/* disable interrupts for brr and bwr, else we get flooded */
	if (status & (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR))
		HCLR4(sc, SDHC_INT_SIGNAL_EN, (SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR));
#endif

	/* Acknowledge the interrupts we are about to handle. */
	HWRITE4(sc, SDHC_INT_STATUS, status);
	DPRINTF(2,("%s: interrupt status=0x%08x\n", HDEVNAME(sc),
	    status));

	/*
	 * Service error interrupts.
	 */
	if (ISSET(status, SDHC_INT_STATUS_CMD_ERR |
	    SDHC_INT_STATUS_CTOE | SDHC_INT_STATUS_DTOE)) {
		sc->intr_status |= status;
		sc->intr_error_status |= status & 0xffff0000;
		wakeup(&sc->intr_status);
	}

	/*
	 * Wake up the blocking process to service command
	 * related interrupt(s).
	 */
	if (ISSET(status, SDHC_INT_STATUS_BRR | SDHC_INT_STATUS_BWR |
	    SDHC_INT_STATUS_TC | SDHC_INT_STATUS_CC)) {
		sc->intr_status |= status;
		wakeup(&sc->intr_status);
	}

	/*
	 * Service SD card interrupts.
	 */
	if (ISSET(status, SDHC_INT_STATUS_CINT)) {
		DPRINTF(0,("%s: card interrupt\n", HDEVNAME(sc)));
		HCLR4(sc, SDHC_INT_STATUS, SDHC_INT_STATUS_CINT);
		sdmmc_card_intr(sc->sdmmc);
	}
	return 1;
}
