]>
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; | |
35 | int i, err; | |
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; | |
53 | bus_range = resource_size(&cfg->busr); | |
e7708f5b | 54 | bus_range_max = resource_size(cfgres) >> bus_shift; |
35ff9477 J |
55 | if (bus_range > bus_range_max) { |
56 | bus_range = bus_range_max; | |
57 | cfg->busr.end = busr->start + bus_range - 1; | |
58 | dev_warn(dev, "ECAM area %pR can only accommodate %pR (reduced from %pR desired)\n", | |
59 | cfgres, &cfg->busr, busr); | |
60 | } | |
e7708f5b | 61 | bsz = 1 << bus_shift; |
35ff9477 J |
62 | |
63 | cfg->res.start = cfgres->start; | |
64 | cfg->res.end = cfgres->end; | |
65 | cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; | |
66 | cfg->res.name = "PCI ECAM"; | |
67 | ||
68 | conflict = request_resource_conflict(&iomem_resource, &cfg->res); | |
69 | if (conflict) { | |
70 | err = -EBUSY; | |
71 | dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n", | |
72 | &cfg->res, conflict->name, conflict); | |
73 | goto err_exit; | |
74 | } | |
75 | ||
76 | if (per_bus_mapping) { | |
77 | cfg->winp = kcalloc(bus_range, sizeof(*cfg->winp), GFP_KERNEL); | |
78 | if (!cfg->winp) | |
79 | goto err_exit_malloc; | |
80 | for (i = 0; i < bus_range; i++) { | |
053497ce LP |
81 | cfg->winp[i] = |
82 | pci_remap_cfgspace(cfgres->start + i * bsz, | |
83 | bsz); | |
35ff9477 J |
84 | if (!cfg->winp[i]) |
85 | goto err_exit_iomap; | |
86 | } | |
87 | } else { | |
053497ce | 88 | cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz); |
35ff9477 J |
89 | if (!cfg->win) |
90 | goto err_exit_iomap; | |
91 | } | |
92 | ||
93 | if (ops->init) { | |
5c3d14f7 | 94 | err = ops->init(cfg); |
35ff9477 J |
95 | if (err) |
96 | goto err_exit; | |
97 | } | |
98 | dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr); | |
99 | return cfg; | |
100 | ||
101 | err_exit_iomap: | |
102 | dev_err(dev, "ECAM ioremap failed\n"); | |
103 | err_exit_malloc: | |
104 | err = -ENOMEM; | |
105 | err_exit: | |
106 | pci_ecam_free(cfg); | |
107 | return ERR_PTR(err); | |
108 | } | |
0c59c06a | 109 | EXPORT_SYMBOL_GPL(pci_ecam_create); |
35ff9477 J |
110 | |
111 | void pci_ecam_free(struct pci_config_window *cfg) | |
112 | { | |
113 | int i; | |
114 | ||
115 | if (per_bus_mapping) { | |
116 | if (cfg->winp) { | |
117 | for (i = 0; i < resource_size(&cfg->busr); i++) | |
118 | if (cfg->winp[i]) | |
119 | iounmap(cfg->winp[i]); | |
120 | kfree(cfg->winp); | |
121 | } | |
122 | } else { | |
123 | if (cfg->win) | |
124 | iounmap(cfg->win); | |
125 | } | |
126 | if (cfg->res.parent) | |
127 | release_resource(&cfg->res); | |
128 | kfree(cfg); | |
129 | } | |
0c59c06a | 130 | EXPORT_SYMBOL_GPL(pci_ecam_free); |
35ff9477 J |
131 | |
132 | /* | |
133 | * Function to implement the pci_ops ->map_bus method | |
134 | */ | |
135 | void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn, | |
136 | int where) | |
137 | { | |
138 | struct pci_config_window *cfg = bus->sysdata; | |
e7708f5b | 139 | unsigned int bus_shift = cfg->ops->bus_shift; |
35ff9477 J |
140 | unsigned int devfn_shift = cfg->ops->bus_shift - 8; |
141 | unsigned int busn = bus->number; | |
142 | void __iomem *base; | |
e7708f5b | 143 | u32 bus_offset, devfn_offset; |
35ff9477 J |
144 | |
145 | if (busn < cfg->busr.start || busn > cfg->busr.end) | |
146 | return NULL; | |
147 | ||
148 | busn -= cfg->busr.start; | |
e7708f5b | 149 | if (per_bus_mapping) { |
35ff9477 | 150 | base = cfg->winp[busn]; |
e7708f5b KW |
151 | busn = 0; |
152 | } else | |
153 | base = cfg->win; | |
154 | ||
155 | if (cfg->ops->bus_shift) { | |
156 | bus_offset = (busn & PCIE_ECAM_BUS_MASK) << bus_shift; | |
157 | devfn_offset = (devfn & PCIE_ECAM_DEVFN_MASK) << devfn_shift; | |
158 | where &= PCIE_ECAM_REG_MASK; | |
159 | ||
160 | return base + (bus_offset | devfn_offset | where); | |
161 | } | |
162 | ||
163 | return base + PCIE_ECAM_OFFSET(busn, devfn, where); | |
35ff9477 | 164 | } |
0c59c06a | 165 | EXPORT_SYMBOL_GPL(pci_ecam_map_bus); |
35ff9477 J |
166 | |
167 | /* ECAM ops */ | |
0b104773 | 168 | const struct pci_ecam_ops pci_generic_ecam_ops = { |
35ff9477 J |
169 | .pci_ops = { |
170 | .map_bus = pci_ecam_map_bus, | |
171 | .read = pci_generic_config_read, | |
172 | .write = pci_generic_config_write, | |
173 | } | |
174 | }; | |
0c59c06a | 175 | EXPORT_SYMBOL_GPL(pci_generic_ecam_ops); |
2ca5b8dd CC |
176 | |
177 | #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) | |
178 | /* ECAM ops for 32-bit access only (non-compliant) */ | |
0b104773 | 179 | const struct pci_ecam_ops pci_32b_ops = { |
2ca5b8dd CC |
180 | .pci_ops = { |
181 | .map_bus = pci_ecam_map_bus, | |
182 | .read = pci_generic_config_read32, | |
183 | .write = pci_generic_config_write32, | |
184 | } | |
185 | }; | |
877c1a5f TP |
186 | |
187 | /* ECAM ops for 32-bit read only (non-compliant) */ | |
188 | const struct pci_ecam_ops pci_32b_read_ops = { | |
877c1a5f TP |
189 | .pci_ops = { |
190 | .map_bus = pci_ecam_map_bus, | |
191 | .read = pci_generic_config_read32, | |
192 | .write = pci_generic_config_write, | |
193 | } | |
194 | }; | |
2ca5b8dd | 195 | #endif |