Merge branch 'remotes/lorenzo/pci/brcmstb'
[linux-2.6-microblaze.git] / drivers / pci / controller / pci-loongson.c
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_device.h>
9 #include <linux/of_pci.h>
10 #include <linux/pci.h>
11 #include <linux/pci_ids.h>
12
13 #include "../pci.h"
14
15 /* Device IDs */
16 #define DEV_PCIE_PORT_0 0x7a09
17 #define DEV_PCIE_PORT_1 0x7a19
18 #define DEV_PCIE_PORT_2 0x7a29
19
20 #define DEV_LS2K_APB    0x7a02
21 #define DEV_LS7A_CONF   0x7a10
22 #define DEV_LS7A_LPC    0x7a0c
23
24 #define FLAG_CFG0       BIT(0)
25 #define FLAG_CFG1       BIT(1)
26 #define FLAG_DEV_FIX    BIT(2)
27
28 struct loongson_pci {
29         void __iomem *cfg0_base;
30         void __iomem *cfg1_base;
31         struct platform_device *pdev;
32         u32 flags;
33 };
34
35 /* Fixup wrong class code in PCIe bridges */
36 static void bridge_class_quirk(struct pci_dev *dev)
37 {
38         dev->class = PCI_CLASS_BRIDGE_PCI << 8;
39 }
40 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
41                         DEV_PCIE_PORT_0, bridge_class_quirk);
42 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
43                         DEV_PCIE_PORT_1, bridge_class_quirk);
44 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
45                         DEV_PCIE_PORT_2, bridge_class_quirk);
46
47 static void system_bus_quirk(struct pci_dev *pdev)
48 {
49         /*
50          * The address space consumed by these devices is outside the
51          * resources of the host bridge.
52          */
53         pdev->mmio_always_on = 1;
54         pdev->non_compliant_bars = 1;
55 }
56 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
57                         DEV_LS2K_APB, system_bus_quirk);
58 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
59                         DEV_LS7A_CONF, system_bus_quirk);
60 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
61                         DEV_LS7A_LPC, system_bus_quirk);
62
63 static void loongson_mrrs_quirk(struct pci_dev *dev)
64 {
65         struct pci_bus *bus = dev->bus;
66         struct pci_dev *bridge;
67         static const struct pci_device_id bridge_devids[] = {
68                 { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_0) },
69                 { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_1) },
70                 { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_2) },
71                 { 0, },
72         };
73
74         /* look for the matching bridge */
75         while (!pci_is_root_bus(bus)) {
76                 bridge = bus->self;
77                 bus = bus->parent;
78                 /*
79                  * Some Loongson PCIe ports have a h/w limitation of
80                  * 256 bytes maximum read request size. They can't handle
81                  * anything larger than this. So force this limit on
82                  * any devices attached under these ports.
83                  */
84                 if (pci_match_id(bridge_devids, bridge)) {
85                         if (pcie_get_readrq(dev) > 256) {
86                                 pci_info(dev, "limiting MRRS to 256\n");
87                                 pcie_set_readrq(dev, 256);
88                         }
89                         break;
90                 }
91         }
92 }
93 DECLARE_PCI_FIXUP_ENABLE(PCI_ANY_ID, PCI_ANY_ID, loongson_mrrs_quirk);
94
95 static void __iomem *cfg1_map(struct loongson_pci *priv, int bus,
96                                 unsigned int devfn, int where)
97 {
98         unsigned long addroff = 0x0;
99
100         if (bus != 0)
101                 addroff |= BIT(28); /* Type 1 Access */
102         addroff |= (where & 0xff) | ((where & 0xf00) << 16);
103         addroff |= (bus << 16) | (devfn << 8);
104         return priv->cfg1_base + addroff;
105 }
106
107 static void __iomem *cfg0_map(struct loongson_pci *priv, int bus,
108                                 unsigned int devfn, int where)
109 {
110         unsigned long addroff = 0x0;
111
112         if (bus != 0)
113                 addroff |= BIT(24); /* Type 1 Access */
114         addroff |= (bus << 16) | (devfn << 8) | where;
115         return priv->cfg0_base + addroff;
116 }
117
118 static void __iomem *pci_loongson_map_bus(struct pci_bus *bus, unsigned int devfn,
119                                int where)
120 {
121         unsigned char busnum = bus->number;
122         struct pci_host_bridge *bridge = pci_find_host_bridge(bus);
123         struct loongson_pci *priv =  pci_host_bridge_priv(bridge);
124
125         /*
126          * Do not read more than one device on the bus other than
127          * the host bus. For our hardware the root bus is always bus 0.
128          */
129         if (priv->flags & FLAG_DEV_FIX && busnum != 0 &&
130                 PCI_SLOT(devfn) > 0)
131                 return NULL;
132
133         /* CFG0 can only access standard space */
134         if (where < PCI_CFG_SPACE_SIZE && priv->cfg0_base)
135                 return cfg0_map(priv, busnum, devfn, where);
136
137         /* CFG1 can access extended space */
138         if (where < PCI_CFG_SPACE_EXP_SIZE && priv->cfg1_base)
139                 return cfg1_map(priv, busnum, devfn, where);
140
141         return NULL;
142 }
143
144 static int loongson_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
145 {
146         int irq;
147         u8 val;
148
149         irq = of_irq_parse_and_map_pci(dev, slot, pin);
150         if (irq > 0)
151                 return irq;
152
153         /* Care i8259 legacy systems */
154         pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &val);
155         /* i8259 only have 15 IRQs */
156         if (val > 15)
157                 return 0;
158
159         return val;
160 }
161
162 /* H/w only accept 32-bit PCI operations */
163 static struct pci_ops loongson_pci_ops = {
164         .map_bus = pci_loongson_map_bus,
165         .read   = pci_generic_config_read32,
166         .write  = pci_generic_config_write32,
167 };
168
169 static const struct of_device_id loongson_pci_of_match[] = {
170         { .compatible = "loongson,ls2k-pci",
171                 .data = (void *)(FLAG_CFG0 | FLAG_CFG1 | FLAG_DEV_FIX), },
172         { .compatible = "loongson,ls7a-pci",
173                 .data = (void *)(FLAG_CFG0 | FLAG_CFG1 | FLAG_DEV_FIX), },
174         { .compatible = "loongson,rs780e-pci",
175                 .data = (void *)(FLAG_CFG0), },
176         {}
177 };
178
179 static int loongson_pci_probe(struct platform_device *pdev)
180 {
181         struct loongson_pci *priv;
182         struct device *dev = &pdev->dev;
183         struct device_node *node = dev->of_node;
184         struct pci_host_bridge *bridge;
185         struct resource *regs;
186         int err;
187
188         if (!node)
189                 return -ENODEV;
190
191         bridge = devm_pci_alloc_host_bridge(dev, sizeof(*priv));
192         if (!bridge)
193                 return -ENODEV;
194
195         priv = pci_host_bridge_priv(bridge);
196         priv->pdev = pdev;
197         priv->flags = (unsigned long)of_device_get_match_data(dev);
198
199         regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
200         if (!regs) {
201                 dev_err(dev, "missing mem resources for cfg0\n");
202                 return -EINVAL;
203         }
204
205         priv->cfg0_base = devm_pci_remap_cfg_resource(dev, regs);
206         if (IS_ERR(priv->cfg0_base))
207                 return PTR_ERR(priv->cfg0_base);
208
209         /* CFG1 is optional */
210         if (priv->flags & FLAG_CFG1) {
211                 regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
212                 if (!regs)
213                         dev_info(dev, "missing mem resource for cfg1\n");
214                 else {
215                         priv->cfg1_base = devm_pci_remap_cfg_resource(dev, regs);
216                         if (IS_ERR(priv->cfg1_base))
217                                 priv->cfg1_base = NULL;
218                 }
219         }
220
221         bridge->sysdata = priv;
222         bridge->ops = &loongson_pci_ops;
223         bridge->map_irq = loongson_map_irq;
224
225         err = pci_host_probe(bridge);
226         if (err)
227                 return err;
228
229         return 0;
230 }
231
232 static struct platform_driver loongson_pci_driver = {
233         .driver = {
234                 .name = "loongson-pci",
235                 .of_match_table = loongson_pci_of_match,
236         },
237         .probe = loongson_pci_probe,
238 };
239 builtin_platform_driver(loongson_pci_driver);