Skip to content

Commit 7880502

Browse files
microchip1davem330
authored andcommitted
net: phy: microchip_t1: add cable test support for lan87xx phy
Add a basic cable test (diagnostic) support for lan87xx phy. Tested with LAN8770 for connected/open/short wires using ethtool. Signed-off-by: Yuiko Oshino <yuiko.oshino@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 11195bf commit 7880502

File tree

1 file changed

+239
-0
lines changed

1 file changed

+239
-0
lines changed

drivers/net/phy/microchip_t1.c

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
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)
@@ -35,8 +37,14 @@
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+
229465
static 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

Comments
 (0)