Commit | Line | Data |
---|---|---|
5f32f7a0 AV |
1 | /* |
2 | * PCI-E support for CNS3xxx | |
3 | * | |
4 | * Copyright 2008 Cavium Networks | |
5 | * Richard Liu <richard.liu@caviumnetworks.com> | |
6 | * Copyright 2010 MontaVista Software, LLC. | |
7 | * Anton Vorontsov <avorontsov@mvista.com> | |
8 | * | |
9 | * This file is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License, Version 2, as | |
11 | * published by the Free Software Foundation. | |
12 | */ | |
13 | ||
14 | #include <linux/init.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/bug.h> | |
17 | #include <linux/pci.h> | |
18 | #include <linux/io.h> | |
19 | #include <linux/ioport.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/ptrace.h> | |
22 | #include <asm/mach/map.h> | |
3f9fb2a0 | 23 | #include "cns3xxx.h" |
5f32f7a0 AV |
24 | #include "core.h" |
25 | ||
5f32f7a0 | 26 | struct cns3xxx_pcie { |
64cf9d07 KH |
27 | void __iomem *host_regs; /* PCI config registers for host bridge */ |
28 | void __iomem *cfg0_regs; /* PCI Type 0 config registers */ | |
29 | void __iomem *cfg1_regs; /* PCI Type 1 config registers */ | |
5f32f7a0 AV |
30 | unsigned int irqs[2]; |
31 | struct resource res_io; | |
32 | struct resource res_mem; | |
33 | struct hw_pci hw_pci; | |
34 | ||
35 | bool linked; | |
36 | }; | |
37 | ||
38 | static struct cns3xxx_pcie cns3xxx_pcie[]; /* forward decl. */ | |
39 | ||
40 | static struct cns3xxx_pcie *sysdata_to_cnspci(void *sysdata) | |
41 | { | |
42 | struct pci_sys_data *root = sysdata; | |
43 | ||
44 | return &cns3xxx_pcie[root->domain]; | |
45 | } | |
46 | ||
7caaf7ef | 47 | static struct cns3xxx_pcie *pdev_to_cnspci(const struct pci_dev *dev) |
5f32f7a0 AV |
48 | { |
49 | return sysdata_to_cnspci(dev->sysdata); | |
50 | } | |
51 | ||
52 | static struct cns3xxx_pcie *pbus_to_cnspci(struct pci_bus *bus) | |
53 | { | |
54 | return sysdata_to_cnspci(bus->sysdata); | |
55 | } | |
56 | ||
57 | static void __iomem *cns3xxx_pci_cfg_base(struct pci_bus *bus, | |
58 | unsigned int devfn, int where) | |
59 | { | |
60 | struct cns3xxx_pcie *cnspci = pbus_to_cnspci(bus); | |
61 | int busno = bus->number; | |
62 | int slot = PCI_SLOT(devfn); | |
63 | int offset; | |
5f32f7a0 AV |
64 | void __iomem *base; |
65 | ||
66 | /* If there is no link, just show the CNS PCI bridge. */ | |
67 | if (!cnspci->linked && (busno > 0 || slot > 0)) | |
68 | return NULL; | |
69 | ||
70 | /* | |
71 | * The CNS PCI bridge doesn't fit into the PCI hierarchy, though | |
72 | * we still want to access it. For this to work, we must place | |
73 | * the first device on the same bus as the CNS PCI bridge. | |
74 | */ | |
64cf9d07 KH |
75 | if (busno == 0) { /* directly connected PCIe bus */ |
76 | switch (slot) { | |
77 | case 0: /* host bridge device, function 0 only */ | |
78 | base = cnspci->host_regs; | |
79 | break; | |
80 | case 1: /* directly connected device */ | |
81 | base = cnspci->cfg0_regs; | |
82 | break; | |
83 | default: | |
84 | return NULL; /* no such device */ | |
85 | } | |
86 | } else /* remote PCI bus */ | |
87 | base = cnspci->cfg1_regs; | |
5f32f7a0 | 88 | |
5f32f7a0 | 89 | offset = ((busno & 0xf) << 20) | (devfn << 12) | (where & 0xffc); |
5f32f7a0 AV |
90 | return base + offset; |
91 | } | |
92 | ||
93 | static int cns3xxx_pci_read_config(struct pci_bus *bus, unsigned int devfn, | |
94 | int where, int size, u32 *val) | |
95 | { | |
96 | u32 v; | |
97 | void __iomem *base; | |
98 | u32 mask = (0x1ull << (size * 8)) - 1; | |
99 | int shift = (where % 4) * 8; | |
100 | ||
101 | base = cns3xxx_pci_cfg_base(bus, devfn, where); | |
102 | if (!base) { | |
103 | *val = 0xffffffff; | |
104 | return PCIBIOS_SUCCESSFUL; | |
105 | } | |
106 | ||
107 | v = __raw_readl(base); | |
108 | ||
109 | if (bus->number == 0 && devfn == 0 && | |
110 | (where & 0xffc) == PCI_CLASS_REVISION) { | |
111 | /* | |
112 | * RC's class is 0xb, but Linux PCI driver needs 0x604 | |
113 | * for a PCIe bridge. So we must fixup the class code | |
114 | * to 0x604 here. | |
115 | */ | |
116 | v &= 0xff; | |
117 | v |= 0x604 << 16; | |
118 | } | |
119 | ||
120 | *val = (v >> shift) & mask; | |
121 | ||
122 | return PCIBIOS_SUCCESSFUL; | |
123 | } | |
124 | ||
125 | static int cns3xxx_pci_write_config(struct pci_bus *bus, unsigned int devfn, | |
126 | int where, int size, u32 val) | |
127 | { | |
128 | u32 v; | |
129 | void __iomem *base; | |
130 | u32 mask = (0x1ull << (size * 8)) - 1; | |
131 | int shift = (where % 4) * 8; | |
132 | ||
133 | base = cns3xxx_pci_cfg_base(bus, devfn, where); | |
134 | if (!base) | |
135 | return PCIBIOS_SUCCESSFUL; | |
136 | ||
137 | v = __raw_readl(base); | |
138 | ||
139 | v &= ~(mask << shift); | |
140 | v |= (val & mask) << shift; | |
141 | ||
142 | __raw_writel(v, base); | |
143 | ||
144 | return PCIBIOS_SUCCESSFUL; | |
145 | } | |
146 | ||
147 | static int cns3xxx_pci_setup(int nr, struct pci_sys_data *sys) | |
148 | { | |
149 | struct cns3xxx_pcie *cnspci = sysdata_to_cnspci(sys); | |
150 | struct resource *res_io = &cnspci->res_io; | |
151 | struct resource *res_mem = &cnspci->res_mem; | |
5f32f7a0 AV |
152 | |
153 | BUG_ON(request_resource(&iomem_resource, res_io) || | |
154 | request_resource(&iomem_resource, res_mem)); | |
155 | ||
9f786d03 BH |
156 | pci_add_resource_offset(&sys->resources, res_io, sys->io_offset); |
157 | pci_add_resource_offset(&sys->resources, res_mem, sys->mem_offset); | |
5f32f7a0 AV |
158 | |
159 | return 1; | |
160 | } | |
161 | ||
162 | static struct pci_ops cns3xxx_pcie_ops = { | |
163 | .read = cns3xxx_pci_read_config, | |
164 | .write = cns3xxx_pci_write_config, | |
165 | }; | |
166 | ||
d5341942 | 167 | static int cns3xxx_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) |
5f32f7a0 AV |
168 | { |
169 | struct cns3xxx_pcie *cnspci = pdev_to_cnspci(dev); | |
170 | int irq = cnspci->irqs[slot]; | |
171 | ||
172 | pr_info("PCIe map irq: %04d:%02x:%02x.%02x slot %d, pin %d, irq: %d\n", | |
173 | pci_domain_nr(dev->bus), dev->bus->number, PCI_SLOT(dev->devfn), | |
174 | PCI_FUNC(dev->devfn), slot, pin, irq); | |
175 | ||
176 | return irq; | |
177 | } | |
178 | ||
179 | static struct cns3xxx_pcie cns3xxx_pcie[] = { | |
180 | [0] = { | |
64cf9d07 KH |
181 | .host_regs = (void __iomem *)CNS3XXX_PCIE0_HOST_BASE_VIRT, |
182 | .cfg0_regs = (void __iomem *)CNS3XXX_PCIE0_CFG0_BASE_VIRT, | |
183 | .cfg1_regs = (void __iomem *)CNS3XXX_PCIE0_CFG1_BASE_VIRT, | |
5f32f7a0 AV |
184 | .res_io = { |
185 | .name = "PCIe0 I/O space", | |
186 | .start = CNS3XXX_PCIE0_IO_BASE, | |
64cf9d07 | 187 | .end = CNS3XXX_PCIE0_CFG0_BASE - 1, /* 16 MiB */ |
5f32f7a0 AV |
188 | .flags = IORESOURCE_IO, |
189 | }, | |
190 | .res_mem = { | |
191 | .name = "PCIe0 non-prefetchable", | |
192 | .start = CNS3XXX_PCIE0_MEM_BASE, | |
64cf9d07 | 193 | .end = CNS3XXX_PCIE0_HOST_BASE - 1, /* 176 MiB */ |
5f32f7a0 AV |
194 | .flags = IORESOURCE_MEM, |
195 | }, | |
196 | .irqs = { IRQ_CNS3XXX_PCIE0_RC, IRQ_CNS3XXX_PCIE0_DEVICE, }, | |
197 | .hw_pci = { | |
198 | .domain = 0, | |
5f32f7a0 | 199 | .nr_controllers = 1, |
c23bfc38 | 200 | .ops = &cns3xxx_pcie_ops, |
5f32f7a0 | 201 | .setup = cns3xxx_pci_setup, |
5f32f7a0 AV |
202 | .map_irq = cns3xxx_pcie_map_irq, |
203 | }, | |
204 | }, | |
205 | [1] = { | |
64cf9d07 KH |
206 | .host_regs = (void __iomem *)CNS3XXX_PCIE1_HOST_BASE_VIRT, |
207 | .cfg0_regs = (void __iomem *)CNS3XXX_PCIE1_CFG0_BASE_VIRT, | |
208 | .cfg1_regs = (void __iomem *)CNS3XXX_PCIE1_CFG1_BASE_VIRT, | |
5f32f7a0 AV |
209 | .res_io = { |
210 | .name = "PCIe1 I/O space", | |
211 | .start = CNS3XXX_PCIE1_IO_BASE, | |
64cf9d07 | 212 | .end = CNS3XXX_PCIE1_CFG0_BASE - 1, /* 16 MiB */ |
5f32f7a0 AV |
213 | .flags = IORESOURCE_IO, |
214 | }, | |
215 | .res_mem = { | |
216 | .name = "PCIe1 non-prefetchable", | |
217 | .start = CNS3XXX_PCIE1_MEM_BASE, | |
64cf9d07 | 218 | .end = CNS3XXX_PCIE1_HOST_BASE - 1, /* 176 MiB */ |
5f32f7a0 AV |
219 | .flags = IORESOURCE_MEM, |
220 | }, | |
221 | .irqs = { IRQ_CNS3XXX_PCIE1_RC, IRQ_CNS3XXX_PCIE1_DEVICE, }, | |
222 | .hw_pci = { | |
223 | .domain = 1, | |
5f32f7a0 | 224 | .nr_controllers = 1, |
c23bfc38 | 225 | .ops = &cns3xxx_pcie_ops, |
5f32f7a0 | 226 | .setup = cns3xxx_pci_setup, |
5f32f7a0 AV |
227 | .map_irq = cns3xxx_pcie_map_irq, |
228 | }, | |
229 | }, | |
230 | }; | |
231 | ||
232 | static void __init cns3xxx_pcie_check_link(struct cns3xxx_pcie *cnspci) | |
233 | { | |
234 | int port = cnspci->hw_pci.domain; | |
235 | u32 reg; | |
236 | unsigned long time; | |
237 | ||
238 | reg = __raw_readl(MISC_PCIE_CTRL(port)); | |
239 | /* | |
240 | * Enable Application Request to 1, it will exit L1 automatically, | |
241 | * but when chip back, it will use another clock, still can use 0x1. | |
242 | */ | |
243 | reg |= 0x3; | |
244 | __raw_writel(reg, MISC_PCIE_CTRL(port)); | |
245 | ||
246 | pr_info("PCIe: Port[%d] Enable PCIe LTSSM\n", port); | |
247 | pr_info("PCIe: Port[%d] Check data link layer...", port); | |
248 | ||
249 | time = jiffies; | |
250 | while (1) { | |
251 | reg = __raw_readl(MISC_PCIE_PM_DEBUG(port)); | |
252 | if (reg & 0x1) { | |
253 | pr_info("Link up.\n"); | |
254 | cnspci->linked = 1; | |
255 | break; | |
256 | } else if (time_after(jiffies, time + 50)) { | |
257 | pr_info("Device not found.\n"); | |
258 | break; | |
259 | } | |
260 | } | |
261 | } | |
262 | ||
263 | static void __init cns3xxx_pcie_hw_init(struct cns3xxx_pcie *cnspci) | |
264 | { | |
265 | int port = cnspci->hw_pci.domain; | |
266 | struct pci_sys_data sd = { | |
267 | .domain = port, | |
268 | }; | |
269 | struct pci_bus bus = { | |
270 | .number = 0, | |
271 | .ops = &cns3xxx_pcie_ops, | |
272 | .sysdata = &sd, | |
273 | }; | |
64cf9d07 KH |
274 | u16 mem_base = cnspci->res_mem.start >> 16; |
275 | u16 mem_limit = cnspci->res_mem.end >> 16; | |
276 | u16 io_base = cnspci->res_io.start >> 16; | |
277 | u16 io_limit = cnspci->res_io.end >> 16; | |
5f32f7a0 AV |
278 | u32 devfn = 0; |
279 | u8 tmp8; | |
280 | u16 pos; | |
281 | u16 dc; | |
282 | ||
5f32f7a0 AV |
283 | pci_bus_write_config_byte(&bus, devfn, PCI_PRIMARY_BUS, 0); |
284 | pci_bus_write_config_byte(&bus, devfn, PCI_SECONDARY_BUS, 1); | |
285 | pci_bus_write_config_byte(&bus, devfn, PCI_SUBORDINATE_BUS, 1); | |
286 | ||
287 | pci_bus_read_config_byte(&bus, devfn, PCI_PRIMARY_BUS, &tmp8); | |
288 | pci_bus_read_config_byte(&bus, devfn, PCI_SECONDARY_BUS, &tmp8); | |
289 | pci_bus_read_config_byte(&bus, devfn, PCI_SUBORDINATE_BUS, &tmp8); | |
290 | ||
291 | pci_bus_write_config_word(&bus, devfn, PCI_MEMORY_BASE, mem_base); | |
64cf9d07 | 292 | pci_bus_write_config_word(&bus, devfn, PCI_MEMORY_LIMIT, mem_limit); |
5f32f7a0 | 293 | pci_bus_write_config_word(&bus, devfn, PCI_IO_BASE_UPPER16, io_base); |
64cf9d07 | 294 | pci_bus_write_config_word(&bus, devfn, PCI_IO_LIMIT_UPPER16, io_limit); |
5f32f7a0 AV |
295 | |
296 | if (!cnspci->linked) | |
297 | return; | |
298 | ||
299 | /* Set Device Max_Read_Request_Size to 128 byte */ | |
300 | devfn = PCI_DEVFN(1, 0); | |
301 | pos = pci_bus_find_capability(&bus, devfn, PCI_CAP_ID_EXP); | |
302 | pci_bus_read_config_word(&bus, devfn, pos + PCI_EXP_DEVCTL, &dc); | |
303 | dc &= ~(0x3 << 12); /* Clear Device Control Register [14:12] */ | |
304 | pci_bus_write_config_word(&bus, devfn, pos + PCI_EXP_DEVCTL, dc); | |
305 | pci_bus_read_config_word(&bus, devfn, pos + PCI_EXP_DEVCTL, &dc); | |
306 | if (!(dc & (0x3 << 12))) | |
307 | pr_info("PCIe: Set Device Max_Read_Request_Size to 128 byte\n"); | |
308 | ||
309 | /* Disable PCIe0 Interrupt Mask INTA to INTD */ | |
310 | __raw_writel(~0x3FFF, MISC_PCIE_INT_MASK(port)); | |
311 | } | |
312 | ||
313 | static int cns3xxx_pcie_abort_handler(unsigned long addr, unsigned int fsr, | |
314 | struct pt_regs *regs) | |
315 | { | |
316 | if (fsr & (1 << 10)) | |
317 | regs->ARM_pc += 4; | |
318 | return 0; | |
319 | } | |
320 | ||
0a2e912d | 321 | void __init cns3xxx_pcie_init_late(void) |
5f32f7a0 AV |
322 | { |
323 | int i; | |
324 | ||
c9d95fbe RH |
325 | pcibios_min_io = 0; |
326 | pcibios_min_mem = 0; | |
327 | ||
44266416 | 328 | hook_fault_code(16 + 6, cns3xxx_pcie_abort_handler, SIGBUS, 0, |
5f32f7a0 AV |
329 | "imprecise external abort"); |
330 | ||
331 | for (i = 0; i < ARRAY_SIZE(cns3xxx_pcie); i++) { | |
5f32f7a0 AV |
332 | cns3xxx_pwr_clk_en(0x1 << PM_CLK_GATE_REG_OFFSET_PCIE(i)); |
333 | cns3xxx_pwr_soft_rst(0x1 << PM_SOFT_RST_REG_OFFST_PCIE(i)); | |
334 | cns3xxx_pcie_check_link(&cns3xxx_pcie[i]); | |
335 | cns3xxx_pcie_hw_init(&cns3xxx_pcie[i]); | |
336 | pci_common_init(&cns3xxx_pcie[i].hw_pci); | |
337 | } | |
338 | ||
339 | pci_assign_unassigned_resources(); | |
5f32f7a0 | 340 | } |