Commit | Line | Data |
---|---|---|
0da4c3d3 IC |
1 | // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) |
2 | /* Copyright 2020 NXP | |
3 | * Lynx PCS MDIO helpers | |
4 | */ | |
5 | ||
6 | #include <linux/mdio.h> | |
7 | #include <linux/phylink.h> | |
8 | #include <linux/pcs-lynx.h> | |
d143898c | 9 | #include <linux/property.h> |
0da4c3d3 IC |
10 | |
11 | #define SGMII_CLOCK_PERIOD_NS 8 /* PCS is clocked at 125 MHz */ | |
12 | #define LINK_TIMER_VAL(ns) ((u32)((ns) / SGMII_CLOCK_PERIOD_NS)) | |
13 | ||
0da4c3d3 IC |
14 | #define LINK_TIMER_LO 0x12 |
15 | #define LINK_TIMER_HI 0x13 | |
16 | #define IF_MODE 0x14 | |
17 | #define IF_MODE_SGMII_EN BIT(0) | |
18 | #define IF_MODE_USE_SGMII_AN BIT(1) | |
19 | #define IF_MODE_SPEED(x) (((x) << 2) & GENMASK(3, 2)) | |
20 | #define IF_MODE_SPEED_MSK GENMASK(3, 2) | |
21 | #define IF_MODE_HALF_DUPLEX BIT(4) | |
22 | ||
e7026f15 CF |
23 | struct lynx_pcs { |
24 | struct phylink_pcs pcs; | |
25 | struct mdio_device *mdio; | |
26 | }; | |
27 | ||
0da4c3d3 IC |
28 | enum sgmii_speed { |
29 | SGMII_SPEED_10 = 0, | |
30 | SGMII_SPEED_100 = 1, | |
31 | SGMII_SPEED_1000 = 2, | |
32 | SGMII_SPEED_2500 = 2, | |
33 | }; | |
34 | ||
35 | #define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs) | |
e7026f15 CF |
36 | #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs) |
37 | ||
0da4c3d3 IC |
38 | static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs, |
39 | struct phylink_link_state *state) | |
40 | { | |
41 | struct mii_bus *bus = pcs->bus; | |
42 | int addr = pcs->addr; | |
43 | int status, lpa; | |
44 | ||
45 | status = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_BMSR); | |
46 | if (status < 0) | |
47 | return; | |
48 | ||
49 | state->link = !!(status & MDIO_STAT1_LSTATUS); | |
50 | state->an_complete = !!(status & MDIO_AN_STAT1_COMPLETE); | |
51 | if (!state->link || !state->an_complete) | |
52 | return; | |
53 | ||
54 | lpa = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_LPA); | |
55 | if (lpa < 0) | |
56 | return; | |
57 | ||
58 | phylink_decode_usxgmii_word(state, lpa); | |
59 | } | |
60 | ||
61 | static void lynx_pcs_get_state_2500basex(struct mdio_device *pcs, | |
62 | struct phylink_link_state *state) | |
63 | { | |
0da4c3d3 IC |
64 | int bmsr, lpa; |
65 | ||
a8236dfd RKO |
66 | bmsr = mdiodev_read(pcs, MII_BMSR); |
67 | lpa = mdiodev_read(pcs, MII_LPA); | |
0da4c3d3 IC |
68 | if (bmsr < 0 || lpa < 0) { |
69 | state->link = false; | |
70 | return; | |
71 | } | |
72 | ||
73 | state->link = !!(bmsr & BMSR_LSTATUS); | |
74 | state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE); | |
75 | if (!state->link) | |
76 | return; | |
77 | ||
78 | state->speed = SPEED_2500; | |
79 | state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX; | |
80 | state->duplex = DUPLEX_FULL; | |
81 | } | |
82 | ||
83 | static void lynx_pcs_get_state(struct phylink_pcs *pcs, | |
84 | struct phylink_link_state *state) | |
85 | { | |
86 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); | |
87 | ||
88 | switch (state->interface) { | |
694a0006 | 89 | case PHY_INTERFACE_MODE_1000BASEX: |
0da4c3d3 IC |
90 | case PHY_INTERFACE_MODE_SGMII: |
91 | case PHY_INTERFACE_MODE_QSGMII: | |
92 | phylink_mii_c22_pcs_get_state(lynx->mdio, state); | |
93 | break; | |
94 | case PHY_INTERFACE_MODE_2500BASEX: | |
95 | lynx_pcs_get_state_2500basex(lynx->mdio, state); | |
96 | break; | |
97 | case PHY_INTERFACE_MODE_USXGMII: | |
98 | lynx_pcs_get_state_usxgmii(lynx->mdio, state); | |
99 | break; | |
e7e95c90 IC |
100 | case PHY_INTERFACE_MODE_10GBASER: |
101 | phylink_mii_c45_pcs_get_state(lynx->mdio, state); | |
102 | break; | |
0da4c3d3 IC |
103 | default: |
104 | break; | |
105 | } | |
106 | ||
107 | dev_dbg(&lynx->mdio->dev, | |
ecec0ebb | 108 | "mode=%s/%s/%s link=%u an_complete=%u\n", |
0da4c3d3 IC |
109 | phy_modes(state->interface), |
110 | phy_speed_to_str(state->speed), | |
111 | phy_duplex_to_str(state->duplex), | |
ecec0ebb | 112 | state->link, state->an_complete); |
0da4c3d3 IC |
113 | } |
114 | ||
febf2aaf | 115 | static int lynx_pcs_config_giga(struct mdio_device *pcs, |
06f9a614 | 116 | phy_interface_t interface, |
febf2aaf RKO |
117 | const unsigned long *advertising, |
118 | unsigned int neg_mode) | |
694a0006 | 119 | { |
e2a95750 | 120 | int link_timer_ns; |
694a0006 | 121 | u32 link_timer; |
0da4c3d3 IC |
122 | u16 if_mode; |
123 | int err; | |
124 | ||
e2a95750 RKO |
125 | link_timer_ns = phylink_get_link_timer_ns(interface); |
126 | if (link_timer_ns > 0) { | |
127 | link_timer = LINK_TIMER_VAL(link_timer_ns); | |
128 | ||
a8236dfd RKO |
129 | mdiodev_write(pcs, LINK_TIMER_LO, link_timer & 0xffff); |
130 | mdiodev_write(pcs, LINK_TIMER_HI, link_timer >> 16); | |
e2a95750 | 131 | } |
06f9a614 | 132 | |
e2a95750 | 133 | if (interface == PHY_INTERFACE_MODE_1000BASEX) { |
06f9a614 RKO |
134 | if_mode = 0; |
135 | } else { | |
febf2aaf | 136 | /* SGMII and QSGMII */ |
06f9a614 | 137 | if_mode = IF_MODE_SGMII_EN; |
febf2aaf | 138 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) |
06f9a614 | 139 | if_mode |= IF_MODE_USE_SGMII_AN; |
0da4c3d3 | 140 | } |
06f9a614 | 141 | |
a8236dfd | 142 | err = mdiodev_modify(pcs, IF_MODE, |
0da4c3d3 IC |
143 | IF_MODE_SGMII_EN | IF_MODE_USE_SGMII_AN, |
144 | if_mode); | |
145 | if (err) | |
146 | return err; | |
147 | ||
febf2aaf RKO |
148 | return phylink_mii_c22_pcs_config(pcs, interface, advertising, |
149 | neg_mode); | |
0da4c3d3 IC |
150 | } |
151 | ||
c689a652 RKO |
152 | static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, |
153 | const unsigned long *advertising, | |
154 | unsigned int neg_mode) | |
0da4c3d3 IC |
155 | { |
156 | struct mii_bus *bus = pcs->bus; | |
157 | int addr = pcs->addr; | |
158 | ||
c689a652 | 159 | if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) { |
0da4c3d3 IC |
160 | dev_err(&pcs->dev, "USXGMII only supports in-band AN for now\n"); |
161 | return -EOPNOTSUPP; | |
162 | } | |
163 | ||
164 | /* Configure device ability for the USXGMII Replicator */ | |
165 | return mdiobus_c45_write(bus, addr, MDIO_MMD_VEND2, MII_ADVERTISE, | |
166 | MDIO_USXGMII_10G | MDIO_USXGMII_LINK | | |
167 | MDIO_USXGMII_FULL_DUPLEX | | |
168 | ADVERTISE_SGMII | ADVERTISE_LPACK); | |
169 | } | |
170 | ||
c689a652 | 171 | static int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, |
0da4c3d3 | 172 | phy_interface_t ifmode, |
c689a652 | 173 | const unsigned long *advertising, bool permit) |
0da4c3d3 IC |
174 | { |
175 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); | |
176 | ||
177 | switch (ifmode) { | |
694a0006 | 178 | case PHY_INTERFACE_MODE_1000BASEX: |
0da4c3d3 IC |
179 | case PHY_INTERFACE_MODE_SGMII: |
180 | case PHY_INTERFACE_MODE_QSGMII: | |
febf2aaf RKO |
181 | return lynx_pcs_config_giga(lynx->mdio, ifmode, advertising, |
182 | neg_mode); | |
0da4c3d3 | 183 | case PHY_INTERFACE_MODE_2500BASEX: |
c689a652 | 184 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { |
0da4c3d3 IC |
185 | dev_err(&lynx->mdio->dev, |
186 | "AN not supported on 3.125GHz SerDes lane\n"); | |
187 | return -EOPNOTSUPP; | |
188 | } | |
189 | break; | |
190 | case PHY_INTERFACE_MODE_USXGMII: | |
c689a652 RKO |
191 | return lynx_pcs_config_usxgmii(lynx->mdio, advertising, |
192 | neg_mode); | |
e7e95c90 IC |
193 | case PHY_INTERFACE_MODE_10GBASER: |
194 | /* Nothing to do here for 10GBASER */ | |
195 | break; | |
0da4c3d3 IC |
196 | default: |
197 | return -EOPNOTSUPP; | |
198 | } | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
694a0006 RK |
203 | static void lynx_pcs_an_restart(struct phylink_pcs *pcs) |
204 | { | |
205 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); | |
206 | ||
207 | phylink_mii_c22_pcs_an_restart(lynx->mdio); | |
208 | } | |
209 | ||
c689a652 RKO |
210 | static void lynx_pcs_link_up_sgmii(struct mdio_device *pcs, |
211 | unsigned int neg_mode, | |
0da4c3d3 IC |
212 | int speed, int duplex) |
213 | { | |
0da4c3d3 | 214 | u16 if_mode = 0, sgmii_speed; |
0da4c3d3 IC |
215 | |
216 | /* The PCS needs to be configured manually only | |
217 | * when not operating on in-band mode | |
218 | */ | |
2f4503f9 | 219 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) |
0da4c3d3 IC |
220 | return; |
221 | ||
222 | if (duplex == DUPLEX_HALF) | |
223 | if_mode |= IF_MODE_HALF_DUPLEX; | |
224 | ||
225 | switch (speed) { | |
226 | case SPEED_1000: | |
227 | sgmii_speed = SGMII_SPEED_1000; | |
228 | break; | |
229 | case SPEED_100: | |
230 | sgmii_speed = SGMII_SPEED_100; | |
231 | break; | |
232 | case SPEED_10: | |
233 | sgmii_speed = SGMII_SPEED_10; | |
234 | break; | |
235 | case SPEED_UNKNOWN: | |
236 | /* Silently don't do anything */ | |
237 | return; | |
238 | default: | |
239 | dev_err(&pcs->dev, "Invalid PCS speed %d\n", speed); | |
240 | return; | |
241 | } | |
242 | if_mode |= IF_MODE_SPEED(sgmii_speed); | |
243 | ||
a8236dfd | 244 | mdiodev_modify(pcs, IF_MODE, |
0da4c3d3 IC |
245 | IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, |
246 | if_mode); | |
247 | } | |
248 | ||
249 | /* 2500Base-X is SerDes protocol 7 on Felix and 6 on ENETC. It is a SerDes lane | |
250 | * clocked at 3.125 GHz which encodes symbols with 8b/10b and does not have | |
251 | * auto-negotiation of any link parameters. Electrically it is compatible with | |
252 | * a single lane of XAUI. | |
253 | * The hardware reference manual wants to call this mode SGMII, but it isn't | |
254 | * really, since the fundamental features of SGMII: | |
255 | * - Downgrading the link speed by duplicating symbols | |
256 | * - Auto-negotiation | |
257 | * are not there. | |
258 | * The speed is configured at 1000 in the IF_MODE because the clock frequency | |
259 | * is actually given by a PLL configured in the Reset Configuration Word (RCW). | |
260 | * Since there is no difference between fixed speed SGMII w/o AN and 802.3z w/o | |
261 | * AN, we call this PHY interface type 2500Base-X. In case a PHY negotiates a | |
262 | * lower link speed on line side, the system-side interface remains fixed at | |
263 | * 2500 Mbps and we do rate adaptation through pause frames. | |
264 | */ | |
265 | static void lynx_pcs_link_up_2500basex(struct mdio_device *pcs, | |
c689a652 | 266 | unsigned int neg_mode, |
0da4c3d3 IC |
267 | int speed, int duplex) |
268 | { | |
0da4c3d3 IC |
269 | u16 if_mode = 0; |
270 | ||
c689a652 | 271 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { |
0da4c3d3 IC |
272 | dev_err(&pcs->dev, "AN not supported for 2500BaseX\n"); |
273 | return; | |
274 | } | |
275 | ||
276 | if (duplex == DUPLEX_HALF) | |
277 | if_mode |= IF_MODE_HALF_DUPLEX; | |
278 | if_mode |= IF_MODE_SPEED(SGMII_SPEED_2500); | |
279 | ||
a8236dfd | 280 | mdiodev_modify(pcs, IF_MODE, |
0da4c3d3 IC |
281 | IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, |
282 | if_mode); | |
283 | } | |
284 | ||
c689a652 | 285 | static void lynx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, |
0da4c3d3 IC |
286 | phy_interface_t interface, |
287 | int speed, int duplex) | |
288 | { | |
289 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); | |
290 | ||
291 | switch (interface) { | |
292 | case PHY_INTERFACE_MODE_SGMII: | |
293 | case PHY_INTERFACE_MODE_QSGMII: | |
c689a652 | 294 | lynx_pcs_link_up_sgmii(lynx->mdio, neg_mode, speed, duplex); |
0da4c3d3 IC |
295 | break; |
296 | case PHY_INTERFACE_MODE_2500BASEX: | |
c689a652 | 297 | lynx_pcs_link_up_2500basex(lynx->mdio, neg_mode, speed, duplex); |
0da4c3d3 IC |
298 | break; |
299 | case PHY_INTERFACE_MODE_USXGMII: | |
300 | /* At the moment, only in-band AN is supported for USXGMII | |
301 | * so nothing to do in link_up | |
302 | */ | |
303 | break; | |
304 | default: | |
305 | break; | |
306 | } | |
307 | } | |
308 | ||
309 | static const struct phylink_pcs_ops lynx_pcs_phylink_ops = { | |
310 | .pcs_get_state = lynx_pcs_get_state, | |
311 | .pcs_config = lynx_pcs_config, | |
694a0006 | 312 | .pcs_an_restart = lynx_pcs_an_restart, |
0da4c3d3 IC |
313 | .pcs_link_up = lynx_pcs_link_up, |
314 | }; | |
315 | ||
84e476b8 | 316 | static struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio) |
0da4c3d3 | 317 | { |
0699b3e0 | 318 | struct lynx_pcs *lynx; |
0da4c3d3 | 319 | |
0699b3e0 CF |
320 | lynx = kzalloc(sizeof(*lynx), GFP_KERNEL); |
321 | if (!lynx) | |
05b606b8 | 322 | return ERR_PTR(-ENOMEM); |
0da4c3d3 | 323 | |
86b5f2d8 | 324 | mdio_device_get(mdio); |
0699b3e0 CF |
325 | lynx->mdio = mdio; |
326 | lynx->pcs.ops = &lynx_pcs_phylink_ops; | |
c689a652 | 327 | lynx->pcs.neg_mode = true; |
0699b3e0 | 328 | lynx->pcs.poll = true; |
0da4c3d3 | 329 | |
0699b3e0 | 330 | return lynx_to_phylink_pcs(lynx); |
0da4c3d3 | 331 | } |
0da4c3d3 | 332 | |
86b5f2d8 RKO |
333 | struct phylink_pcs *lynx_pcs_create_mdiodev(struct mii_bus *bus, int addr) |
334 | { | |
335 | struct mdio_device *mdio; | |
336 | struct phylink_pcs *pcs; | |
337 | ||
338 | mdio = mdio_device_create(bus, addr); | |
339 | if (IS_ERR(mdio)) | |
340 | return ERR_CAST(mdio); | |
341 | ||
342 | pcs = lynx_pcs_create(mdio); | |
343 | ||
86b5f2d8 RKO |
344 | /* lynx_create() has taken a refcount on the mdiodev if it was |
345 | * successful. If lynx_create() fails, this will free the mdio | |
346 | * device here. In any case, we don't need to hold our reference | |
347 | * anymore, and putting it here will allow mdio_device_put() in | |
348 | * lynx_destroy() to automatically free the mdio device. | |
349 | */ | |
350 | mdio_device_put(mdio); | |
351 | ||
352 | return pcs; | |
353 | } | |
354 | EXPORT_SYMBOL(lynx_pcs_create_mdiodev); | |
355 | ||
d143898c RKO |
356 | /* |
357 | * lynx_pcs_create_fwnode() creates a lynx PCS instance from the fwnode | |
358 | * device indicated by node. | |
359 | * | |
360 | * Returns: | |
361 | * -ENODEV if the fwnode is marked unavailable | |
362 | * -EPROBE_DEFER if we fail to find the device | |
363 | * -ENOMEM if we fail to allocate memory | |
364 | * pointer to a phylink_pcs on success | |
365 | */ | |
6e1a1282 RKO |
366 | struct phylink_pcs *lynx_pcs_create_fwnode(struct fwnode_handle *node) |
367 | { | |
368 | struct mdio_device *mdio; | |
369 | struct phylink_pcs *pcs; | |
370 | ||
d143898c RKO |
371 | if (!fwnode_device_is_available(node)) |
372 | return ERR_PTR(-ENODEV); | |
373 | ||
6e1a1282 RKO |
374 | mdio = fwnode_mdio_find_device(node); |
375 | if (!mdio) | |
376 | return ERR_PTR(-EPROBE_DEFER); | |
377 | ||
378 | pcs = lynx_pcs_create(mdio); | |
379 | ||
6e1a1282 RKO |
380 | /* lynx_create() has taken a refcount on the mdiodev if it was |
381 | * successful. If lynx_create() fails, this will free the mdio | |
382 | * device here. In any case, we don't need to hold our reference | |
383 | * anymore, and putting it here will allow mdio_device_put() in | |
384 | * lynx_destroy() to automatically free the mdio device. | |
385 | */ | |
386 | mdio_device_put(mdio); | |
387 | ||
388 | return pcs; | |
389 | } | |
390 | EXPORT_SYMBOL_GPL(lynx_pcs_create_fwnode); | |
391 | ||
e7026f15 | 392 | void lynx_pcs_destroy(struct phylink_pcs *pcs) |
0da4c3d3 | 393 | { |
e7026f15 CF |
394 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); |
395 | ||
86b5f2d8 | 396 | mdio_device_put(lynx->mdio); |
e7026f15 | 397 | kfree(lynx); |
0da4c3d3 IC |
398 | } |
399 | EXPORT_SYMBOL(lynx_pcs_destroy); | |
400 | ||
be884c15 | 401 | MODULE_DESCRIPTION("NXP Lynx PCS phylink library"); |
0da4c3d3 | 402 | MODULE_LICENSE("Dual BSD/GPL"); |