NetBSD/sys/dev/pcmcia/pcmcia.c

924 lines
23 KiB
C
Raw Normal View History

/* $NetBSD: pcmcia.c,v 1.93 2009/05/12 14:42:19 cegger Exp $ */
2004-08-09 03:19:59 +04:00
/*
* 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.
*/
2001-11-13 10:24:43 +03:00
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pcmcia.c,v 1.93 2009/05/12 14:42:19 cegger Exp $");
2001-11-13 10:24:43 +03:00
#include "opt_pcmciaverbose.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
2007-12-09 23:27:42 +03:00
#include <net/if.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(device_t, cfdata_t, void *);
void pcmcia_attach(device_t, device_t, void *);
int pcmcia_detach(device_t, int);
int pcmcia_rescan(device_t, const char *, const int *);
void pcmcia_childdetached(device_t, device_t);
2005-02-04 05:10:35 +03:00
int pcmcia_print(void *, const char *);
During shutdown, detach devices in an orderly fashion. Call the detach routine for every device in the device tree, starting with the leaves and moving toward the root, expecting that each (pseudo-)device driver will use the opportunity to gracefully commit outstandings transactions to the underlying (pseudo-)device and to relinquish control of the hardware to the system BIOS. Detaching devices is not suitable for every shutdown: in an emergency, or if the system state is inconsistent, we should resort to a fast, simple shutdown that uses only the pmf(9) shutdown hooks and the (deprecated) shutdownhooks. For now, if the flag RB_NOSYNC is set in boothowto, opt for the fast, simple shutdown. Add a device flag, DVF_DETACH_SHUTDOWN, that indicates by its presence that it is safe to detach a device during shutdown. Introduce macros CFATTACH_DECL3() and CFATTACH_DECL3_NEW() for creating autoconf attachments with default device flags. Add DVF_DETACH_SHUTDOWN to configuration attachments for atabus(4), atw(4) at cardbus(4), cardbus(4), cardslot(4), com(4) at isa(4), elanpar(4), elanpex(4), elansc(4), gpio(4), npx(4) at isa(4), nsphyter(4), pci(4), pcib(4), pcmcia(4), ppb(4), sip(4), wd(4), and wdc(4) at isa(4). Add a device-detachment "reason" flag, DETACH_SHUTDOWN, that tells the autoconf code and a device driver that the reason for detachment is system shutdown. Add a sysctl, kern.detachall, that tells the system to try to detach every device at shutdown, regardless of any device's DVF_DETACH_SHUTDOWN flag. The default for kern.detachall is 0. SET IT TO 1, PLEASE, TO HELP TEST AND DEBUG DEVICE DETACHMENT AT SHUTDOWN. This is a work in progress. In future work, I aim to treat pseudo-devices more thoroughly, and to gracefully tear down a stack of (pseudo-)disk drivers and filesystems, including cgd(4), vnd(4), and raid(4) instances at shutdown. Also commit some changes that are not easily untangled from the rest: (1) begin to simplify device_t locking: rename struct pmf_private to device_lock, and incorporate device_lock into struct device. (2) #include <sys/device.h> in sys/pmf.h in order to get some definitions that it needs. Stop unnecessarily #including <sys/device.h> in sys/arch/x86/include/pic.h to keep the amd64, xen, and i386 releases building.
2009-04-02 04:09:32 +04:00
CFATTACH_DECL3_NEW(pcmcia, sizeof(struct pcmcia_softc),
pcmcia_match, pcmcia_attach, pcmcia_detach, NULL,
During shutdown, detach devices in an orderly fashion. Call the detach routine for every device in the device tree, starting with the leaves and moving toward the root, expecting that each (pseudo-)device driver will use the opportunity to gracefully commit outstandings transactions to the underlying (pseudo-)device and to relinquish control of the hardware to the system BIOS. Detaching devices is not suitable for every shutdown: in an emergency, or if the system state is inconsistent, we should resort to a fast, simple shutdown that uses only the pmf(9) shutdown hooks and the (deprecated) shutdownhooks. For now, if the flag RB_NOSYNC is set in boothowto, opt for the fast, simple shutdown. Add a device flag, DVF_DETACH_SHUTDOWN, that indicates by its presence that it is safe to detach a device during shutdown. Introduce macros CFATTACH_DECL3() and CFATTACH_DECL3_NEW() for creating autoconf attachments with default device flags. Add DVF_DETACH_SHUTDOWN to configuration attachments for atabus(4), atw(4) at cardbus(4), cardbus(4), cardslot(4), com(4) at isa(4), elanpar(4), elanpex(4), elansc(4), gpio(4), npx(4) at isa(4), nsphyter(4), pci(4), pcib(4), pcmcia(4), ppb(4), sip(4), wd(4), and wdc(4) at isa(4). Add a device-detachment "reason" flag, DETACH_SHUTDOWN, that tells the autoconf code and a device driver that the reason for detachment is system shutdown. Add a sysctl, kern.detachall, that tells the system to try to detach every device at shutdown, regardless of any device's DVF_DETACH_SHUTDOWN flag. The default for kern.detachall is 0. SET IT TO 1, PLEASE, TO HELP TEST AND DEBUG DEVICE DETACHMENT AT SHUTDOWN. This is a work in progress. In future work, I aim to treat pseudo-devices more thoroughly, and to gracefully tear down a stack of (pseudo-)disk drivers and filesystems, including cgd(4), vnd(4), and raid(4) instances at shutdown. Also commit some changes that are not easily untangled from the rest: (1) begin to simplify device_t locking: rename struct pmf_private to device_lock, and incorporate device_lock into struct device. (2) #include <sys/device.h> in sys/pmf.h in order to get some definitions that it needs. Stop unnecessarily #including <sys/device.h> in sys/arch/x86/include/pic.h to keep the amd64, xen, and i386 releases building.
2009-04-02 04:09:32 +04:00
pcmcia_rescan, pcmcia_childdetached, DVF_DETACH_SHUTDOWN);
int
pcmcia_ccr_read(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(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(device_t parent, cfdata_t 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(device_t parent, device_t self, void *aux)
{
struct pcmciabus_attach_args *paa = aux;
struct pcmcia_softc *sc = device_private(self);
2007-12-01 17:35:51 +03:00
aprint_naive("\n");
aprint_normal("\n");
sc->dev = self;
sc->pct = paa->pct;
sc->pch = paa->pch;
sc->iobase = paa->iobase;
sc->iosize = paa->iosize;
sc->ih = NULL;
2007-12-09 23:27:42 +03:00
if (!pmf_device_register(self, NULL, NULL))
aprint_error_dev(self, "couldn't establish power handler\n");
}
int
pcmcia_detach(device_t self, int flags)
{
int rc;
if ((rc = config_detach_children(self, flags)) != 0)
return rc;
pmf_device_deregister(self);
return 0;
}
int
pcmcia_card_attach(device_t dev)
{
struct pcmcia_softc *sc = device_private(dev);
struct pcmcia_function *pf;
int error;
static const int wildcard[PCMCIACF_NLOCS] = {
PCMCIACF_FUNCTION_DEFAULT
};
/*
* this is here so that when socket_enable calls gettype, trt happens
*/
SIMPLEQ_FIRST(&sc->card.pf_head) = NULL;
pcmcia_socket_enable(dev);
pcmcia_read_cis(sc);
pcmcia_check_cis_quirks(sc);
#if 1 /* XXX remove this, done below ??? */
/*
* bail now if the card has no functions, or if there was an error in
* the cis.
*/
if (sc->card.error ||
SIMPLEQ_EMPTY(&sc->card.pf_head)) {
printf("%s: card appears to have bogus CIS\n",
device_xname(sc->dev));
error = EIO;
goto done;
}
#endif
if (pcmcia_verbose)
pcmcia_print_cis(sc);
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",
device_xname(sc->dev), device_xname(pf->child),
pf->number);
panic("pcmcia_card_attach");
}
#endif
pf->sc = sc;
pf->child = NULL;
pf->cfe = NULL;
pf->pf_ih = NULL;
}
error = pcmcia_rescan(dev, "pcmcia", wildcard);
done:
pcmcia_socket_disable(dev);
return (error);
}
int
pcmcia_rescan(device_t self, const char *ifattr,
const int *locators)
{
struct pcmcia_softc *sc = device_private(self);
struct pcmcia_function *pf;
struct pcmcia_attach_args paa;
int locs[PCMCIACF_NLOCS];
if (sc->card.error ||
SIMPLEQ_EMPTY(&sc->card.pf_head)) {
/* XXX silently ignore if no card present? */
return (EIO);
}
SIMPLEQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
if ((locators[PCMCIACF_FUNCTION] != PCMCIACF_FUNCTION_DEFAULT)
&& (locators[PCMCIACF_FUNCTION] != pf->number))
continue;
if (pf->child)
continue;
locs[PCMCIACF_FUNCTION] = pf->number;
paa.manufacturer = sc->card.manufacturer;
paa.product = sc->card.product;
paa.card = &sc->card;
paa.pf = pf;
pf->child = config_found_sm_loc(self, "pcmcia", locs, &paa,
pcmcia_print,
config_stdsubmatch);
}
return (0);
}
void
pcmcia_card_detach(device_t dev, int flags)
/* flags: DETACH_* flags */
{
struct pcmcia_softc *sc = device_private(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) {
pf->pf_flags |= PFF_DETACHED;
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
if (pf->child == NULL)
continue;
DPRINTF(("%s: detaching %s (function %d)\n",
device_xname(sc->dev), device_xname(pf->child), pf->number));
if ((error = config_detach(pf->child, flags)) != 0) {
printf("%s: error %d detaching %s (function %d)\n",
device_xname(sc->dev), error, device_xname(pf->child),
pf->number);
}
}
if (sc->sc_enabled_count != 0) {
#ifdef DIAGNOSTIC
printf("pcmcia_card_detach: enabled_count should be 0 here??\n");
#endif
pcmcia_chip_socket_disable(sc->pct, sc->pch);
sc->sc_enabled_count = 0;
}
}
void
pcmcia_childdetached(device_t self, device_t child)
{
struct pcmcia_softc *sc = device_private(self);
struct pcmcia_function *pf;
SIMPLEQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
if (SIMPLEQ_EMPTY(&pf->cfe_head))
continue;
if (pf->child == child) {
KASSERT(device_locator(child, PCMCIACF_FUNCTION)
== pf->number);
pf->child = NULL;
return;
}
}
2008-04-06 01:31:23 +04:00
aprint_error_dev(self, "pcmcia_childdetached: %s not found\n",
device_xname(child));
}
void
pcmcia_card_deactivate(device_t dev)
{
struct pcmcia_softc *sc = device_private(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",
device_xname(sc->dev), device_xname(pf->child), pf->number));
config_deactivate(pf->child);
}
}
int
pcmcia_print(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\n", pa->pf->number, devinfo);
return (UNCONF);
}
void
pcmcia_devinfo(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 void *
pcmcia_product_lookup(struct pcmcia_attach_args *pa, const void *tab, size_t nent, size_t ent_size, pcmcia_product_match_fn matchfn)
{
const struct pcmcia_product *pp;
int n;
int matches;
#ifdef DIAGNOSTIC
if (sizeof *pp > ent_size)
2005-02-27 03:26:58 +03:00
panic("pcmcia_product_lookup: bogus ent_size %ld",
2000-02-05 23:02:42 +03:00
(long) ent_size);
#endif
for (pp = tab, n = nent; n; pp = (const struct pcmcia_product *)
((const char *)pp + ent_size), n--) {
/* see if it matches vendor/product */
matches = 0;
if ((pp->pp_vendor != PCMCIA_VENDOR_INVALID &&
pp->pp_vendor == pa->manufacturer) &&
(pp->pp_product != PCMCIA_PRODUCT_INVALID &&
pp->pp_product == pa->product))
matches = 1;
if ((pp->pp_cisinfo[0] && pa->card->cis1_info[0] &&
!strcmp(pp->pp_cisinfo[0], pa->card->cis1_info[0])) &&
(pp->pp_cisinfo[1] && pa->card->cis1_info[1] &&
!strcmp(pp->pp_cisinfo[1], pa->card->cis1_info[1])) &&
(!pp->pp_cisinfo[2] || (pa->card->cis1_info[2] &&
!strcmp(pp->pp_cisinfo[2], pa->card->cis1_info[2]))) &&
(!pp->pp_cisinfo[3] || (pa->card->cis1_info[3] &&
!strcmp(pp->pp_cisinfo[3], pa->card->cis1_info[3]))))
matches = 1;
/* if a separate match function is given, let it override */
if (matchfn)
matches = (*matchfn)(pa, pp, matches);
if (matches)
return (pp);
}
return (0);
}
void
pcmcia_socket_settype(device_t dev, int type)
{
struct pcmcia_softc *sc = device_private(dev);
pcmcia_chip_socket_settype(sc->pct, sc->pch, type);
}
/*
* Initialize a PCMCIA function. May be called as long as the function is
* disabled.
*/
void
pcmcia_function_init(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(device_t dev)
{
struct pcmcia_softc *sc = device_private(dev);
if (sc->sc_enabled_count++ == 0)
pcmcia_chip_socket_enable(sc->pct, sc->pch);
DPRINTF(("%s: ++enabled_count = %d\n", device_xname(sc->dev),
sc->sc_enabled_count));
}
void
pcmcia_socket_disable(device_t dev)
{
struct pcmcia_softc *sc = device_private(dev);
if (--sc->sc_enabled_count == 0)
pcmcia_chip_socket_disable(sc->pct, sc->pch);
DPRINTF(("%s: --enabled_count = %d\n", device_xname(sc->dev),
sc->sc_enabled_count));
}
/* Enable a PCMCIA function */
int
pcmcia_function_enable(struct pcmcia_function *pf)
{
struct pcmcia_softc *sc = pf->sc;
struct pcmcia_function *tmp;
int reg;
int error;
if (pf->cfe == NULL)
panic("pcmcia_function_enable: function not initialized");
/*
* Increase the reference count on the socket, enabling power, if
* necessary.
*/
pcmcia_socket_enable(sc->dev);
pcmcia_socket_settype(sc->dev, pf->cfe->iftype);
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, &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) {
error = pcmcia_mem_alloc(pf, PCMCIA_CCR_SIZE, &pf->pf_pcmh);
if (error)
goto bad;
error = pcmcia_mem_map(pf, PCMCIA_MEM_ATTR, pf->ccr_base,
PCMCIA_CCR_SIZE, &pf->pf_pcmh, &pf->pf_ccr_offset,
&pf->pf_ccr_window);
if (error) {
pcmcia_mem_free(pf, &pf->pf_pcmh);
goto bad;
}
}
if (pcmcia_mfc(sc) || 1) {
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE0,
(pf->pf_mfc_iobase >> 0) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE1,
(pf->pf_mfc_iobase >> 8) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE2,
(pf->pf_mfc_iobase >> 16) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE3,
(pf->pf_mfc_iobase >> 24) & 0xff);
pcmcia_ccr_write(pf, PCMCIA_CCR_IOLIMIT,
pf->pf_mfc_iomax - pf->pf_mfc_iobase);
}
reg = 0;
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);
reg = (pf->cfe->number & PCMCIA_CCR_OPTION_CFINDEX);
reg |= PCMCIA_CCR_OPTION_LEVIREQ;
if (pcmcia_mfc(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);
#ifdef PCMCIADEBUG
if (pcmcia_debug) {
SIMPLEQ_FOREACH(tmp, &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",
device_xname(tmp->sc->dev), 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),
2005-02-27 03:26:58 +03:00
pcmcia_ccr_read(tmp, 6),
pcmcia_ccr_read(tmp, 7),
pcmcia_ccr_read(tmp, 8),
pcmcia_ccr_read(tmp, 9));
}
}
#endif
#ifdef IT8368E_LEGACY_MODE
/* return to I/O mode */
it8368_mode(pf, IT8368_IO_MODE, IT8368_WIDTH_16);
#endif
pf->pf_flags |= PFF_ENABLED;
return (0);
bad:
/*
* Decrement the reference count, and power down the socket, if
* necessary.
*/
2008-04-06 01:31:23 +04:00
printf("%s: couldn't map the CCR\n", device_xname(pf->child));
pcmcia_socket_disable(sc->dev);
return (error);
}
/* Disable PCMCIA function. */
void
pcmcia_function_disable(struct pcmcia_function *pf)
{
struct pcmcia_softc *sc = pf->sc;
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(sc) &&
(pf->pf_flags & PFF_DETACHED) == 0) {
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, &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.
*/
pcmcia_socket_disable(sc->dev);
}
int
pcmcia_io_map(struct pcmcia_function *pf, int width, struct pcmcia_io_handle *pcihp, int *windowp)
{
struct pcmcia_softc *sc = pf->sc;
int error;
if (pf->pf_flags & PFF_ENABLED)
2004-08-09 23:33:07 +04:00
printf("pcmcia_io_map: function is enabled!\n");
error = pcmcia_chip_io_map(sc->pct, 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(sc) || 1) {
bus_addr_t iobase = pcihp->addr;
bus_addr_t iomax = pcihp->addr + pcihp->size - 1;
DPRINTF(("window iobase %lx iomax %lx\n", (long)iobase,
(long)iomax));
if (pf->pf_mfc_iobase == 0) {
pf->pf_mfc_iobase = iobase;
pf->pf_mfc_iomax = iomax;
} else {
if (iobase < pf->pf_mfc_iobase)
pf->pf_mfc_iobase = iobase;
if (iomax > pf->pf_mfc_iomax)
pf->pf_mfc_iomax = iomax;
}
DPRINTF(("function iobase %lx iomax %lx\n",
(long)pf->pf_mfc_iobase, (long)pf->pf_mfc_iomax));
}
return (0);
}
void
pcmcia_io_unmap(struct pcmcia_function *pf, int window)
{
struct pcmcia_softc *sc = pf->sc;
if (pf->pf_flags & PFF_ENABLED)
2004-08-09 23:33:07 +04:00
printf("pcmcia_io_unmap: function is enabled!\n");
pcmcia_chip_io_unmap(sc->pct, sc->pch, window);
}
void *
2009-03-15 23:30:05 +03:00
pcmcia_intr_establish(struct pcmcia_function *pf, int ipl,
int (*ih_fct)(void *), void *ih_arg)
{
if (pf->pf_flags & PFF_ENABLED)
2004-08-09 23:33:07 +04:00
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);
if (!pf->pf_ih)
2008-04-06 01:31:23 +04:00
aprint_error_dev(pf->child, "interrupt establish failed\n");
return (pf->pf_ih);
}
void
pcmcia_intr_disestablish(struct pcmcia_function *pf, void *ih)
{
if (pf->pf_flags & PFF_ENABLED)
2004-08-09 23:33:07 +04:00
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;
}
int
pcmcia_config_alloc(struct pcmcia_function *pf, struct pcmcia_config_entry *cfe)
{
int error = 0;
int n, m;
for (n = 0; n < cfe->num_iospace; n++) {
bus_addr_t start = cfe->iospace[n].start;
bus_size_t length = cfe->iospace[n].length;
bus_size_t align = cfe->iomask ? (1 << cfe->iomask) :
length;
bus_size_t skew = start & (align - 1);
if ((start - skew) == 0 && align < 0x400) {
if (skew)
printf("Drats! I need a skew!\n");
start = 0;
}
DPRINTF(("pcmcia_config_alloc: io %d start=%lx length=%lx align=%lx skew=%lx\n",
n, (long)start, (long)length, (long)align, (long)skew));
error = pcmcia_io_alloc(pf, start, length, align,
&cfe->iospace[n].handle);
if (error)
break;
}
if (n < cfe->num_iospace) {
for (m = 0; m < n; m++)
pcmcia_io_free(pf, &cfe->iospace[m].handle);
return (error);
}
for (n = 0; n < cfe->num_memspace; n++) {
bus_size_t length = cfe->memspace[n].length;
DPRINTF(("pcmcia_config_alloc: mem %d length %lx\n", n,
(long)length));
error = pcmcia_mem_alloc(pf, length, &cfe->memspace[n].handle);
if (error)
break;
}
if (n < cfe->num_memspace) {
for (m = 0; m < cfe->num_iospace; m++)
pcmcia_io_free(pf, &cfe->iospace[m].handle);
for (m = 0; m < n; m++)
pcmcia_mem_free(pf, &cfe->memspace[m].handle);
return (error);
}
/* This one's good! */
return (error);
}
void
pcmcia_config_free(struct pcmcia_function *pf)
{
struct pcmcia_config_entry *cfe = pf->cfe;
int m;
for (m = 0; m < cfe->num_iospace; m++)
pcmcia_io_free(pf, &cfe->iospace[m].handle);
for (m = 0; m < cfe->num_memspace; m++)
pcmcia_mem_free(pf, &cfe->memspace[m].handle);
}
int
pcmcia_config_map(struct pcmcia_function *pf)
{
struct pcmcia_config_entry *cfe = pf->cfe;
int error = 0;
int n, m;
for (n = 0; n < cfe->num_iospace; n++) {
int width;
if (cfe->flags & PCMCIA_CFE_IO16)
width = PCMCIA_WIDTH_AUTO;
else
width = PCMCIA_WIDTH_IO8;
error = pcmcia_io_map(pf, width, &cfe->iospace[n].handle,
&cfe->iospace[n].window);
if (error)
break;
}
if (n < cfe->num_iospace) {
for (m = 0; m < n; m++)
pcmcia_io_unmap(pf, cfe->iospace[m].window);
return (error);
}
for (n = 0; n < cfe->num_memspace; n++) {
bus_size_t length = cfe->memspace[n].length;
int width;
DPRINTF(("pcmcia_config_alloc: mem %d length %lx\n", n,
(long)length));
/*XXX*/
width = PCMCIA_WIDTH_MEM8|PCMCIA_MEM_COMMON;
error = pcmcia_mem_map(pf, width, 0, length,
&cfe->memspace[n].handle, &cfe->memspace[n].offset,
&cfe->memspace[n].window);
if (error)
break;
}
if (n < cfe->num_memspace) {
for (m = 0; m < cfe->num_iospace; m++)
pcmcia_io_unmap(pf, cfe->iospace[m].window);
for (m = 0; m < n; m++)
pcmcia_mem_unmap(pf, cfe->memspace[m].window);
return (error);
}
/* This one's good! */
return (error);
}
void
pcmcia_config_unmap(struct pcmcia_function *pf)
{
struct pcmcia_config_entry *cfe = pf->cfe;
int m;
for (m = 0; m < cfe->num_iospace; m++)
pcmcia_io_unmap(pf, cfe->iospace[m].window);
for (m = 0; m < cfe->num_memspace; m++)
pcmcia_mem_unmap(pf, cfe->memspace[m].window);
}
int
2009-03-15 23:30:05 +03:00
pcmcia_function_configure(struct pcmcia_function *pf,
int (*validator)(struct pcmcia_config_entry *))
{
struct pcmcia_config_entry *cfe;
int error = ENOENT;
SIMPLEQ_FOREACH(cfe, &pf->cfe_head, cfe_list) {
error = validator(cfe);
if (error)
continue;
error = pcmcia_config_alloc(pf, cfe);
if (!error)
break;
}
if (!cfe) {
DPRINTF(("pcmcia_function_configure: no config entry found, error=%d\n",
error));
return (error);
}
/* Remember which configuration entry we are using. */
pf->cfe = cfe;
error = pcmcia_config_map(pf);
if (error) {
DPRINTF(("pcmcia_function_configure: map failed, error=%d\n",
error));
return (error);
}
return (0);
}
void
pcmcia_function_unconfigure(struct pcmcia_function *pf)
{
pcmcia_config_unmap(pf);
pcmcia_config_free(pf);
}