diff options
author | iedowse <iedowse@FreeBSD.org> | 2002-10-30 19:55:06 +0000 |
---|---|---|
committer | iedowse <iedowse@FreeBSD.org> | 2002-10-30 19:55:06 +0000 |
commit | d7097cadfdf94bc54970b6b9fdb80951af9e9065 (patch) | |
tree | 7d3748ad99e2a51113e9f14204e365b6d11661d2 | |
parent | 94318a914fff31084d6a418582cbea4340d294fc (diff) | |
download | FreeBSD-src-d7097cadfdf94bc54970b6b9fdb80951af9e9065.zip FreeBSD-src-d7097cadfdf94bc54970b6b9fdb80951af9e9065.tar.gz |
Save and restore the interrupt Edge/Level Control Registers (ELCR)
across system suspends on the Intel 82371AB PCI-ISA bridge. On a
Sony Vaio C1XD that I have, these registers are not set correctly
after an ACPI resume. The result is that after resuming, a shared
IRQ is left in edge-triggered mode so the interrupt can later become
jammed in a state where the line remains asserted, but the handler
is never called.
Reviewed by: jhb
-rw-r--r-- | sys/dev/pci/isa_pci.c | 93 |
1 files changed, 88 insertions, 5 deletions
diff --git a/sys/dev/pci/isa_pci.c b/sys/dev/pci/isa_pci.c index 85ef75d..5690df1 100644 --- a/sys/dev/pci/isa_pci.c +++ b/sys/dev/pci/isa_pci.c @@ -37,21 +37,36 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> +#include <machine/bus.h> +#include <machine/resource.h> #include <sys/bus.h> +#include <sys/rman.h> #include <pci/pcivar.h> #include <pci/pcireg.h> +#define ELCR_IOADDR 0x4d0 /* Interrupt Edge/Level Control Registers */ +#define ELCR_IOLEN 2 + +struct isab_softc { + struct resource *elcr_res; + u_char saved_elcr[ELCR_IOLEN]; +}; + static int isab_probe(device_t dev); static int isab_attach(device_t dev); +static int isab_detach(device_t dev); +static int isab_resume(device_t dev); +static int isab_suspend(device_t dev); static device_method_t isab_methods[] = { /* Device interface */ DEVMETHOD(device_probe, isab_probe), DEVMETHOD(device_attach, isab_attach), + DEVMETHOD(device_detach, isab_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), - DEVMETHOD(device_suspend, bus_generic_suspend), - DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_suspend, isab_suspend), + DEVMETHOD(device_resume, isab_resume), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), @@ -68,7 +83,7 @@ static device_method_t isab_methods[] = { static driver_t isab_driver = { "isab", isab_methods, - 0, + sizeof(struct isab_softc), }; static devclass_t isab_devclass; @@ -143,14 +158,82 @@ static int isab_attach(device_t dev) { device_t child; + struct isab_softc *sc = device_get_softc(dev); + int error, rid; /* * Attach an ISA bus. Note that we can only have one ISA bus. */ child = device_add_child(dev, "isa", 0); - if (child != NULL) - return(bus_generic_attach(dev)); + if (child != NULL) { + error = bus_generic_attach(dev); + if (error) + return (error); + } + + switch (pci_get_devid(dev)) { + case 0x71108086: /* Intel 82371AB */ + /* + * Sometimes the ELCR (Edge/Level Control Register) is not restored + * correctly on resume by the BIOS, so we handle it ourselves. + */ + rid = 0; + bus_set_resource(dev, SYS_RES_IOPORT, rid, ELCR_IOADDR, ELCR_IOLEN); + sc->elcr_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (sc->elcr_res == NULL) + device_printf(dev, "failed to allocate ELCR resource\n"); + break; + } return(0); } +static int +isab_detach(device_t dev) +{ + struct isab_softc *sc = device_get_softc(dev); + + if (sc->elcr_res != NULL) + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->elcr_res); + + return (bus_generic_detach(dev)); +} + +static int +isab_suspend(device_t dev) +{ + struct isab_softc *sc = device_get_softc(dev); + bus_space_tag_t bst; + bus_space_handle_t bsh; + int i; + + /* Save the ELCR if required. */ + if (sc->elcr_res != NULL) { + bst = rman_get_bustag(sc->elcr_res); + bsh = rman_get_bushandle(sc->elcr_res); + for (i = 0; i < ELCR_IOLEN; i++) + sc->saved_elcr[i] = bus_space_read_1(bst, bsh, i); + } + + return (bus_generic_suspend(dev)); +} + +static int +isab_resume(device_t dev) +{ + struct isab_softc *sc = device_get_softc(dev); + bus_space_tag_t bst; + bus_space_handle_t bsh; + int i; + + /* Restore the ELCR if required. */ + if (sc->elcr_res != NULL) { + bst = rman_get_bustag(sc->elcr_res); + bsh = rman_get_bushandle(sc->elcr_res); + for (i = 0; i < ELCR_IOLEN; i++) + bus_space_write_1(bst, bsh, i, sc->saved_elcr[i]); + } + + return (bus_generic_resume(dev)); +} |