NetBSD/sys/dev/cardbus/if_rtw_cardbus.c
dyoung 36fffd8d02 In pmf(9), improve the implementation of device self-suspension
and make suspension by self, by drvctl(8), and by ACPI system sleep
play nice together.  Start solidifying some temporary API changes.

1. Extract a new header file, <sys/device_if.h>, from <sys/device.h> and
   #include it from <sys/pmf.h> instead of <sys/device.h> to break the
   circular dependency between <sys/device.h> and <sys/pmf.h>.

2. Introduce pmf_qual_t, an aggregate of qualifications on a PMF
   suspend/resume call.  Start to replace instances of PMF_FN_PROTO,
   PMF_FN_ARGS, et cetera, with a pmf_qual_t.

3. Introduce the notion of a "suspensor," an entity that holds a
   device in suspension.  More than one suspensor may hold a device
   at once.  A device stays suspended as long as at least one
   suspensor holds it.  A device resumes when the last suspensor
   releases it.

   Currently, the kernel defines three suspensors,

   3a the system-suspensor: for system suspension, initiated
      by 'sysctl -w machdep.sleep_state=3', by lid closure, by
      power-button press, et cetera,

   3b the drvctl-suspensor: for device suspension by /dev/drvctl
      ioctl, e.g., drvctl -S sip0.

   3c the system self-suspensor: for device drivers that suspend
      themselves and their children.  Several drivers for network
      interfaces put the network device to sleep while it is not
      administratively up, that is, after the kernel calls if_stop(,
      1).  The self-suspensor should not be used directly.  See
      the description of suspensor delegates, below.

   A suspensor can have one or more "delegates".  A suspensor can
   release devices that its delegates hold suspended.  Right now,
   only the system self-suspensor has delegates.  For each device
   that a self-suspending driver attaches, it creates the device's
   self-suspensor, a delegate of the system self-suspensor.

   Suspensors stop a system-wide suspend/resume cycle from waking
   devices that the operator put to sleep with drvctl before the cycle.
   They also help self-suspension to work more simply, safely, and in
   accord with expectations.

4. Add the notion of device activation level, devact_level_t,
   and a routine for checking the current activation level,
   device_activation().  Current activation levels are DEVACT_LEVEL_BUS,
   DEVACT_LEVEL_DRIVER, and DEVACT_LEVEL_CLASS, which respectively
   indicate that the device's bus is active, that the bus and device are
   active, and that the bus, device, and the functions of the device's
   class (network, audio) are active.

   Suspend/resume calls can be qualified with a devact_level_t.
   The power-management framework treats a devact_level_t that
   qualifies a device suspension as the device's current activation
   level; it only runs hooks to reduce the activation level from
   the presumed current level to the fully suspended state.  The
   framework treats a devact_level_t qualifying device resumption
   as the target activation level; it only runs hooks to raise the
   activation level to the target.

5. Use pmf_qual_t, devact_level_t, and self-suspensors in several
   drivers.

6. Temporarily add an unused power-management workqueue that I will
   remove or replace, soon.
2009-09-16 16:34:49 +00:00

425 lines
12 KiB
C

/* $NetBSD: if_rtw_cardbus.c,v 1.31 2009/09/16 16:34:50 dyoung Exp $ */
/*-
* Copyright (c) 2004, 2005 David Young. All rights reserved.
*
* Adapted for the RTL8180 by David Young.
*
* 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. The name of David Young may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY David Young ``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 David
* Young 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.
*/
/*-
* Copyright (c) 1999, 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
* NASA Ames Research Center.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.
*/
/*
* Cardbus front-end for the Realtek RTL8180 802.11 MAC/BBP driver.
*
* TBD factor with atw, tlp Cardbus front-ends?
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_rtw_cardbus.c,v 1.31 2009/09/16 16:34:50 dyoung Exp $");
#include "opt_inet.h"
#include "bpfilter.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <machine/endian.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_ether.h>
#include <net80211/ieee80211_netbsd.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_var.h>
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#endif
#include <sys/bus.h>
#include <sys/intr.h>
#include <dev/ic/rtwreg.h>
#include <dev/ic/rtwvar.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcidevs.h>
#include <dev/cardbus/cardbusvar.h>
#include <dev/pci/pcidevs.h>
/*
* PCI configuration space registers used by the RTL8180.
*/
#define RTW_PCI_IOBA 0x10 /* i/o mapped base */
#define RTW_PCI_MMBA 0x14 /* memory mapped base */
struct rtw_cardbus_softc {
struct rtw_softc sc_rtw; /* real RTL8180 softc */
/* CardBus-specific goo. */
void *sc_ih; /* interrupt handle */
cardbus_devfunc_t sc_ct; /* our CardBus devfuncs */
cardbustag_t sc_tag; /* our CardBus tag */
int sc_csr; /* CSR bits */
bus_size_t sc_mapsize; /* size of the mapped bus space
* region
*/
int sc_bar_reg; /* which BAR to use */
pcireg_t sc_bar_val; /* value of the BAR */
cardbus_intr_line_t sc_intrline; /* interrupt line */
};
int rtw_cardbus_match(device_t, cfdata_t, void *);
void rtw_cardbus_attach(device_t, device_t, void *);
int rtw_cardbus_detach(device_t, int);
CFATTACH_DECL_NEW(rtw_cardbus, sizeof(struct rtw_cardbus_softc),
rtw_cardbus_match, rtw_cardbus_attach, rtw_cardbus_detach, NULL);
void rtw_cardbus_setup(struct rtw_cardbus_softc *);
bool rtw_cardbus_resume(device_t PMF_FN_PROTO);
bool rtw_cardbus_suspend(device_t PMF_FN_PROTO);
const struct rtw_cardbus_product *rtw_cardbus_lookup(
const struct cardbus_attach_args *);
const struct rtw_cardbus_product {
u_int32_t rcp_vendor; /* PCI vendor ID */
u_int32_t rcp_product; /* PCI product ID */
const char *rcp_product_name;
} rtw_cardbus_products[] = {
{ PCI_VENDOR_REALTEK, PCI_PRODUCT_REALTEK_RT8180,
"Realtek RTL8180 802.11 MAC/BBP" },
{ PCI_VENDOR_BELKIN, PCI_PRODUCT_BELKIN_F5D6020V3,
"Belkin F5D6020v3 802.11b (RTL8180 MAC/BBP)" },
{ PCI_VENDOR_DLINK, PCI_PRODUCT_DLINK_DWL610,
"DWL-610 D-Link Air 802.11b (RTL8180 MAC/BBP)" },
{ 0, 0, NULL },
};
const struct rtw_cardbus_product *
rtw_cardbus_lookup(const struct cardbus_attach_args *ca)
{
const struct rtw_cardbus_product *rcp;
for (rcp = rtw_cardbus_products; rcp->rcp_product_name != NULL; rcp++) {
if (PCI_VENDOR(ca->ca_id) == rcp->rcp_vendor &&
PCI_PRODUCT(ca->ca_id) == rcp->rcp_product)
return rcp;
}
return NULL;
}
int
rtw_cardbus_match(device_t parent, cfdata_t match, void *aux)
{
struct cardbus_attach_args *ca = aux;
if (rtw_cardbus_lookup(ca) != NULL)
return 1;
return 0;
}
static void
rtw_cardbus_funcregen(struct rtw_regs *regs, int enable)
{
u_int32_t reg;
rtw_config0123_enable(regs, 1);
reg = RTW_READ(regs, RTW_CONFIG3);
if (enable)
RTW_WRITE(regs, RTW_CONFIG3, reg | RTW_CONFIG3_FUNCREGEN);
else
RTW_WRITE(regs, RTW_CONFIG3, reg & ~RTW_CONFIG3_FUNCREGEN);
rtw_config0123_enable(regs, 0);
}
void
rtw_cardbus_attach(device_t parent, device_t self, void *aux)
{
struct rtw_cardbus_softc *csc = device_private(self);
struct rtw_softc *sc = &csc->sc_rtw;
struct rtw_regs *regs = &sc->sc_regs;
struct cardbus_attach_args *ca = aux;
cardbus_devfunc_t ct = ca->ca_ct;
const struct rtw_cardbus_product *rcp;
bus_addr_t adr;
int rev;
sc->sc_dev = self;
sc->sc_dmat = ca->ca_dmat;
csc->sc_ct = ct;
csc->sc_tag = ca->ca_tag;
rcp = rtw_cardbus_lookup(ca);
if (rcp == NULL) {
printf("\n");
panic("rtw_cardbus_attach: impossible");
}
/* Get revision info. */
rev = PCI_REVISION(ca->ca_class);
printf(": %s\n", rcp->rcp_product_name);
RTW_DPRINTF(RTW_DEBUG_ATTACH,
("%s: pass %d.%d signature %08x\n", device_xname(self),
(rev >> 4) & 0xf, rev & 0xf,
cardbus_conf_read(ct->ct_cc, ct->ct_cf, csc->sc_tag, 0x80)));
/*
* Map the device.
*/
csc->sc_csr = CARDBUS_COMMAND_MASTER_ENABLE |
CARDBUS_COMMAND_PARITY_ENABLE |
CARDBUS_COMMAND_SERR_ENABLE;
if (Cardbus_mapreg_map(ct, RTW_PCI_MMBA, CARDBUS_MAPREG_TYPE_MEM, 0,
&regs->r_bt, &regs->r_bh, &adr, &regs->r_sz) == 0) {
RTW_DPRINTF(RTW_DEBUG_ATTACH,
("%s: %s mapped %" PRIuMAX " bytes mem space\n",
device_xname(self), __func__, (uintmax_t)regs->r_sz));
#if rbus
#else
(*ct->ct_cf->cardbus_mem_open)(cc, 0, adr, adr+csc->sc_mapsize);
#endif
csc->sc_csr |= CARDBUS_COMMAND_MEM_ENABLE;
csc->sc_bar_reg = RTW_PCI_MMBA;
csc->sc_bar_val = adr | CARDBUS_MAPREG_TYPE_MEM;
} else if (Cardbus_mapreg_map(ct, RTW_PCI_IOBA, CARDBUS_MAPREG_TYPE_IO,
0, &regs->r_bt, &regs->r_bh, &adr, &regs->r_sz) == 0) {
RTW_DPRINTF(RTW_DEBUG_ATTACH,
("%s: %s mapped %" PRIuMAX " bytes I/O space\n",
device_xname(self), __func__, (uintmax_t)regs->r_sz));
#if rbus
#else
(*ct->ct_cf->cardbus_io_open)(cc, 0, adr, adr+csc->sc_mapsize);
#endif
csc->sc_csr |= CARDBUS_COMMAND_IO_ENABLE;
csc->sc_bar_reg = RTW_PCI_IOBA;
csc->sc_bar_val = adr | CARDBUS_MAPREG_TYPE_IO;
} else {
aprint_error_dev(self, "unable to map device registers\n");
return;
}
/*
* Bring the chip out of powersave mode and initialize the
* configuration registers.
*/
rtw_cardbus_setup(csc);
/* Remember which interrupt line. */
csc->sc_intrline = ca->ca_intrline;
/*
* Finish off the attach.
*/
rtw_attach(sc);
rtw_cardbus_funcregen(regs, 1);
RTW_WRITE(regs, RTW_FEMR, 0);
RTW_WRITE(regs, RTW_FER, RTW_READ(regs, RTW_FER));
if (pmf_device_register(self,
rtw_cardbus_suspend, rtw_cardbus_resume)) {
pmf_class_network_register(self, &sc->sc_if);
/*
* Power down the socket.
*/
pmf_device_suspend(self, &sc->sc_qual);
} else
aprint_error_dev(self, "couldn't establish power handler\n");
}
int
rtw_cardbus_detach(device_t self, int flags)
{
struct rtw_cardbus_softc *csc = device_private(self);
struct rtw_softc *sc = &csc->sc_rtw;
struct rtw_regs *regs = &sc->sc_regs;
struct cardbus_devfunc *ct = csc->sc_ct;
int rc;
#if defined(DIAGNOSTIC)
if (ct == NULL)
panic("%s: data structure lacks", device_xname(self));
#endif
if ((rc = rtw_detach(sc)) != 0)
return rc;
/*
* Unhook the interrupt handler.
*/
if (csc->sc_ih != NULL)
cardbus_intr_disestablish(ct->ct_cc, ct->ct_cf, csc->sc_ih);
/*
* Release bus space and close window.
*/
if (csc->sc_bar_reg != 0)
Cardbus_mapreg_unmap(ct, csc->sc_bar_reg,
regs->r_bt, regs->r_bh, regs->r_sz);
return 0;
}
bool
rtw_cardbus_resume(device_t self PMF_FN_ARGS)
{
struct rtw_cardbus_softc *csc = device_private(self);
struct rtw_softc *sc = &csc->sc_rtw;
cardbus_devfunc_t ct = csc->sc_ct;
cardbus_chipset_tag_t cc = ct->ct_cc;
cardbus_function_tag_t cf = ct->ct_cf;
/*
* Map and establish the interrupt.
*/
csc->sc_ih = cardbus_intr_establish(cc, cf, csc->sc_intrline, IPL_NET,
rtw_intr, sc);
if (csc->sc_ih == NULL) {
aprint_error_dev(sc->sc_dev,
"unable to establish interrupt\n");
return false;
}
rtw_cardbus_funcregen(&sc->sc_regs, 1);
RTW_WRITE(&sc->sc_regs, RTW_FEMR, RTW_FEMR_INTR);
RTW_WRITE(&sc->sc_regs, RTW_FER, RTW_FER_INTR);
return rtw_resume(self PMF_FN_CALL);
}
bool
rtw_cardbus_suspend(device_t self PMF_FN_ARGS)
{
struct rtw_cardbus_softc *csc = device_private(self);
struct rtw_softc *sc = &csc->sc_rtw;
cardbus_devfunc_t ct = csc->sc_ct;
cardbus_chipset_tag_t cc = ct->ct_cc;
cardbus_function_tag_t cf = ct->ct_cf;
if (!rtw_suspend(self PMF_FN_CALL))
return false;
RTW_WRITE(&sc->sc_regs, RTW_FEMR,
RTW_READ(&sc->sc_regs, RTW_FEMR) & ~RTW_FEMR_INTR);
rtw_cardbus_funcregen(&sc->sc_regs, 0);
/* Unhook the interrupt handler. */
cardbus_intr_disestablish(cc, cf, csc->sc_ih);
csc->sc_ih = NULL;
return true;
}
void
rtw_cardbus_setup(struct rtw_cardbus_softc *csc)
{
cardbustag_t tag = csc->sc_tag;
cardbus_devfunc_t ct = csc->sc_ct;
cardbus_chipset_tag_t cc = ct->ct_cc;
cardbusreg_t bhlc, csr, lattimer;
cardbus_function_tag_t cf = ct->ct_cf;
(void)cardbus_set_powerstate(ct, tag, PCI_PWR_D0);
/* I believe the datasheet tries to warn us that the RTL8180
* wants for 16 (0x10) to divide the latency timer.
*/
bhlc = cardbus_conf_read(cc, cf, tag, CARDBUS_BHLC_REG);
lattimer = rounddown(PCI_LATTIMER(bhlc), 0x10);
if (PCI_LATTIMER(bhlc) != lattimer) {
bhlc &= ~(PCI_LATTIMER_MASK << PCI_LATTIMER_SHIFT);
bhlc |= (lattimer << PCI_LATTIMER_SHIFT);
cardbus_conf_write(cc, cf, tag, CARDBUS_BHLC_REG, bhlc);
}
/* Program the BAR. */
cardbus_conf_write(cc, cf, tag, csc->sc_bar_reg, csc->sc_bar_val);
/* Enable the appropriate bits in the PCI CSR. */
csr = cardbus_conf_read(cc, cf, tag, PCI_COMMAND_STATUS_REG);
csr &= ~(PCI_COMMAND_IO_ENABLE|PCI_COMMAND_MEM_ENABLE);
csr |= csc->sc_csr;
cardbus_conf_write(cc, cf, tag, PCI_COMMAND_STATUS_REG, csr);
}