xref: /openbmc/linux/drivers/net/phy/bcm-phy-lib.c (revision 11ecf8c5)
1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0
2a1cba561SArun Parameswaran /*
3cda792c3SDoug Berger  * Copyright (C) 2015-2017 Broadcom
4a1cba561SArun Parameswaran  */
5a1cba561SArun Parameswaran 
6a1cba561SArun Parameswaran #include "bcm-phy-lib.h"
711ecf8c5SMichael Walle #include <linux/bitfield.h>
8a1cba561SArun Parameswaran #include <linux/brcmphy.h>
9a1cba561SArun Parameswaran #include <linux/export.h>
10a1cba561SArun Parameswaran #include <linux/mdio.h>
11b89eb1fcSArun Parameswaran #include <linux/module.h>
12a1cba561SArun Parameswaran #include <linux/phy.h>
13820ee17bSFlorian Fainelli #include <linux/ethtool.h>
1411ecf8c5SMichael Walle #include <linux/ethtool_netlink.h>
15a1cba561SArun Parameswaran 
16a1cba561SArun Parameswaran #define MII_BCM_CHANNEL_WIDTH     0x2000
17a1cba561SArun Parameswaran #define BCM_CL45VEN_EEE_ADV       0x3c
18a1cba561SArun Parameswaran 
197d7e7bceSMichael Walle int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
207d7e7bceSMichael Walle {
217d7e7bceSMichael Walle 	int rc;
227d7e7bceSMichael Walle 
237d7e7bceSMichael Walle 	rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
247d7e7bceSMichael Walle 	if (rc < 0)
257d7e7bceSMichael Walle 		return rc;
267d7e7bceSMichael Walle 
277d7e7bceSMichael Walle 	return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
287d7e7bceSMichael Walle }
297d7e7bceSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_write_exp);
307d7e7bceSMichael Walle 
31a1cba561SArun Parameswaran int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
32a1cba561SArun Parameswaran {
33a1cba561SArun Parameswaran 	int rc;
34a1cba561SArun Parameswaran 
357d7e7bceSMichael Walle 	phy_lock_mdio_bus(phydev);
367d7e7bceSMichael Walle 	rc = __bcm_phy_write_exp(phydev, reg, val);
377d7e7bceSMichael Walle 	phy_unlock_mdio_bus(phydev);
38a1cba561SArun Parameswaran 
397d7e7bceSMichael Walle 	return rc;
40a1cba561SArun Parameswaran }
41a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_write_exp);
42a1cba561SArun Parameswaran 
437d7e7bceSMichael Walle int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
44a1cba561SArun Parameswaran {
45a1cba561SArun Parameswaran 	int val;
46a1cba561SArun Parameswaran 
477d7e7bceSMichael Walle 	val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
48a1cba561SArun Parameswaran 	if (val < 0)
49a1cba561SArun Parameswaran 		return val;
50a1cba561SArun Parameswaran 
517d7e7bceSMichael Walle 	val = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
52a1cba561SArun Parameswaran 
53a1cba561SArun Parameswaran 	/* Restore default value.  It's O.K. if this write fails. */
547d7e7bceSMichael Walle 	__phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
55a1cba561SArun Parameswaran 
56a1cba561SArun Parameswaran 	return val;
57a1cba561SArun Parameswaran }
587d7e7bceSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_read_exp);
597d7e7bceSMichael Walle 
607d7e7bceSMichael Walle int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
617d7e7bceSMichael Walle {
627d7e7bceSMichael Walle 	int rc;
637d7e7bceSMichael Walle 
647d7e7bceSMichael Walle 	phy_lock_mdio_bus(phydev);
657d7e7bceSMichael Walle 	rc = __bcm_phy_read_exp(phydev, reg);
667d7e7bceSMichael Walle 	phy_unlock_mdio_bus(phydev);
677d7e7bceSMichael Walle 
687d7e7bceSMichael Walle 	return rc;
697d7e7bceSMichael Walle }
70a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_read_exp);
71a1cba561SArun Parameswaran 
72e184a907SMichael Walle int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
73e184a907SMichael Walle {
74e184a907SMichael Walle 	int new, ret;
75e184a907SMichael Walle 
76e184a907SMichael Walle 	ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
77e184a907SMichael Walle 	if (ret < 0)
78e184a907SMichael Walle 		return ret;
79e184a907SMichael Walle 
80e184a907SMichael Walle 	ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
81e184a907SMichael Walle 	if (ret < 0)
82e184a907SMichael Walle 		return ret;
83e184a907SMichael Walle 
84e184a907SMichael Walle 	new = (ret & ~mask) | set;
85e184a907SMichael Walle 	if (new == ret)
86e184a907SMichael Walle 		return 0;
87e184a907SMichael Walle 
88e184a907SMichael Walle 	return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new);
89e184a907SMichael Walle }
90e184a907SMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp);
91e184a907SMichael Walle 
92e184a907SMichael Walle int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
93e184a907SMichael Walle {
94e184a907SMichael Walle 	int ret;
95e184a907SMichael Walle 
96e184a907SMichael Walle 	phy_lock_mdio_bus(phydev);
97e184a907SMichael Walle 	ret = __bcm_phy_modify_exp(phydev, reg, mask, set);
98e184a907SMichael Walle 	phy_unlock_mdio_bus(phydev);
99e184a907SMichael Walle 
100e184a907SMichael Walle 	return ret;
101e184a907SMichael Walle }
102e184a907SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_modify_exp);
103e184a907SMichael Walle 
1045519da87SFlorian Fainelli int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum)
1055519da87SFlorian Fainelli {
1065519da87SFlorian Fainelli 	/* The register must be written to both the Shadow Register Select and
1075519da87SFlorian Fainelli 	 * the Shadow Read Register Selector
1085519da87SFlorian Fainelli 	 */
109733a969aSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK |
1105519da87SFlorian Fainelli 		  regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT);
1115519da87SFlorian Fainelli 	return phy_read(phydev, MII_BCM54XX_AUX_CTL);
1125519da87SFlorian Fainelli }
1135519da87SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read);
1145519da87SFlorian Fainelli 
1155519da87SFlorian Fainelli int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
1165519da87SFlorian Fainelli {
1175519da87SFlorian Fainelli 	return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
1185519da87SFlorian Fainelli }
1195519da87SFlorian Fainelli EXPORT_SYMBOL(bcm54xx_auxctl_write);
1205519da87SFlorian Fainelli 
121a1cba561SArun Parameswaran int bcm_phy_write_misc(struct phy_device *phydev,
122a1cba561SArun Parameswaran 		       u16 reg, u16 chl, u16 val)
123a1cba561SArun Parameswaran {
124a1cba561SArun Parameswaran 	int rc;
125a1cba561SArun Parameswaran 	int tmp;
126a1cba561SArun Parameswaran 
127a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
128a1cba561SArun Parameswaran 		       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
129a1cba561SArun Parameswaran 	if (rc < 0)
130a1cba561SArun Parameswaran 		return rc;
131a1cba561SArun Parameswaran 
132a1cba561SArun Parameswaran 	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
133a1cba561SArun Parameswaran 	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
134a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
135a1cba561SArun Parameswaran 	if (rc < 0)
136a1cba561SArun Parameswaran 		return rc;
137a1cba561SArun Parameswaran 
138a1cba561SArun Parameswaran 	tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
139a1cba561SArun Parameswaran 	rc = bcm_phy_write_exp(phydev, tmp, val);
140a1cba561SArun Parameswaran 
141a1cba561SArun Parameswaran 	return rc;
142a1cba561SArun Parameswaran }
143a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_write_misc);
144a1cba561SArun Parameswaran 
145a1cba561SArun Parameswaran int bcm_phy_read_misc(struct phy_device *phydev,
146a1cba561SArun Parameswaran 		      u16 reg, u16 chl)
147a1cba561SArun Parameswaran {
148a1cba561SArun Parameswaran 	int rc;
149a1cba561SArun Parameswaran 	int tmp;
150a1cba561SArun Parameswaran 
151a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
152a1cba561SArun Parameswaran 		       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
153a1cba561SArun Parameswaran 	if (rc < 0)
154a1cba561SArun Parameswaran 		return rc;
155a1cba561SArun Parameswaran 
156a1cba561SArun Parameswaran 	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
157a1cba561SArun Parameswaran 	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
158a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
159a1cba561SArun Parameswaran 	if (rc < 0)
160a1cba561SArun Parameswaran 		return rc;
161a1cba561SArun Parameswaran 
162a1cba561SArun Parameswaran 	tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
163a1cba561SArun Parameswaran 	rc = bcm_phy_read_exp(phydev, tmp);
164a1cba561SArun Parameswaran 
165a1cba561SArun Parameswaran 	return rc;
166a1cba561SArun Parameswaran }
167a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_read_misc);
168a1cba561SArun Parameswaran 
169a1cba561SArun Parameswaran int bcm_phy_ack_intr(struct phy_device *phydev)
170a1cba561SArun Parameswaran {
171a1cba561SArun Parameswaran 	int reg;
172a1cba561SArun Parameswaran 
173a1cba561SArun Parameswaran 	/* Clear pending interrupts.  */
174a1cba561SArun Parameswaran 	reg = phy_read(phydev, MII_BCM54XX_ISR);
175a1cba561SArun Parameswaran 	if (reg < 0)
176a1cba561SArun Parameswaran 		return reg;
177a1cba561SArun Parameswaran 
178a1cba561SArun Parameswaran 	return 0;
179a1cba561SArun Parameswaran }
180a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_ack_intr);
181a1cba561SArun Parameswaran 
182a1cba561SArun Parameswaran int bcm_phy_config_intr(struct phy_device *phydev)
183a1cba561SArun Parameswaran {
184a1cba561SArun Parameswaran 	int reg;
185a1cba561SArun Parameswaran 
186a1cba561SArun Parameswaran 	reg = phy_read(phydev, MII_BCM54XX_ECR);
187a1cba561SArun Parameswaran 	if (reg < 0)
188a1cba561SArun Parameswaran 		return reg;
189a1cba561SArun Parameswaran 
190a1cba561SArun Parameswaran 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
191a1cba561SArun Parameswaran 		reg &= ~MII_BCM54XX_ECR_IM;
192a1cba561SArun Parameswaran 	else
193a1cba561SArun Parameswaran 		reg |= MII_BCM54XX_ECR_IM;
194a1cba561SArun Parameswaran 
195a1cba561SArun Parameswaran 	return phy_write(phydev, MII_BCM54XX_ECR, reg);
196a1cba561SArun Parameswaran }
197a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_config_intr);
198a1cba561SArun Parameswaran 
199a1cba561SArun Parameswaran int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow)
200a1cba561SArun Parameswaran {
201a1cba561SArun Parameswaran 	phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow));
202a1cba561SArun Parameswaran 	return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD));
203a1cba561SArun Parameswaran }
204a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_read_shadow);
205a1cba561SArun Parameswaran 
206a1cba561SArun Parameswaran int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow,
207a1cba561SArun Parameswaran 			 u16 val)
208a1cba561SArun Parameswaran {
209a1cba561SArun Parameswaran 	return phy_write(phydev, MII_BCM54XX_SHD,
210a1cba561SArun Parameswaran 			 MII_BCM54XX_SHD_WRITE |
211a1cba561SArun Parameswaran 			 MII_BCM54XX_SHD_VAL(shadow) |
212a1cba561SArun Parameswaran 			 MII_BCM54XX_SHD_DATA(val));
213a1cba561SArun Parameswaran }
214a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_write_shadow);
215a1cba561SArun Parameswaran 
2160a32f1ffSMichael Walle int __bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
2170a32f1ffSMichael Walle {
2180a32f1ffSMichael Walle 	int val;
2190a32f1ffSMichael Walle 
2200a32f1ffSMichael Walle 	val = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
2210a32f1ffSMichael Walle 	if (val < 0)
2220a32f1ffSMichael Walle 		return val;
2230a32f1ffSMichael Walle 
2240a32f1ffSMichael Walle 	return __phy_read(phydev, MII_BCM54XX_RDB_DATA);
2250a32f1ffSMichael Walle }
2260a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_read_rdb);
2270a32f1ffSMichael Walle 
2280a32f1ffSMichael Walle int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
2290a32f1ffSMichael Walle {
2300a32f1ffSMichael Walle 	int ret;
2310a32f1ffSMichael Walle 
2320a32f1ffSMichael Walle 	phy_lock_mdio_bus(phydev);
2330a32f1ffSMichael Walle 	ret = __bcm_phy_read_rdb(phydev, rdb);
2340a32f1ffSMichael Walle 	phy_unlock_mdio_bus(phydev);
2350a32f1ffSMichael Walle 
2360a32f1ffSMichael Walle 	return ret;
2370a32f1ffSMichael Walle }
2380a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_read_rdb);
2390a32f1ffSMichael Walle 
2400a32f1ffSMichael Walle int __bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
2410a32f1ffSMichael Walle {
2420a32f1ffSMichael Walle 	int ret;
2430a32f1ffSMichael Walle 
2440a32f1ffSMichael Walle 	ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
2450a32f1ffSMichael Walle 	if (ret < 0)
2460a32f1ffSMichael Walle 		return ret;
2470a32f1ffSMichael Walle 
2480a32f1ffSMichael Walle 	return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val);
2490a32f1ffSMichael Walle }
2500a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_write_rdb);
2510a32f1ffSMichael Walle 
2520a32f1ffSMichael Walle int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
2530a32f1ffSMichael Walle {
2540a32f1ffSMichael Walle 	int ret;
2550a32f1ffSMichael Walle 
2560a32f1ffSMichael Walle 	phy_lock_mdio_bus(phydev);
2570a32f1ffSMichael Walle 	ret = __bcm_phy_write_rdb(phydev, rdb, val);
2580a32f1ffSMichael Walle 	phy_unlock_mdio_bus(phydev);
2590a32f1ffSMichael Walle 
2600a32f1ffSMichael Walle 	return ret;
2610a32f1ffSMichael Walle }
2620a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_write_rdb);
2630a32f1ffSMichael Walle 
2640a32f1ffSMichael Walle int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
2650a32f1ffSMichael Walle {
2660a32f1ffSMichael Walle 	int new, ret;
2670a32f1ffSMichael Walle 
2680a32f1ffSMichael Walle 	ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
2690a32f1ffSMichael Walle 	if (ret < 0)
2700a32f1ffSMichael Walle 		return ret;
2710a32f1ffSMichael Walle 
2720a32f1ffSMichael Walle 	ret = __phy_read(phydev, MII_BCM54XX_RDB_DATA);
2730a32f1ffSMichael Walle 	if (ret < 0)
2740a32f1ffSMichael Walle 		return ret;
2750a32f1ffSMichael Walle 
2760a32f1ffSMichael Walle 	new = (ret & ~mask) | set;
2770a32f1ffSMichael Walle 	if (new == ret)
2780a32f1ffSMichael Walle 		return 0;
2790a32f1ffSMichael Walle 
2800a32f1ffSMichael Walle 	return __phy_write(phydev, MII_BCM54XX_RDB_DATA, new);
2810a32f1ffSMichael Walle }
2820a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_modify_rdb);
2830a32f1ffSMichael Walle 
2840a32f1ffSMichael Walle int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
2850a32f1ffSMichael Walle {
2860a32f1ffSMichael Walle 	int ret;
2870a32f1ffSMichael Walle 
2880a32f1ffSMichael Walle 	phy_lock_mdio_bus(phydev);
2890a32f1ffSMichael Walle 	ret = __bcm_phy_modify_rdb(phydev, rdb, mask, set);
2900a32f1ffSMichael Walle 	phy_unlock_mdio_bus(phydev);
2910a32f1ffSMichael Walle 
2920a32f1ffSMichael Walle 	return ret;
2930a32f1ffSMichael Walle }
2940a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_modify_rdb);
2950a32f1ffSMichael Walle 
296a1cba561SArun Parameswaran int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down)
297a1cba561SArun Parameswaran {
298a1cba561SArun Parameswaran 	int val;
299a1cba561SArun Parameswaran 
300a1cba561SArun Parameswaran 	if (dll_pwr_down) {
301a1cba561SArun Parameswaran 		val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
302a1cba561SArun Parameswaran 		if (val < 0)
303a1cba561SArun Parameswaran 			return val;
304a1cba561SArun Parameswaran 
305a1cba561SArun Parameswaran 		val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
306a1cba561SArun Parameswaran 		bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val);
307a1cba561SArun Parameswaran 	}
308a1cba561SArun Parameswaran 
309a1cba561SArun Parameswaran 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD);
310a1cba561SArun Parameswaran 	if (val < 0)
311a1cba561SArun Parameswaran 		return val;
312a1cba561SArun Parameswaran 
313a1cba561SArun Parameswaran 	/* Clear APD bits */
314a1cba561SArun Parameswaran 	val &= BCM_APD_CLR_MASK;
315a1cba561SArun Parameswaran 
316a1cba561SArun Parameswaran 	if (phydev->autoneg == AUTONEG_ENABLE)
317a1cba561SArun Parameswaran 		val |= BCM54XX_SHD_APD_EN;
318a1cba561SArun Parameswaran 	else
319a1cba561SArun Parameswaran 		val |= BCM_NO_ANEG_APD_EN;
320a1cba561SArun Parameswaran 
321a1cba561SArun Parameswaran 	/* Enable energy detect single link pulse for easy wakeup */
322a1cba561SArun Parameswaran 	val |= BCM_APD_SINGLELP_EN;
323a1cba561SArun Parameswaran 
324a1cba561SArun Parameswaran 	/* Enable Auto Power-Down (APD) for the PHY */
325a1cba561SArun Parameswaran 	return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);
326a1cba561SArun Parameswaran }
327a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_enable_apd);
328a1cba561SArun Parameswaran 
32999cec8a4SFlorian Fainelli int bcm_phy_set_eee(struct phy_device *phydev, bool enable)
330a1cba561SArun Parameswaran {
331a1cba561SArun Parameswaran 	int val;
332a1cba561SArun Parameswaran 
333a1cba561SArun Parameswaran 	/* Enable EEE at PHY level */
334a6d99fcdSRussell King 	val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL);
335a1cba561SArun Parameswaran 	if (val < 0)
336a1cba561SArun Parameswaran 		return val;
337a1cba561SArun Parameswaran 
33899cec8a4SFlorian Fainelli 	if (enable)
339a1cba561SArun Parameswaran 		val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X;
34099cec8a4SFlorian Fainelli 	else
34199cec8a4SFlorian Fainelli 		val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X);
342a1cba561SArun Parameswaran 
343a6d99fcdSRussell King 	phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val);
344a1cba561SArun Parameswaran 
345a1cba561SArun Parameswaran 	/* Advertise EEE */
346a6d99fcdSRussell King 	val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV);
347a1cba561SArun Parameswaran 	if (val < 0)
348a1cba561SArun Parameswaran 		return val;
349a1cba561SArun Parameswaran 
35099cec8a4SFlorian Fainelli 	if (enable)
351cda792c3SDoug Berger 		val |= (MDIO_EEE_100TX | MDIO_EEE_1000T);
35299cec8a4SFlorian Fainelli 	else
353cda792c3SDoug Berger 		val &= ~(MDIO_EEE_100TX | MDIO_EEE_1000T);
354a1cba561SArun Parameswaran 
355a6d99fcdSRussell King 	phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val);
356a1cba561SArun Parameswaran 
357a1cba561SArun Parameswaran 	return 0;
358a1cba561SArun Parameswaran }
35999cec8a4SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_set_eee);
360b89eb1fcSArun Parameswaran 
361d06f78c4SFlorian Fainelli int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count)
362d06f78c4SFlorian Fainelli {
363d06f78c4SFlorian Fainelli 	int val;
364d06f78c4SFlorian Fainelli 
365d06f78c4SFlorian Fainelli 	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
366d06f78c4SFlorian Fainelli 	if (val < 0)
367d06f78c4SFlorian Fainelli 		return val;
368d06f78c4SFlorian Fainelli 
369d06f78c4SFlorian Fainelli 	/* Check if wirespeed is enabled or not */
370d06f78c4SFlorian Fainelli 	if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) {
371d06f78c4SFlorian Fainelli 		*count = DOWNSHIFT_DEV_DISABLE;
372d06f78c4SFlorian Fainelli 		return 0;
373d06f78c4SFlorian Fainelli 	}
374d06f78c4SFlorian Fainelli 
375d06f78c4SFlorian Fainelli 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
376d06f78c4SFlorian Fainelli 	if (val < 0)
377d06f78c4SFlorian Fainelli 		return val;
378d06f78c4SFlorian Fainelli 
379d06f78c4SFlorian Fainelli 	/* Downgrade after one link attempt */
380d06f78c4SFlorian Fainelli 	if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) {
381d06f78c4SFlorian Fainelli 		*count = 1;
382d06f78c4SFlorian Fainelli 	} else {
383d06f78c4SFlorian Fainelli 		/* Downgrade after configured retry count */
384d06f78c4SFlorian Fainelli 		val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
385d06f78c4SFlorian Fainelli 		val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK;
386d06f78c4SFlorian Fainelli 		*count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET;
387d06f78c4SFlorian Fainelli 	}
388d06f78c4SFlorian Fainelli 
389d06f78c4SFlorian Fainelli 	return 0;
390d06f78c4SFlorian Fainelli }
391d06f78c4SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_downshift_get);
392d06f78c4SFlorian Fainelli 
393d06f78c4SFlorian Fainelli int bcm_phy_downshift_set(struct phy_device *phydev, u8 count)
394d06f78c4SFlorian Fainelli {
395d06f78c4SFlorian Fainelli 	int val = 0, ret = 0;
396d06f78c4SFlorian Fainelli 
397d06f78c4SFlorian Fainelli 	/* Range check the number given */
398d06f78c4SFlorian Fainelli 	if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET >
399d06f78c4SFlorian Fainelli 	    BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK &&
400d06f78c4SFlorian Fainelli 	    count != DOWNSHIFT_DEV_DEFAULT_COUNT) {
401d06f78c4SFlorian Fainelli 		return -ERANGE;
402d06f78c4SFlorian Fainelli 	}
403d06f78c4SFlorian Fainelli 
404d06f78c4SFlorian Fainelli 	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
405d06f78c4SFlorian Fainelli 	if (val < 0)
406d06f78c4SFlorian Fainelli 		return val;
407d06f78c4SFlorian Fainelli 
408d06f78c4SFlorian Fainelli 	/* Se the write enable bit */
409d06f78c4SFlorian Fainelli 	val |= MII_BCM54XX_AUXCTL_MISC_WREN;
410d06f78c4SFlorian Fainelli 
411d06f78c4SFlorian Fainelli 	if (count == DOWNSHIFT_DEV_DISABLE) {
412d06f78c4SFlorian Fainelli 		val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
413d06f78c4SFlorian Fainelli 		return bcm54xx_auxctl_write(phydev,
414d06f78c4SFlorian Fainelli 					    MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
415d06f78c4SFlorian Fainelli 					    val);
416d06f78c4SFlorian Fainelli 	} else {
417d06f78c4SFlorian Fainelli 		val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
418d06f78c4SFlorian Fainelli 		ret = bcm54xx_auxctl_write(phydev,
419d06f78c4SFlorian Fainelli 					   MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
420d06f78c4SFlorian Fainelli 					   val);
421d06f78c4SFlorian Fainelli 		if (ret < 0)
422d06f78c4SFlorian Fainelli 			return ret;
423d06f78c4SFlorian Fainelli 	}
424d06f78c4SFlorian Fainelli 
425d06f78c4SFlorian Fainelli 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
426d06f78c4SFlorian Fainelli 	val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK <<
427d06f78c4SFlorian Fainelli 		 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT |
428d06f78c4SFlorian Fainelli 		 BCM54XX_SHD_SCR2_WSPD_RTRY_DIS);
429d06f78c4SFlorian Fainelli 
430d06f78c4SFlorian Fainelli 	switch (count) {
431d06f78c4SFlorian Fainelli 	case 1:
432d06f78c4SFlorian Fainelli 		val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS;
433d06f78c4SFlorian Fainelli 		break;
434d06f78c4SFlorian Fainelli 	case DOWNSHIFT_DEV_DEFAULT_COUNT:
435d06f78c4SFlorian Fainelli 		val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
436d06f78c4SFlorian Fainelli 		break;
437d06f78c4SFlorian Fainelli 	default:
438d06f78c4SFlorian Fainelli 		val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) <<
439d06f78c4SFlorian Fainelli 			BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
440d06f78c4SFlorian Fainelli 		break;
441d06f78c4SFlorian Fainelli 	}
442d06f78c4SFlorian Fainelli 
443d06f78c4SFlorian Fainelli 	return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val);
444d06f78c4SFlorian Fainelli }
445d06f78c4SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_downshift_set);
446d06f78c4SFlorian Fainelli 
447820ee17bSFlorian Fainelli struct bcm_phy_hw_stat {
448820ee17bSFlorian Fainelli 	const char *string;
449820ee17bSFlorian Fainelli 	u8 reg;
450820ee17bSFlorian Fainelli 	u8 shift;
451820ee17bSFlorian Fainelli 	u8 bits;
452820ee17bSFlorian Fainelli };
453820ee17bSFlorian Fainelli 
454820ee17bSFlorian Fainelli /* Counters freeze at either 0xffff or 0xff, better than nothing */
455820ee17bSFlorian Fainelli static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = {
456820ee17bSFlorian Fainelli 	{ "phy_receive_errors", MII_BRCM_CORE_BASE12, 0, 16 },
457820ee17bSFlorian Fainelli 	{ "phy_serdes_ber_errors", MII_BRCM_CORE_BASE13, 8, 8 },
458820ee17bSFlorian Fainelli 	{ "phy_false_carrier_sense_errors", MII_BRCM_CORE_BASE13, 0, 8 },
459820ee17bSFlorian Fainelli 	{ "phy_local_rcvr_nok", MII_BRCM_CORE_BASE14, 8, 8 },
460820ee17bSFlorian Fainelli 	{ "phy_remote_rcv_nok", MII_BRCM_CORE_BASE14, 0, 8 },
461820ee17bSFlorian Fainelli };
462820ee17bSFlorian Fainelli 
463820ee17bSFlorian Fainelli int bcm_phy_get_sset_count(struct phy_device *phydev)
464820ee17bSFlorian Fainelli {
465820ee17bSFlorian Fainelli 	return ARRAY_SIZE(bcm_phy_hw_stats);
466820ee17bSFlorian Fainelli }
467820ee17bSFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count);
468820ee17bSFlorian Fainelli 
469820ee17bSFlorian Fainelli void bcm_phy_get_strings(struct phy_device *phydev, u8 *data)
470820ee17bSFlorian Fainelli {
471820ee17bSFlorian Fainelli 	unsigned int i;
472820ee17bSFlorian Fainelli 
473820ee17bSFlorian Fainelli 	for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
4748a17eefaSFlorian Fainelli 		strlcpy(data + i * ETH_GSTRING_LEN,
475820ee17bSFlorian Fainelli 			bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN);
476820ee17bSFlorian Fainelli }
477820ee17bSFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_strings);
478820ee17bSFlorian Fainelli 
479820ee17bSFlorian Fainelli /* Caller is supposed to provide appropriate storage for the library code to
480820ee17bSFlorian Fainelli  * access the shadow copy
481820ee17bSFlorian Fainelli  */
482820ee17bSFlorian Fainelli static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow,
483820ee17bSFlorian Fainelli 			    unsigned int i)
484820ee17bSFlorian Fainelli {
485820ee17bSFlorian Fainelli 	struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i];
486820ee17bSFlorian Fainelli 	int val;
487820ee17bSFlorian Fainelli 	u64 ret;
488820ee17bSFlorian Fainelli 
489820ee17bSFlorian Fainelli 	val = phy_read(phydev, stat.reg);
490820ee17bSFlorian Fainelli 	if (val < 0) {
4916c3442f5SJisheng Zhang 		ret = U64_MAX;
492820ee17bSFlorian Fainelli 	} else {
493820ee17bSFlorian Fainelli 		val >>= stat.shift;
494820ee17bSFlorian Fainelli 		val = val & ((1 << stat.bits) - 1);
495820ee17bSFlorian Fainelli 		shadow[i] += val;
496820ee17bSFlorian Fainelli 		ret = shadow[i];
497820ee17bSFlorian Fainelli 	}
498820ee17bSFlorian Fainelli 
499820ee17bSFlorian Fainelli 	return ret;
500820ee17bSFlorian Fainelli }
501820ee17bSFlorian Fainelli 
502820ee17bSFlorian Fainelli void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow,
503820ee17bSFlorian Fainelli 		       struct ethtool_stats *stats, u64 *data)
504820ee17bSFlorian Fainelli {
505820ee17bSFlorian Fainelli 	unsigned int i;
506820ee17bSFlorian Fainelli 
507820ee17bSFlorian Fainelli 	for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
508820ee17bSFlorian Fainelli 		data[i] = bcm_phy_get_stat(phydev, shadow, i);
509820ee17bSFlorian Fainelli }
510820ee17bSFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_stats);
511820ee17bSFlorian Fainelli 
512f878fe56SFlorian Fainelli void bcm_phy_r_rc_cal_reset(struct phy_device *phydev)
513f878fe56SFlorian Fainelli {
514f878fe56SFlorian Fainelli 	/* Reset R_CAL/RC_CAL Engine */
515f878fe56SFlorian Fainelli 	bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0010);
516f878fe56SFlorian Fainelli 
517f878fe56SFlorian Fainelli 	/* Disable Reset R_AL/RC_CAL Engine */
518f878fe56SFlorian Fainelli 	bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0000);
519f878fe56SFlorian Fainelli }
520f878fe56SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_r_rc_cal_reset);
521f878fe56SFlorian Fainelli 
522f878fe56SFlorian Fainelli int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev)
523f878fe56SFlorian Fainelli {
524f878fe56SFlorian Fainelli 	/* Increase VCO range to prevent unlocking problem of PLL at low
525f878fe56SFlorian Fainelli 	 * temp
526f878fe56SFlorian Fainelli 	 */
527f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048);
528f878fe56SFlorian Fainelli 
529f878fe56SFlorian Fainelli 	/* Change Ki to 011 */
530f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b);
531f878fe56SFlorian Fainelli 
532f878fe56SFlorian Fainelli 	/* Disable loading of TVCO buffer to bandgap, set bandgap trim
533f878fe56SFlorian Fainelli 	 * to 111
534f878fe56SFlorian Fainelli 	 */
535f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20);
536f878fe56SFlorian Fainelli 
537f878fe56SFlorian Fainelli 	/* Adjust bias current trim by -3 */
538f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, DSP_TAP10, 0x690b);
539f878fe56SFlorian Fainelli 
540f878fe56SFlorian Fainelli 	/* Switch to CORE_BASE1E */
541f878fe56SFlorian Fainelli 	phy_write(phydev, MII_BRCM_CORE_BASE1E, 0xd);
542f878fe56SFlorian Fainelli 
543f878fe56SFlorian Fainelli 	bcm_phy_r_rc_cal_reset(phydev);
544f878fe56SFlorian Fainelli 
545f878fe56SFlorian Fainelli 	/* write AFE_RXCONFIG_0 */
546f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19);
547f878fe56SFlorian Fainelli 
548f878fe56SFlorian Fainelli 	/* write AFE_RXCONFIG_1 */
549f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f);
550f878fe56SFlorian Fainelli 
551f878fe56SFlorian Fainelli 	/* write AFE_RX_LP_COUNTER */
552f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
553f878fe56SFlorian Fainelli 
554f878fe56SFlorian Fainelli 	/* write AFE_HPF_TRIM_OTHERS */
555f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b);
556f878fe56SFlorian Fainelli 
557f878fe56SFlorian Fainelli 	/* write AFTE_TX_CONFIG */
558f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800);
559f878fe56SFlorian Fainelli 
560f878fe56SFlorian Fainelli 	return 0;
561f878fe56SFlorian Fainelli }
562f878fe56SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_28nm_a0b0_afe_config_init);
563f878fe56SFlorian Fainelli 
564ab41ca34SMurali Krishna Policharla int bcm_phy_enable_jumbo(struct phy_device *phydev)
565ab41ca34SMurali Krishna Policharla {
566ab41ca34SMurali Krishna Policharla 	int ret;
567ab41ca34SMurali Krishna Policharla 
568ab41ca34SMurali Krishna Policharla 	ret = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL);
569ab41ca34SMurali Krishna Policharla 	if (ret < 0)
570ab41ca34SMurali Krishna Policharla 		return ret;
571ab41ca34SMurali Krishna Policharla 
572ab41ca34SMurali Krishna Policharla 	/* Enable extended length packet reception */
573ab41ca34SMurali Krishna Policharla 	ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
574ab41ca34SMurali Krishna Policharla 				   ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN);
575ab41ca34SMurali Krishna Policharla 	if (ret < 0)
576ab41ca34SMurali Krishna Policharla 		return ret;
577ab41ca34SMurali Krishna Policharla 
578ab41ca34SMurali Krishna Policharla 	/* Enable the elastic FIFO for raising the transmission limit from
579ab41ca34SMurali Krishna Policharla 	 * 4.5KB to 10KB, at the expense of an additional 16 ns in propagation
580ab41ca34SMurali Krishna Policharla 	 * latency.
581ab41ca34SMurali Krishna Policharla 	 */
582ab41ca34SMurali Krishna Policharla 	return phy_set_bits(phydev, MII_BCM54XX_ECR, MII_BCM54XX_ECR_FIFOE);
583ab41ca34SMurali Krishna Policharla }
584ab41ca34SMurali Krishna Policharla EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);
585ab41ca34SMurali Krishna Policharla 
58611ecf8c5SMichael Walle int __bcm_phy_enable_rdb_access(struct phy_device *phydev)
58711ecf8c5SMichael Walle {
58811ecf8c5SMichael Walle 	return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0);
58911ecf8c5SMichael Walle }
59011ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_enable_rdb_access);
59111ecf8c5SMichael Walle 
59211ecf8c5SMichael Walle int __bcm_phy_enable_legacy_access(struct phy_device *phydev)
59311ecf8c5SMichael Walle {
59411ecf8c5SMichael Walle 	return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087,
59511ecf8c5SMichael Walle 				   BCM54XX_ACCESS_MODE_LEGACY_EN);
59611ecf8c5SMichael Walle }
59711ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_enable_legacy_access);
59811ecf8c5SMichael Walle 
59911ecf8c5SMichael Walle static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb)
60011ecf8c5SMichael Walle {
60111ecf8c5SMichael Walle 	u16 mask, set;
60211ecf8c5SMichael Walle 	int ret;
60311ecf8c5SMichael Walle 
60411ecf8c5SMichael Walle 	/* Auto-negotiation must be enabled for cable diagnostics to work, but
60511ecf8c5SMichael Walle 	 * don't advertise any capabilities.
60611ecf8c5SMichael Walle 	 */
60711ecf8c5SMichael Walle 	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
60811ecf8c5SMichael Walle 	phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
60911ecf8c5SMichael Walle 	phy_write(phydev, MII_CTRL1000, 0);
61011ecf8c5SMichael Walle 
61111ecf8c5SMichael Walle 	phy_lock_mdio_bus(phydev);
61211ecf8c5SMichael Walle 	if (is_rdb) {
61311ecf8c5SMichael Walle 		ret = __bcm_phy_enable_legacy_access(phydev);
61411ecf8c5SMichael Walle 		if (ret)
61511ecf8c5SMichael Walle 			goto out;
61611ecf8c5SMichael Walle 	}
61711ecf8c5SMichael Walle 
61811ecf8c5SMichael Walle 	mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK;
61911ecf8c5SMichael Walle 	set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
62011ecf8c5SMichael Walle 	      FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK,
62111ecf8c5SMichael Walle 			 BCM54XX_ECD_CTRL_UNIT_CM);
62211ecf8c5SMichael Walle 
62311ecf8c5SMichael Walle 	ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set);
62411ecf8c5SMichael Walle 
62511ecf8c5SMichael Walle out:
62611ecf8c5SMichael Walle 	/* re-enable the RDB access even if there was an error */
62711ecf8c5SMichael Walle 	if (is_rdb)
62811ecf8c5SMichael Walle 		ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
62911ecf8c5SMichael Walle 
63011ecf8c5SMichael Walle 	phy_unlock_mdio_bus(phydev);
63111ecf8c5SMichael Walle 
63211ecf8c5SMichael Walle 	return ret;
63311ecf8c5SMichael Walle }
63411ecf8c5SMichael Walle 
63511ecf8c5SMichael Walle static int bcm_phy_cable_test_report_trans(int result)
63611ecf8c5SMichael Walle {
63711ecf8c5SMichael Walle 	switch (result) {
63811ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_OK:
63911ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
64011ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_OPEN:
64111ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
64211ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
64311ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
64411ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
64511ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
64611ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_INVALID:
64711ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_BUSY:
64811ecf8c5SMichael Walle 	default:
64911ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
65011ecf8c5SMichael Walle 	}
65111ecf8c5SMichael Walle }
65211ecf8c5SMichael Walle 
65311ecf8c5SMichael Walle static bool bcm_phy_distance_valid(int result)
65411ecf8c5SMichael Walle {
65511ecf8c5SMichael Walle 	switch (result) {
65611ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_OPEN:
65711ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
65811ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
65911ecf8c5SMichael Walle 		return true;
66011ecf8c5SMichael Walle 	}
66111ecf8c5SMichael Walle 	return false;
66211ecf8c5SMichael Walle }
66311ecf8c5SMichael Walle 
66411ecf8c5SMichael Walle static int bcm_phy_report_length(struct phy_device *phydev, int pair)
66511ecf8c5SMichael Walle {
66611ecf8c5SMichael Walle 	int val;
66711ecf8c5SMichael Walle 
66811ecf8c5SMichael Walle 	val = __bcm_phy_read_exp(phydev,
66911ecf8c5SMichael Walle 				 BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair);
67011ecf8c5SMichael Walle 	if (val < 0)
67111ecf8c5SMichael Walle 		return val;
67211ecf8c5SMichael Walle 
67311ecf8c5SMichael Walle 	if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID)
67411ecf8c5SMichael Walle 		return 0;
67511ecf8c5SMichael Walle 
67611ecf8c5SMichael Walle 	ethnl_cable_test_fault_length(phydev, pair, val);
67711ecf8c5SMichael Walle 
67811ecf8c5SMichael Walle 	return 0;
67911ecf8c5SMichael Walle }
68011ecf8c5SMichael Walle 
68111ecf8c5SMichael Walle static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
68211ecf8c5SMichael Walle 					  bool *finished, bool is_rdb)
68311ecf8c5SMichael Walle {
68411ecf8c5SMichael Walle 	int pair_a, pair_b, pair_c, pair_d, ret;
68511ecf8c5SMichael Walle 
68611ecf8c5SMichael Walle 	*finished = false;
68711ecf8c5SMichael Walle 
68811ecf8c5SMichael Walle 	phy_lock_mdio_bus(phydev);
68911ecf8c5SMichael Walle 
69011ecf8c5SMichael Walle 	if (is_rdb) {
69111ecf8c5SMichael Walle 		ret = __bcm_phy_enable_legacy_access(phydev);
69211ecf8c5SMichael Walle 		if (ret)
69311ecf8c5SMichael Walle 			goto out;
69411ecf8c5SMichael Walle 	}
69511ecf8c5SMichael Walle 
69611ecf8c5SMichael Walle 	ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL);
69711ecf8c5SMichael Walle 	if (ret < 0)
69811ecf8c5SMichael Walle 		goto out;
69911ecf8c5SMichael Walle 
70011ecf8c5SMichael Walle 	if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) {
70111ecf8c5SMichael Walle 		ret = 0;
70211ecf8c5SMichael Walle 		goto out;
70311ecf8c5SMichael Walle 	}
70411ecf8c5SMichael Walle 
70511ecf8c5SMichael Walle 	ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE);
70611ecf8c5SMichael Walle 	if (ret < 0)
70711ecf8c5SMichael Walle 		goto out;
70811ecf8c5SMichael Walle 
70911ecf8c5SMichael Walle 	pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret);
71011ecf8c5SMichael Walle 	pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret);
71111ecf8c5SMichael Walle 	pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret);
71211ecf8c5SMichael Walle 	pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret);
71311ecf8c5SMichael Walle 
71411ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
71511ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_a));
71611ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
71711ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_b));
71811ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
71911ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_c));
72011ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
72111ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_d));
72211ecf8c5SMichael Walle 
72311ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_a))
72411ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 0);
72511ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_b))
72611ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 1);
72711ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_c))
72811ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 2);
72911ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_d))
73011ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 3);
73111ecf8c5SMichael Walle 
73211ecf8c5SMichael Walle 	ret = 0;
73311ecf8c5SMichael Walle 	*finished = true;
73411ecf8c5SMichael Walle out:
73511ecf8c5SMichael Walle 	/* re-enable the RDB access even if there was an error */
73611ecf8c5SMichael Walle 	if (is_rdb)
73711ecf8c5SMichael Walle 		ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
73811ecf8c5SMichael Walle 
73911ecf8c5SMichael Walle 	phy_unlock_mdio_bus(phydev);
74011ecf8c5SMichael Walle 
74111ecf8c5SMichael Walle 	return ret;
74211ecf8c5SMichael Walle }
74311ecf8c5SMichael Walle 
74411ecf8c5SMichael Walle int bcm_phy_cable_test_start(struct phy_device *phydev)
74511ecf8c5SMichael Walle {
74611ecf8c5SMichael Walle 	return _bcm_phy_cable_test_start(phydev, false);
74711ecf8c5SMichael Walle }
74811ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start);
74911ecf8c5SMichael Walle 
75011ecf8c5SMichael Walle int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished)
75111ecf8c5SMichael Walle {
75211ecf8c5SMichael Walle 	return _bcm_phy_cable_test_get_status(phydev, finished, false);
75311ecf8c5SMichael Walle }
75411ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status);
75511ecf8c5SMichael Walle 
75611ecf8c5SMichael Walle /* We assume that all PHYs which support RDB access can be switched to legacy
75711ecf8c5SMichael Walle  * mode. If, in the future, this is not true anymore, we have to re-implement
75811ecf8c5SMichael Walle  * this with RDB access.
75911ecf8c5SMichael Walle  */
76011ecf8c5SMichael Walle int bcm_phy_cable_test_start_rdb(struct phy_device *phydev)
76111ecf8c5SMichael Walle {
76211ecf8c5SMichael Walle 	return _bcm_phy_cable_test_start(phydev, true);
76311ecf8c5SMichael Walle }
76411ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb);
76511ecf8c5SMichael Walle 
76611ecf8c5SMichael Walle int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
76711ecf8c5SMichael Walle 				      bool *finished)
76811ecf8c5SMichael Walle {
76911ecf8c5SMichael Walle 	return _bcm_phy_cable_test_get_status(phydev, finished, true);
77011ecf8c5SMichael Walle }
77111ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);
77211ecf8c5SMichael Walle 
773b89eb1fcSArun Parameswaran MODULE_DESCRIPTION("Broadcom PHY Library");
774b89eb1fcSArun Parameswaran MODULE_LICENSE("GPL v2");
775b89eb1fcSArun Parameswaran MODULE_AUTHOR("Broadcom Corporation");
776