diff options
author | Will Deacon <will.deacon@arm.com> | 2013-11-22 11:14:41 -0500 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2014-05-30 13:34:49 -0400 |
commit | ce292991d88b77160f348fb8a3a2cf6e78f4b456 (patch) | |
tree | b6ab6cb10e05db9dc03de18a595974d3361d30ac | |
parent | c9eaa447e77efe77b7fa4c953bd62de8297fd6c5 (diff) |
PCI: generic: Add generic PCI host controller driver
Add support for a generic PCI host controller, such as a
firmware-initialised device with static windows or an emulation by
something such as kvmtool.
The controller itself has no configuration registers and has its address
spaces described entirely by the device-tree (using the bindings from
ePAPR). Both CAM and ECAM are supported for Config Space accesses.
Add corresponding documentation for the DT binding.
[bhelgaas: currently uses the ARM-specific pci_common_init_dev() interface]
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>
-rw-r--r-- | Documentation/devicetree/bindings/pci/host-generic-pci.txt | 100 | ||||
-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 |
4 files changed, 496 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/pci/host-generic-pci.txt b/Documentation/devicetree/bindings/pci/host-generic-pci.txt new file mode 100644 index 000000000000..f0b0436807b4 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/host-generic-pci.txt | |||
@@ -0,0 +1,100 @@ | |||
1 | * Generic PCI host controller | ||
2 | |||
3 | Firmware-initialised PCI host controllers and PCI emulations, such as the | ||
4 | virtio-pci implementations found in kvmtool and other para-virtualised | ||
5 | systems, do not require driver support for complexities such as regulator | ||
6 | and clock management. In fact, the controller may not even require the | ||
7 | configuration of a control interface by the operating system, instead | ||
8 | presenting a set of fixed windows describing a subset of IO, Memory and | ||
9 | Configuration Spaces. | ||
10 | |||
11 | Such a controller can be described purely in terms of the standardized device | ||
12 | tree bindings communicated in pci.txt: | ||
13 | |||
14 | |||
15 | Properties of the host controller node: | ||
16 | |||
17 | - compatible : Must be "pci-host-cam-generic" or "pci-host-ecam-generic" | ||
18 | depending on the layout of configuration space (CAM vs | ||
19 | ECAM respectively). | ||
20 | |||
21 | - device_type : Must be "pci". | ||
22 | |||
23 | - ranges : As described in IEEE Std 1275-1994, but must provide | ||
24 | at least a definition of non-prefetchable memory. One | ||
25 | or both of prefetchable Memory and IO Space may also | ||
26 | be provided. | ||
27 | |||
28 | - bus-range : Optional property (also described in IEEE Std 1275-1994) | ||
29 | to indicate the range of bus numbers for this controller. | ||
30 | If absent, defaults to <0 255> (i.e. all buses). | ||
31 | |||
32 | - #address-cells : Must be 3. | ||
33 | |||
34 | - #size-cells : Must be 2. | ||
35 | |||
36 | - reg : The Configuration Space base address and size, as accessed | ||
37 | from the parent bus. | ||
38 | |||
39 | |||
40 | Properties of the /chosen node: | ||
41 | |||
42 | - linux,pci-probe-only | ||
43 | : Optional property which takes a single-cell argument. | ||
44 | If '0', then Linux will assign devices in its usual manner, | ||
45 | otherwise it will not try to assign devices and instead use | ||
46 | them as they are configured already. | ||
47 | |||
48 | Configuration Space is assumed to be memory-mapped (as opposed to being | ||
49 | accessed via an ioport) and laid out with a direct correspondence to the | ||
50 | geography of a PCI bus address by concatenating the various components to | ||
51 | form an offset. | ||
52 | |||
53 | For CAM, this 24-bit offset is: | ||
54 | |||
55 | cfg_offset(bus, device, function, register) = | ||
56 | bus << 16 | device << 11 | function << 8 | register | ||
57 | |||
58 | Whilst ECAM extends this by 4 bits to accomodate 4k of function space: | ||
59 | |||
60 | cfg_offset(bus, device, function, register) = | ||
61 | bus << 20 | device << 15 | function << 12 | register | ||
62 | |||
63 | Interrupt mapping is exactly as described in `Open Firmware Recommended | ||
64 | Practice: Interrupt Mapping' and requires the following properties: | ||
65 | |||
66 | - #interrupt-cells : Must be 1 | ||
67 | |||
68 | - interrupt-map : <see aforementioned specification> | ||
69 | |||
70 | - interrupt-map-mask : <see aforementioned specification> | ||
71 | |||
72 | |||
73 | Example: | ||
74 | |||
75 | pci { | ||
76 | compatible = "pci-host-cam-generic" | ||
77 | device_type = "pci"; | ||
78 | #address-cells = <3>; | ||
79 | #size-cells = <2>; | ||
80 | bus-range = <0x0 0x1>; | ||
81 | |||
82 | // CPU_PHYSICAL(2) SIZE(2) | ||
83 | reg = <0x0 0x40000000 0x0 0x1000000>; | ||
84 | |||
85 | // BUS_ADDRESS(3) CPU_PHYSICAL(2) SIZE(2) | ||
86 | ranges = <0x01000000 0x0 0x01000000 0x0 0x01000000 0x0 0x00010000>, | ||
87 | <0x02000000 0x0 0x41000000 0x0 0x41000000 0x0 0x3f000000>; | ||
88 | |||
89 | |||
90 | #interrupt-cells = <0x1>; | ||
91 | |||
92 | // PCI_DEVICE(3) INT#(1) CONTROLLER(PHANDLE) CONTROLLER_DATA(3) | ||
93 | interrupt-map = < 0x0 0x0 0x0 0x1 &gic 0x0 0x4 0x1 | ||
94 | 0x800 0x0 0x0 0x1 &gic 0x0 0x5 0x1 | ||
95 | 0x1000 0x0 0x0 0x1 &gic 0x0 0x6 0x1 | ||
96 | 0x1800 0x0 0x0 0x1 &gic 0x0 0x7 0x1>; | ||
97 | |||
98 | // PCI_DEVICE(3) INT#(1) | ||
99 | interrupt-map-mask = <0xf800 0x0 0x0 0x7>; | ||
100 | } | ||
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index a6f67ec8882f..32d446effbb3 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig | |||
@@ -33,4 +33,11 @@ config PCI_RCAR_GEN2 | |||
33 | There are 3 internal PCI controllers available with a single | 33 | There are 3 internal PCI controllers available with a single |
34 | built-in EHCI/OHCI host controller present on each one. | 34 | built-in EHCI/OHCI host controller present on each one. |
35 | 35 | ||
36 | config PCI_HOST_GENERIC | ||
37 | bool "Generic PCI host controller" | ||
38 | depends on ARM && OF | ||
39 | help | ||
40 | Say Y here if you want to support a simple generic PCI host | ||
41 | controller, such as the one emulated by kvmtool. | ||
42 | |||
36 | endmenu | 43 | endmenu |
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 13fb3333aa05..bd1bf1ab4ac8 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile | |||
@@ -4,3 +4,4 @@ obj-$(CONFIG_PCI_IMX6) += pci-imx6.o | |||
4 | obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o | 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_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"); | ||