b890549d41
dirty and murky past. * Override the default cache line size to be something reasonable if it's set to 0. Some NICs initialise with '0' (eg embedded ones) and there are comments in the driver stating that various OSes (eg older Linux ones) would incorrectly program things and 0 out this register. * Just default to overriding the latency timer. Every other driver does this. * Use a default cache line size of 32 bytes. It should be "reasonable enough". Obtained from: Linux ath9k, Atheros
357 lines
8.7 KiB
C
357 lines
8.7 KiB
C
/*-
|
|
* Copyright (c) 2002-2008 Sam Leffler, Errno Consulting
|
|
* 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,
|
|
* without modification.
|
|
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
|
|
* redistribution must be conditioned upon including a substantially
|
|
* similar Disclaimer requirement for further binary redistribution.
|
|
*
|
|
* NO WARRANTY
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* PCI/Cardbus front-end for the Atheros Wireless LAN controller driver.
|
|
*/
|
|
#include "opt_ath.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/module.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/errno.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/rman.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_media.h>
|
|
#include <net/if_arp.h>
|
|
|
|
#include <net80211/ieee80211_var.h>
|
|
|
|
#include <dev/ath/if_athvar.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pcireg.h>
|
|
|
|
/* For EEPROM firmware */
|
|
#ifdef ATH_EEPROM_FIRMWARE
|
|
#include <sys/linker.h>
|
|
#include <sys/firmware.h>
|
|
#endif /* ATH_EEPROM_FIRMWARE */
|
|
|
|
/*
|
|
* PCI glue.
|
|
*/
|
|
|
|
struct ath_pci_softc {
|
|
struct ath_softc sc_sc;
|
|
struct resource *sc_sr; /* memory resource */
|
|
struct resource *sc_irq; /* irq resource */
|
|
void *sc_ih; /* interrupt handler */
|
|
};
|
|
|
|
#define BS_BAR 0x10
|
|
#define PCIR_RETRY_TIMEOUT 0x41
|
|
#define PCIR_CFG_PMCSR 0x48
|
|
|
|
#define DEFAULT_CACHESIZE 32
|
|
|
|
static void
|
|
ath_pci_setup(device_t dev)
|
|
{
|
|
uint8_t cz;
|
|
|
|
/* XXX TODO: need to override the _system_ saved copies of this */
|
|
|
|
/*
|
|
* If the cache line size is 0, force it to a reasonable
|
|
* value.
|
|
*/
|
|
cz = pci_read_config(dev, PCIR_CACHELNSZ, 1);
|
|
if (cz == 0) {
|
|
pci_write_config(dev, PCIR_CACHELNSZ,
|
|
DEFAULT_CACHESIZE / 4, 1);
|
|
}
|
|
|
|
/* Override the system latency timer */
|
|
pci_write_config(dev, PCIR_LATTIMER, 0xa8, 1);
|
|
|
|
/* If a PCI NIC, force wakeup */
|
|
#ifdef ATH_PCI_WAKEUP_WAR
|
|
/* XXX TODO: don't do this for non-PCI (ie, PCIe, Cardbus!) */
|
|
if (1) {
|
|
uint16_t pmcsr;
|
|
pmcsr = pci_read_config(dev, PCIR_CFG_PMCSR, 2);
|
|
pmcsr |= 3;
|
|
pci_write_config(dev, PCIR_CFG_PMCSR, pmcsr, 2);
|
|
pmcsr &= ~3;
|
|
pci_write_config(dev, PCIR_CFG_PMCSR, pmcsr, 2);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Disable retry timeout to keep PCI Tx retries from
|
|
* interfering with C3 CPU state.
|
|
*/
|
|
pci_write_config(dev, PCIR_RETRY_TIMEOUT, 0, 1);
|
|
}
|
|
|
|
static int
|
|
ath_pci_probe(device_t dev)
|
|
{
|
|
const char* devname;
|
|
|
|
devname = ath_hal_probe(pci_get_vendor(dev), pci_get_device(dev));
|
|
if (devname != NULL) {
|
|
device_set_desc(dev, devname);
|
|
return BUS_PROBE_DEFAULT;
|
|
}
|
|
return ENXIO;
|
|
}
|
|
|
|
static int
|
|
ath_pci_attach(device_t dev)
|
|
{
|
|
struct ath_pci_softc *psc = device_get_softc(dev);
|
|
struct ath_softc *sc = &psc->sc_sc;
|
|
int error = ENXIO;
|
|
int rid;
|
|
#ifdef ATH_EEPROM_FIRMWARE
|
|
const struct firmware *fw = NULL;
|
|
const char *buf;
|
|
#endif
|
|
|
|
sc->sc_dev = dev;
|
|
|
|
/*
|
|
* Enable bus mastering.
|
|
*/
|
|
pci_enable_busmaster(dev);
|
|
|
|
/*
|
|
* Setup other PCI bus configuration parameters.
|
|
*/
|
|
ath_pci_setup(dev);
|
|
|
|
/*
|
|
* Setup memory-mapping of PCI registers.
|
|
*/
|
|
rid = BS_BAR;
|
|
psc->sc_sr = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
|
|
RF_ACTIVE);
|
|
if (psc->sc_sr == NULL) {
|
|
device_printf(dev, "cannot map register space\n");
|
|
goto bad;
|
|
}
|
|
/* XXX uintptr_t is a bandaid for ia64; to be fixed */
|
|
sc->sc_st = (HAL_BUS_TAG)(uintptr_t) rman_get_bustag(psc->sc_sr);
|
|
sc->sc_sh = (HAL_BUS_HANDLE) rman_get_bushandle(psc->sc_sr);
|
|
/*
|
|
* Mark device invalid so any interrupts (shared or otherwise)
|
|
* that arrive before the HAL is setup are discarded.
|
|
*/
|
|
sc->sc_invalid = 1;
|
|
|
|
/*
|
|
* Arrange interrupt line.
|
|
*/
|
|
rid = 0;
|
|
psc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
|
|
RF_SHAREABLE|RF_ACTIVE);
|
|
if (psc->sc_irq == NULL) {
|
|
device_printf(dev, "could not map interrupt\n");
|
|
goto bad1;
|
|
}
|
|
if (bus_setup_intr(dev, psc->sc_irq,
|
|
INTR_TYPE_NET | INTR_MPSAFE,
|
|
NULL, ath_intr, sc, &psc->sc_ih)) {
|
|
device_printf(dev, "could not establish interrupt\n");
|
|
goto bad2;
|
|
}
|
|
|
|
/*
|
|
* Setup DMA descriptor area.
|
|
*/
|
|
if (bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */
|
|
1, 0, /* alignment, bounds */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filter, filterarg */
|
|
0x3ffff, /* maxsize XXX */
|
|
ATH_MAX_SCATTER, /* nsegments */
|
|
0x3ffff, /* maxsegsize XXX */
|
|
BUS_DMA_ALLOCNOW, /* flags */
|
|
NULL, /* lockfunc */
|
|
NULL, /* lockarg */
|
|
&sc->sc_dmat)) {
|
|
device_printf(dev, "cannot allocate DMA tag\n");
|
|
goto bad3;
|
|
}
|
|
|
|
#ifdef ATH_EEPROM_FIRMWARE
|
|
/*
|
|
* If there's an EEPROM firmware image, load that in.
|
|
*/
|
|
if (resource_string_value(device_get_name(dev), device_get_unit(dev),
|
|
"eeprom_firmware", &buf) == 0) {
|
|
if (bootverbose)
|
|
device_printf(dev, "%s: looking up firmware @ '%s'\n",
|
|
__func__, buf);
|
|
|
|
fw = firmware_get(buf);
|
|
if (fw == NULL) {
|
|
device_printf(dev, "%s: couldn't find firmware\n",
|
|
__func__);
|
|
goto bad3;
|
|
}
|
|
|
|
device_printf(dev, "%s: EEPROM firmware @ %p\n",
|
|
__func__, fw->data);
|
|
sc->sc_eepromdata =
|
|
malloc(fw->datasize, M_TEMP, M_WAITOK | M_ZERO);
|
|
if (! sc->sc_eepromdata) {
|
|
device_printf(dev, "%s: can't malloc eepromdata\n",
|
|
__func__);
|
|
goto bad3;
|
|
}
|
|
memcpy(sc->sc_eepromdata, fw->data, fw->datasize);
|
|
firmware_put(fw, 0);
|
|
}
|
|
#endif /* ATH_EEPROM_FIRMWARE */
|
|
|
|
ATH_LOCK_INIT(sc);
|
|
ATH_PCU_LOCK_INIT(sc);
|
|
|
|
error = ath_attach(pci_get_device(dev), sc);
|
|
if (error == 0) /* success */
|
|
return 0;
|
|
|
|
ATH_PCU_LOCK_DESTROY(sc);
|
|
ATH_LOCK_DESTROY(sc);
|
|
bus_dma_tag_destroy(sc->sc_dmat);
|
|
bad3:
|
|
bus_teardown_intr(dev, psc->sc_irq, psc->sc_ih);
|
|
bad2:
|
|
bus_release_resource(dev, SYS_RES_IRQ, 0, psc->sc_irq);
|
|
bad1:
|
|
bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR, psc->sc_sr);
|
|
bad:
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
ath_pci_detach(device_t dev)
|
|
{
|
|
struct ath_pci_softc *psc = device_get_softc(dev);
|
|
struct ath_softc *sc = &psc->sc_sc;
|
|
|
|
/* check if device was removed */
|
|
sc->sc_invalid = !bus_child_present(dev);
|
|
|
|
/*
|
|
* Do a config read to clear pre-existing pci error status.
|
|
*/
|
|
(void) pci_read_config(dev, PCIR_COMMAND, 4);
|
|
|
|
ath_detach(sc);
|
|
|
|
bus_generic_detach(dev);
|
|
bus_teardown_intr(dev, psc->sc_irq, psc->sc_ih);
|
|
bus_release_resource(dev, SYS_RES_IRQ, 0, psc->sc_irq);
|
|
|
|
bus_dma_tag_destroy(sc->sc_dmat);
|
|
bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR, psc->sc_sr);
|
|
|
|
if (sc->sc_eepromdata)
|
|
free(sc->sc_eepromdata, M_TEMP);
|
|
|
|
ATH_PCU_LOCK_DESTROY(sc);
|
|
ATH_LOCK_DESTROY(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ath_pci_shutdown(device_t dev)
|
|
{
|
|
struct ath_pci_softc *psc = device_get_softc(dev);
|
|
|
|
ath_shutdown(&psc->sc_sc);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ath_pci_suspend(device_t dev)
|
|
{
|
|
struct ath_pci_softc *psc = device_get_softc(dev);
|
|
|
|
ath_suspend(&psc->sc_sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ath_pci_resume(device_t dev)
|
|
{
|
|
struct ath_pci_softc *psc = device_get_softc(dev);
|
|
|
|
/*
|
|
* Suspend/resume resets the PCI configuration space.
|
|
*/
|
|
ath_pci_setup(dev);
|
|
|
|
ath_resume(&psc->sc_sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t ath_pci_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, ath_pci_probe),
|
|
DEVMETHOD(device_attach, ath_pci_attach),
|
|
DEVMETHOD(device_detach, ath_pci_detach),
|
|
DEVMETHOD(device_shutdown, ath_pci_shutdown),
|
|
DEVMETHOD(device_suspend, ath_pci_suspend),
|
|
DEVMETHOD(device_resume, ath_pci_resume),
|
|
|
|
{ 0,0 }
|
|
};
|
|
static driver_t ath_pci_driver = {
|
|
"ath",
|
|
ath_pci_methods,
|
|
sizeof (struct ath_pci_softc)
|
|
};
|
|
static devclass_t ath_devclass;
|
|
DRIVER_MODULE(ath_pci, pci, ath_pci_driver, ath_devclass, 0, 0);
|
|
MODULE_VERSION(ath_pci, 1);
|
|
MODULE_DEPEND(ath_pci, wlan, 1, 1, 1); /* 802.11 media layer */
|
|
MODULE_DEPEND(ath_pci, if_ath, 1, 1, 1); /* if_ath driver */
|