blob: 9a696b9052394a943ac62a486f8b94041bce89df [file] [log] [blame]
Kevin O'Connor061d1372008-06-11 22:39:46 -04001// PCI config space access functions.
2//
3// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
4// Copyright (C) 2002 MandrakeSoft S.A.
5//
Kevin O'Connorb1b7c2a2009-01-15 20:52:58 -05006// This file may be distributed under the terms of the GNU LGPLv3 license.
Kevin O'Connor061d1372008-06-11 22:39:46 -04007
Kevin O'Connore6338322008-11-29 20:31:49 -05008#include "pci.h" // pci_config_writel
Kevin O'Connora0dc2962008-03-16 14:29:32 -04009#include "ioport.h" // outl
Kevin O'Connordb03d5d2008-06-21 11:55:29 -040010#include "util.h" // dprintf
Kevin O'Connorcde7a582008-08-17 11:08:46 -040011#include "config.h" // CONFIG_*
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +010012#include "farptr.h" // CONFIG_*
Kevin O'Connor2ed2f582008-11-08 15:53:36 -050013#include "pci_regs.h" // PCI_VENDOR_ID
Kevin O'Connor04eece22009-07-19 19:05:30 -040014#include "pci_ids.h" // PCI_CLASS_DISPLAY_VGA
Kevin O'Connora0dc2962008-03-16 14:29:32 -040015
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050016void pci_config_writel(u16 bdf, u32 addr, u32 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040017{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050018 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040019 outl(val, PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040020}
21
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050022void pci_config_writew(u16 bdf, u32 addr, u16 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040023{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050024 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040025 outw(val, PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040026}
27
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050028void pci_config_writeb(u16 bdf, u32 addr, u8 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040029{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050030 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040031 outb(val, PORT_PCI_DATA + (addr & 3));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040032}
33
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050034u32 pci_config_readl(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040035{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050036 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040037 return inl(PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040038}
39
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050040u16 pci_config_readw(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040041{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050042 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040043 return inw(PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040044}
45
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050046u8 pci_config_readb(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040047{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050048 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040049 return inb(PORT_PCI_DATA + (addr & 3));
50}
51
Kevin O'Connor59f02832009-10-12 10:09:15 -040052void
53pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on)
54{
55 u16 val = pci_config_readw(bdf, addr);
56 val = (val & ~off) | on;
57 pci_config_writew(bdf, addr, val);
58}
59
Kevin O'Connorbaac6b62011-06-19 10:46:28 -040060// Helper function for foreachbdf() macro - return next device
Kevin O'Connore6338322008-11-29 20:31:49 -050061int
62pci_next(int bdf, int *pmax)
Kevin O'Connor0f803e42008-05-24 23:07:16 -040063{
Kevin O'Connore6338322008-11-29 20:31:49 -050064 if (pci_bdf_to_fn(bdf) == 1
65 && (pci_config_readb(bdf-1, PCI_HEADER_TYPE) & 0x80) == 0)
66 // Last found device wasn't a multi-function device - skip to
67 // the next device.
68 bdf += 7;
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050069
Kevin O'Connore6338322008-11-29 20:31:49 -050070 int max = *pmax;
71 for (;;) {
Kevin O'Connor6707c302009-02-28 12:26:39 -050072 if (bdf >= max) {
73 if (CONFIG_PCI_ROOT1 && bdf <= (CONFIG_PCI_ROOT1 << 8))
74 bdf = CONFIG_PCI_ROOT1 << 8;
75 else if (CONFIG_PCI_ROOT2 && bdf <= (CONFIG_PCI_ROOT2 << 8))
76 bdf = CONFIG_PCI_ROOT2 << 8;
77 else
78 return -1;
79 *pmax = max = bdf + 0x0100;
80 }
Kevin O'Connore6338322008-11-29 20:31:49 -050081
82 u16 v = pci_config_readw(bdf, PCI_VENDOR_ID);
83 if (v != 0x0000 && v != 0xffff)
84 // Device is present.
85 break;
86
87 if (pci_bdf_to_fn(bdf) == 0)
88 bdf += 8;
89 else
90 bdf += 1;
91 }
92
93 // Check if found device is a bridge.
94 u32 v = pci_config_readb(bdf, PCI_HEADER_TYPE);
95 v &= 0x7f;
96 if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) {
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050097 v = pci_config_readl(bdf, PCI_PRIMARY_BUS);
98 int newmax = (v & 0xff00) + 0x0100;
99 if (newmax > max)
Kevin O'Connore6338322008-11-29 20:31:49 -0500100 *pmax = newmax;
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500101 }
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500102
Kevin O'Connore6338322008-11-29 20:31:49 -0500103 return bdf;
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500104}
105
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400106struct pci_device *PCIDevices;
107int MaxPCIBus;
108
109void
110pci_probe(void)
111{
112 int rootbuses = 0;
113 struct pci_device *busdevs[256];
114 memset(busdevs, 0, sizeof(busdevs));
115
116 struct pci_device **pprev = &PCIDevices;
117 u8 lastbus = 0;
118 int bdf, max;
119 foreachbdf(bdf, max) {
120 // Create new pci_device struct and add to list.
121 struct pci_device *dev = malloc_tmp(sizeof(*dev));
122 if (!dev) {
123 warn_noalloc();
124 return;
125 }
126 memset(dev, 0, sizeof(*dev));
127 *pprev = dev;
128 pprev = &dev->next;
129
130 // Find parent device.
131 u8 bus = pci_bdf_to_bus(bdf), rootbus;
132 struct pci_device *parent = busdevs[bus];
133 if (!parent) {
134 if (bus != lastbus)
135 rootbuses++;
136 lastbus = bus;
137 rootbus = rootbuses;
138 } else {
139 rootbus = parent->rootbus;
140 }
141 if (bus > MaxPCIBus)
142 MaxPCIBus = bus;
143
144 // Populate pci_device info.
145 dev->bdf = bdf;
146 dev->parent = parent;
147 dev->rootbus = rootbus;
148 u32 vendev = pci_config_readl(bdf, PCI_VENDOR_ID);
149 dev->vendor = vendev & 0xffff;
150 dev->device = vendev >> 16;
151 u32 classrev = pci_config_readl(bdf, PCI_CLASS_REVISION);
152 dev->class = classrev >> 16;
153 dev->prog_if = classrev >> 8;
154 dev->revision = classrev & 0xff;
155 dev->header_type = pci_config_readb(bdf, PCI_HEADER_TYPE);
156 u8 v = dev->header_type & 0x7f;
157 if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) {
158 u8 secbus = pci_config_readb(bdf, PCI_SECONDARY_BUS);
159 dev->secondary_bus = secbus;
160 if (secbus > bus && !busdevs[secbus])
161 busdevs[secbus] = dev;
162 }
163 }
164}
165
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500166// Search for a device with the specified vendor and device ids.
167int
Kevin O'Connor4132e022008-12-04 19:39:10 -0500168pci_find_device(u16 vendid, u16 devid)
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500169{
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400170 u32 id = (devid << 16) | vendid;
Kevin O'Connore6338322008-11-29 20:31:49 -0500171 int bdf, max;
Kevin O'Connorbaac6b62011-06-19 10:46:28 -0400172 foreachbdf(bdf, max) {
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500173 u32 v = pci_config_readl(bdf, PCI_VENDOR_ID);
Kevin O'Connor59f02832009-10-12 10:09:15 -0400174 if (v == id)
175 return bdf;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400176 }
177 return -1;
178}
179
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400180// Search for a device with the specified class id.
181int
Kevin O'Connor4132e022008-12-04 19:39:10 -0500182pci_find_class(u16 classid)
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400183{
Kevin O'Connore6338322008-11-29 20:31:49 -0500184 int bdf, max;
Kevin O'Connorbaac6b62011-06-19 10:46:28 -0400185 foreachbdf(bdf, max) {
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500186 u16 v = pci_config_readw(bdf, PCI_CLASS_DEVICE);
Kevin O'Connor59f02832009-10-12 10:09:15 -0400187 if (v == classid)
188 return bdf;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400189 }
190 return -1;
Kevin O'Connora0dc2962008-03-16 14:29:32 -0400191}
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900192
Kevin O'Connord08eb9c2011-01-10 00:48:45 -0500193int *PCIpaths;
194
195// Build the PCI path designations.
196void
197pci_path_setup(void)
198{
199 PCIpaths = malloc_tmp(sizeof(*PCIpaths) * 256);
200 if (!PCIpaths)
201 return;
Kevin O'Connor6fc7cf12011-01-22 10:53:08 -0500202 memset(PCIpaths, 0, sizeof(*PCIpaths) * 256);
Kevin O'Connord08eb9c2011-01-10 00:48:45 -0500203
204 int roots = 0;
205 int bdf, max;
Kevin O'Connorbaac6b62011-06-19 10:46:28 -0400206 foreachbdf(bdf, max) {
Kevin O'Connord08eb9c2011-01-10 00:48:45 -0500207 int bus = pci_bdf_to_bus(bdf);
208 if (! PCIpaths[bus])
209 PCIpaths[bus] = (roots++) | PP_ROOT;
210
211 // Check if found device is a bridge.
212 u32 v = pci_config_readb(bdf, PCI_HEADER_TYPE);
213 v &= 0x7f;
214 if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) {
215 v = pci_config_readl(bdf, PCI_PRIMARY_BUS);
216 int childbus = (v >> 8) & 0xff;
Kevin O'Connor6fc7cf12011-01-22 10:53:08 -0500217 if (childbus > bus)
218 PCIpaths[childbus] = bdf | PP_PCIBRIDGE;
Kevin O'Connord08eb9c2011-01-10 00:48:45 -0500219 }
220 }
221}
222
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900223int pci_init_device(const struct pci_device_id *ids, u16 bdf, void *arg)
224{
225 u16 vendor_id = pci_config_readw(bdf, PCI_VENDOR_ID);
226 u16 device_id = pci_config_readw(bdf, PCI_DEVICE_ID);
227 u16 class = pci_config_readw(bdf, PCI_CLASS_DEVICE);
228
229 while (ids->vendid || ids->class_mask) {
230 if ((ids->vendid == PCI_ANY_ID || ids->vendid == vendor_id) &&
231 (ids->devid == PCI_ANY_ID || ids->devid == device_id) &&
232 !((ids->class ^ class) & ids->class_mask)) {
233 if (ids->func) {
234 ids->func(bdf, arg);
235 }
236 return 0;
237 }
238 ids++;
239 }
240 return -1;
241}
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900242
243int pci_find_init_device(const struct pci_device_id *ids, void *arg)
244{
245 int bdf, max;
246
Kevin O'Connorbaac6b62011-06-19 10:46:28 -0400247 foreachbdf(bdf, max) {
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900248 if (pci_init_device(ids, bdf, arg) == 0) {
249 return bdf;
250 }
251 }
252 return -1;
253}
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400254
255void
256pci_reboot(void)
257{
258 u8 v = inb(PORT_PCI_REBOOT) & ~6;
259 outb(v|2, PORT_PCI_REBOOT); /* Request hard reset */
260 udelay(50);
261 outb(v|6, PORT_PCI_REBOOT); /* Actually do the reset */
262 udelay(50);
263}
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100264
265// helper functions to access pci mmio bars from real mode
266
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100267u32 VISIBLE32FLAT
268pci_readl_32(u32 addr)
269{
270 dprintf(3, "32: pci read : %x\n", addr);
271 return readl((void*)addr);
272}
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100273
274u32 pci_readl(u32 addr)
275{
276 if (MODESEGMENT) {
277 dprintf(3, "16: pci read : %x\n", addr);
Kevin O'Connorf3fe3aa2010-12-05 12:38:33 -0500278 extern void _cfunc32flat_pci_readl_32(u32 addr);
279 return call32(_cfunc32flat_pci_readl_32, addr, -1);
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100280 } else {
281 return pci_readl_32(addr);
282 }
283}
284
285struct reg32 {
286 u32 addr;
287 u32 data;
288};
289
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100290void VISIBLE32FLAT
291pci_writel_32(struct reg32 *reg32)
292{
293 dprintf(3, "32: pci write: %x, %x (%p)\n", reg32->addr, reg32->data, reg32);
294 writel((void*)(reg32->addr), reg32->data);
295}
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100296
297void pci_writel(u32 addr, u32 val)
298{
299 struct reg32 reg32 = { .addr = addr, .data = val };
300 if (MODESEGMENT) {
301 dprintf(3, "16: pci write: %x, %x (%x:%p)\n",
302 reg32.addr, reg32.data, GET_SEG(SS), &reg32);
303 void *flatptr = MAKE_FLATPTR(GET_SEG(SS), &reg32);
Kevin O'Connorf3fe3aa2010-12-05 12:38:33 -0500304 extern void _cfunc32flat_pci_writel_32(struct reg32 *reg32);
305 call32(_cfunc32flat_pci_writel_32, (u32)flatptr, -1);
Gerd Hoffmannf1f18eb2010-11-29 09:42:10 +0100306 } else {
307 pci_writel_32(&reg32);
308 }
309}