diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2014-05-30 13:40:13 -0400 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2014-05-30 13:40:13 -0400 |
commit | d785260e2f57d87de5c059de2dabc3cd31b745f0 (patch) | |
tree | 3218775e396bde8b8139a8c3a01bb95cdc0822c2 /drivers/pci | |
parent | fdaf36bd360fe1e74b34262ad705ef39d52c12de (diff) | |
parent | cf28855ba7e03f2e2ce85166c117c7b6100dc710 (diff) |
Merge branch 'pci/host-generic' into next
* pci/host-generic:
MAINTAINERS: Add generic PCI host controller driver
PCI: generic: Add generic PCI host controller driver
Conflicts:
drivers/pci/host/Kconfig
drivers/pci/host/Makefile
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/host/Kconfig | 7 | ||||
-rw-r--r-- | drivers/pci/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/pci/host/pci-host-generic.c | 388 |
3 files changed, 396 insertions, 0 deletions
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 24d290d097be..21df477be0c8 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig | |||
@@ -39,4 +39,11 @@ config PCI_RCAR_GEN2_PCIE | |||
39 | help | 39 | help |
40 | Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. | 40 | Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. |
41 | 41 | ||
42 | config PCI_HOST_GENERIC | ||
43 | bool "Generic PCI host controller" | ||
44 | depends on ARM && OF | ||
45 | help | ||
46 | Say Y here if you want to support a simple generic PCI host | ||
47 | controller, such as the one emulated by kvmtool. | ||
48 | |||
42 | endmenu | 49 | endmenu |
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 19946f9a4521..611ba4b48c94 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile | |||
@@ -5,3 +5,4 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o | |||
5 | obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o | 5 | obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o |
6 | obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o | 6 | obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o |
7 | obj-$(CONFIG_PCI_RCAR_GEN2_PCIE) += pcie-rcar.o | 7 | obj-$(CONFIG_PCI_RCAR_GEN2_PCIE) += pcie-rcar.o |
8 | obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o | ||
diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c new file mode 100644 index 000000000000..44fe6aa6a43f --- /dev/null +++ b/drivers/pci/host/pci-host-generic.c | |||
@@ -0,0 +1,388 @@ | |||
1 | /* | ||
2 | * Simple, generic PCI host controller driver targetting firmware-initialised | ||
3 | * systems and virtual machines (e.g. the PCI emulation provided by kvmtool). | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | * | ||
17 | * Copyright (C) 2014 ARM Limited | ||
18 | * | ||
19 | * Author: Will Deacon <will.deacon@arm.com> | ||
20 | */ | ||
21 | |||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/of_address.h> | ||
25 | #include <linux/of_pci.h> | ||
26 | #include <linux/platform_device.h> | ||
27 | |||
28 | struct gen_pci_cfg_bus_ops { | ||
29 | u32 bus_shift; | ||
30 | void __iomem *(*map_bus)(struct pci_bus *, unsigned int, int); | ||
31 | }; | ||
32 | |||
33 | struct gen_pci_cfg_windows { | ||
34 | struct resource res; | ||
35 | struct resource bus_range; | ||
36 | void __iomem **win; | ||
37 | |||
38 | const struct gen_pci_cfg_bus_ops *ops; | ||
39 | }; | ||
40 | |||
41 | struct gen_pci { | ||
42 | struct pci_host_bridge host; | ||
43 | struct gen_pci_cfg_windows cfg; | ||
44 | struct list_head resources; | ||
45 | }; | ||
46 | |||
47 | static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus, | ||
48 | unsigned int devfn, | ||
49 | int where) | ||
50 | { | ||
51 | struct pci_sys_data *sys = bus->sysdata; | ||
52 | struct gen_pci *pci = sys->private_data; | ||
53 | resource_size_t idx = bus->number - pci->cfg.bus_range.start; | ||
54 | |||
55 | return pci->cfg.win[idx] + ((devfn << 8) | where); | ||
56 | } | ||
57 | |||
58 | static struct gen_pci_cfg_bus_ops gen_pci_cfg_cam_bus_ops = { | ||
59 | .bus_shift = 16, | ||
60 | .map_bus = gen_pci_map_cfg_bus_cam, | ||
61 | }; | ||
62 | |||
63 | static void __iomem *gen_pci_map_cfg_bus_ecam(struct pci_bus *bus, | ||
64 | unsigned int devfn, | ||
65 | int where) | ||
66 | { | ||
67 | struct pci_sys_data *sys = bus->sysdata; | ||
68 | struct gen_pci *pci = sys->private_data; | ||
69 | resource_size_t idx = bus->number - pci->cfg.bus_range.start; | ||
70 | |||
71 | return pci->cfg.win[idx] + ((devfn << 12) | where); | ||
72 | } | ||
73 | |||
74 | static struct gen_pci_cfg_bus_ops gen_pci_cfg_ecam_bus_ops = { | ||
75 | .bus_shift = 20, | ||
76 | .map_bus = gen_pci_map_cfg_bus_ecam, | ||
77 | }; | ||
78 | |||
79 | static int gen_pci_config_read(struct pci_bus *bus, unsigned int devfn, | ||
80 | int where, int size, u32 *val) | ||
81 | { | ||
82 | void __iomem *addr; | ||
83 | struct pci_sys_data *sys = bus->sysdata; | ||
84 | struct gen_pci *pci = sys->private_data; | ||
85 | |||
86 | addr = pci->cfg.ops->map_bus(bus, devfn, where); | ||
87 | |||
88 | switch (size) { | ||
89 | case 1: | ||
90 | *val = readb(addr); | ||
91 | break; | ||
92 | case 2: | ||
93 | *val = readw(addr); | ||
94 | break; | ||
95 | default: | ||
96 | *val = readl(addr); | ||
97 | } | ||
98 | |||
99 | return PCIBIOS_SUCCESSFUL; | ||
100 | } | ||
101 | |||
102 | static int gen_pci_config_write(struct pci_bus *bus, unsigned int devfn, | ||
103 | int where, int size, u32 val) | ||
104 | { | ||
105 | void __iomem *addr; | ||
106 | struct pci_sys_data *sys = bus->sysdata; | ||
107 | struct gen_pci *pci = sys->private_data; | ||
108 | |||
109 | addr = pci->cfg.ops->map_bus(bus, devfn, where); | ||
110 | |||
111 | switch (size) { | ||
112 | case 1: | ||
113 | writeb(val, addr); | ||
114 | break; | ||
115 | case 2: | ||
116 | writew(val, addr); | ||
117 | break; | ||
118 | default: | ||
119 | writel(val, addr); | ||
120 | } | ||
121 | |||
122 | return PCIBIOS_SUCCESSFUL; | ||
123 | } | ||
124 | |||
125 | static struct pci_ops gen_pci_ops = { | ||
126 | .read = gen_pci_config_read, | ||
127 | .write = gen_pci_config_write, | ||
128 | }; | ||
129 | |||
130 | static const struct of_device_id gen_pci_of_match[] = { | ||
131 | { .compatible = "pci-host-cam-generic", | ||
132 | .data = &gen_pci_cfg_cam_bus_ops }, | ||
133 | |||
134 | { .compatible = "pci-host-ecam-generic", | ||
135 | .data = &gen_pci_cfg_ecam_bus_ops }, | ||
136 | |||
137 | { }, | ||
138 | }; | ||
139 | MODULE_DEVICE_TABLE(of, gen_pci_of_match); | ||
140 | |||
141 | static int gen_pci_calc_io_offset(struct device *dev, | ||
142 | struct of_pci_range *range, | ||
143 | struct resource *res, | ||
144 | resource_size_t *offset) | ||
145 | { | ||
146 | static atomic_t wins = ATOMIC_INIT(0); | ||
147 | int err, idx, max_win; | ||
148 | unsigned int window; | ||
149 | |||
150 | if (!PAGE_ALIGNED(range->cpu_addr)) | ||
151 | return -EINVAL; | ||
152 | |||
153 | max_win = (IO_SPACE_LIMIT + 1) / SZ_64K; | ||
154 | idx = atomic_inc_return(&wins); | ||
155 | if (idx > max_win) | ||
156 | return -ENOSPC; | ||
157 | |||
158 | window = (idx - 1) * SZ_64K; | ||
159 | err = pci_ioremap_io(window, range->cpu_addr); | ||
160 | if (err) | ||
161 | return err; | ||
162 | |||
163 | of_pci_range_to_resource(range, dev->of_node, res); | ||
164 | res->start = window; | ||
165 | res->end = res->start + range->size - 1; | ||
166 | *offset = window - range->pci_addr; | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static int gen_pci_calc_mem_offset(struct device *dev, | ||
171 | struct of_pci_range *range, | ||
172 | struct resource *res, | ||
173 | resource_size_t *offset) | ||
174 | { | ||
175 | of_pci_range_to_resource(range, dev->of_node, res); | ||
176 | *offset = range->cpu_addr - range->pci_addr; | ||
177 | return 0; | ||
178 | } | ||
179 | |||
180 | static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) | ||
181 | { | ||
182 | struct pci_host_bridge_window *win; | ||
183 | |||
184 | list_for_each_entry(win, &pci->resources, list) | ||
185 | release_resource(win->res); | ||
186 | |||
187 | pci_free_resource_list(&pci->resources); | ||
188 | } | ||
189 | |||
190 | static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) | ||
191 | { | ||
192 | struct of_pci_range range; | ||
193 | struct of_pci_range_parser parser; | ||
194 | int err, res_valid = 0; | ||
195 | struct device *dev = pci->host.dev.parent; | ||
196 | struct device_node *np = dev->of_node; | ||
197 | |||
198 | if (of_pci_range_parser_init(&parser, np)) { | ||
199 | dev_err(dev, "missing \"ranges\" property\n"); | ||
200 | return -EINVAL; | ||
201 | } | ||
202 | |||
203 | for_each_of_pci_range(&parser, &range) { | ||
204 | struct resource *parent, *res; | ||
205 | resource_size_t offset; | ||
206 | u32 restype = range.flags & IORESOURCE_TYPE_BITS; | ||
207 | |||
208 | res = devm_kmalloc(dev, sizeof(*res), GFP_KERNEL); | ||
209 | if (!res) { | ||
210 | err = -ENOMEM; | ||
211 | goto out_release_res; | ||
212 | } | ||
213 | |||
214 | switch (restype) { | ||
215 | case IORESOURCE_IO: | ||
216 | parent = &ioport_resource; | ||
217 | err = gen_pci_calc_io_offset(dev, &range, res, &offset); | ||
218 | break; | ||
219 | case IORESOURCE_MEM: | ||
220 | parent = &iomem_resource; | ||
221 | err = gen_pci_calc_mem_offset(dev, &range, res, &offset); | ||
222 | res_valid |= !(res->flags & IORESOURCE_PREFETCH || err); | ||
223 | break; | ||
224 | default: | ||
225 | err = -EINVAL; | ||
226 | continue; | ||
227 | } | ||
228 | |||
229 | if (err) { | ||
230 | dev_warn(dev, | ||
231 | "error %d: failed to add resource [type 0x%x, %lld bytes]\n", | ||
232 | err, restype, range.size); | ||
233 | continue; | ||
234 | } | ||
235 | |||
236 | err = request_resource(parent, res); | ||
237 | if (err) | ||
238 | goto out_release_res; | ||
239 | |||
240 | pci_add_resource_offset(&pci->resources, res, offset); | ||
241 | } | ||
242 | |||
243 | if (!res_valid) { | ||
244 | dev_err(dev, "non-prefetchable memory resource required\n"); | ||
245 | err = -EINVAL; | ||
246 | goto out_release_res; | ||
247 | } | ||
248 | |||
249 | return 0; | ||
250 | |||
251 | out_release_res: | ||
252 | gen_pci_release_of_pci_ranges(pci); | ||
253 | return err; | ||
254 | } | ||
255 | |||
256 | static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) | ||
257 | { | ||
258 | int err; | ||
259 | u8 bus_max; | ||
260 | resource_size_t busn; | ||
261 | struct resource *bus_range; | ||
262 | struct device *dev = pci->host.dev.parent; | ||
263 | struct device_node *np = dev->of_node; | ||
264 | |||
265 | if (of_pci_parse_bus_range(np, &pci->cfg.bus_range)) | ||
266 | pci->cfg.bus_range = (struct resource) { | ||
267 | .name = np->name, | ||
268 | .start = 0, | ||
269 | .end = 0xff, | ||
270 | .flags = IORESOURCE_BUS, | ||
271 | }; | ||
272 | |||
273 | err = of_address_to_resource(np, 0, &pci->cfg.res); | ||
274 | if (err) { | ||
275 | dev_err(dev, "missing \"reg\" property\n"); | ||
276 | return err; | ||
277 | } | ||
278 | |||
279 | pci->cfg.win = devm_kcalloc(dev, resource_size(&pci->cfg.bus_range), | ||
280 | sizeof(*pci->cfg.win), GFP_KERNEL); | ||
281 | if (!pci->cfg.win) | ||
282 | return -ENOMEM; | ||
283 | |||
284 | /* Limit the bus-range to fit within reg */ | ||
285 | bus_max = pci->cfg.bus_range.start + | ||
286 | (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; | ||
287 | pci->cfg.bus_range.end = min_t(resource_size_t, pci->cfg.bus_range.end, | ||
288 | bus_max); | ||
289 | |||
290 | /* Map our Configuration Space windows */ | ||
291 | if (!devm_request_mem_region(dev, pci->cfg.res.start, | ||
292 | resource_size(&pci->cfg.res), | ||
293 | "Configuration Space")) | ||
294 | return -ENOMEM; | ||
295 | |||
296 | bus_range = &pci->cfg.bus_range; | ||
297 | for (busn = bus_range->start; busn <= bus_range->end; ++busn) { | ||
298 | u32 idx = busn - bus_range->start; | ||
299 | u32 sz = 1 << pci->cfg.ops->bus_shift; | ||
300 | |||
301 | pci->cfg.win[idx] = devm_ioremap(dev, | ||
302 | pci->cfg.res.start + busn * sz, | ||
303 | sz); | ||
304 | if (!pci->cfg.win[idx]) | ||
305 | return -ENOMEM; | ||
306 | } | ||
307 | |||
308 | /* Register bus resource */ | ||
309 | pci_add_resource(&pci->resources, bus_range); | ||
310 | return 0; | ||
311 | } | ||
312 | |||
313 | static int gen_pci_setup(int nr, struct pci_sys_data *sys) | ||
314 | { | ||
315 | struct gen_pci *pci = sys->private_data; | ||
316 | list_splice_init(&pci->resources, &sys->resources); | ||
317 | return 1; | ||
318 | } | ||
319 | |||
320 | static int gen_pci_probe(struct platform_device *pdev) | ||
321 | { | ||
322 | int err; | ||
323 | const char *type; | ||
324 | const struct of_device_id *of_id; | ||
325 | const int *prop; | ||
326 | struct device *dev = &pdev->dev; | ||
327 | struct device_node *np = dev->of_node; | ||
328 | struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); | ||
329 | struct hw_pci hw = { | ||
330 | .nr_controllers = 1, | ||
331 | .private_data = (void **)&pci, | ||
332 | .setup = gen_pci_setup, | ||
333 | .map_irq = of_irq_parse_and_map_pci, | ||
334 | .ops = &gen_pci_ops, | ||
335 | }; | ||
336 | |||
337 | if (!pci) | ||
338 | return -ENOMEM; | ||
339 | |||
340 | type = of_get_property(np, "device_type", NULL); | ||
341 | if (!type || strcmp(type, "pci")) { | ||
342 | dev_err(dev, "invalid \"device_type\" %s\n", type); | ||
343 | return -EINVAL; | ||
344 | } | ||
345 | |||
346 | prop = of_get_property(of_chosen, "linux,pci-probe-only", NULL); | ||
347 | if (prop) { | ||
348 | if (*prop) | ||
349 | pci_add_flags(PCI_PROBE_ONLY); | ||
350 | else | ||
351 | pci_clear_flags(PCI_PROBE_ONLY); | ||
352 | } | ||
353 | |||
354 | of_id = of_match_node(gen_pci_of_match, np); | ||
355 | pci->cfg.ops = of_id->data; | ||
356 | pci->host.dev.parent = dev; | ||
357 | INIT_LIST_HEAD(&pci->host.windows); | ||
358 | INIT_LIST_HEAD(&pci->resources); | ||
359 | |||
360 | /* Parse our PCI ranges and request their resources */ | ||
361 | err = gen_pci_parse_request_of_pci_ranges(pci); | ||
362 | if (err) | ||
363 | return err; | ||
364 | |||
365 | /* Parse and map our Configuration Space windows */ | ||
366 | err = gen_pci_parse_map_cfg_windows(pci); | ||
367 | if (err) { | ||
368 | gen_pci_release_of_pci_ranges(pci); | ||
369 | return err; | ||
370 | } | ||
371 | |||
372 | pci_common_init_dev(dev, &hw); | ||
373 | return 0; | ||
374 | } | ||
375 | |||
376 | static struct platform_driver gen_pci_driver = { | ||
377 | .driver = { | ||
378 | .name = "pci-host-generic", | ||
379 | .owner = THIS_MODULE, | ||
380 | .of_match_table = gen_pci_of_match, | ||
381 | }, | ||
382 | .probe = gen_pci_probe, | ||
383 | }; | ||
384 | module_platform_driver(gen_pci_driver); | ||
385 | |||
386 | MODULE_DESCRIPTION("Generic PCI host driver"); | ||
387 | MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>"); | ||
388 | MODULE_LICENSE("GPLv2"); | ||