Commit | Line | Data |
---|---|---|
35ff9477 J |
1 | /* |
2 | * Copyright 2016 Broadcom | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License, version 2, as | |
6 | * published by the Free Software Foundation (the "GPL"). | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, but | |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
11 | * General Public License version 2 (GPLv2) for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * version 2 (GPLv2) along with this source code. | |
15 | */ | |
16 | ||
17 | #include <linux/device.h> | |
18 | #include <linux/io.h> | |
19 | #include <linux/kernel.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/pci.h> | |
80955f9e | 22 | #include <linux/pci-ecam.h> |
35ff9477 J |
23 | #include <linux/slab.h> |
24 | ||
35ff9477 J |
25 | /* |
26 | * On 64-bit systems, we do a single ioremap for the whole config space | |
27 | * since we have enough virtual address range available. On 32-bit, we | |
28 | * ioremap the config space for each bus individually. | |
29 | */ | |
97f2645f | 30 | static const bool per_bus_mapping = !IS_ENABLED(CONFIG_64BIT); |
35ff9477 J |
31 | |
32 | /* | |
33 | * Create a PCI config space window | |
34 | * - reserve mem region | |
35 | * - alloc struct pci_config_window with space for all mappings | |
36 | * - ioremap the config space | |
37 | */ | |
38 | struct pci_config_window *pci_ecam_create(struct device *dev, | |
39 | struct resource *cfgres, struct resource *busr, | |
40 | struct pci_ecam_ops *ops) | |
41 | { | |
42 | struct pci_config_window *cfg; | |
43 | unsigned int bus_range, bus_range_max, bsz; | |
44 | struct resource *conflict; | |
45 | int i, err; | |
46 | ||
47 | if (busr->start > busr->end) | |
48 | return ERR_PTR(-EINVAL); | |
49 | ||
50 | cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); | |
51 | if (!cfg) | |
52 | return ERR_PTR(-ENOMEM); | |
53 | ||
5c3d14f7 | 54 | cfg->parent = dev; |
35ff9477 J |
55 | cfg->ops = ops; |
56 | cfg->busr.start = busr->start; | |
57 | cfg->busr.end = busr->end; | |
58 | cfg->busr.flags = IORESOURCE_BUS; | |
59 | bus_range = resource_size(&cfg->busr); | |
60 | bus_range_max = resource_size(cfgres) >> ops->bus_shift; | |
61 | if (bus_range > bus_range_max) { | |
62 | bus_range = bus_range_max; | |
63 | cfg->busr.end = busr->start + bus_range - 1; | |
64 | dev_warn(dev, "ECAM area %pR can only accommodate %pR (reduced from %pR desired)\n", | |
65 | cfgres, &cfg->busr, busr); | |
66 | } | |
67 | bsz = 1 << ops->bus_shift; | |
68 | ||
69 | cfg->res.start = cfgres->start; | |
70 | cfg->res.end = cfgres->end; | |
71 | cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; | |
72 | cfg->res.name = "PCI ECAM"; | |
73 | ||
74 | conflict = request_resource_conflict(&iomem_resource, &cfg->res); | |
75 | if (conflict) { | |
76 | err = -EBUSY; | |
77 | dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n", | |
78 | &cfg->res, conflict->name, conflict); | |
79 | goto err_exit; | |
80 | } | |
81 | ||
82 | if (per_bus_mapping) { | |
83 | cfg->winp = kcalloc(bus_range, sizeof(*cfg->winp), GFP_KERNEL); | |
84 | if (!cfg->winp) | |
85 | goto err_exit_malloc; | |
86 | for (i = 0; i < bus_range; i++) { | |
87 | cfg->winp[i] = ioremap(cfgres->start + i * bsz, bsz); | |
88 | if (!cfg->winp[i]) | |
89 | goto err_exit_iomap; | |
90 | } | |
91 | } else { | |
92 | cfg->win = ioremap(cfgres->start, bus_range * bsz); | |
93 | if (!cfg->win) | |
94 | goto err_exit_iomap; | |
95 | } | |
96 | ||
97 | if (ops->init) { | |
5c3d14f7 | 98 | err = ops->init(cfg); |
35ff9477 J |
99 | if (err) |
100 | goto err_exit; | |
101 | } | |
102 | dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr); | |
103 | return cfg; | |
104 | ||
105 | err_exit_iomap: | |
106 | dev_err(dev, "ECAM ioremap failed\n"); | |
107 | err_exit_malloc: | |
108 | err = -ENOMEM; | |
109 | err_exit: | |
110 | pci_ecam_free(cfg); | |
111 | return ERR_PTR(err); | |
112 | } | |
113 | ||
114 | void pci_ecam_free(struct pci_config_window *cfg) | |
115 | { | |
116 | int i; | |
117 | ||
118 | if (per_bus_mapping) { | |
119 | if (cfg->winp) { | |
120 | for (i = 0; i < resource_size(&cfg->busr); i++) | |
121 | if (cfg->winp[i]) | |
122 | iounmap(cfg->winp[i]); | |
123 | kfree(cfg->winp); | |
124 | } | |
125 | } else { | |
126 | if (cfg->win) | |
127 | iounmap(cfg->win); | |
128 | } | |
129 | if (cfg->res.parent) | |
130 | release_resource(&cfg->res); | |
131 | kfree(cfg); | |
132 | } | |
133 | ||
134 | /* | |
135 | * Function to implement the pci_ops ->map_bus method | |
136 | */ | |
137 | void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn, | |
138 | int where) | |
139 | { | |
140 | struct pci_config_window *cfg = bus->sysdata; | |
141 | unsigned int devfn_shift = cfg->ops->bus_shift - 8; | |
142 | unsigned int busn = bus->number; | |
143 | void __iomem *base; | |
144 | ||
145 | if (busn < cfg->busr.start || busn > cfg->busr.end) | |
146 | return NULL; | |
147 | ||
148 | busn -= cfg->busr.start; | |
149 | if (per_bus_mapping) | |
150 | base = cfg->winp[busn]; | |
151 | else | |
152 | base = cfg->win + (busn << cfg->ops->bus_shift); | |
153 | return base + (devfn << devfn_shift) + where; | |
154 | } | |
155 | ||
156 | /* ECAM ops */ | |
157 | struct pci_ecam_ops pci_generic_ecam_ops = { | |
158 | .bus_shift = 20, | |
159 | .pci_ops = { | |
160 | .map_bus = pci_ecam_map_bus, | |
161 | .read = pci_generic_config_read, | |
162 | .write = pci_generic_config_write, | |
163 | } | |
164 | }; | |
2ca5b8dd CC |
165 | |
166 | #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) | |
167 | /* ECAM ops for 32-bit access only (non-compliant) */ | |
168 | struct pci_ecam_ops pci_32b_ops = { | |
169 | .bus_shift = 20, | |
170 | .pci_ops = { | |
171 | .map_bus = pci_ecam_map_bus, | |
172 | .read = pci_generic_config_read32, | |
173 | .write = pci_generic_config_write32, | |
174 | } | |
175 | }; | |
176 | #endif |