1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2015-2017 Broadcom |
4 | */ |
5 | |
6 | #include "bcm-phy-lib.h" |
7 | #include <linux/bitfield.h> |
8 | #include <linux/brcmphy.h> |
9 | #include <linux/etherdevice.h> |
10 | #include <linux/export.h> |
11 | #include <linux/mdio.h> |
12 | #include <linux/module.h> |
13 | #include <linux/phy.h> |
14 | #include <linux/ethtool.h> |
15 | #include <linux/ethtool_netlink.h> |
16 | #include <linux/netdevice.h> |
17 | |
18 | #define MII_BCM_CHANNEL_WIDTH 0x2000 |
19 | #define BCM_CL45VEN_EEE_ADV 0x3c |
20 | |
21 | int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val) |
22 | { |
23 | int rc; |
24 | |
25 | rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, val: reg); |
26 | if (rc < 0) |
27 | return rc; |
28 | |
29 | return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val); |
30 | } |
31 | EXPORT_SYMBOL_GPL(__bcm_phy_write_exp); |
32 | |
33 | int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val) |
34 | { |
35 | int rc; |
36 | |
37 | phy_lock_mdio_bus(phydev); |
38 | rc = __bcm_phy_write_exp(phydev, reg, val); |
39 | phy_unlock_mdio_bus(phydev); |
40 | |
41 | return rc; |
42 | } |
43 | EXPORT_SYMBOL_GPL(bcm_phy_write_exp); |
44 | |
45 | int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg) |
46 | { |
47 | int val; |
48 | |
49 | val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, val: reg); |
50 | if (val < 0) |
51 | return val; |
52 | |
53 | val = __phy_read(phydev, MII_BCM54XX_EXP_DATA); |
54 | |
55 | /* Restore default value. It's O.K. if this write fails. */ |
56 | __phy_write(phydev, MII_BCM54XX_EXP_SEL, val: 0); |
57 | |
58 | return val; |
59 | } |
60 | EXPORT_SYMBOL_GPL(__bcm_phy_read_exp); |
61 | |
62 | int bcm_phy_read_exp(struct phy_device *phydev, u16 reg) |
63 | { |
64 | int rc; |
65 | |
66 | phy_lock_mdio_bus(phydev); |
67 | rc = __bcm_phy_read_exp(phydev, reg); |
68 | phy_unlock_mdio_bus(phydev); |
69 | |
70 | return rc; |
71 | } |
72 | EXPORT_SYMBOL_GPL(bcm_phy_read_exp); |
73 | |
74 | int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set) |
75 | { |
76 | int new, ret; |
77 | |
78 | ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, val: reg); |
79 | if (ret < 0) |
80 | return ret; |
81 | |
82 | ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA); |
83 | if (ret < 0) |
84 | return ret; |
85 | |
86 | new = (ret & ~mask) | set; |
87 | if (new == ret) |
88 | return 0; |
89 | |
90 | return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val: new); |
91 | } |
92 | EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp); |
93 | |
94 | int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set) |
95 | { |
96 | int ret; |
97 | |
98 | phy_lock_mdio_bus(phydev); |
99 | ret = __bcm_phy_modify_exp(phydev, reg, mask, set); |
100 | phy_unlock_mdio_bus(phydev); |
101 | |
102 | return ret; |
103 | } |
104 | EXPORT_SYMBOL_GPL(bcm_phy_modify_exp); |
105 | |
106 | int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum) |
107 | { |
108 | /* The register must be written to both the Shadow Register Select and |
109 | * the Shadow Read Register Selector |
110 | */ |
111 | phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK | |
112 | regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT); |
113 | return phy_read(phydev, MII_BCM54XX_AUX_CTL); |
114 | } |
115 | EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read); |
116 | |
117 | int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val) |
118 | { |
119 | return phy_write(phydev, MII_BCM54XX_AUX_CTL, val: regnum | val); |
120 | } |
121 | EXPORT_SYMBOL(bcm54xx_auxctl_write); |
122 | |
123 | int bcm_phy_write_misc(struct phy_device *phydev, |
124 | u16 reg, u16 chl, u16 val) |
125 | { |
126 | int rc; |
127 | int tmp; |
128 | |
129 | rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, |
130 | MII_BCM54XX_AUXCTL_SHDWSEL_MISC); |
131 | if (rc < 0) |
132 | return rc; |
133 | |
134 | tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); |
135 | tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; |
136 | rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, val: tmp); |
137 | if (rc < 0) |
138 | return rc; |
139 | |
140 | tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg; |
141 | rc = bcm_phy_write_exp(phydev, tmp, val); |
142 | |
143 | return rc; |
144 | } |
145 | EXPORT_SYMBOL_GPL(bcm_phy_write_misc); |
146 | |
147 | int bcm_phy_read_misc(struct phy_device *phydev, |
148 | u16 reg, u16 chl) |
149 | { |
150 | int rc; |
151 | int tmp; |
152 | |
153 | rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, |
154 | MII_BCM54XX_AUXCTL_SHDWSEL_MISC); |
155 | if (rc < 0) |
156 | return rc; |
157 | |
158 | tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); |
159 | tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; |
160 | rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, val: tmp); |
161 | if (rc < 0) |
162 | return rc; |
163 | |
164 | tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg; |
165 | rc = bcm_phy_read_exp(phydev, tmp); |
166 | |
167 | return rc; |
168 | } |
169 | EXPORT_SYMBOL_GPL(bcm_phy_read_misc); |
170 | |
171 | int bcm_phy_ack_intr(struct phy_device *phydev) |
172 | { |
173 | int reg; |
174 | |
175 | /* Clear pending interrupts. */ |
176 | reg = phy_read(phydev, MII_BCM54XX_ISR); |
177 | if (reg < 0) |
178 | return reg; |
179 | |
180 | return 0; |
181 | } |
182 | EXPORT_SYMBOL_GPL(bcm_phy_ack_intr); |
183 | |
184 | int bcm_phy_config_intr(struct phy_device *phydev) |
185 | { |
186 | int reg, err; |
187 | |
188 | reg = phy_read(phydev, MII_BCM54XX_ECR); |
189 | if (reg < 0) |
190 | return reg; |
191 | |
192 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { |
193 | err = bcm_phy_ack_intr(phydev); |
194 | if (err) |
195 | return err; |
196 | |
197 | reg &= ~MII_BCM54XX_ECR_IM; |
198 | err = phy_write(phydev, MII_BCM54XX_ECR, val: reg); |
199 | } else { |
200 | reg |= MII_BCM54XX_ECR_IM; |
201 | err = phy_write(phydev, MII_BCM54XX_ECR, val: reg); |
202 | if (err) |
203 | return err; |
204 | |
205 | err = bcm_phy_ack_intr(phydev); |
206 | } |
207 | return err; |
208 | } |
209 | EXPORT_SYMBOL_GPL(bcm_phy_config_intr); |
210 | |
211 | irqreturn_t bcm_phy_handle_interrupt(struct phy_device *phydev) |
212 | { |
213 | int irq_status, irq_mask; |
214 | |
215 | irq_status = phy_read(phydev, MII_BCM54XX_ISR); |
216 | if (irq_status < 0) { |
217 | phy_error(phydev); |
218 | return IRQ_NONE; |
219 | } |
220 | |
221 | /* If a bit from the Interrupt Mask register is set, the corresponding |
222 | * bit from the Interrupt Status register is masked. So read the IMR |
223 | * and then flip the bits to get the list of possible interrupt |
224 | * sources. |
225 | */ |
226 | irq_mask = phy_read(phydev, MII_BCM54XX_IMR); |
227 | if (irq_mask < 0) { |
228 | phy_error(phydev); |
229 | return IRQ_NONE; |
230 | } |
231 | irq_mask = ~irq_mask; |
232 | |
233 | if (!(irq_status & irq_mask)) |
234 | return IRQ_NONE; |
235 | |
236 | phy_trigger_machine(phydev); |
237 | |
238 | return IRQ_HANDLED; |
239 | } |
240 | EXPORT_SYMBOL_GPL(bcm_phy_handle_interrupt); |
241 | |
242 | int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow) |
243 | { |
244 | phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow)); |
245 | return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD)); |
246 | } |
247 | EXPORT_SYMBOL_GPL(bcm_phy_read_shadow); |
248 | |
249 | int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow, |
250 | u16 val) |
251 | { |
252 | return phy_write(phydev, MII_BCM54XX_SHD, |
253 | MII_BCM54XX_SHD_WRITE | |
254 | MII_BCM54XX_SHD_VAL(shadow) | |
255 | MII_BCM54XX_SHD_DATA(val)); |
256 | } |
257 | EXPORT_SYMBOL_GPL(bcm_phy_write_shadow); |
258 | |
259 | int __bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb) |
260 | { |
261 | int val; |
262 | |
263 | val = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, val: rdb); |
264 | if (val < 0) |
265 | return val; |
266 | |
267 | return __phy_read(phydev, MII_BCM54XX_RDB_DATA); |
268 | } |
269 | EXPORT_SYMBOL_GPL(__bcm_phy_read_rdb); |
270 | |
271 | int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb) |
272 | { |
273 | int ret; |
274 | |
275 | phy_lock_mdio_bus(phydev); |
276 | ret = __bcm_phy_read_rdb(phydev, rdb); |
277 | phy_unlock_mdio_bus(phydev); |
278 | |
279 | return ret; |
280 | } |
281 | EXPORT_SYMBOL_GPL(bcm_phy_read_rdb); |
282 | |
283 | int __bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val) |
284 | { |
285 | int ret; |
286 | |
287 | ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, val: rdb); |
288 | if (ret < 0) |
289 | return ret; |
290 | |
291 | return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val); |
292 | } |
293 | EXPORT_SYMBOL_GPL(__bcm_phy_write_rdb); |
294 | |
295 | int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val) |
296 | { |
297 | int ret; |
298 | |
299 | phy_lock_mdio_bus(phydev); |
300 | ret = __bcm_phy_write_rdb(phydev, rdb, val); |
301 | phy_unlock_mdio_bus(phydev); |
302 | |
303 | return ret; |
304 | } |
305 | EXPORT_SYMBOL_GPL(bcm_phy_write_rdb); |
306 | |
307 | int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set) |
308 | { |
309 | int new, ret; |
310 | |
311 | ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, val: rdb); |
312 | if (ret < 0) |
313 | return ret; |
314 | |
315 | ret = __phy_read(phydev, MII_BCM54XX_RDB_DATA); |
316 | if (ret < 0) |
317 | return ret; |
318 | |
319 | new = (ret & ~mask) | set; |
320 | if (new == ret) |
321 | return 0; |
322 | |
323 | return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val: new); |
324 | } |
325 | EXPORT_SYMBOL_GPL(__bcm_phy_modify_rdb); |
326 | |
327 | int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set) |
328 | { |
329 | int ret; |
330 | |
331 | phy_lock_mdio_bus(phydev); |
332 | ret = __bcm_phy_modify_rdb(phydev, rdb, mask, set); |
333 | phy_unlock_mdio_bus(phydev); |
334 | |
335 | return ret; |
336 | } |
337 | EXPORT_SYMBOL_GPL(bcm_phy_modify_rdb); |
338 | |
339 | int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down) |
340 | { |
341 | int val; |
342 | |
343 | if (dll_pwr_down) { |
344 | val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3); |
345 | if (val < 0) |
346 | return val; |
347 | |
348 | val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; |
349 | bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val); |
350 | } |
351 | |
352 | val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD); |
353 | if (val < 0) |
354 | return val; |
355 | |
356 | /* Clear APD bits */ |
357 | val &= BCM_APD_CLR_MASK; |
358 | |
359 | if (phydev->autoneg == AUTONEG_ENABLE) |
360 | val |= BCM54XX_SHD_APD_EN; |
361 | else |
362 | val |= BCM_NO_ANEG_APD_EN; |
363 | |
364 | /* Enable energy detect single link pulse for easy wakeup */ |
365 | val |= BCM_APD_SINGLELP_EN; |
366 | |
367 | /* Enable Auto Power-Down (APD) for the PHY */ |
368 | return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val); |
369 | } |
370 | EXPORT_SYMBOL_GPL(bcm_phy_enable_apd); |
371 | |
372 | int bcm_phy_set_eee(struct phy_device *phydev, bool enable) |
373 | { |
374 | int val, mask = 0; |
375 | |
376 | /* Enable EEE at PHY level */ |
377 | val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL); |
378 | if (val < 0) |
379 | return val; |
380 | |
381 | if (enable) |
382 | val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X; |
383 | else |
384 | val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X); |
385 | |
386 | phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, val: (u32)val); |
387 | |
388 | /* Advertise EEE */ |
389 | val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV); |
390 | if (val < 0) |
391 | return val; |
392 | |
393 | if (linkmode_test_bit(nr: ETHTOOL_LINK_MODE_1000baseT_Full_BIT, |
394 | addr: phydev->supported)) |
395 | mask |= MDIO_EEE_1000T; |
396 | if (linkmode_test_bit(nr: ETHTOOL_LINK_MODE_100baseT_Full_BIT, |
397 | addr: phydev->supported)) |
398 | mask |= MDIO_EEE_100TX; |
399 | |
400 | if (enable) |
401 | val |= mask; |
402 | else |
403 | val &= ~mask; |
404 | |
405 | phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, val: (u32)val); |
406 | |
407 | return 0; |
408 | } |
409 | EXPORT_SYMBOL_GPL(bcm_phy_set_eee); |
410 | |
411 | int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count) |
412 | { |
413 | int val; |
414 | |
415 | val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); |
416 | if (val < 0) |
417 | return val; |
418 | |
419 | /* Check if wirespeed is enabled or not */ |
420 | if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) { |
421 | *count = DOWNSHIFT_DEV_DISABLE; |
422 | return 0; |
423 | } |
424 | |
425 | val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2); |
426 | if (val < 0) |
427 | return val; |
428 | |
429 | /* Downgrade after one link attempt */ |
430 | if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) { |
431 | *count = 1; |
432 | } else { |
433 | /* Downgrade after configured retry count */ |
434 | val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; |
435 | val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK; |
436 | *count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET; |
437 | } |
438 | |
439 | return 0; |
440 | } |
441 | EXPORT_SYMBOL_GPL(bcm_phy_downshift_get); |
442 | |
443 | int bcm_phy_downshift_set(struct phy_device *phydev, u8 count) |
444 | { |
445 | int val = 0, ret = 0; |
446 | |
447 | /* Range check the number given */ |
448 | if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET > |
449 | BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK && |
450 | count != DOWNSHIFT_DEV_DEFAULT_COUNT) { |
451 | return -ERANGE; |
452 | } |
453 | |
454 | val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); |
455 | if (val < 0) |
456 | return val; |
457 | |
458 | /* Se the write enable bit */ |
459 | val |= MII_BCM54XX_AUXCTL_MISC_WREN; |
460 | |
461 | if (count == DOWNSHIFT_DEV_DISABLE) { |
462 | val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN; |
463 | return bcm54xx_auxctl_write(phydev, |
464 | MII_BCM54XX_AUXCTL_SHDWSEL_MISC, |
465 | val); |
466 | } else { |
467 | val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN; |
468 | ret = bcm54xx_auxctl_write(phydev, |
469 | MII_BCM54XX_AUXCTL_SHDWSEL_MISC, |
470 | val); |
471 | if (ret < 0) |
472 | return ret; |
473 | } |
474 | |
475 | val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2); |
476 | val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK << |
477 | BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT | |
478 | BCM54XX_SHD_SCR2_WSPD_RTRY_DIS); |
479 | |
480 | switch (count) { |
481 | case 1: |
482 | val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS; |
483 | break; |
484 | case DOWNSHIFT_DEV_DEFAULT_COUNT: |
485 | val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; |
486 | break; |
487 | default: |
488 | val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) << |
489 | BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; |
490 | break; |
491 | } |
492 | |
493 | return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val); |
494 | } |
495 | EXPORT_SYMBOL_GPL(bcm_phy_downshift_set); |
496 | |
497 | struct bcm_phy_hw_stat { |
498 | const char *string; |
499 | int devad; |
500 | u16 reg; |
501 | u8 shift; |
502 | u8 bits; |
503 | }; |
504 | |
505 | /* Counters freeze at either 0xffff or 0xff, better than nothing */ |
506 | static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = { |
507 | { "phy_receive_errors" , -1, MII_BRCM_CORE_BASE12, 0, 16 }, |
508 | { "phy_serdes_ber_errors" , -1, MII_BRCM_CORE_BASE13, 8, 8 }, |
509 | { "phy_false_carrier_sense_errors" , -1, MII_BRCM_CORE_BASE13, 0, 8 }, |
510 | { "phy_local_rcvr_nok" , -1, MII_BRCM_CORE_BASE14, 8, 8 }, |
511 | { "phy_remote_rcv_nok" , -1, MII_BRCM_CORE_BASE14, 0, 8 }, |
512 | { "phy_lpi_count" , MDIO_MMD_AN, BRCM_CL45VEN_EEE_LPI_CNT, 0, 16 }, |
513 | }; |
514 | |
515 | int bcm_phy_get_sset_count(struct phy_device *phydev) |
516 | { |
517 | return ARRAY_SIZE(bcm_phy_hw_stats); |
518 | } |
519 | EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count); |
520 | |
521 | void bcm_phy_get_strings(struct phy_device *phydev, u8 *data) |
522 | { |
523 | unsigned int i; |
524 | |
525 | for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) |
526 | strscpy(p: data + i * ETH_GSTRING_LEN, |
527 | q: bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN); |
528 | } |
529 | EXPORT_SYMBOL_GPL(bcm_phy_get_strings); |
530 | |
531 | /* Caller is supposed to provide appropriate storage for the library code to |
532 | * access the shadow copy |
533 | */ |
534 | static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow, |
535 | unsigned int i) |
536 | { |
537 | struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i]; |
538 | int val; |
539 | u64 ret; |
540 | |
541 | if (stat.devad < 0) |
542 | val = phy_read(phydev, regnum: stat.reg); |
543 | else |
544 | val = phy_read_mmd(phydev, devad: stat.devad, regnum: stat.reg); |
545 | if (val < 0) { |
546 | ret = U64_MAX; |
547 | } else { |
548 | val >>= stat.shift; |
549 | val = val & ((1 << stat.bits) - 1); |
550 | shadow[i] += val; |
551 | ret = shadow[i]; |
552 | } |
553 | |
554 | return ret; |
555 | } |
556 | |
557 | void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow, |
558 | struct ethtool_stats *stats, u64 *data) |
559 | { |
560 | unsigned int i; |
561 | |
562 | for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) |
563 | data[i] = bcm_phy_get_stat(phydev, shadow, i); |
564 | } |
565 | EXPORT_SYMBOL_GPL(bcm_phy_get_stats); |
566 | |
567 | void bcm_phy_r_rc_cal_reset(struct phy_device *phydev) |
568 | { |
569 | /* Reset R_CAL/RC_CAL Engine */ |
570 | bcm_phy_write_exp_sel(phydev, reg: 0x00b0, val: 0x0010); |
571 | |
572 | /* Disable Reset R_AL/RC_CAL Engine */ |
573 | bcm_phy_write_exp_sel(phydev, reg: 0x00b0, val: 0x0000); |
574 | } |
575 | EXPORT_SYMBOL_GPL(bcm_phy_r_rc_cal_reset); |
576 | |
577 | int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev) |
578 | { |
579 | /* Increase VCO range to prevent unlocking problem of PLL at low |
580 | * temp |
581 | */ |
582 | bcm_phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048); |
583 | |
584 | /* Change Ki to 011 */ |
585 | bcm_phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b); |
586 | |
587 | /* Disable loading of TVCO buffer to bandgap, set bandgap trim |
588 | * to 111 |
589 | */ |
590 | bcm_phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20); |
591 | |
592 | /* Adjust bias current trim by -3 */ |
593 | bcm_phy_write_misc(phydev, DSP_TAP10, 0x690b); |
594 | |
595 | /* Switch to CORE_BASE1E */ |
596 | phy_write(phydev, MII_BRCM_CORE_BASE1E, val: 0xd); |
597 | |
598 | bcm_phy_r_rc_cal_reset(phydev); |
599 | |
600 | /* write AFE_RXCONFIG_0 */ |
601 | bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19); |
602 | |
603 | /* write AFE_RXCONFIG_1 */ |
604 | bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f); |
605 | |
606 | /* write AFE_RX_LP_COUNTER */ |
607 | bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0); |
608 | |
609 | /* write AFE_HPF_TRIM_OTHERS */ |
610 | bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b); |
611 | |
612 | /* write AFTE_TX_CONFIG */ |
613 | bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800); |
614 | |
615 | return 0; |
616 | } |
617 | EXPORT_SYMBOL_GPL(bcm_phy_28nm_a0b0_afe_config_init); |
618 | |
619 | int bcm_phy_enable_jumbo(struct phy_device *phydev) |
620 | { |
621 | int ret; |
622 | |
623 | ret = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL); |
624 | if (ret < 0) |
625 | return ret; |
626 | |
627 | /* Enable extended length packet reception */ |
628 | ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL, |
629 | ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN); |
630 | if (ret < 0) |
631 | return ret; |
632 | |
633 | /* Enable the elastic FIFO for raising the transmission limit from |
634 | * 4.5KB to 10KB, at the expense of an additional 16 ns in propagation |
635 | * latency. |
636 | */ |
637 | return phy_set_bits(phydev, MII_BCM54XX_ECR, MII_BCM54XX_ECR_FIFOE); |
638 | } |
639 | EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo); |
640 | |
641 | static int __bcm_phy_enable_rdb_access(struct phy_device *phydev) |
642 | { |
643 | return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0); |
644 | } |
645 | |
646 | static int __bcm_phy_enable_legacy_access(struct phy_device *phydev) |
647 | { |
648 | return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087, |
649 | BCM54XX_ACCESS_MODE_LEGACY_EN); |
650 | } |
651 | |
652 | static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb) |
653 | { |
654 | u16 mask, set; |
655 | int ret; |
656 | |
657 | /* Auto-negotiation must be enabled for cable diagnostics to work, but |
658 | * don't advertise any capabilities. |
659 | */ |
660 | phy_write(phydev, MII_BMCR, BMCR_ANENABLE); |
661 | phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA); |
662 | phy_write(phydev, MII_CTRL1000, val: 0); |
663 | |
664 | phy_lock_mdio_bus(phydev); |
665 | if (is_rdb) { |
666 | ret = __bcm_phy_enable_legacy_access(phydev); |
667 | if (ret) |
668 | goto out; |
669 | } |
670 | |
671 | mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK; |
672 | set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK | |
673 | FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK, |
674 | BCM54XX_ECD_CTRL_UNIT_CM); |
675 | |
676 | ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set); |
677 | |
678 | out: |
679 | /* re-enable the RDB access even if there was an error */ |
680 | if (is_rdb) |
681 | ret = __bcm_phy_enable_rdb_access(phydev) ? : ret; |
682 | |
683 | phy_unlock_mdio_bus(phydev); |
684 | |
685 | return ret; |
686 | } |
687 | |
688 | static int bcm_phy_cable_test_report_trans(int result) |
689 | { |
690 | switch (result) { |
691 | case BCM54XX_ECD_FAULT_TYPE_OK: |
692 | return ETHTOOL_A_CABLE_RESULT_CODE_OK; |
693 | case BCM54XX_ECD_FAULT_TYPE_OPEN: |
694 | return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; |
695 | case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT: |
696 | return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; |
697 | case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT: |
698 | return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT; |
699 | case BCM54XX_ECD_FAULT_TYPE_INVALID: |
700 | case BCM54XX_ECD_FAULT_TYPE_BUSY: |
701 | default: |
702 | return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; |
703 | } |
704 | } |
705 | |
706 | static bool bcm_phy_distance_valid(int result) |
707 | { |
708 | switch (result) { |
709 | case BCM54XX_ECD_FAULT_TYPE_OPEN: |
710 | case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT: |
711 | case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT: |
712 | return true; |
713 | } |
714 | return false; |
715 | } |
716 | |
717 | static int bcm_phy_report_length(struct phy_device *phydev, int pair) |
718 | { |
719 | int val; |
720 | |
721 | val = __bcm_phy_read_exp(phydev, |
722 | BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair); |
723 | if (val < 0) |
724 | return val; |
725 | |
726 | if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID) |
727 | return 0; |
728 | |
729 | ethnl_cable_test_fault_length(phydev, pair, cm: val); |
730 | |
731 | return 0; |
732 | } |
733 | |
734 | static int _bcm_phy_cable_test_get_status(struct phy_device *phydev, |
735 | bool *finished, bool is_rdb) |
736 | { |
737 | int pair_a, pair_b, pair_c, pair_d, ret; |
738 | |
739 | *finished = false; |
740 | |
741 | phy_lock_mdio_bus(phydev); |
742 | |
743 | if (is_rdb) { |
744 | ret = __bcm_phy_enable_legacy_access(phydev); |
745 | if (ret) |
746 | goto out; |
747 | } |
748 | |
749 | ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL); |
750 | if (ret < 0) |
751 | goto out; |
752 | |
753 | if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) { |
754 | ret = 0; |
755 | goto out; |
756 | } |
757 | |
758 | ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE); |
759 | if (ret < 0) |
760 | goto out; |
761 | |
762 | pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret); |
763 | pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret); |
764 | pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret); |
765 | pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret); |
766 | |
767 | ethnl_cable_test_result(phydev, pair: ETHTOOL_A_CABLE_PAIR_A, |
768 | result: bcm_phy_cable_test_report_trans(result: pair_a)); |
769 | ethnl_cable_test_result(phydev, pair: ETHTOOL_A_CABLE_PAIR_B, |
770 | result: bcm_phy_cable_test_report_trans(result: pair_b)); |
771 | ethnl_cable_test_result(phydev, pair: ETHTOOL_A_CABLE_PAIR_C, |
772 | result: bcm_phy_cable_test_report_trans(result: pair_c)); |
773 | ethnl_cable_test_result(phydev, pair: ETHTOOL_A_CABLE_PAIR_D, |
774 | result: bcm_phy_cable_test_report_trans(result: pair_d)); |
775 | |
776 | if (bcm_phy_distance_valid(result: pair_a)) |
777 | bcm_phy_report_length(phydev, pair: 0); |
778 | if (bcm_phy_distance_valid(result: pair_b)) |
779 | bcm_phy_report_length(phydev, pair: 1); |
780 | if (bcm_phy_distance_valid(result: pair_c)) |
781 | bcm_phy_report_length(phydev, pair: 2); |
782 | if (bcm_phy_distance_valid(result: pair_d)) |
783 | bcm_phy_report_length(phydev, pair: 3); |
784 | |
785 | ret = 0; |
786 | *finished = true; |
787 | out: |
788 | /* re-enable the RDB access even if there was an error */ |
789 | if (is_rdb) |
790 | ret = __bcm_phy_enable_rdb_access(phydev) ? : ret; |
791 | |
792 | phy_unlock_mdio_bus(phydev); |
793 | |
794 | return ret; |
795 | } |
796 | |
797 | int bcm_phy_cable_test_start(struct phy_device *phydev) |
798 | { |
799 | return _bcm_phy_cable_test_start(phydev, is_rdb: false); |
800 | } |
801 | EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start); |
802 | |
803 | int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished) |
804 | { |
805 | return _bcm_phy_cable_test_get_status(phydev, finished, is_rdb: false); |
806 | } |
807 | EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status); |
808 | |
809 | /* We assume that all PHYs which support RDB access can be switched to legacy |
810 | * mode. If, in the future, this is not true anymore, we have to re-implement |
811 | * this with RDB access. |
812 | */ |
813 | int bcm_phy_cable_test_start_rdb(struct phy_device *phydev) |
814 | { |
815 | return _bcm_phy_cable_test_start(phydev, is_rdb: true); |
816 | } |
817 | EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb); |
818 | |
819 | int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev, |
820 | bool *finished) |
821 | { |
822 | return _bcm_phy_cable_test_get_status(phydev, finished, is_rdb: true); |
823 | } |
824 | EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb); |
825 | |
826 | #define BCM54XX_WOL_SUPPORTED_MASK (WAKE_UCAST | \ |
827 | WAKE_MCAST | \ |
828 | WAKE_BCAST | \ |
829 | WAKE_MAGIC | \ |
830 | WAKE_MAGICSECURE) |
831 | |
832 | int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) |
833 | { |
834 | struct net_device *ndev = phydev->attached_dev; |
835 | u8 da[ETH_ALEN], mask[ETH_ALEN]; |
836 | unsigned int i; |
837 | u16 ctl; |
838 | int ret; |
839 | |
840 | /* Allow a MAC driver to play through its own Wake-on-LAN |
841 | * implementation |
842 | */ |
843 | if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK) |
844 | return -EOPNOTSUPP; |
845 | |
846 | /* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's |
847 | * ethtool only supports 6, for now. |
848 | */ |
849 | BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN); |
850 | |
851 | /* Clear previous interrupts */ |
852 | ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS); |
853 | if (ret < 0) |
854 | return ret; |
855 | |
856 | ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL); |
857 | if (ret < 0) |
858 | return ret; |
859 | |
860 | ctl = ret; |
861 | |
862 | if (!wol->wolopts) { |
863 | if (phy_interrupt_is_valid(phydev)) |
864 | disable_irq_wake(irq: phydev->irq); |
865 | |
866 | /* Leave all interrupts disabled */ |
867 | ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, |
868 | BCM54XX_WOL_ALL_INTRS); |
869 | if (ret < 0) |
870 | return ret; |
871 | |
872 | /* Disable the global Wake-on-LAN enable bit */ |
873 | ctl &= ~BCM54XX_WOL_EN; |
874 | |
875 | return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl); |
876 | } |
877 | |
878 | /* Clear the previously configured mode and mask mode for Wake-on-LAN */ |
879 | ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT); |
880 | ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT); |
881 | ctl &= ~BCM54XX_WOL_DIR_PKT_EN; |
882 | ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT); |
883 | |
884 | /* When using WAKE_MAGIC, we program the magic pattern filter to match |
885 | * the device's MAC address and we accept any MAC DA in the Ethernet |
886 | * frame. |
887 | * |
888 | * When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the |
889 | * following: |
890 | * - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match |
891 | * - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care |
892 | * - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match |
893 | * |
894 | * Note that the Broadcast MAC DA is inherently going to match the |
895 | * multicast pattern being matched. |
896 | */ |
897 | memset(mask, 0, sizeof(mask)); |
898 | |
899 | if (wol->wolopts & WAKE_MCAST) { |
900 | memset(da, 0, sizeof(da)); |
901 | memset(mask, 0xff, sizeof(mask)); |
902 | da[0] = 0x01; |
903 | mask[0] = ~da[0]; |
904 | } else { |
905 | if (wol->wolopts & WAKE_UCAST) { |
906 | ether_addr_copy(dst: da, src: ndev->dev_addr); |
907 | } else if (wol->wolopts & WAKE_BCAST) { |
908 | eth_broadcast_addr(addr: da); |
909 | } else if (wol->wolopts & WAKE_MAGICSECURE) { |
910 | ether_addr_copy(dst: da, src: wol->sopass); |
911 | } else if (wol->wolopts & WAKE_MAGIC) { |
912 | memset(da, 0, sizeof(da)); |
913 | memset(mask, 0xff, sizeof(mask)); |
914 | } |
915 | } |
916 | |
917 | for (i = 0; i < ETH_ALEN / 2; i++) { |
918 | if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) { |
919 | ret = bcm_phy_write_exp(phydev, |
920 | BCM54XX_WOL_MPD_DATA1(2 - i), |
921 | ndev->dev_addr[i * 2] << 8 | |
922 | ndev->dev_addr[i * 2 + 1]); |
923 | if (ret < 0) |
924 | return ret; |
925 | } |
926 | |
927 | ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i), |
928 | da[i * 2] << 8 | da[i * 2 + 1]); |
929 | if (ret < 0) |
930 | return ret; |
931 | |
932 | ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i), |
933 | mask[i * 2] << 8 | mask[i * 2 + 1]); |
934 | if (ret) |
935 | return ret; |
936 | } |
937 | |
938 | if (wol->wolopts & WAKE_MAGICSECURE) { |
939 | ctl |= BCM54XX_WOL_SECKEY_OPT_6B << |
940 | BCM54XX_WOL_SECKEY_OPT_SHIFT; |
941 | ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT; |
942 | ctl |= BCM54XX_WOL_MASK_MODE_DA_FF << |
943 | BCM54XX_WOL_MASK_MODE_SHIFT; |
944 | } else { |
945 | if (wol->wolopts & WAKE_MAGIC) |
946 | ctl |= BCM54XX_WOL_MODE_SINGLE_MPD; |
947 | else |
948 | ctl |= BCM54XX_WOL_DIR_PKT_EN; |
949 | ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY << |
950 | BCM54XX_WOL_MASK_MODE_SHIFT; |
951 | } |
952 | |
953 | /* Globally enable Wake-on-LAN */ |
954 | ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK; |
955 | |
956 | ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl); |
957 | if (ret < 0) |
958 | return ret; |
959 | |
960 | /* Enable WOL interrupt on LED4 */ |
961 | ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL); |
962 | if (ret < 0) |
963 | return ret; |
964 | |
965 | ret |= BCM54XX_LED4_SEL_INTR; |
966 | ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret); |
967 | if (ret < 0) |
968 | return ret; |
969 | |
970 | /* Enable all Wake-on-LAN interrupt sources */ |
971 | ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0); |
972 | if (ret < 0) |
973 | return ret; |
974 | |
975 | if (phy_interrupt_is_valid(phydev)) |
976 | enable_irq_wake(irq: phydev->irq); |
977 | |
978 | return 0; |
979 | } |
980 | EXPORT_SYMBOL_GPL(bcm_phy_set_wol); |
981 | |
982 | void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) |
983 | { |
984 | struct net_device *ndev = phydev->attached_dev; |
985 | u8 da[ETH_ALEN]; |
986 | unsigned int i; |
987 | int ret; |
988 | u16 ctl; |
989 | |
990 | wol->supported = BCM54XX_WOL_SUPPORTED_MASK; |
991 | wol->wolopts = 0; |
992 | |
993 | ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL); |
994 | if (ret < 0) |
995 | return; |
996 | |
997 | ctl = ret; |
998 | |
999 | if (!(ctl & BCM54XX_WOL_EN)) |
1000 | return; |
1001 | |
1002 | for (i = 0; i < sizeof(da) / 2; i++) { |
1003 | ret = bcm_phy_read_exp(phydev, |
1004 | BCM54XX_WOL_MPD_DATA2(2 - i)); |
1005 | if (ret < 0) |
1006 | return; |
1007 | |
1008 | da[i * 2] = ret >> 8; |
1009 | da[i * 2 + 1] = ret & 0xff; |
1010 | } |
1011 | |
1012 | if (ctl & BCM54XX_WOL_DIR_PKT_EN) { |
1013 | if (is_broadcast_ether_addr(addr: da)) |
1014 | wol->wolopts |= WAKE_BCAST; |
1015 | else if (is_multicast_ether_addr(addr: da)) |
1016 | wol->wolopts |= WAKE_MCAST; |
1017 | else if (ether_addr_equal(addr1: da, addr2: ndev->dev_addr)) |
1018 | wol->wolopts |= WAKE_UCAST; |
1019 | } else { |
1020 | ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK; |
1021 | switch (ctl) { |
1022 | case BCM54XX_WOL_MODE_SINGLE_MPD: |
1023 | wol->wolopts |= WAKE_MAGIC; |
1024 | break; |
1025 | case BCM54XX_WOL_MODE_SINGLE_MPDSEC: |
1026 | wol->wolopts |= WAKE_MAGICSECURE; |
1027 | memcpy(wol->sopass, da, sizeof(da)); |
1028 | break; |
1029 | default: |
1030 | break; |
1031 | } |
1032 | } |
1033 | } |
1034 | EXPORT_SYMBOL_GPL(bcm_phy_get_wol); |
1035 | |
1036 | irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id) |
1037 | { |
1038 | return IRQ_HANDLED; |
1039 | } |
1040 | EXPORT_SYMBOL_GPL(bcm_phy_wol_isr); |
1041 | |
1042 | int bcm_phy_led_brightness_set(struct phy_device *phydev, |
1043 | u8 index, enum led_brightness value) |
1044 | { |
1045 | u8 led_num; |
1046 | int ret; |
1047 | u16 reg; |
1048 | |
1049 | if (index >= 4) |
1050 | return -EINVAL; |
1051 | |
1052 | /* Two LEDS per register */ |
1053 | led_num = index % 2; |
1054 | reg = index >= 2 ? BCM54XX_SHD_LEDS2 : BCM54XX_SHD_LEDS1; |
1055 | |
1056 | ret = bcm_phy_read_shadow(phydev, reg); |
1057 | if (ret < 0) |
1058 | return ret; |
1059 | |
1060 | ret &= ~(BCM_LED_SRC_MASK << BCM54XX_SHD_LEDS_SHIFT(led_num)); |
1061 | if (value == LED_OFF) |
1062 | ret |= BCM_LED_SRC_OFF << BCM54XX_SHD_LEDS_SHIFT(led_num); |
1063 | else |
1064 | ret |= BCM_LED_SRC_ON << BCM54XX_SHD_LEDS_SHIFT(led_num); |
1065 | return bcm_phy_write_shadow(phydev, reg, ret); |
1066 | } |
1067 | EXPORT_SYMBOL_GPL(bcm_phy_led_brightness_set); |
1068 | |
1069 | MODULE_DESCRIPTION("Broadcom PHY Library" ); |
1070 | MODULE_LICENSE("GPL v2" ); |
1071 | MODULE_AUTHOR("Broadcom Corporation" ); |
1072 | |