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> |
9 | #include <linux/property.h> |
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 | |
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 | |
23 | struct lynx_pcs { |
24 | struct phylink_pcs pcs; |
25 | struct mdio_device *mdio; |
26 | }; |
27 | |
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) |
36 | #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs) |
37 | |
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 | { |
64 | int bmsr, lpa; |
65 | |
66 | bmsr = mdiodev_read(mdiodev: pcs, MII_BMSR); |
67 | lpa = mdiodev_read(mdiodev: pcs, MII_LPA); |
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) { |
89 | case PHY_INTERFACE_MODE_1000BASEX: |
90 | case PHY_INTERFACE_MODE_SGMII: |
91 | case PHY_INTERFACE_MODE_QSGMII: |
92 | phylink_mii_c22_pcs_get_state(pcs: lynx->mdio, state); |
93 | break; |
94 | case PHY_INTERFACE_MODE_2500BASEX: |
95 | lynx_pcs_get_state_2500basex(pcs: lynx->mdio, state); |
96 | break; |
97 | case PHY_INTERFACE_MODE_USXGMII: |
98 | lynx_pcs_get_state_usxgmii(pcs: lynx->mdio, state); |
99 | break; |
100 | case PHY_INTERFACE_MODE_10GBASER: |
101 | phylink_mii_c45_pcs_get_state(pcs: lynx->mdio, state); |
102 | break; |
103 | default: |
104 | break; |
105 | } |
106 | |
107 | dev_dbg(&lynx->mdio->dev, |
108 | "mode=%s/%s/%s link=%u an_complete=%u\n" , |
109 | phy_modes(state->interface), |
110 | phy_speed_to_str(state->speed), |
111 | phy_duplex_to_str(state->duplex), |
112 | state->link, state->an_complete); |
113 | } |
114 | |
115 | static int lynx_pcs_config_giga(struct mdio_device *pcs, |
116 | phy_interface_t interface, |
117 | const unsigned long *advertising, |
118 | unsigned int neg_mode) |
119 | { |
120 | int link_timer_ns; |
121 | u32 link_timer; |
122 | u16 if_mode; |
123 | int err; |
124 | |
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 | |
129 | mdiodev_write(mdiodev: pcs, LINK_TIMER_LO, val: link_timer & 0xffff); |
130 | mdiodev_write(mdiodev: pcs, LINK_TIMER_HI, val: link_timer >> 16); |
131 | } |
132 | |
133 | if (interface == PHY_INTERFACE_MODE_1000BASEX) { |
134 | if_mode = 0; |
135 | } else { |
136 | /* SGMII and QSGMII */ |
137 | if_mode = IF_MODE_SGMII_EN; |
138 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) |
139 | if_mode |= IF_MODE_USE_SGMII_AN; |
140 | } |
141 | |
142 | err = mdiodev_modify(mdiodev: pcs, IF_MODE, |
143 | IF_MODE_SGMII_EN | IF_MODE_USE_SGMII_AN, |
144 | set: if_mode); |
145 | if (err) |
146 | return err; |
147 | |
148 | return phylink_mii_c22_pcs_config(pcs, interface, advertising, |
149 | neg_mode); |
150 | } |
151 | |
152 | static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, |
153 | const unsigned long *advertising, |
154 | unsigned int neg_mode) |
155 | { |
156 | struct mii_bus *bus = pcs->bus; |
157 | int addr = pcs->addr; |
158 | |
159 | if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) { |
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 | |
171 | static int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, |
172 | phy_interface_t ifmode, |
173 | const unsigned long *advertising, bool permit) |
174 | { |
175 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); |
176 | |
177 | switch (ifmode) { |
178 | case PHY_INTERFACE_MODE_1000BASEX: |
179 | case PHY_INTERFACE_MODE_SGMII: |
180 | case PHY_INTERFACE_MODE_QSGMII: |
181 | return lynx_pcs_config_giga(pcs: lynx->mdio, interface: ifmode, advertising, |
182 | neg_mode); |
183 | case PHY_INTERFACE_MODE_2500BASEX: |
184 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { |
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: |
191 | return lynx_pcs_config_usxgmii(pcs: lynx->mdio, advertising, |
192 | neg_mode); |
193 | case PHY_INTERFACE_MODE_10GBASER: |
194 | /* Nothing to do here for 10GBASER */ |
195 | break; |
196 | default: |
197 | return -EOPNOTSUPP; |
198 | } |
199 | |
200 | return 0; |
201 | } |
202 | |
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(pcs: lynx->mdio); |
208 | } |
209 | |
210 | static void lynx_pcs_link_up_sgmii(struct mdio_device *pcs, |
211 | unsigned int neg_mode, |
212 | int speed, int duplex) |
213 | { |
214 | u16 if_mode = 0, sgmii_speed; |
215 | |
216 | /* The PCS needs to be configured manually only |
217 | * when not operating on in-band mode |
218 | */ |
219 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) |
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 | |
244 | mdiodev_modify(mdiodev: pcs, IF_MODE, |
245 | IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, |
246 | set: 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, |
266 | unsigned int neg_mode, |
267 | int speed, int duplex) |
268 | { |
269 | u16 if_mode = 0; |
270 | |
271 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { |
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 | |
280 | mdiodev_modify(mdiodev: pcs, IF_MODE, |
281 | IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, |
282 | set: if_mode); |
283 | } |
284 | |
285 | static void lynx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, |
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: |
294 | lynx_pcs_link_up_sgmii(pcs: lynx->mdio, neg_mode, speed, duplex); |
295 | break; |
296 | case PHY_INTERFACE_MODE_2500BASEX: |
297 | lynx_pcs_link_up_2500basex(pcs: lynx->mdio, neg_mode, speed, duplex); |
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, |
312 | .pcs_an_restart = lynx_pcs_an_restart, |
313 | .pcs_link_up = lynx_pcs_link_up, |
314 | }; |
315 | |
316 | static struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio) |
317 | { |
318 | struct lynx_pcs *lynx; |
319 | |
320 | lynx = kzalloc(size: sizeof(*lynx), GFP_KERNEL); |
321 | if (!lynx) |
322 | return ERR_PTR(error: -ENOMEM); |
323 | |
324 | mdio_device_get(mdiodev: mdio); |
325 | lynx->mdio = mdio; |
326 | lynx->pcs.ops = &lynx_pcs_phylink_ops; |
327 | lynx->pcs.neg_mode = true; |
328 | lynx->pcs.poll = true; |
329 | |
330 | return lynx_to_phylink_pcs(lynx); |
331 | } |
332 | |
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(ptr: mdio)) |
340 | return ERR_CAST(ptr: mdio); |
341 | |
342 | pcs = lynx_pcs_create(mdio); |
343 | |
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(mdiodev: mdio); |
351 | |
352 | return pcs; |
353 | } |
354 | EXPORT_SYMBOL(lynx_pcs_create_mdiodev); |
355 | |
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 | */ |
366 | struct phylink_pcs *lynx_pcs_create_fwnode(struct fwnode_handle *node) |
367 | { |
368 | struct mdio_device *mdio; |
369 | struct phylink_pcs *pcs; |
370 | |
371 | if (!fwnode_device_is_available(fwnode: node)) |
372 | return ERR_PTR(error: -ENODEV); |
373 | |
374 | mdio = fwnode_mdio_find_device(fwnode: node); |
375 | if (!mdio) |
376 | return ERR_PTR(error: -EPROBE_DEFER); |
377 | |
378 | pcs = lynx_pcs_create(mdio); |
379 | |
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(mdiodev: mdio); |
387 | |
388 | return pcs; |
389 | } |
390 | EXPORT_SYMBOL_GPL(lynx_pcs_create_fwnode); |
391 | |
392 | void lynx_pcs_destroy(struct phylink_pcs *pcs) |
393 | { |
394 | struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); |
395 | |
396 | mdio_device_put(mdiodev: lynx->mdio); |
397 | kfree(objp: lynx); |
398 | } |
399 | EXPORT_SYMBOL(lynx_pcs_destroy); |
400 | |
401 | MODULE_DESCRIPTION("NXP Lynx PCS phylink library" ); |
402 | MODULE_LICENSE("Dual BSD/GPL" ); |
403 | |