1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Marvell 88E6352 family SERDES PCS support |
4 | * |
5 | * Copyright (c) 2008 Marvell Semiconductor |
6 | * |
7 | * Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch> |
8 | */ |
9 | #include <linux/interrupt.h> |
10 | #include <linux/irqdomain.h> |
11 | #include <linux/mii.h> |
12 | |
13 | #include "chip.h" |
14 | #include "global2.h" |
15 | #include "phy.h" |
16 | #include "port.h" |
17 | #include "serdes.h" |
18 | |
19 | struct mv88e639x_pcs { |
20 | struct mdio_device mdio; |
21 | struct phylink_pcs sgmii_pcs; |
22 | struct phylink_pcs xg_pcs; |
23 | bool erratum_3_14; |
24 | bool supports_5g; |
25 | phy_interface_t interface; |
26 | unsigned int irq; |
27 | char name[64]; |
28 | irqreturn_t (*handle_irq)(struct mv88e639x_pcs *mpcs); |
29 | }; |
30 | |
31 | static int mv88e639x_read(struct mv88e639x_pcs *mpcs, u16 regnum, u16 *val) |
32 | { |
33 | int err; |
34 | |
35 | err = mdiodev_c45_read(mdiodev: &mpcs->mdio, MDIO_MMD_PHYXS, regnum); |
36 | if (err < 0) |
37 | return err; |
38 | |
39 | *val = err; |
40 | |
41 | return 0; |
42 | } |
43 | |
44 | static int mv88e639x_write(struct mv88e639x_pcs *mpcs, u16 regnum, u16 val) |
45 | { |
46 | return mdiodev_c45_write(mdiodev: &mpcs->mdio, MDIO_MMD_PHYXS, regnum, val); |
47 | } |
48 | |
49 | static int mv88e639x_modify(struct mv88e639x_pcs *mpcs, u16 regnum, u16 mask, |
50 | u16 val) |
51 | { |
52 | return mdiodev_c45_modify(mdiodev: &mpcs->mdio, MDIO_MMD_PHYXS, regnum, mask, |
53 | set: val); |
54 | } |
55 | |
56 | static int mv88e639x_modify_changed(struct mv88e639x_pcs *mpcs, u16 regnum, |
57 | u16 mask, u16 set) |
58 | { |
59 | return mdiodev_c45_modify_changed(mdiodev: &mpcs->mdio, MDIO_MMD_PHYXS, regnum, |
60 | mask, set); |
61 | } |
62 | |
63 | static struct mv88e639x_pcs * |
64 | mv88e639x_pcs_alloc(struct device *dev, struct mii_bus *bus, unsigned int addr, |
65 | int port) |
66 | { |
67 | struct mv88e639x_pcs *mpcs; |
68 | |
69 | mpcs = kzalloc(size: sizeof(*mpcs), GFP_KERNEL); |
70 | if (!mpcs) |
71 | return NULL; |
72 | |
73 | mpcs->mdio.dev.parent = dev; |
74 | mpcs->mdio.bus = bus; |
75 | mpcs->mdio.addr = addr; |
76 | |
77 | snprintf(buf: mpcs->name, size: sizeof(mpcs->name), |
78 | fmt: "mv88e6xxx-%s-serdes-%d" , dev_name(dev), port); |
79 | |
80 | return mpcs; |
81 | } |
82 | |
83 | static irqreturn_t mv88e639x_pcs_handle_irq(int irq, void *dev_id) |
84 | { |
85 | struct mv88e639x_pcs *mpcs = dev_id; |
86 | irqreturn_t (*handler)(struct mv88e639x_pcs *); |
87 | |
88 | handler = READ_ONCE(mpcs->handle_irq); |
89 | if (!handler) |
90 | return IRQ_NONE; |
91 | |
92 | return handler(mpcs); |
93 | } |
94 | |
95 | static int mv88e639x_pcs_setup_irq(struct mv88e639x_pcs *mpcs, |
96 | struct mv88e6xxx_chip *chip, int port) |
97 | { |
98 | unsigned int irq; |
99 | |
100 | irq = mv88e6xxx_serdes_irq_mapping(chip, port); |
101 | if (!irq) { |
102 | /* Use polling mode */ |
103 | mpcs->sgmii_pcs.poll = true; |
104 | mpcs->xg_pcs.poll = true; |
105 | return 0; |
106 | } |
107 | |
108 | mpcs->irq = irq; |
109 | |
110 | return request_threaded_irq(irq, NULL, thread_fn: mv88e639x_pcs_handle_irq, |
111 | IRQF_ONESHOT, name: mpcs->name, dev: mpcs); |
112 | } |
113 | |
114 | static void mv88e639x_pcs_teardown(struct mv88e6xxx_chip *chip, int port) |
115 | { |
116 | struct mv88e639x_pcs *mpcs = chip->ports[port].pcs_private; |
117 | |
118 | if (!mpcs) |
119 | return; |
120 | |
121 | if (mpcs->irq) |
122 | free_irq(mpcs->irq, mpcs); |
123 | |
124 | kfree(objp: mpcs); |
125 | |
126 | chip->ports[port].pcs_private = NULL; |
127 | } |
128 | |
129 | static struct mv88e639x_pcs *sgmii_pcs_to_mv88e639x_pcs(struct phylink_pcs *pcs) |
130 | { |
131 | return container_of(pcs, struct mv88e639x_pcs, sgmii_pcs); |
132 | } |
133 | |
134 | static irqreturn_t mv88e639x_sgmii_handle_irq(struct mv88e639x_pcs *mpcs) |
135 | { |
136 | u16 int_status; |
137 | int err; |
138 | |
139 | err = mv88e639x_read(mpcs, MV88E6390_SGMII_INT_STATUS, val: &int_status); |
140 | if (err) |
141 | return IRQ_NONE; |
142 | |
143 | if (int_status & (MV88E6390_SGMII_INT_LINK_DOWN | |
144 | MV88E6390_SGMII_INT_LINK_UP)) { |
145 | phylink_pcs_change(&mpcs->sgmii_pcs, |
146 | up: int_status & MV88E6390_SGMII_INT_LINK_UP); |
147 | |
148 | return IRQ_HANDLED; |
149 | } |
150 | |
151 | return IRQ_NONE; |
152 | } |
153 | |
154 | static int mv88e639x_sgmii_pcs_control_irq(struct mv88e639x_pcs *mpcs, |
155 | bool enable) |
156 | { |
157 | u16 val = 0; |
158 | |
159 | if (enable) |
160 | val |= MV88E6390_SGMII_INT_LINK_DOWN | |
161 | MV88E6390_SGMII_INT_LINK_UP; |
162 | |
163 | return mv88e639x_modify(mpcs, MV88E6390_SGMII_INT_ENABLE, |
164 | MV88E6390_SGMII_INT_LINK_DOWN | |
165 | MV88E6390_SGMII_INT_LINK_UP, val); |
166 | } |
167 | |
168 | static int mv88e639x_sgmii_pcs_control_pwr(struct mv88e639x_pcs *mpcs, |
169 | bool enable) |
170 | { |
171 | u16 mask, val; |
172 | |
173 | if (enable) { |
174 | mask = BMCR_RESET | BMCR_LOOPBACK | BMCR_PDOWN; |
175 | val = 0; |
176 | } else { |
177 | mask = val = BMCR_PDOWN; |
178 | } |
179 | |
180 | return mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR, mask, val); |
181 | } |
182 | |
183 | static int mv88e639x_sgmii_pcs_enable(struct phylink_pcs *pcs) |
184 | { |
185 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
186 | |
187 | /* power enable done in post_config */ |
188 | mpcs->handle_irq = mv88e639x_sgmii_handle_irq; |
189 | |
190 | return mv88e639x_sgmii_pcs_control_irq(mpcs, enable: !!mpcs->irq); |
191 | } |
192 | |
193 | static void mv88e639x_sgmii_pcs_disable(struct phylink_pcs *pcs) |
194 | { |
195 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
196 | |
197 | mv88e639x_sgmii_pcs_control_irq(mpcs, enable: false); |
198 | mv88e639x_sgmii_pcs_control_pwr(mpcs, enable: false); |
199 | } |
200 | |
201 | static void mv88e639x_sgmii_pcs_pre_config(struct phylink_pcs *pcs, |
202 | phy_interface_t interface) |
203 | { |
204 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
205 | |
206 | mv88e639x_sgmii_pcs_control_pwr(mpcs, enable: false); |
207 | } |
208 | |
209 | static int mv88e6390_erratum_3_14(struct mv88e639x_pcs *mpcs) |
210 | { |
211 | static const int lanes[] = { MV88E6390_PORT9_LANE0, MV88E6390_PORT9_LANE1, |
212 | MV88E6390_PORT9_LANE2, MV88E6390_PORT9_LANE3, |
213 | MV88E6390_PORT10_LANE0, MV88E6390_PORT10_LANE1, |
214 | MV88E6390_PORT10_LANE2, MV88E6390_PORT10_LANE3 }; |
215 | int err, i; |
216 | |
217 | /* 88e6190x and 88e6390x errata 3.14: |
218 | * After chip reset, SERDES reconfiguration or SERDES core |
219 | * Software Reset, the SERDES lanes may not be properly aligned |
220 | * resulting in CRC errors |
221 | */ |
222 | |
223 | for (i = 0; i < ARRAY_SIZE(lanes); i++) { |
224 | err = mdiobus_c45_write(bus: mpcs->mdio.bus, addr: lanes[i], |
225 | MDIO_MMD_PHYXS, |
226 | regnum: 0xf054, val: 0x400C); |
227 | if (err) |
228 | return err; |
229 | |
230 | err = mdiobus_c45_write(bus: mpcs->mdio.bus, addr: lanes[i], |
231 | MDIO_MMD_PHYXS, |
232 | regnum: 0xf054, val: 0x4000); |
233 | if (err) |
234 | return err; |
235 | } |
236 | |
237 | return 0; |
238 | } |
239 | |
240 | static int mv88e639x_sgmii_pcs_post_config(struct phylink_pcs *pcs, |
241 | phy_interface_t interface) |
242 | { |
243 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
244 | int err; |
245 | |
246 | mv88e639x_sgmii_pcs_control_pwr(mpcs, enable: true); |
247 | |
248 | if (mpcs->erratum_3_14) { |
249 | err = mv88e6390_erratum_3_14(mpcs); |
250 | if (err) |
251 | dev_err(mpcs->mdio.dev.parent, |
252 | "failed to apply erratum 3.14: %pe\n" , |
253 | ERR_PTR(err)); |
254 | } |
255 | |
256 | return 0; |
257 | } |
258 | |
259 | static void mv88e639x_sgmii_pcs_get_state(struct phylink_pcs *pcs, |
260 | struct phylink_link_state *state) |
261 | { |
262 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
263 | u16 bmsr, lpa, status; |
264 | int err; |
265 | |
266 | err = mv88e639x_read(mpcs, MV88E6390_SGMII_BMSR, val: &bmsr); |
267 | if (err) { |
268 | dev_err(mpcs->mdio.dev.parent, |
269 | "can't read Serdes PHY %s: %pe\n" , |
270 | "BMSR" , ERR_PTR(err)); |
271 | state->link = false; |
272 | return; |
273 | } |
274 | |
275 | err = mv88e639x_read(mpcs, MV88E6390_SGMII_LPA, val: &lpa); |
276 | if (err) { |
277 | dev_err(mpcs->mdio.dev.parent, |
278 | "can't read Serdes PHY %s: %pe\n" , |
279 | "LPA" , ERR_PTR(err)); |
280 | state->link = false; |
281 | return; |
282 | } |
283 | |
284 | err = mv88e639x_read(mpcs, MV88E6390_SGMII_PHY_STATUS, val: &status); |
285 | if (err) { |
286 | dev_err(mpcs->mdio.dev.parent, |
287 | "can't read Serdes PHY %s: %pe\n" , |
288 | "status" , ERR_PTR(err)); |
289 | state->link = false; |
290 | return; |
291 | } |
292 | |
293 | mv88e6xxx_pcs_decode_state(dev: mpcs->mdio.dev.parent, bmsr, lpa, status, |
294 | state); |
295 | } |
296 | |
297 | static int mv88e639x_sgmii_pcs_config(struct phylink_pcs *pcs, |
298 | unsigned int neg_mode, |
299 | phy_interface_t interface, |
300 | const unsigned long *advertising, |
301 | bool permit_pause_to_mac) |
302 | { |
303 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
304 | u16 val, bmcr; |
305 | bool changed; |
306 | int adv, err; |
307 | |
308 | adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising); |
309 | if (adv < 0) |
310 | return 0; |
311 | |
312 | mpcs->interface = interface; |
313 | |
314 | err = mv88e639x_modify_changed(mpcs, MV88E6390_SGMII_ADVERTISE, |
315 | mask: 0xffff, set: adv); |
316 | if (err < 0) |
317 | return err; |
318 | |
319 | changed = err > 0; |
320 | |
321 | err = mv88e639x_read(mpcs, MV88E6390_SGMII_BMCR, val: &val); |
322 | if (err) |
323 | return err; |
324 | |
325 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) |
326 | bmcr = val | BMCR_ANENABLE; |
327 | else |
328 | bmcr = val & ~BMCR_ANENABLE; |
329 | |
330 | /* setting ANENABLE triggers a restart of negotiation */ |
331 | if (bmcr == val) |
332 | return changed; |
333 | |
334 | return mv88e639x_write(mpcs, MV88E6390_SGMII_BMCR, val: bmcr); |
335 | } |
336 | |
337 | static void mv88e639x_sgmii_pcs_an_restart(struct phylink_pcs *pcs) |
338 | { |
339 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
340 | |
341 | mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR, |
342 | BMCR_ANRESTART, BMCR_ANRESTART); |
343 | } |
344 | |
345 | static void mv88e639x_sgmii_pcs_link_up(struct phylink_pcs *pcs, |
346 | unsigned int mode, |
347 | phy_interface_t interface, |
348 | int speed, int duplex) |
349 | { |
350 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
351 | u16 bmcr; |
352 | int err; |
353 | |
354 | if (phylink_autoneg_inband(mode)) |
355 | return; |
356 | |
357 | bmcr = mii_bmcr_encode_fixed(speed, duplex); |
358 | |
359 | err = mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR, |
360 | BMCR_SPEED1000 | BMCR_SPEED100 | BMCR_FULLDPLX, |
361 | val: bmcr); |
362 | if (err) |
363 | dev_err(mpcs->mdio.dev.parent, |
364 | "can't access Serdes PHY %s: %pe\n" , |
365 | "BMCR" , ERR_PTR(err)); |
366 | } |
367 | |
368 | static const struct phylink_pcs_ops mv88e639x_sgmii_pcs_ops = { |
369 | .pcs_enable = mv88e639x_sgmii_pcs_enable, |
370 | .pcs_disable = mv88e639x_sgmii_pcs_disable, |
371 | .pcs_pre_config = mv88e639x_sgmii_pcs_pre_config, |
372 | .pcs_post_config = mv88e639x_sgmii_pcs_post_config, |
373 | .pcs_get_state = mv88e639x_sgmii_pcs_get_state, |
374 | .pcs_an_restart = mv88e639x_sgmii_pcs_an_restart, |
375 | .pcs_config = mv88e639x_sgmii_pcs_config, |
376 | .pcs_link_up = mv88e639x_sgmii_pcs_link_up, |
377 | }; |
378 | |
379 | static struct mv88e639x_pcs *xg_pcs_to_mv88e639x_pcs(struct phylink_pcs *pcs) |
380 | { |
381 | return container_of(pcs, struct mv88e639x_pcs, xg_pcs); |
382 | } |
383 | |
384 | static int mv88e639x_xg_pcs_enable(struct mv88e639x_pcs *mpcs) |
385 | { |
386 | return mv88e639x_modify(mpcs, MV88E6390_10G_CTRL1, |
387 | MDIO_CTRL1_RESET | MDIO_PCS_CTRL1_LOOPBACK | |
388 | MDIO_CTRL1_LPOWER, val: 0); |
389 | } |
390 | |
391 | static void mv88e639x_xg_pcs_disable(struct mv88e639x_pcs *mpcs) |
392 | { |
393 | mv88e639x_modify(mpcs, MV88E6390_10G_CTRL1, MDIO_CTRL1_LPOWER, |
394 | MDIO_CTRL1_LPOWER); |
395 | } |
396 | |
397 | static void mv88e639x_xg_pcs_get_state(struct phylink_pcs *pcs, |
398 | struct phylink_link_state *state) |
399 | { |
400 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
401 | u16 status; |
402 | int err; |
403 | |
404 | state->link = false; |
405 | |
406 | err = mv88e639x_read(mpcs, MV88E6390_10G_STAT1, val: &status); |
407 | if (err) { |
408 | dev_err(mpcs->mdio.dev.parent, |
409 | "can't read Serdes PHY %s: %pe\n" , |
410 | "STAT1" , ERR_PTR(err)); |
411 | return; |
412 | } |
413 | |
414 | state->link = !!(status & MDIO_STAT1_LSTATUS); |
415 | if (state->link) { |
416 | switch (state->interface) { |
417 | case PHY_INTERFACE_MODE_5GBASER: |
418 | state->speed = SPEED_5000; |
419 | break; |
420 | |
421 | case PHY_INTERFACE_MODE_10GBASER: |
422 | case PHY_INTERFACE_MODE_RXAUI: |
423 | case PHY_INTERFACE_MODE_XAUI: |
424 | state->speed = SPEED_10000; |
425 | break; |
426 | |
427 | default: |
428 | state->link = false; |
429 | return; |
430 | } |
431 | |
432 | state->duplex = DUPLEX_FULL; |
433 | } |
434 | } |
435 | |
436 | static int mv88e639x_xg_pcs_config(struct phylink_pcs *pcs, |
437 | unsigned int neg_mode, |
438 | phy_interface_t interface, |
439 | const unsigned long *advertising, |
440 | bool permit_pause_to_mac) |
441 | { |
442 | return 0; |
443 | } |
444 | |
445 | static struct phylink_pcs * |
446 | mv88e639x_pcs_select(struct mv88e6xxx_chip *chip, int port, |
447 | phy_interface_t mode) |
448 | { |
449 | struct mv88e639x_pcs *mpcs; |
450 | |
451 | mpcs = chip->ports[port].pcs_private; |
452 | if (!mpcs) |
453 | return NULL; |
454 | |
455 | switch (mode) { |
456 | case PHY_INTERFACE_MODE_SGMII: |
457 | case PHY_INTERFACE_MODE_1000BASEX: |
458 | case PHY_INTERFACE_MODE_2500BASEX: |
459 | return &mpcs->sgmii_pcs; |
460 | |
461 | case PHY_INTERFACE_MODE_5GBASER: |
462 | if (!mpcs->supports_5g) |
463 | return NULL; |
464 | fallthrough; |
465 | case PHY_INTERFACE_MODE_10GBASER: |
466 | case PHY_INTERFACE_MODE_XAUI: |
467 | case PHY_INTERFACE_MODE_RXAUI: |
468 | case PHY_INTERFACE_MODE_USXGMII: |
469 | return &mpcs->xg_pcs; |
470 | |
471 | default: |
472 | return NULL; |
473 | } |
474 | } |
475 | |
476 | /* Marvell 88E6390 Specific support */ |
477 | |
478 | static irqreturn_t mv88e6390_xg_handle_irq(struct mv88e639x_pcs *mpcs) |
479 | { |
480 | u16 int_status; |
481 | int err; |
482 | |
483 | err = mv88e639x_read(mpcs, MV88E6390_10G_INT_STATUS, val: &int_status); |
484 | if (err) |
485 | return IRQ_NONE; |
486 | |
487 | if (int_status & (MV88E6390_10G_INT_LINK_DOWN | |
488 | MV88E6390_10G_INT_LINK_UP)) { |
489 | phylink_pcs_change(&mpcs->xg_pcs, |
490 | up: int_status & MV88E6390_10G_INT_LINK_UP); |
491 | |
492 | return IRQ_HANDLED; |
493 | } |
494 | |
495 | return IRQ_NONE; |
496 | } |
497 | |
498 | static int mv88e6390_xg_control_irq(struct mv88e639x_pcs *mpcs, bool enable) |
499 | { |
500 | u16 val = 0; |
501 | |
502 | if (enable) |
503 | val = MV88E6390_10G_INT_LINK_DOWN | MV88E6390_10G_INT_LINK_UP; |
504 | |
505 | return mv88e639x_modify(mpcs, MV88E6390_10G_INT_ENABLE, |
506 | MV88E6390_10G_INT_LINK_DOWN | |
507 | MV88E6390_10G_INT_LINK_UP, val); |
508 | } |
509 | |
510 | static int mv88e6390_xg_pcs_enable(struct phylink_pcs *pcs) |
511 | { |
512 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
513 | int err; |
514 | |
515 | err = mv88e639x_xg_pcs_enable(mpcs); |
516 | if (err) |
517 | return err; |
518 | |
519 | mpcs->handle_irq = mv88e6390_xg_handle_irq; |
520 | |
521 | return mv88e6390_xg_control_irq(mpcs, enable: !!mpcs->irq); |
522 | } |
523 | |
524 | static void mv88e6390_xg_pcs_disable(struct phylink_pcs *pcs) |
525 | { |
526 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
527 | |
528 | mv88e6390_xg_control_irq(mpcs, enable: false); |
529 | mv88e639x_xg_pcs_disable(mpcs); |
530 | } |
531 | |
532 | static const struct phylink_pcs_ops mv88e6390_xg_pcs_ops = { |
533 | .pcs_enable = mv88e6390_xg_pcs_enable, |
534 | .pcs_disable = mv88e6390_xg_pcs_disable, |
535 | .pcs_get_state = mv88e639x_xg_pcs_get_state, |
536 | .pcs_config = mv88e639x_xg_pcs_config, |
537 | }; |
538 | |
539 | static int mv88e6390_pcs_enable_checker(struct mv88e639x_pcs *mpcs) |
540 | { |
541 | return mv88e639x_modify(mpcs, MV88E6390_PG_CONTROL, |
542 | MV88E6390_PG_CONTROL_ENABLE_PC, |
543 | MV88E6390_PG_CONTROL_ENABLE_PC); |
544 | } |
545 | |
546 | static int mv88e6390_pcs_init(struct mv88e6xxx_chip *chip, int port) |
547 | { |
548 | struct mv88e639x_pcs *mpcs; |
549 | struct mii_bus *bus; |
550 | struct device *dev; |
551 | int lane, err; |
552 | |
553 | lane = mv88e6xxx_serdes_get_lane(chip, port); |
554 | if (lane < 0) |
555 | return 0; |
556 | |
557 | bus = mv88e6xxx_default_mdio_bus(chip); |
558 | dev = chip->dev; |
559 | |
560 | mpcs = mv88e639x_pcs_alloc(dev, bus, addr: lane, port); |
561 | if (!mpcs) |
562 | return -ENOMEM; |
563 | |
564 | mpcs->sgmii_pcs.ops = &mv88e639x_sgmii_pcs_ops; |
565 | mpcs->sgmii_pcs.neg_mode = true; |
566 | mpcs->xg_pcs.ops = &mv88e6390_xg_pcs_ops; |
567 | mpcs->xg_pcs.neg_mode = true; |
568 | |
569 | if (chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6190X || |
570 | chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6390X) |
571 | mpcs->erratum_3_14 = true; |
572 | |
573 | err = mv88e639x_pcs_setup_irq(mpcs, chip, port); |
574 | if (err) |
575 | goto err_free; |
576 | |
577 | /* 6390 and 6390x has the checker, 6393x doesn't appear to? */ |
578 | /* This is to enable gathering the statistics. Maybe this |
579 | * should call out to a helper? Or we could do this at init time. |
580 | */ |
581 | err = mv88e6390_pcs_enable_checker(mpcs); |
582 | if (err) |
583 | goto err_free; |
584 | |
585 | chip->ports[port].pcs_private = mpcs; |
586 | |
587 | return 0; |
588 | |
589 | err_free: |
590 | kfree(objp: mpcs); |
591 | return err; |
592 | } |
593 | |
594 | const struct mv88e6xxx_pcs_ops mv88e6390_pcs_ops = { |
595 | .pcs_init = mv88e6390_pcs_init, |
596 | .pcs_teardown = mv88e639x_pcs_teardown, |
597 | .pcs_select = mv88e639x_pcs_select, |
598 | }; |
599 | |
600 | /* Marvell 88E6393X Specific support */ |
601 | |
602 | static int mv88e6393x_power_lane(struct mv88e639x_pcs *mpcs, bool enable) |
603 | { |
604 | u16 val = MV88E6393X_SERDES_CTRL1_TX_PDOWN | |
605 | MV88E6393X_SERDES_CTRL1_RX_PDOWN; |
606 | |
607 | return mv88e639x_modify(mpcs, MV88E6393X_SERDES_CTRL1, mask: val, |
608 | val: enable ? 0 : val); |
609 | } |
610 | |
611 | /* mv88e6393x family errata 4.6: |
612 | * Cannot clear PwrDn bit on SERDES if device is configured CPU_MGD mode or |
613 | * P0_mode is configured for [x]MII. |
614 | * Workaround: Set SERDES register 4.F002 bit 5=0 and bit 15=1. |
615 | * |
616 | * It seems that after this workaround the SERDES is automatically powered up |
617 | * (the bit is cleared), so power it down. |
618 | */ |
619 | static int mv88e6393x_erratum_4_6(struct mv88e639x_pcs *mpcs) |
620 | { |
621 | int err; |
622 | |
623 | err = mv88e639x_modify(mpcs, MV88E6393X_SERDES_POC, |
624 | MV88E6393X_SERDES_POC_PDOWN | |
625 | MV88E6393X_SERDES_POC_RESET, |
626 | MV88E6393X_SERDES_POC_RESET); |
627 | if (err) |
628 | return err; |
629 | |
630 | err = mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR, |
631 | BMCR_PDOWN, BMCR_PDOWN); |
632 | if (err) |
633 | return err; |
634 | |
635 | err = mv88e639x_sgmii_pcs_control_pwr(mpcs, enable: false); |
636 | if (err) |
637 | return err; |
638 | |
639 | return mv88e6393x_power_lane(mpcs, enable: false); |
640 | } |
641 | |
642 | /* mv88e6393x family errata 4.8: |
643 | * When a SERDES port is operating in 1000BASE-X or SGMII mode link may not |
644 | * come up after hardware reset or software reset of SERDES core. Workaround |
645 | * is to write SERDES register 4.F074.14=1 for only those modes and 0 in all |
646 | * other modes. |
647 | */ |
648 | static int mv88e6393x_erratum_4_8(struct mv88e639x_pcs *mpcs) |
649 | { |
650 | u16 reg, poc; |
651 | int err; |
652 | |
653 | err = mv88e639x_read(mpcs, MV88E6393X_SERDES_POC, val: &poc); |
654 | if (err) |
655 | return err; |
656 | |
657 | poc &= MV88E6393X_SERDES_POC_PCS_MASK; |
658 | if (poc == MV88E6393X_SERDES_POC_PCS_1000BASEX || |
659 | poc == MV88E6393X_SERDES_POC_PCS_SGMII_PHY || |
660 | poc == MV88E6393X_SERDES_POC_PCS_SGMII_MAC) |
661 | reg = MV88E6393X_ERRATA_4_8_BIT; |
662 | else |
663 | reg = 0; |
664 | |
665 | return mv88e639x_modify(mpcs, MV88E6393X_ERRATA_4_8_REG, |
666 | MV88E6393X_ERRATA_4_8_BIT, val: reg); |
667 | } |
668 | |
669 | /* mv88e6393x family errata 5.2: |
670 | * For optimal signal integrity the following sequence should be applied to |
671 | * SERDES operating in 10G mode. These registers only apply to 10G operation |
672 | * and have no effect on other speeds. |
673 | */ |
674 | static int mv88e6393x_erratum_5_2(struct mv88e639x_pcs *mpcs) |
675 | { |
676 | static const struct { |
677 | u16 dev, reg, val, mask; |
678 | } fixes[] = { |
679 | { MDIO_MMD_VEND1, 0x8093, 0xcb5a, 0xffff }, |
680 | { MDIO_MMD_VEND1, 0x8171, 0x7088, 0xffff }, |
681 | { MDIO_MMD_VEND1, 0x80c9, 0x311a, 0xffff }, |
682 | { MDIO_MMD_VEND1, 0x80a2, 0x8000, 0xff7f }, |
683 | { MDIO_MMD_VEND1, 0x80a9, 0x0000, 0xfff0 }, |
684 | { MDIO_MMD_VEND1, 0x80a3, 0x0000, 0xf8ff }, |
685 | { MDIO_MMD_PHYXS, MV88E6393X_SERDES_POC, |
686 | MV88E6393X_SERDES_POC_RESET, MV88E6393X_SERDES_POC_RESET }, |
687 | }; |
688 | int err, i; |
689 | |
690 | for (i = 0; i < ARRAY_SIZE(fixes); ++i) { |
691 | err = mdiodev_c45_modify(mdiodev: &mpcs->mdio, devad: fixes[i].dev, |
692 | regnum: fixes[i].reg, mask: fixes[i].mask, |
693 | set: fixes[i].val); |
694 | if (err) |
695 | return err; |
696 | } |
697 | |
698 | return 0; |
699 | } |
700 | |
701 | /* Inband AN is broken on Amethyst in 2500base-x mode when set by standard |
702 | * mechanism (via cmode). |
703 | * We can get around this by configuring the PCS mode to 1000base-x and then |
704 | * writing value 0x58 to register 1e.8000. (This must be done while SerDes |
705 | * receiver and transmitter are disabled, which is, when this function is |
706 | * called.) |
707 | * It seem that when we do this configuration to 2500base-x mode (by changing |
708 | * PCS mode to 1000base-x and frequency to 3.125 GHz from 1.25 GHz) and then |
709 | * configure to sgmii or 1000base-x, the device thinks that it already has |
710 | * SerDes at 1.25 GHz and does not change the 1e.8000 register, leaving SerDes |
711 | * at 3.125 GHz. |
712 | * To avoid this, change PCS mode back to 2500base-x when disabling SerDes from |
713 | * 2500base-x mode. |
714 | */ |
715 | static int mv88e6393x_fix_2500basex_an(struct mv88e639x_pcs *mpcs, bool on) |
716 | { |
717 | u16 reg; |
718 | int err; |
719 | |
720 | if (on) |
721 | reg = MV88E6393X_SERDES_POC_PCS_1000BASEX | |
722 | MV88E6393X_SERDES_POC_AN; |
723 | else |
724 | reg = MV88E6393X_SERDES_POC_PCS_2500BASEX; |
725 | |
726 | reg |= MV88E6393X_SERDES_POC_RESET; |
727 | |
728 | err = mv88e639x_modify(mpcs, MV88E6393X_SERDES_POC, |
729 | MV88E6393X_SERDES_POC_PCS_MASK | |
730 | MV88E6393X_SERDES_POC_AN | |
731 | MV88E6393X_SERDES_POC_RESET, val: reg); |
732 | if (err) |
733 | return err; |
734 | |
735 | return mdiodev_c45_write(mdiodev: &mpcs->mdio, MDIO_MMD_VEND1, regnum: 0x8000, val: 0x58); |
736 | } |
737 | |
738 | static int mv88e6393x_sgmii_apply_2500basex_an(struct mv88e639x_pcs *mpcs, |
739 | phy_interface_t interface, |
740 | bool enable) |
741 | { |
742 | int err; |
743 | |
744 | if (interface != PHY_INTERFACE_MODE_2500BASEX) |
745 | return 0; |
746 | |
747 | err = mv88e6393x_fix_2500basex_an(mpcs, on: enable); |
748 | if (err) |
749 | dev_err(mpcs->mdio.dev.parent, |
750 | "failed to %s 2500basex fix: %pe\n" , |
751 | enable ? "enable" : "disable" , ERR_PTR(err)); |
752 | |
753 | return err; |
754 | } |
755 | |
756 | static void mv88e6393x_sgmii_pcs_disable(struct phylink_pcs *pcs) |
757 | { |
758 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
759 | |
760 | mv88e639x_sgmii_pcs_disable(pcs); |
761 | mv88e6393x_power_lane(mpcs, enable: false); |
762 | mv88e6393x_sgmii_apply_2500basex_an(mpcs, interface: mpcs->interface, enable: false); |
763 | } |
764 | |
765 | static void mv88e6393x_sgmii_pcs_pre_config(struct phylink_pcs *pcs, |
766 | phy_interface_t interface) |
767 | { |
768 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
769 | |
770 | mv88e639x_sgmii_pcs_pre_config(pcs, interface); |
771 | mv88e6393x_power_lane(mpcs, enable: false); |
772 | mv88e6393x_sgmii_apply_2500basex_an(mpcs, interface: mpcs->interface, enable: false); |
773 | } |
774 | |
775 | static int mv88e6393x_sgmii_pcs_post_config(struct phylink_pcs *pcs, |
776 | phy_interface_t interface) |
777 | { |
778 | struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs); |
779 | int err; |
780 | |
781 | err = mv88e6393x_erratum_4_8(mpcs); |
782 | if (err) |
783 | return err; |
784 | |
785 | err = mv88e6393x_sgmii_apply_2500basex_an(mpcs, interface, enable: true); |
786 | if (err) |
787 | return err; |
788 | |
789 | err = mv88e6393x_power_lane(mpcs, enable: true); |
790 | if (err) |
791 | return err; |
792 | |
793 | return mv88e639x_sgmii_pcs_post_config(pcs, interface); |
794 | } |
795 | |
796 | static const struct phylink_pcs_ops mv88e6393x_sgmii_pcs_ops = { |
797 | .pcs_enable = mv88e639x_sgmii_pcs_enable, |
798 | .pcs_disable = mv88e6393x_sgmii_pcs_disable, |
799 | .pcs_pre_config = mv88e6393x_sgmii_pcs_pre_config, |
800 | .pcs_post_config = mv88e6393x_sgmii_pcs_post_config, |
801 | .pcs_get_state = mv88e639x_sgmii_pcs_get_state, |
802 | .pcs_an_restart = mv88e639x_sgmii_pcs_an_restart, |
803 | .pcs_config = mv88e639x_sgmii_pcs_config, |
804 | .pcs_link_up = mv88e639x_sgmii_pcs_link_up, |
805 | }; |
806 | |
807 | static irqreturn_t mv88e6393x_xg_handle_irq(struct mv88e639x_pcs *mpcs) |
808 | { |
809 | u16 int_status, stat1; |
810 | bool link_down; |
811 | int err; |
812 | |
813 | err = mv88e639x_read(mpcs, MV88E6393X_10G_INT_STATUS, val: &int_status); |
814 | if (err) |
815 | return IRQ_NONE; |
816 | |
817 | if (int_status & MV88E6393X_10G_INT_LINK_CHANGE) { |
818 | err = mv88e639x_read(mpcs, MV88E6390_10G_STAT1, val: &stat1); |
819 | if (err) |
820 | return IRQ_NONE; |
821 | |
822 | link_down = !(stat1 & MDIO_STAT1_LSTATUS); |
823 | |
824 | phylink_pcs_change(&mpcs->xg_pcs, up: !link_down); |
825 | |
826 | return IRQ_HANDLED; |
827 | } |
828 | |
829 | return IRQ_NONE; |
830 | } |
831 | |
832 | static int mv88e6393x_xg_control_irq(struct mv88e639x_pcs *mpcs, bool enable) |
833 | { |
834 | u16 val = 0; |
835 | |
836 | if (enable) |
837 | val = MV88E6393X_10G_INT_LINK_CHANGE; |
838 | |
839 | return mv88e639x_modify(mpcs, MV88E6393X_10G_INT_ENABLE, |
840 | MV88E6393X_10G_INT_LINK_CHANGE, val); |
841 | } |
842 | |
843 | static int mv88e6393x_xg_pcs_enable(struct phylink_pcs *pcs) |
844 | { |
845 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
846 | |
847 | mpcs->handle_irq = mv88e6393x_xg_handle_irq; |
848 | |
849 | return mv88e6393x_xg_control_irq(mpcs, enable: !!mpcs->irq); |
850 | } |
851 | |
852 | static void mv88e6393x_xg_pcs_disable(struct phylink_pcs *pcs) |
853 | { |
854 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
855 | |
856 | mv88e6393x_xg_control_irq(mpcs, enable: false); |
857 | mv88e639x_xg_pcs_disable(mpcs); |
858 | mv88e6393x_power_lane(mpcs, enable: false); |
859 | } |
860 | |
861 | /* The PCS has to be powered down while CMODE is changed */ |
862 | static void mv88e6393x_xg_pcs_pre_config(struct phylink_pcs *pcs, |
863 | phy_interface_t interface) |
864 | { |
865 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
866 | |
867 | mv88e639x_xg_pcs_disable(mpcs); |
868 | mv88e6393x_power_lane(mpcs, enable: false); |
869 | } |
870 | |
871 | static int mv88e6393x_xg_pcs_post_config(struct phylink_pcs *pcs, |
872 | phy_interface_t interface) |
873 | { |
874 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
875 | int err; |
876 | |
877 | if (interface == PHY_INTERFACE_MODE_10GBASER || |
878 | interface == PHY_INTERFACE_MODE_USXGMII) { |
879 | err = mv88e6393x_erratum_5_2(mpcs); |
880 | if (err) |
881 | return err; |
882 | } |
883 | |
884 | err = mv88e6393x_power_lane(mpcs, enable: true); |
885 | if (err) |
886 | return err; |
887 | |
888 | return mv88e639x_xg_pcs_enable(mpcs); |
889 | } |
890 | |
891 | static void mv88e6393x_xg_pcs_get_state(struct phylink_pcs *pcs, |
892 | struct phylink_link_state *state) |
893 | { |
894 | struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs); |
895 | u16 status, lp_status; |
896 | int err; |
897 | |
898 | if (state->interface != PHY_INTERFACE_MODE_USXGMII) |
899 | return mv88e639x_xg_pcs_get_state(pcs, state); |
900 | |
901 | state->link = false; |
902 | |
903 | err = mv88e639x_read(mpcs, MV88E6390_USXGMII_PHY_STATUS, val: &status); |
904 | err = err ? : mv88e639x_read(mpcs, MV88E6390_USXGMII_LP_STATUS, val: &lp_status); |
905 | if (err) { |
906 | dev_err(mpcs->mdio.dev.parent, |
907 | "can't read USXGMII status: %pe\n" , ERR_PTR(err)); |
908 | return; |
909 | } |
910 | |
911 | state->link = !!(status & MDIO_USXGMII_LINK); |
912 | state->an_complete = state->link; |
913 | phylink_decode_usxgmii_word(state, lpa: lp_status); |
914 | } |
915 | |
916 | static const struct phylink_pcs_ops mv88e6393x_xg_pcs_ops = { |
917 | .pcs_enable = mv88e6393x_xg_pcs_enable, |
918 | .pcs_disable = mv88e6393x_xg_pcs_disable, |
919 | .pcs_pre_config = mv88e6393x_xg_pcs_pre_config, |
920 | .pcs_post_config = mv88e6393x_xg_pcs_post_config, |
921 | .pcs_get_state = mv88e6393x_xg_pcs_get_state, |
922 | .pcs_config = mv88e639x_xg_pcs_config, |
923 | }; |
924 | |
925 | static int mv88e6393x_pcs_init(struct mv88e6xxx_chip *chip, int port) |
926 | { |
927 | struct mv88e639x_pcs *mpcs; |
928 | struct mii_bus *bus; |
929 | struct device *dev; |
930 | int lane, err; |
931 | |
932 | lane = mv88e6xxx_serdes_get_lane(chip, port); |
933 | if (lane < 0) |
934 | return 0; |
935 | |
936 | bus = mv88e6xxx_default_mdio_bus(chip); |
937 | dev = chip->dev; |
938 | |
939 | mpcs = mv88e639x_pcs_alloc(dev, bus, addr: lane, port); |
940 | if (!mpcs) |
941 | return -ENOMEM; |
942 | |
943 | mpcs->sgmii_pcs.ops = &mv88e6393x_sgmii_pcs_ops; |
944 | mpcs->sgmii_pcs.neg_mode = true; |
945 | mpcs->xg_pcs.ops = &mv88e6393x_xg_pcs_ops; |
946 | mpcs->xg_pcs.neg_mode = true; |
947 | mpcs->supports_5g = true; |
948 | |
949 | err = mv88e6393x_erratum_4_6(mpcs); |
950 | if (err) |
951 | goto err_free; |
952 | |
953 | err = mv88e639x_pcs_setup_irq(mpcs, chip, port); |
954 | if (err) |
955 | goto err_free; |
956 | |
957 | chip->ports[port].pcs_private = mpcs; |
958 | |
959 | return 0; |
960 | |
961 | err_free: |
962 | kfree(objp: mpcs); |
963 | return err; |
964 | } |
965 | |
966 | const struct mv88e6xxx_pcs_ops mv88e6393x_pcs_ops = { |
967 | .pcs_init = mv88e6393x_pcs_init, |
968 | .pcs_teardown = mv88e639x_pcs_teardown, |
969 | .pcs_select = mv88e639x_pcs_select, |
970 | }; |
971 | |