Linux 6.6.15-rt22
[platform/kernel/linux-rpi.git] / drivers / pci / ecam.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2016 Broadcom
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>
11 #include <linux/pci-ecam.h>
12 #include <linux/slab.h>
13
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  */
19 static const bool per_bus_mapping = !IS_ENABLED(CONFIG_64BIT);
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,
29                 const struct pci_ecam_ops *ops)
30 {
31         unsigned int bus_shift = ops->bus_shift;
32         struct pci_config_window *cfg;
33         unsigned int bus_range, bus_range_max, bsz;
34         struct resource *conflict;
35         int 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
44         /* ECAM-compliant platforms need not supply ops->bus_shift */
45         if (!bus_shift)
46                 bus_shift = PCIE_ECAM_BUS_SHIFT;
47
48         cfg->parent = dev;
49         cfg->ops = ops;
50         cfg->busr.start = busr->start;
51         cfg->busr.end = busr->end;
52         cfg->busr.flags = IORESOURCE_BUS;
53         cfg->bus_shift = bus_shift;
54         bus_range = resource_size(&cfg->busr);
55         bus_range_max = resource_size(cfgres) >> bus_shift;
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         }
62         bsz = 1 << bus_shift;
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;
81         } else {
82                 cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz);
83                 if (!cfg->win)
84                         goto err_exit_iomap;
85         }
86
87         if (ops->init) {
88                 err = ops->init(cfg);
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 }
103 EXPORT_SYMBOL_GPL(pci_ecam_create);
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 }
124 EXPORT_SYMBOL_GPL(pci_ecam_free);
125
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
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;
171         unsigned int bus_shift = cfg->ops->bus_shift;
172         unsigned int devfn_shift = cfg->ops->bus_shift - 8;
173         unsigned int busn = bus->number;
174         void __iomem *base;
175         u32 bus_offset, devfn_offset;
176
177         if (busn < cfg->busr.start || busn > cfg->busr.end)
178                 return NULL;
179
180         busn -= cfg->busr.start;
181         if (per_bus_mapping) {
182                 base = cfg->winp[busn];
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);
196 }
197 EXPORT_SYMBOL_GPL(pci_ecam_map_bus);
198
199 /* ECAM ops */
200 const struct pci_ecam_ops pci_generic_ecam_ops = {
201         .pci_ops        = {
202                 .add_bus        = pci_ecam_add_bus,
203                 .remove_bus     = pci_ecam_remove_bus,
204                 .map_bus        = pci_ecam_map_bus,
205                 .read           = pci_generic_config_read,
206                 .write          = pci_generic_config_write,
207         }
208 };
209 EXPORT_SYMBOL_GPL(pci_generic_ecam_ops);
210
211 #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
212 /* ECAM ops for 32-bit access only (non-compliant) */
213 const struct pci_ecam_ops pci_32b_ops = {
214         .pci_ops        = {
215                 .add_bus        = pci_ecam_add_bus,
216                 .remove_bus     = pci_ecam_remove_bus,
217                 .map_bus        = pci_ecam_map_bus,
218                 .read           = pci_generic_config_read32,
219                 .write          = pci_generic_config_write32,
220         }
221 };
222
223 /* ECAM ops for 32-bit read only (non-compliant) */
224 const struct pci_ecam_ops pci_32b_read_ops = {
225         .pci_ops        = {
226                 .add_bus        = pci_ecam_add_bus,
227                 .remove_bus     = pci_ecam_remove_bus,
228                 .map_bus        = pci_ecam_map_bus,
229                 .read           = pci_generic_config_read32,
230                 .write          = pci_generic_config_write,
231         }
232 };
233 #endif