Omnibus power and interrupt fixes:

o Don't busy wait on powerup.  Instead, use the power up interrupt to wait
  for the card to power up.  Don't wait when we're turning the card off,
  since no interrupt happens in that case.
o Convert many of the long DELAYs to tsleeps.  We do not run before
  the timer have stared, so DELAY isn't necessary.  More DELAYs can likely
  be eliminated in the future.
o When powering up the card, don't do anything if the card is already
  powered up (before we'd power cycle it).  This means that for most
  cards we power them up once and then never change the power.
o On card eject, mask (by clearing) the CD bit.  Before we set it, which
  was wrong.  We don't want to see any CD events past the first one since
  they need to be debounced.

With these changes, I can insert/eject 16bit cards without glitching xmms'
sound output.  Something very important to the development of better pccard
drivers :-)
This commit is contained in:
Warner Losh 2005-07-14 20:46:59 +00:00
parent 2bb5d7f9ac
commit fd4e475233
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=148013
3 changed files with 77 additions and 49 deletions

View file

@ -157,7 +157,7 @@ SYSCTL_ULONG(_hw_cbb, OID_AUTO, debug, CTLFLAG_RW, &cbb_debug, 0,
static void cbb_insert(struct cbb_softc *sc);
static void cbb_removal(struct cbb_softc *sc);
static int cbb_detect_voltage(device_t brdev);
static uint32_t cbb_detect_voltage(device_t brdev);
static void cbb_cardbus_reset(device_t brdev);
static int cbb_cardbus_io_open(device_t brdev, int win, uint32_t start,
uint32_t end);
@ -318,6 +318,7 @@ cbb_detach(device_t brdev)
sc->base_res);
mtx_destroy(&sc->mtx);
cv_destroy(&sc->cv);
cv_destroy(&sc->powercv);
return (0);
}
@ -643,12 +644,22 @@ cbb_intr(void *arg)
*/
if (sockevent & CBB_SOCKET_EVENT_CD) {
mtx_lock(&sc->mtx);
cbb_setb(sc, CBB_SOCKET_MASK, CBB_SOCKET_MASK_CD);
cbb_clrb(sc, CBB_SOCKET_MASK, CBB_SOCKET_MASK_CD);
sc->flags &= ~CBB_CARD_OK;
cbb_disable_func_intr(sc);
cv_signal(&sc->cv);
mtx_unlock(&sc->mtx);
}
/*
* If we get a power interrupt, wakeup anybody that might
* be waiting for one.
*/
if (sockevent & CBB_SOCKET_EVENT_POWER) {
mtx_lock(&sc->mtx);
sc->powerintr++;
cv_signal(&sc->powercv);
mtx_unlock(&sc->mtx);
}
}
/*
* Some chips also require us to read the old ExCA registe for
@ -669,12 +680,12 @@ cbb_intr(void *arg)
/* Generic Power functions */
/************************************************************************/
static int
static uint32_t
cbb_detect_voltage(device_t brdev)
{
struct cbb_softc *sc = device_get_softc(brdev);
uint32_t psr;
int vol = CARD_UKN_CARD;
uint32_t vol = CARD_UKN_CARD;
psr = cbb_get(sc, CBB_SOCKET_STATE);
@ -740,27 +751,30 @@ cbb_power(device_t brdev, int volts)
{
uint32_t status, sock_ctrl;
struct cbb_softc *sc = device_get_softc(brdev);
int timeout;
int cnt;
int retval = 0;
uint32_t sockevent;
int on = 0;
uint8_t reg = 0;
status = cbb_get(sc, CBB_SOCKET_STATE);
sock_ctrl = cbb_get(sc, CBB_SOCKET_CONTROL);
sock_ctrl &= ~CBB_SOCKET_CTRL_VCCMASK;
switch (volts & CARD_VCCMASK) {
case 5:
sock_ctrl |= CBB_SOCKET_CTRL_VCC_5V;
on++;
break;
case 3:
sock_ctrl |= CBB_SOCKET_CTRL_VCC_3V;
on++;
break;
case XV:
sock_ctrl |= CBB_SOCKET_CTRL_VCC_XV;
on++;
break;
case YV:
sock_ctrl |= CBB_SOCKET_CTRL_VCC_YV;
on++;
break;
case 0:
break;
@ -778,41 +792,24 @@ cbb_power(device_t brdev, int volts)
if (volts != 0 && sc->chipset == CB_O2MICRO)
reg = cbb_o2micro_power_hack(sc);
cbb_setb(sc, CBB_SOCKET_MASK, CBB_SOCKET_MASK_POWER);
cbb_set(sc, CBB_SOCKET_CONTROL, sock_ctrl);
status = cbb_get(sc, CBB_SOCKET_STATE);
/*
* XXX This busy wait is bogus. We should wait for a power
* interrupt and then whine if the status is bad. If we're
* worried about the card not coming up, then we should also
* schedule a timeout which we can cancel in the power interrupt.
*/
timeout = 20;
do {
DELAY(20*1000);
sockevent = cbb_get(sc, CBB_SOCKET_EVENT);
} while (!(sockevent & CBB_SOCKET_EVENT_POWER) && --timeout > 0);
/* reset event status */
/* XXX should only reset EVENT_POWER */
cbb_set(sc, CBB_SOCKET_EVENT, sockevent);
if (timeout < 0) {
printf ("VCC supply failed.\n");
goto done;
if (on) {
mtx_lock(&sc->mtx);
cnt = sc->powerintr;
/* XXX timeout needed! */
while (cnt == sc->powerintr)
cv_wait(&sc->powercv, &sc->mtx);
mtx_unlock(&sc->mtx);
}
cbb_clrb(sc, CBB_SOCKET_MASK, CBB_SOCKET_MASK_POWER);
status = cbb_get(sc, CBB_SOCKET_STATE);
if (on) {
if ((status & CBB_STATE_POWER_CYCLE) == 0)
device_printf(sc->dev, "Power not on?\n");
}
/* XXX
* delay 400 ms: thgough the standard defines that the Vcc set-up time
* is 20 ms, some PC-Card bridge requires longer duration.
* XXX Note: We should check the stutus AFTER the delay to give time
* for things to stabilize.
*/
DELAY(400*1000);
if (status & CBB_STATE_BAD_VCC_REQ) {
device_printf(sc->dev,
"bad Vcc request. ctrl=0x%x, status=0x%x\n",
sock_ctrl ,status);
printf("cbb_power: %dV\n", volts);
device_printf(sc->dev, "Bad Vcc requested\n");
goto done;
}
retval = 1;
@ -822,25 +819,54 @@ done:;
return (retval);
}
static int
cbb_current_voltage(device_t brdev)
{
struct cbb_softc *sc = device_get_softc(brdev);
uint32_t ctrl;
ctrl = cbb_get(sc, CBB_SOCKET_CONTROL);
switch (ctrl & CBB_SOCKET_CTRL_VCCMASK) {
case CBB_SOCKET_CTRL_VCC_5V:
return CARD_5V_CARD;
case CBB_SOCKET_CTRL_VCC_3V:
return CARD_3V_CARD;
case CBB_SOCKET_CTRL_VCC_XV:
return CARD_XV_CARD;
case CBB_SOCKET_CTRL_VCC_YV:
return CARD_YV_CARD;
}
return 0;
}
/*
* detect the voltage for the card, and set it. Since the power
* used is the square of the voltage, lower voltages is a big win
* and what Windows does (and what Microsoft prefers). The MS paper
* also talks about preferring the CIS entry as well. In addition,
* we power up with OE disabled. We'll set it later in the power
* up sequence.
* also talks about preferring the CIS entry as well, but that has
* to be done elsewhere. We also optimize power sequencing here
* and don't change things if we're already powered up at a supported
* voltage.
*
* In addition, we power up with OE disabled. We'll set it later
* in the power up sequence.
*/
static int
cbb_do_power(device_t brdev)
{
struct cbb_softc *sc = device_get_softc(brdev);
int voltage;
uint32_t voltage, curpwr;
uint32_t status;
/* Don't enable OE */
/* Don't enable OE (output enable) until power stable */
exca_clrb(&sc->exca[0], EXCA_PWRCTL, EXCA_PWRCTL_OE);
/* Prefer lowest voltage supported */
voltage = cbb_detect_voltage(brdev);
curpwr = cbb_current_voltage(brdev);
status = cbb_get(sc, CBB_SOCKET_STATE);
if ((status & CBB_STATE_POWER_CYCLE) && (voltage & curpwr))
return 0;
/* Prefer lowest voltage supported */
cbb_power(brdev, CARD_OFF);
if (voltage & CARD_YV_CARD)
cbb_power(brdev, CARD_VCC(YV));
@ -1198,14 +1224,14 @@ cbb_pcic_power_disable_socket(device_t brdev, device_t child)
/* reset signal asserting... */
exca_clrb(&sc->exca[0], EXCA_INTR, EXCA_INTR_RESET);
DELAY(2*1000);
tsleep(sc, PZERO, "cbbP1", hz / 100);
/* power down the socket */
cbb_power(brdev, CARD_OFF);
exca_clrb(&sc->exca[0], EXCA_PWRCTL, EXCA_PWRCTL_OE);
cbb_power(brdev, CARD_OFF);
/* wait 300ms until power fails (Tpf). */
DELAY(300 * 1000);
tsleep(sc, PZERO, "cbbP1", hz * 300 / 1000);
}
/************************************************************************/

View file

@ -292,6 +292,7 @@ cbb_pci_attach(device_t brdev)
parent = device_get_parent(brdev);
mtx_init(&sc->mtx, device_get_nameunit(brdev), "cbb", MTX_DEF);
cv_init(&sc->cv, "cbb cv");
cv_init(&sc->powercv, "cbb cv");
sc->chipset = cbb_chipset(pci_get_devid(brdev), NULL);
sc->dev = brdev;
sc->cbdev = NULL;

View file

@ -66,6 +66,7 @@ struct cbb_softc {
u_int8_t subbus;
struct mtx mtx;
struct cv cv;
struct cv powercv;
u_int32_t flags;
#define CBB_CARD_OK 0x08000000
#define CBB_16BIT_CARD 0x20000000
@ -84,10 +85,10 @@ struct cbb_softc {
#define CB_O2MICRO 9 /* O2Micro chips */
SLIST_HEAD(, cbb_reslist) rl;
STAILQ_HEAD(, cbb_intrhand) intr_handlers;
device_t cbdev;
struct proc *event_thread;
void (*chipinit)(struct cbb_softc *);
volatile int powerintr;
};
/* result of detect_card */