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, | |
29 | struct pci_ecam_ops *ops) | |
30 | { | |
31 | struct pci_config_window *cfg; | |
32 | unsigned int bus_range, bus_range_max, bsz; | |
33 | struct resource *conflict; | |
34 | int i, err; | |
35 | ||
36 | if (busr->start > busr->end) | |
37 | return ERR_PTR(-EINVAL); | |
38 | ||
39 | cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); | |
40 | if (!cfg) | |
41 | return ERR_PTR(-ENOMEM); | |
42 | ||
5c3d14f7 | 43 | cfg->parent = dev; |
35ff9477 J |
44 | cfg->ops = ops; |
45 | cfg->busr.start = busr->start; | |
46 | cfg->busr.end = busr->end; | |
47 | cfg->busr.flags = IORESOURCE_BUS; | |
48 | bus_range = resource_size(&cfg->busr); | |
49 | bus_range_max = resource_size(cfgres) >> ops->bus_shift; | |
50 | if (bus_range > bus_range_max) { | |
51 | bus_range = bus_range_max; | |
52 | cfg->busr.end = busr->start + bus_range - 1; | |
53 | dev_warn(dev, "ECAM area %pR can only accommodate %pR (reduced from %pR desired)\n", | |
54 | cfgres, &cfg->busr, busr); | |
55 | } | |
56 | bsz = 1 << ops->bus_shift; | |
57 | ||
58 | cfg->res.start = cfgres->start; | |
59 | cfg->res.end = cfgres->end; | |
60 | cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; | |
61 | cfg->res.name = "PCI ECAM"; | |
62 | ||
63 | conflict = request_resource_conflict(&iomem_resource, &cfg->res); | |
64 | if (conflict) { | |
65 | err = -EBUSY; | |
66 | dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n", | |
67 | &cfg->res, conflict->name, conflict); | |
68 | goto err_exit; | |
69 | } | |
70 | ||
71 | if (per_bus_mapping) { | |
72 | cfg->winp = kcalloc(bus_range, sizeof(*cfg->winp), GFP_KERNEL); | |
73 | if (!cfg->winp) | |
74 | goto err_exit_malloc; | |
75 | for (i = 0; i < bus_range; i++) { | |
053497ce LP |
76 | cfg->winp[i] = |
77 | pci_remap_cfgspace(cfgres->start + i * bsz, | |
78 | bsz); | |
35ff9477 J |
79 | if (!cfg->winp[i]) |
80 | goto err_exit_iomap; | |
81 | } | |
82 | } else { | |
053497ce | 83 | cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz); |
35ff9477 J |
84 | if (!cfg->win) |
85 | goto err_exit_iomap; | |
86 | } | |
87 | ||
88 | if (ops->init) { | |
5c3d14f7 | 89 | err = ops->init(cfg); |
35ff9477 J |
90 | if (err) |
91 | goto err_exit; | |
92 | } | |
93 | dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr); | |
94 | return cfg; | |
95 | ||
96 | err_exit_iomap: | |
97 | dev_err(dev, "ECAM ioremap failed\n"); | |
98 | err_exit_malloc: | |
99 | err = -ENOMEM; | |
100 | err_exit: | |
101 | pci_ecam_free(cfg); | |
102 | return ERR_PTR(err); | |
103 | } | |
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 | ||
125 | /* | |
126 | * Function to implement the pci_ops ->map_bus method | |
127 | */ | |
128 | void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn, | |
129 | int where) | |
130 | { | |
131 | struct pci_config_window *cfg = bus->sysdata; | |
132 | unsigned int devfn_shift = cfg->ops->bus_shift - 8; | |
133 | unsigned int busn = bus->number; | |
134 | void __iomem *base; | |
135 | ||
136 | if (busn < cfg->busr.start || busn > cfg->busr.end) | |
137 | return NULL; | |
138 | ||
139 | busn -= cfg->busr.start; | |
140 | if (per_bus_mapping) | |
141 | base = cfg->winp[busn]; | |
142 | else | |
143 | base = cfg->win + (busn << cfg->ops->bus_shift); | |
144 | return base + (devfn << devfn_shift) + where; | |
145 | } | |
146 | ||
147 | /* ECAM ops */ | |
148 | struct pci_ecam_ops pci_generic_ecam_ops = { | |
149 | .bus_shift = 20, | |
150 | .pci_ops = { | |
151 | .map_bus = pci_ecam_map_bus, | |
152 | .read = pci_generic_config_read, | |
153 | .write = pci_generic_config_write, | |
154 | } | |
155 | }; | |
2ca5b8dd CC |
156 | |
157 | #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) | |
158 | /* ECAM ops for 32-bit access only (non-compliant) */ | |
159 | struct pci_ecam_ops pci_32b_ops = { | |
160 | .bus_shift = 20, | |
161 | .pci_ops = { | |
162 | .map_bus = pci_ecam_map_bus, | |
163 | .read = pci_generic_config_read32, | |
164 | .write = pci_generic_config_write32, | |
165 | } | |
166 | }; | |
167 | #endif |