1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Loongson PCI Host Controller Driver |
4 | * |
5 | * Copyright (C) 2020 Jiaxun Yang <jiaxun.yang@flygoat.com> |
6 | */ |
7 | |
8 | #include <linux/of.h> |
9 | #include <linux/of_pci.h> |
10 | #include <linux/pci.h> |
11 | #include <linux/pci_ids.h> |
12 | #include <linux/pci-acpi.h> |
13 | #include <linux/pci-ecam.h> |
14 | |
15 | #include "../pci.h" |
16 | |
17 | /* Device IDs */ |
18 | #define DEV_LS2K_PCIE_PORT0 0x1a05 |
19 | #define DEV_LS7A_PCIE_PORT0 0x7a09 |
20 | #define DEV_LS7A_PCIE_PORT1 0x7a19 |
21 | #define DEV_LS7A_PCIE_PORT2 0x7a29 |
22 | #define DEV_LS7A_PCIE_PORT3 0x7a39 |
23 | #define DEV_LS7A_PCIE_PORT4 0x7a49 |
24 | #define DEV_LS7A_PCIE_PORT5 0x7a59 |
25 | #define DEV_LS7A_PCIE_PORT6 0x7a69 |
26 | |
27 | #define DEV_LS2K_APB 0x7a02 |
28 | #define DEV_LS7A_GMAC 0x7a03 |
29 | #define DEV_LS7A_DC1 0x7a06 |
30 | #define DEV_LS7A_LPC 0x7a0c |
31 | #define DEV_LS7A_AHCI 0x7a08 |
32 | #define DEV_LS7A_CONF 0x7a10 |
33 | #define DEV_LS7A_GNET 0x7a13 |
34 | #define DEV_LS7A_EHCI 0x7a14 |
35 | #define DEV_LS7A_DC2 0x7a36 |
36 | #define DEV_LS7A_HDMI 0x7a37 |
37 | |
38 | #define FLAG_CFG0 BIT(0) |
39 | #define FLAG_CFG1 BIT(1) |
40 | #define FLAG_DEV_FIX BIT(2) |
41 | #define FLAG_DEV_HIDDEN BIT(3) |
42 | |
43 | struct loongson_pci_data { |
44 | u32 flags; |
45 | struct pci_ops *ops; |
46 | }; |
47 | |
48 | struct loongson_pci { |
49 | void __iomem *cfg0_base; |
50 | void __iomem *cfg1_base; |
51 | struct platform_device *pdev; |
52 | const struct loongson_pci_data *data; |
53 | }; |
54 | |
55 | /* Fixup wrong class code in PCIe bridges */ |
56 | static void bridge_class_quirk(struct pci_dev *dev) |
57 | { |
58 | dev->class = PCI_CLASS_BRIDGE_PCI_NORMAL; |
59 | } |
60 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
61 | DEV_LS7A_PCIE_PORT0, bridge_class_quirk); |
62 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
63 | DEV_LS7A_PCIE_PORT1, bridge_class_quirk); |
64 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
65 | DEV_LS7A_PCIE_PORT2, bridge_class_quirk); |
66 | |
67 | static void system_bus_quirk(struct pci_dev *pdev) |
68 | { |
69 | /* |
70 | * The address space consumed by these devices is outside the |
71 | * resources of the host bridge. |
72 | */ |
73 | pdev->mmio_always_on = 1; |
74 | pdev->non_compliant_bars = 1; |
75 | } |
76 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
77 | DEV_LS2K_APB, system_bus_quirk); |
78 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
79 | DEV_LS7A_CONF, system_bus_quirk); |
80 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
81 | DEV_LS7A_LPC, system_bus_quirk); |
82 | |
83 | /* |
84 | * Some Loongson PCIe ports have hardware limitations on their Maximum Read |
85 | * Request Size. They can't handle anything larger than this. Sane |
86 | * firmware will set proper MRRS at boot, so we only need no_inc_mrrs for |
87 | * bridges. However, some MIPS Loongson firmware doesn't set MRRS properly, |
88 | * so we have to enforce maximum safe MRRS, which is 256 bytes. |
89 | */ |
90 | #ifdef CONFIG_MIPS |
91 | static void loongson_set_min_mrrs_quirk(struct pci_dev *pdev) |
92 | { |
93 | struct pci_bus *bus = pdev->bus; |
94 | struct pci_dev *bridge; |
95 | static const struct pci_device_id bridge_devids[] = { |
96 | { PCI_VDEVICE(LOONGSON, DEV_LS2K_PCIE_PORT0) }, |
97 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT0) }, |
98 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT1) }, |
99 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT2) }, |
100 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT3) }, |
101 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT4) }, |
102 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT5) }, |
103 | { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT6) }, |
104 | { 0, }, |
105 | }; |
106 | |
107 | /* look for the matching bridge */ |
108 | while (!pci_is_root_bus(bus)) { |
109 | bridge = bus->self; |
110 | bus = bus->parent; |
111 | |
112 | if (pci_match_id(bridge_devids, bridge)) { |
113 | if (pcie_get_readrq(pdev) > 256) { |
114 | pci_info(pdev, "limiting MRRS to 256\n" ); |
115 | pcie_set_readrq(pdev, 256); |
116 | } |
117 | break; |
118 | } |
119 | } |
120 | } |
121 | DECLARE_PCI_FIXUP_ENABLE(PCI_ANY_ID, PCI_ANY_ID, loongson_set_min_mrrs_quirk); |
122 | #endif |
123 | |
124 | static void loongson_mrrs_quirk(struct pci_dev *pdev) |
125 | { |
126 | struct pci_host_bridge *bridge = pci_find_host_bridge(bus: pdev->bus); |
127 | |
128 | bridge->no_inc_mrrs = 1; |
129 | } |
130 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
131 | DEV_LS2K_PCIE_PORT0, loongson_mrrs_quirk); |
132 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
133 | DEV_LS7A_PCIE_PORT0, loongson_mrrs_quirk); |
134 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
135 | DEV_LS7A_PCIE_PORT1, loongson_mrrs_quirk); |
136 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
137 | DEV_LS7A_PCIE_PORT2, loongson_mrrs_quirk); |
138 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
139 | DEV_LS7A_PCIE_PORT3, loongson_mrrs_quirk); |
140 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
141 | DEV_LS7A_PCIE_PORT4, loongson_mrrs_quirk); |
142 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
143 | DEV_LS7A_PCIE_PORT5, loongson_mrrs_quirk); |
144 | DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, |
145 | DEV_LS7A_PCIE_PORT6, loongson_mrrs_quirk); |
146 | |
147 | static void loongson_pci_pin_quirk(struct pci_dev *pdev) |
148 | { |
149 | pdev->pin = 1 + (PCI_FUNC(pdev->devfn) & 3); |
150 | } |
151 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
152 | DEV_LS7A_DC1, loongson_pci_pin_quirk); |
153 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
154 | DEV_LS7A_DC2, loongson_pci_pin_quirk); |
155 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
156 | DEV_LS7A_GMAC, loongson_pci_pin_quirk); |
157 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
158 | DEV_LS7A_AHCI, loongson_pci_pin_quirk); |
159 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
160 | DEV_LS7A_EHCI, loongson_pci_pin_quirk); |
161 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
162 | DEV_LS7A_GNET, loongson_pci_pin_quirk); |
163 | DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, |
164 | DEV_LS7A_HDMI, loongson_pci_pin_quirk); |
165 | |
166 | static struct loongson_pci *pci_bus_to_loongson_pci(struct pci_bus *bus) |
167 | { |
168 | struct pci_config_window *cfg; |
169 | |
170 | if (acpi_disabled) |
171 | return (struct loongson_pci *)(bus->sysdata); |
172 | |
173 | cfg = bus->sysdata; |
174 | return (struct loongson_pci *)(cfg->priv); |
175 | } |
176 | |
177 | static void __iomem *cfg0_map(struct loongson_pci *priv, struct pci_bus *bus, |
178 | unsigned int devfn, int where) |
179 | { |
180 | unsigned long addroff = 0x0; |
181 | unsigned char busnum = bus->number; |
182 | |
183 | if (!pci_is_root_bus(pbus: bus)) { |
184 | addroff |= BIT(24); /* Type 1 Access */ |
185 | addroff |= (busnum << 16); |
186 | } |
187 | addroff |= (devfn << 8) | where; |
188 | return priv->cfg0_base + addroff; |
189 | } |
190 | |
191 | static void __iomem *cfg1_map(struct loongson_pci *priv, struct pci_bus *bus, |
192 | unsigned int devfn, int where) |
193 | { |
194 | unsigned long addroff = 0x0; |
195 | unsigned char busnum = bus->number; |
196 | |
197 | if (!pci_is_root_bus(pbus: bus)) { |
198 | addroff |= BIT(28); /* Type 1 Access */ |
199 | addroff |= (busnum << 16); |
200 | } |
201 | addroff |= (devfn << 8) | (where & 0xff) | ((where & 0xf00) << 16); |
202 | return priv->cfg1_base + addroff; |
203 | } |
204 | |
205 | static bool pdev_may_exist(struct pci_bus *bus, unsigned int device, |
206 | unsigned int function) |
207 | { |
208 | return !(pci_is_root_bus(pbus: bus) && |
209 | (device >= 9 && device <= 20) && (function > 0)); |
210 | } |
211 | |
212 | static void __iomem *pci_loongson_map_bus(struct pci_bus *bus, |
213 | unsigned int devfn, int where) |
214 | { |
215 | unsigned int device = PCI_SLOT(devfn); |
216 | unsigned int function = PCI_FUNC(devfn); |
217 | struct loongson_pci *priv = pci_bus_to_loongson_pci(bus); |
218 | |
219 | /* |
220 | * Do not read more than one device on the bus other than |
221 | * the host bus. |
222 | */ |
223 | if ((priv->data->flags & FLAG_DEV_FIX) && bus->self) { |
224 | if (!pci_is_root_bus(pbus: bus) && (device > 0)) |
225 | return NULL; |
226 | } |
227 | |
228 | /* Don't access non-existent devices */ |
229 | if (priv->data->flags & FLAG_DEV_HIDDEN) { |
230 | if (!pdev_may_exist(bus, device, function)) |
231 | return NULL; |
232 | } |
233 | |
234 | /* CFG0 can only access standard space */ |
235 | if (where < PCI_CFG_SPACE_SIZE && priv->cfg0_base) |
236 | return cfg0_map(priv, bus, devfn, where); |
237 | |
238 | /* CFG1 can access extended space */ |
239 | if (where < PCI_CFG_SPACE_EXP_SIZE && priv->cfg1_base) |
240 | return cfg1_map(priv, bus, devfn, where); |
241 | |
242 | return NULL; |
243 | } |
244 | |
245 | #ifdef CONFIG_OF |
246 | |
247 | static int loongson_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) |
248 | { |
249 | int irq; |
250 | u8 val; |
251 | |
252 | irq = of_irq_parse_and_map_pci(dev, slot, pin); |
253 | if (irq > 0) |
254 | return irq; |
255 | |
256 | /* Care i8259 legacy systems */ |
257 | pci_read_config_byte(dev, PCI_INTERRUPT_LINE, val: &val); |
258 | /* i8259 only have 15 IRQs */ |
259 | if (val > 15) |
260 | return 0; |
261 | |
262 | return val; |
263 | } |
264 | |
265 | /* LS2K/LS7A accept 8/16/32-bit PCI config operations */ |
266 | static struct pci_ops loongson_pci_ops = { |
267 | .map_bus = pci_loongson_map_bus, |
268 | .read = pci_generic_config_read, |
269 | .write = pci_generic_config_write, |
270 | }; |
271 | |
272 | /* RS780/SR5690 only accept 32-bit PCI config operations */ |
273 | static struct pci_ops loongson_pci_ops32 = { |
274 | .map_bus = pci_loongson_map_bus, |
275 | .read = pci_generic_config_read32, |
276 | .write = pci_generic_config_write32, |
277 | }; |
278 | |
279 | static const struct loongson_pci_data ls2k_pci_data = { |
280 | .flags = FLAG_CFG1 | FLAG_DEV_FIX | FLAG_DEV_HIDDEN, |
281 | .ops = &loongson_pci_ops, |
282 | }; |
283 | |
284 | static const struct loongson_pci_data ls7a_pci_data = { |
285 | .flags = FLAG_CFG1 | FLAG_DEV_FIX | FLAG_DEV_HIDDEN, |
286 | .ops = &loongson_pci_ops, |
287 | }; |
288 | |
289 | static const struct loongson_pci_data rs780e_pci_data = { |
290 | .flags = FLAG_CFG0, |
291 | .ops = &loongson_pci_ops32, |
292 | }; |
293 | |
294 | static const struct of_device_id loongson_pci_of_match[] = { |
295 | { .compatible = "loongson,ls2k-pci" , |
296 | .data = &ls2k_pci_data, }, |
297 | { .compatible = "loongson,ls7a-pci" , |
298 | .data = &ls7a_pci_data, }, |
299 | { .compatible = "loongson,rs780e-pci" , |
300 | .data = &rs780e_pci_data, }, |
301 | {} |
302 | }; |
303 | |
304 | static int loongson_pci_probe(struct platform_device *pdev) |
305 | { |
306 | struct loongson_pci *priv; |
307 | struct device *dev = &pdev->dev; |
308 | struct device_node *node = dev->of_node; |
309 | struct pci_host_bridge *bridge; |
310 | struct resource *regs; |
311 | |
312 | if (!node) |
313 | return -ENODEV; |
314 | |
315 | bridge = devm_pci_alloc_host_bridge(dev, priv: sizeof(*priv)); |
316 | if (!bridge) |
317 | return -ENODEV; |
318 | |
319 | priv = pci_host_bridge_priv(bridge); |
320 | priv->pdev = pdev; |
321 | priv->data = of_device_get_match_data(dev); |
322 | |
323 | if (priv->data->flags & FLAG_CFG0) { |
324 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
325 | if (!regs) |
326 | dev_err(dev, "missing mem resources for cfg0\n" ); |
327 | else { |
328 | priv->cfg0_base = devm_pci_remap_cfg_resource(dev, res: regs); |
329 | if (IS_ERR(ptr: priv->cfg0_base)) |
330 | return PTR_ERR(ptr: priv->cfg0_base); |
331 | } |
332 | } |
333 | |
334 | if (priv->data->flags & FLAG_CFG1) { |
335 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
336 | if (!regs) |
337 | dev_info(dev, "missing mem resource for cfg1\n" ); |
338 | else { |
339 | priv->cfg1_base = devm_pci_remap_cfg_resource(dev, res: regs); |
340 | if (IS_ERR(ptr: priv->cfg1_base)) |
341 | priv->cfg1_base = NULL; |
342 | } |
343 | } |
344 | |
345 | bridge->sysdata = priv; |
346 | bridge->ops = priv->data->ops; |
347 | bridge->map_irq = loongson_map_irq; |
348 | |
349 | return pci_host_probe(bridge); |
350 | } |
351 | |
352 | static struct platform_driver loongson_pci_driver = { |
353 | .driver = { |
354 | .name = "loongson-pci" , |
355 | .of_match_table = loongson_pci_of_match, |
356 | }, |
357 | .probe = loongson_pci_probe, |
358 | }; |
359 | builtin_platform_driver(loongson_pci_driver); |
360 | |
361 | #endif |
362 | |
363 | #ifdef CONFIG_ACPI |
364 | |
365 | static int loongson_pci_ecam_init(struct pci_config_window *cfg) |
366 | { |
367 | struct device *dev = cfg->parent; |
368 | struct loongson_pci *priv; |
369 | struct loongson_pci_data *data; |
370 | |
371 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
372 | if (!priv) |
373 | return -ENOMEM; |
374 | |
375 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
376 | if (!data) |
377 | return -ENOMEM; |
378 | |
379 | cfg->priv = priv; |
380 | data->flags = FLAG_CFG1 | FLAG_DEV_HIDDEN; |
381 | priv->data = data; |
382 | priv->cfg1_base = cfg->win - (cfg->busr.start << 16); |
383 | |
384 | return 0; |
385 | } |
386 | |
387 | const struct pci_ecam_ops loongson_pci_ecam_ops = { |
388 | .bus_shift = 16, |
389 | .init = loongson_pci_ecam_init, |
390 | .pci_ops = { |
391 | .map_bus = pci_loongson_map_bus, |
392 | .read = pci_generic_config_read, |
393 | .write = pci_generic_config_write, |
394 | } |
395 | }; |
396 | |
397 | #endif |
398 | |