diff options
Diffstat (limited to 'drivers/pci/controller/dwc/pci-layerscape.c')
-rw-r--r-- | drivers/pci/controller/dwc/pci-layerscape.c | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/drivers/pci/controller/dwc/pci-layerscape.c b/drivers/pci/controller/dwc/pci-layerscape.c new file mode 100644 index 000000000000..3724d3ef7008 --- /dev/null +++ b/drivers/pci/controller/dwc/pci-layerscape.c | |||
@@ -0,0 +1,341 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * PCIe host controller driver for Freescale Layerscape SoCs | ||
4 | * | ||
5 | * Copyright (C) 2014 Freescale Semiconductor. | ||
6 | * | ||
7 | * Author: Minghuan Lian <Minghuan.Lian@freescale.com> | ||
8 | */ | ||
9 | |||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/interrupt.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/of_pci.h> | ||
14 | #include <linux/of_platform.h> | ||
15 | #include <linux/of_irq.h> | ||
16 | #include <linux/of_address.h> | ||
17 | #include <linux/pci.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/resource.h> | ||
20 | #include <linux/mfd/syscon.h> | ||
21 | #include <linux/regmap.h> | ||
22 | |||
23 | #include "pcie-designware.h" | ||
24 | |||
25 | /* PEX1/2 Misc Ports Status Register */ | ||
26 | #define SCFG_PEXMSCPORTSR(pex_idx) (0x94 + (pex_idx) * 4) | ||
27 | #define LTSSM_STATE_SHIFT 20 | ||
28 | #define LTSSM_STATE_MASK 0x3f | ||
29 | #define LTSSM_PCIE_L0 0x11 /* L0 state */ | ||
30 | |||
31 | /* PEX Internal Configuration Registers */ | ||
32 | #define PCIE_STRFMR1 0x71c /* Symbol Timer & Filter Mask Register1 */ | ||
33 | #define PCIE_ABSERR 0x8d0 /* Bridge Slave Error Response Register */ | ||
34 | #define PCIE_ABSERR_SETTING 0x9401 /* Forward error of non-posted request */ | ||
35 | |||
36 | #define PCIE_IATU_NUM 6 | ||
37 | |||
38 | struct ls_pcie_drvdata { | ||
39 | u32 lut_offset; | ||
40 | u32 ltssm_shift; | ||
41 | u32 lut_dbg; | ||
42 | const struct dw_pcie_host_ops *ops; | ||
43 | const struct dw_pcie_ops *dw_pcie_ops; | ||
44 | }; | ||
45 | |||
46 | struct ls_pcie { | ||
47 | struct dw_pcie *pci; | ||
48 | void __iomem *lut; | ||
49 | struct regmap *scfg; | ||
50 | const struct ls_pcie_drvdata *drvdata; | ||
51 | int index; | ||
52 | }; | ||
53 | |||
54 | #define to_ls_pcie(x) dev_get_drvdata((x)->dev) | ||
55 | |||
56 | static bool ls_pcie_is_bridge(struct ls_pcie *pcie) | ||
57 | { | ||
58 | struct dw_pcie *pci = pcie->pci; | ||
59 | u32 header_type; | ||
60 | |||
61 | header_type = ioread8(pci->dbi_base + PCI_HEADER_TYPE); | ||
62 | header_type &= 0x7f; | ||
63 | |||
64 | return header_type == PCI_HEADER_TYPE_BRIDGE; | ||
65 | } | ||
66 | |||
67 | /* Clear multi-function bit */ | ||
68 | static void ls_pcie_clear_multifunction(struct ls_pcie *pcie) | ||
69 | { | ||
70 | struct dw_pcie *pci = pcie->pci; | ||
71 | |||
72 | iowrite8(PCI_HEADER_TYPE_BRIDGE, pci->dbi_base + PCI_HEADER_TYPE); | ||
73 | } | ||
74 | |||
75 | /* Drop MSG TLP except for Vendor MSG */ | ||
76 | static void ls_pcie_drop_msg_tlp(struct ls_pcie *pcie) | ||
77 | { | ||
78 | u32 val; | ||
79 | struct dw_pcie *pci = pcie->pci; | ||
80 | |||
81 | val = ioread32(pci->dbi_base + PCIE_STRFMR1); | ||
82 | val &= 0xDFFFFFFF; | ||
83 | iowrite32(val, pci->dbi_base + PCIE_STRFMR1); | ||
84 | } | ||
85 | |||
86 | static void ls_pcie_disable_outbound_atus(struct ls_pcie *pcie) | ||
87 | { | ||
88 | int i; | ||
89 | |||
90 | for (i = 0; i < PCIE_IATU_NUM; i++) | ||
91 | dw_pcie_disable_atu(pcie->pci, DW_PCIE_REGION_OUTBOUND, i); | ||
92 | } | ||
93 | |||
94 | static int ls1021_pcie_link_up(struct dw_pcie *pci) | ||
95 | { | ||
96 | u32 state; | ||
97 | struct ls_pcie *pcie = to_ls_pcie(pci); | ||
98 | |||
99 | if (!pcie->scfg) | ||
100 | return 0; | ||
101 | |||
102 | regmap_read(pcie->scfg, SCFG_PEXMSCPORTSR(pcie->index), &state); | ||
103 | state = (state >> LTSSM_STATE_SHIFT) & LTSSM_STATE_MASK; | ||
104 | |||
105 | if (state < LTSSM_PCIE_L0) | ||
106 | return 0; | ||
107 | |||
108 | return 1; | ||
109 | } | ||
110 | |||
111 | static int ls_pcie_link_up(struct dw_pcie *pci) | ||
112 | { | ||
113 | struct ls_pcie *pcie = to_ls_pcie(pci); | ||
114 | u32 state; | ||
115 | |||
116 | state = (ioread32(pcie->lut + pcie->drvdata->lut_dbg) >> | ||
117 | pcie->drvdata->ltssm_shift) & | ||
118 | LTSSM_STATE_MASK; | ||
119 | |||
120 | if (state < LTSSM_PCIE_L0) | ||
121 | return 0; | ||
122 | |||
123 | return 1; | ||
124 | } | ||
125 | |||
126 | /* Forward error response of outbound non-posted requests */ | ||
127 | static void ls_pcie_fix_error_response(struct ls_pcie *pcie) | ||
128 | { | ||
129 | struct dw_pcie *pci = pcie->pci; | ||
130 | |||
131 | iowrite32(PCIE_ABSERR_SETTING, pci->dbi_base + PCIE_ABSERR); | ||
132 | } | ||
133 | |||
134 | static int ls_pcie_host_init(struct pcie_port *pp) | ||
135 | { | ||
136 | struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | ||
137 | struct ls_pcie *pcie = to_ls_pcie(pci); | ||
138 | |||
139 | /* | ||
140 | * Disable outbound windows configured by the bootloader to avoid | ||
141 | * one transaction hitting multiple outbound windows. | ||
142 | * dw_pcie_setup_rc() will reconfigure the outbound windows. | ||
143 | */ | ||
144 | ls_pcie_disable_outbound_atus(pcie); | ||
145 | ls_pcie_fix_error_response(pcie); | ||
146 | |||
147 | dw_pcie_dbi_ro_wr_en(pci); | ||
148 | ls_pcie_clear_multifunction(pcie); | ||
149 | dw_pcie_dbi_ro_wr_dis(pci); | ||
150 | |||
151 | ls_pcie_drop_msg_tlp(pcie); | ||
152 | |||
153 | dw_pcie_setup_rc(pp); | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static int ls1021_pcie_host_init(struct pcie_port *pp) | ||
159 | { | ||
160 | struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | ||
161 | struct ls_pcie *pcie = to_ls_pcie(pci); | ||
162 | struct device *dev = pci->dev; | ||
163 | u32 index[2]; | ||
164 | int ret; | ||
165 | |||
166 | pcie->scfg = syscon_regmap_lookup_by_phandle(dev->of_node, | ||
167 | "fsl,pcie-scfg"); | ||
168 | if (IS_ERR(pcie->scfg)) { | ||
169 | ret = PTR_ERR(pcie->scfg); | ||
170 | dev_err(dev, "No syscfg phandle specified\n"); | ||
171 | pcie->scfg = NULL; | ||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | if (of_property_read_u32_array(dev->of_node, | ||
176 | "fsl,pcie-scfg", index, 2)) { | ||
177 | pcie->scfg = NULL; | ||
178 | return -EINVAL; | ||
179 | } | ||
180 | pcie->index = index[1]; | ||
181 | |||
182 | return ls_pcie_host_init(pp); | ||
183 | } | ||
184 | |||
185 | static int ls_pcie_msi_host_init(struct pcie_port *pp) | ||
186 | { | ||
187 | struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | ||
188 | struct device *dev = pci->dev; | ||
189 | struct device_node *np = dev->of_node; | ||
190 | struct device_node *msi_node; | ||
191 | |||
192 | /* | ||
193 | * The MSI domain is set by the generic of_msi_configure(). This | ||
194 | * .msi_host_init() function keeps us from doing the default MSI | ||
195 | * domain setup in dw_pcie_host_init() and also enforces the | ||
196 | * requirement that "msi-parent" exists. | ||
197 | */ | ||
198 | msi_node = of_parse_phandle(np, "msi-parent", 0); | ||
199 | if (!msi_node) { | ||
200 | dev_err(dev, "failed to find msi-parent\n"); | ||
201 | return -EINVAL; | ||
202 | } | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static const struct dw_pcie_host_ops ls1021_pcie_host_ops = { | ||
208 | .host_init = ls1021_pcie_host_init, | ||
209 | .msi_host_init = ls_pcie_msi_host_init, | ||
210 | }; | ||
211 | |||
212 | static const struct dw_pcie_host_ops ls_pcie_host_ops = { | ||
213 | .host_init = ls_pcie_host_init, | ||
214 | .msi_host_init = ls_pcie_msi_host_init, | ||
215 | }; | ||
216 | |||
217 | static const struct dw_pcie_ops dw_ls1021_pcie_ops = { | ||
218 | .link_up = ls1021_pcie_link_up, | ||
219 | }; | ||
220 | |||
221 | static const struct dw_pcie_ops dw_ls_pcie_ops = { | ||
222 | .link_up = ls_pcie_link_up, | ||
223 | }; | ||
224 | |||
225 | static struct ls_pcie_drvdata ls1021_drvdata = { | ||
226 | .ops = &ls1021_pcie_host_ops, | ||
227 | .dw_pcie_ops = &dw_ls1021_pcie_ops, | ||
228 | }; | ||
229 | |||
230 | static struct ls_pcie_drvdata ls1043_drvdata = { | ||
231 | .lut_offset = 0x10000, | ||
232 | .ltssm_shift = 24, | ||
233 | .lut_dbg = 0x7fc, | ||
234 | .ops = &ls_pcie_host_ops, | ||
235 | .dw_pcie_ops = &dw_ls_pcie_ops, | ||
236 | }; | ||
237 | |||
238 | static struct ls_pcie_drvdata ls1046_drvdata = { | ||
239 | .lut_offset = 0x80000, | ||
240 | .ltssm_shift = 24, | ||
241 | .lut_dbg = 0x407fc, | ||
242 | .ops = &ls_pcie_host_ops, | ||
243 | .dw_pcie_ops = &dw_ls_pcie_ops, | ||
244 | }; | ||
245 | |||
246 | static struct ls_pcie_drvdata ls2080_drvdata = { | ||
247 | .lut_offset = 0x80000, | ||
248 | .ltssm_shift = 0, | ||
249 | .lut_dbg = 0x7fc, | ||
250 | .ops = &ls_pcie_host_ops, | ||
251 | .dw_pcie_ops = &dw_ls_pcie_ops, | ||
252 | }; | ||
253 | |||
254 | static struct ls_pcie_drvdata ls2088_drvdata = { | ||
255 | .lut_offset = 0x80000, | ||
256 | .ltssm_shift = 0, | ||
257 | .lut_dbg = 0x407fc, | ||
258 | .ops = &ls_pcie_host_ops, | ||
259 | .dw_pcie_ops = &dw_ls_pcie_ops, | ||
260 | }; | ||
261 | |||
262 | static const struct of_device_id ls_pcie_of_match[] = { | ||
263 | { .compatible = "fsl,ls1012a-pcie", .data = &ls1046_drvdata }, | ||
264 | { .compatible = "fsl,ls1021a-pcie", .data = &ls1021_drvdata }, | ||
265 | { .compatible = "fsl,ls1043a-pcie", .data = &ls1043_drvdata }, | ||
266 | { .compatible = "fsl,ls1046a-pcie", .data = &ls1046_drvdata }, | ||
267 | { .compatible = "fsl,ls2080a-pcie", .data = &ls2080_drvdata }, | ||
268 | { .compatible = "fsl,ls2085a-pcie", .data = &ls2080_drvdata }, | ||
269 | { .compatible = "fsl,ls2088a-pcie", .data = &ls2088_drvdata }, | ||
270 | { .compatible = "fsl,ls1088a-pcie", .data = &ls2088_drvdata }, | ||
271 | { }, | ||
272 | }; | ||
273 | |||
274 | static int __init ls_add_pcie_port(struct ls_pcie *pcie) | ||
275 | { | ||
276 | struct dw_pcie *pci = pcie->pci; | ||
277 | struct pcie_port *pp = &pci->pp; | ||
278 | struct device *dev = pci->dev; | ||
279 | int ret; | ||
280 | |||
281 | pp->ops = pcie->drvdata->ops; | ||
282 | |||
283 | ret = dw_pcie_host_init(pp); | ||
284 | if (ret) { | ||
285 | dev_err(dev, "failed to initialize host\n"); | ||
286 | return ret; | ||
287 | } | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static int __init ls_pcie_probe(struct platform_device *pdev) | ||
293 | { | ||
294 | struct device *dev = &pdev->dev; | ||
295 | struct dw_pcie *pci; | ||
296 | struct ls_pcie *pcie; | ||
297 | struct resource *dbi_base; | ||
298 | int ret; | ||
299 | |||
300 | pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); | ||
301 | if (!pcie) | ||
302 | return -ENOMEM; | ||
303 | |||
304 | pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); | ||
305 | if (!pci) | ||
306 | return -ENOMEM; | ||
307 | |||
308 | pcie->drvdata = of_device_get_match_data(dev); | ||
309 | |||
310 | pci->dev = dev; | ||
311 | pci->ops = pcie->drvdata->dw_pcie_ops; | ||
312 | |||
313 | pcie->pci = pci; | ||
314 | |||
315 | dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); | ||
316 | pci->dbi_base = devm_pci_remap_cfg_resource(dev, dbi_base); | ||
317 | if (IS_ERR(pci->dbi_base)) | ||
318 | return PTR_ERR(pci->dbi_base); | ||
319 | |||
320 | pcie->lut = pci->dbi_base + pcie->drvdata->lut_offset; | ||
321 | |||
322 | if (!ls_pcie_is_bridge(pcie)) | ||
323 | return -ENODEV; | ||
324 | |||
325 | platform_set_drvdata(pdev, pcie); | ||
326 | |||
327 | ret = ls_add_pcie_port(pcie); | ||
328 | if (ret < 0) | ||
329 | return ret; | ||
330 | |||
331 | return 0; | ||
332 | } | ||
333 | |||
334 | static struct platform_driver ls_pcie_driver = { | ||
335 | .driver = { | ||
336 | .name = "layerscape-pcie", | ||
337 | .of_match_table = ls_pcie_of_match, | ||
338 | .suppress_bind_attrs = true, | ||
339 | }, | ||
340 | }; | ||
341 | builtin_platform_driver_probe(ls_pcie_driver, ls_pcie_probe); | ||