NetBSD/sys/dev/pcmcia/pcmcia.c
mycroft 36c30a2f1a Dispense with all the interrupt multiplexing code here, and assume that the
underlying implementation DTRT.  This has the side effect of causing us to
ignore the INTR and INTRACK bits in the CCR -- but this seems for the best
anyway, since they are not reliably implemented.  (I note that Linux doesn't
bother either.)
2004-08-09 20:02:36 +00:00

723 lines
18 KiB
C

/* $NetBSD: pcmcia.c,v 1.52 2004/08/09 20:02:36 mycroft Exp $ */
/*
* Copyright (c) 2004 Charles M. Hannum. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Charles M. Hannum.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*/
/*
* Copyright (c) 1997 Marc Horowitz. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Marc Horowitz.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pcmcia.c,v 1.52 2004/08/09 20:02:36 mycroft Exp $");
#include "opt_pcmciaverbose.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <dev/pcmcia/pcmciareg.h>
#include <dev/pcmcia/pcmciachip.h>
#include <dev/pcmcia/pcmciavar.h>
#ifdef IT8368E_LEGACY_MODE /* XXX -uch */
#include <arch/hpcmips/dev/it8368var.h>
#endif
#include "locators.h"
#ifdef PCMCIADEBUG
int pcmcia_debug = 0;
#define DPRINTF(arg) if (pcmcia_debug) printf arg
#else
#define DPRINTF(arg)
#endif
#ifdef PCMCIAVERBOSE
int pcmcia_verbose = 1;
#else
int pcmcia_verbose = 0;
#endif
int pcmcia_match __P((struct device *, struct cfdata *, void *));
int pcmcia_submatch __P((struct device *, struct cfdata *, void *));
void pcmcia_attach __P((struct device *, struct device *, void *));
int pcmcia_print __P((void *, const char *));
CFATTACH_DECL(pcmcia, sizeof(struct pcmcia_softc),
pcmcia_match, pcmcia_attach, NULL, NULL);
int
pcmcia_ccr_read(pf, ccr)
struct pcmcia_function *pf;
int ccr;
{
return (bus_space_read_1(pf->pf_ccrt, pf->pf_ccrh,
pf->pf_ccr_offset + ccr * 2));
}
void
pcmcia_ccr_write(pf, ccr, val)
struct pcmcia_function *pf;
int ccr;
int val;
{
if (pf->ccr_mask & (1 << ccr)) {
bus_space_write_1(pf->pf_ccrt, pf->pf_ccrh,
pf->pf_ccr_offset + ccr * 2, val);
}
}
int
pcmcia_match(parent, match, aux)
struct device *parent;
struct cfdata *match;
void *aux;
{
struct pcmciabus_attach_args *paa = aux;
if (strcmp(paa->paa_busname, match->cf_name)) {
return 0;
}
/* if the autoconfiguration got this far, there's a socket here */
return (1);
}
void
pcmcia_attach(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct pcmciabus_attach_args *paa = aux;
struct pcmcia_softc *sc = (struct pcmcia_softc *) self;
printf("\n");
sc->pct = paa->pct;
sc->pch = paa->pch;
sc->iobase = paa->iobase;
sc->iosize = paa->iosize;
sc->ih = NULL;
}
int
pcmcia_card_attach(dev)
struct device *dev;
{
struct pcmcia_softc *sc = (struct pcmcia_softc *) dev;
struct pcmcia_function *pf;
struct pcmcia_attach_args paa;
int attached;
/*
* this is here so that when socket_enable calls gettype, trt happens
*/
SIMPLEQ_FIRST(&sc->card.pf_head) = NULL;
pcmcia_chip_socket_enable(sc->pct, sc->pch);
pcmcia_read_cis(sc);
pcmcia_chip_socket_disable(sc->pct, sc->pch);
pcmcia_check_cis_quirks(sc);
/*
* bail now if the card has no functions, or if there was an error in
* the cis.
*/
if (sc->card.error)
return (1);
if (SIMPLEQ_EMPTY(&sc->card.pf_head))
return (1);
if (pcmcia_verbose)
pcmcia_print_cis(sc);
attached = 0;
SIMPLEQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
#ifdef DIAGNOSTIC
if (pf->child != NULL) {
printf("%s: %s still attached to function %d!\n",
sc->dev.dv_xname, pf->child->dv_xname,
pf->number);
panic("pcmcia_card_attach");
}
#endif
pf->sc = sc;
pf->child = NULL;
pf->cfe = NULL;
pf->pf_ih = NULL;
}
SIMPLEQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
paa.manufacturer = sc->card.manufacturer;
paa.product = sc->card.product;
paa.card = &sc->card;
paa.pf = pf;
if ((pf->child = config_found_sm(&sc->dev, &paa, pcmcia_print,
pcmcia_submatch)) != NULL)
attached++;
}
return (attached ? 0 : 1);
}
void
pcmcia_card_detach(dev, flags)
struct device *dev;
int flags; /* DETACH_* flags */
{
struct pcmcia_softc *sc = (struct pcmcia_softc *) dev;
struct pcmcia_function *pf;
int error;
/*
* We are running on either the PCMCIA socket's event thread
* or in user context detaching a device by user request.
*/
SIMPLEQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
if (pf->child == NULL)
continue;
DPRINTF(("%s: detaching %s (function %d)\n",
sc->dev.dv_xname, pf->child->dv_xname, pf->number));
if ((error = config_detach(pf->child, flags)) != 0) {
printf("%s: error %d detaching %s (function %d)\n",
sc->dev.dv_xname, error, pf->child->dv_xname,
pf->number);
} else
pf->child = NULL;
}
}
void
pcmcia_card_deactivate(dev)
struct device *dev;
{
struct pcmcia_softc *sc = (struct pcmcia_softc *) dev;
struct pcmcia_function *pf;
/*
* We're in the chip's card removal interrupt handler.
* Deactivate the child driver. The PCMCIA socket's
* event thread will run later to finish the detach.
*/
SIMPLEQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
if (pf->child == NULL)
continue;
DPRINTF(("%s: deactivating %s (function %d)\n",
sc->dev.dv_xname, pf->child->dv_xname, pf->number));
config_deactivate(pf->child);
}
}
int
pcmcia_submatch(parent, cf, aux)
struct device *parent;
struct cfdata *cf;
void *aux;
{
struct pcmcia_attach_args *paa = aux;
if (cf->cf_loc[PCMCIACF_FUNCTION] != PCMCIACF_FUNCTION_DEFAULT &&
cf->cf_loc[PCMCIACF_FUNCTION] != paa->pf->number)
return (0);
return (config_match(parent, cf, aux));
}
int
pcmcia_print(arg, pnp)
void *arg;
const char *pnp;
{
struct pcmcia_attach_args *pa = arg;
struct pcmcia_softc *sc = pa->pf->sc;
struct pcmcia_card *card = &sc->card;
char devinfo[256];
if (pnp)
aprint_normal("%s", pnp);
pcmcia_devinfo(card, !!pnp, devinfo, sizeof(devinfo));
aprint_normal(" function %d: %s", pa->pf->number, devinfo);
return (UNCONF);
}
void
pcmcia_devinfo(card, showhex, cp, cplen)
struct pcmcia_card *card;
int showhex;
char *cp;
size_t cplen;
{
int i, n;
if (cplen > 1) {
*cp++ = '<';
*cp = '\0';
cplen--;
}
for (i = 0; i < 4 && card->cis1_info[i] != NULL && cplen > 1; i++) {
n = snprintf(cp, cplen, "%s%s", i ? ", " : "",
card->cis1_info[i]);
cp += n;
if (cplen < n)
return;
cplen -= n;
}
if (cplen > 1) {
*cp++ = '>';
*cp = '\0';
cplen--;
}
if (showhex && cplen > 1)
snprintf(cp, cplen, " (manufacturer 0x%04x, product 0x%04x)",
card->manufacturer, card->product);
}
const struct pcmcia_product *
pcmcia_product_lookup(pa, tab, ent_size, matchfn)
struct pcmcia_attach_args *pa;
const struct pcmcia_product *tab;
size_t ent_size;
pcmcia_product_match_fn matchfn;
{
const struct pcmcia_product *ent;
int matches;
#ifdef DIAGNOSTIC
if (sizeof *ent > ent_size)
panic("pcmcia_product_lookup: bogus ent_size %ld",
(long) ent_size);
#endif
for (ent = tab;
ent->pp_name != NULL;
ent = (const struct pcmcia_product *)
((const char *)ent + ent_size)) {
/* see if it matches vendor/product/function */
matches = (pa->manufacturer == ent->pp_vendor) &&
(pa->product == ent->pp_product) &&
(pa->pf->number == ent->pp_expfunc);
/* if a separate match function is given, let it override */
if (matchfn != NULL)
matches = (*matchfn)(pa, ent, matches);
if (matches)
return (ent);
}
return (NULL);
}
int
pcmcia_card_gettype(dev)
struct device *dev;
{
struct pcmcia_softc *sc = (struct pcmcia_softc *)dev;
struct pcmcia_function *pf;
/*
* set the iftype to memory if this card has no functions (not yet
* probed), or only one function, and that is not initialized yet or
* that is memory.
*/
pf = SIMPLEQ_FIRST(&sc->card.pf_head);
if (pf == NULL ||
(SIMPLEQ_NEXT(pf, pf_list) == NULL &&
(pf->cfe == NULL || pf->cfe->iftype == PCMCIA_IFTYPE_MEMORY)))
return (PCMCIA_IFTYPE_MEMORY);
else
return (PCMCIA_IFTYPE_IO);
}
/*
* Initialize a PCMCIA function. May be called as long as the function is
* disabled.
*/
void
pcmcia_function_init(pf, cfe)
struct pcmcia_function *pf;
struct pcmcia_config_entry *cfe;
{
if (pf->pf_flags & PFF_ENABLED)
panic("pcmcia_function_init: function is enabled");
/* Remember which configuration entry we are using. */
pf->cfe = cfe;
}
void pcmcia_socket_enable(dev)
struct device *dev;
{
struct pcmcia_softc *sc = (void *)dev;
pcmcia_chip_socket_enable(sc->pct, sc->pch);
}
void pcmcia_socket_disable(dev)
struct device *dev;
{
struct pcmcia_softc *sc = (void *)dev;
pcmcia_chip_socket_disable(sc->pct, sc->pch);
}
/* Enable a PCMCIA function */
int
pcmcia_function_enable(pf)
struct pcmcia_function *pf;
{
struct pcmcia_function *tmp;
int reg;
if (pf->cfe == NULL)
panic("pcmcia_function_enable: function not initialized");
/*
* Increase the reference count on the socket, enabling power, if
* necessary.
*/
if (pf->sc->sc_enabled_count++ == 0)
pcmcia_chip_socket_enable(pf->sc->pct, pf->sc->pch);
DPRINTF(("%s: ++enabled_count = %d\n", pf->sc->dev.dv_xname,
pf->sc->sc_enabled_count));
if (pf->pf_flags & PFF_ENABLED) {
/*
* Don't do anything if we're already enabled.
*/
return (0);
}
/*
* it's possible for different functions' CCRs to be in the same
* underlying page. Check for that.
*/
SIMPLEQ_FOREACH(tmp, &pf->sc->card.pf_head, pf_list) {
if ((tmp->pf_flags & PFF_ENABLED) &&
(pf->ccr_base >= (tmp->ccr_base - tmp->pf_ccr_offset)) &&
((pf->ccr_base + PCMCIA_CCR_SIZE) <=
(tmp->ccr_base - tmp->pf_ccr_offset +
tmp->pf_ccr_realsize))) {
pf->pf_ccrt = tmp->pf_ccrt;
pf->pf_ccrh = tmp->pf_ccrh;
pf->pf_ccr_realsize = tmp->pf_ccr_realsize;
/*
* pf->pf_ccr_offset = (tmp->pf_ccr_offset -
* tmp->ccr_base) + pf->ccr_base;
*/
pf->pf_ccr_offset =
(tmp->pf_ccr_offset + pf->ccr_base) -
tmp->ccr_base;
pf->pf_ccr_window = tmp->pf_ccr_window;
break;
}
}
if (tmp == NULL) {
if (pcmcia_mem_alloc(pf, PCMCIA_CCR_SIZE, &pf->pf_pcmh))
goto bad;
if (pcmcia_mem_map(pf, PCMCIA_MEM_ATTR, pf->ccr_base,
PCMCIA_CCR_SIZE, &pf->pf_pcmh, &pf->pf_ccr_offset,
&pf->pf_ccr_window)) {
pcmcia_mem_free(pf, &pf->pf_pcmh);
goto bad;
}
}
if (pcmcia_mfc(pf->sc)) {
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE0,
(pf->pf_mfc_iobase[0] >> 0) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE1,
(pf->pf_mfc_iobase[0] >> 8) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE2,
(pf->pf_mfc_iobase[1] >> 0) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE3,
(pf->pf_mfc_iobase[1] >> 8) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOSIZE, pf->pf_mfc_iomask);
}
reg = (pf->cfe->number & PCMCIA_CCR_OPTION_CFINDEX);
reg |= PCMCIA_CCR_OPTION_LEVIREQ;
if (pcmcia_mfc(pf->sc)) {
reg |= (PCMCIA_CCR_OPTION_FUNC_ENABLE |
PCMCIA_CCR_OPTION_ADDR_DECODE);
if (pf->pf_ih)
reg |= PCMCIA_CCR_OPTION_IREQ_ENABLE;
}
pcmcia_ccr_write(pf, PCMCIA_CCR_OPTION, reg);
reg = 0;
if ((pf->cfe->flags & PCMCIA_CFE_IO16) == 0)
reg |= PCMCIA_CCR_STATUS_IOIS8;
if (pf->cfe->flags & PCMCIA_CFE_AUDIO)
reg |= PCMCIA_CCR_STATUS_AUDIO;
pcmcia_ccr_write(pf, PCMCIA_CCR_STATUS, reg);
pcmcia_ccr_write(pf, PCMCIA_CCR_SOCKETCOPY, 0);
#ifdef PCMCIADEBUG
if (pcmcia_debug) {
SIMPLEQ_FOREACH(tmp, &pf->sc->card.pf_head, pf_list) {
printf("%s: function %d CCR at %d offset %lx: "
"%x %x %x %x, %x %x %x %x, %x\n",
tmp->sc->dev.dv_xname, tmp->number,
tmp->pf_ccr_window,
(unsigned long) tmp->pf_ccr_offset,
pcmcia_ccr_read(tmp, 0),
pcmcia_ccr_read(tmp, 1),
pcmcia_ccr_read(tmp, 2),
pcmcia_ccr_read(tmp, 3),
pcmcia_ccr_read(tmp, 5),
pcmcia_ccr_read(tmp, 6),
pcmcia_ccr_read(tmp, 7),
pcmcia_ccr_read(tmp, 8),
pcmcia_ccr_read(tmp, 9));
}
}
#endif
pf->pf_flags |= PFF_ENABLED;
#ifdef IT8368E_LEGACY_MODE
/* return to I/O mode */
it8368_mode(pf, IT8368_IO_MODE, IT8368_WIDTH_16);
#endif
return (0);
bad:
/*
* Decrement the reference count, and power down the socket, if
* necessary.
*/
if (--pf->sc->sc_enabled_count == 0)
pcmcia_chip_socket_disable(pf->sc->pct, pf->sc->pch);
DPRINTF(("%s: --enabled_count = %d\n", pf->sc->dev.dv_xname,
pf->sc->sc_enabled_count));
return (1);
}
/* Disable PCMCIA function. */
void
pcmcia_function_disable(pf)
struct pcmcia_function *pf;
{
struct pcmcia_function *tmp;
int reg;
if (pf->cfe == NULL)
panic("pcmcia_function_enable: function not initialized");
if ((pf->pf_flags & PFF_ENABLED) == 0) {
/*
* Don't do anything but decrement if we're already disabled.
*/
goto out;
}
if (pcmcia_mfc(pf->sc)) {
reg = pcmcia_ccr_read(pf, PCMCIA_CCR_OPTION);
reg &= ~(PCMCIA_CCR_OPTION_FUNC_ENABLE|
PCMCIA_CCR_OPTION_ADDR_DECODE|
PCMCIA_CCR_OPTION_IREQ_ENABLE);
pcmcia_ccr_write(pf, PCMCIA_CCR_OPTION, reg);
}
/*
* it's possible for different functions' CCRs to be in the same
* underlying page. Check for that. Note we mark us as disabled
* first to avoid matching ourself.
*/
pf->pf_flags &= ~PFF_ENABLED;
SIMPLEQ_FOREACH(tmp, &pf->sc->card.pf_head, pf_list) {
if ((tmp->pf_flags & PFF_ENABLED) &&
(pf->ccr_base >= (tmp->ccr_base - tmp->pf_ccr_offset)) &&
((pf->ccr_base + PCMCIA_CCR_SIZE) <=
(tmp->ccr_base - tmp->pf_ccr_offset + tmp->pf_ccr_realsize)))
break;
}
/* Not used by anyone else; unmap the CCR. */
if (tmp == NULL) {
pcmcia_mem_unmap(pf, pf->pf_ccr_window);
pcmcia_mem_free(pf, &pf->pf_pcmh);
}
out:
/*
* Decrement the reference count, and power down the socket, if
* necessary.
*/
if (--pf->sc->sc_enabled_count == 0)
pcmcia_chip_socket_disable(pf->sc->pct, pf->sc->pch);
DPRINTF(("%s: --enabled_count = %d\n", pf->sc->dev.dv_xname,
pf->sc->sc_enabled_count));
}
int
pcmcia_io_map(pf, width, pcihp, windowp)
struct pcmcia_function *pf;
int width;
struct pcmcia_io_handle *pcihp;
int *windowp;
{
int error;
if (pf->pf_flags & PFF_ENABLED)
printf("pcmcia_io_map: function is enabled!\n");
error = pcmcia_chip_io_map(pf->sc->pct, pf->sc->pch,
width, 0, pcihp->size, pcihp, windowp);
if (error)
return (error);
/*
* XXX in the multifunction multi-iospace-per-function case, this
* needs to cooperate with io_alloc to make sure that the spaces
* don't overlap, and that the ccr's are set correctly
*/
if (pcmcia_mfc(pf->sc)) {
int win;
long iomask;
/* round up to nearest (2^n)-1 */
for (iomask = 1; iomask < pcihp->size; iomask <<= 1)
;
iomask--;
win = pf->pf_mfc_windows;
KASSERT(win < 2);
printf("win %d = addr %lx size %lx iomask %lx\n", win, (long)pcihp->addr, (long)pcihp->size, (long)iomask);
pf->pf_mfc_iobase[win] = pcihp->addr;
pf->pf_mfc_iomask = iomask;
}
return (0);
}
void
pcmcia_io_unmap(pf, window)
struct pcmcia_function *pf;
int window;
{
if (pf->pf_flags & PFF_ENABLED)
printf("pcmcia_io_unmap: function is enabled!\n");
if (pcmcia_mfc(pf->sc)) {
/*
* Don't bother trying to unmap windows in the CCR here,
* because we generally get called when a card has been
* ejected, and the CCR isn't there any more.
*/
--pf->pf_mfc_windows;
}
pcmcia_chip_io_unmap(pf->sc->pct, pf->sc->pch, window);
}
void *
pcmcia_intr_establish(pf, ipl, ih_fct, ih_arg)
struct pcmcia_function *pf;
int ipl;
int (*ih_fct) __P((void *));
void *ih_arg;
{
if (pf->pf_flags & PFF_ENABLED)
printf("pcmcia_intr_establish: function is enabled!\n");
if (pf->pf_ih)
panic("pcmcia_intr_establish: already done\n");
pf->pf_ih = pcmcia_chip_intr_establish(pf->sc->pct, pf->sc->pch,
pf, ipl, ih_fct, ih_arg);
return (pf->pf_ih);
}
void
pcmcia_intr_disestablish(pf, ih)
struct pcmcia_function *pf;
void *ih;
{
if (pf->pf_flags & PFF_ENABLED)
printf("pcmcia_intr_disestablish: function is enabled!\n");
if (!pf->pf_ih)
panic("pcmcia_intr_distestablish: already done\n");
pcmcia_chip_intr_disestablish(pf->sc->pct, pf->sc->pch, ih);
pf->pf_ih = 0;
}