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 | |
| 60 | // Helper function for foreachpci() 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 | 04eece2 | 2009-07-19 19:05:30 -0400 | [diff] [blame] | 106 | // Find a vga device with legacy address decoding enabled. |
| 107 | int |
Kevin O'Connor | 1ca05b0 | 2010-01-03 17:43:37 -0500 | [diff] [blame] | 108 | pci_find_vga(void) |
Kevin O'Connor | 04eece2 | 2009-07-19 19:05:30 -0400 | [diff] [blame] | 109 | { |
| 110 | int bdf = 0x0000, max = 0x0100; |
| 111 | for (;;) { |
| 112 | if (bdf >= max) { |
| 113 | if (CONFIG_PCI_ROOT1 && bdf <= (CONFIG_PCI_ROOT1 << 8)) |
| 114 | bdf = CONFIG_PCI_ROOT1 << 8; |
| 115 | else if (CONFIG_PCI_ROOT2 && bdf <= (CONFIG_PCI_ROOT2 << 8)) |
| 116 | bdf = CONFIG_PCI_ROOT2 << 8; |
| 117 | else |
| 118 | return -1; |
| 119 | max = bdf + 0x0100; |
| 120 | } |
| 121 | |
| 122 | u16 cls = pci_config_readw(bdf, PCI_CLASS_DEVICE); |
| 123 | if (cls == 0x0000 || cls == 0xffff) { |
| 124 | // Device not present. |
| 125 | if (pci_bdf_to_fn(bdf) == 0) |
| 126 | bdf += 8; |
| 127 | else |
| 128 | bdf += 1; |
| 129 | continue; |
| 130 | } |
| 131 | if (cls == PCI_CLASS_DISPLAY_VGA) { |
| 132 | u16 cmd = pci_config_readw(bdf, PCI_COMMAND); |
| 133 | if (cmd & PCI_COMMAND_IO && cmd & PCI_COMMAND_MEMORY) |
| 134 | // Found active vga card |
| 135 | return bdf; |
| 136 | } |
| 137 | |
| 138 | // Check if device is a bridge. |
| 139 | u8 hdr = pci_config_readb(bdf, PCI_HEADER_TYPE); |
| 140 | u8 ht = hdr & 0x7f; |
| 141 | if (ht == PCI_HEADER_TYPE_BRIDGE || ht == PCI_HEADER_TYPE_CARDBUS) { |
| 142 | u32 ctrl = pci_config_readb(bdf, PCI_BRIDGE_CONTROL); |
| 143 | if (ctrl & PCI_BRIDGE_CTL_VGA) { |
| 144 | // Found a VGA enabled bridge. |
| 145 | u32 pbus = pci_config_readl(bdf, PCI_PRIMARY_BUS); |
| 146 | bdf = (pbus & 0xff00); |
| 147 | max = bdf + 0x100; |
| 148 | continue; |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | if (pci_bdf_to_fn(bdf) == 0 && (hdr & 0x80) == 0) |
| 153 | // Last found device wasn't a multi-function device - skip to |
| 154 | // the next device. |
| 155 | bdf += 8; |
| 156 | else |
| 157 | bdf += 1; |
| 158 | } |
| 159 | } |
| 160 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 161 | // Search for a device with the specified vendor and device ids. |
| 162 | int |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 163 | pci_find_device(u16 vendid, u16 devid) |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 164 | { |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 165 | u32 id = (devid << 16) | vendid; |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 166 | int bdf, max; |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 167 | foreachpci(bdf, max) { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 168 | u32 v = pci_config_readl(bdf, PCI_VENDOR_ID); |
Kevin O'Connor | 59f0283 | 2009-10-12 10:09:15 -0400 | [diff] [blame] | 169 | if (v == id) |
| 170 | return bdf; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 171 | } |
| 172 | return -1; |
| 173 | } |
| 174 | |
Kevin O'Connor | 5fdaa03 | 2008-08-31 11:06:27 -0400 | [diff] [blame] | 175 | // Search for a device with the specified class id. |
| 176 | int |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 177 | pci_find_class(u16 classid) |
Kevin O'Connor | 5fdaa03 | 2008-08-31 11:06:27 -0400 | [diff] [blame] | 178 | { |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 179 | int bdf, max; |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 180 | foreachpci(bdf, max) { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 181 | u16 v = pci_config_readw(bdf, PCI_CLASS_DEVICE); |
Kevin O'Connor | 59f0283 | 2009-10-12 10:09:15 -0400 | [diff] [blame] | 182 | if (v == classid) |
| 183 | return bdf; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 184 | } |
| 185 | return -1; |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 186 | } |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 187 | |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 188 | int *PCIpaths; |
| 189 | |
| 190 | // Build the PCI path designations. |
| 191 | void |
| 192 | pci_path_setup(void) |
| 193 | { |
| 194 | PCIpaths = malloc_tmp(sizeof(*PCIpaths) * 256); |
| 195 | if (!PCIpaths) |
| 196 | return; |
Kevin O'Connor | 6fc7cf1 | 2011-01-22 10:53:08 -0500 | [diff] [blame^] | 197 | memset(PCIpaths, 0, sizeof(*PCIpaths) * 256); |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 198 | |
| 199 | int roots = 0; |
| 200 | int bdf, max; |
| 201 | foreachpci(bdf, max) { |
| 202 | int bus = pci_bdf_to_bus(bdf); |
| 203 | if (! PCIpaths[bus]) |
| 204 | PCIpaths[bus] = (roots++) | PP_ROOT; |
| 205 | |
| 206 | // Check if found device is a bridge. |
| 207 | u32 v = pci_config_readb(bdf, PCI_HEADER_TYPE); |
| 208 | v &= 0x7f; |
| 209 | if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) { |
| 210 | v = pci_config_readl(bdf, PCI_PRIMARY_BUS); |
| 211 | int childbus = (v >> 8) & 0xff; |
Kevin O'Connor | 6fc7cf1 | 2011-01-22 10:53:08 -0500 | [diff] [blame^] | 212 | if (childbus > bus) |
| 213 | PCIpaths[childbus] = bdf | PP_PCIBRIDGE; |
Kevin O'Connor | d08eb9c | 2011-01-10 00:48:45 -0500 | [diff] [blame] | 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 218 | int pci_init_device(const struct pci_device_id *ids, u16 bdf, void *arg) |
| 219 | { |
| 220 | u16 vendor_id = pci_config_readw(bdf, PCI_VENDOR_ID); |
| 221 | u16 device_id = pci_config_readw(bdf, PCI_DEVICE_ID); |
| 222 | u16 class = pci_config_readw(bdf, PCI_CLASS_DEVICE); |
| 223 | |
| 224 | while (ids->vendid || ids->class_mask) { |
| 225 | if ((ids->vendid == PCI_ANY_ID || ids->vendid == vendor_id) && |
| 226 | (ids->devid == PCI_ANY_ID || ids->devid == device_id) && |
| 227 | !((ids->class ^ class) & ids->class_mask)) { |
| 228 | if (ids->func) { |
| 229 | ids->func(bdf, arg); |
| 230 | } |
| 231 | return 0; |
| 232 | } |
| 233 | ids++; |
| 234 | } |
| 235 | return -1; |
| 236 | } |
Isaku Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 237 | |
| 238 | int pci_find_init_device(const struct pci_device_id *ids, void *arg) |
| 239 | { |
| 240 | int bdf, max; |
| 241 | |
| 242 | foreachpci(bdf, max) { |
| 243 | if (pci_init_device(ids, bdf, arg) == 0) { |
| 244 | return bdf; |
| 245 | } |
| 246 | } |
| 247 | return -1; |
| 248 | } |
Kevin O'Connor | adaf373 | 2010-09-13 20:22:07 -0400 | [diff] [blame] | 249 | |
| 250 | void |
| 251 | pci_reboot(void) |
| 252 | { |
| 253 | u8 v = inb(PORT_PCI_REBOOT) & ~6; |
| 254 | outb(v|2, PORT_PCI_REBOOT); /* Request hard reset */ |
| 255 | udelay(50); |
| 256 | outb(v|6, PORT_PCI_REBOOT); /* Actually do the reset */ |
| 257 | udelay(50); |
| 258 | } |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 259 | |
| 260 | // helper functions to access pci mmio bars from real mode |
| 261 | |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 262 | u32 VISIBLE32FLAT |
| 263 | pci_readl_32(u32 addr) |
| 264 | { |
| 265 | dprintf(3, "32: pci read : %x\n", addr); |
| 266 | return readl((void*)addr); |
| 267 | } |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 268 | |
| 269 | u32 pci_readl(u32 addr) |
| 270 | { |
| 271 | if (MODESEGMENT) { |
| 272 | dprintf(3, "16: pci read : %x\n", addr); |
Kevin O'Connor | f3fe3aa | 2010-12-05 12:38:33 -0500 | [diff] [blame] | 273 | extern void _cfunc32flat_pci_readl_32(u32 addr); |
| 274 | return call32(_cfunc32flat_pci_readl_32, addr, -1); |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 275 | } else { |
| 276 | return pci_readl_32(addr); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | struct reg32 { |
| 281 | u32 addr; |
| 282 | u32 data; |
| 283 | }; |
| 284 | |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 285 | void VISIBLE32FLAT |
| 286 | pci_writel_32(struct reg32 *reg32) |
| 287 | { |
| 288 | dprintf(3, "32: pci write: %x, %x (%p)\n", reg32->addr, reg32->data, reg32); |
| 289 | writel((void*)(reg32->addr), reg32->data); |
| 290 | } |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 291 | |
| 292 | void pci_writel(u32 addr, u32 val) |
| 293 | { |
| 294 | struct reg32 reg32 = { .addr = addr, .data = val }; |
| 295 | if (MODESEGMENT) { |
| 296 | dprintf(3, "16: pci write: %x, %x (%x:%p)\n", |
| 297 | reg32.addr, reg32.data, GET_SEG(SS), ®32); |
| 298 | void *flatptr = MAKE_FLATPTR(GET_SEG(SS), ®32); |
Kevin O'Connor | f3fe3aa | 2010-12-05 12:38:33 -0500 | [diff] [blame] | 299 | extern void _cfunc32flat_pci_writel_32(struct reg32 *reg32); |
| 300 | call32(_cfunc32flat_pci_writel_32, (u32)flatptr, -1); |
Gerd Hoffmann | f1f18eb | 2010-11-29 09:42:10 +0100 | [diff] [blame] | 301 | } else { |
| 302 | pci_writel_32(®32); |
| 303 | } |
| 304 | } |