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; | |
c88d54ba | 33 | int port; |
5f32f7a0 AV |
34 | bool linked; |
35 | }; | |
36 | ||
5f32f7a0 AV |
37 | static struct cns3xxx_pcie *sysdata_to_cnspci(void *sysdata) |
38 | { | |
39 | struct pci_sys_data *root = sysdata; | |
40 | ||
c88d54ba | 41 | return root->private_data; |
5f32f7a0 AV |
42 | } |
43 | ||
7caaf7ef | 44 | static struct cns3xxx_pcie *pdev_to_cnspci(const struct pci_dev *dev) |
5f32f7a0 AV |
45 | { |
46 | return sysdata_to_cnspci(dev->sysdata); | |
47 | } | |
48 | ||
49 | static struct cns3xxx_pcie *pbus_to_cnspci(struct pci_bus *bus) | |
50 | { | |
51 | return sysdata_to_cnspci(bus->sysdata); | |
52 | } | |
53 | ||
802b7c06 RH |
54 | static void __iomem *cns3xxx_pci_map_bus(struct pci_bus *bus, |
55 | unsigned int devfn, int where) | |
5f32f7a0 AV |
56 | { |
57 | struct cns3xxx_pcie *cnspci = pbus_to_cnspci(bus); | |
58 | int busno = bus->number; | |
59 | int slot = PCI_SLOT(devfn); | |
5f32f7a0 AV |
60 | void __iomem *base; |
61 | ||
62 | /* If there is no link, just show the CNS PCI bridge. */ | |
defaa4d1 | 63 | if (!cnspci->linked && busno > 0) |
5f32f7a0 AV |
64 | return NULL; |
65 | ||
66 | /* | |
67 | * The CNS PCI bridge doesn't fit into the PCI hierarchy, though | |
498a92d4 AB |
68 | * we still want to access it. |
69 | * We place the host bridge on bus 0, and the directly connected | |
70 | * device on bus 1, slot 0. | |
5f32f7a0 | 71 | */ |
defaa4d1 KH |
72 | if (busno == 0) { /* internal PCIe bus, host bridge device */ |
73 | if (devfn == 0) /* device# and function# are ignored by hw */ | |
64cf9d07 | 74 | base = cnspci->host_regs; |
defaa4d1 KH |
75 | else |
76 | return NULL; /* no such device */ | |
77 | ||
78 | } else if (busno == 1) { /* directly connected PCIe device */ | |
79 | if (slot == 0) /* device# is ignored by hw */ | |
64cf9d07 | 80 | base = cnspci->cfg0_regs; |
defaa4d1 | 81 | else |
64cf9d07 | 82 | return NULL; /* no such device */ |
64cf9d07 | 83 | } else /* remote PCI bus */ |
defaa4d1 | 84 | base = cnspci->cfg1_regs + ((busno & 0xf) << 20); |
5f32f7a0 | 85 | |
defaa4d1 | 86 | return base + (where & 0xffc) + (devfn << 12); |
5f32f7a0 AV |
87 | } |
88 | ||
89 | static int cns3xxx_pci_read_config(struct pci_bus *bus, unsigned int devfn, | |
90 | int where, int size, u32 *val) | |
91 | { | |
802b7c06 | 92 | int ret; |
5f32f7a0 AV |
93 | u32 mask = (0x1ull << (size * 8)) - 1; |
94 | int shift = (where % 4) * 8; | |
95 | ||
802b7c06 | 96 | ret = pci_generic_config_read32(bus, devfn, where, size, val); |
5f32f7a0 | 97 | |
802b7c06 RH |
98 | if (ret == PCIBIOS_SUCCESSFUL && !bus->number && !devfn && |
99 | (where & 0xffc) == PCI_CLASS_REVISION) | |
5f32f7a0 AV |
100 | /* |
101 | * RC's class is 0xb, but Linux PCI driver needs 0x604 | |
102 | * for a PCIe bridge. So we must fixup the class code | |
103 | * to 0x604 here. | |
104 | */ | |
802b7c06 | 105 | *val = ((((*val << shift) & 0xff) | (0x604 << 16)) >> shift) & mask; |
5f32f7a0 | 106 | |
802b7c06 | 107 | return ret; |
5f32f7a0 AV |
108 | } |
109 | ||
110 | static int cns3xxx_pci_setup(int nr, struct pci_sys_data *sys) | |
111 | { | |
112 | struct cns3xxx_pcie *cnspci = sysdata_to_cnspci(sys); | |
113 | struct resource *res_io = &cnspci->res_io; | |
114 | struct resource *res_mem = &cnspci->res_mem; | |
5f32f7a0 AV |
115 | |
116 | BUG_ON(request_resource(&iomem_resource, res_io) || | |
117 | request_resource(&iomem_resource, res_mem)); | |
118 | ||
9f786d03 BH |
119 | pci_add_resource_offset(&sys->resources, res_io, sys->io_offset); |
120 | pci_add_resource_offset(&sys->resources, res_mem, sys->mem_offset); | |
5f32f7a0 AV |
121 | |
122 | return 1; | |
123 | } | |
124 | ||
125 | static struct pci_ops cns3xxx_pcie_ops = { | |
802b7c06 | 126 | .map_bus = cns3xxx_pci_map_bus, |
5f32f7a0 | 127 | .read = cns3xxx_pci_read_config, |
802b7c06 | 128 | .write = pci_generic_config_write, |
5f32f7a0 AV |
129 | }; |
130 | ||
d5341942 | 131 | static int cns3xxx_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) |
5f32f7a0 AV |
132 | { |
133 | struct cns3xxx_pcie *cnspci = pdev_to_cnspci(dev); | |
defaa4d1 | 134 | int irq = cnspci->irqs[!!dev->bus->number]; |
5f32f7a0 AV |
135 | |
136 | pr_info("PCIe map irq: %04d:%02x:%02x.%02x slot %d, pin %d, irq: %d\n", | |
137 | pci_domain_nr(dev->bus), dev->bus->number, PCI_SLOT(dev->devfn), | |
138 | PCI_FUNC(dev->devfn), slot, pin, irq); | |
139 | ||
140 | return irq; | |
141 | } | |
142 | ||
143 | static struct cns3xxx_pcie cns3xxx_pcie[] = { | |
144 | [0] = { | |
64cf9d07 KH |
145 | .host_regs = (void __iomem *)CNS3XXX_PCIE0_HOST_BASE_VIRT, |
146 | .cfg0_regs = (void __iomem *)CNS3XXX_PCIE0_CFG0_BASE_VIRT, | |
147 | .cfg1_regs = (void __iomem *)CNS3XXX_PCIE0_CFG1_BASE_VIRT, | |
5f32f7a0 AV |
148 | .res_io = { |
149 | .name = "PCIe0 I/O space", | |
150 | .start = CNS3XXX_PCIE0_IO_BASE, | |
64cf9d07 | 151 | .end = CNS3XXX_PCIE0_CFG0_BASE - 1, /* 16 MiB */ |
5f32f7a0 AV |
152 | .flags = IORESOURCE_IO, |
153 | }, | |
154 | .res_mem = { | |
155 | .name = "PCIe0 non-prefetchable", | |
156 | .start = CNS3XXX_PCIE0_MEM_BASE, | |
64cf9d07 | 157 | .end = CNS3XXX_PCIE0_HOST_BASE - 1, /* 176 MiB */ |
5f32f7a0 AV |
158 | .flags = IORESOURCE_MEM, |
159 | }, | |
160 | .irqs = { IRQ_CNS3XXX_PCIE0_RC, IRQ_CNS3XXX_PCIE0_DEVICE, }, | |
c88d54ba | 161 | .port = 0, |
5f32f7a0 AV |
162 | }, |
163 | [1] = { | |
64cf9d07 KH |
164 | .host_regs = (void __iomem *)CNS3XXX_PCIE1_HOST_BASE_VIRT, |
165 | .cfg0_regs = (void __iomem *)CNS3XXX_PCIE1_CFG0_BASE_VIRT, | |
166 | .cfg1_regs = (void __iomem *)CNS3XXX_PCIE1_CFG1_BASE_VIRT, | |
5f32f7a0 AV |
167 | .res_io = { |
168 | .name = "PCIe1 I/O space", | |
169 | .start = CNS3XXX_PCIE1_IO_BASE, | |
64cf9d07 | 170 | .end = CNS3XXX_PCIE1_CFG0_BASE - 1, /* 16 MiB */ |
5f32f7a0 AV |
171 | .flags = IORESOURCE_IO, |
172 | }, | |
173 | .res_mem = { | |
174 | .name = "PCIe1 non-prefetchable", | |
175 | .start = CNS3XXX_PCIE1_MEM_BASE, | |
64cf9d07 | 176 | .end = CNS3XXX_PCIE1_HOST_BASE - 1, /* 176 MiB */ |
5f32f7a0 AV |
177 | .flags = IORESOURCE_MEM, |
178 | }, | |
179 | .irqs = { IRQ_CNS3XXX_PCIE1_RC, IRQ_CNS3XXX_PCIE1_DEVICE, }, | |
c88d54ba | 180 | .port = 1, |
5f32f7a0 AV |
181 | }, |
182 | }; | |
183 | ||
184 | static void __init cns3xxx_pcie_check_link(struct cns3xxx_pcie *cnspci) | |
185 | { | |
c88d54ba | 186 | int port = cnspci->port; |
5f32f7a0 AV |
187 | u32 reg; |
188 | unsigned long time; | |
189 | ||
190 | reg = __raw_readl(MISC_PCIE_CTRL(port)); | |
191 | /* | |
192 | * Enable Application Request to 1, it will exit L1 automatically, | |
193 | * but when chip back, it will use another clock, still can use 0x1. | |
194 | */ | |
195 | reg |= 0x3; | |
196 | __raw_writel(reg, MISC_PCIE_CTRL(port)); | |
197 | ||
198 | pr_info("PCIe: Port[%d] Enable PCIe LTSSM\n", port); | |
199 | pr_info("PCIe: Port[%d] Check data link layer...", port); | |
200 | ||
201 | time = jiffies; | |
202 | while (1) { | |
203 | reg = __raw_readl(MISC_PCIE_PM_DEBUG(port)); | |
204 | if (reg & 0x1) { | |
205 | pr_info("Link up.\n"); | |
206 | cnspci->linked = 1; | |
207 | break; | |
208 | } else if (time_after(jiffies, time + 50)) { | |
209 | pr_info("Device not found.\n"); | |
210 | break; | |
211 | } | |
212 | } | |
213 | } | |
214 | ||
498a92d4 AB |
215 | static void cns3xxx_write_config(struct cns3xxx_pcie *cnspci, |
216 | int where, int size, u32 val) | |
217 | { | |
218 | void __iomem *base = cnspci->host_regs + (where & 0xffc); | |
219 | u32 v; | |
220 | u32 mask = (0x1ull << (size * 8)) - 1; | |
221 | int shift = (where % 4) * 8; | |
222 | ||
223 | v = readl_relaxed(base + (where & 0xffc)); | |
224 | ||
225 | v &= ~(mask << shift); | |
226 | v |= (val & mask) << shift; | |
227 | ||
228 | writel_relaxed(v, base + (where & 0xffc)); | |
229 | readl_relaxed(base + (where & 0xffc)); | |
230 | } | |
231 | ||
5f32f7a0 AV |
232 | static void __init cns3xxx_pcie_hw_init(struct cns3xxx_pcie *cnspci) |
233 | { | |
64cf9d07 KH |
234 | u16 mem_base = cnspci->res_mem.start >> 16; |
235 | u16 mem_limit = cnspci->res_mem.end >> 16; | |
236 | u16 io_base = cnspci->res_io.start >> 16; | |
237 | u16 io_limit = cnspci->res_io.end >> 16; | |
5f32f7a0 | 238 | |
498a92d4 AB |
239 | cns3xxx_write_config(cnspci, PCI_PRIMARY_BUS, 1, 0); |
240 | cns3xxx_write_config(cnspci, PCI_SECONDARY_BUS, 1, 1); | |
241 | cns3xxx_write_config(cnspci, PCI_SUBORDINATE_BUS, 1, 1); | |
242 | cns3xxx_write_config(cnspci, PCI_MEMORY_BASE, 2, mem_base); | |
243 | cns3xxx_write_config(cnspci, PCI_MEMORY_LIMIT, 2, mem_limit); | |
244 | cns3xxx_write_config(cnspci, PCI_IO_BASE_UPPER16, 2, io_base); | |
245 | cns3xxx_write_config(cnspci, PCI_IO_LIMIT_UPPER16, 2, io_limit); | |
5f32f7a0 AV |
246 | |
247 | if (!cnspci->linked) | |
248 | return; | |
249 | ||
250 | /* Set Device Max_Read_Request_Size to 128 byte */ | |
498a92d4 AB |
251 | pcie_bus_config = PCIE_BUS_PEER2PEER; |
252 | ||
5f32f7a0 | 253 | /* Disable PCIe0 Interrupt Mask INTA to INTD */ |
498a92d4 | 254 | __raw_writel(~0x3FFF, MISC_PCIE_INT_MASK(cnspci->port)); |
5f32f7a0 AV |
255 | } |
256 | ||
257 | static int cns3xxx_pcie_abort_handler(unsigned long addr, unsigned int fsr, | |
258 | struct pt_regs *regs) | |
259 | { | |
260 | if (fsr & (1 << 10)) | |
261 | regs->ARM_pc += 4; | |
262 | return 0; | |
263 | } | |
264 | ||
0a2e912d | 265 | void __init cns3xxx_pcie_init_late(void) |
5f32f7a0 AV |
266 | { |
267 | int i; | |
c88d54ba LP |
268 | void *private_data; |
269 | struct hw_pci hw_pci = { | |
270 | .nr_controllers = 1, | |
271 | .ops = &cns3xxx_pcie_ops, | |
272 | .setup = cns3xxx_pci_setup, | |
273 | .map_irq = cns3xxx_pcie_map_irq, | |
274 | .private_data = &private_data, | |
275 | }; | |
5f32f7a0 | 276 | |
c9d95fbe RH |
277 | pcibios_min_io = 0; |
278 | pcibios_min_mem = 0; | |
279 | ||
44266416 | 280 | hook_fault_code(16 + 6, cns3xxx_pcie_abort_handler, SIGBUS, 0, |
5f32f7a0 AV |
281 | "imprecise external abort"); |
282 | ||
283 | for (i = 0; i < ARRAY_SIZE(cns3xxx_pcie); i++) { | |
5f32f7a0 AV |
284 | cns3xxx_pwr_clk_en(0x1 << PM_CLK_GATE_REG_OFFSET_PCIE(i)); |
285 | cns3xxx_pwr_soft_rst(0x1 << PM_SOFT_RST_REG_OFFST_PCIE(i)); | |
286 | cns3xxx_pcie_check_link(&cns3xxx_pcie[i]); | |
287 | cns3xxx_pcie_hw_init(&cns3xxx_pcie[i]); | |
c88d54ba LP |
288 | private_data = &cns3xxx_pcie[i]; |
289 | pci_common_init(&hw_pci); | |
5f32f7a0 AV |
290 | } |
291 | ||
292 | pci_assign_unassigned_resources(); | |
5f32f7a0 | 293 | } |