summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWarner Losh <imp@FreeBSD.org>2001-07-21 22:40:27 +0000
committerWarner Losh <imp@FreeBSD.org>2001-07-21 22:40:27 +0000
commit2c987b07e12faa614276d519091e9ca355787d5d (patch)
treeaa1af70e29b364206ce665357bc658a3ae775de3
parent4809def8c7ba6ef1bdd0a3f2b2acf2b1a5a7309a (diff)
Notes
-rw-r--r--sys/pci/pci.c233
-rw-r--r--sys/pci/pci_if.m43
-rw-r--r--sys/pci/pcireg.h61
-rw-r--r--sys/pci/pcivar.h68
4 files changed, 405 insertions, 0 deletions
diff --git a/sys/pci/pci.c b/sys/pci/pci.c
index 5841be95689c..b3a336b3484d 100644
--- a/sys/pci/pci.c
+++ b/sys/pci/pci.c
@@ -64,6 +64,8 @@
#include <machine/smp.h>
#endif /* APIC_IO */
+static void pci_read_extcap(pcicfgregs *cfg);
+
struct pci_quirk {
u_int32_t devid; /* Vendor/device of the card */
int type;
@@ -286,6 +288,8 @@ pci_hdrtypedata(pcicfgregs *cfg)
static struct pci_devinfo *
pci_readcfg(pcicfgregs *probe)
{
+#define REG(n, w) pci_cfgread(probe, n, w)
+
pcicfgregs *cfg = NULL;
struct pci_devinfo *devlist_entry;
struct devlist *devlist_head;
@@ -362,6 +366,9 @@ pci_readcfg(pcicfgregs *probe)
pci_fixancient(cfg);
pci_hdrtypedata(cfg);
+ if (REG(PCIR_STATUS, 2) & PCIM_STATUS_CAPPRESENT)
+ pci_read_extcap(cfg);
+
STAILQ_INSERT_TAIL(devlist_head, devlist_entry, pci_links);
devlist_entry->conf.pc_sel.pc_bus = cfg->bus;
@@ -383,6 +390,57 @@ pci_readcfg(pcicfgregs *probe)
pci_generation++;
}
return (devlist_entry);
+#undef REG
+}
+
+static void
+pci_read_extcap(pcicfgregs *cfg)
+{
+#define REG(n, w) pci_cfgread(cfg, n, w)
+ int ptr, nextptr, ptrptr;
+
+ switch (cfg->hdrtype) {
+ case 0:
+ ptrptr = 0x34;
+ break;
+ case 2:
+ ptrptr = 0x14;
+ break;
+ default:
+ return; /* no extended capabilities support */
+ }
+ nextptr = REG(ptrptr, 1); /* sanity check? */
+
+ /*
+ * Read capability entries.
+ */
+ while (nextptr != 0) {
+ /* Sanity check */
+ if (nextptr > 255) {
+ printf("illegal PCI extended capability offset %d\n",
+ nextptr);
+ return;
+ }
+ /* Find the next entry */
+ ptr = nextptr;
+ nextptr = REG(ptr + 1, 1);
+
+ /* Process this entry */
+ switch (REG(ptr, 1)) {
+ case 0x01: /* PCI power management */
+ if (cfg->pp_cap == 0) {
+ cfg->pp_cap = REG(ptr + PCIR_POWER_CAP, 2);
+ cfg->pp_status = ptr + PCIR_POWER_STATUS;
+ cfg->pp_pmcsr = ptr + PCIR_POWER_PMCSR;
+ if ((nextptr - ptr) > PCIR_POWER_DATA)
+ cfg->pp_data = ptr + PCIR_POWER_DATA;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+#undef REG
}
#if 0
@@ -414,6 +472,148 @@ pci_freecfg(struct pci_devinfo *dinfo)
/*
+ * PCI power manangement
+ */
+static int
+pci_set_powerstate_method(device_t dev, device_t child, int state)
+{
+ struct pci_devinfo *dinfo = device_get_ivars(child);
+ pcicfgregs *cfg = &dinfo->cfg;
+ u_int16_t status;
+ int result;
+
+ if (cfg->pp_cap != 0) {
+ status = PCI_READ_CONFIG(dev, child, cfg->pp_status, 2) & ~PCIM_PSTAT_DMASK;
+ result = 0;
+ switch (state) {
+ case PCI_POWERSTATE_D0:
+ status |= PCIM_PSTAT_D0;
+ break;
+ case PCI_POWERSTATE_D1:
+ if (cfg->pp_cap & PCIM_PCAP_D1SUPP) {
+ status |= PCIM_PSTAT_D1;
+ } else {
+ result = EOPNOTSUPP;
+ }
+ break;
+ case PCI_POWERSTATE_D2:
+ if (cfg->pp_cap & PCIM_PCAP_D2SUPP) {
+ status |= PCIM_PSTAT_D2;
+ } else {
+ result = EOPNOTSUPP;
+ }
+ break;
+ case PCI_POWERSTATE_D3:
+ status |= PCIM_PSTAT_D3;
+ break;
+ default:
+ result = EINVAL;
+ }
+ if (result == 0)
+ PCI_WRITE_CONFIG(dev, child, cfg->pp_status, status, 2);
+ } else {
+ result = ENXIO;
+ }
+ return(result);
+}
+
+static int
+pci_get_powerstate_method(device_t dev, device_t child)
+{
+ struct pci_devinfo *dinfo = device_get_ivars(child);
+ pcicfgregs *cfg = &dinfo->cfg;
+ u_int16_t status;
+ int result;
+
+ if (cfg->pp_cap != 0) {
+ status = PCI_READ_CONFIG(dev, child, cfg->pp_status, 2);
+ switch (status & PCIM_PSTAT_DMASK) {
+ case PCIM_PSTAT_D0:
+ result = PCI_POWERSTATE_D0;
+ break;
+ case PCIM_PSTAT_D1:
+ result = PCI_POWERSTATE_D1;
+ break;
+ case PCIM_PSTAT_D2:
+ result = PCI_POWERSTATE_D2;
+ break;
+ case PCIM_PSTAT_D3:
+ result = PCI_POWERSTATE_D3;
+ break;
+ default:
+ result = PCI_POWERSTATE_UNKNOWN;
+ break;
+ }
+ } else {
+ /* No support, device is always at D0 */
+ result = PCI_POWERSTATE_D0;
+ }
+ return(result);
+}
+
+/*
+ * Some convenience functions for PCI device drivers.
+ */
+
+static __inline void
+pci_set_command_bit(device_t dev, device_t child, u_int16_t bit)
+{
+ u_int16_t command;
+
+ command = PCI_READ_CONFIG(dev, child, PCIR_COMMAND, 2);
+ command |= bit;
+ PCI_WRITE_CONFIG(dev, child, PCIR_COMMAND, command, 2);
+}
+
+static __inline void
+pci_clear_command_bit(device_t dev, device_t child, u_int16_t bit)
+{
+ u_int16_t command;
+
+ command = PCI_READ_CONFIG(dev, child, PCIR_COMMAND, 2);
+ command &= ~bit;
+ PCI_WRITE_CONFIG(dev, child, PCIR_COMMAND, command, 2);
+}
+
+static void
+pci_enable_busmaster_method(device_t dev, device_t child)
+{
+ pci_set_command_bit(dev, child, PCIM_CMD_BUSMASTEREN);
+}
+
+static void
+pci_disable_busmaster_method(device_t dev, device_t child)
+{
+ pci_clear_command_bit(dev, child, PCIM_CMD_BUSMASTEREN);
+}
+
+static void
+pci_enable_io_method(device_t dev, device_t child, int space)
+{
+ switch(space) {
+ case SYS_RES_IOPORT:
+ pci_set_command_bit(dev, child, PCIM_CMD_PORTEN);
+ break;
+ case SYS_RES_MEMORY:
+ pci_set_command_bit(dev, child, PCIM_CMD_MEMEN);
+ break;
+ }
+}
+
+static void
+pci_disable_io_method(device_t dev, device_t child, int space)
+{
+ switch(space) {
+ case SYS_RES_IOPORT:
+ pci_clear_command_bit(dev, child, PCIM_CMD_PORTEN);
+ break;
+ case SYS_RES_MEMORY:
+ pci_clear_command_bit(dev, child, PCIM_CMD_MEMEN);
+ break;
+ }
+}
+
+/*
* This is the user interface to PCI configuration space.
*/
@@ -1394,6 +1594,33 @@ pci_alloc_resource(device_t dev, device_t child, int type, int *rid,
{
struct pci_devinfo *dinfo = device_get_ivars(child);
struct resource_list *rl = &dinfo->resources;
+#ifdef PCI_ENABLE_IRQ_ROUTING
+ pcicfgregs *cfg = &dinfo->cfg;
+
+ /*
+ * Perform lazy resource allocation
+ *
+ * XXX add support here for SYS_RES_IOPORT and SYS_RES_MEMORY
+ */
+ if (device_get_parent(child) == dev) {
+ /*
+ * If device doesn't have an interrupt routed, and is
+ * deserving of an interrupt, try to assign it one.
+ */
+ if ((type == SYS_RES_IRQ) && (cfg->intline == 255) &&
+ (cfg->intpin != 0)) {
+ device_printf(device_get_parent(dev), "intreq\n");
+ cfg->intline = pci_cfgintr(pci_get_bus(child),
+ pci_get_slot(child), cfg->intpin);
+ if (cfg->intline != 255) {
+ pci_write_config(child, PCIR_INTLINE,
+ cfg->intline, 1);
+ resource_list_add(rl, SYS_RES_IRQ, 0,
+ cfg->intline, cfg->intline, 1);
+ }
+ }
+ }
+#endif
return resource_list_alloc(rl, dev, child, type, rid,
start, end, count, flags);
@@ -1505,6 +1732,12 @@ static device_method_t pci_methods[] = {
/* PCI interface */
DEVMETHOD(pci_read_config, pci_read_config_method),
DEVMETHOD(pci_write_config, pci_write_config_method),
+ DEVMETHOD(pci_enable_busmaster, pci_enable_busmaster_method),
+ DEVMETHOD(pci_disable_busmaster, pci_disable_busmaster_method),
+ DEVMETHOD(pci_enable_io, pci_enable_io_method),
+ DEVMETHOD(pci_disable_io, pci_disable_io_method),
+ DEVMETHOD(pci_get_powerstate, pci_get_powerstate_method),
+ DEVMETHOD(pci_set_powerstate, pci_set_powerstate_method),
{ 0, 0 }
};
diff --git a/sys/pci/pci_if.m b/sys/pci/pci_if.m
index 95b0081ca32b..21357f7b2c74 100644
--- a/sys/pci/pci_if.m
+++ b/sys/pci/pci_if.m
@@ -42,3 +42,46 @@ METHOD void write_config {
u_int32_t val;
int width;
};
+
+METHOD int get_powerstate {
+ device_t dev;
+ device_t child;
+};
+
+METHOD int set_powerstate {
+ device_t dev;
+ device_t child;
+ int state;
+};
+
+METHOD void enable_busmaster {
+ device_t dev;
+ device_t child;
+};
+
+METHOD void disable_busmaster {
+ device_t dev;
+ device_t child;
+};
+
+METHOD void enable_io {
+ device_t dev;
+ device_t child;
+ int space;
+};
+
+METHOD void disable_io {
+ device_t dev;
+ device_t child;
+ int space;
+};
+
+#
+# Route an interrupt. Returns a value suitable for stuffing into
+# a device's interrupt register.
+#
+METHOD int route_interrupt {
+ device_t pcib;
+ device_t dev;
+ int pin;
+};
diff --git a/sys/pci/pcireg.h b/sys/pci/pcireg.h
index 2c638144cbc9..2cdba33840a7 100644
--- a/sys/pci/pcireg.h
+++ b/sys/pci/pcireg.h
@@ -59,6 +59,19 @@
#define PCIM_CMD_MWRICEN 0x0010
#define PCIM_CMD_PERRESPEN 0x0040
#define PCIR_STATUS 0x06
+#define PCIM_STATUS_CAPPRESENT 0x0010
+#define PCIM_STATUS_66CAPABLE 0x0020
+#define PCIM_STATUS_BACKTOBACK 0x0080
+#define PCIM_STATUS_PERRREPORT 0x0100
+#define PCIM_STATUS_SEL_FAST 0x0000
+#define PCIM_STATUS_SEL_MEDIMUM 0x0200
+#define PCIM_STATUS_SEL_SLOW 0x0400
+#define PCIM_STATUS_SEL_MASK 0x0600
+#define PCIM_STATUS_STABORT 0x0800
+#define PCIM_STATUS_RTABORT 0x1000
+#define PCIM_STATUS_RMABORT 0x2000
+#define PCIM_STATUS_SERR 0x4000
+#define PCIM_STATUS_PERR 0x8000
#define PCIR_REVID 0x08
#define PCIR_PROGIF 0x09
#define PCIR_SUBCLASS 0x0a
@@ -228,6 +241,54 @@
#define PCIC_OTHER 0xff
+/* PCI power manangement */
+
+#define PCIR_POWER_CAP 0x2
+#define PCIM_PCAP_SPEC 0x0007
+#define PCIM_PCAP_PMEREQCLK 0x0008
+#define PCIM_PCAP_PMEREQPWR 0x0010
+#define PCIM_PCAP_DEVSPECINIT 0x0020
+#define PCIM_PCAP_DYNCLOCK 0x0040
+#define PCIM_PCAP_SECCLOCK 0x00c0
+#define PCIM_PCAP_CLOCKMASK 0x00c0
+#define PCIM_PCAP_REQFULLCLOCK 0x0100
+#define PCIM_PCAP_D1SUPP 0x0200
+#define PCIM_PCAP_D2SUPP 0x0400
+#define PCIM_PCAP_D0PME 0x1000
+#define PCIM_PCAP_D1PME 0x2000
+#define PCIM_PCAP_D2PME 0x4000
+
+#define PCIR_POWER_STATUS 0x4
+#define PCIM_PSTAT_D0 0x0000
+#define PCIM_PSTAT_D1 0x0001
+#define PCIM_PSTAT_D2 0x0002
+#define PCIM_PSTAT_D3 0x0003
+#define PCIM_PSTAT_DMASK 0x0003
+#define PCIM_PSTAT_REPENABLE 0x0010
+#define PCIM_PSTAT_PMEENABLE 0x0100
+#define PCIM_PSTAT_D0POWER 0x0000
+#define PCIM_PSTAT_D1POWER 0x0200
+#define PCIM_PSTAT_D2POWER 0x0400
+#define PCIM_PSTAT_D3POWER 0x0600
+#define PCIM_PSTAT_D0HEAT 0x0800
+#define PCIM_PSTAT_D1HEAT 0x1000
+#define PCIM_PSTAT_D2HEAT 0x1200
+#define PCIM_PSTAT_D3HEAT 0x1400
+#define PCIM_PSTAT_DATAUNKN 0x0000
+#define PCIM_PSTAT_DATADIV10 0x2000
+#define PCIM_PSTAT_DATADIV100 0x4000
+#define PCIM_PSTAT_DATADIV1000 0x6000
+#define PCIM_PSTAT_DATADIVMASK 0x6000
+#define PCIM_PSTAT_PME 0x8000
+
+#define PCIR_POWER_PMCSR 0x6
+#define PCIM_PMCSR_DCLOCK 0x10
+#define PCIM_PMCSR_B2SUPP 0x20
+#define PCIM_BMCSR_B3SUPP 0x40
+#define PCIM_BMCSR_BPCE 0x80
+
+#define PCIR_POWER_DATA 0x7
+
/* some PCI vendor definitions (only used to identify ancient devices !!! */
#define PCIV_INTEL 0x8086
diff --git a/sys/pci/pcivar.h b/sys/pci/pcivar.h
index 9c345c08d969..4e3889bb8fa5 100644
--- a/sys/pci/pcivar.h
+++ b/sys/pci/pcivar.h
@@ -93,6 +93,11 @@ typedef struct pcicfg {
u_int8_t secondarybus; /* bus on secondary side of bridge, if any */
u_int8_t subordinatebus; /* topmost bus number behind bridge, if any */
+
+ u_int16_t pp_cap; /* PCI power management capabilities */
+ u_int8_t pp_status; /* config space address of PCI power status reg */
+ u_int8_t pp_pmcsr; /* config space address of PMCSR reg */
+ u_int8_t pp_data; /* config space address of PCI power data reg */
} pcicfgregs;
/* additional type 1 device config header information (PCI to PCI bridge) */
@@ -246,6 +251,69 @@ pci_write_config(device_t dev, int reg, u_int32_t val, int width)
}
/*
+ * Convenience functions.
+ *
+ * These should be used in preference to manually manipulating
+ * configuration space.
+ */
+static __inline void
+pci_enable_busmaster(device_t dev)
+{
+ PCI_ENABLE_BUSMASTER(device_get_parent(dev), dev);
+}
+
+static __inline void
+pci_disable_busmaster(device_t dev)
+{
+ PCI_DISABLE_BUSMASTER(device_get_parent(dev), dev);
+}
+
+static __inline void
+pci_enable_io(device_t dev, int space)
+{
+ PCI_ENABLE_IO(device_get_parent(dev), dev, space);
+}
+
+static __inline void
+pci_disable_io(device_t dev, int space)
+{
+ PCI_DISABLE_IO(device_get_parent(dev), dev, space);
+}
+
+/*
+ * PCI power states are as defined by ACPI:
+ *
+ * D0 State in which device is on and running. It is receiving full
+ * power from the system and delivering full functionality to the user.
+ * D1 Class-specific low-power state in which device context may or may not
+ * be lost. Buses in D1 cannot do anything to the bus that would force
+ * devices on that bus to loose context.
+ * D2 Class-specific low-power state in which device context may or may
+ * not be lost. Attains greater power savings than D1. Buses in D2
+ * can cause devices on that bus to loose some context. Devices in D2
+ * must be prepared for the bus to be in D2 or higher.
+ * D3 State in which the device is off and not running. Device context is
+ * lost. Power can be removed from the device.
+ */
+#define PCI_POWERSTATE_D0 0
+#define PCI_POWERSTATE_D1 1
+#define PCI_POWERSTATE_D2 2
+#define PCI_POWERSTATE_D3 3
+#define PCI_POWERSTATE_UNKNOWN -1
+
+static __inline int
+pci_set_powerstate(device_t dev, int state)
+{
+ return PCI_SET_POWERSTATE(device_get_parent(dev), dev, state);
+}
+
+static __inline int
+pci_get_powerstate(device_t dev)
+{
+ return PCI_GET_POWERSTATE(device_get_parent(dev), dev);
+}
+
+/*
* Ivars for pci bridges.
*/