diff options
Diffstat (limited to 'drivers/pci/host/pci-dra7xx.c')
-rw-r--r-- | drivers/pci/host/pci-dra7xx.c | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c new file mode 100644 index 000000000000..52b34fee07fd --- /dev/null +++ b/drivers/pci/host/pci-dra7xx.c | |||
@@ -0,0 +1,458 @@ | |||
1 | /* | ||
2 | * pcie-dra7xx - PCIe controller driver for TI DRA7xx SoCs | ||
3 | * | ||
4 | * Copyright (C) 2013-2014 Texas Instruments Incorporated - http://www.ti.com | ||
5 | * | ||
6 | * Authors: Kishon Vijay Abraham I <kishon@ti.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/delay.h> | ||
14 | #include <linux/err.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/irq.h> | ||
17 | #include <linux/irqdomain.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/pci.h> | ||
21 | #include <linux/phy/phy.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/pm_runtime.h> | ||
24 | #include <linux/resource.h> | ||
25 | #include <linux/types.h> | ||
26 | |||
27 | #include "pcie-designware.h" | ||
28 | |||
29 | /* PCIe controller wrapper DRA7XX configuration registers */ | ||
30 | |||
31 | #define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN 0x0024 | ||
32 | #define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN 0x0028 | ||
33 | #define ERR_SYS BIT(0) | ||
34 | #define ERR_FATAL BIT(1) | ||
35 | #define ERR_NONFATAL BIT(2) | ||
36 | #define ERR_COR BIT(3) | ||
37 | #define ERR_AXI BIT(4) | ||
38 | #define ERR_ECRC BIT(5) | ||
39 | #define PME_TURN_OFF BIT(8) | ||
40 | #define PME_TO_ACK BIT(9) | ||
41 | #define PM_PME BIT(10) | ||
42 | #define LINK_REQ_RST BIT(11) | ||
43 | #define LINK_UP_EVT BIT(12) | ||
44 | #define CFG_BME_EVT BIT(13) | ||
45 | #define CFG_MSE_EVT BIT(14) | ||
46 | #define INTERRUPTS (ERR_SYS | ERR_FATAL | ERR_NONFATAL | ERR_COR | ERR_AXI | \ | ||
47 | ERR_ECRC | PME_TURN_OFF | PME_TO_ACK | PM_PME | \ | ||
48 | LINK_REQ_RST | LINK_UP_EVT | CFG_BME_EVT | CFG_MSE_EVT) | ||
49 | |||
50 | #define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI 0x0034 | ||
51 | #define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI 0x0038 | ||
52 | #define INTA BIT(0) | ||
53 | #define INTB BIT(1) | ||
54 | #define INTC BIT(2) | ||
55 | #define INTD BIT(3) | ||
56 | #define MSI BIT(4) | ||
57 | #define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD) | ||
58 | |||
59 | #define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104 | ||
60 | #define LTSSM_EN 0x1 | ||
61 | |||
62 | #define PCIECTRL_DRA7XX_CONF_PHY_CS 0x010C | ||
63 | #define LINK_UP BIT(16) | ||
64 | |||
65 | struct dra7xx_pcie { | ||
66 | void __iomem *base; | ||
67 | struct phy **phy; | ||
68 | int phy_count; | ||
69 | struct device *dev; | ||
70 | struct pcie_port pp; | ||
71 | }; | ||
72 | |||
73 | #define to_dra7xx_pcie(x) container_of((x), struct dra7xx_pcie, pp) | ||
74 | |||
75 | static inline u32 dra7xx_pcie_readl(struct dra7xx_pcie *pcie, u32 offset) | ||
76 | { | ||
77 | return readl(pcie->base + offset); | ||
78 | } | ||
79 | |||
80 | static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset, | ||
81 | u32 value) | ||
82 | { | ||
83 | writel(value, pcie->base + offset); | ||
84 | } | ||
85 | |||
86 | static int dra7xx_pcie_link_up(struct pcie_port *pp) | ||
87 | { | ||
88 | struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); | ||
89 | u32 reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); | ||
90 | |||
91 | return !!(reg & LINK_UP); | ||
92 | } | ||
93 | |||
94 | static int dra7xx_pcie_establish_link(struct pcie_port *pp) | ||
95 | { | ||
96 | u32 reg; | ||
97 | unsigned int retries = 1000; | ||
98 | struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); | ||
99 | |||
100 | if (dw_pcie_link_up(pp)) { | ||
101 | dev_err(pp->dev, "link is already up\n"); | ||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); | ||
106 | reg |= LTSSM_EN; | ||
107 | dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); | ||
108 | |||
109 | while (retries--) { | ||
110 | reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); | ||
111 | if (reg & LINK_UP) | ||
112 | break; | ||
113 | usleep_range(10, 20); | ||
114 | } | ||
115 | |||
116 | if (retries == 0) { | ||
117 | dev_err(pp->dev, "link is not up\n"); | ||
118 | return -ETIMEDOUT; | ||
119 | } | ||
120 | |||
121 | return 0; | ||
122 | } | ||
123 | |||
124 | static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) | ||
125 | { | ||
126 | struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); | ||
127 | |||
128 | dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, | ||
129 | ~INTERRUPTS); | ||
130 | dra7xx_pcie_writel(dra7xx, | ||
131 | PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN, INTERRUPTS); | ||
132 | dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI, | ||
133 | ~LEG_EP_INTERRUPTS & ~MSI); | ||
134 | |||
135 | if (IS_ENABLED(CONFIG_PCI_MSI)) | ||
136 | dra7xx_pcie_writel(dra7xx, | ||
137 | PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, MSI); | ||
138 | else | ||
139 | dra7xx_pcie_writel(dra7xx, | ||
140 | PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, | ||
141 | LEG_EP_INTERRUPTS); | ||
142 | } | ||
143 | |||
144 | static void dra7xx_pcie_host_init(struct pcie_port *pp) | ||
145 | { | ||
146 | dw_pcie_setup_rc(pp); | ||
147 | dra7xx_pcie_establish_link(pp); | ||
148 | if (IS_ENABLED(CONFIG_PCI_MSI)) | ||
149 | dw_pcie_msi_init(pp); | ||
150 | dra7xx_pcie_enable_interrupts(pp); | ||
151 | } | ||
152 | |||
153 | static struct pcie_host_ops dra7xx_pcie_host_ops = { | ||
154 | .link_up = dra7xx_pcie_link_up, | ||
155 | .host_init = dra7xx_pcie_host_init, | ||
156 | }; | ||
157 | |||
158 | static int dra7xx_pcie_intx_map(struct irq_domain *domain, unsigned int irq, | ||
159 | irq_hw_number_t hwirq) | ||
160 | { | ||
161 | irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); | ||
162 | irq_set_chip_data(irq, domain->host_data); | ||
163 | set_irq_flags(irq, IRQF_VALID); | ||
164 | |||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static const struct irq_domain_ops intx_domain_ops = { | ||
169 | .map = dra7xx_pcie_intx_map, | ||
170 | }; | ||
171 | |||
172 | static int dra7xx_pcie_init_irq_domain(struct pcie_port *pp) | ||
173 | { | ||
174 | struct device *dev = pp->dev; | ||
175 | struct device_node *node = dev->of_node; | ||
176 | struct device_node *pcie_intc_node = of_get_next_child(node, NULL); | ||
177 | |||
178 | if (!pcie_intc_node) { | ||
179 | dev_err(dev, "No PCIe Intc node found\n"); | ||
180 | return PTR_ERR(pcie_intc_node); | ||
181 | } | ||
182 | |||
183 | pp->irq_domain = irq_domain_add_linear(pcie_intc_node, 4, | ||
184 | &intx_domain_ops, pp); | ||
185 | if (!pp->irq_domain) { | ||
186 | dev_err(dev, "Failed to get a INTx IRQ domain\n"); | ||
187 | return PTR_ERR(pp->irq_domain); | ||
188 | } | ||
189 | |||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static irqreturn_t dra7xx_pcie_msi_irq_handler(int irq, void *arg) | ||
194 | { | ||
195 | struct pcie_port *pp = arg; | ||
196 | struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); | ||
197 | u32 reg; | ||
198 | |||
199 | reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI); | ||
200 | |||
201 | switch (reg) { | ||
202 | case MSI: | ||
203 | dw_handle_msi_irq(pp); | ||
204 | break; | ||
205 | case INTA: | ||
206 | case INTB: | ||
207 | case INTC: | ||
208 | case INTD: | ||
209 | generic_handle_irq(irq_find_mapping(pp->irq_domain, ffs(reg))); | ||
210 | break; | ||
211 | } | ||
212 | |||
213 | dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI, reg); | ||
214 | |||
215 | return IRQ_HANDLED; | ||
216 | } | ||
217 | |||
218 | |||
219 | static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) | ||
220 | { | ||
221 | struct dra7xx_pcie *dra7xx = arg; | ||
222 | u32 reg; | ||
223 | |||
224 | reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN); | ||
225 | |||
226 | if (reg & ERR_SYS) | ||
227 | dev_dbg(dra7xx->dev, "System Error\n"); | ||
228 | |||
229 | if (reg & ERR_FATAL) | ||
230 | dev_dbg(dra7xx->dev, "Fatal Error\n"); | ||
231 | |||
232 | if (reg & ERR_NONFATAL) | ||
233 | dev_dbg(dra7xx->dev, "Non Fatal Error\n"); | ||
234 | |||
235 | if (reg & ERR_COR) | ||
236 | dev_dbg(dra7xx->dev, "Correctable Error\n"); | ||
237 | |||
238 | if (reg & ERR_AXI) | ||
239 | dev_dbg(dra7xx->dev, "AXI tag lookup fatal Error\n"); | ||
240 | |||
241 | if (reg & ERR_ECRC) | ||
242 | dev_dbg(dra7xx->dev, "ECRC Error\n"); | ||
243 | |||
244 | if (reg & PME_TURN_OFF) | ||
245 | dev_dbg(dra7xx->dev, | ||
246 | "Power Management Event Turn-Off message received\n"); | ||
247 | |||
248 | if (reg & PME_TO_ACK) | ||
249 | dev_dbg(dra7xx->dev, | ||
250 | "Power Management Turn-Off Ack message received\n"); | ||
251 | |||
252 | if (reg & PM_PME) | ||
253 | dev_dbg(dra7xx->dev, | ||
254 | "PM Power Management Event message received\n"); | ||
255 | |||
256 | if (reg & LINK_REQ_RST) | ||
257 | dev_dbg(dra7xx->dev, "Link Request Reset\n"); | ||
258 | |||
259 | if (reg & LINK_UP_EVT) | ||
260 | dev_dbg(dra7xx->dev, "Link-up state change\n"); | ||
261 | |||
262 | if (reg & CFG_BME_EVT) | ||
263 | dev_dbg(dra7xx->dev, "CFG 'Bus Master Enable' change\n"); | ||
264 | |||
265 | if (reg & CFG_MSE_EVT) | ||
266 | dev_dbg(dra7xx->dev, "CFG 'Memory Space Enable' change\n"); | ||
267 | |||
268 | dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, reg); | ||
269 | |||
270 | return IRQ_HANDLED; | ||
271 | } | ||
272 | |||
273 | static int add_pcie_port(struct dra7xx_pcie *dra7xx, | ||
274 | struct platform_device *pdev) | ||
275 | { | ||
276 | int ret; | ||
277 | struct pcie_port *pp; | ||
278 | struct resource *res; | ||
279 | struct device *dev = &pdev->dev; | ||
280 | |||
281 | pp = &dra7xx->pp; | ||
282 | pp->dev = dev; | ||
283 | pp->ops = &dra7xx_pcie_host_ops; | ||
284 | |||
285 | pp->irq = platform_get_irq(pdev, 1); | ||
286 | if (pp->irq < 0) { | ||
287 | dev_err(dev, "missing IRQ resource\n"); | ||
288 | return -EINVAL; | ||
289 | } | ||
290 | |||
291 | ret = devm_request_irq(&pdev->dev, pp->irq, | ||
292 | dra7xx_pcie_msi_irq_handler, IRQF_SHARED, | ||
293 | "dra7-pcie-msi", pp); | ||
294 | if (ret) { | ||
295 | dev_err(&pdev->dev, "failed to request irq\n"); | ||
296 | return ret; | ||
297 | } | ||
298 | |||
299 | if (!IS_ENABLED(CONFIG_PCI_MSI)) { | ||
300 | ret = dra7xx_pcie_init_irq_domain(pp); | ||
301 | if (ret < 0) | ||
302 | return ret; | ||
303 | } | ||
304 | |||
305 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc_dbics"); | ||
306 | pp->dbi_base = devm_ioremap(dev, res->start, resource_size(res)); | ||
307 | if (!pp->dbi_base) | ||
308 | return -ENOMEM; | ||
309 | |||
310 | ret = dw_pcie_host_init(pp); | ||
311 | if (ret) { | ||
312 | dev_err(dra7xx->dev, "failed to initialize host\n"); | ||
313 | return ret; | ||
314 | } | ||
315 | |||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | static int __init dra7xx_pcie_probe(struct platform_device *pdev) | ||
320 | { | ||
321 | u32 reg; | ||
322 | int ret; | ||
323 | int irq; | ||
324 | int i; | ||
325 | int phy_count; | ||
326 | struct phy **phy; | ||
327 | void __iomem *base; | ||
328 | struct resource *res; | ||
329 | struct dra7xx_pcie *dra7xx; | ||
330 | struct device *dev = &pdev->dev; | ||
331 | struct device_node *np = dev->of_node; | ||
332 | char name[10]; | ||
333 | |||
334 | dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL); | ||
335 | if (!dra7xx) | ||
336 | return -ENOMEM; | ||
337 | |||
338 | irq = platform_get_irq(pdev, 0); | ||
339 | if (irq < 0) { | ||
340 | dev_err(dev, "missing IRQ resource\n"); | ||
341 | return -EINVAL; | ||
342 | } | ||
343 | |||
344 | ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler, | ||
345 | IRQF_SHARED, "dra7xx-pcie-main", dra7xx); | ||
346 | if (ret) { | ||
347 | dev_err(dev, "failed to request irq\n"); | ||
348 | return ret; | ||
349 | } | ||
350 | |||
351 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ti_conf"); | ||
352 | base = devm_ioremap_nocache(dev, res->start, resource_size(res)); | ||
353 | if (!base) | ||
354 | return -ENOMEM; | ||
355 | |||
356 | phy_count = of_property_count_strings(np, "phy-names"); | ||
357 | if (phy_count < 0) { | ||
358 | dev_err(dev, "unable to find the strings\n"); | ||
359 | return phy_count; | ||
360 | } | ||
361 | |||
362 | phy = devm_kzalloc(dev, sizeof(*phy) * phy_count, GFP_KERNEL); | ||
363 | if (!phy) | ||
364 | return -ENOMEM; | ||
365 | |||
366 | for (i = 0; i < phy_count; i++) { | ||
367 | snprintf(name, sizeof(name), "pcie-phy%d", i); | ||
368 | phy[i] = devm_phy_get(dev, name); | ||
369 | if (IS_ERR(phy[i])) | ||
370 | return PTR_ERR(phy[i]); | ||
371 | |||
372 | ret = phy_init(phy[i]); | ||
373 | if (ret < 0) | ||
374 | goto err_phy; | ||
375 | |||
376 | ret = phy_power_on(phy[i]); | ||
377 | if (ret < 0) { | ||
378 | phy_exit(phy[i]); | ||
379 | goto err_phy; | ||
380 | } | ||
381 | } | ||
382 | |||
383 | dra7xx->base = base; | ||
384 | dra7xx->phy = phy; | ||
385 | dra7xx->dev = dev; | ||
386 | dra7xx->phy_count = phy_count; | ||
387 | |||
388 | pm_runtime_enable(dev); | ||
389 | ret = pm_runtime_get_sync(dev); | ||
390 | if (IS_ERR_VALUE(ret)) { | ||
391 | dev_err(dev, "pm_runtime_get_sync failed\n"); | ||
392 | goto err_phy; | ||
393 | } | ||
394 | |||
395 | reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); | ||
396 | reg &= ~LTSSM_EN; | ||
397 | dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); | ||
398 | |||
399 | platform_set_drvdata(pdev, dra7xx); | ||
400 | |||
401 | ret = add_pcie_port(dra7xx, pdev); | ||
402 | if (ret < 0) | ||
403 | goto err_add_port; | ||
404 | |||
405 | return 0; | ||
406 | |||
407 | err_add_port: | ||
408 | pm_runtime_put(dev); | ||
409 | pm_runtime_disable(dev); | ||
410 | |||
411 | err_phy: | ||
412 | while (--i >= 0) { | ||
413 | phy_power_off(phy[i]); | ||
414 | phy_exit(phy[i]); | ||
415 | } | ||
416 | |||
417 | return ret; | ||
418 | } | ||
419 | |||
420 | static int __exit dra7xx_pcie_remove(struct platform_device *pdev) | ||
421 | { | ||
422 | struct dra7xx_pcie *dra7xx = platform_get_drvdata(pdev); | ||
423 | struct pcie_port *pp = &dra7xx->pp; | ||
424 | struct device *dev = &pdev->dev; | ||
425 | int count = dra7xx->phy_count; | ||
426 | |||
427 | if (pp->irq_domain) | ||
428 | irq_domain_remove(pp->irq_domain); | ||
429 | pm_runtime_put(dev); | ||
430 | pm_runtime_disable(dev); | ||
431 | while (count--) { | ||
432 | phy_power_off(dra7xx->phy[count]); | ||
433 | phy_exit(dra7xx->phy[count]); | ||
434 | } | ||
435 | |||
436 | return 0; | ||
437 | } | ||
438 | |||
439 | static const struct of_device_id of_dra7xx_pcie_match[] = { | ||
440 | { .compatible = "ti,dra7-pcie", }, | ||
441 | {}, | ||
442 | }; | ||
443 | MODULE_DEVICE_TABLE(of, of_dra7xx_pcie_match); | ||
444 | |||
445 | static struct platform_driver dra7xx_pcie_driver = { | ||
446 | .remove = __exit_p(dra7xx_pcie_remove), | ||
447 | .driver = { | ||
448 | .name = "dra7-pcie", | ||
449 | .owner = THIS_MODULE, | ||
450 | .of_match_table = of_dra7xx_pcie_match, | ||
451 | }, | ||
452 | }; | ||
453 | |||
454 | module_platform_driver_probe(dra7xx_pcie_driver, dra7xx_pcie_probe); | ||
455 | |||
456 | MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); | ||
457 | MODULE_DESCRIPTION("TI PCIe controller driver"); | ||
458 | MODULE_LICENSE("GPL v2"); | ||