Commit | Line | Data |
---|---|---|
6d91782f AL |
1 | /* |
2 | * Marvell 88E6xxx SERDES manipulation, via SMI bus | |
3 | * | |
4 | * Copyright (c) 2008 Marvell Semiconductor | |
5 | * | |
6 | * Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | */ | |
13 | ||
14 | #include <linux/mii.h> | |
15 | ||
4d5f2ba7 | 16 | #include "chip.h" |
6335e9f2 | 17 | #include "global2.h" |
6d91782f AL |
18 | #include "phy.h" |
19 | #include "port.h" | |
20 | #include "serdes.h" | |
21 | ||
22 | static int mv88e6352_serdes_read(struct mv88e6xxx_chip *chip, int reg, | |
23 | u16 *val) | |
24 | { | |
25 | return mv88e6xxx_phy_page_read(chip, MV88E6352_ADDR_SERDES, | |
26 | MV88E6352_SERDES_PAGE_FIBER, | |
27 | reg, val); | |
28 | } | |
29 | ||
30 | static int mv88e6352_serdes_write(struct mv88e6xxx_chip *chip, int reg, | |
31 | u16 val) | |
32 | { | |
33 | return mv88e6xxx_phy_page_write(chip, MV88E6352_ADDR_SERDES, | |
34 | MV88E6352_SERDES_PAGE_FIBER, | |
35 | reg, val); | |
36 | } | |
37 | ||
e6891c76 AL |
38 | static int mv88e6390_serdes_read(struct mv88e6xxx_chip *chip, |
39 | int lane, int device, int reg, u16 *val) | |
40 | { | |
41 | int reg_c45 = MII_ADDR_C45 | device << 16 | reg; | |
42 | ||
43 | return mv88e6xxx_phy_read(chip, lane, reg_c45, val); | |
44 | } | |
45 | ||
46 | static int mv88e6390_serdes_write(struct mv88e6xxx_chip *chip, | |
47 | int lane, int device, int reg, u16 val) | |
48 | { | |
49 | int reg_c45 = MII_ADDR_C45 | device << 16 | reg; | |
50 | ||
51 | return mv88e6xxx_phy_write(chip, lane, reg_c45, val); | |
52 | } | |
53 | ||
6d91782f AL |
54 | static int mv88e6352_serdes_power_set(struct mv88e6xxx_chip *chip, bool on) |
55 | { | |
56 | u16 val, new_val; | |
57 | int err; | |
58 | ||
59 | err = mv88e6352_serdes_read(chip, MII_BMCR, &val); | |
60 | if (err) | |
61 | return err; | |
62 | ||
63 | if (on) | |
64 | new_val = val & ~BMCR_PDOWN; | |
65 | else | |
66 | new_val = val | BMCR_PDOWN; | |
67 | ||
68 | if (val != new_val) | |
69 | err = mv88e6352_serdes_write(chip, MII_BMCR, new_val); | |
70 | ||
71 | return err; | |
72 | } | |
73 | ||
eb755c3f | 74 | static bool mv88e6352_port_has_serdes(struct mv88e6xxx_chip *chip, int port) |
6d91782f | 75 | { |
2d2e1dd2 | 76 | u8 cmode = chip->ports[port].cmode; |
6d91782f | 77 | |
5f83dc93 VD |
78 | if ((cmode == MV88E6XXX_PORT_STS_CMODE_100BASE_X) || |
79 | (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) || | |
eb755c3f | 80 | (cmode == MV88E6XXX_PORT_STS_CMODE_SGMII)) |
b1312b85 | 81 | return true; |
eb755c3f | 82 | |
b1312b85 | 83 | return false; |
eb755c3f AL |
84 | } |
85 | ||
86 | int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on) | |
87 | { | |
88 | int err; | |
89 | ||
90 | if (mv88e6352_port_has_serdes(chip, port)) { | |
6d91782f AL |
91 | err = mv88e6352_serdes_power_set(chip, on); |
92 | if (err < 0) | |
93 | return err; | |
94 | } | |
95 | ||
96 | return 0; | |
97 | } | |
6335e9f2 | 98 | |
cda9f4aa AL |
99 | struct mv88e6352_serdes_hw_stat { |
100 | char string[ETH_GSTRING_LEN]; | |
101 | int sizeof_stat; | |
102 | int reg; | |
103 | }; | |
104 | ||
105 | static struct mv88e6352_serdes_hw_stat mv88e6352_serdes_hw_stats[] = { | |
106 | { "serdes_fibre_rx_error", 16, 21 }, | |
107 | { "serdes_PRBS_error", 32, 24 }, | |
108 | }; | |
109 | ||
110 | int mv88e6352_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port) | |
111 | { | |
112 | if (mv88e6352_port_has_serdes(chip, port)) | |
113 | return ARRAY_SIZE(mv88e6352_serdes_hw_stats); | |
114 | ||
115 | return 0; | |
116 | } | |
117 | ||
65f60e45 AL |
118 | int mv88e6352_serdes_get_strings(struct mv88e6xxx_chip *chip, |
119 | int port, uint8_t *data) | |
cda9f4aa AL |
120 | { |
121 | struct mv88e6352_serdes_hw_stat *stat; | |
122 | int i; | |
123 | ||
124 | if (!mv88e6352_port_has_serdes(chip, port)) | |
65f60e45 | 125 | return 0; |
cda9f4aa AL |
126 | |
127 | for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_hw_stats); i++) { | |
128 | stat = &mv88e6352_serdes_hw_stats[i]; | |
129 | memcpy(data + i * ETH_GSTRING_LEN, stat->string, | |
130 | ETH_GSTRING_LEN); | |
131 | } | |
65f60e45 | 132 | return ARRAY_SIZE(mv88e6352_serdes_hw_stats); |
cda9f4aa AL |
133 | } |
134 | ||
135 | static uint64_t mv88e6352_serdes_get_stat(struct mv88e6xxx_chip *chip, | |
136 | struct mv88e6352_serdes_hw_stat *stat) | |
137 | { | |
138 | u64 val = 0; | |
139 | u16 reg; | |
140 | int err; | |
141 | ||
142 | err = mv88e6352_serdes_read(chip, stat->reg, ®); | |
143 | if (err) { | |
144 | dev_err(chip->dev, "failed to read statistic\n"); | |
145 | return 0; | |
146 | } | |
147 | ||
148 | val = reg; | |
149 | ||
150 | if (stat->sizeof_stat == 32) { | |
151 | err = mv88e6352_serdes_read(chip, stat->reg + 1, ®); | |
152 | if (err) { | |
153 | dev_err(chip->dev, "failed to read statistic\n"); | |
154 | return 0; | |
155 | } | |
156 | val = val << 16 | reg; | |
157 | } | |
158 | ||
159 | return val; | |
160 | } | |
161 | ||
65f60e45 AL |
162 | int mv88e6352_serdes_get_stats(struct mv88e6xxx_chip *chip, int port, |
163 | uint64_t *data) | |
cda9f4aa AL |
164 | { |
165 | struct mv88e6xxx_port *mv88e6xxx_port = &chip->ports[port]; | |
166 | struct mv88e6352_serdes_hw_stat *stat; | |
167 | u64 value; | |
168 | int i; | |
169 | ||
170 | if (!mv88e6352_port_has_serdes(chip, port)) | |
65f60e45 | 171 | return 0; |
cda9f4aa AL |
172 | |
173 | BUILD_BUG_ON(ARRAY_SIZE(mv88e6352_serdes_hw_stats) > | |
174 | ARRAY_SIZE(mv88e6xxx_port->serdes_stats)); | |
175 | ||
176 | for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_hw_stats); i++) { | |
177 | stat = &mv88e6352_serdes_hw_stats[i]; | |
178 | value = mv88e6352_serdes_get_stat(chip, stat); | |
179 | mv88e6xxx_port->serdes_stats[i] += value; | |
180 | data[i] = mv88e6xxx_port->serdes_stats[i]; | |
181 | } | |
65f60e45 AL |
182 | |
183 | return ARRAY_SIZE(mv88e6352_serdes_hw_stats); | |
cda9f4aa AL |
184 | } |
185 | ||
07ffbd74 AL |
186 | /* Return the SERDES lane address a port is using. Only Ports 9 and 10 |
187 | * have SERDES lanes. Returns -ENODEV if a port does not have a lane. | |
188 | */ | |
189 | static int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port) | |
190 | { | |
2d2e1dd2 | 191 | u8 cmode = chip->ports[port].cmode; |
07ffbd74 AL |
192 | |
193 | switch (port) { | |
194 | case 9: | |
195 | if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
196 | cmode == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
197 | cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX) | |
198 | return MV88E6390_PORT9_LANE0; | |
199 | return -ENODEV; | |
200 | case 10: | |
201 | if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
202 | cmode == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
203 | cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX) | |
204 | return MV88E6390_PORT10_LANE0; | |
205 | return -ENODEV; | |
206 | default: | |
207 | return -ENODEV; | |
208 | } | |
209 | } | |
210 | ||
a8c01c0d AL |
211 | /* Return the SERDES lane address a port is using. Ports 9 and 10 can |
212 | * use multiple lanes. If so, return the first lane the port uses. | |
213 | * Returns -ENODEV if a port does not have a lane. | |
214 | */ | |
07ffbd74 | 215 | static int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port) |
a8c01c0d AL |
216 | { |
217 | u8 cmode_port9, cmode_port10, cmode_port; | |
a8c01c0d | 218 | |
2d2e1dd2 AL |
219 | cmode_port9 = chip->ports[9].cmode; |
220 | cmode_port10 = chip->ports[10].cmode; | |
221 | cmode_port = chip->ports[port].cmode; | |
a8c01c0d AL |
222 | |
223 | switch (port) { | |
224 | case 2: | |
225 | if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
226 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
227 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX) | |
228 | if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) | |
229 | return MV88E6390_PORT9_LANE1; | |
230 | return -ENODEV; | |
231 | case 3: | |
232 | if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
233 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
234 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX || | |
235 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI) | |
236 | if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) | |
237 | return MV88E6390_PORT9_LANE2; | |
238 | return -ENODEV; | |
239 | case 4: | |
240 | if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
241 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
242 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX || | |
243 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI) | |
244 | if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) | |
245 | return MV88E6390_PORT9_LANE3; | |
246 | return -ENODEV; | |
247 | case 5: | |
248 | if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
249 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
250 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX) | |
251 | if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) | |
252 | return MV88E6390_PORT10_LANE1; | |
253 | return -ENODEV; | |
254 | case 6: | |
255 | if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
256 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
257 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX || | |
258 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI) | |
259 | if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) | |
260 | return MV88E6390_PORT10_LANE2; | |
261 | return -ENODEV; | |
262 | case 7: | |
263 | if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
264 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
265 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX || | |
266 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI) | |
267 | if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) | |
268 | return MV88E6390_PORT10_LANE3; | |
269 | return -ENODEV; | |
270 | case 9: | |
271 | if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
272 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
273 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX || | |
274 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_XAUI || | |
275 | cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI) | |
276 | return MV88E6390_PORT9_LANE0; | |
277 | return -ENODEV; | |
278 | case 10: | |
279 | if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || | |
280 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
281 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX || | |
282 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_XAUI || | |
283 | cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI) | |
284 | return MV88E6390_PORT10_LANE0; | |
285 | return -ENODEV; | |
286 | default: | |
287 | return -ENODEV; | |
288 | } | |
289 | } | |
290 | ||
6335e9f2 | 291 | /* Set the power on/off for 10GBASE-R and 10GBASE-X4/X2 */ |
23ef57d8 AL |
292 | static int mv88e6390_serdes_power_10g(struct mv88e6xxx_chip *chip, int lane, |
293 | bool on) | |
6335e9f2 AL |
294 | { |
295 | u16 val, new_val; | |
6335e9f2 AL |
296 | int err; |
297 | ||
e6891c76 AL |
298 | err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS, |
299 | MV88E6390_PCS_CONTROL_1, &val); | |
300 | ||
6335e9f2 AL |
301 | if (err) |
302 | return err; | |
303 | ||
304 | if (on) | |
305 | new_val = val & ~(MV88E6390_PCS_CONTROL_1_RESET | | |
306 | MV88E6390_PCS_CONTROL_1_LOOPBACK | | |
307 | MV88E6390_PCS_CONTROL_1_PDOWN); | |
308 | else | |
309 | new_val = val | MV88E6390_PCS_CONTROL_1_PDOWN; | |
310 | ||
311 | if (val != new_val) | |
e6891c76 AL |
312 | err = mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS, |
313 | MV88E6390_PCS_CONTROL_1, new_val); | |
6335e9f2 AL |
314 | |
315 | return err; | |
316 | } | |
317 | ||
a8c01c0d | 318 | /* Set the power on/off for SGMII and 1000Base-X */ |
23ef57d8 AL |
319 | static int mv88e6390_serdes_power_sgmii(struct mv88e6xxx_chip *chip, int lane, |
320 | bool on) | |
6335e9f2 AL |
321 | { |
322 | u16 val, new_val; | |
6335e9f2 AL |
323 | int err; |
324 | ||
e6891c76 AL |
325 | err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS, |
326 | MV88E6390_SGMII_CONTROL, &val); | |
6335e9f2 AL |
327 | if (err) |
328 | return err; | |
329 | ||
330 | if (on) | |
331 | new_val = val & ~(MV88E6390_SGMII_CONTROL_RESET | | |
332 | MV88E6390_SGMII_CONTROL_LOOPBACK | | |
333 | MV88E6390_SGMII_CONTROL_PDOWN); | |
334 | else | |
335 | new_val = val | MV88E6390_SGMII_CONTROL_PDOWN; | |
336 | ||
337 | if (val != new_val) | |
e6891c76 AL |
338 | err = mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS, |
339 | MV88E6390_SGMII_CONTROL, new_val); | |
6335e9f2 AL |
340 | |
341 | return err; | |
342 | } | |
343 | ||
a8c01c0d AL |
344 | static int mv88e6390_serdes_power_lane(struct mv88e6xxx_chip *chip, int port, |
345 | int lane, bool on) | |
6335e9f2 | 346 | { |
2d2e1dd2 | 347 | u8 cmode = chip->ports[port].cmode; |
6335e9f2 | 348 | |
6335e9f2 | 349 | switch (cmode) { |
5f83dc93 | 350 | case MV88E6XXX_PORT_STS_CMODE_SGMII: |
a8c01c0d | 351 | case MV88E6XXX_PORT_STS_CMODE_1000BASE_X: |
f8236a08 | 352 | case MV88E6XXX_PORT_STS_CMODE_2500BASEX: |
23ef57d8 | 353 | return mv88e6390_serdes_power_sgmii(chip, lane, on); |
5f83dc93 VD |
354 | case MV88E6XXX_PORT_STS_CMODE_XAUI: |
355 | case MV88E6XXX_PORT_STS_CMODE_RXAUI: | |
23ef57d8 | 356 | return mv88e6390_serdes_power_10g(chip, lane, on); |
6335e9f2 AL |
357 | } |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on) | |
363 | { | |
a8c01c0d | 364 | int lane; |
6335e9f2 | 365 | |
a8c01c0d AL |
366 | lane = mv88e6390_serdes_get_lane(chip, port); |
367 | if (lane == -ENODEV) | |
368 | return 0; | |
369 | ||
370 | if (lane < 0) | |
371 | return lane; | |
6335e9f2 | 372 | |
07ffbd74 AL |
373 | switch (port) { |
374 | case 9 ... 10: | |
375 | return mv88e6390_serdes_power_lane(chip, port, lane, on); | |
376 | } | |
377 | ||
378 | return 0; | |
379 | } | |
380 | ||
381 | int mv88e6390x_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on) | |
382 | { | |
383 | int lane; | |
384 | ||
385 | lane = mv88e6390x_serdes_get_lane(chip, port); | |
386 | if (lane == -ENODEV) | |
387 | return 0; | |
388 | ||
389 | if (lane < 0) | |
390 | return lane; | |
391 | ||
6335e9f2 | 392 | switch (port) { |
a8c01c0d AL |
393 | case 2 ... 4: |
394 | case 5 ... 7: | |
395 | case 9 ... 10: | |
396 | return mv88e6390_serdes_power_lane(chip, port, lane, on); | |
6335e9f2 AL |
397 | } |
398 | ||
399 | return 0; | |
400 | } | |
5bafeb6e MB |
401 | |
402 | int mv88e6341_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on) | |
403 | { | |
2d2e1dd2 | 404 | u8 cmode = chip->ports[port].cmode; |
5bafeb6e MB |
405 | |
406 | if (port != 5) | |
407 | return 0; | |
408 | ||
5bafeb6e MB |
409 | if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X || |
410 | cmode == MV88E6XXX_PORT_STS_CMODE_SGMII || | |
411 | cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX) | |
23ef57d8 AL |
412 | return mv88e6390_serdes_power_sgmii(chip, MV88E6341_ADDR_SERDES, |
413 | on); | |
5bafeb6e MB |
414 | |
415 | return 0; | |
416 | } |