/*
 * $QNXLicenseC:
 * Copyright 2014, QNX Software Systems.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not reproduce, modify or distribute this software except in
 * compliance with the License. You may obtain a copy of the License
 * at: http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OF ANY KIND, either express or implied.
 *
 * This file may contain contributions from others, either as
 * contributors under the License or as licensors under other terms.
 * Please review this entire file for other proprietary rights or license
 * notices, as well as the QNX Development Suite License Guide at
 * http://licensing.qnx.com/license-guide/ for other information.
 * $
 */


// Samsung Questions:
//  Describe UHS/HS200 tuning
//  Describe voltage switching
//	Describe AUTO_STOP failure
//	Can we assume IDMAC xfer has completed when DTO is set?


#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <hw/inout.h>
#include <sys/mman.h>

#include <internal.h>

#ifdef SDIO_HC_SYNOPSYS

//#define DW_DEBUG

#include <dwmshc.h>

static int dw_pwr( sdio_hc_t *hc, int vdd );
static int dw_drv_type( sdio_hc_t *hc, int type );
static int dw_bus_mode( sdio_hc_t *hc, int bus_mode );
static int dw_bus_width( sdio_hc_t *hc, int width );
static int dw_clk( sdio_hc_t *hc, int clk );
static int dw_timing( sdio_hc_t *hc, int timing );
static int dw_signal_voltage( sdio_hc_t *hc, int signal_voltage );
static int dw_cd( sdio_hc_t *hc );
static int dw_pm( sdio_hc_t *hc, int op );
static int dw_cmd( sdio_hc_t *hc, sdio_cmd_t *cmd );
static int dw_abort( sdio_hc_t *hc, sdio_cmd_t *cmd );
static int dw_event( sdio_hc_t *hc, sdio_event_t *ev );
static int dw_tune( sdio_hc_t *hc, int op );
static int dw_preset( sdio_hc_t *hc, int enable );

static sdio_hc_entry_t dw_hc_entry =	{	.nentries			= SDIO_HC_ENTRY_NFUNCS,
											.dinit				= dw_dinit,
											.pm					= NULL,
											.cmd				= dw_cmd,
											.abort				= dw_abort,
											.event				= dw_event,
											.cd					= dw_cd,
											.pwr				= dw_pwr,
											.clk				= dw_clk,
											.bus_mode			= dw_bus_mode,
											.bus_width			= dw_bus_width,
											.timing				= dw_timing,
											.signal_voltage		= dw_signal_voltage,
											.drv_type			= dw_drv_type,
											.driver_strength	= NULL,
											.tune				= dw_tune,
											.preset				= dw_preset
										};

#ifdef DW_DEBUG
static int dw_idma_dump( sdio_hc_t *hc, const char *func )
{
	dw_hc_msh_t			*msh;
	dw_idmac_desc_t		*idma;

	msh		= (dw_hc_msh_t *)hc->cs_hdl;
	idma	= (dw_idmac_desc_t *)msh->idma;

	do {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s (%s): des0 0x%x, des1 0x%x, des2 0x%x, des3 0x%x", __FUNCTION__, func, idma->des0, idma->des1, idma->des2, idma->des3 );
		idma++;
	} while( idma->des0 );

	return( EOK );
}
#endif


static unsigned clksel_reg_get(uintptr_t base) {
#ifdef EXYNOS_DW_MMC
	return in32( base + DW_CLKSEL );
#else
	(void) base;
	return 0;
#endif
}

static void clksel_reg_set(uintptr_t base, unsigned clksel) {
#ifdef EXYNOS_DW_MMC
	out32( base + DW_CLKSEL, clksel );
#else
	(void) base;
	(void) clksel;
#endif
}

static int dw_reg_dump( sdio_hc_t *hc, const char *func, int line )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;

	msh	= hc->cs_hdl;
	base	= msh->base;

	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: line %d", func, line );
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: CTRL %x, PWREN %x, CLKDIV %x, CLKSRC %x, CLKENA %x, TMOUT %x, CTYPE %x",
		__FUNCTION__,
		in32( base + DW_CTRL ),
		in32( base + DW_PWREN ),
		in32( base + DW_CLKDIV ),
		in32( base + DW_CLKSRC ),
		in32( base + DW_CLKENA ),
		in32( base + DW_TMOUT ),
		in32( base + DW_CTYPE ) );

	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: BLKSIZ %x, BYTCNT %x, INTMASK %x, CMDARG %x, CMD %x, MINTSTS %x, RINTSTS %x",
		__FUNCTION__,
		in32( base + DW_BLKSIZ ),
		in32( base + DW_BYTCNT ),
		in32( base + DW_INTMASK ),
		in32( base + DW_CMDARG ),
		in32( base + DW_CMD ),
		in32( base + DW_MINTSTS ),
		in32( base + DW_RINTSTS ) );

	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: RESP0 %x, RESP1 %x, RESP2 %x, RESP3 %x, STATUS %x, FIFOTH %x, CDETECT %x",
		__FUNCTION__,
		in32( base + DW_RESP0 ),
		in32( base + DW_RESP1 ),
		in32( base + DW_RESP2 ),
		in32( base + DW_RESP3 ),
		in32( base + DW_STATUS ),
		in32( base + DW_FIFOTH ),
		in32( base + DW_CDETECT ) );

	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: WRTPRT %x, GPIO %x, TCBCNT %x, TBBCNT %x, DEBNCE %x, USRID %x, VERID %x",
		__FUNCTION__,
		in32( base + DW_WRTPRT ),
		in32( base + DW_GPIO ),
		in32( base + DW_TCBCNT ),
		in32( base + DW_TBBCNT ),
		in32( base + DW_DEBNCE ),
		in32( base + DW_USRID ),
		in32( base + DW_VERID ) );

	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: HCON %x, UHS_REG %x, BMOD %x, PLDMND %x, DBADDR %x, IDSTS %x, IDINTEN %x, DSCADDR %x, BUFADDR %x",
		__FUNCTION__,
		in32( base + DW_HCON ),
		in32( base + DW_UHS_REG ),
		in32( base + DW_BMOD ),
		in32( base + DW_PLDMND ),
		in32( base + DW_DBADDR ),
		in32( base + DW_IDSTS ),
		in32( base + DW_IDINTEN ),
		in32( base + DW_DSCADDR ),
		in32( base + DW_BUFADDR ) );

	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: CLKSEL %x, CARDTHRCTL %x, BACKEND PWR %x, EMMC DDR REG %x, DDR200 RDDQS EN %x, DDR200 ASYNC FIFO CTRL %x",
		__FUNCTION__,
		clksel_reg_get(base),
		in32( base + DW_CARDTHRCTL ),
		in32( base + DW_BACK_END_POWER ),
		in32( base + DW_EMMC_DDR_REG ),
		in32( base + DW_DDR200_RDDQS_EN ),
		in32( base + DW_DDR200_ASYNC_FIFO_CTRL ) );

	return( EOK );
}

static int dw_waitmask( sdio_hc_t *hc, uint32_t reg, uint32_t mask, uint32_t val, uint32_t usec )
{
	dw_hc_msh_t			*msh;
	uintptr_t			base;
	uint32_t			cnt;
	int					stat;
	uint32_t		rval;
	uint32_t			iter;

	msh	= hc->cs_hdl;
	base	= msh->base;
	stat	= ETIMEDOUT;
	rval	= 0;

		// fast poll for 1ms - 1us intervals
	for( cnt = min( usec, 1000 ); cnt; cnt-- ) {
		rval = in32( base + reg );
		if ( ( rval & mask ) == val ) {
			stat = EOK;
			break;
		}
		nanospin_ns( 1000L );
	}

	if( ( usec > 1000u ) && ( stat != 0 ) ) {
		iter	= usec / 1000UL;
		for( cnt = iter; cnt; cnt-- ) {
			rval = in32( base + reg );
			if ( ( rval  & mask ) == val ) {
				stat = EOK;
				break;
			}
			delay( 1 );
		}
	}

#ifdef DW_DEBUG
	if( !cnt ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: reg %x, mask %x, val %x, rval %x", __FUNCTION__, reg, mask, val, in32( base + reg ) );
	}
#endif

	return( stat );
}

static int dw_set_ldo( sdio_hc_t *hc, int ldo, uint32_t voltage )
{
	int				status;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  ldo %s, voltage %d", __FUNCTION__, ( ldo == SDIO_LDO_VCC ) ? "VCC" : "VCC_IO", voltage );
#endif

	status = bs_set_ldo( hc, ldo, (int) voltage );
	if( status  != EOK ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: bs_set_ldo (status %d)", __FUNCTION__, status );
	}

	return( status );
}

static int dw_pm( sdio_hc_t *hc, int op )
{
#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:   pm %d", __FUNCTION__, pm );
#endif

	switch( op ) {
		case PM_IDLE:
			bs_clock_gate( hc, SDIO_TRUE );
			break;

		case PM_ACTIVE:
			bs_clock_gate( hc, SDIO_FALSE );
			if( hc->pm_state == PM_SLEEP ) {
				dw_set_ldo( hc, SDIO_LDO_VCC,  ( ( hc->vdd <= OCR_VDD_17_195 ) ? 1800u : 3000u ) );
			}
			break;

		case PM_SLEEP:
			dw_set_ldo( hc, SDIO_LDO_VCC, 0 );		// pwr off device
#ifdef BS_PAD_CONF
			bs_pad_conf( hc, SDIO_FALSE );
#endif
			bs_clock_gate( hc, SDIO_TRUE );
			break;

		default:
			break;
	}

	return( EOK );
}

static int dw_cmd_wait( sdio_hc_t *hc, uint32_t cmd, uint32_t arg, uint32_t tms )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;

	msh		= hc->cs_hdl;
	base		= msh->base;

	out32( base + DW_CMDARG, arg );
	out32( base + DW_CMD, cmd | DW_CMD_START );

	return( dw_waitmask( hc, DW_CMD, (uint32_t) DW_CMD_START, 0, tms ) );
}

static int dw_reset( sdio_hc_t *hc, uint32_t rst, uint32_t flgs )
{
	dw_hc_msh_t			*msh;
	uintptr_t			base;
	int					stat;
	uint32_t			ctrl;

	msh	= hc->cs_hdl;
	base	= msh->base;

	out32( base + DW_RINTSTS, (uint32_t) ~0 );

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  rst %x", __FUNCTION__, rst );
#endif

	ctrl	= in32( base + DW_CTRL );

	out32( base + DW_CTRL, ( ctrl & ~DW_CTRL_INT_ENABLE ) | rst );
	if( ( stat = dw_waitmask( hc, DW_CTRL, rst, 0, 100000 ) ) ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  timeout", __FUNCTION__ );
	}

	out32( base + DW_RINTSTS, (uint32_t) ~0 );		// clear pending interrupts
	out32( base + DW_CTRL, ctrl );

	if( flgs ) {					// update clk after CIU reset
		if( dw_cmd_wait( hc, DW_CMD_UPD_CLK | DW_CMD_PRV_DAT_WAIT, 0, 1000 ) ) {
		}
	}

	return( stat );
}

static inline int dw_pio_write( sdio_hc_t *hc, int fcnt, uint8_t *src, int slen )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	uint8_t			*sptr;
	int				olen;

	msh	= hc->cs_hdl;
	base	= msh->base;
	sptr	= src;

	while( slen && fcnt ) {
		olen = min( slen, fcnt );
		sptr = msh->douts( sptr, olen >> msh->dshft, base + DW_DATA );
		slen -= olen;
	}

	out32( base + DW_RINTSTS, DW_INT_TXDR );

	return( sptr - src );
}

static inline int dw_pio_read( sdio_hc_t *hc, int fcnt, uint8_t *dst, int dlen )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	void			*dptr;
	int				ilen;

	msh	= hc->cs_hdl;
	base	= msh->base;
	dptr	= (uint64_t *)dst;

	while( dlen && fcnt && !( in32( base + DW_STATUS ) & DW_STATUS_FIFO_EMPTY ) ) {
		ilen = min( dlen, fcnt );
		dptr = msh->dins( dptr, ilen >> msh->dshft, base + DW_DATA );
		dlen -= ilen;
		fcnt -= ilen;
	}

	return( (uint8_t *)dptr - dst );
}

static int dw_xfer_pio( sdio_hc_t *hc, sdio_cmd_t *cmd )
{
	dw_hc_msh_t			*msh;
	sdio_wspc_t			*wspc;
	uintptr_t			base;
	int			        len;
	uint32_t			fcnt;

	msh	= hc->cs_hdl;
	base	= msh->base;
	wspc	= &hc->wspc;

	do {

#ifdef DW_DEBUG
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  STATUS 0x%x, fcnt %d, xlen %d", __FUNCTION__, in32( base + DW_STATUS ), DW_STATUS_FIFO_COUNT( in32( base + DW_STATUS ) ) << msh->dshft, msh->xlen );
#endif

		if( !msh->xlen ) {
			break;
		}

		fcnt = DW_STATUS_FIFO_COUNT( in32( base + DW_STATUS ) ) << msh->dshft;
		if( ( cmd->flags & SCF_DIR_IN ) ) {
			len = dw_pio_read( hc, (int) fcnt, wspc->sga, wspc->sgc );
		}
		else {
			len = dw_pio_write( hc, (int) (msh->fifo_size - fcnt), wspc->sga, wspc->sgc );
		}

		msh->xlen	-= (uint) len;
		wspc->sga	+= len;
		wspc->sgc	-= len;

		if( !wspc->sgc && msh->xlen ) {	// advance to next sg entry
			if( --wspc->nsg ) {
				wspc->sge++;
				wspc->sgc	= (int) wspc->sge->sg_count;
				wspc->sga	= SDIO_DATA_PTR_V( wspc->sge->sg_address );
			}
		}
		out32( base + DW_RINTSTS, DW_INT_TXDR | DW_INT_RXDR );
	} while( ( in32( base + DW_MINTSTS ) & ( DW_INT_TXDR | DW_INT_RXDR ) ) );

	return( EOK );
}

static int dw_idma_stop( sdio_hc_t *hc )
{
	dw_hc_msh_t			*msh;
	uintptr_t			base;
	uint32_t			ctrl;
	uint32_t			bmod;

	msh	= hc->cs_hdl;
	base	= msh->base;

	ctrl	= in32( base + DW_CTRL ) & ~DW_CTRL_USE_IDMAC;
	out32( base + DW_CTRL, ctrl | DW_CTRL_DMA_RESET );

	bmod = in32( base + DW_BMOD ) & ~( DW_IDMAC_ENABLE | DW_IDMAC_FB );
	out32( base + DW_BMOD, bmod );

	return( EOK );
}

static int dw_intr_event( sdio_hc_t *hc )
{
	dw_hc_msh_t			*msh;
	sdio_cmd_t			*cmd;
	uint32_t			cs;
	uint32_t			sts;
	uint32_t			idsts;
	uintptr_t			base;

	msh	= hc->cs_hdl;
	base	= msh->base;

	cmd		= hc->wspc.cmd;
	cs		= CS_CMD_INPROG;

	sts		= in32( base + DW_MINTSTS );
	idsts	= in32( base + DW_IDSTS );

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  MINTSTS 0x%x, IDSTS 0x%x", __FUNCTION__, sts, idsts );
#endif

	if( ( sts & DW_INT_CDET ) ) {
		sdio_hc_event( hc, HC_EV_CD );
	}

	out32( base + DW_RINTSTS, sts );

	if( cmd == NULL ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  cmd == NULL MINTSTS 0x%x, IDSTS 0x%x", __FUNCTION__, sts, idsts );
		dw_reg_dump( hc, __FUNCTION__, __LINE__ );
		return( EOK );
	}

	if( (cmd->opcode == SD_VOLTAGE_SWITCH) && (( sts & DW_INT_VOLT_SWITCH ) != 0)) {
		sts &= ~DW_INT_VOLT_SWITCH;
		dw_reset( hc, DW_CTRL_RESET_ALL, SDIO_TRUE );		// reset CIU, FIFO, DMA
		cs = CS_CMD_CMP;
	}

		// Check of errors
	if( ( sts & DW_INT_ERRORS ) || ( idsts & DW_IDMAC_INT_AI ) ) {
		dw_reg_dump( hc, __FUNCTION__, __LINE__ );

		if( ( sts & DW_INT_DRTO ) ) {
			cs = CS_DATA_TO_ERR;
		}
		if( ( sts & DW_INT_DCRC ) ) {
			cs = CS_DATA_CRC_ERR;
		}
		if( ( sts & DW_INT_EBE ) ) {
			cs = CS_DATA_END_ERR;
		}
		if( !cs ) {
			cs = CS_CMD_CMP_ERR;
		}

		if( ( msh->flags & MF_XFER_DMA ) ) {
			if( ( idsts & DW_IDMAC_INT_AI ) ) {
				sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  IDSTS 0x%x", __FUNCTION__, idsts );
			}
			out32( base + DW_IDSTS, idsts );

#ifdef DW_DEBUG
			dw_idma_dump( hc, __FUNCTION__ );
#endif
			dw_idma_stop( hc );
		}
		dw_reset( hc, DW_CTRL_RESET_ALL, SDIO_TRUE );	// reset CIU, FIFO, DMA
	}
	else {
		if( ( sts & DW_INT_CD ) && !( cmd->flags & SCF_DATA_MSK ) ) {
			if( ( cmd->flags & SCF_RSP_136 ) ) {
				cmd->rsp[0] = in32( base + DW_RESP3 );
				cmd->rsp[1] = in32( base + DW_RESP2 );
				cmd->rsp[2] = in32( base + DW_RESP1 );
				cmd->rsp[3] = in32( base + DW_RESP0 );
			}
			else if( ( cmd->flags & SCF_RSP_PRESENT ) ) {
				cmd->rsp[0] = in32( base + DW_RESP0 );
			}
			else {
				// nothing
			}
			cs = CS_CMD_CMP;
		}

		if( ( sts & ( DW_INT_RXDR | DW_INT_TXDR ) ) ) {
			cs = (uint32_t) dw_xfer_pio( hc, cmd );
		}

		if( ( sts & DW_INT_DTO ) ) {			// Data Transfer Over
			cs = CS_CMD_CMP;
			cmd->rsp[0] = in32( base + DW_RESP0 );

			if( ( msh->flags & MF_XFER_DMA ) ) {
					// wait for IDMAC to complete/idle
				if( dw_waitmask( hc, DW_IDSTS, DW_IDSTS_FSM_MSK, DMAC_FSM_DMA_IDLE, 10000 ) ) {
					cs = CS_DATA_TO_ERR;
				}
				dw_idma_stop( hc );
			}
			else {
				// While reading in PIO mode DTO can be set while
				// the FIFO still has data.  This can happen when the
				// remaining data is < the FIFO threshold watermark.
				if( ( cmd->flags & SCF_DIR_IN ) && msh->xlen ) {
					dw_xfer_pio( hc, cmd );
				}
			}
		}

		if( ( sts & DW_INT_ACD ) ) {		// check for Auto Command Done
			cmd->rsp[0] = in32( base + DW_RESP1 );	// AUTO_STOP command resp
			cs = CS_CMD_CMP;
		}
	}

	if( cs != CS_CMD_INPROG ) {
#ifdef DW_DEBUG
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  cmd cmplt", __FUNCTION__ );
#endif
		msh->flags &= ~MF_XFER_DMA;

		if( (cs == CS_CMD_CMP) && (( in32( base + DW_INTMASK ) & (uint32_t) DW_INT_ACD ) != 0 )) {
			if( !( sts & DW_INT_ACD ) ) {	// wait until Auto Command Done
				return( EOK );
			}
		}

		sdio_cmd_cmplt( hc, cmd, (int) cs );
	}

	return( EOK );
}

static int dw_event( sdio_hc_t *hc, sdio_event_t *ev )
{
	int		status;

	switch( ev->code ) {
		case HC_EV_INTR:
			status = dw_intr_event( hc );
			InterruptUnmask( hc->cfg.irq[0], hc->hc_iid );
			break;

		default:
#ifdef BS_EVENT
			status = bs_event( hc, ev );
#else
			status = ENOTSUP;
#endif
			break;
	}

	return( status );
}

static int dw_idmac_setup( sdio_hc_t *hc, sdio_cmd_t *cmd )
{
	dw_hc_msh_t       *msh;
	dw_idmac_desc_t   *idma;
	sdio_sge_t        *sgp;
	int                sgc;
	int                sgi;
	int                acnt;
	unsigned int       alen;
	uintptr_t          base;
	unsigned int       sg_count;
	paddr_t            sg_paddr;
	paddr_t            idmap;

	msh	= hc->cs_hdl;
	idma	= msh->idma;
	base	= msh->base;
	idmap	= msh->idmap;

	sgc = (int) cmd->sgc;
	sgp = cmd->sgl;

	if( !( cmd->flags & SCF_DATA_PHYS ) ) {
		sdio_vtop_sg( sgp, msh->sgl, sgc, cmd->mhdl );
		sgp = msh->sgl;
	}

	acnt = 0;
	for( sgi = 0; sgi < sgc; sgi++ ) {
		sg_count	= sgp->sg_count;
		sg_paddr	= sgp->sg_address;
		while( sg_count ) {
			alen		= min( sg_count, IDMA_MAX_XFER );
			idmap		+= sizeof( dw_idmac_desc_t );
			idma->des0	= (uint32_t) (IDMAC_DES0_OWN | IDMAC_DES0_DIC | IDMAC_DES0_CH);
			idma->des1	= alen;
			idma->des2	= (uint32_t) sg_paddr;
			idma->des3	= (uint32_t) idmap;

			sg_count	-= alen;
			sg_paddr	+= alen;
			idma++;

			if( ++acnt > DW_DMA_DESC_MAX ) {
				return( ENOTSUP );
			}
		}
		sgp++;
	}

	idma->des0 = 0;				// temp for debug

	idma--;
	idma->des0	= (uint32_t) (IDMAC_DES0_OWN | IDMAC_DES0_DIC | IDMAC_DES0_LD);
	idma->des3	= 0;

		// set first descriptor
	msh->idma->des0	|= IDMAC_DES0_FD;

	out32( base + DW_DBADDR, msh->idmap );
	out32( base + DW_BMOD, in32( base + DW_BMOD ) | DW_IDMAC_ENABLE | DW_IDMAC_FB );
	out32( base + DW_PLDMND, DW_PLDMND_PD );

	return( EOK );
}

static int dw_xfer_setup( sdio_hc_t *hc, sdio_cmd_t *cmd, uint32_t *command, uint32_t *imask )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	uint32_t		ctrl;
	int				status;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  %s, blksz %d, nblks %d", __FUNCTION__, ( cmd->flags & SCF_DIR_IN ) ? "in" : "out", cmd->blksz, cmd->blks );
#endif

	msh		= hc->cs_hdl;
	base		= msh->base;
	status		= EOK;
	*imask		|= DW_INT_DTO;			// data transfer over interrupt
	*command	|= ( cmd->flags & SCF_DIR_IN ) ? DW_CMD_DAT_EXP : ( DW_CMD_DAT_EXP | DW_CMD_DAT_WR );
	ctrl		= in32( base + DW_CTRL ) & ~( DW_CTRL_USE_IDMAC | DW_CTRL_DMA_ENABLE );


	sdio_sg_start( hc, cmd->sgl, (int) cmd->sgc );

	if( ( hc->caps & HC_CAP_DMA ) ) {
		status = dw_idmac_setup( hc, cmd );
		if( status  == EOK ) {
			ctrl |= ( DW_CTRL_USE_IDMAC | DW_CTRL_DMA_ENABLE );
			msh->flags |= MF_XFER_DMA;
		}
	}

	if( status || !( hc->caps & HC_CAP_DMA ) ) {	// use PIO
		if( !( hc->caps & HC_CAP_PIO ) || ( cmd->flags & SCF_DATA_PHYS ) ) {
			return( ENOTSUP );
		}
		msh->xlen	= cmd->blksz * cmd->blks;
		*imask		|= ( cmd->flags & SCF_DIR_IN ) ? DW_INT_RXDR : DW_INT_TXDR;
	}

	if( cmd->blks > 1 ) {
		if( ( hc->caps & HC_CAP_ACMD12 ) ) {
			*imask		|= DW_INT_ACD;
			*command	|= DW_CMD_SEND_STOP;
		}
	}

	out32( base + DW_CTRL, ctrl );
	out32( base + DW_BYTCNT, cmd->blksz * cmd->blks );
	out32( base + DW_BLKSIZ, cmd->blksz );

	return( EOK );
}

static int dw_cmd( sdio_hc_t *hc, sdio_cmd_t *cmd )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	uint32_t		imask;
	int			status;
	uint32_t		command;

	msh		= hc->cs_hdl;
	base		= msh->base;
	imask		= DW_INT_DFLTS;
	cmd->status	= CS_CMD_INPROG;

#ifdef BS_POWMAN
	bs_powman( hc, POWMAN_ALIVE );
#endif

	out32( base + DW_RINTSTS, (uint32_t) ~0 );			// Clear Status

	command	= cmd->opcode | DW_CMD_USE_HOLD_REG | DW_CMD_START;

	if( ( msh->flags & MF_SEND_INIT_STREAM ) ) {
		msh->flags &= ~MF_SEND_INIT_STREAM;
		command |= DW_CMD_INIT;
	}

	if( cmd->opcode == SD_VOLTAGE_SWITCH ) {
		command |= DW_CMD_VOLT_SWITCH;
	}

	if( cmd->opcode == MMC_STOP_TRANSMISSION ) {
		command |= DW_CMD_STOP;
	}
	else {
		command |= DW_CMD_PRV_DAT_WAIT;
	}

	if( ( cmd->flags & SCF_DATA_MSK ) ) {
		status = dw_xfer_setup( hc, cmd, &command, &imask );
		if( status != EOK ) {
			return( status );
		}
	}
	else {
		imask |= DW_INT_CD;				// Enable command done int
	}

	if( ( cmd->flags & SCF_RSP_PRESENT ) ) {
		command |= DW_CMD_RESP_EXP;

		if( ( cmd->flags & SCF_RSP_136 ) ) {
			command |= DW_CMD_RESP_LONG;
		}

		if( ( cmd->flags & SCF_RSP_CRC ) ) {		// CRC check
			command |= DW_CMD_RESP_CRC;
		}
	}

	out32( base + DW_INTMASK, imask );
	out32( base + DW_CMDARG, cmd->arg );
	out32( base + DW_CMD, command );

	return( EOK );
}

static int dw_abort( sdio_hc_t *hc, sdio_cmd_t *cmd )
{
	(void) cmd;
	dw_idma_stop( hc );
	dw_reset( hc, DW_CTRL_RESET_ALL, SDIO_TRUE );	// reset CIU, FIFO, DMA
	delay( 2 );							// allow pending events to complete

	return( EOK );
}

static int dw_pwr( sdio_hc_t *hc, int vdd )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  vdd 0x%x", __FUNCTION__, vdd );
#endif

	msh	= hc->cs_hdl;
	base	= msh->base;

	dw_pm( hc, PM_ACTIVE );

	out32( base + DW_IDINTEN, 0 );				// disable intrs

	if( !vdd ) {
		dw_reset( hc, DW_CTRL_RESET_ALL, SDIO_FALSE );	// reset CIU, FIFO, DMA

		if( (hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED) == 0) { 	// don't turn off eMMC
			dw_set_ldo( hc, SDIO_LDO_VCC, 0 );		// pwr off device
			dw_set_ldo( hc, SDIO_LDO_VCC_IO, 0 );
#ifdef BS_PAD_CONF
			bs_pad_conf( hc, SDIO_FALSE );
#endif
		}
		dw_pm( hc, PM_IDLE );
	}
	else {
		msh->flags |= MF_SEND_INIT_STREAM;

#ifdef BS_PAD_CONF
		bs_pad_conf( hc, SDIO_TRUE );
#endif

		dw_set_ldo( hc, SDIO_LDO_VCC,  ( ( vdd <= OCR_VDD_17_195 ) ? 1800u : 3000u ) );
		dw_set_ldo( hc, SDIO_LDO_VCC_IO, 3000 );

		out32( base + DW_CTRL, DW_CTRL_INT_ENABLE );	// enable intrs
	}

	hc->vdd = (uint32_t) vdd;

	return( EOK );
}

static int dw_bus_mode( sdio_hc_t *hc, int bus_mode )
{
#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  bus_mode %d", __FUNCTION__, bus_mode );
#endif

	if( bus_mode == BUS_MODE_OPEN_DRAIN ) {
	}

	hc->bus_mode = (uint32_t) bus_mode;

	return( EOK );
}

static int dw_bus_width( sdio_hc_t *hc, int width )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	uint32_t		ctype;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  width %d", __FUNCTION__, width );
#endif

	msh	= hc->cs_hdl;
	base	= msh->base;
	ctype	= 0;

	switch( width ) {
		case BUS_WIDTH_8:
			ctype |= DW_CTYPE_8BIT;
			break;
		case BUS_WIDTH_4:
			ctype |= DW_CTYPE_4BIT;
			break;
		case BUS_WIDTH_1:
		default:
			ctype |= DW_CTYPE_1BIT;
			break;
	}

	out32( base + DW_CTYPE, ctype );

	hc->bus_width = (uint32_t) width;

	return( EOK );
}

static int dw_clk( sdio_hc_t *hc, int clk )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	uint32_t		divisor;
	uint32_t		clkd;
	uint32_t		clkena;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  clk %d", __FUNCTION__, clk );
#endif

	msh			= hc->cs_hdl;
	base		= msh->base;

	divisor = 1;
	for( clkd = 0; clkd <= 0xFF; ) {
		if( ( ( msh->sclk / msh->divratio ) / divisor ) <= (uint64_t)clk ) {
			break;
		}
		divisor = ++clkd * 2;
	}

		// disable card clock generation
	out32( base + DW_CLKENA, 0 );
	out32( base + DW_CLKSRC, 0 );
	if( dw_cmd_wait( hc, DW_CMD_UPD_CLK | DW_CMD_PRV_DAT_WAIT, 0, 1000 ) ) {
	}

	out32( base + DW_CLKDIV, clkd );
	if( dw_cmd_wait( hc, DW_CMD_UPD_CLK | DW_CMD_PRV_DAT_WAIT, 0, 1000 ) ) {
	}

	clkena = (uint32_t) ( DW_CLKEN_ENABLE << msh->cardno ) | ( DW_CLKEN_LOW_PWR << msh->cardno );
	out32( base + DW_CLKENA, clkena );
	if( dw_cmd_wait( hc, DW_CMD_UPD_CLK | DW_CMD_PRV_DAT_WAIT, 0, 1000 ) ) {
	}

	hc->clk = (uint32_t)( msh->sclk / msh->divratio ) / divisor;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  hc->clk %d", __FUNCTION__, hc->clk );
#endif

	return( EOK );
}

static int dw_timing( sdio_hc_t *hc, int timing )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	uint32_t		uhs;
	uint32_t		clksel;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: timing %d", __FUNCTION__, timing );
#endif

	msh	= hc->cs_hdl;
	base	= msh->base;

	clksel	= msh->sdr;
	uhs		= in32( base + DW_UHS_REG ) & ~DW_DDR( msh->cardno );

	switch( timing ) {
		case TIMING_HS200:
			clksel	= msh->sdr;
			break;

		case TIMING_DDR50:
			clksel	= msh->ddr;
			uhs		|= DW_DDR( msh->cardno );
			break;

		case TIMING_SDR104:
		case TIMING_SDR50:
		case TIMING_SDR25:
		case TIMING_SDR12:
			clksel	= msh->sdr;
			uhs		|= DW_VOLT_1_8( msh->cardno );
			break;

		case TIMING_HS:
		case TIMING_LS:
			clksel	= msh->sdr;
			break;

		default:
			break;
	}

	out32( base + DW_UHS_REG, uhs );

	if( DW_VERID_CREV( hc->version ) >= DW_VERID_CREV_240A ) {
		clksel_reg_set(base, clksel );
	}

	hc->timing = (uint32_t) timing;

	return( EOK );
}

static int dw_signal_voltage( sdio_hc_t *hc, int signal_voltage )
{
	dw_hc_msh_t			*msh;
	uintptr_t			base;
	uint32_t			uhs;
	uint32_t			clkena;
	int					status;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s:  signal voltage %d", __FUNCTION__, signal_voltage );
#endif

	msh	= hc->cs_hdl;
	status	= EOK;
	base	= msh->base;
	uhs		= in32( base + DW_UHS_REG );

	if( signal_voltage == SIGNAL_VOLTAGE_1_8 ) {
		clkena = in32( base + DW_CLKENA ) & ~( ( DW_CLKEN_ENABLE | DW_CLKEN_LOW_PWR ) << msh->cardno );
		out32( base + DW_CLKENA, clkena );
		if( dw_cmd_wait( hc, DW_CMD_UPD_CLK | DW_CMD_PRV_DAT_WAIT, 0, 1000 ) ) {
		}

		status = dw_set_ldo( hc, SDIO_LDO_VCC_IO, 1800 );
		if( status == EOK ) {
			out32( base + DW_UHS_REG, uhs | DW_VOLT_1_8( msh->cardno ) );
		}

		delay( 5 );

		clkena = in32( base + DW_CLKENA ) | ( DW_CLKEN_ENABLE << msh->cardno ) | ( DW_CLKEN_LOW_PWR << msh->cardno );
		out32( base + DW_CLKENA, clkena );
		if( dw_cmd_wait( hc, DW_CMD_UPD_CLK | DW_CMD_PRV_DAT_WAIT, 0, 1000 ) ) {
		}
	}
	else if( signal_voltage == SIGNAL_VOLTAGE_3_3 ) {
		if( hc->vdd > OCR_VDD_17_195 ) {
			uhs &= ~DW_VOLT_1_8( msh->cardno );
			status = dw_set_ldo( hc, SDIO_LDO_VCC_IO, 3000 );
			if( status == EOK ) {
				out32( base + DW_UHS_REG, uhs );
			}
			delay( 5 );
		}
	}
	else {
		return( EINVAL );
	}

	hc->signal_voltage = (uint32_t) signal_voltage;

	return( status );
}

static int dw_drv_type( sdio_hc_t *hc, int type )
{
#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: type %d", __FUNCTION__, type );
#endif

	hc->drv_type = (uint32_t) type;

	return( EOK );
}

static int dw_median_sample( uint8_t map, uint8_t bit )
{
	uint8_t			_map;
	uint8_t			pattern;
	int				sample;
	const uint8_t	patterns[9] = { 0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };

	pattern = (uint8_t) DW_ROR8( patterns[bit], bit / 2 );

	for( sample = 0; sample < DW_MS_ITER; sample++ ) {
		_map = (uint8_t) DW_ROR8( map, sample );
		if( (uint8_t)( _map & pattern ) == pattern ) {
			return( sample );
		}
	}

	return( -1 );
}

static int dw_tune( sdio_hc_t *hc, int op )
{
	dw_hc_msh_t		*msh;
	sdio_cmd_t		*cmd;
	sdio_sge_t		sge;
	int				status;
	const uint8_t	*tbp;
	uint8_t			*tbd;
	uint8_t			map;
	uint32_t		tbl;
	uintptr_t		base;
	uint32_t		bits;
	uint32_t		clksel;
	uint32_t		divratio;
	int				sample;

#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: ", __FUNCTION__ );
#endif

	msh	= hc->cs_hdl;
	base	= msh->base;
	status	= EOK;
	map		= 0;

	switch( hc->timing ) {
		case TIMING_HS200:		// only HS200/SDR104/SDR50 require tuning
		case TIMING_SDR104:
		case TIMING_SDR50:
			break;

		default:
			return( EOK );
	}

	if( ( op == MMC_SEND_TUNING_BLOCK ) && ( hc->bus_width == ( (uint32_t) BUS_WIDTH_8 ) ) ) {
		tbl = 128;
		tbp = sdio_tbp_8bit;
	}
	else {
		tbl = 64;
		tbp = sdio_tbp_4bit;
	}

	divratio = ( msh->sdr >> DW_DIVRATIO_SHFT ) & DW_DIVRATIO_MSK;
	if( ( divratio != 1U ) && ( divratio != 3U ) ) {
		return( EIO );
	}

	bits = divratio + 2;

	cmd = sdio_alloc_cmd( );
	if( cmd  == NULL ) {
		return( ENOMEM );
	}

	tbd = sdio_alloc( tbl );
	if( tbd  == NULL ) {
		sdio_free_cmd( cmd );
		return( ENOMEM );
	}

	clksel = clksel_reg_get(base)  & ~DW_CLK_SMPL_MSK;
	out32( base + DW_CARDTHRCTL, DW_CARDTHRCTL_THRES( 512 ) | DW_CARDTHRCTL_EN );

	for( sample = 0; sample <= DW_CLK_SMPL_MAX; sample++ ) {
		clksel_reg_set(base, clksel | sample);
		sdio_setup_cmd( cmd, SCF_CTYPE_ADTC | SCF_RSP_R1, (uint32_t) op, 0 );
		sge.sg_count = tbl;
		sge.sg_address = (paddr_t)tbd;
		sdio_setup_cmd_io( cmd, SCF_DIR_IN, 1, tbl, &sge, 1, NULL );
		status = sdio_issue_cmd( &hc->device, cmd, DW_TUNING_TIMEOUT );
		if( status == EOK ) {
			if( !memcmp( tbp, tbd, tbl ) ) {
				map |= ( 1 << sample );
			}
		}
	}

	sample = dw_median_sample( map, (uint8_t) bits );
	if( sample < 0 ) {
		out32( base + DW_CARDTHRCTL, 0 );
		clksel_reg_set(base, clksel);
		status = ESTALE;
	}
	else {
		clksel_reg_set(base, clksel | sample);
	}

	sdio_free( tbd, tbl );
	sdio_free_cmd( cmd );

	return( status );
}

static int dw_preset( sdio_hc_t *hc, int enable )
{
	(void) hc;
	(void) enable;
#ifdef DW_DEBUG
	sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: ", __FUNCTION__ );
#endif

	return( EOK );
}

static int dw_cd( sdio_hc_t *hc )
{
	dw_hc_msh_t		*msh;
	uintptr_t		base;
	int				cstate;

	msh	= hc->cs_hdl;
	base	= msh->base;

	cstate	= CD_RMV;

	if( ( hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED ) ) {
		cstate  |= CD_INS;
	}
	else if( !( in32( base + DW_CDETECT ) & ( 1 << msh->cardno ) ) ) {
		cstate  |= CD_INS;
		if( ( in32( base + DW_WRTPRT ) & ( 1 << msh->cardno ) ) ) {
			cstate  |= CD_WP;
		}
	}
	else {
		// nothing
	}

	return( cstate );
}

int dw_dinit( sdio_hc_t *hc )
{
	dw_hc_msh_t		*msh;

	msh = hc->cs_hdl ;
	if( msh == NULL ) {
		return( EOK );
	}

	if( msh->base ) {
		dw_pwr( hc, 0 );
		if( hc->hc_iid != -1 ) {
			InterruptDetach( hc->hc_iid );
		}
		munmap_device_io( msh->base, DW_MSH_SIZE );
	}

	if( msh->idma ) {
		munmap( msh->idma, sizeof( dw_idmac_desc_t ) * DW_DMA_DESC_MAX );
	}

	free( msh );
	hc->cs_hdl = NULL;

	return( EOK );
}

static int dw_idmac_init( sdio_hc_t *hc )
{
	dw_hc_msh_t			*msh;
	int					n_idma;
	paddr_t				paddr;
	dw_idmac_desc_t		*idesc;

	msh	= hc->cs_hdl;
	msh->idma = mmap( NULL, sizeof( dw_idmac_desc_t ) * DW_DMA_DESC_MAX,
			PROT_READ | PROT_WRITE | PROT_NOCACHE,
			MAP_SHARED | MAP_ANON | MAP_PHYS, NOFD, 0 );
	idesc = msh->idma;
	if( idesc == MAP_FAILED ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: IDMA descriptor mmap %s", __FUNCTION__, strerror( errno ) );
		return( errno );
	}

	msh->idmap = (uint32_t) sdio_vtop( msh->idma );
	paddr = msh->idmap;

		// link descriptors
	for( n_idma = DW_DMA_DESC_MAX - 1; n_idma; n_idma-- ) {
		paddr      += sizeof( dw_idmac_desc_t );
		idesc->des3	= (uint32_t) paddr;
		idesc++;
	}

	idesc->des0	= IDMAC_DES0_ER;

	out32( msh->base + DW_DBADDR, msh->idmap );

	return( EOK );
}

static int dw_msh_init( sdio_hc_t *hc )
{
	dw_hc_msh_t			*msh;
	uintptr_t			base;
	uint32_t			fsize;

	msh	= hc->cs_hdl;
	base	= msh->base;
	fsize	= msh->fifo_size >> msh->dshft;

//		do we need a card hardware reset
//	out32( base + DW_RST, in32( base + DW_RST ) & ~( DW_RST_ACTIVE << msh->cardno ) );
//	out32( base + DW_RST, in32( base + DW_RST ) | ( DW_RST_ACTIVE << msh->cardno ) );
//	out32( base + DW_PWREN, 0 );

	out32( base + DW_PWREN, DW_PWREN_POWER_ENABLE );
//	delay( 10 );								// Wait for the power ramp-up time
	dw_reset( hc, DW_CTRL_RESET_ALL, SDIO_FALSE );	// reset CIU, FIFO, DMA
	out32( base + DW_BMOD, DW_IDMAC_SWRESET );	// reset IDMAC
	out32( base + DW_INTMASK, 0 );
	out32( base + DW_RINTSTS, (uint32_t) ~0 );
	out32( base + DW_IDINTEN, 0 );
	out32( base + DW_IDSTS, DW_IDMAC_INT_MSK );
	out32( base + DW_CTRL, DW_CTRL_INT_ENABLE );

		// set multiple transaction size, rx watermark,  tx watermark
	out32( base + DW_FIFOTH, DW_FIFOTH_MSIZE_32 | ( ( ( fsize / 2 ) - 1 ) << DW_FIFOTH_RX_WMARK_SHFT ) | ( fsize / 2 ) );

	out32( base + DW_CTYPE, DW_CTYPE_1BIT );
	out32( base + DW_DEBNCE, DW_DEBNCE_25MS );
	out32( base + DW_TMOUT, DW_TMOUT_DATA_MAX | DW_TMOUT_RESP_MAX );
	out32( base + DW_CARDTHRCTL, DW_CARDTHRCTL_EN | DW_CARDTHRCTL_THRES( 0x200 ) );

	return( EOK );
}

int dw_init( sdio_hc_t *hc )
{
	sdio_hc_cfg_t		*cfg;
	dw_hc_msh_t			*msh;
	uint32_t			hcon;
	uint32_t			verid;
	uint32_t			status;
	uintptr_t			base;
	struct sigevent		event;

	hc->cs_hdl = calloc( 1, sizeof( dw_hc_msh_t ) );
	if( hc->cs_hdl == NULL ) {
		return( ENOMEM );
	}

	cfg			= &hc->cfg;
	msh			= hc->cs_hdl;
	msh->pbase		= cfg->base_addr[0];
	hc->hc_iid		= -1;

	msh->sclk = (uint64_t)( (cfg->clk) ? cfg->clk : DW_SCLK );

	sdio_slogf( _SLOGC_SDIODI, _SLOG_INFO, 1, 1, "%s: Base 0x%lx Irq %d, Clk %d", __FUNCTION__,
		cfg->base_addr[0], cfg->irq[0], cfg->clk);

	memcpy( &hc->entry, &dw_hc_entry, sizeof( sdio_hc_entry_t ) );

        msh->base = mmap_device_io( DW_MSH_SIZE, msh->pbase );
        base = msh->base;

	if( base == (uintptr_t)MAP_FAILED ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: msh base mmap_device_io (0x%lx) %s", __FUNCTION__, msh->pbase, strerror( errno ) );
		dw_dinit( hc );
		return( errno );
	}

	hc->ocr		= OCR_VDD_32_33 | OCR_VDD_33_34;
	hc->clk_max	= (uint32_t) (hc->clk_max ? hc->clk_max : DW_CLK_MAX);
	hc->clk_min	= DW_CLK_MIN;

	hc->caps |= HC_CAP_BW4 | HC_CAP_BW8 | HC_CAP_CD_WP;
	hc->caps |= HC_CAP_PIO | HC_CAP_DMA | HC_CAP_ACMD12;
	hc->caps |= HC_CAP_200MA | HC_CAP_DRV_TYPE_B;
#if 0
	hc->caps |= HC_CAP_HS200 | HC_CAP_HS | HC_CAP_DDR50 | HC_CAP_SDR104 |
				HC_CAP_SDR50 | HC_CAP_SDR25 | HC_CAP_SDR12;
#else
	hc->caps |= HC_CAP_HS | HC_CAP_DDR50;
#endif

	hc->caps |= HC_CAP_SV_3_3V;

	verid       = in32( base + DW_VERID );
	hc->version = verid;

	msh->divratio	= DW_CLKGEN_DIVRATIO;
	hcon			= in32( base + DW_HCON );
	switch( DW_HCON_DATA_WIDTH( hcon ) ) {
		case DW_HCON_DATA_WIDTH16:
			msh->dshft	= 0;
			msh->dins	= in16s;
			msh->douts	= out16s;
			break;
		case DW_HCON_DATA_WIDTH32:
			msh->dshft	= 2;
			msh->dins	= in32s;
			msh->douts	= out32s;
			break;
		case DW_HCON_DATA_WIDTH64:
			msh->dshft	= 3;
			msh->dins	= dw_in64s;
			msh->douts	= dw_out64s;
			break;
		default:
			sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: msh invalid HCON DATA WIDTH (0x%x)", __FUNCTION__, DW_HCON_DATA_WIDTH( hcon ) );
			dw_dinit( hc );
			return( EINVAL );
	}

	if( !msh->ncards ) {
		msh->ncards		= DW_HCON_NUM_CARD( hcon ) + 1;
	}

	if( !msh->fifo_size ) {
			// we could use power on value, but it has
			// probably been changed by loader
//		msh->fifo_size	= ( in32( base + DW_FIFOTH ) >> DW_FIFOTH_RX_WMARK ) + 1;
		msh->fifo_size = (uint32_t) ( ( DW_VERID_CREV( verid ) >= DW_VERID_CREV_240A ) ? DW_FIFO_SZ_128 : DW_FIFO_SZ_32 );
	}

	msh->fifo_size <<= msh->dshft;

	if( DW_VERID_CREV( verid ) >= DW_VERID_CREV_240A ) {

	}

	dw_msh_init( hc );

	status = (uint32_t) dw_idmac_init( hc );
	if( status != EOK ) {
		dw_dinit( hc );
		return( status );
	}

	hc->cfg.sg_max	= DW_DMA_DESC_MAX;

	hc->caps	&= cfg->caps;		// reconcile command line options

	SIGEV_PULSE_INIT( &event, hc->hc_coid, (short) hc->priority, HC_EV_INTR, NULL );
	hc->hc_iid = InterruptAttachEvent( cfg->irq[0], &event, _NTO_INTR_FLAGS_TRK_MSK );
	if( hc->hc_iid == -1 ) {
		sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 1, 1, "%s: InterrruptAttachEvent (irq 0x%x) - %s", __FUNCTION__, cfg->irq[0], strerror( errno ) );
		dw_dinit( hc );
		return( errno );
	}

	return( EOK );
}

#endif

#if defined(__QNXNTO__) && defined(__USESRCVERSION)
#include <sys/srcversion.h>
__SRCVERSION("$URL: http://svn.ott.qnx.com/product/hardware/branches/release/hardware/devb/sdmmc/sdiodi/hc/dwmshc.c $ $Rev: 993163 $")
#endif
