diff options
| author | Warner Losh <imp@FreeBSD.org> | 2001-07-21 22:40:27 +0000 |
|---|---|---|
| committer | Warner Losh <imp@FreeBSD.org> | 2001-07-21 22:40:27 +0000 |
| commit | 2c987b07e12faa614276d519091e9ca355787d5d (patch) | |
| tree | aa1af70e29b364206ce665357bc658a3ae775de3 | |
| parent | 4809def8c7ba6ef1bdd0a3f2b2acf2b1a5a7309a (diff) | |
Notes
| -rw-r--r-- | sys/pci/pci.c | 233 | ||||
| -rw-r--r-- | sys/pci/pci_if.m | 43 | ||||
| -rw-r--r-- | sys/pci/pcireg.h | 61 | ||||
| -rw-r--r-- | sys/pci/pcivar.h | 68 |
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. */ |
