Kevin O'Connor | 061d137 | 2008-06-11 22:39:46 -0400 | [diff] [blame] | 1 | // 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'Connor | b1b7c2a | 2009-01-15 20:52:58 -0500 | [diff] [blame] | 6 | // This file may be distributed under the terms of the GNU LGPLv3 license. |
Kevin O'Connor | 061d137 | 2008-06-11 22:39:46 -0400 | [diff] [blame] | 7 | |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 8 | #include "pci.h" // pci_config_writel |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 9 | #include "ioport.h" // outl |
Kevin O'Connor | db03d5d | 2008-06-21 11:55:29 -0400 | [diff] [blame] | 10 | #include "util.h" // dprintf |
Kevin O'Connor | cde7a58 | 2008-08-17 11:08:46 -0400 | [diff] [blame] | 11 | #include "config.h" // CONFIG_* |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 12 | #include "farptr.h" // CONFIG_* |
Kevin O'Connor | 2ed2f58 | 2008-11-08 15:53:36 -0500 | [diff] [blame] | 13 | #include "pci_regs.h" // PCI_VENDOR_ID |
Kevin O'Connor | 04eece2 | 2009-07-19 19:05:30 -0400 | [diff] [blame] | 14 | #include "pci_ids.h" // PCI_CLASS_DISPLAY_VGA |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 15 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 16 | void pci_config_writel(u16 bdf, u32 addr, u32 val) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 17 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 18 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 19 | outl(val, PORT_PCI_DATA); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 20 | } |
| 21 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 22 | void pci_config_writew(u16 bdf, u32 addr, u16 val) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 23 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 24 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 25 | outw(val, PORT_PCI_DATA + (addr & 2)); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 26 | } |
| 27 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 28 | void pci_config_writeb(u16 bdf, u32 addr, u8 val) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 29 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 30 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 31 | outb(val, PORT_PCI_DATA + (addr & 3)); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 32 | } |
| 33 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 34 | u32 pci_config_readl(u16 bdf, u32 addr) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 35 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 36 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 37 | return inl(PORT_PCI_DATA); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 38 | } |
| 39 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 40 | u16 pci_config_readw(u16 bdf, u32 addr) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 41 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 42 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 43 | return inw(PORT_PCI_DATA + (addr & 2)); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 44 | } |
| 45 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 46 | u8 pci_config_readb(u16 bdf, u32 addr) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 47 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 48 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 49 | return inb(PORT_PCI_DATA + (addr & 3)); |
| 50 | } |
| 51 | |
Kevin O'Connor | 59f0283 | 2009-10-12 10:09:15 -0400 | [diff] [blame] | 52 | void |
| 53 | pci_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'Connor | baac6b6 | 2011-06-19 10:46:28 -0400 | [diff] [blame] | 60 | // Helper function for foreachbdf() macro - return next device |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 61 | int |
| 62 | pci_next(int bdf, int *pmax) |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 63 | { |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 64 | 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'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 69 | |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 70 | int max = *pmax; |
| 71 | for (;;) { |
Kevin O'Connor | 6707c30 | 2009-02-28 12:26:39 -0500 | [diff] [blame] | 72 | 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'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 81 | |
| 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'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 97 | v = pci_config_readl(bdf, PCI_PRIMARY_BUS); |
| 98 | int newmax = (v & 0xff00) + 0x0100; |
| 99 | if (newmax > max) |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 100 | *pmax = newmax; |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 101 | } |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 102 | |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 103 | return bdf; |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 104 | } |
| 105 | |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 106 | struct pci_device *PCIDevices; |
| 107 | int MaxPCIBus; |
| 108 | |
| 109 | void |
| 110 | pci_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'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 166 | // Search for a device with the specified vendor and device ids. |
| 167 | int |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 168 | pci_find_device(u16 vendid, u16 devid) |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 169 | { |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 170 | u32 id = (devid << 16) | vendid; |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 171 | int bdf, max; |
Kevin O'Connor | baac6b6 | 2011-06-19 10:46:28 -0400 | [diff] [blame] | 172 | foreachbdf(bdf, max) { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 173 | u32 v = pci_config_readl(bdf, PCI_VENDOR_ID); |
Kevin O'Connor | 59f0283 | 2009-10-12 10:09:15 -0400 | [diff] [blame] | 174 | if (v == id) |
| 175 | return bdf; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 176 | } |
| 177 | return -1; |
| 178 | } |
| 179 | |
Kevin O'Connor | 5fdaa03 | 2008-08-31 11:06:27 -0400 | [diff] [blame] | 180 | // Search for a device with the specified class id. |
| 181 | int |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 182 | pci_find_class(u16 classid) |
Kevin O'Connor | 5fdaa03 | 2008-08-31 11:06:27 -0400 | [diff] [blame] | 183 | { |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 184 | int bdf, max; |
Kevin O'Connor | baac6b6 | 2011-06-19 10:46:28 -0400 | [diff] [blame] | 185 | foreachbdf(bdf, max) { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 186 | u16 v = pci_config_readw(bdf, PCI_CLASS_DEVICE); |
Kevin O'Connor | 59f0283 | 2009-10-12 10:09:15 -0400 | [diff] [blame] | 187 | if (v == classid) |
| 188 | return bdf; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 189 | } |
| 190 | return -1; |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 191 | } |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 192 | |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 193 | int *PCIpaths; |
| 194 | |
| 195 | // Build the PCI path designations. |
| 196 | void |
| 197 | pci_path_setup(void) |
| 198 | { |
| 199 | PCIpaths = malloc_tmp(sizeof(*PCIpaths) * 256); |
| 200 | if (!PCIpaths) |
| 201 | return; |
Kevin O'Connor | 6fc7cf1 | 2011-01-22 10:53:08 -0500 | [diff] [blame] | 202 | memset(PCIpaths, 0, sizeof(*PCIpaths) * 256); |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 203 | |
| 204 | int roots = 0; |
| 205 | int bdf, max; |
Kevin O'Connor | baac6b6 | 2011-06-19 10:46:28 -0400 | [diff] [blame] | 206 | foreachbdf(bdf, max) { |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 207 | 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'Connor | 6fc7cf1 | 2011-01-22 10:53:08 -0500 | [diff] [blame] | 217 | if (childbus > bus) |
| 218 | PCIpaths[childbus] = bdf | PP_PCIBRIDGE; |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 223 | int 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 Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 242 | |
| 243 | int pci_find_init_device(const struct pci_device_id *ids, void *arg) |
| 244 | { |
| 245 | int bdf, max; |
| 246 | |
Kevin O'Connor | baac6b6 | 2011-06-19 10:46:28 -0400 | [diff] [blame] | 247 | foreachbdf(bdf, max) { |
Isaku Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 248 | if (pci_init_device(ids, bdf, arg) == 0) { |
| 249 | return bdf; |
| 250 | } |
| 251 | } |
| 252 | return -1; |
| 253 | } |
Kevin O'Connor | adaf373 | 2010-09-13 20:22:07 -0400 | [diff] [blame] | 254 | |
| 255 | void |
| 256 | pci_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 Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 264 | |
| 265 | // helper functions to access pci mmio bars from real mode |
| 266 | |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 267 | u32 VISIBLE32FLAT |
| 268 | pci_readl_32(u32 addr) |
| 269 | { |
| 270 | dprintf(3, "32: pci read : %x\n", addr); |
| 271 | return readl((void*)addr); |
| 272 | } |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 273 | |
| 274 | u32 pci_readl(u32 addr) |
| 275 | { |
| 276 | if (MODESEGMENT) { |
| 277 | dprintf(3, "16: pci read : %x\n", addr); |
Kevin O'Connor | f3fe3aa | 2010-12-05 12:38:33 -0500 | [diff] [blame] | 278 | extern void _cfunc32flat_pci_readl_32(u32 addr); |
| 279 | return call32(_cfunc32flat_pci_readl_32, addr, -1); |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 280 | } else { |
| 281 | return pci_readl_32(addr); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | struct reg32 { |
| 286 | u32 addr; |
| 287 | u32 data; |
| 288 | }; |
| 289 | |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 290 | void VISIBLE32FLAT |
| 291 | pci_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 Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 296 | |
| 297 | void 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), ®32); |
| 303 | void *flatptr = MAKE_FLATPTR(GET_SEG(SS), ®32); |
Kevin O'Connor | f3fe3aa | 2010-12-05 12:38:33 -0500 | [diff] [blame] | 304 | extern void _cfunc32flat_pci_writel_32(struct reg32 *reg32); |
| 305 | call32(_cfunc32flat_pci_writel_32, (u32)flatptr, -1); |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 306 | } else { |
| 307 | pci_writel_32(®32); |
| 308 | } |
| 309 | } |