1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Synopsys DWC Ethernet Quality-of-Service v4.10a linux driver |
4 | * |
5 | * Copyright (C) 2016 Joao Pinto <jpinto@synopsys.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/device.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/ethtool.h> |
13 | #include <linux/io.h> |
14 | #include <linux/iopoll.h> |
15 | #include <linux/ioport.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_net.h> |
19 | #include <linux/mfd/syscon.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/reset.h> |
22 | #include <linux/stmmac.h> |
23 | |
24 | #include "stmmac_platform.h" |
25 | #include "dwmac4.h" |
26 | |
27 | struct tegra_eqos { |
28 | struct device *dev; |
29 | void __iomem *regs; |
30 | |
31 | struct reset_control *rst; |
32 | struct clk *clk_master; |
33 | struct clk *clk_slave; |
34 | struct clk *clk_tx; |
35 | struct clk *clk_rx; |
36 | |
37 | struct gpio_desc *reset; |
38 | }; |
39 | |
40 | static int dwc_eth_dwmac_config_dt(struct platform_device *pdev, |
41 | struct plat_stmmacenet_data *plat_dat) |
42 | { |
43 | struct device *dev = &pdev->dev; |
44 | u32 burst_map = 0; |
45 | u32 bit_index = 0; |
46 | u32 a_index = 0; |
47 | |
48 | if (!plat_dat->axi) { |
49 | plat_dat->axi = kzalloc(size: sizeof(struct stmmac_axi), GFP_KERNEL); |
50 | |
51 | if (!plat_dat->axi) |
52 | return -ENOMEM; |
53 | } |
54 | |
55 | plat_dat->axi->axi_lpi_en = device_property_read_bool(dev, |
56 | propname: "snps,en-lpi" ); |
57 | if (device_property_read_u32(dev, propname: "snps,write-requests" , |
58 | val: &plat_dat->axi->axi_wr_osr_lmt)) { |
59 | /** |
60 | * Since the register has a reset value of 1, if property |
61 | * is missing, default to 1. |
62 | */ |
63 | plat_dat->axi->axi_wr_osr_lmt = 1; |
64 | } else { |
65 | /** |
66 | * If property exists, to keep the behavior from dwc_eth_qos, |
67 | * subtract one after parsing. |
68 | */ |
69 | plat_dat->axi->axi_wr_osr_lmt--; |
70 | } |
71 | |
72 | if (device_property_read_u32(dev, propname: "snps,read-requests" , |
73 | val: &plat_dat->axi->axi_rd_osr_lmt)) { |
74 | /** |
75 | * Since the register has a reset value of 1, if property |
76 | * is missing, default to 1. |
77 | */ |
78 | plat_dat->axi->axi_rd_osr_lmt = 1; |
79 | } else { |
80 | /** |
81 | * If property exists, to keep the behavior from dwc_eth_qos, |
82 | * subtract one after parsing. |
83 | */ |
84 | plat_dat->axi->axi_rd_osr_lmt--; |
85 | } |
86 | device_property_read_u32(dev, propname: "snps,burst-map" , val: &burst_map); |
87 | |
88 | /* converts burst-map bitmask to burst array */ |
89 | for (bit_index = 0; bit_index < 7; bit_index++) { |
90 | if (burst_map & (1 << bit_index)) { |
91 | switch (bit_index) { |
92 | case 0: |
93 | plat_dat->axi->axi_blen[a_index] = 4; break; |
94 | case 1: |
95 | plat_dat->axi->axi_blen[a_index] = 8; break; |
96 | case 2: |
97 | plat_dat->axi->axi_blen[a_index] = 16; break; |
98 | case 3: |
99 | plat_dat->axi->axi_blen[a_index] = 32; break; |
100 | case 4: |
101 | plat_dat->axi->axi_blen[a_index] = 64; break; |
102 | case 5: |
103 | plat_dat->axi->axi_blen[a_index] = 128; break; |
104 | case 6: |
105 | plat_dat->axi->axi_blen[a_index] = 256; break; |
106 | default: |
107 | break; |
108 | } |
109 | a_index++; |
110 | } |
111 | } |
112 | |
113 | /* dwc-qos needs GMAC4, AAL, TSO and PMT */ |
114 | plat_dat->has_gmac4 = 1; |
115 | plat_dat->dma_cfg->aal = 1; |
116 | plat_dat->flags |= STMMAC_FLAG_TSO_EN; |
117 | plat_dat->pmt = 1; |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static int dwc_qos_probe(struct platform_device *pdev, |
123 | struct plat_stmmacenet_data *plat_dat, |
124 | struct stmmac_resources *stmmac_res) |
125 | { |
126 | int err; |
127 | |
128 | plat_dat->stmmac_clk = devm_clk_get(dev: &pdev->dev, id: "apb_pclk" ); |
129 | if (IS_ERR(ptr: plat_dat->stmmac_clk)) { |
130 | dev_err(&pdev->dev, "apb_pclk clock not found.\n" ); |
131 | return PTR_ERR(ptr: plat_dat->stmmac_clk); |
132 | } |
133 | |
134 | err = clk_prepare_enable(clk: plat_dat->stmmac_clk); |
135 | if (err < 0) { |
136 | dev_err(&pdev->dev, "failed to enable apb_pclk clock: %d\n" , |
137 | err); |
138 | return err; |
139 | } |
140 | |
141 | plat_dat->pclk = devm_clk_get(dev: &pdev->dev, id: "phy_ref_clk" ); |
142 | if (IS_ERR(ptr: plat_dat->pclk)) { |
143 | dev_err(&pdev->dev, "phy_ref_clk clock not found.\n" ); |
144 | err = PTR_ERR(ptr: plat_dat->pclk); |
145 | goto disable; |
146 | } |
147 | |
148 | err = clk_prepare_enable(clk: plat_dat->pclk); |
149 | if (err < 0) { |
150 | dev_err(&pdev->dev, "failed to enable phy_ref clock: %d\n" , |
151 | err); |
152 | goto disable; |
153 | } |
154 | |
155 | return 0; |
156 | |
157 | disable: |
158 | clk_disable_unprepare(clk: plat_dat->stmmac_clk); |
159 | return err; |
160 | } |
161 | |
162 | static void dwc_qos_remove(struct platform_device *pdev) |
163 | { |
164 | struct net_device *ndev = platform_get_drvdata(pdev); |
165 | struct stmmac_priv *priv = netdev_priv(dev: ndev); |
166 | |
167 | clk_disable_unprepare(clk: priv->plat->pclk); |
168 | clk_disable_unprepare(clk: priv->plat->stmmac_clk); |
169 | } |
170 | |
171 | #define SDMEMCOMPPADCTRL 0x8800 |
172 | #define SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD BIT(31) |
173 | |
174 | #define AUTO_CAL_CONFIG 0x8804 |
175 | #define AUTO_CAL_CONFIG_START BIT(31) |
176 | #define AUTO_CAL_CONFIG_ENABLE BIT(29) |
177 | |
178 | #define AUTO_CAL_STATUS 0x880c |
179 | #define AUTO_CAL_STATUS_ACTIVE BIT(31) |
180 | |
181 | static void tegra_eqos_fix_speed(void *priv, unsigned int speed, unsigned int mode) |
182 | { |
183 | struct tegra_eqos *eqos = priv; |
184 | unsigned long rate = 125000000; |
185 | bool needs_calibration = false; |
186 | u32 value; |
187 | int err; |
188 | |
189 | switch (speed) { |
190 | case SPEED_1000: |
191 | needs_calibration = true; |
192 | rate = 125000000; |
193 | break; |
194 | |
195 | case SPEED_100: |
196 | needs_calibration = true; |
197 | rate = 25000000; |
198 | break; |
199 | |
200 | case SPEED_10: |
201 | rate = 2500000; |
202 | break; |
203 | |
204 | default: |
205 | dev_err(eqos->dev, "invalid speed %u\n" , speed); |
206 | break; |
207 | } |
208 | |
209 | if (needs_calibration) { |
210 | /* calibrate */ |
211 | value = readl(addr: eqos->regs + SDMEMCOMPPADCTRL); |
212 | value |= SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD; |
213 | writel(val: value, addr: eqos->regs + SDMEMCOMPPADCTRL); |
214 | |
215 | udelay(1); |
216 | |
217 | value = readl(addr: eqos->regs + AUTO_CAL_CONFIG); |
218 | value |= AUTO_CAL_CONFIG_START | AUTO_CAL_CONFIG_ENABLE; |
219 | writel(val: value, addr: eqos->regs + AUTO_CAL_CONFIG); |
220 | |
221 | err = readl_poll_timeout_atomic(eqos->regs + AUTO_CAL_STATUS, |
222 | value, |
223 | value & AUTO_CAL_STATUS_ACTIVE, |
224 | 1, 10); |
225 | if (err < 0) { |
226 | dev_err(eqos->dev, "calibration did not start\n" ); |
227 | goto failed; |
228 | } |
229 | |
230 | err = readl_poll_timeout_atomic(eqos->regs + AUTO_CAL_STATUS, |
231 | value, |
232 | (value & AUTO_CAL_STATUS_ACTIVE) == 0, |
233 | 20, 200); |
234 | if (err < 0) { |
235 | dev_err(eqos->dev, "calibration didn't finish\n" ); |
236 | goto failed; |
237 | } |
238 | |
239 | failed: |
240 | value = readl(addr: eqos->regs + SDMEMCOMPPADCTRL); |
241 | value &= ~SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD; |
242 | writel(val: value, addr: eqos->regs + SDMEMCOMPPADCTRL); |
243 | } else { |
244 | value = readl(addr: eqos->regs + AUTO_CAL_CONFIG); |
245 | value &= ~AUTO_CAL_CONFIG_ENABLE; |
246 | writel(val: value, addr: eqos->regs + AUTO_CAL_CONFIG); |
247 | } |
248 | |
249 | err = clk_set_rate(clk: eqos->clk_tx, rate); |
250 | if (err < 0) |
251 | dev_err(eqos->dev, "failed to set TX rate: %d\n" , err); |
252 | } |
253 | |
254 | static int tegra_eqos_init(struct platform_device *pdev, void *priv) |
255 | { |
256 | struct tegra_eqos *eqos = priv; |
257 | unsigned long rate; |
258 | u32 value; |
259 | |
260 | rate = clk_get_rate(clk: eqos->clk_slave); |
261 | |
262 | value = (rate / 1000000) - 1; |
263 | writel(val: value, addr: eqos->regs + GMAC_1US_TIC_COUNTER); |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | static int tegra_eqos_probe(struct platform_device *pdev, |
269 | struct plat_stmmacenet_data *data, |
270 | struct stmmac_resources *res) |
271 | { |
272 | struct device *dev = &pdev->dev; |
273 | struct tegra_eqos *eqos; |
274 | int err; |
275 | |
276 | eqos = devm_kzalloc(dev: &pdev->dev, size: sizeof(*eqos), GFP_KERNEL); |
277 | if (!eqos) |
278 | return -ENOMEM; |
279 | |
280 | eqos->dev = &pdev->dev; |
281 | eqos->regs = res->addr; |
282 | |
283 | if (!is_of_node(fwnode: dev->fwnode)) |
284 | goto bypass_clk_reset_gpio; |
285 | |
286 | eqos->clk_master = devm_clk_get(dev: &pdev->dev, id: "master_bus" ); |
287 | if (IS_ERR(ptr: eqos->clk_master)) { |
288 | err = PTR_ERR(ptr: eqos->clk_master); |
289 | goto error; |
290 | } |
291 | |
292 | err = clk_prepare_enable(clk: eqos->clk_master); |
293 | if (err < 0) |
294 | goto error; |
295 | |
296 | eqos->clk_slave = devm_clk_get(dev: &pdev->dev, id: "slave_bus" ); |
297 | if (IS_ERR(ptr: eqos->clk_slave)) { |
298 | err = PTR_ERR(ptr: eqos->clk_slave); |
299 | goto disable_master; |
300 | } |
301 | |
302 | data->stmmac_clk = eqos->clk_slave; |
303 | |
304 | err = clk_prepare_enable(clk: eqos->clk_slave); |
305 | if (err < 0) |
306 | goto disable_master; |
307 | |
308 | eqos->clk_rx = devm_clk_get(dev: &pdev->dev, id: "rx" ); |
309 | if (IS_ERR(ptr: eqos->clk_rx)) { |
310 | err = PTR_ERR(ptr: eqos->clk_rx); |
311 | goto disable_slave; |
312 | } |
313 | |
314 | err = clk_prepare_enable(clk: eqos->clk_rx); |
315 | if (err < 0) |
316 | goto disable_slave; |
317 | |
318 | eqos->clk_tx = devm_clk_get(dev: &pdev->dev, id: "tx" ); |
319 | if (IS_ERR(ptr: eqos->clk_tx)) { |
320 | err = PTR_ERR(ptr: eqos->clk_tx); |
321 | goto disable_rx; |
322 | } |
323 | |
324 | err = clk_prepare_enable(clk: eqos->clk_tx); |
325 | if (err < 0) |
326 | goto disable_rx; |
327 | |
328 | eqos->reset = devm_gpiod_get(dev: &pdev->dev, con_id: "phy-reset" , flags: GPIOD_OUT_HIGH); |
329 | if (IS_ERR(ptr: eqos->reset)) { |
330 | err = PTR_ERR(ptr: eqos->reset); |
331 | goto disable_tx; |
332 | } |
333 | |
334 | usleep_range(min: 2000, max: 4000); |
335 | gpiod_set_value(desc: eqos->reset, value: 0); |
336 | |
337 | /* MDIO bus was already reset just above */ |
338 | data->mdio_bus_data->needs_reset = false; |
339 | |
340 | eqos->rst = devm_reset_control_get(dev: &pdev->dev, id: "eqos" ); |
341 | if (IS_ERR(ptr: eqos->rst)) { |
342 | err = PTR_ERR(ptr: eqos->rst); |
343 | goto reset_phy; |
344 | } |
345 | |
346 | err = reset_control_assert(rstc: eqos->rst); |
347 | if (err < 0) |
348 | goto reset_phy; |
349 | |
350 | usleep_range(min: 2000, max: 4000); |
351 | |
352 | err = reset_control_deassert(rstc: eqos->rst); |
353 | if (err < 0) |
354 | goto reset_phy; |
355 | |
356 | usleep_range(min: 2000, max: 4000); |
357 | |
358 | bypass_clk_reset_gpio: |
359 | data->fix_mac_speed = tegra_eqos_fix_speed; |
360 | data->init = tegra_eqos_init; |
361 | data->bsp_priv = eqos; |
362 | data->flags |= STMMAC_FLAG_SPH_DISABLE; |
363 | |
364 | err = tegra_eqos_init(pdev, priv: eqos); |
365 | if (err < 0) |
366 | goto reset; |
367 | |
368 | return 0; |
369 | reset: |
370 | reset_control_assert(rstc: eqos->rst); |
371 | reset_phy: |
372 | gpiod_set_value(desc: eqos->reset, value: 1); |
373 | disable_tx: |
374 | clk_disable_unprepare(clk: eqos->clk_tx); |
375 | disable_rx: |
376 | clk_disable_unprepare(clk: eqos->clk_rx); |
377 | disable_slave: |
378 | clk_disable_unprepare(clk: eqos->clk_slave); |
379 | disable_master: |
380 | clk_disable_unprepare(clk: eqos->clk_master); |
381 | error: |
382 | return err; |
383 | } |
384 | |
385 | static void tegra_eqos_remove(struct platform_device *pdev) |
386 | { |
387 | struct tegra_eqos *eqos = get_stmmac_bsp_priv(dev: &pdev->dev); |
388 | |
389 | reset_control_assert(rstc: eqos->rst); |
390 | gpiod_set_value(desc: eqos->reset, value: 1); |
391 | clk_disable_unprepare(clk: eqos->clk_tx); |
392 | clk_disable_unprepare(clk: eqos->clk_rx); |
393 | clk_disable_unprepare(clk: eqos->clk_slave); |
394 | clk_disable_unprepare(clk: eqos->clk_master); |
395 | } |
396 | |
397 | struct dwc_eth_dwmac_data { |
398 | int (*probe)(struct platform_device *pdev, |
399 | struct plat_stmmacenet_data *data, |
400 | struct stmmac_resources *res); |
401 | void (*remove)(struct platform_device *pdev); |
402 | }; |
403 | |
404 | static const struct dwc_eth_dwmac_data dwc_qos_data = { |
405 | .probe = dwc_qos_probe, |
406 | .remove = dwc_qos_remove, |
407 | }; |
408 | |
409 | static const struct dwc_eth_dwmac_data tegra_eqos_data = { |
410 | .probe = tegra_eqos_probe, |
411 | .remove = tegra_eqos_remove, |
412 | }; |
413 | |
414 | static int dwc_eth_dwmac_probe(struct platform_device *pdev) |
415 | { |
416 | const struct dwc_eth_dwmac_data *data; |
417 | struct plat_stmmacenet_data *plat_dat; |
418 | struct stmmac_resources stmmac_res; |
419 | int ret; |
420 | |
421 | data = device_get_match_data(dev: &pdev->dev); |
422 | |
423 | memset(&stmmac_res, 0, sizeof(struct stmmac_resources)); |
424 | |
425 | /** |
426 | * Since stmmac_platform supports name IRQ only, basic platform |
427 | * resource initialization is done in the glue logic. |
428 | */ |
429 | stmmac_res.irq = platform_get_irq(pdev, 0); |
430 | if (stmmac_res.irq < 0) |
431 | return stmmac_res.irq; |
432 | stmmac_res.wol_irq = stmmac_res.irq; |
433 | |
434 | stmmac_res.addr = devm_platform_ioremap_resource(pdev, index: 0); |
435 | if (IS_ERR(ptr: stmmac_res.addr)) |
436 | return PTR_ERR(ptr: stmmac_res.addr); |
437 | |
438 | plat_dat = devm_stmmac_probe_config_dt(pdev, mac: stmmac_res.mac); |
439 | if (IS_ERR(ptr: plat_dat)) |
440 | return PTR_ERR(ptr: plat_dat); |
441 | |
442 | ret = data->probe(pdev, plat_dat, &stmmac_res); |
443 | if (ret < 0) { |
444 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "failed to probe subdriver\n" ); |
445 | return ret; |
446 | } |
447 | |
448 | ret = dwc_eth_dwmac_config_dt(pdev, plat_dat); |
449 | if (ret) |
450 | goto remove; |
451 | |
452 | ret = stmmac_dvr_probe(device: &pdev->dev, plat_dat, res: &stmmac_res); |
453 | if (ret) |
454 | goto remove; |
455 | |
456 | return ret; |
457 | |
458 | remove: |
459 | data->remove(pdev); |
460 | |
461 | return ret; |
462 | } |
463 | |
464 | static void dwc_eth_dwmac_remove(struct platform_device *pdev) |
465 | { |
466 | const struct dwc_eth_dwmac_data *data = device_get_match_data(dev: &pdev->dev); |
467 | |
468 | stmmac_dvr_remove(dev: &pdev->dev); |
469 | |
470 | data->remove(pdev); |
471 | } |
472 | |
473 | static const struct of_device_id dwc_eth_dwmac_match[] = { |
474 | { .compatible = "snps,dwc-qos-ethernet-4.10" , .data = &dwc_qos_data }, |
475 | { .compatible = "nvidia,tegra186-eqos" , .data = &tegra_eqos_data }, |
476 | { } |
477 | }; |
478 | MODULE_DEVICE_TABLE(of, dwc_eth_dwmac_match); |
479 | |
480 | static struct platform_driver dwc_eth_dwmac_driver = { |
481 | .probe = dwc_eth_dwmac_probe, |
482 | .remove_new = dwc_eth_dwmac_remove, |
483 | .driver = { |
484 | .name = "dwc-eth-dwmac" , |
485 | .pm = &stmmac_pltfr_pm_ops, |
486 | .of_match_table = dwc_eth_dwmac_match, |
487 | }, |
488 | }; |
489 | module_platform_driver(dwc_eth_dwmac_driver); |
490 | |
491 | MODULE_AUTHOR("Joao Pinto <jpinto@synopsys.com>" ); |
492 | MODULE_DESCRIPTION("Synopsys DWC Ethernet Quality-of-Service v4.10a driver" ); |
493 | MODULE_LICENSE("GPL v2" ); |
494 | |