1 | /* |
---|
2 | * PCI Backend - Configuration space overlay for power management |
---|
3 | * |
---|
4 | * Author: Ryan Wilson <hap9@epoch.ncsc.mil> |
---|
5 | */ |
---|
6 | |
---|
7 | #include <linux/pci.h> |
---|
8 | #include "conf_space.h" |
---|
9 | #include "conf_space_capability.h" |
---|
10 | |
---|
11 | static int pm_caps_read(struct pci_dev *dev, int offset, u16 *value, |
---|
12 | void *data) |
---|
13 | { |
---|
14 | int err; |
---|
15 | u16 real_value; |
---|
16 | |
---|
17 | err = pci_read_config_word(dev, offset, &real_value); |
---|
18 | if (err) |
---|
19 | goto out; |
---|
20 | |
---|
21 | *value = real_value & ~PCI_PM_CAP_PME_MASK; |
---|
22 | |
---|
23 | out: |
---|
24 | return err; |
---|
25 | } |
---|
26 | |
---|
27 | /* PM_OK_BITS specifies the bits that the driver domain is allowed to change. |
---|
28 | * Can't allow driver domain to enable PMEs - they're shared */ |
---|
29 | #define PM_OK_BITS (PCI_PM_CTRL_PME_STATUS|PCI_PM_CTRL_DATA_SEL_MASK) |
---|
30 | |
---|
31 | static int pm_ctrl_write(struct pci_dev *dev, int offset, u16 new_value, |
---|
32 | void *data) |
---|
33 | { |
---|
34 | int err; |
---|
35 | u16 old_value; |
---|
36 | pci_power_t new_state, old_state; |
---|
37 | |
---|
38 | err = pci_read_config_word(dev, offset, &old_value); |
---|
39 | if (err) |
---|
40 | goto out; |
---|
41 | |
---|
42 | old_state = (pci_power_t)(old_value & PCI_PM_CTRL_STATE_MASK); |
---|
43 | new_state = (pci_power_t)(new_value & PCI_PM_CTRL_STATE_MASK); |
---|
44 | |
---|
45 | new_value &= PM_OK_BITS; |
---|
46 | if ((old_value & PM_OK_BITS) != new_value) { |
---|
47 | new_value = (old_value & ~PM_OK_BITS) | new_value; |
---|
48 | err = pci_write_config_word(dev, offset, new_value); |
---|
49 | if (err) |
---|
50 | goto out; |
---|
51 | } |
---|
52 | |
---|
53 | /* Let pci core handle the power management change */ |
---|
54 | dev_dbg(&dev->dev, "set power state to %x\n", new_state); |
---|
55 | err = pci_set_power_state(dev, new_state); |
---|
56 | if (err) { |
---|
57 | err = PCIBIOS_SET_FAILED; |
---|
58 | goto out; |
---|
59 | } |
---|
60 | |
---|
61 | /* |
---|
62 | * Device may lose PCI config info on D3->D0 transition. This |
---|
63 | * is a problem for some guests which will not reset BARs. Even |
---|
64 | * those that have a go will be foiled by our BAR-write handler |
---|
65 | * which will discard the write! Since Linux won't re-init |
---|
66 | * the config space automatically in all cases, we do it here. |
---|
67 | * Future: Should we re-initialise all first 64 bytes of config space? |
---|
68 | */ |
---|
69 | if (new_state == PCI_D0 && |
---|
70 | (old_state == PCI_D3hot || old_state == PCI_D3cold) && |
---|
71 | !(old_value & PCI_PM_CTRL_NO_SOFT_RESET)) |
---|
72 | pci_restore_bars(dev); |
---|
73 | |
---|
74 | out: |
---|
75 | return err; |
---|
76 | } |
---|
77 | |
---|
78 | /* Ensure PMEs are disabled */ |
---|
79 | static void *pm_ctrl_init(struct pci_dev *dev, int offset) |
---|
80 | { |
---|
81 | int err; |
---|
82 | u16 value; |
---|
83 | |
---|
84 | err = pci_read_config_word(dev, offset, &value); |
---|
85 | if (err) |
---|
86 | goto out; |
---|
87 | |
---|
88 | if (value & PCI_PM_CTRL_PME_ENABLE) { |
---|
89 | value &= ~PCI_PM_CTRL_PME_ENABLE; |
---|
90 | err = pci_write_config_word(dev, offset, value); |
---|
91 | } |
---|
92 | |
---|
93 | out: |
---|
94 | return ERR_PTR(err); |
---|
95 | } |
---|
96 | |
---|
97 | static struct config_field caplist_pm[] = { |
---|
98 | { |
---|
99 | .offset = PCI_PM_PMC, |
---|
100 | .size = 2, |
---|
101 | .u.w.read = pm_caps_read, |
---|
102 | }, |
---|
103 | { |
---|
104 | .offset = PCI_PM_CTRL, |
---|
105 | .size = 2, |
---|
106 | .init = pm_ctrl_init, |
---|
107 | .u.w.read = pciback_read_config_word, |
---|
108 | .u.w.write = pm_ctrl_write, |
---|
109 | }, |
---|
110 | { |
---|
111 | .offset = PCI_PM_PPB_EXTENSIONS, |
---|
112 | .size = 1, |
---|
113 | .u.b.read = pciback_read_config_byte, |
---|
114 | }, |
---|
115 | { |
---|
116 | .offset = PCI_PM_DATA_REGISTER, |
---|
117 | .size = 1, |
---|
118 | .u.b.read = pciback_read_config_byte, |
---|
119 | }, |
---|
120 | { |
---|
121 | .size = 0, |
---|
122 | }, |
---|
123 | }; |
---|
124 | |
---|
125 | struct pciback_config_capability pciback_config_capability_pm = { |
---|
126 | .capability = PCI_CAP_ID_PM, |
---|
127 | .fields = caplist_pm, |
---|
128 | }; |
---|