wbsd.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include "wbsd.h"
/*
* Soft state.
*/
typedef struct wbsd {
} wbsd_t;
static int wbsd_attach(dev_info_t *);
static int wbsd_detach(dev_info_t *);
static int wbsd_resume(dev_info_t *);
static int wbsd_suspend(dev_info_t *);
static sda_err_t wbsd_reset(void *);
static int wbsd_setup_interrupts(wbsd_t *);
static void wbsd_teardown_interrupts(wbsd_t *);
static void wbsd_fifo_read(wbsd_t *);
static void wbsd_fifo_write(wbsd_t *);
static void wbsd_reset_hw(wbsd_t *);
static void wbsd_halt_hw(wbsd_t *);
static void wbsd_send_stop(wbsd_t *);
static void wbsd_busy_end(wbsd_t *);
static void wbsd_prog_end(wbsd_t *);
static void wbsd_detect(wbsd_t *);
static struct dev_ops wbsd_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
ddi_no_info, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
wbsd_ddi_attach, /* devo_attach */
wbsd_ddi_detach, /* devo_detach */
nodev, /* devo_reset */
NULL, /* devo_cb_ops */
NULL, /* devo_bus_ops */
NULL, /* devo_power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
static struct modldrv wbsd_modldrv = {
&mod_driverops, /* drv_modops */
"Winbond W83L519D SD Host", /* drv_linkinfo */
&wbsd_dev_ops /* drv_dev_ops */
};
static struct modlinkage modlinkage = {
MODREV_1, /* ml_rev */
};
static struct sda_ops wbsd_sda_ops = {
wbsd_cmd, /* so_cmd */
wbsd_getprop, /* so_getprop */
wbsd_setprop, /* so_setprop */
wbsd_poll, /* so_poll */
wbsd_reset, /* so_reset */
wbsd_halt, /* so_halt */
};
static ddi_device_acc_attr_t wbsd_regattr = {
DDI_DEVICE_ATTR_V0, /* devacc_attr_version */
DDI_NEVERSWAP_ACC, /* devacc_attr_endian_flags */
DDI_STRICTORDER_ACC, /* devacc_attr_dataorder */
DDI_DEFAULT_ACC, /* devacc_attr_access */
};
int
_init(void)
{
int rv;
}
return (rv);
}
int
_fini(void)
{
int rv;
}
return (rv);
}
int
{
}
int
{
switch (cmd) {
case DDI_ATTACH:
return (wbsd_attach(dip));
case DDI_RESUME:
return (wbsd_resume(dip));
default:
return (DDI_FAILURE);
}
}
int
{
switch (cmd) {
case DDI_DETACH:
return (wbsd_detach(dip));
case DDI_SUSPEND:
return (wbsd_suspend(dip));
default:
return (DDI_FAILURE);
}
}
int
{
goto failed;
}
goto failed;
}
/* make sure interrupts are disabled */
/* setup interrupts, also initializes locks */
goto failed;
/* enable device interrupts in DDI */
/* attach to the framework */
goto failed;
}
return (DDI_SUCCESS);
/* tear down interrupts */
}
/* toss register map */
}
/* free host resources */
}
return (DDI_FAILURE);
}
int
{
return (DDI_FAILURE);
}
/* disable interrupts */
/* remove power from the socket */
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
int actual;
/*
* Setup interrupt. Note that these are ISA devices, and only have
* a single fixed interrupt.
*/
return (DDI_FAILURE);
}
(void) ddi_intr_free(ih);
return (DDI_FAILURE);
}
DDI_SUCCESS) {
(void) ddi_intr_free(ih);
return (DDI_FAILURE);
}
/*
* Soft interrupt is next.
*/
(void) ddi_intr_remove_handler(ih);
(void) ddi_intr_free(ih);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
void
{
/*
* These are here to ensure that any previously
* running interrupts (hard or soft) have completed.
*/
}
void
{
}
void
{
}
void
{
}
void
{
/*
* Start transferring the next block.
*/
} else {
/*
* If we needed auto terminate, then do it now.
*/
/*
* Otherwise its a single block write completion, so
* just complete the transfer.
*/
} else {
}
}
}
}
{
int i;
if (wp->w_suspended) {
return (rv);
}
for (i = 0; i < 100000; i++) {
break;
/*
* Make sure that the chip is fully reset after
* a card interrupt occurs.
*/
break;
}
/*
* FIFO data ready. Process this as quickly as
* possible.
*/
} else {
}
}
if (isr & ISR_BUSY_END) {
}
if (isr & ISR_PROG_END) {
}
if (isr & ISR_TIMEOUT) {
}
if (isr & ISR_CRC_ERR) {
}
}
if (i >= 100000) {
"Stuck interrupt detected (isr %x)", isr);
}
/*
* If arg2 is NULL, then we are running as an ordinary interrupt.
* Otherwise we are running from polled context, and cannot trigger
* soft interrupts.
*/
}
if (do_soft)
return (rv);
}
/*ARGSUSED1*/
{
if (detect) {
}
if (done) {
}
return (DDI_INTR_CLAIMED);
}
{
/* 2nd argument indicates running from poll */
return (SDA_EOK);
}
{
if (wp->w_suspended) {
return (SDA_ESUSPENDED);
}
switch (prop) {
case SDA_PROP_INSERTED:
break;
case SDA_PROP_WPROTECT:
/* switch signal select */
drv_usecwait(1000);
break;
case SDA_PROP_OCR:
*val = OCR_32_33V;
break;
case SDA_PROP_CLOCK:
switch (clock) {
case IDX_CLOCK_24M:
*val = 24000000;
break;
case IDX_CLOCK_16M:
*val = 16000000;
break;
case IDX_CLOCK_12M:
*val = 12000000;
break;
case IDX_CLOCK_375K:
*val = 375000;
break;
default:
*val = 0;
break;
}
break;
case SDA_PROP_CAP_4BITS:
/*
* On Tadpole SPARCLE hardware, the card detect uses
* DAT3, which causes all kinds of problems. It is quite
* troublesome to support card detection events properly with
* this configuration, so we fall back to supporting only
* single bit mode. It is possible to correct this, but
* it requires changes in the framework, particularly to
* note that the DAT3 pin is used this way.
*
* In particular, this would require separate commands to
* the card to connect/disconnect the card internal pullup
* resistor, as well as to manipulate the interrupt register,
* and then poll card status on command completion.
*
* The Winbond part is so slow, that it is doubtful that the
* trouble would be worth it. On x86 hardware where we can
* make use of GPIO pin detection, the situation might be
* quite different.
*/
break;
case SDA_PROP_CAP_NOPIO:
case SDA_PROP_CAP_INTR:
case SDA_PROP_CAP_8BITS:
break;
default:
rv = SDA_ENOTSUP;
break;
}
return (rv);
}
{
if (wp->w_suspended) {
return (SDA_ESUSPENDED);
}
switch (prop) {
case SDA_PROP_LED:
break;
case SDA_PROP_CLOCK:
/*
* Note that the "worst case" command timeouts are 16.7us for
* the "slow" 12MHz clock. So a 20us timeout is enough for
* everything faster.
*/
if (val >= 24000000) {
} else if (val >= 16000000) {
} else if (val >= 12000000) {
} else {
/*
* Worst case command timeout is 533.3 usec. Just
* pick a big enough value to force it. If we choose
* a value of 2 msec, it is enough even if the clock
* runs as low as 100KHz.
*/
}
break;
case SDA_PROP_BUSWIDTH:
/*
* See the comment in SDA_PROP_CAP_4BITS, though.
*/
} else {
rv = SDA_EINVAL;
}
break;
case SDA_PROP_OCR:
if ((val == OCR_32_33V) &&
/* apply power */
/* activate the various other interrupts on the chip */
} else {
/* power down and reset */
if (val != 0) {
rv = SDA_EINVAL;
}
}
break;
default:
break;
}
return (rv);
}
void
{
/*
* The point of this logic is to avoid extra reads of
* the fifo status register. We are throughput
* limited by the number of PIOs.
*/
cnt = 16;
} else if ((fsr & FSR_FULL_THRE) != 0) {
cnt = 8;
} else {
cnt = 1;
}
cnt--;
}
}
} else {
} else {
}
}
}
void
{
cnt = 16;
} else if ((fsr & FSR_EMPTY_THRE) != 0) {
cnt = 8;
} else {
cnt = 1;
}
cnt--;
}
}
} else {
/* wait for PROG interrupt */
}
}
void
{
}
{
rwords = 1;
case R0:
rwords = 0;
break;
case R1:
case R5:
case R6:
case R7:
case R1b:
case R5b:
break;
case R2:
rstart = IDX_RESP_1;
rwords = 4;
break;
case R3:
case R4:
break;
}
if (wp->w_suspended) {
return (SDA_ESUSPENDED);
}
uint8_t v;
/* save a few things for completion */
/* maximum timeouts, 127 msec and 25500 cycles */
/* set data width */
/* make sure start the fifo with a clean slate */
v |= IDX_RESET_FIFO;
/* we don't use DMA, period */
/*
* Reading... we arrange to wait for the full
* transfer, than doing a block at a time.
* Simpler that way.
*/
} else {
/*
* Writing... go ahead and prefill the fifo.
* We write a block at a time, because we need
* the PROG interrupts in the block gaps.
*/
}
}
/*
* This chip is a bit simple minded. It cannot distinguish
* between errors that occur on the data line, and those that
* occur on the CMD line.
*/
/* make sure we clear any preexisting error condition */
/*
* Note that while we are waiting for the timer to run out (which
* is really short), a timeout or other error interrupt can occur.
* We want to know about such error indications, so we have to drop
* to the lock so that the interrupt service routine can post the
* appropriate error in the w_cmd_err variable.
*/
if ((stat & IDX_STATUS_TRAFFIC) != 0) {
}
}
/* some commands don't use valid CRC */
}
while (rwords != 0) {
uint32_t v;
v <<= 8;
v <<= 8;
v <<= 8;
rwords--;
}
return (rv);
}
void
{
/* reset chip and fifo */
/* disable interrupts */
/* remove power */
}
void
{
/* remove power from slot, set LED enable */
/* reset chip and fifo */
/* clear any pending interrupts */
/* enable card interrupt */
}
wbsd_reset(void *arg)
{
if (!wp->w_suspended) {
/* reset occurred when we suspended */
}
return (SDA_EOK);
}
{
if (!wp->w_suspended) {
}
return (SDA_EOK);
}