Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Generic PCI host controller as described in PCI Bus Binding to Open Firmware |
| 3 | * |
| 4 | * Copyright (C) 2016, Red Hat Inc, Alexander Gordeev <agordeev@redhat.com> |
| 5 | * |
| 6 | * This work is licensed under the terms of the GNU LGPL, version 2. |
| 7 | */ |
| 8 | #include "libcflat.h" |
| 9 | #include "devicetree.h" |
| 10 | #include "alloc.h" |
| 11 | #include "pci.h" |
| 12 | #include "asm/pci.h" |
| 13 | #include "asm/io.h" |
| 14 | #include "pci-host-generic.h" |
| 15 | #include <linux/pci_regs.h> |
| 16 | |
| 17 | static struct pci_host_bridge *pci_host_bridge; |
| 18 | |
| 19 | static int of_flags_to_pci_type(u32 of_flags) |
| 20 | { |
| 21 | static int type_map[] = { |
| 22 | [1] = PCI_BASE_ADDRESS_SPACE_IO, |
| 23 | [2] = PCI_BASE_ADDRESS_MEM_TYPE_32, |
| 24 | [3] = PCI_BASE_ADDRESS_MEM_TYPE_64 |
| 25 | }; |
| 26 | int idx = (of_flags >> 24) & 0x03; |
| 27 | int res; |
| 28 | |
| 29 | assert(idx > 0); |
| 30 | res = type_map[idx]; |
| 31 | |
| 32 | if (of_flags & 0x40000000) |
| 33 | res |= PCI_BASE_ADDRESS_MEM_PREFETCH; |
| 34 | |
| 35 | return res; |
| 36 | } |
| 37 | |
| 38 | static int pci_bar_type(u32 bar) |
| 39 | { |
| 40 | if (bar & PCI_BASE_ADDRESS_SPACE) |
| 41 | return PCI_BASE_ADDRESS_SPACE_IO; |
| 42 | else |
| 43 | return bar & (PCI_BASE_ADDRESS_MEM_TYPE_MASK | |
| 44 | PCI_BASE_ADDRESS_MEM_PREFETCH); |
| 45 | } |
| 46 | |
| 47 | /* |
| 48 | * Probe DT for a generic PCI host controller |
| 49 | * See kernel Documentation/devicetree/bindings/pci/host-generic-pci.txt |
| 50 | * and function gen_pci_probe() in drivers/pci/host/pci-host-generic.c |
| 51 | */ |
| 52 | static struct pci_host_bridge *pci_dt_probe(void) |
| 53 | { |
| 54 | struct pci_host_bridge *host; |
| 55 | const void *fdt = dt_fdt(); |
| 56 | const struct fdt_property *prop; |
| 57 | struct dt_pbus_reg base; |
| 58 | struct dt_device dt_dev; |
| 59 | struct dt_bus dt_bus; |
| 60 | struct pci_addr_space *as; |
| 61 | fdt32_t *data; |
| 62 | u32 bus, bus_max; |
| 63 | u32 nac, nsc, nac_root, nsc_root; |
| 64 | int nr_range_cells, nr_addr_spaces; |
| 65 | int ret, node, len, i; |
| 66 | |
| 67 | if (!dt_available()) { |
| 68 | printf("No device tree found\n"); |
| 69 | return NULL; |
| 70 | } |
| 71 | |
| 72 | dt_bus_init_defaults(&dt_bus); |
| 73 | dt_device_init(&dt_dev, &dt_bus, NULL); |
| 74 | |
| 75 | node = fdt_path_offset(fdt, "/"); |
| 76 | assert(node >= 0); |
| 77 | |
| 78 | ret = dt_get_nr_cells(node, &nac_root, &nsc_root); |
| 79 | assert(ret == 0); |
| 80 | assert(nac_root == 1 || nac_root == 2); |
| 81 | |
| 82 | node = fdt_node_offset_by_compatible(fdt, node, |
| 83 | "pci-host-ecam-generic"); |
| 84 | if (node == -FDT_ERR_NOTFOUND) { |
| 85 | printf("No PCIe ECAM compatible controller found\n"); |
| 86 | return NULL; |
| 87 | } |
| 88 | assert(node >= 0); |
| 89 | |
| 90 | prop = fdt_get_property(fdt, node, "device_type", &len); |
| 91 | assert(prop && len == 4 && !strcmp((char *)prop->data, "pci")); |
| 92 | |
| 93 | dt_device_bind_node(&dt_dev, node); |
| 94 | ret = dt_pbus_get_base(&dt_dev, &base); |
| 95 | assert(ret == 0); |
| 96 | |
| 97 | prop = fdt_get_property(fdt, node, "bus-range", &len); |
| 98 | if (prop == NULL) { |
| 99 | assert(len == -FDT_ERR_NOTFOUND); |
| 100 | bus = 0x00; |
| 101 | bus_max = 0xff; |
| 102 | } else { |
| 103 | data = (fdt32_t *)prop->data; |
| 104 | bus = fdt32_to_cpu(data[0]); |
| 105 | bus_max = fdt32_to_cpu(data[1]); |
| 106 | assert(bus <= bus_max); |
| 107 | } |
| 108 | assert(bus_max < base.size / (1 << PCI_ECAM_BUS_SHIFT)); |
| 109 | |
| 110 | ret = dt_get_nr_cells(node, &nac, &nsc); |
| 111 | assert(ret == 0); |
| 112 | assert(nac == 3 && nsc == 2); |
| 113 | |
| 114 | prop = fdt_get_property(fdt, node, "ranges", &len); |
| 115 | assert(prop != NULL); |
| 116 | |
| 117 | nr_range_cells = nac + nsc + nac_root; |
| 118 | nr_addr_spaces = (len / 4) / nr_range_cells; |
| 119 | assert(nr_addr_spaces); |
| 120 | |
| 121 | host = malloc(sizeof(*host) + |
| 122 | sizeof(host->addr_space[0]) * nr_addr_spaces); |
| 123 | assert(host != NULL); |
| 124 | |
Andrew Jones | 340cf7d | 2020-11-03 21:47:08 +0100 | [diff] [blame] | 125 | host->start = ioremap(base.addr, base.size); |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 126 | host->size = base.size; |
| 127 | host->bus = bus; |
| 128 | host->bus_max = bus_max; |
| 129 | host->nr_addr_spaces = nr_addr_spaces; |
| 130 | |
| 131 | data = (fdt32_t *)prop->data; |
| 132 | as = &host->addr_space[0]; |
| 133 | |
| 134 | for (i = 0; i < nr_addr_spaces; i++) { |
| 135 | /* |
| 136 | * The PCI binding encodes the PCI address with three |
| 137 | * cells as follows: |
| 138 | * |
| 139 | * phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr |
| 140 | * phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh |
| 141 | * phys.lo cell: llllllll llllllll llllllll llllllll |
| 142 | * |
| 143 | * PCI device bus address and flags are encoded into phys.high |
| 144 | * PCI 64 bit address is encoded into phys.mid and phys.low |
| 145 | */ |
| 146 | as->type = of_flags_to_pci_type(fdt32_to_cpu(data[0])); |
| 147 | as->pci_start = ((u64)fdt32_to_cpu(data[1]) << 32) | |
| 148 | fdt32_to_cpu(data[2]); |
| 149 | |
| 150 | if (nr_range_cells == 6) { |
| 151 | as->start = fdt32_to_cpu(data[3]); |
| 152 | as->size = ((u64)fdt32_to_cpu(data[4]) << 32) | |
| 153 | fdt32_to_cpu(data[5]); |
| 154 | } else { |
| 155 | as->start = ((u64)fdt32_to_cpu(data[3]) << 32) | |
| 156 | fdt32_to_cpu(data[4]); |
| 157 | as->size = ((u64)fdt32_to_cpu(data[5]) << 32) | |
| 158 | fdt32_to_cpu(data[6]); |
| 159 | } |
| 160 | |
| 161 | data += nr_range_cells; |
| 162 | as++; |
| 163 | } |
| 164 | |
| 165 | return host; |
| 166 | } |
| 167 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 168 | static bool pci_alloc_resource(struct pci_dev *dev, int bar_num, u64 *addr) |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 169 | { |
| 170 | struct pci_host_bridge *host = pci_host_bridge; |
| 171 | struct pci_addr_space *as = &host->addr_space[0]; |
Alexander Gordeev | ae88ce5 | 2016-11-25 11:29:43 +0100 | [diff] [blame] | 172 | u32 bar; |
Alexander Gordeev | ae17c20 | 2016-11-25 11:29:44 +0100 | [diff] [blame] | 173 | u64 size, pci_addr; |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 174 | int type, i; |
| 175 | |
Alexander Gordeev | afb9a02 | 2017-02-28 19:08:26 +0100 | [diff] [blame] | 176 | *addr = INVALID_PHYS_ADDR; |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 177 | |
| 178 | size = pci_bar_size(dev, bar_num); |
| 179 | if (!size) |
| 180 | return false; |
| 181 | |
| 182 | bar = pci_bar_get(dev, bar_num); |
| 183 | type = pci_bar_type(bar); |
| 184 | if (type & PCI_BASE_ADDRESS_MEM_TYPE_MASK) |
| 185 | type &= ~PCI_BASE_ADDRESS_MEM_PREFETCH; |
| 186 | |
| 187 | for (i = 0; i < host->nr_addr_spaces; i++) { |
| 188 | if (as->type == type) |
| 189 | break; |
| 190 | as++; |
| 191 | } |
| 192 | |
| 193 | if (i >= host->nr_addr_spaces) { |
| 194 | printf("%s: warning: can't satisfy request for ", __func__); |
Alexander Gordeev | cb02602 | 2017-02-28 19:08:30 +0100 | [diff] [blame] | 195 | pci_dev_print_id(dev); |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 196 | printf(" "); |
| 197 | pci_bar_print(dev, bar_num); |
| 198 | printf("\n"); |
| 199 | return false; |
| 200 | } |
| 201 | |
Alexander Gordeev | ae17c20 | 2016-11-25 11:29:44 +0100 | [diff] [blame] | 202 | pci_addr = ALIGN(as->pci_start + as->allocated, size); |
| 203 | size += pci_addr - (as->pci_start + as->allocated); |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 204 | assert(as->allocated + size <= as->size); |
Alexander Gordeev | ae17c20 | 2016-11-25 11:29:44 +0100 | [diff] [blame] | 205 | *addr = pci_addr; |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 206 | as->allocated += size; |
| 207 | |
| 208 | return true; |
| 209 | } |
| 210 | |
| 211 | bool pci_probe(void) |
| 212 | { |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 213 | struct pci_dev pci_dev; |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 214 | pcidevaddr_t dev; |
| 215 | u8 header; |
| 216 | u32 cmd; |
| 217 | int i; |
| 218 | |
| 219 | assert(!pci_host_bridge); |
| 220 | pci_host_bridge = pci_dt_probe(); |
| 221 | if (!pci_host_bridge) |
| 222 | return false; |
| 223 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 224 | for (dev = 0; dev < PCI_DEVFN_MAX; dev++) { |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 225 | if (!pci_dev_exists(dev)) |
| 226 | continue; |
| 227 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 228 | pci_dev_init(&pci_dev, dev); |
| 229 | |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 230 | /* We are only interested in normal PCI devices */ |
| 231 | header = pci_config_readb(dev, PCI_HEADER_TYPE); |
| 232 | if ((header & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_NORMAL) |
| 233 | continue; |
| 234 | |
| 235 | cmd = PCI_COMMAND_SERR | PCI_COMMAND_PARITY; |
| 236 | |
Peter Xu | e954ce2 | 2016-12-12 11:08:15 +0800 | [diff] [blame] | 237 | for (i = 0; i < PCI_BAR_NUM; i++) { |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 238 | u64 addr; |
| 239 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 240 | if (pci_alloc_resource(&pci_dev, i, &addr)) { |
| 241 | pci_bar_set_addr(&pci_dev, i, addr); |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 242 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 243 | if (pci_bar_is_memory(&pci_dev, i)) |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 244 | cmd |= PCI_COMMAND_MEMORY; |
| 245 | else |
| 246 | cmd |= PCI_COMMAND_IO; |
| 247 | } |
| 248 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 249 | if (pci_bar_is64(&pci_dev, i)) |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 250 | i++; |
| 251 | } |
| 252 | |
| 253 | pci_config_writew(dev, PCI_COMMAND, cmd); |
| 254 | } |
| 255 | |
| 256 | return true; |
| 257 | } |
| 258 | |
| 259 | /* |
| 260 | * This function is to be called from pci_translate_addr() to provide |
| 261 | * mapping between this host bridge's PCI busses address and CPU physical |
| 262 | * address. |
| 263 | */ |
| 264 | phys_addr_t pci_host_bridge_get_paddr(u64 pci_addr) |
| 265 | { |
| 266 | struct pci_host_bridge *host = pci_host_bridge; |
| 267 | struct pci_addr_space *as = &host->addr_space[0]; |
| 268 | int i; |
| 269 | |
| 270 | for (i = 0; i < host->nr_addr_spaces; i++) { |
| 271 | if (pci_addr >= as->pci_start && |
| 272 | pci_addr < as->pci_start + as->size) |
| 273 | return as->start + (pci_addr - as->pci_start); |
| 274 | as++; |
| 275 | } |
| 276 | |
Alexander Gordeev | b8b9fcf | 2016-11-25 15:56:05 +0100 | [diff] [blame] | 277 | return INVALID_PHYS_ADDR; |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 278 | } |
| 279 | |
| 280 | static void __iomem *pci_get_dev_conf(struct pci_host_bridge *host, int devfn) |
| 281 | { |
Andrew Jones | 340cf7d | 2020-11-03 21:47:08 +0100 | [diff] [blame] | 282 | return host->start + (devfn << PCI_ECAM_DEVFN_SHIFT); |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 283 | } |
| 284 | |
| 285 | u8 pci_config_readb(pcidevaddr_t dev, u8 off) |
| 286 | { |
| 287 | void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); |
| 288 | return readb(conf + off); |
| 289 | } |
| 290 | |
| 291 | u16 pci_config_readw(pcidevaddr_t dev, u8 off) |
| 292 | { |
| 293 | void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); |
| 294 | return readw(conf + off); |
| 295 | } |
| 296 | |
| 297 | u32 pci_config_readl(pcidevaddr_t dev, u8 off) |
| 298 | { |
| 299 | void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); |
| 300 | return readl(conf + off); |
| 301 | } |
| 302 | |
| 303 | void pci_config_writeb(pcidevaddr_t dev, u8 off, u8 val) |
| 304 | { |
| 305 | void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); |
| 306 | writeb(val, conf + off); |
| 307 | } |
| 308 | |
| 309 | void pci_config_writew(pcidevaddr_t dev, u8 off, u16 val) |
| 310 | { |
| 311 | void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); |
| 312 | writew(val, conf + off); |
| 313 | } |
| 314 | |
| 315 | void pci_config_writel(pcidevaddr_t dev, u8 off, u32 val) |
| 316 | { |
| 317 | void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); |
| 318 | writel(val, conf + off); |
| 319 | } |