1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #include <linux/platform_device.h> |
3 | #include <linux/of.h> |
4 | #include <linux/module.h> |
5 | #include <linux/stmmac.h> |
6 | #include <linux/clk.h> |
7 | |
8 | #include "stmmac_platform.h" |
9 | |
10 | static const char *const mgbe_clks[] = { |
11 | "rx-pcs" , "tx" , "tx-pcs" , "mac-divider" , "mac" , "mgbe" , "ptp-ref" , "mac" |
12 | }; |
13 | |
14 | struct tegra_mgbe { |
15 | struct device *dev; |
16 | |
17 | struct clk_bulk_data *clks; |
18 | |
19 | struct reset_control *rst_mac; |
20 | struct reset_control *rst_pcs; |
21 | |
22 | void __iomem *hv; |
23 | void __iomem *regs; |
24 | void __iomem *xpcs; |
25 | |
26 | struct mii_bus *mii; |
27 | }; |
28 | |
29 | #define XPCS_WRAP_UPHY_RX_CONTROL 0x801c |
30 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD BIT(31) |
31 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY BIT(10) |
32 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET BIT(9) |
33 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN BIT(8) |
34 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP (BIT(7) | BIT(6)) |
35 | #define XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ BIT(5) |
36 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ BIT(4) |
37 | #define XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN BIT(0) |
38 | #define XPCS_WRAP_UPHY_HW_INIT_CTRL 0x8020 |
39 | #define XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN BIT(0) |
40 | #define XPCS_WRAP_UPHY_HW_INIT_CTRL_RX_EN BIT(2) |
41 | #define XPCS_WRAP_UPHY_STATUS 0x8044 |
42 | #define XPCS_WRAP_UPHY_STATUS_TX_P_UP BIT(0) |
43 | #define XPCS_WRAP_IRQ_STATUS 0x8050 |
44 | #define XPCS_WRAP_IRQ_STATUS_PCS_LINK_STS BIT(6) |
45 | |
46 | #define XPCS_REG_ADDR_SHIFT 10 |
47 | #define XPCS_REG_ADDR_MASK 0x1fff |
48 | #define XPCS_ADDR 0x3fc |
49 | |
50 | #define MGBE_WRAP_COMMON_INTR_ENABLE 0x8704 |
51 | #define MAC_SBD_INTR BIT(2) |
52 | #define MGBE_WRAP_AXI_ASID0_CTRL 0x8400 |
53 | #define MGBE_SID 0x6 |
54 | |
55 | static int __maybe_unused tegra_mgbe_suspend(struct device *dev) |
56 | { |
57 | struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev); |
58 | int err; |
59 | |
60 | err = stmmac_suspend(dev); |
61 | if (err) |
62 | return err; |
63 | |
64 | clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
65 | |
66 | return reset_control_assert(rstc: mgbe->rst_mac); |
67 | } |
68 | |
69 | static int __maybe_unused tegra_mgbe_resume(struct device *dev) |
70 | { |
71 | struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev); |
72 | u32 value; |
73 | int err; |
74 | |
75 | err = clk_bulk_prepare_enable(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
76 | if (err < 0) |
77 | return err; |
78 | |
79 | err = reset_control_deassert(rstc: mgbe->rst_mac); |
80 | if (err < 0) |
81 | return err; |
82 | |
83 | /* Enable common interrupt at wrapper level */ |
84 | writel(MAC_SBD_INTR, addr: mgbe->regs + MGBE_WRAP_COMMON_INTR_ENABLE); |
85 | |
86 | /* Program SID */ |
87 | writel(MGBE_SID, addr: mgbe->hv + MGBE_WRAP_AXI_ASID0_CTRL); |
88 | |
89 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_STATUS); |
90 | if ((value & XPCS_WRAP_UPHY_STATUS_TX_P_UP) == 0) { |
91 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL); |
92 | value |= XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN; |
93 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL); |
94 | } |
95 | |
96 | err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL, value, |
97 | (value & XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN) == 0, |
98 | 500, 500 * 2000); |
99 | if (err < 0) { |
100 | dev_err(mgbe->dev, "timeout waiting for TX lane to become enabled\n" ); |
101 | clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
102 | return err; |
103 | } |
104 | |
105 | err = stmmac_resume(dev); |
106 | if (err < 0) |
107 | clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
108 | |
109 | return err; |
110 | } |
111 | |
112 | static int mgbe_uphy_lane_bringup_serdes_up(struct net_device *ndev, void *mgbe_data) |
113 | { |
114 | struct tegra_mgbe *mgbe = (struct tegra_mgbe *)mgbe_data; |
115 | u32 value; |
116 | int err; |
117 | |
118 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
119 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD; |
120 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
121 | |
122 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
123 | value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ; |
124 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
125 | |
126 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
127 | value &= ~XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ; |
128 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
129 | |
130 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
131 | value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP; |
132 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
133 | |
134 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
135 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN; |
136 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
137 | |
138 | err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL, value, |
139 | (value & XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN) == 0, |
140 | 1000, 1000 * 2000); |
141 | if (err < 0) { |
142 | dev_err(mgbe->dev, "timeout waiting for RX calibration to become enabled\n" ); |
143 | return err; |
144 | } |
145 | |
146 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
147 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN; |
148 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
149 | |
150 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
151 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET; |
152 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
153 | |
154 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
155 | value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET; |
156 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
157 | |
158 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
159 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY; |
160 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
161 | |
162 | err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_IRQ_STATUS, value, |
163 | value & XPCS_WRAP_IRQ_STATUS_PCS_LINK_STS, |
164 | 500, 500 * 2000); |
165 | if (err < 0) { |
166 | dev_err(mgbe->dev, "timeout waiting for link to become ready\n" ); |
167 | return err; |
168 | } |
169 | |
170 | /* clear status */ |
171 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_IRQ_STATUS); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static void mgbe_uphy_lane_bringup_serdes_down(struct net_device *ndev, void *mgbe_data) |
177 | { |
178 | struct tegra_mgbe *mgbe = (struct tegra_mgbe *)mgbe_data; |
179 | u32 value; |
180 | |
181 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
182 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD; |
183 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
184 | |
185 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
186 | value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN; |
187 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
188 | |
189 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
190 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP; |
191 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
192 | |
193 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
194 | value |= XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ; |
195 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
196 | |
197 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
198 | value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ; |
199 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL); |
200 | } |
201 | |
202 | static int tegra_mgbe_probe(struct platform_device *pdev) |
203 | { |
204 | struct plat_stmmacenet_data *plat; |
205 | struct stmmac_resources res; |
206 | struct tegra_mgbe *mgbe; |
207 | int irq, err, i; |
208 | u32 value; |
209 | |
210 | mgbe = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mgbe), GFP_KERNEL); |
211 | if (!mgbe) |
212 | return -ENOMEM; |
213 | |
214 | mgbe->dev = &pdev->dev; |
215 | |
216 | memset(&res, 0, sizeof(res)); |
217 | |
218 | irq = platform_get_irq(pdev, 0); |
219 | if (irq < 0) |
220 | return irq; |
221 | |
222 | mgbe->hv = devm_platform_ioremap_resource_byname(pdev, name: "hypervisor" ); |
223 | if (IS_ERR(ptr: mgbe->hv)) |
224 | return PTR_ERR(ptr: mgbe->hv); |
225 | |
226 | mgbe->regs = devm_platform_ioremap_resource_byname(pdev, name: "mac" ); |
227 | if (IS_ERR(ptr: mgbe->regs)) |
228 | return PTR_ERR(ptr: mgbe->regs); |
229 | |
230 | mgbe->xpcs = devm_platform_ioremap_resource_byname(pdev, name: "xpcs" ); |
231 | if (IS_ERR(ptr: mgbe->xpcs)) |
232 | return PTR_ERR(ptr: mgbe->xpcs); |
233 | |
234 | res.addr = mgbe->regs; |
235 | res.irq = irq; |
236 | |
237 | mgbe->clks = devm_kcalloc(dev: &pdev->dev, ARRAY_SIZE(mgbe_clks), |
238 | size: sizeof(*mgbe->clks), GFP_KERNEL); |
239 | if (!mgbe->clks) |
240 | return -ENOMEM; |
241 | |
242 | for (i = 0; i < ARRAY_SIZE(mgbe_clks); i++) |
243 | mgbe->clks[i].id = mgbe_clks[i]; |
244 | |
245 | err = devm_clk_bulk_get(dev: mgbe->dev, ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
246 | if (err < 0) |
247 | return err; |
248 | |
249 | err = clk_bulk_prepare_enable(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
250 | if (err < 0) |
251 | return err; |
252 | |
253 | /* Perform MAC reset */ |
254 | mgbe->rst_mac = devm_reset_control_get(dev: &pdev->dev, id: "mac" ); |
255 | if (IS_ERR(ptr: mgbe->rst_mac)) { |
256 | err = PTR_ERR(ptr: mgbe->rst_mac); |
257 | goto disable_clks; |
258 | } |
259 | |
260 | err = reset_control_assert(rstc: mgbe->rst_mac); |
261 | if (err < 0) |
262 | goto disable_clks; |
263 | |
264 | usleep_range(min: 2000, max: 4000); |
265 | |
266 | err = reset_control_deassert(rstc: mgbe->rst_mac); |
267 | if (err < 0) |
268 | goto disable_clks; |
269 | |
270 | /* Perform PCS reset */ |
271 | mgbe->rst_pcs = devm_reset_control_get(dev: &pdev->dev, id: "pcs" ); |
272 | if (IS_ERR(ptr: mgbe->rst_pcs)) { |
273 | err = PTR_ERR(ptr: mgbe->rst_pcs); |
274 | goto disable_clks; |
275 | } |
276 | |
277 | err = reset_control_assert(rstc: mgbe->rst_pcs); |
278 | if (err < 0) |
279 | goto disable_clks; |
280 | |
281 | usleep_range(min: 2000, max: 4000); |
282 | |
283 | err = reset_control_deassert(rstc: mgbe->rst_pcs); |
284 | if (err < 0) |
285 | goto disable_clks; |
286 | |
287 | plat = devm_stmmac_probe_config_dt(pdev, mac: res.mac); |
288 | if (IS_ERR(ptr: plat)) { |
289 | err = PTR_ERR(ptr: plat); |
290 | goto disable_clks; |
291 | } |
292 | |
293 | plat->has_xgmac = 1; |
294 | plat->flags |= STMMAC_FLAG_TSO_EN; |
295 | plat->pmt = 1; |
296 | plat->bsp_priv = mgbe; |
297 | |
298 | if (!plat->mdio_node) |
299 | plat->mdio_node = of_get_child_by_name(node: pdev->dev.of_node, name: "mdio" ); |
300 | |
301 | if (!plat->mdio_bus_data) { |
302 | plat->mdio_bus_data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*plat->mdio_bus_data), |
303 | GFP_KERNEL); |
304 | if (!plat->mdio_bus_data) { |
305 | err = -ENOMEM; |
306 | goto disable_clks; |
307 | } |
308 | } |
309 | |
310 | plat->mdio_bus_data->needs_reset = true; |
311 | |
312 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_STATUS); |
313 | if ((value & XPCS_WRAP_UPHY_STATUS_TX_P_UP) == 0) { |
314 | value = readl(addr: mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL); |
315 | value |= XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN; |
316 | writel(val: value, addr: mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL); |
317 | } |
318 | |
319 | err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL, value, |
320 | (value & XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN) == 0, |
321 | 500, 500 * 2000); |
322 | if (err < 0) { |
323 | dev_err(mgbe->dev, "timeout waiting for TX lane to become enabled\n" ); |
324 | goto disable_clks; |
325 | } |
326 | |
327 | plat->serdes_powerup = mgbe_uphy_lane_bringup_serdes_up; |
328 | plat->serdes_powerdown = mgbe_uphy_lane_bringup_serdes_down; |
329 | |
330 | /* Tx FIFO Size - 128KB */ |
331 | plat->tx_fifo_size = 131072; |
332 | /* Rx FIFO Size - 192KB */ |
333 | plat->rx_fifo_size = 196608; |
334 | |
335 | /* Enable common interrupt at wrapper level */ |
336 | writel(MAC_SBD_INTR, addr: mgbe->regs + MGBE_WRAP_COMMON_INTR_ENABLE); |
337 | |
338 | /* Program SID */ |
339 | writel(MGBE_SID, addr: mgbe->hv + MGBE_WRAP_AXI_ASID0_CTRL); |
340 | |
341 | plat->flags |= STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP; |
342 | |
343 | err = stmmac_dvr_probe(device: &pdev->dev, plat_dat: plat, res: &res); |
344 | if (err < 0) |
345 | goto disable_clks; |
346 | |
347 | return 0; |
348 | |
349 | disable_clks: |
350 | clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
351 | |
352 | return err; |
353 | } |
354 | |
355 | static void tegra_mgbe_remove(struct platform_device *pdev) |
356 | { |
357 | struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev: &pdev->dev); |
358 | |
359 | clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), clks: mgbe->clks); |
360 | |
361 | stmmac_pltfr_remove(pdev); |
362 | } |
363 | |
364 | static const struct of_device_id tegra_mgbe_match[] = { |
365 | { .compatible = "nvidia,tegra234-mgbe" , }, |
366 | { } |
367 | }; |
368 | MODULE_DEVICE_TABLE(of, tegra_mgbe_match); |
369 | |
370 | static SIMPLE_DEV_PM_OPS(tegra_mgbe_pm_ops, tegra_mgbe_suspend, tegra_mgbe_resume); |
371 | |
372 | static struct platform_driver tegra_mgbe_driver = { |
373 | .probe = tegra_mgbe_probe, |
374 | .remove_new = tegra_mgbe_remove, |
375 | .driver = { |
376 | .name = "tegra-mgbe" , |
377 | .pm = &tegra_mgbe_pm_ops, |
378 | .of_match_table = tegra_mgbe_match, |
379 | }, |
380 | }; |
381 | module_platform_driver(tegra_mgbe_driver); |
382 | |
383 | MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>" ); |
384 | MODULE_DESCRIPTION("NVIDIA Tegra MGBE driver" ); |
385 | MODULE_LICENSE("GPL" ); |
386 | |