#include "sdhost.h"
typedef struct sdstats sdstats_t;
typedef struct sdslot sdslot_t;
typedef struct sdhost sdhost_t;
struct sdstats {
kstat_named_t ks_ncmd;
kstat_named_t ks_ixfr;
kstat_named_t ks_oxfr;
kstat_named_t ks_ibytes;
kstat_named_t ks_obytes;
kstat_named_t ks_npio;
kstat_named_t ks_ndma;
kstat_named_t ks_nmulti;
kstat_named_t ks_baseclk;
kstat_named_t ks_cardclk;
kstat_named_t ks_tmusecs;
kstat_named_t ks_width;
kstat_named_t ks_flags;
kstat_named_t ks_capab;
};
#define SDFLAG_FORCE_PIO (1U << 0)
#define SDFLAG_FORCE_DMA (1U << 1)
struct sdslot {
sda_host_t *ss_host;
int ss_num;
ddi_acc_handle_t ss_acch;
caddr_t ss_regva;
kmutex_t ss_lock;
uint8_t ss_tmoutclk;
uint32_t ss_ocr;
uint16_t ss_mode;
boolean_t ss_suspended;
sdstats_t ss_stats;
#define ss_ncmd ss_stats.ks_ncmd.value.ui64
#define ss_ixfr ss_stats.ks_ixfr.value.ui64
#define ss_oxfr ss_stats.ks_oxfr.value.ui64
#define ss_ibytes ss_stats.ks_ibytes.value.ui64
#define ss_obytes ss_stats.ks_obytes.value.ui64
#define ss_ndma ss_stats.ks_ndma.value.ui64
#define ss_npio ss_stats.ks_npio.value.ui64
#define ss_nmulti ss_stats.ks_nmulti.value.ui64
#define ss_baseclk ss_stats.ks_baseclk.value.ui32
#define ss_cardclk ss_stats.ks_cardclk.value.ui32
#define ss_tmusecs ss_stats.ks_tmusecs.value.ui32
#define ss_width ss_stats.ks_width.value.ui32
#define ss_flags ss_stats.ks_flags.value.ui32
#define ss_capab ss_stats.ks_capab.value.ui32
kstat_t *ss_ksp;
uint8_t *ss_kvaddr;
int ss_blksz;
uint16_t ss_resid;
int ss_rcnt;
caddr_t ss_bounce;
ddi_dma_handle_t ss_bufdmah;
ddi_acc_handle_t ss_bufacch;
ddi_dma_cookie_t ss_bufdmac;
};
#define SDHOST_BOUNCESZ 65536
struct sdhost {
int sh_numslots;
ddi_dma_attr_t sh_dmaattr;
sdslot_t sh_slots[SDHOST_MAXSLOTS];
sda_host_t *sh_host;
ddi_intr_handle_t sh_ihandle;
int sh_icap;
uint_t sh_ipri;
};
#define PROPSET(x) \
(ddi_prop_get_int(DDI_DEV_T_ANY, dip, \
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, x, 0) != 0)
static int sdhost_attach(dev_info_t *, ddi_attach_cmd_t);
static int sdhost_detach(dev_info_t *, ddi_detach_cmd_t);
static int sdhost_quiesce(dev_info_t *);
static int sdhost_suspend(dev_info_t *);
static int sdhost_resume(dev_info_t *);
static void sdhost_enable_interrupts(sdslot_t *);
static void sdhost_disable_interrupts(sdslot_t *);
static int sdhost_setup_intr(dev_info_t *, sdhost_t *);
static uint_t sdhost_intr(caddr_t, caddr_t);
static int sdhost_init_slot(dev_info_t *, sdhost_t *, int, int);
static void sdhost_uninit_slot(sdhost_t *, int);
static sda_err_t sdhost_soft_reset(sdslot_t *, uint8_t);
static sda_err_t sdhost_set_clock(sdslot_t *, uint32_t);
static void sdhost_xfer_done(sdslot_t *, sda_err_t);
static sda_err_t sdhost_wait_cmd(sdslot_t *, sda_cmd_t *);
static uint_t sdhost_slot_intr(sdslot_t *);
static sda_err_t sdhost_cmd(void *, sda_cmd_t *);
static sda_err_t sdhost_getprop(void *, sda_prop_t, uint32_t *);
static sda_err_t sdhost_setprop(void *, sda_prop_t, uint32_t);
static sda_err_t sdhost_poll(void *);
static sda_err_t sdhost_reset(void *);
static sda_err_t sdhost_halt(void *);
static struct dev_ops sdhost_dev_ops = {
DEVO_REV,
0,
ddi_no_info,
nulldev,
nulldev,
sdhost_attach,
sdhost_detach,
nodev,
NULL,
NULL,
NULL,
sdhost_quiesce,
};
static struct modldrv sdhost_modldrv = {
&mod_driverops,
"Standard SD Host Controller",
&sdhost_dev_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
{ &sdhost_modldrv, NULL }
};
static struct sda_ops sdhost_ops = {
SDA_OPS_VERSION,
sdhost_cmd,
sdhost_getprop,
sdhost_setprop,
sdhost_poll,
sdhost_reset,
sdhost_halt,
};
static ddi_device_acc_attr_t sdhost_regattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC,
DDI_DEFAULT_ACC,
};
static ddi_device_acc_attr_t sdhost_bufattr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC,
DDI_DEFAULT_ACC,
};
#define GET16(ss, reg) \
ddi_get16(ss->ss_acch, (void *)(ss->ss_regva + reg))
#define PUT16(ss, reg, val) \
ddi_put16(ss->ss_acch, (void *)(ss->ss_regva + reg), val)
#define GET32(ss, reg) \
ddi_get32(ss->ss_acch, (void *)(ss->ss_regva + reg))
#define PUT32(ss, reg, val) \
ddi_put32(ss->ss_acch, (void *)(ss->ss_regva + reg), val)
#define GET64(ss, reg) \
ddi_get64(ss->ss_acch, (void *)(ss->ss_regva + reg))
#define GET8(ss, reg) \
ddi_get8(ss->ss_acch, (void *)(ss->ss_regva + reg))
#define PUT8(ss, reg, val) \
ddi_put8(ss->ss_acch, (void *)(ss->ss_regva + reg), val)
#define CLR8(ss, reg, mask) PUT8(ss, reg, GET8(ss, reg) & ~(mask))
#define SET8(ss, reg, mask) PUT8(ss, reg, GET8(ss, reg) | (mask))
#ifdef _BIG_ENDIAN
#define sw32(x) ddi_swap32(x)
#define sw16(x) ddi_swap16(x)
#else
#define sw32(x) (x)
#define sw16(x) (x)
#endif
#define GETDATA32(ss) sw32(GET32(ss, REG_DATA))
#define GETDATA16(ss) sw16(GET16(ss, REG_DATA))
#define GETDATA8(ss) GET8(ss, REG_DATA)
#define PUTDATA32(ss, val) PUT32(ss, REG_DATA, sw32(val))
#define PUTDATA16(ss, val) PUT16(ss, REG_DATA, sw16(val))
#define PUTDATA8(ss, val) PUT8(ss, REG_DATA, val)
#define CHECK_STATE(ss, nm) \
((GET32(ss, REG_PRS) & PRS_ ## nm) != 0)
int
_init(void)
{
int rv;
sda_host_init_ops(&sdhost_dev_ops);
if ((rv = mod_install(&modlinkage)) != 0) {
sda_host_fini_ops(&sdhost_dev_ops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&modlinkage)) == 0) {
sda_host_fini_ops(&sdhost_dev_ops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
sdhost_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
sdhost_t *shp;
ddi_acc_handle_t pcih;
uint8_t slotinfo;
uint8_t bar;
int i;
int rv;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (sdhost_resume(dip));
default:
return (DDI_FAILURE);
}
shp = kmem_zalloc(sizeof (*shp), KM_SLEEP);
ddi_set_driver_private(dip, shp);
for (i = 0; i < SDHOST_MAXSLOTS; i++) {
shp->sh_slots[i].ss_num = -1;
}
shp->sh_dmaattr.dma_attr_version = DMA_ATTR_V0;
shp->sh_dmaattr.dma_attr_addr_lo = 0;
shp->sh_dmaattr.dma_attr_addr_hi = 0xffffffffU;
shp->sh_dmaattr.dma_attr_count_max = 0xffffffffU;
shp->sh_dmaattr.dma_attr_align = 4096;
shp->sh_dmaattr.dma_attr_burstsizes = 0;
shp->sh_dmaattr.dma_attr_minxfer = 1;
shp->sh_dmaattr.dma_attr_maxxfer = 0x7ffffU;
shp->sh_dmaattr.dma_attr_sgllen = 1;
shp->sh_dmaattr.dma_attr_seg = 0x7ffffU;
shp->sh_dmaattr.dma_attr_granular = 1;
shp->sh_dmaattr.dma_attr_flags = 0;
if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
cmn_err(CE_WARN, "pci_config_setup failed");
goto failed;
}
slotinfo = pci_config_get8(pcih, SLOTINFO);
shp->sh_numslots = SLOTINFO_NSLOT(slotinfo);
if (shp->sh_numslots > SDHOST_MAXSLOTS) {
cmn_err(CE_WARN, "Host reports to have too many slots: %d",
shp->sh_numslots);
pci_config_teardown(&pcih);
goto failed;
}
pci_config_put16(pcih, PCI_CONF_COMM,
pci_config_get16(pcih, PCI_CONF_COMM) |
PCI_COMM_MAE | PCI_COMM_ME);
bar = SLOTINFO_BAR(slotinfo) + 1;
pci_config_teardown(&pcih);
if (sdhost_setup_intr(dip, shp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed to setup interrupts");
goto failed;
}
shp->sh_host = sda_host_alloc(dip, shp->sh_numslots, &sdhost_ops,
NULL);
if (shp->sh_host == NULL) {
cmn_err(CE_WARN, "Failed allocating SD host structure");
goto failed;
}
for (i = 0; i < shp->sh_numslots; i++) {
if (sdhost_init_slot(dip, shp, i, bar + i) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed initializing slot %d", i);
goto failed;
}
}
ddi_report_dev(dip);
if (shp->sh_icap & DDI_INTR_FLAG_BLOCK) {
rv = ddi_intr_block_enable(&shp->sh_ihandle, 1);
} else {
rv = ddi_intr_enable(shp->sh_ihandle);
}
if (rv != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed enabling interrupts");
goto failed;
}
if (sda_host_attach(shp->sh_host) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed attaching to SDA framework");
if (shp->sh_icap & DDI_INTR_FLAG_BLOCK) {
(void) ddi_intr_block_disable(&shp->sh_ihandle, 1);
} else {
(void) ddi_intr_disable(shp->sh_ihandle);
}
goto failed;
}
return (DDI_SUCCESS);
failed:
if (shp->sh_ihandle != NULL) {
(void) ddi_intr_remove_handler(shp->sh_ihandle);
(void) ddi_intr_free(shp->sh_ihandle);
}
for (i = 0; i < shp->sh_numslots; i++)
sdhost_uninit_slot(shp, i);
if (shp->sh_host != NULL)
sda_host_free(shp->sh_host);
kmem_free(shp, sizeof (*shp));
return (DDI_FAILURE);
}
int
sdhost_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
sdhost_t *shp;
int i;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (sdhost_suspend(dip));
default:
return (DDI_FAILURE);
}
shp = ddi_get_driver_private(dip);
sda_host_detach(shp->sh_host);
if (shp->sh_ihandle != NULL) {
if (shp->sh_icap & DDI_INTR_FLAG_BLOCK) {
(void) ddi_intr_block_disable(&shp->sh_ihandle, 1);
} else {
(void) ddi_intr_disable(shp->sh_ihandle);
}
(void) ddi_intr_remove_handler(shp->sh_ihandle);
(void) ddi_intr_free(shp->sh_ihandle);
}
for (i = 0; i < shp->sh_numslots; i++)
sdhost_uninit_slot(shp, i);
sda_host_free(shp->sh_host);
kmem_free(shp, sizeof (*shp));
return (DDI_SUCCESS);
}
int
sdhost_quiesce(dev_info_t *dip)
{
sdhost_t *shp;
sdslot_t *ss;
shp = ddi_get_driver_private(dip);
for (int i = 0; i < shp->sh_numslots; i++) {
ss = &shp->sh_slots[i];
if (ss->ss_acch == NULL)
continue;
(void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
}
return (DDI_SUCCESS);
}
int
sdhost_suspend(dev_info_t *dip)
{
sdhost_t *shp;
sdslot_t *ss;
int i;
shp = ddi_get_driver_private(dip);
sda_host_suspend(shp->sh_host);
for (i = 0; i < shp->sh_numslots; i++) {
ss = &shp->sh_slots[i];
mutex_enter(&ss->ss_lock);
ss->ss_suspended = B_TRUE;
sdhost_disable_interrupts(ss);
(void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
mutex_exit(&ss->ss_lock);
}
return (DDI_SUCCESS);
}
int
sdhost_resume(dev_info_t *dip)
{
sdhost_t *shp;
sdslot_t *ss;
int i;
shp = ddi_get_driver_private(dip);
for (i = 0; i < shp->sh_numslots; i++) {
ss = &shp->sh_slots[i];
mutex_enter(&ss->ss_lock);
ss->ss_suspended = B_FALSE;
(void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
sdhost_enable_interrupts(ss);
mutex_exit(&ss->ss_lock);
}
sda_host_resume(shp->sh_host);
return (DDI_SUCCESS);
}
sda_err_t
sdhost_set_clock(sdslot_t *ss, uint32_t hz)
{
uint16_t div;
uint32_t val;
uint32_t clk;
int count;
ss->ss_cardclk = 0;
PUT16(ss, REG_CLOCK_CONTROL, 0);
if (hz == 0) {
return (SDA_EOK);
}
if (ss->ss_baseclk == 0) {
sda_host_log(ss->ss_host, ss->ss_num,
"Base clock frequency not established.");
return (SDA_EINVAL);
}
if ((hz > 25000000) && ((ss->ss_capab & CAPAB_HIGH_SPEED) != 0)) {
SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
} else {
hz = min(hz, 25000000);
CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
}
clk = ss->ss_baseclk;
div = 1;
while (clk > hz) {
if (div > 0x80)
break;
clk >>= 1;
div <<= 1;
}
div >>= 1;
PUT16(ss, REG_CLOCK_CONTROL,
(div << CLOCK_CONTROL_FREQ_SHIFT) | CLOCK_CONTROL_INT_CLOCK_EN);
for (count = 100000; count; count -= 10) {
val = GET16(ss, REG_CLOCK_CONTROL);
if (val & CLOCK_CONTROL_INT_CLOCK_STABLE) {
PUT16(ss, REG_CLOCK_CONTROL, val |
CLOCK_CONTROL_SD_CLOCK_EN);
ss->ss_cardclk = clk;
return (SDA_EOK);
}
drv_usecwait(10);
}
return (SDA_ETIME);
}
sda_err_t
sdhost_soft_reset(sdslot_t *ss, uint8_t bits)
{
int count;
if ((bits == SOFT_RESET_ALL) || !(CHECK_STATE(ss, CARD_INSERTED))) {
PUT16(ss, REG_CLOCK_CONTROL, CLOCK_CONTROL_INT_CLOCK_EN);
drv_usecwait(1000);
ss->ss_cardclk = 0;
ss->ss_width = 1;
}
PUT8(ss, REG_SOFT_RESET, bits);
for (count = 100000; count != 0; count -= 10) {
if ((GET8(ss, REG_SOFT_RESET) & bits) == 0) {
return (SDA_EOK);
}
drv_usecwait(10);
}
return (SDA_ETIME);
}
void
sdhost_disable_interrupts(sdslot_t *ss)
{
PUT16(ss, REG_INT_MASK, 0);
PUT16(ss, REG_INT_EN, 0);
PUT16(ss, REG_ERR_MASK, 0);
PUT16(ss, REG_ERR_EN, 0);
}
void
sdhost_enable_interrupts(sdslot_t *ss)
{
PUT16(ss, REG_INT_MASK, INT_MASK);
PUT16(ss, REG_INT_EN, INT_ENAB);
PUT16(ss, REG_ERR_MASK, ERR_MASK);
PUT16(ss, REG_ERR_EN, ERR_ENAB);
}
int
sdhost_setup_intr(dev_info_t *dip, sdhost_t *shp)
{
int itypes;
int itype;
if (ddi_intr_get_supported_types(dip, &itypes) != DDI_SUCCESS) {
cmn_err(CE_WARN, "ddi_intr_get_supported_types failed");
return (DDI_FAILURE);
}
if (itypes & DDI_INTR_TYPE_FIXED) {
if (!PROPSET(SDHOST_PROP_ENABLE_MSI)) {
itypes &= ~DDI_INTR_TYPE_MSI;
}
if (!PROPSET(SDHOST_PROP_ENABLE_MSIX)) {
itypes &= ~DDI_INTR_TYPE_MSIX;
}
}
for (itype = DDI_INTR_TYPE_MSIX; itype != 0; itype >>= 1) {
int count;
if ((itypes & itype) == 0) {
continue;
}
if ((ddi_intr_get_nintrs(dip, itype, &count) != DDI_SUCCESS) ||
(count == 0)) {
cmn_err(CE_WARN, "ddi_intr_get_nintrs failed");
continue;
}
if (count > 1) {
cmn_err(CE_WARN, "Controller offers %d interrupts, "
"but driver only supports one", count);
continue;
}
if ((ddi_intr_alloc(dip, &shp->sh_ihandle, itype, 0, 1,
&count, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) ||
(count != 1)) {
cmn_err(CE_WARN, "ddi_intr_alloc failed");
continue;
}
if (ddi_intr_get_pri(shp->sh_ihandle, &shp->sh_ipri) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "ddi_intr_get_pri failed");
(void) ddi_intr_free(shp->sh_ihandle);
shp->sh_ihandle = NULL;
continue;
}
if (shp->sh_ipri >= ddi_intr_get_hilevel_pri()) {
cmn_err(CE_WARN, "Hi level interrupt not supported");
(void) ddi_intr_free(shp->sh_ihandle);
shp->sh_ihandle = NULL;
continue;
}
if (ddi_intr_get_cap(shp->sh_ihandle, &shp->sh_icap) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "ddi_intr_get_cap failed");
(void) ddi_intr_free(shp->sh_ihandle);
shp->sh_ihandle = NULL;
continue;
}
if (ddi_intr_add_handler(shp->sh_ihandle, sdhost_intr,
shp, NULL) != DDI_SUCCESS) {
cmn_err(CE_WARN, "ddi_intr_add_handler failed");
(void) ddi_intr_free(shp->sh_ihandle);
shp->sh_ihandle = NULL;
continue;
}
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
void
sdhost_xfer_done(sdslot_t *ss, sda_err_t errno)
{
if ((errno == SDA_EOK) && (ss->ss_resid != 0)) {
errno = SDA_ERESID;
}
ss->ss_blksz = 0;
ss->ss_resid = 0;
if (errno != SDA_EOK) {
(void) sdhost_soft_reset(ss, SOFT_RESET_CMD);
(void) sdhost_soft_reset(ss, SOFT_RESET_DAT);
if (ss->ss_mode & XFR_MODE_AUTO_CMD12) {
PUT32(ss, REG_ARGUMENT, 0);
PUT16(ss, REG_COMMAND,
(CMD_STOP_TRANSMIT << 8) |
COMMAND_TYPE_NORM | COMMAND_INDEX_CHECK_EN |
COMMAND_CRC_CHECK_EN | COMMAND_RESP_48_BUSY);
}
}
sda_host_transfer(ss->ss_host, ss->ss_num, errno);
}
uint_t
sdhost_slot_intr(sdslot_t *ss)
{
uint16_t intr;
uint16_t errs;
caddr_t data;
int count;
mutex_enter(&ss->ss_lock);
if (ss->ss_suspended) {
mutex_exit(&ss->ss_lock);
return (DDI_INTR_UNCLAIMED);
}
intr = GET16(ss, REG_INT_STAT);
if (intr == 0) {
mutex_exit(&ss->ss_lock);
return (DDI_INTR_UNCLAIMED);
}
errs = GET16(ss, REG_ERR_STAT);
if (intr & (INT_REM | INT_INS)) {
PUT16(ss, REG_INT_STAT, intr);
mutex_exit(&ss->ss_lock);
sda_host_detect(ss->ss_host, ss->ss_num);
return (DDI_INTR_CLAIMED);
}
if (intr & INT_DMA) {
PUT16(ss, REG_INT_STAT, INT_DMA);
}
if (intr & INT_RD) {
PUT16(ss, REG_INT_STAT, INT_RD);
while ((ss->ss_resid > 0) && CHECK_STATE(ss, BUF_RD_EN)) {
data = ss->ss_bounce;
count = ss->ss_blksz;
ASSERT(count > 0);
ASSERT(ss->ss_kvaddr != NULL);
while (count >= sizeof (uint32_t)) {
*(uint32_t *)(void *)data = GETDATA32(ss);
data += sizeof (uint32_t);
count -= sizeof (uint32_t);
}
while (count >= sizeof (uint16_t)) {
*(uint16_t *)(void *)data = GETDATA16(ss);
data += sizeof (uint16_t);
count -= sizeof (uint16_t);
}
while (count >= sizeof (uint8_t)) {
*(uint8_t *)data = GETDATA8(ss);
data += sizeof (uint8_t);
count -= sizeof (uint8_t);
}
bcopy(ss->ss_bounce, ss->ss_kvaddr, ss->ss_blksz);
ss->ss_kvaddr += ss->ss_blksz;
ss->ss_resid--;
}
}
if (intr & INT_WR) {
PUT16(ss, REG_INT_STAT, INT_WR);
while ((ss->ss_resid > 0) && CHECK_STATE(ss, BUF_WR_EN)) {
data = ss->ss_bounce;
count = ss->ss_blksz;
ASSERT(count > 0);
ASSERT(ss->ss_kvaddr != NULL);
bcopy(ss->ss_kvaddr, data, count);
while (count >= sizeof (uint32_t)) {
PUTDATA32(ss, *(uint32_t *)(void *)data);
data += sizeof (uint32_t);
count -= sizeof (uint32_t);
}
while (count >= sizeof (uint16_t)) {
PUTDATA16(ss, *(uint16_t *)(void *)data);
data += sizeof (uint16_t);
count -= sizeof (uint16_t);
}
while (count >= sizeof (uint8_t)) {
PUTDATA8(ss, *(uint8_t *)data);
data += sizeof (uint8_t);
count -= sizeof (uint8_t);
}
ss->ss_kvaddr += ss->ss_blksz;
ss->ss_resid--;
}
}
if (intr & INT_XFR) {
if ((ss->ss_mode & (XFR_MODE_READ | XFR_MODE_DMA_EN)) ==
(XFR_MODE_READ | XFR_MODE_DMA_EN)) {
(void) ddi_dma_sync(ss->ss_bufdmah, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
bcopy(ss->ss_bounce, ss->ss_kvaddr, ss->ss_rcnt);
ss->ss_rcnt = 0;
}
PUT16(ss, REG_INT_STAT, INT_XFR);
sdhost_xfer_done(ss, SDA_EOK);
}
if (intr & INT_ERR) {
PUT16(ss, REG_ERR_STAT, errs);
PUT16(ss, REG_INT_STAT, INT_ERR);
if (errs & ERR_DAT) {
if ((errs & ERR_DAT_END) == ERR_DAT_END) {
sdhost_xfer_done(ss, SDA_EPROTO);
} else if ((errs & ERR_DAT_CRC) == ERR_DAT_CRC) {
sdhost_xfer_done(ss, SDA_ECRC7);
} else {
sdhost_xfer_done(ss, SDA_ETIME);
}
} else if (errs & ERR_ACMD12) {
sdhost_xfer_done(ss, SDA_ECMD12);
}
if (errs & ERR_CURRENT) {
sda_host_fault(ss->ss_host, ss->ss_num,
SDA_FAULT_CURRENT);
}
}
mutex_exit(&ss->ss_lock);
return (DDI_INTR_CLAIMED);
}
uint_t
sdhost_intr(caddr_t arg1, caddr_t arg2)
{
sdhost_t *shp = (void *)arg1;
int rv = DDI_INTR_UNCLAIMED;
int num;
for (num = 0; num < shp->sh_numslots; num++) {
if (sdhost_slot_intr(&shp->sh_slots[num]) ==
DDI_INTR_CLAIMED) {
rv = DDI_INTR_CLAIMED;
}
}
return (rv);
}
int
sdhost_init_slot(dev_info_t *dip, sdhost_t *shp, int num, int bar)
{
sdslot_t *ss;
uint32_t capab;
uint32_t clk;
char ksname[16];
size_t blen;
unsigned ndmac;
int rv;
ss = &shp->sh_slots[num];
ss->ss_host = shp->sh_host;
ss->ss_num = num;
sda_host_set_private(shp->sh_host, num, ss);
mutex_init(&ss->ss_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(shp->sh_ipri));
rv = ddi_dma_alloc_handle(dip, &shp->sh_dmaattr,
DDI_DMA_SLEEP, NULL, &ss->ss_bufdmah);
if (rv != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed to alloc dma handle (%d)!", rv);
return (DDI_FAILURE);
}
rv = ddi_dma_mem_alloc(ss->ss_bufdmah, SDHOST_BOUNCESZ,
&sdhost_bufattr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&ss->ss_bounce, &blen, &ss->ss_bufacch);
if (rv != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed to alloc bounce buffer (%d)!", rv);
return (DDI_FAILURE);
}
rv = ddi_dma_addr_bind_handle(ss->ss_bufdmah, NULL, ss->ss_bounce,
blen, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&ss->ss_bufdmac, &ndmac);
if ((rv != DDI_DMA_MAPPED) || (ndmac != 1)) {
cmn_err(CE_WARN, "Failed to bind DMA bounce buffer (%d, %u)!",
rv, ndmac);
return (DDI_FAILURE);
}
(void) snprintf(ksname, sizeof (ksname), "slot%d", num);
ss->ss_ksp = kstat_create(ddi_driver_name(dip), ddi_get_instance(dip),
ksname, "misc", KSTAT_TYPE_NAMED,
sizeof (sdstats_t) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL);
if (ss->ss_ksp != NULL) {
sdstats_t *sp = &ss->ss_stats;
ss->ss_ksp->ks_data = sp;
ss->ss_ksp->ks_private = ss;
ss->ss_ksp->ks_lock = &ss->ss_lock;
kstat_named_init(&sp->ks_ncmd, "ncmd", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_ixfr, "ixfr", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_oxfr, "oxfr", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_ibytes, "ibytes", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_obytes, "obytes", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_npio, "npio", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_ndma, "ndma", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_nmulti, "nmulti", KSTAT_DATA_UINT64);
kstat_named_init(&sp->ks_baseclk, "baseclk", KSTAT_DATA_UINT32);
kstat_named_init(&sp->ks_cardclk, "cardclk", KSTAT_DATA_UINT32);
kstat_named_init(&sp->ks_tmusecs, "tmusecs", KSTAT_DATA_UINT32);
kstat_named_init(&sp->ks_width, "width", KSTAT_DATA_UINT32);
kstat_named_init(&sp->ks_flags, "flags", KSTAT_DATA_UINT32);
kstat_named_init(&sp->ks_capab, "capab", KSTAT_DATA_UINT32);
kstat_install(ss->ss_ksp);
}
if (PROPSET(SDHOST_PROP_FORCE_PIO)) {
ss->ss_flags |= SDFLAG_FORCE_PIO;
}
if (PROPSET(SDHOST_PROP_FORCE_DMA)) {
ss->ss_flags |= SDFLAG_FORCE_DMA;
}
if (ddi_regs_map_setup(dip, bar, &ss->ss_regva, 0, 0, &sdhost_regattr,
&ss->ss_acch) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Failed to map registers!");
return (DDI_FAILURE);
}
if (sdhost_soft_reset(ss, SOFT_RESET_ALL) != SDA_EOK)
return (DDI_FAILURE);
capab = GET64(ss, REG_CAPAB) & 0xffffffffU;
ss->ss_capab = capab;
ss->ss_ocr = 0;
if (capab & CAPAB_18V)
ss->ss_ocr |= OCR_18_19V;
if (capab & CAPAB_30V)
ss->ss_ocr |= OCR_30_31V;
if (capab & CAPAB_33V)
ss->ss_ocr |= OCR_32_33V;
ss->ss_baseclk =
((capab & CAPAB_BASE_FREQ_MASK) >> CAPAB_BASE_FREQ_SHIFT);
ss->ss_baseclk *= 1000000;
clk = ((capab & CAPAB_TIMEOUT_FREQ_MASK) >> CAPAB_TIMEOUT_FREQ_SHIFT);
if ((ss->ss_baseclk == 0) || (clk == 0)) {
cmn_err(CE_WARN, "Unable to determine clock frequencies");
return (DDI_FAILURE);
}
if (capab & CAPAB_TIMEOUT_UNITS) {
ss->ss_tmusecs = (1 << 13) / clk;
clk *= 1000000;
} else {
ss->ss_tmusecs = (1000 * (1 << 13)) / clk;
clk *= 1000;
}
for (ss->ss_tmoutclk = 0; ss->ss_tmoutclk < 14; ss->ss_tmoutclk++) {
if ((ss->ss_tmusecs * (1 << ss->ss_tmoutclk)) >= 4000000) {
break;
}
}
sdhost_enable_interrupts(ss);
return (DDI_SUCCESS);
}
void
sdhost_uninit_slot(sdhost_t *shp, int num)
{
sdslot_t *ss;
ss = &shp->sh_slots[num];
if (ss->ss_acch != NULL)
(void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
if (ss->ss_bufdmac.dmac_address)
(void) ddi_dma_unbind_handle(ss->ss_bufdmah);
if (ss->ss_bufacch != NULL)
ddi_dma_mem_free(&ss->ss_bufacch);
if (ss->ss_bufdmah != NULL)
ddi_dma_free_handle(&ss->ss_bufdmah);
if (ss->ss_ksp != NULL)
kstat_delete(ss->ss_ksp);
if (ss->ss_acch != NULL)
ddi_regs_map_free(&ss->ss_acch);
if (ss->ss_num != -1)
mutex_destroy(&ss->ss_lock);
}
void
sdhost_get_response(sdslot_t *ss, sda_cmd_t *cmdp)
{
uint32_t *resp = cmdp->sc_response;
int i;
resp[0] = GET32(ss, REG_RESP1);
resp[1] = GET32(ss, REG_RESP2);
resp[2] = GET32(ss, REG_RESP3);
resp[3] = GET32(ss, REG_RESP4);
if (cmdp->sc_rtype == R2) {
for (i = 3; i > 0; i--) {
resp[i] <<= 8;
resp[i] |= (resp[i - 1] >> 24);
}
resp[0] <<= 8;
}
}
sda_err_t
sdhost_wait_cmd(sdslot_t *ss, sda_cmd_t *cmdp)
{
int i;
uint16_t errs;
sda_err_t rv;
for (i = 3000; i > 0; i -= 5) {
if (GET16(ss, REG_INT_STAT) & INT_CMD) {
PUT16(ss, REG_INT_STAT, INT_CMD);
sdhost_get_response(ss, cmdp);
return (SDA_EOK);
}
if ((errs = (GET16(ss, REG_ERR_STAT) & ERR_CMD)) != 0) {
PUT16(ss, REG_ERR_STAT, errs);
if ((errs & ERR_CMD_TMO) == ERR_CMD_TMO) {
rv = SDA_ETIME;
} else if ((errs & ERR_CMD_CRC) == ERR_CMD_CRC) {
rv = SDA_ECRC7;
} else {
rv = SDA_EPROTO;
}
goto error;
}
drv_usecwait(5);
}
rv = SDA_ETIME;
error:
(void) sdhost_soft_reset(ss, SOFT_RESET_CMD);
(void) sdhost_soft_reset(ss, SOFT_RESET_DAT);
return (rv);
}
sda_err_t
sdhost_poll(void *arg)
{
sdslot_t *ss = arg;
(void) sdhost_slot_intr(ss);
return (SDA_EOK);
}
sda_err_t
sdhost_cmd(void *arg, sda_cmd_t *cmdp)
{
sdslot_t *ss = arg;
uint16_t command;
uint16_t mode;
sda_err_t rv;
command = ((uint16_t)cmdp->sc_index << 8);
command |= COMMAND_TYPE_NORM |
COMMAND_INDEX_CHECK_EN | COMMAND_CRC_CHECK_EN;
switch (cmdp->sc_rtype) {
case R0:
command |= COMMAND_RESP_NONE;
break;
case R1:
case R5:
case R6:
case R7:
command |= COMMAND_RESP_48;
break;
case R1b:
case R5b:
command |= COMMAND_RESP_48_BUSY;
break;
case R2:
command |= COMMAND_RESP_136;
command &= ~(COMMAND_INDEX_CHECK_EN | COMMAND_CRC_CHECK_EN);
break;
case R3:
case R4:
command |= COMMAND_RESP_48;
command &= ~COMMAND_CRC_CHECK_EN;
command &= ~COMMAND_INDEX_CHECK_EN;
break;
default:
return (SDA_EINVAL);
}
mutex_enter(&ss->ss_lock);
if (ss->ss_suspended) {
mutex_exit(&ss->ss_lock);
return (SDA_ESUSPENDED);
}
if (cmdp->sc_nblks != 0) {
uint16_t blksz;
uint16_t nblks;
blksz = cmdp->sc_blksz;
nblks = cmdp->sc_nblks;
if ((blksz < 1) || (blksz > 2048)) {
mutex_exit(&ss->ss_lock);
return (SDA_EINVAL);
}
command |= COMMAND_DATA_PRESENT;
ss->ss_blksz = blksz;
ss->ss_kvaddr = (void *)cmdp->sc_kvaddr;
ss->ss_rcnt = 0;
ss->ss_resid = 0;
if (((ss->ss_capab & CAPAB_SDMA) != 0) &&
((ss->ss_flags & SDFLAG_FORCE_PIO) == 0) &&
((blksz * nblks) <= SDHOST_BOUNCESZ)) {
if (cmdp->sc_flags & SDA_CMDF_WRITE) {
bcopy(cmdp->sc_kvaddr, ss->ss_bounce,
nblks * blksz);
(void) ddi_dma_sync(ss->ss_bufdmah, 0, 0,
DDI_DMA_SYNC_FORDEV);
} else {
ss->ss_rcnt = nblks * blksz;
}
PUT32(ss, REG_SDMA_ADDR, ss->ss_bufdmac.dmac_address);
mode = XFR_MODE_DMA_EN;
PUT16(ss, REG_BLKSZ, BLKSZ_BOUNDARY_512K | blksz);
ss->ss_ndma++;
} else {
mode = 0;
ss->ss_npio++;
ss->ss_resid = nblks;
PUT16(ss, REG_BLKSZ, blksz);
}
if (nblks > 1) {
mode |= XFR_MODE_MULTI | XFR_MODE_COUNT;
if (cmdp->sc_flags & SDA_CMDF_AUTO_CMD12)
mode |= XFR_MODE_AUTO_CMD12;
ss->ss_nmulti++;
}
if ((cmdp->sc_flags & SDA_CMDF_READ) != 0) {
mode |= XFR_MODE_READ;
ss->ss_ixfr++;
ss->ss_ibytes += nblks * blksz;
} else {
ss->ss_oxfr++;
ss->ss_obytes += nblks * blksz;
}
ss->ss_mode = mode;
PUT8(ss, REG_TIMEOUT_CONTROL, ss->ss_tmoutclk);
PUT16(ss, REG_BLOCK_COUNT, nblks);
PUT16(ss, REG_XFR_MODE, mode);
}
PUT32(ss, REG_ARGUMENT, cmdp->sc_argument);
PUT16(ss, REG_COMMAND, command);
ss->ss_ncmd++;
rv = sdhost_wait_cmd(ss, cmdp);
mutex_exit(&ss->ss_lock);
return (rv);
}
sda_err_t
sdhost_getprop(void *arg, sda_prop_t prop, uint32_t *val)
{
sdslot_t *ss = arg;
sda_err_t rv = 0;
mutex_enter(&ss->ss_lock);
if (ss->ss_suspended) {
mutex_exit(&ss->ss_lock);
return (SDA_ESUSPENDED);
}
switch (prop) {
case SDA_PROP_INSERTED:
if (CHECK_STATE(ss, CARD_INSERTED)) {
*val = B_TRUE;
} else {
*val = B_FALSE;
}
break;
case SDA_PROP_WPROTECT:
if (CHECK_STATE(ss, WRITE_ENABLE)) {
*val = B_FALSE;
} else {
*val = B_TRUE;
}
break;
case SDA_PROP_OCR:
*val = ss->ss_ocr;
break;
case SDA_PROP_CLOCK:
*val = ss->ss_cardclk;
break;
case SDA_PROP_CAP_HISPEED:
if ((ss->ss_capab & CAPAB_HIGH_SPEED) != 0) {
*val = B_TRUE;
} else {
*val = B_FALSE;
}
break;
case SDA_PROP_CAP_4BITS:
*val = B_TRUE;
break;
case SDA_PROP_CAP_NOPIO:
*val = B_FALSE;
break;
case SDA_PROP_CAP_INTR:
case SDA_PROP_CAP_8BITS:
*val = B_FALSE;
break;
default:
rv = SDA_ENOTSUP;
break;
}
mutex_exit(&ss->ss_lock);
return (rv);
}
sda_err_t
sdhost_setprop(void *arg, sda_prop_t prop, uint32_t val)
{
sdslot_t *ss = arg;
sda_err_t rv = SDA_EOK;
mutex_enter(&ss->ss_lock);
if (ss->ss_suspended) {
mutex_exit(&ss->ss_lock);
return (SDA_ESUSPENDED);
}
switch (prop) {
case SDA_PROP_LED:
if (val) {
SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_LED_ON);
} else {
CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_LED_ON);
}
break;
case SDA_PROP_CLOCK:
rv = sdhost_set_clock(arg, val);
break;
case SDA_PROP_BUSWIDTH:
switch (val) {
case 1:
ss->ss_width = val;
CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_DATA_WIDTH);
break;
case 4:
ss->ss_width = val;
SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_DATA_WIDTH);
break;
default:
rv = SDA_EINVAL;
}
break;
case SDA_PROP_OCR:
val &= ss->ss_ocr;
if (val & OCR_17_18V) {
PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_18V);
PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_18V |
POWER_CONTROL_BUS_POWER);
} else if (val & OCR_29_30V) {
PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_30V);
PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_30V |
POWER_CONTROL_BUS_POWER);
} else if (val & OCR_32_33V) {
PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_33V);
PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_33V |
POWER_CONTROL_BUS_POWER);
} else if (val == 0) {
PUT8(ss, REG_POWER_CONTROL, 0);
} else {
rv = SDA_EINVAL;
}
break;
case SDA_PROP_HISPEED:
if (val) {
SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
} else {
CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
}
drv_usecwait(10);
break;
default:
rv = SDA_ENOTSUP;
break;
}
if (rv == SDA_EOK) {
(void) sdhost_soft_reset(ss, SOFT_RESET_CMD);
(void) sdhost_soft_reset(ss, SOFT_RESET_DAT);
}
mutex_exit(&ss->ss_lock);
return (rv);
}
sda_err_t
sdhost_reset(void *arg)
{
sdslot_t *ss = arg;
mutex_enter(&ss->ss_lock);
if (!ss->ss_suspended) {
if (sdhost_soft_reset(ss, SOFT_RESET_ALL) != SDA_EOK) {
mutex_exit(&ss->ss_lock);
return (SDA_ETIME);
}
sdhost_enable_interrupts(ss);
}
mutex_exit(&ss->ss_lock);
return (SDA_EOK);
}
sda_err_t
sdhost_halt(void *arg)
{
sdslot_t *ss = arg;
mutex_enter(&ss->ss_lock);
if (!ss->ss_suspended) {
sdhost_disable_interrupts(ss);
if (sdhost_soft_reset(ss, SOFT_RESET_ALL) != SDA_EOK) {
mutex_exit(&ss->ss_lock);
return (SDA_ETIME);
}
}
mutex_exit(&ss->ss_lock);
return (SDA_EOK);
}