1426 lines
33 KiB
C
1426 lines
33 KiB
C
/*
|
|
* $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
|