66#include <linux/delay.h>
77#include <linux/mii.h>
88#include <linux/phy.h>
9+ #include <linux/ethtool.h>
10+ #include <linux/ethtool_netlink.h>
911
1012/* External Register Control Register */
1113#define LAN87XX_EXT_REG_CTL (0x14)
3537#define PHYACC_ATTR_BANK_MISC 1
3638#define PHYACC_ATTR_BANK_PCS 2
3739#define PHYACC_ATTR_BANK_AFE 3
40+ #define PHYACC_ATTR_BANK_DSP 4
3841#define PHYACC_ATTR_BANK_MAX 7
3942
43+ /* measurement defines */
44+ #define LAN87XX_CABLE_TEST_OK 0
45+ #define LAN87XX_CABLE_TEST_OPEN 1
46+ #define LAN87XX_CABLE_TEST_SAME_SHORT 2
47+
4048#define DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>"
4149#define DRIVER_DESC "Microchip LAN87XX T1 PHY driver"
4250
@@ -226,11 +234,240 @@ static int lan87xx_config_init(struct phy_device *phydev)
226234 return rc < 0 ? rc : 0 ;
227235}
228236
237+ static int microchip_cable_test_start_common (struct phy_device * phydev )
238+ {
239+ int bmcr , bmsr , ret ;
240+
241+ /* If auto-negotiation is enabled, but not complete, the cable
242+ * test never completes. So disable auto-neg.
243+ */
244+ bmcr = phy_read (phydev , MII_BMCR );
245+ if (bmcr < 0 )
246+ return bmcr ;
247+
248+ bmsr = phy_read (phydev , MII_BMSR );
249+
250+ if (bmsr < 0 )
251+ return bmsr ;
252+
253+ if (bmcr & BMCR_ANENABLE ) {
254+ ret = phy_modify (phydev , MII_BMCR , BMCR_ANENABLE , 0 );
255+ if (ret < 0 )
256+ return ret ;
257+ ret = genphy_soft_reset (phydev );
258+ if (ret < 0 )
259+ return ret ;
260+ }
261+
262+ /* If the link is up, allow it some time to go down */
263+ if (bmsr & BMSR_LSTATUS )
264+ msleep (1500 );
265+
266+ return 0 ;
267+ }
268+
269+ static int lan87xx_cable_test_start (struct phy_device * phydev )
270+ {
271+ static const struct access_ereg_val cable_test [] = {
272+ /* min wait */
273+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 93 ,
274+ 0 , 0 },
275+ /* max wait */
276+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 94 ,
277+ 10 , 0 },
278+ /* pulse cycle */
279+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 95 ,
280+ 90 , 0 },
281+ /* cable diag thresh */
282+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 92 ,
283+ 60 , 0 },
284+ /* max gain */
285+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 79 ,
286+ 31 , 0 },
287+ /* clock align for each iteration */
288+ {PHYACC_ATTR_MODE_MODIFY , PHYACC_ATTR_BANK_DSP , 55 ,
289+ 0 , 0x0038 },
290+ /* max cycle wait config */
291+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 94 ,
292+ 70 , 0 },
293+ /* start cable diag*/
294+ {PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_DSP , 90 ,
295+ 1 , 0 },
296+ };
297+ int rc , i ;
298+
299+ rc = microchip_cable_test_start_common (phydev );
300+ if (rc < 0 )
301+ return rc ;
302+
303+ /* start cable diag */
304+ /* check if part is alive - if not, return diagnostic error */
305+ rc = access_ereg (phydev , PHYACC_ATTR_MODE_READ , PHYACC_ATTR_BANK_SMI ,
306+ 0x00 , 0 );
307+ if (rc < 0 )
308+ return rc ;
309+
310+ /* master/slave specific configs */
311+ rc = access_ereg (phydev , PHYACC_ATTR_MODE_READ , PHYACC_ATTR_BANK_SMI ,
312+ 0x0A , 0 );
313+ if (rc < 0 )
314+ return rc ;
315+
316+ if ((rc & 0x4000 ) != 0x4000 ) {
317+ /* DUT is Slave */
318+ rc = access_ereg_modify_changed (phydev , PHYACC_ATTR_BANK_AFE ,
319+ 0x0E , 0x5 , 0x7 );
320+ if (rc < 0 )
321+ return rc ;
322+ rc = access_ereg_modify_changed (phydev , PHYACC_ATTR_BANK_SMI ,
323+ 0x1A , 0x8 , 0x8 );
324+ if (rc < 0 )
325+ return rc ;
326+ } else {
327+ /* DUT is Master */
328+ rc = access_ereg_modify_changed (phydev , PHYACC_ATTR_BANK_SMI ,
329+ 0x10 , 0x8 , 0x40 );
330+ if (rc < 0 )
331+ return rc ;
332+ }
333+
334+ for (i = 0 ; i < ARRAY_SIZE (cable_test ); i ++ ) {
335+ if (cable_test [i ].mode == PHYACC_ATTR_MODE_MODIFY ) {
336+ rc = access_ereg_modify_changed (phydev ,
337+ cable_test [i ].bank ,
338+ cable_test [i ].offset ,
339+ cable_test [i ].val ,
340+ cable_test [i ].mask );
341+ /* wait 50ms */
342+ msleep (50 );
343+ } else {
344+ rc = access_ereg (phydev , cable_test [i ].mode ,
345+ cable_test [i ].bank ,
346+ cable_test [i ].offset ,
347+ cable_test [i ].val );
348+ }
349+ if (rc < 0 )
350+ return rc ;
351+ }
352+ /* cable diag started */
353+
354+ return 0 ;
355+ }
356+
357+ static int lan87xx_cable_test_report_trans (u32 result )
358+ {
359+ switch (result ) {
360+ case LAN87XX_CABLE_TEST_OK :
361+ return ETHTOOL_A_CABLE_RESULT_CODE_OK ;
362+ case LAN87XX_CABLE_TEST_OPEN :
363+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN ;
364+ case LAN87XX_CABLE_TEST_SAME_SHORT :
365+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT ;
366+ default :
367+ /* DIAGNOSTIC_ERROR */
368+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC ;
369+ }
370+ }
371+
372+ static int lan87xx_cable_test_report (struct phy_device * phydev )
373+ {
374+ int pos_peak_cycle = 0 , pos_peak_in_phases = 0 , pos_peak_phase = 0 ;
375+ int neg_peak_cycle = 0 , neg_peak_in_phases = 0 , neg_peak_phase = 0 ;
376+ int noise_margin = 20 , time_margin = 89 , jitter_var = 30 ;
377+ int min_time_diff = 96 , max_time_diff = 96 + time_margin ;
378+ bool fault = false, check_a = false, check_b = false;
379+ int gain_idx = 0 , pos_peak = 0 , neg_peak = 0 ;
380+ int pos_peak_time = 0 , neg_peak_time = 0 ;
381+ int pos_peak_in_phases_hybrid = 0 ;
382+ int detect = -1 ;
383+
384+ gain_idx = access_ereg (phydev , PHYACC_ATTR_MODE_READ ,
385+ PHYACC_ATTR_BANK_DSP , 151 , 0 );
386+ /* read non-hybrid results */
387+ pos_peak = access_ereg (phydev , PHYACC_ATTR_MODE_READ ,
388+ PHYACC_ATTR_BANK_DSP , 153 , 0 );
389+ neg_peak = access_ereg (phydev , PHYACC_ATTR_MODE_READ ,
390+ PHYACC_ATTR_BANK_DSP , 154 , 0 );
391+ pos_peak_time = access_ereg (phydev , PHYACC_ATTR_MODE_READ ,
392+ PHYACC_ATTR_BANK_DSP , 156 , 0 );
393+ neg_peak_time = access_ereg (phydev , PHYACC_ATTR_MODE_READ ,
394+ PHYACC_ATTR_BANK_DSP , 157 , 0 );
395+
396+ pos_peak_cycle = (pos_peak_time >> 7 ) & 0x7F ;
397+ /* calculate non-hybrid values */
398+ pos_peak_phase = pos_peak_time & 0x7F ;
399+ pos_peak_in_phases = (pos_peak_cycle * 96 ) + pos_peak_phase ;
400+ neg_peak_cycle = (neg_peak_time >> 7 ) & 0x7F ;
401+ neg_peak_phase = neg_peak_time & 0x7F ;
402+ neg_peak_in_phases = (neg_peak_cycle * 96 ) + neg_peak_phase ;
403+
404+ /* process values */
405+ check_a =
406+ ((pos_peak_in_phases - neg_peak_in_phases ) >= min_time_diff ) &&
407+ ((pos_peak_in_phases - neg_peak_in_phases ) < max_time_diff ) &&
408+ pos_peak_in_phases_hybrid < pos_peak_in_phases &&
409+ (pos_peak_in_phases_hybrid < (neg_peak_in_phases + jitter_var ));
410+ check_b =
411+ ((neg_peak_in_phases - pos_peak_in_phases ) >= min_time_diff ) &&
412+ ((neg_peak_in_phases - pos_peak_in_phases ) < max_time_diff ) &&
413+ pos_peak_in_phases_hybrid < neg_peak_in_phases &&
414+ (pos_peak_in_phases_hybrid < (pos_peak_in_phases + jitter_var ));
415+
416+ if (pos_peak_in_phases > neg_peak_in_phases && check_a )
417+ detect = 2 ;
418+ else if ((neg_peak_in_phases > pos_peak_in_phases ) && check_b )
419+ detect = 1 ;
420+
421+ if (pos_peak > noise_margin && neg_peak > noise_margin &&
422+ gain_idx >= 0 ) {
423+ if (detect == 1 || detect == 2 )
424+ fault = true;
425+ }
426+
427+ if (!fault )
428+ detect = 0 ;
429+
430+ ethnl_cable_test_result (phydev , ETHTOOL_A_CABLE_PAIR_A ,
431+ lan87xx_cable_test_report_trans (detect ));
432+
433+ return 0 ;
434+ }
435+
436+ static int lan87xx_cable_test_get_status (struct phy_device * phydev ,
437+ bool * finished )
438+ {
439+ int rc = 0 ;
440+
441+ * finished = false;
442+
443+ /* check if cable diag was finished */
444+ rc = access_ereg (phydev , PHYACC_ATTR_MODE_READ , PHYACC_ATTR_BANK_DSP ,
445+ 90 , 0 );
446+ if (rc < 0 )
447+ return rc ;
448+
449+ if ((rc & 2 ) == 2 ) {
450+ /* stop cable diag*/
451+ rc = access_ereg (phydev , PHYACC_ATTR_MODE_WRITE ,
452+ PHYACC_ATTR_BANK_DSP ,
453+ 90 , 0 );
454+ if (rc < 0 )
455+ return rc ;
456+
457+ * finished = true;
458+
459+ return lan87xx_cable_test_report (phydev );
460+ }
461+
462+ return 0 ;
463+ }
464+
229465static struct phy_driver microchip_t1_phy_driver [] = {
230466 {
231467 .phy_id = 0x0007c150 ,
232468 .phy_id_mask = 0xfffffff0 ,
233469 .name = "Microchip LAN87xx T1" ,
470+ .flags = PHY_POLL_CABLE_TEST ,
234471
235472 .features = PHY_BASIC_T1_FEATURES ,
236473
@@ -241,6 +478,8 @@ static struct phy_driver microchip_t1_phy_driver[] = {
241478
242479 .suspend = genphy_suspend ,
243480 .resume = genphy_resume ,
481+ .cable_test_start = lan87xx_cable_test_start ,
482+ .cable_test_get_status = lan87xx_cable_test_get_status ,
244483 }
245484};
246485
0 commit comments