]>
Commit | Line | Data |
---|---|---|
8cfab3cf | 1 | // SPDX-License-Identifier: GPL-2.0 |
35ff9477 J |
2 | /* |
3 | * Copyright 2016 Broadcom | |
35ff9477 J |
4 | */ |
5 | ||
6 | #include <linux/device.h> | |
7 | #include <linux/io.h> | |
8 | #include <linux/kernel.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/pci.h> | |
80955f9e | 11 | #include <linux/pci-ecam.h> |
35ff9477 J |
12 | #include <linux/slab.h> |
13 | ||
35ff9477 J |
14 | /* |
15 | * On 64-bit systems, we do a single ioremap for the whole config space | |
16 | * since we have enough virtual address range available. On 32-bit, we | |
17 | * ioremap the config space for each bus individually. | |
18 | */ | |
97f2645f | 19 | static const bool per_bus_mapping = !IS_ENABLED(CONFIG_64BIT); |
35ff9477 J |
20 | |
21 | /* | |
22 | * Create a PCI config space window | |
23 | * - reserve mem region | |
24 | * - alloc struct pci_config_window with space for all mappings | |
25 | * - ioremap the config space | |
26 | */ | |
27 | struct pci_config_window *pci_ecam_create(struct device *dev, | |
28 | struct resource *cfgres, struct resource *busr, | |
0b104773 | 29 | const struct pci_ecam_ops *ops) |
35ff9477 | 30 | { |
e7708f5b | 31 | unsigned int bus_shift = ops->bus_shift; |
35ff9477 J |
32 | struct pci_config_window *cfg; |
33 | unsigned int bus_range, bus_range_max, bsz; | |
34 | struct resource *conflict; | |
8fe55ef2 | 35 | int err; |
35ff9477 J |
36 | |
37 | if (busr->start > busr->end) | |
38 | return ERR_PTR(-EINVAL); | |
39 | ||
40 | cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); | |
41 | if (!cfg) | |
42 | return ERR_PTR(-ENOMEM); | |
43 | ||
e7708f5b KW |
44 | /* ECAM-compliant platforms need not supply ops->bus_shift */ |
45 | if (!bus_shift) | |
46 | bus_shift = PCIE_ECAM_BUS_SHIFT; | |
47 | ||
5c3d14f7 | 48 | cfg->parent = dev; |
35ff9477 J |
49 | cfg->ops = ops; |
50 | cfg->busr.start = busr->start; | |
51 | cfg->busr.end = busr->end; | |
52 | cfg->busr.flags = IORESOURCE_BUS; | |
8fe55ef2 | 53 | cfg->bus_shift = bus_shift; |
35ff9477 | 54 | bus_range = resource_size(&cfg->busr); |
e7708f5b | 55 | bus_range_max = resource_size(cfgres) >> bus_shift; |
35ff9477 J |
56 | if (bus_range > bus_range_max) { |
57 | bus_range = bus_range_max; | |
58 | cfg->busr.end = busr->start + bus_range - 1; | |
59 | dev_warn(dev, "ECAM area %pR can only accommodate %pR (reduced from %pR desired)\n", | |
60 | cfgres, &cfg->busr, busr); | |
61 | } | |
e7708f5b | 62 | bsz = 1 << bus_shift; |
35ff9477 J |
63 | |
64 | cfg->res.start = cfgres->start; | |
65 | cfg->res.end = cfgres->end; | |
66 | cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; | |
67 | cfg->res.name = "PCI ECAM"; | |
68 | ||
69 | conflict = request_resource_conflict(&iomem_resource, &cfg->res); | |
70 | if (conflict) { | |
71 | err = -EBUSY; | |
72 | dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n", | |
73 | &cfg->res, conflict->name, conflict); | |
74 | goto err_exit; | |
75 | } | |
76 | ||
77 | if (per_bus_mapping) { | |
78 | cfg->winp = kcalloc(bus_range, sizeof(*cfg->winp), GFP_KERNEL); | |
79 | if (!cfg->winp) | |
80 | goto err_exit_malloc; | |
35ff9477 | 81 | } else { |
053497ce | 82 | cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz); |
35ff9477 J |
83 | if (!cfg->win) |
84 | goto err_exit_iomap; | |
85 | } | |
86 | ||
87 | if (ops->init) { | |
5c3d14f7 | 88 | err = ops->init(cfg); |
35ff9477 J |
89 | if (err) |
90 | goto err_exit; | |
91 | } | |
92 | dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr); | |
93 | return cfg; | |
94 | ||
95 | err_exit_iomap: | |
96 | dev_err(dev, "ECAM ioremap failed\n"); | |
97 | err_exit_malloc: | |
98 | err = -ENOMEM; | |
99 | err_exit: | |
100 | pci_ecam_free(cfg); | |
101 | return ERR_PTR(err); | |
102 | } | |
0c59c06a | 103 | EXPORT_SYMBOL_GPL(pci_ecam_create); |
35ff9477 J |
104 | |
105 | void pci_ecam_free(struct pci_config_window *cfg) | |
106 | { | |
107 | int i; | |
108 | ||
109 | if (per_bus_mapping) { | |
110 | if (cfg->winp) { | |
111 | for (i = 0; i < resource_size(&cfg->busr); i++) | |
112 | if (cfg->winp[i]) | |
113 | iounmap(cfg->winp[i]); | |
114 | kfree(cfg->winp); | |
115 | } | |
116 | } else { | |
117 | if (cfg->win) | |
118 | iounmap(cfg->win); | |
119 | } | |
120 | if (cfg->res.parent) | |
121 | release_resource(&cfg->res); | |
122 | kfree(cfg); | |
123 | } | |
0c59c06a | 124 | EXPORT_SYMBOL_GPL(pci_ecam_free); |
35ff9477 | 125 | |
8fe55ef2 RK |
126 | static int pci_ecam_add_bus(struct pci_bus *bus) |
127 | { | |
128 | struct pci_config_window *cfg = bus->sysdata; | |
129 | unsigned int bsz = 1 << cfg->bus_shift; | |
130 | unsigned int busn = bus->number; | |
131 | phys_addr_t start; | |
132 | ||
133 | if (!per_bus_mapping) | |
134 | return 0; | |
135 | ||
136 | if (busn < cfg->busr.start || busn > cfg->busr.end) | |
137 | return -EINVAL; | |
138 | ||
139 | busn -= cfg->busr.start; | |
140 | start = cfg->res.start + busn * bsz; | |
141 | ||
142 | cfg->winp[busn] = pci_remap_cfgspace(start, bsz); | |
143 | if (!cfg->winp[busn]) | |
144 | return -ENOMEM; | |
145 | ||
146 | return 0; | |
147 | } | |
148 | ||
149 | static void pci_ecam_remove_bus(struct pci_bus *bus) | |
150 | { | |
151 | struct pci_config_window *cfg = bus->sysdata; | |
152 | unsigned int busn = bus->number; | |
153 | ||
154 | if (!per_bus_mapping || busn < cfg->busr.start || busn > cfg->busr.end) | |
155 | return; | |
156 | ||
157 | busn -= cfg->busr.start; | |
158 | if (cfg->winp[busn]) { | |
159 | iounmap(cfg->winp[busn]); | |
160 | cfg->winp[busn] = NULL; | |
161 | } | |
162 | } | |
163 | ||
35ff9477 J |
164 | /* |
165 | * Function to implement the pci_ops ->map_bus method | |
166 | */ | |
167 | void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn, | |
168 | int where) | |
169 | { | |
170 | struct pci_config_window *cfg = bus->sysdata; | |
e7708f5b | 171 | unsigned int bus_shift = cfg->ops->bus_shift; |
35ff9477 J |
172 | unsigned int devfn_shift = cfg->ops->bus_shift - 8; |
173 | unsigned int busn = bus->number; | |
174 | void __iomem *base; | |
e7708f5b | 175 | u32 bus_offset, devfn_offset; |
35ff9477 J |
176 | |
177 | if (busn < cfg->busr.start || busn > cfg->busr.end) | |
178 | return NULL; | |
179 | ||
180 | busn -= cfg->busr.start; | |
e7708f5b | 181 | if (per_bus_mapping) { |
35ff9477 | 182 | base = cfg->winp[busn]; |
e7708f5b KW |
183 | busn = 0; |
184 | } else | |
185 | base = cfg->win; | |
186 | ||
187 | if (cfg->ops->bus_shift) { | |
188 | bus_offset = (busn & PCIE_ECAM_BUS_MASK) << bus_shift; | |
189 | devfn_offset = (devfn & PCIE_ECAM_DEVFN_MASK) << devfn_shift; | |
190 | where &= PCIE_ECAM_REG_MASK; | |
191 | ||
192 | return base + (bus_offset | devfn_offset | where); | |
193 | } | |
194 | ||
195 | return base + PCIE_ECAM_OFFSET(busn, devfn, where); | |
35ff9477 | 196 | } |
0c59c06a | 197 | EXPORT_SYMBOL_GPL(pci_ecam_map_bus); |
35ff9477 J |
198 | |
199 | /* ECAM ops */ | |
0b104773 | 200 | const struct pci_ecam_ops pci_generic_ecam_ops = { |
35ff9477 | 201 | .pci_ops = { |
8fe55ef2 RK |
202 | .add_bus = pci_ecam_add_bus, |
203 | .remove_bus = pci_ecam_remove_bus, | |
35ff9477 J |
204 | .map_bus = pci_ecam_map_bus, |
205 | .read = pci_generic_config_read, | |
206 | .write = pci_generic_config_write, | |
207 | } | |
208 | }; | |
0c59c06a | 209 | EXPORT_SYMBOL_GPL(pci_generic_ecam_ops); |
2ca5b8dd CC |
210 | |
211 | #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) | |
212 | /* ECAM ops for 32-bit access only (non-compliant) */ | |
0b104773 | 213 | const struct pci_ecam_ops pci_32b_ops = { |
2ca5b8dd | 214 | .pci_ops = { |
8fe55ef2 RK |
215 | .add_bus = pci_ecam_add_bus, |
216 | .remove_bus = pci_ecam_remove_bus, | |
2ca5b8dd CC |
217 | .map_bus = pci_ecam_map_bus, |
218 | .read = pci_generic_config_read32, | |
219 | .write = pci_generic_config_write32, | |
220 | } | |
221 | }; | |
877c1a5f TP |
222 | |
223 | /* ECAM ops for 32-bit read only (non-compliant) */ | |
224 | const struct pci_ecam_ops pci_32b_read_ops = { | |
877c1a5f | 225 | .pci_ops = { |
8fe55ef2 RK |
226 | .add_bus = pci_ecam_add_bus, |
227 | .remove_bus = pci_ecam_remove_bus, | |
877c1a5f TP |
228 | .map_bus = pci_ecam_map_bus, |
229 | .read = pci_generic_config_read32, | |
230 | .write = pci_generic_config_write, | |
231 | } | |
232 | }; | |
2ca5b8dd | 233 | #endif |