Add support for enabling PCIe Common Clock and ASPM
[coreboot.git] / src / devices / pciexp_device.c
1 /*
2  * This file is part of the coreboot project.
3  *
4  * Copyright (C) 2005 Linux Networx
5  * (Written by Eric Biederman <ebiederman@lnxi.com> for Linux Networx)
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; version 2 of the License.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  */
20
21 #include <console/console.h>
22 #include <delay.h>
23 #include <device/device.h>
24 #include <device/pci.h>
25 #include <device/pci_ids.h>
26 #include <device/pciexp.h>
27
28 #if CONFIG_PCIEXP_COMMON_CLOCK
29 /*
30  * Re-train a PCIe link
31  */
32 #define PCIE_TRAIN_RETRY 10000
33 static int pciexp_retrain_link(device_t dev, unsigned cap)
34 {
35         unsigned try = PCIE_TRAIN_RETRY;
36         u16 lnk;
37
38         /* Start link retraining */
39         lnk = pci_read_config16(dev, cap + PCI_EXP_LNKCTL);
40         lnk |= PCI_EXP_LNKCTL_RL;
41         pci_write_config16(dev, cap + PCI_EXP_LNKCTL, lnk);
42
43         /* Wait for training to complete */
44         while (try--) {
45                 lnk = pci_read_config16(dev, cap + PCI_EXP_LNKSTA);
46                 if (!(lnk & PCI_EXP_LNKSTA_LT))
47                         return 0;
48                 udelay(100);
49         }
50
51         printk(BIOS_ERR, "%s: Link Retrain timeout\n", dev_path(dev));
52         return -1;
53 }
54
55 /*
56  * Check the Slot Clock Configuration for root port and endpoint
57  * and enable Common Clock Configuration if possible.  If CCC is
58  * enabled the link must be retrained.
59  */
60 static void pciexp_enable_common_clock(device_t root, unsigned root_cap,
61                                        device_t endp, unsigned endp_cap)
62 {
63         u16 root_scc, endp_scc, lnkctl;
64
65         /* Get Slot Clock Configuration for root port */
66         root_scc = pci_read_config16(root, root_cap + PCI_EXP_LNKSTA);
67         root_scc &= PCI_EXP_LNKSTA_SLC;
68
69         /* Get Slot Clock Configuration for endpoint */
70         endp_scc = pci_read_config16(endp, endp_cap + PCI_EXP_LNKSTA);
71         endp_scc &= PCI_EXP_LNKSTA_SLC;
72
73         /* Enable Common Clock Configuration and retrain */
74         if (root_scc && endp_scc) {
75                 printk(BIOS_INFO, "Enabling Common Clock Configuration\n");
76
77                 /* Set in endpoint */
78                 lnkctl = pci_read_config16(endp, endp_cap + PCI_EXP_LNKCTL);
79                 lnkctl |= PCI_EXP_LNKCTL_CCC;
80                 pci_write_config16(endp, endp_cap + PCI_EXP_LNKCTL, lnkctl);
81
82                 /* Set in root port */
83                 lnkctl = pci_read_config16(root, root_cap + PCI_EXP_LNKCTL);
84                 lnkctl |= PCI_EXP_LNKCTL_CCC;
85                 pci_write_config16(root, root_cap + PCI_EXP_LNKCTL, lnkctl);
86
87                 /* Retrain link if CCC was enabled */
88                 pciexp_retrain_link(root, root_cap);
89         }
90 }
91 #endif /* CONFIG_PCIEXP_COMMON_CLOCK */
92
93 #if CONFIG_PCIEXP_ASPM
94 /*
95  * Determine the ASPM L0s or L1 exit latency for a link
96  * by checking both root port and endpoint and returning
97  * the highest latency value.
98  */
99 static int pciexp_aspm_latency(device_t root, unsigned root_cap,
100                                device_t endp, unsigned endp_cap,
101                                enum aspm_type type)
102 {
103         int root_lat = 0, endp_lat = 0;
104         u32 root_lnkcap, endp_lnkcap;
105
106         root_lnkcap = pci_read_config32(root, root_cap + PCI_EXP_LNKCAP);
107         endp_lnkcap = pci_read_config32(endp, endp_cap + PCI_EXP_LNKCAP);
108
109         /* Make sure the link supports this ASPM type by checking
110          * capability bits 11:10 with aspm_type offset by 1 */
111         if (!(root_lnkcap & (1 << (type + 9))) ||
112             !(endp_lnkcap & (1 << (type + 9))))
113                 return -1;
114
115         /* Find the one with higher latency */
116         switch (type) {
117         case PCIE_ASPM_L0S:
118                 root_lat = (root_lnkcap & PCI_EXP_LNKCAP_L0SEL) >> 12;
119                 endp_lat = (endp_lnkcap & PCI_EXP_LNKCAP_L0SEL) >> 12;
120                 break;
121         case PCIE_ASPM_L1:
122                 root_lat = (root_lnkcap & PCI_EXP_LNKCAP_L1EL) >> 15;
123                 endp_lat = (endp_lnkcap & PCI_EXP_LNKCAP_L1EL) >> 15;
124                 break;
125         default:
126                 return -1;
127         }
128
129         return (endp_lat > root_lat) ? endp_lat : root_lat;
130 }
131
132 /*
133  * Enable ASPM on PCIe root port and endpoint.
134  *
135  * Returns APMC value:
136  *   -1 = Error
137  *    0 = no ASPM
138  *    1 = L0s Enabled
139  *    2 = L1 Enabled
140  *    3 = L0s and L1 Enabled
141  */
142 static enum aspm_type pciexp_enable_aspm(device_t root, unsigned root_cap,
143                                          device_t endp, unsigned endp_cap)
144 {
145         const char *aspm_type_str[] = { "None", "L0s", "L1", "L0s and L1" };
146         enum aspm_type apmc = PCIE_ASPM_NONE;
147         int exit_latency, ok_latency;
148         u16 lnkctl;
149         u32 devcap;
150
151         /* Get endpoint device capabilities for acceptable limits */
152         devcap = pci_read_config32(endp, endp_cap + PCI_EXP_DEVCAP);
153
154         /* Enable L0s if it is within endpoint acceptable limit */
155         ok_latency = (devcap & PCI_EXP_DEVCAP_L0S) >> 6;
156         exit_latency = pciexp_aspm_latency(root, root_cap, endp, endp_cap,
157                                            PCIE_ASPM_L0S);
158         if (exit_latency >= 0 && exit_latency <= ok_latency)
159                 apmc |= PCIE_ASPM_L0S;
160
161         /* Enable L1 if it is within endpoint acceptable limit */
162         ok_latency = (devcap & PCI_EXP_DEVCAP_L1) >> 9;
163         exit_latency = pciexp_aspm_latency(root, root_cap, endp, endp_cap,
164                                            PCIE_ASPM_L1);
165         if (exit_latency >= 0 && exit_latency <= ok_latency)
166                 apmc |= PCIE_ASPM_L1;
167
168         if (apmc != PCIE_ASPM_NONE) {
169                 /* Set APMC in root port first */
170                 lnkctl = pci_read_config16(root, root_cap + PCI_EXP_LNKCTL);
171                 lnkctl |= apmc;
172                 pci_write_config16(root, root_cap + PCI_EXP_LNKCTL, lnkctl);
173
174                 /* Set APMC in endpoint device next */
175                 lnkctl = pci_read_config16(endp, endp_cap + PCI_EXP_LNKCTL);
176                 lnkctl |= apmc;
177                 pci_write_config16(endp, endp_cap + PCI_EXP_LNKCTL, lnkctl);
178         }
179
180         printk(BIOS_INFO, "ASPM: Enabled %s\n", aspm_type_str[apmc]);
181         return apmc;
182 }
183 #endif /* CONFIG_PCIEXP_ASPM */
184
185 static void pciexp_tune_dev(device_t dev)
186 {
187         device_t root = dev->bus->dev;
188         unsigned int root_cap, cap;
189
190         cap = pci_find_capability(dev, PCI_CAP_ID_PCIE);
191         if (!cap)
192                 return;
193
194         root_cap = pci_find_capability(root, PCI_CAP_ID_PCIE);
195         if (!root_cap)
196                 return;
197
198 #if CONFIG_PCIEXP_COMMON_CLOCK
199         /* Check for and enable Common Clock */
200         pciexp_enable_common_clock(root, root_cap, dev, cap);
201 #endif
202
203 #if CONFIG_PCIEXP_ASPM
204         /* Check for and enable ASPM */
205         enum aspm_type apmc = pciexp_enable_aspm(root, root_cap, dev, cap);
206
207         if (apmc != PCIE_ASPM_NONE) {
208                 /* Enable ASPM role based error reporting. */
209                 u32 reg32 = pci_read_config32(dev, cap + PCI_EXP_DEVCAP);
210                 reg32 |= PCI_EXP_DEVCAP_RBER;
211                 pci_write_config32(dev, cap + PCI_EXP_DEVCAP, reg32);
212         }
213 #endif
214 }
215
216 unsigned int pciexp_scan_bus(struct bus *bus, unsigned int min_devfn,
217                              unsigned int max_devfn, unsigned int max)
218 {
219         device_t child;
220
221         max = pci_scan_bus(bus, min_devfn, max_devfn, max);
222
223         for (child = bus->children; child; child = child->sibling) {
224                 if ((child->path.pci.devfn < min_devfn) ||
225                     (child->path.pci.devfn > max_devfn)) {
226                         continue;
227                 }
228                 pciexp_tune_dev(child);
229         }
230         return max;
231 }
232
233 unsigned int pciexp_scan_bridge(device_t dev, unsigned int max)
234 {
235         return do_pci_scan_bridge(dev, max, pciexp_scan_bus);
236 }
237
238 /** Default device operations for PCI Express bridges */
239 static struct pci_operations pciexp_bus_ops_pci = {
240         .set_subsystem = 0,
241 };
242
243 struct device_operations default_pciexp_ops_bus = {
244         .read_resources   = pci_bus_read_resources,
245         .set_resources    = pci_dev_set_resources,
246         .enable_resources = pci_bus_enable_resources,
247         .init             = 0,
248         .scan_bus         = pciexp_scan_bridge,
249         .enable           = 0,
250         .reset_bus        = pci_bus_reset,
251         .ops_pci          = &pciexp_bus_ops_pci,
252 };