xref: /openbmc/linux/drivers/net/phy/bcm-phy-lib.c (revision bd5736e1)
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>
98baddaa9SFlorian Fainelli #include <linux/etherdevice.h>
10a1cba561SArun Parameswaran #include <linux/export.h>
11a1cba561SArun Parameswaran #include <linux/mdio.h>
12b89eb1fcSArun Parameswaran #include <linux/module.h>
13a1cba561SArun Parameswaran #include <linux/phy.h>
14820ee17bSFlorian Fainelli #include <linux/ethtool.h>
1511ecf8c5SMichael Walle #include <linux/ethtool_netlink.h>
168baddaa9SFlorian Fainelli #include <linux/netdevice.h>
17a1cba561SArun Parameswaran 
18a1cba561SArun Parameswaran #define MII_BCM_CHANNEL_WIDTH     0x2000
19a1cba561SArun Parameswaran #define BCM_CL45VEN_EEE_ADV       0x3c
20a1cba561SArun Parameswaran 
__bcm_phy_write_exp(struct phy_device * phydev,u16 reg,u16 val)217d7e7bceSMichael Walle int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
227d7e7bceSMichael Walle {
237d7e7bceSMichael Walle 	int rc;
247d7e7bceSMichael Walle 
257d7e7bceSMichael Walle 	rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
267d7e7bceSMichael Walle 	if (rc < 0)
277d7e7bceSMichael Walle 		return rc;
287d7e7bceSMichael Walle 
297d7e7bceSMichael Walle 	return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
307d7e7bceSMichael Walle }
317d7e7bceSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_write_exp);
327d7e7bceSMichael Walle 
bcm_phy_write_exp(struct phy_device * phydev,u16 reg,u16 val)33a1cba561SArun Parameswaran int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
34a1cba561SArun Parameswaran {
35a1cba561SArun Parameswaran 	int rc;
36a1cba561SArun Parameswaran 
377d7e7bceSMichael Walle 	phy_lock_mdio_bus(phydev);
387d7e7bceSMichael Walle 	rc = __bcm_phy_write_exp(phydev, reg, val);
397d7e7bceSMichael Walle 	phy_unlock_mdio_bus(phydev);
40a1cba561SArun Parameswaran 
417d7e7bceSMichael Walle 	return rc;
42a1cba561SArun Parameswaran }
43a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_write_exp);
44a1cba561SArun Parameswaran 
__bcm_phy_read_exp(struct phy_device * phydev,u16 reg)457d7e7bceSMichael Walle int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
46a1cba561SArun Parameswaran {
47a1cba561SArun Parameswaran 	int val;
48a1cba561SArun Parameswaran 
497d7e7bceSMichael Walle 	val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
50a1cba561SArun Parameswaran 	if (val < 0)
51a1cba561SArun Parameswaran 		return val;
52a1cba561SArun Parameswaran 
537d7e7bceSMichael Walle 	val = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
54a1cba561SArun Parameswaran 
55a1cba561SArun Parameswaran 	/* Restore default value.  It's O.K. if this write fails. */
567d7e7bceSMichael Walle 	__phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
57a1cba561SArun Parameswaran 
58a1cba561SArun Parameswaran 	return val;
59a1cba561SArun Parameswaran }
607d7e7bceSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_read_exp);
617d7e7bceSMichael Walle 
bcm_phy_read_exp(struct phy_device * phydev,u16 reg)627d7e7bceSMichael Walle int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
637d7e7bceSMichael Walle {
647d7e7bceSMichael Walle 	int rc;
657d7e7bceSMichael Walle 
667d7e7bceSMichael Walle 	phy_lock_mdio_bus(phydev);
677d7e7bceSMichael Walle 	rc = __bcm_phy_read_exp(phydev, reg);
687d7e7bceSMichael Walle 	phy_unlock_mdio_bus(phydev);
697d7e7bceSMichael Walle 
707d7e7bceSMichael Walle 	return rc;
717d7e7bceSMichael Walle }
72a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_read_exp);
73a1cba561SArun Parameswaran 
__bcm_phy_modify_exp(struct phy_device * phydev,u16 reg,u16 mask,u16 set)74e184a907SMichael Walle int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
75e184a907SMichael Walle {
76e184a907SMichael Walle 	int new, ret;
77e184a907SMichael Walle 
78e184a907SMichael Walle 	ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
79e184a907SMichael Walle 	if (ret < 0)
80e184a907SMichael Walle 		return ret;
81e184a907SMichael Walle 
82e184a907SMichael Walle 	ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
83e184a907SMichael Walle 	if (ret < 0)
84e184a907SMichael Walle 		return ret;
85e184a907SMichael Walle 
86e184a907SMichael Walle 	new = (ret & ~mask) | set;
87e184a907SMichael Walle 	if (new == ret)
88e184a907SMichael Walle 		return 0;
89e184a907SMichael Walle 
90e184a907SMichael Walle 	return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new);
91e184a907SMichael Walle }
92e184a907SMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp);
93e184a907SMichael Walle 
bcm_phy_modify_exp(struct phy_device * phydev,u16 reg,u16 mask,u16 set)94e184a907SMichael Walle int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
95e184a907SMichael Walle {
96e184a907SMichael Walle 	int ret;
97e184a907SMichael Walle 
98e184a907SMichael Walle 	phy_lock_mdio_bus(phydev);
99e184a907SMichael Walle 	ret = __bcm_phy_modify_exp(phydev, reg, mask, set);
100e184a907SMichael Walle 	phy_unlock_mdio_bus(phydev);
101e184a907SMichael Walle 
102e184a907SMichael Walle 	return ret;
103e184a907SMichael Walle }
104e184a907SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_modify_exp);
105e184a907SMichael Walle 
bcm54xx_auxctl_read(struct phy_device * phydev,u16 regnum)1065519da87SFlorian Fainelli int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum)
1075519da87SFlorian Fainelli {
1085519da87SFlorian Fainelli 	/* The register must be written to both the Shadow Register Select and
1095519da87SFlorian Fainelli 	 * the Shadow Read Register Selector
1105519da87SFlorian Fainelli 	 */
111733a969aSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK |
1125519da87SFlorian Fainelli 		  regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT);
1135519da87SFlorian Fainelli 	return phy_read(phydev, MII_BCM54XX_AUX_CTL);
1145519da87SFlorian Fainelli }
1155519da87SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read);
1165519da87SFlorian Fainelli 
bcm54xx_auxctl_write(struct phy_device * phydev,u16 regnum,u16 val)1175519da87SFlorian Fainelli int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
1185519da87SFlorian Fainelli {
1195519da87SFlorian Fainelli 	return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
1205519da87SFlorian Fainelli }
1215519da87SFlorian Fainelli EXPORT_SYMBOL(bcm54xx_auxctl_write);
1225519da87SFlorian Fainelli 
bcm_phy_write_misc(struct phy_device * phydev,u16 reg,u16 chl,u16 val)123a1cba561SArun Parameswaran int bcm_phy_write_misc(struct phy_device *phydev,
124a1cba561SArun Parameswaran 		       u16 reg, u16 chl, u16 val)
125a1cba561SArun Parameswaran {
126a1cba561SArun Parameswaran 	int rc;
127a1cba561SArun Parameswaran 	int tmp;
128a1cba561SArun Parameswaran 
129a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
130a1cba561SArun Parameswaran 		       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
131a1cba561SArun Parameswaran 	if (rc < 0)
132a1cba561SArun Parameswaran 		return rc;
133a1cba561SArun Parameswaran 
134a1cba561SArun Parameswaran 	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
135a1cba561SArun Parameswaran 	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
136a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
137a1cba561SArun Parameswaran 	if (rc < 0)
138a1cba561SArun Parameswaran 		return rc;
139a1cba561SArun Parameswaran 
140a1cba561SArun Parameswaran 	tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
141a1cba561SArun Parameswaran 	rc = bcm_phy_write_exp(phydev, tmp, val);
142a1cba561SArun Parameswaran 
143a1cba561SArun Parameswaran 	return rc;
144a1cba561SArun Parameswaran }
145a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_write_misc);
146a1cba561SArun Parameswaran 
bcm_phy_read_misc(struct phy_device * phydev,u16 reg,u16 chl)147a1cba561SArun Parameswaran int bcm_phy_read_misc(struct phy_device *phydev,
148a1cba561SArun Parameswaran 		      u16 reg, u16 chl)
149a1cba561SArun Parameswaran {
150a1cba561SArun Parameswaran 	int rc;
151a1cba561SArun Parameswaran 	int tmp;
152a1cba561SArun Parameswaran 
153a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
154a1cba561SArun Parameswaran 		       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
155a1cba561SArun Parameswaran 	if (rc < 0)
156a1cba561SArun Parameswaran 		return rc;
157a1cba561SArun Parameswaran 
158a1cba561SArun Parameswaran 	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
159a1cba561SArun Parameswaran 	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
160a1cba561SArun Parameswaran 	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
161a1cba561SArun Parameswaran 	if (rc < 0)
162a1cba561SArun Parameswaran 		return rc;
163a1cba561SArun Parameswaran 
164a1cba561SArun Parameswaran 	tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
165a1cba561SArun Parameswaran 	rc = bcm_phy_read_exp(phydev, tmp);
166a1cba561SArun Parameswaran 
167a1cba561SArun Parameswaran 	return rc;
168a1cba561SArun Parameswaran }
169a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_read_misc);
170a1cba561SArun Parameswaran 
bcm_phy_ack_intr(struct phy_device * phydev)171a1cba561SArun Parameswaran int bcm_phy_ack_intr(struct phy_device *phydev)
172a1cba561SArun Parameswaran {
173a1cba561SArun Parameswaran 	int reg;
174a1cba561SArun Parameswaran 
175a1cba561SArun Parameswaran 	/* Clear pending interrupts.  */
176a1cba561SArun Parameswaran 	reg = phy_read(phydev, MII_BCM54XX_ISR);
177a1cba561SArun Parameswaran 	if (reg < 0)
178a1cba561SArun Parameswaran 		return reg;
179a1cba561SArun Parameswaran 
180a1cba561SArun Parameswaran 	return 0;
181a1cba561SArun Parameswaran }
182a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_ack_intr);
183a1cba561SArun Parameswaran 
bcm_phy_config_intr(struct phy_device * phydev)184a1cba561SArun Parameswaran int bcm_phy_config_intr(struct phy_device *phydev)
185a1cba561SArun Parameswaran {
18615772e4dSIoana Ciornei 	int reg, err;
187a1cba561SArun Parameswaran 
188a1cba561SArun Parameswaran 	reg = phy_read(phydev, MII_BCM54XX_ECR);
189a1cba561SArun Parameswaran 	if (reg < 0)
190a1cba561SArun Parameswaran 		return reg;
191a1cba561SArun Parameswaran 
19215772e4dSIoana Ciornei 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
19315772e4dSIoana Ciornei 		err = bcm_phy_ack_intr(phydev);
19415772e4dSIoana Ciornei 		if (err)
19515772e4dSIoana Ciornei 			return err;
196a1cba561SArun Parameswaran 
19715772e4dSIoana Ciornei 		reg &= ~MII_BCM54XX_ECR_IM;
19815772e4dSIoana Ciornei 		err = phy_write(phydev, MII_BCM54XX_ECR, reg);
19915772e4dSIoana Ciornei 	} else {
20015772e4dSIoana Ciornei 		reg |= MII_BCM54XX_ECR_IM;
20115772e4dSIoana Ciornei 		err = phy_write(phydev, MII_BCM54XX_ECR, reg);
20215772e4dSIoana Ciornei 		if (err)
20315772e4dSIoana Ciornei 			return err;
20415772e4dSIoana Ciornei 
20515772e4dSIoana Ciornei 		err = bcm_phy_ack_intr(phydev);
20615772e4dSIoana Ciornei 	}
20715772e4dSIoana Ciornei 	return err;
208a1cba561SArun Parameswaran }
209a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_config_intr);
210a1cba561SArun Parameswaran 
bcm_phy_handle_interrupt(struct phy_device * phydev)2114567d5c3SIoana Ciornei irqreturn_t bcm_phy_handle_interrupt(struct phy_device *phydev)
2124567d5c3SIoana Ciornei {
2134567d5c3SIoana Ciornei 	int irq_status, irq_mask;
2144567d5c3SIoana Ciornei 
2154567d5c3SIoana Ciornei 	irq_status = phy_read(phydev, MII_BCM54XX_ISR);
2164567d5c3SIoana Ciornei 	if (irq_status < 0) {
2174567d5c3SIoana Ciornei 		phy_error(phydev);
2184567d5c3SIoana Ciornei 		return IRQ_NONE;
2194567d5c3SIoana Ciornei 	}
2204567d5c3SIoana Ciornei 
2214567d5c3SIoana Ciornei 	/* If a bit from the Interrupt Mask register is set, the corresponding
2224567d5c3SIoana Ciornei 	 * bit from the Interrupt Status register is masked. So read the IMR
2234567d5c3SIoana Ciornei 	 * and then flip the bits to get the list of possible interrupt
2244567d5c3SIoana Ciornei 	 * sources.
2254567d5c3SIoana Ciornei 	 */
2264567d5c3SIoana Ciornei 	irq_mask = phy_read(phydev, MII_BCM54XX_IMR);
2274567d5c3SIoana Ciornei 	if (irq_mask < 0) {
2284567d5c3SIoana Ciornei 		phy_error(phydev);
2294567d5c3SIoana Ciornei 		return IRQ_NONE;
2304567d5c3SIoana Ciornei 	}
2314567d5c3SIoana Ciornei 	irq_mask = ~irq_mask;
2324567d5c3SIoana Ciornei 
2334567d5c3SIoana Ciornei 	if (!(irq_status & irq_mask))
2344567d5c3SIoana Ciornei 		return IRQ_NONE;
2354567d5c3SIoana Ciornei 
2364567d5c3SIoana Ciornei 	phy_trigger_machine(phydev);
2374567d5c3SIoana Ciornei 
2384567d5c3SIoana Ciornei 	return IRQ_HANDLED;
2394567d5c3SIoana Ciornei }
2404567d5c3SIoana Ciornei EXPORT_SYMBOL_GPL(bcm_phy_handle_interrupt);
2414567d5c3SIoana Ciornei 
bcm_phy_read_shadow(struct phy_device * phydev,u16 shadow)242a1cba561SArun Parameswaran int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow)
243a1cba561SArun Parameswaran {
244a1cba561SArun Parameswaran 	phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow));
245a1cba561SArun Parameswaran 	return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD));
246a1cba561SArun Parameswaran }
247a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_read_shadow);
248a1cba561SArun Parameswaran 
bcm_phy_write_shadow(struct phy_device * phydev,u16 shadow,u16 val)249a1cba561SArun Parameswaran int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow,
250a1cba561SArun Parameswaran 			 u16 val)
251a1cba561SArun Parameswaran {
252a1cba561SArun Parameswaran 	return phy_write(phydev, MII_BCM54XX_SHD,
253a1cba561SArun Parameswaran 			 MII_BCM54XX_SHD_WRITE |
254a1cba561SArun Parameswaran 			 MII_BCM54XX_SHD_VAL(shadow) |
255a1cba561SArun Parameswaran 			 MII_BCM54XX_SHD_DATA(val));
256a1cba561SArun Parameswaran }
257a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_write_shadow);
258a1cba561SArun Parameswaran 
__bcm_phy_read_rdb(struct phy_device * phydev,u16 rdb)2590a32f1ffSMichael Walle int __bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
2600a32f1ffSMichael Walle {
2610a32f1ffSMichael Walle 	int val;
2620a32f1ffSMichael Walle 
2630a32f1ffSMichael Walle 	val = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
2640a32f1ffSMichael Walle 	if (val < 0)
2650a32f1ffSMichael Walle 		return val;
2660a32f1ffSMichael Walle 
2670a32f1ffSMichael Walle 	return __phy_read(phydev, MII_BCM54XX_RDB_DATA);
2680a32f1ffSMichael Walle }
2690a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_read_rdb);
2700a32f1ffSMichael Walle 
bcm_phy_read_rdb(struct phy_device * phydev,u16 rdb)2710a32f1ffSMichael Walle int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
2720a32f1ffSMichael Walle {
2730a32f1ffSMichael Walle 	int ret;
2740a32f1ffSMichael Walle 
2750a32f1ffSMichael Walle 	phy_lock_mdio_bus(phydev);
2760a32f1ffSMichael Walle 	ret = __bcm_phy_read_rdb(phydev, rdb);
2770a32f1ffSMichael Walle 	phy_unlock_mdio_bus(phydev);
2780a32f1ffSMichael Walle 
2790a32f1ffSMichael Walle 	return ret;
2800a32f1ffSMichael Walle }
2810a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_read_rdb);
2820a32f1ffSMichael Walle 
__bcm_phy_write_rdb(struct phy_device * phydev,u16 rdb,u16 val)2830a32f1ffSMichael Walle int __bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
2840a32f1ffSMichael Walle {
2850a32f1ffSMichael Walle 	int ret;
2860a32f1ffSMichael Walle 
2870a32f1ffSMichael Walle 	ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
2880a32f1ffSMichael Walle 	if (ret < 0)
2890a32f1ffSMichael Walle 		return ret;
2900a32f1ffSMichael Walle 
2910a32f1ffSMichael Walle 	return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val);
2920a32f1ffSMichael Walle }
2930a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_write_rdb);
2940a32f1ffSMichael Walle 
bcm_phy_write_rdb(struct phy_device * phydev,u16 rdb,u16 val)2950a32f1ffSMichael Walle int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
2960a32f1ffSMichael Walle {
2970a32f1ffSMichael Walle 	int ret;
2980a32f1ffSMichael Walle 
2990a32f1ffSMichael Walle 	phy_lock_mdio_bus(phydev);
3000a32f1ffSMichael Walle 	ret = __bcm_phy_write_rdb(phydev, rdb, val);
3010a32f1ffSMichael Walle 	phy_unlock_mdio_bus(phydev);
3020a32f1ffSMichael Walle 
3030a32f1ffSMichael Walle 	return ret;
3040a32f1ffSMichael Walle }
3050a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_write_rdb);
3060a32f1ffSMichael Walle 
__bcm_phy_modify_rdb(struct phy_device * phydev,u16 rdb,u16 mask,u16 set)3070a32f1ffSMichael Walle int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
3080a32f1ffSMichael Walle {
3090a32f1ffSMichael Walle 	int new, ret;
3100a32f1ffSMichael Walle 
3110a32f1ffSMichael Walle 	ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
3120a32f1ffSMichael Walle 	if (ret < 0)
3130a32f1ffSMichael Walle 		return ret;
3140a32f1ffSMichael Walle 
3150a32f1ffSMichael Walle 	ret = __phy_read(phydev, MII_BCM54XX_RDB_DATA);
3160a32f1ffSMichael Walle 	if (ret < 0)
3170a32f1ffSMichael Walle 		return ret;
3180a32f1ffSMichael Walle 
3190a32f1ffSMichael Walle 	new = (ret & ~mask) | set;
3200a32f1ffSMichael Walle 	if (new == ret)
3210a32f1ffSMichael Walle 		return 0;
3220a32f1ffSMichael Walle 
3230a32f1ffSMichael Walle 	return __phy_write(phydev, MII_BCM54XX_RDB_DATA, new);
3240a32f1ffSMichael Walle }
3250a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(__bcm_phy_modify_rdb);
3260a32f1ffSMichael Walle 
bcm_phy_modify_rdb(struct phy_device * phydev,u16 rdb,u16 mask,u16 set)3270a32f1ffSMichael Walle int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
3280a32f1ffSMichael Walle {
3290a32f1ffSMichael Walle 	int ret;
3300a32f1ffSMichael Walle 
3310a32f1ffSMichael Walle 	phy_lock_mdio_bus(phydev);
3320a32f1ffSMichael Walle 	ret = __bcm_phy_modify_rdb(phydev, rdb, mask, set);
3330a32f1ffSMichael Walle 	phy_unlock_mdio_bus(phydev);
3340a32f1ffSMichael Walle 
3350a32f1ffSMichael Walle 	return ret;
3360a32f1ffSMichael Walle }
3370a32f1ffSMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_modify_rdb);
3380a32f1ffSMichael Walle 
bcm_phy_enable_apd(struct phy_device * phydev,bool dll_pwr_down)339a1cba561SArun Parameswaran int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down)
340a1cba561SArun Parameswaran {
341a1cba561SArun Parameswaran 	int val;
342a1cba561SArun Parameswaran 
343a1cba561SArun Parameswaran 	if (dll_pwr_down) {
344a1cba561SArun Parameswaran 		val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
345a1cba561SArun Parameswaran 		if (val < 0)
346a1cba561SArun Parameswaran 			return val;
347a1cba561SArun Parameswaran 
348a1cba561SArun Parameswaran 		val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
349a1cba561SArun Parameswaran 		bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val);
350a1cba561SArun Parameswaran 	}
351a1cba561SArun Parameswaran 
352a1cba561SArun Parameswaran 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD);
353a1cba561SArun Parameswaran 	if (val < 0)
354a1cba561SArun Parameswaran 		return val;
355a1cba561SArun Parameswaran 
356a1cba561SArun Parameswaran 	/* Clear APD bits */
357a1cba561SArun Parameswaran 	val &= BCM_APD_CLR_MASK;
358a1cba561SArun Parameswaran 
359a1cba561SArun Parameswaran 	if (phydev->autoneg == AUTONEG_ENABLE)
360a1cba561SArun Parameswaran 		val |= BCM54XX_SHD_APD_EN;
361a1cba561SArun Parameswaran 	else
362a1cba561SArun Parameswaran 		val |= BCM_NO_ANEG_APD_EN;
363a1cba561SArun Parameswaran 
364a1cba561SArun Parameswaran 	/* Enable energy detect single link pulse for easy wakeup */
365a1cba561SArun Parameswaran 	val |= BCM_APD_SINGLELP_EN;
366a1cba561SArun Parameswaran 
367a1cba561SArun Parameswaran 	/* Enable Auto Power-Down (APD) for the PHY */
368a1cba561SArun Parameswaran 	return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);
369a1cba561SArun Parameswaran }
370a1cba561SArun Parameswaran EXPORT_SYMBOL_GPL(bcm_phy_enable_apd);
371a1cba561SArun Parameswaran 
bcm_phy_set_eee(struct phy_device * phydev,bool enable)37299cec8a4SFlorian Fainelli int bcm_phy_set_eee(struct phy_device *phydev, bool enable)
373a1cba561SArun Parameswaran {
374c056d480SFlorian Fainelli 	int val, mask = 0;
375a1cba561SArun Parameswaran 
376a1cba561SArun Parameswaran 	/* Enable EEE at PHY level */
377a6d99fcdSRussell King 	val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL);
378a1cba561SArun Parameswaran 	if (val < 0)
379a1cba561SArun Parameswaran 		return val;
380a1cba561SArun Parameswaran 
38199cec8a4SFlorian Fainelli 	if (enable)
382a1cba561SArun Parameswaran 		val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X;
38399cec8a4SFlorian Fainelli 	else
38499cec8a4SFlorian Fainelli 		val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X);
385a1cba561SArun Parameswaran 
386a6d99fcdSRussell King 	phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val);
387a1cba561SArun Parameswaran 
388a1cba561SArun Parameswaran 	/* Advertise EEE */
389a6d99fcdSRussell King 	val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV);
390a1cba561SArun Parameswaran 	if (val < 0)
391a1cba561SArun Parameswaran 		return val;
392a1cba561SArun Parameswaran 
393c056d480SFlorian Fainelli 	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
394c056d480SFlorian Fainelli 			      phydev->supported))
395c056d480SFlorian Fainelli 		mask |= MDIO_EEE_1000T;
396c056d480SFlorian Fainelli 	if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
397c056d480SFlorian Fainelli 			      phydev->supported))
398c056d480SFlorian Fainelli 		mask |= MDIO_EEE_100TX;
399c056d480SFlorian Fainelli 
40099cec8a4SFlorian Fainelli 	if (enable)
401c056d480SFlorian Fainelli 		val |= mask;
40299cec8a4SFlorian Fainelli 	else
403c056d480SFlorian Fainelli 		val &= ~mask;
404a1cba561SArun Parameswaran 
405a6d99fcdSRussell King 	phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val);
406a1cba561SArun Parameswaran 
407a1cba561SArun Parameswaran 	return 0;
408a1cba561SArun Parameswaran }
40999cec8a4SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_set_eee);
410b89eb1fcSArun Parameswaran 
bcm_phy_downshift_get(struct phy_device * phydev,u8 * count)411d06f78c4SFlorian Fainelli int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count)
412d06f78c4SFlorian Fainelli {
413d06f78c4SFlorian Fainelli 	int val;
414d06f78c4SFlorian Fainelli 
415d06f78c4SFlorian Fainelli 	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
416d06f78c4SFlorian Fainelli 	if (val < 0)
417d06f78c4SFlorian Fainelli 		return val;
418d06f78c4SFlorian Fainelli 
419d06f78c4SFlorian Fainelli 	/* Check if wirespeed is enabled or not */
420d06f78c4SFlorian Fainelli 	if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) {
421d06f78c4SFlorian Fainelli 		*count = DOWNSHIFT_DEV_DISABLE;
422d06f78c4SFlorian Fainelli 		return 0;
423d06f78c4SFlorian Fainelli 	}
424d06f78c4SFlorian Fainelli 
425d06f78c4SFlorian Fainelli 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
426d06f78c4SFlorian Fainelli 	if (val < 0)
427d06f78c4SFlorian Fainelli 		return val;
428d06f78c4SFlorian Fainelli 
429d06f78c4SFlorian Fainelli 	/* Downgrade after one link attempt */
430d06f78c4SFlorian Fainelli 	if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) {
431d06f78c4SFlorian Fainelli 		*count = 1;
432d06f78c4SFlorian Fainelli 	} else {
433d06f78c4SFlorian Fainelli 		/* Downgrade after configured retry count */
434d06f78c4SFlorian Fainelli 		val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
435d06f78c4SFlorian Fainelli 		val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK;
436d06f78c4SFlorian Fainelli 		*count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET;
437d06f78c4SFlorian Fainelli 	}
438d06f78c4SFlorian Fainelli 
439d06f78c4SFlorian Fainelli 	return 0;
440d06f78c4SFlorian Fainelli }
441d06f78c4SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_downshift_get);
442d06f78c4SFlorian Fainelli 
bcm_phy_downshift_set(struct phy_device * phydev,u8 count)443d06f78c4SFlorian Fainelli int bcm_phy_downshift_set(struct phy_device *phydev, u8 count)
444d06f78c4SFlorian Fainelli {
445d06f78c4SFlorian Fainelli 	int val = 0, ret = 0;
446d06f78c4SFlorian Fainelli 
447d06f78c4SFlorian Fainelli 	/* Range check the number given */
448d06f78c4SFlorian Fainelli 	if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET >
449d06f78c4SFlorian Fainelli 	    BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK &&
450d06f78c4SFlorian Fainelli 	    count != DOWNSHIFT_DEV_DEFAULT_COUNT) {
451d06f78c4SFlorian Fainelli 		return -ERANGE;
452d06f78c4SFlorian Fainelli 	}
453d06f78c4SFlorian Fainelli 
454d06f78c4SFlorian Fainelli 	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
455d06f78c4SFlorian Fainelli 	if (val < 0)
456d06f78c4SFlorian Fainelli 		return val;
457d06f78c4SFlorian Fainelli 
458d06f78c4SFlorian Fainelli 	/* Se the write enable bit */
459d06f78c4SFlorian Fainelli 	val |= MII_BCM54XX_AUXCTL_MISC_WREN;
460d06f78c4SFlorian Fainelli 
461d06f78c4SFlorian Fainelli 	if (count == DOWNSHIFT_DEV_DISABLE) {
462d06f78c4SFlorian Fainelli 		val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
463d06f78c4SFlorian Fainelli 		return bcm54xx_auxctl_write(phydev,
464d06f78c4SFlorian Fainelli 					    MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
465d06f78c4SFlorian Fainelli 					    val);
466d06f78c4SFlorian Fainelli 	} else {
467d06f78c4SFlorian Fainelli 		val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
468d06f78c4SFlorian Fainelli 		ret = bcm54xx_auxctl_write(phydev,
469d06f78c4SFlorian Fainelli 					   MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
470d06f78c4SFlorian Fainelli 					   val);
471d06f78c4SFlorian Fainelli 		if (ret < 0)
472d06f78c4SFlorian Fainelli 			return ret;
473d06f78c4SFlorian Fainelli 	}
474d06f78c4SFlorian Fainelli 
475d06f78c4SFlorian Fainelli 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
476d06f78c4SFlorian Fainelli 	val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK <<
477d06f78c4SFlorian Fainelli 		 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT |
478d06f78c4SFlorian Fainelli 		 BCM54XX_SHD_SCR2_WSPD_RTRY_DIS);
479d06f78c4SFlorian Fainelli 
480d06f78c4SFlorian Fainelli 	switch (count) {
481d06f78c4SFlorian Fainelli 	case 1:
482d06f78c4SFlorian Fainelli 		val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS;
483d06f78c4SFlorian Fainelli 		break;
484d06f78c4SFlorian Fainelli 	case DOWNSHIFT_DEV_DEFAULT_COUNT:
485d06f78c4SFlorian Fainelli 		val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
486d06f78c4SFlorian Fainelli 		break;
487d06f78c4SFlorian Fainelli 	default:
488d06f78c4SFlorian Fainelli 		val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) <<
489d06f78c4SFlorian Fainelli 			BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
490d06f78c4SFlorian Fainelli 		break;
491d06f78c4SFlorian Fainelli 	}
492d06f78c4SFlorian Fainelli 
493d06f78c4SFlorian Fainelli 	return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val);
494d06f78c4SFlorian Fainelli }
495d06f78c4SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_downshift_set);
496d06f78c4SFlorian Fainelli 
497820ee17bSFlorian Fainelli struct bcm_phy_hw_stat {
498820ee17bSFlorian Fainelli 	const char *string;
499e8b6f79bSFlorian Fainelli 	int devad;
500e8b6f79bSFlorian Fainelli 	u16 reg;
501820ee17bSFlorian Fainelli 	u8 shift;
502820ee17bSFlorian Fainelli 	u8 bits;
503820ee17bSFlorian Fainelli };
504820ee17bSFlorian Fainelli 
505820ee17bSFlorian Fainelli /* Counters freeze at either 0xffff or 0xff, better than nothing */
506820ee17bSFlorian Fainelli static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = {
507e8b6f79bSFlorian Fainelli 	{ "phy_receive_errors", -1, MII_BRCM_CORE_BASE12, 0, 16 },
508e8b6f79bSFlorian Fainelli 	{ "phy_serdes_ber_errors", -1, MII_BRCM_CORE_BASE13, 8, 8 },
509e8b6f79bSFlorian Fainelli 	{ "phy_false_carrier_sense_errors", -1, MII_BRCM_CORE_BASE13, 0, 8 },
510e8b6f79bSFlorian Fainelli 	{ "phy_local_rcvr_nok", -1, MII_BRCM_CORE_BASE14, 8, 8 },
511e8b6f79bSFlorian Fainelli 	{ "phy_remote_rcv_nok", -1, MII_BRCM_CORE_BASE14, 0, 8 },
512e8b6f79bSFlorian Fainelli 	{ "phy_lpi_count", MDIO_MMD_AN, BRCM_CL45VEN_EEE_LPI_CNT, 0, 16 },
513820ee17bSFlorian Fainelli };
514820ee17bSFlorian Fainelli 
bcm_phy_get_sset_count(struct phy_device * phydev)515820ee17bSFlorian Fainelli int bcm_phy_get_sset_count(struct phy_device *phydev)
516820ee17bSFlorian Fainelli {
517820ee17bSFlorian Fainelli 	return ARRAY_SIZE(bcm_phy_hw_stats);
518820ee17bSFlorian Fainelli }
519820ee17bSFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count);
520820ee17bSFlorian Fainelli 
bcm_phy_get_strings(struct phy_device * phydev,u8 * data)521820ee17bSFlorian Fainelli void bcm_phy_get_strings(struct phy_device *phydev, u8 *data)
522820ee17bSFlorian Fainelli {
523820ee17bSFlorian Fainelli 	unsigned int i;
524820ee17bSFlorian Fainelli 
525820ee17bSFlorian Fainelli 	for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
526fb3ceec1SWolfram Sang 		strscpy(data + i * ETH_GSTRING_LEN,
527820ee17bSFlorian Fainelli 			bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN);
528820ee17bSFlorian Fainelli }
529820ee17bSFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_strings);
530820ee17bSFlorian Fainelli 
531820ee17bSFlorian Fainelli /* Caller is supposed to provide appropriate storage for the library code to
532820ee17bSFlorian Fainelli  * access the shadow copy
533820ee17bSFlorian Fainelli  */
bcm_phy_get_stat(struct phy_device * phydev,u64 * shadow,unsigned int i)534820ee17bSFlorian Fainelli static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow,
535820ee17bSFlorian Fainelli 			    unsigned int i)
536820ee17bSFlorian Fainelli {
537820ee17bSFlorian Fainelli 	struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i];
538820ee17bSFlorian Fainelli 	int val;
539820ee17bSFlorian Fainelli 	u64 ret;
540820ee17bSFlorian Fainelli 
541e8b6f79bSFlorian Fainelli 	if (stat.devad < 0)
542820ee17bSFlorian Fainelli 		val = phy_read(phydev, stat.reg);
543e8b6f79bSFlorian Fainelli 	else
544e8b6f79bSFlorian Fainelli 		val = phy_read_mmd(phydev, stat.devad, stat.reg);
545820ee17bSFlorian Fainelli 	if (val < 0) {
5466c3442f5SJisheng Zhang 		ret = U64_MAX;
547820ee17bSFlorian Fainelli 	} else {
548820ee17bSFlorian Fainelli 		val >>= stat.shift;
549820ee17bSFlorian Fainelli 		val = val & ((1 << stat.bits) - 1);
550820ee17bSFlorian Fainelli 		shadow[i] += val;
551820ee17bSFlorian Fainelli 		ret = shadow[i];
552820ee17bSFlorian Fainelli 	}
553820ee17bSFlorian Fainelli 
554820ee17bSFlorian Fainelli 	return ret;
555820ee17bSFlorian Fainelli }
556820ee17bSFlorian Fainelli 
bcm_phy_get_stats(struct phy_device * phydev,u64 * shadow,struct ethtool_stats * stats,u64 * data)557820ee17bSFlorian Fainelli void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow,
558820ee17bSFlorian Fainelli 		       struct ethtool_stats *stats, u64 *data)
559820ee17bSFlorian Fainelli {
560820ee17bSFlorian Fainelli 	unsigned int i;
561820ee17bSFlorian Fainelli 
562820ee17bSFlorian Fainelli 	for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
563820ee17bSFlorian Fainelli 		data[i] = bcm_phy_get_stat(phydev, shadow, i);
564820ee17bSFlorian Fainelli }
565820ee17bSFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_stats);
566820ee17bSFlorian Fainelli 
bcm_phy_r_rc_cal_reset(struct phy_device * phydev)567f878fe56SFlorian Fainelli void bcm_phy_r_rc_cal_reset(struct phy_device *phydev)
568f878fe56SFlorian Fainelli {
569f878fe56SFlorian Fainelli 	/* Reset R_CAL/RC_CAL Engine */
570f878fe56SFlorian Fainelli 	bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0010);
571f878fe56SFlorian Fainelli 
572f878fe56SFlorian Fainelli 	/* Disable Reset R_AL/RC_CAL Engine */
573f878fe56SFlorian Fainelli 	bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0000);
574f878fe56SFlorian Fainelli }
575f878fe56SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_r_rc_cal_reset);
576f878fe56SFlorian Fainelli 
bcm_phy_28nm_a0b0_afe_config_init(struct phy_device * phydev)577f878fe56SFlorian Fainelli int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev)
578f878fe56SFlorian Fainelli {
579f878fe56SFlorian Fainelli 	/* Increase VCO range to prevent unlocking problem of PLL at low
580f878fe56SFlorian Fainelli 	 * temp
581f878fe56SFlorian Fainelli 	 */
582f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048);
583f878fe56SFlorian Fainelli 
584f878fe56SFlorian Fainelli 	/* Change Ki to 011 */
585f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b);
586f878fe56SFlorian Fainelli 
587f878fe56SFlorian Fainelli 	/* Disable loading of TVCO buffer to bandgap, set bandgap trim
588f878fe56SFlorian Fainelli 	 * to 111
589f878fe56SFlorian Fainelli 	 */
590f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20);
591f878fe56SFlorian Fainelli 
592f878fe56SFlorian Fainelli 	/* Adjust bias current trim by -3 */
593f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, DSP_TAP10, 0x690b);
594f878fe56SFlorian Fainelli 
595f878fe56SFlorian Fainelli 	/* Switch to CORE_BASE1E */
596f878fe56SFlorian Fainelli 	phy_write(phydev, MII_BRCM_CORE_BASE1E, 0xd);
597f878fe56SFlorian Fainelli 
598f878fe56SFlorian Fainelli 	bcm_phy_r_rc_cal_reset(phydev);
599f878fe56SFlorian Fainelli 
600f878fe56SFlorian Fainelli 	/* write AFE_RXCONFIG_0 */
601f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19);
602f878fe56SFlorian Fainelli 
603f878fe56SFlorian Fainelli 	/* write AFE_RXCONFIG_1 */
604f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f);
605f878fe56SFlorian Fainelli 
606f878fe56SFlorian Fainelli 	/* write AFE_RX_LP_COUNTER */
607f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
608f878fe56SFlorian Fainelli 
609f878fe56SFlorian Fainelli 	/* write AFE_HPF_TRIM_OTHERS */
610f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b);
611f878fe56SFlorian Fainelli 
612f878fe56SFlorian Fainelli 	/* write AFTE_TX_CONFIG */
613f878fe56SFlorian Fainelli 	bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800);
614f878fe56SFlorian Fainelli 
615f878fe56SFlorian Fainelli 	return 0;
616f878fe56SFlorian Fainelli }
617f878fe56SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_28nm_a0b0_afe_config_init);
618f878fe56SFlorian Fainelli 
bcm_phy_enable_jumbo(struct phy_device * phydev)619ab41ca34SMurali Krishna Policharla int bcm_phy_enable_jumbo(struct phy_device *phydev)
620ab41ca34SMurali Krishna Policharla {
621ab41ca34SMurali Krishna Policharla 	int ret;
622ab41ca34SMurali Krishna Policharla 
623ab41ca34SMurali Krishna Policharla 	ret = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL);
624ab41ca34SMurali Krishna Policharla 	if (ret < 0)
625ab41ca34SMurali Krishna Policharla 		return ret;
626ab41ca34SMurali Krishna Policharla 
627ab41ca34SMurali Krishna Policharla 	/* Enable extended length packet reception */
628ab41ca34SMurali Krishna Policharla 	ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
629ab41ca34SMurali Krishna Policharla 				   ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN);
630ab41ca34SMurali Krishna Policharla 	if (ret < 0)
631ab41ca34SMurali Krishna Policharla 		return ret;
632ab41ca34SMurali Krishna Policharla 
633ab41ca34SMurali Krishna Policharla 	/* Enable the elastic FIFO for raising the transmission limit from
634ab41ca34SMurali Krishna Policharla 	 * 4.5KB to 10KB, at the expense of an additional 16 ns in propagation
635ab41ca34SMurali Krishna Policharla 	 * latency.
636ab41ca34SMurali Krishna Policharla 	 */
637ab41ca34SMurali Krishna Policharla 	return phy_set_bits(phydev, MII_BCM54XX_ECR, MII_BCM54XX_ECR_FIFOE);
638ab41ca34SMurali Krishna Policharla }
639ab41ca34SMurali Krishna Policharla EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);
640ab41ca34SMurali Krishna Policharla 
__bcm_phy_enable_rdb_access(struct phy_device * phydev)6413190ca3bSMichael Walle static int __bcm_phy_enable_rdb_access(struct phy_device *phydev)
64211ecf8c5SMichael Walle {
64311ecf8c5SMichael Walle 	return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0);
64411ecf8c5SMichael Walle }
64511ecf8c5SMichael Walle 
__bcm_phy_enable_legacy_access(struct phy_device * phydev)6463190ca3bSMichael Walle static int __bcm_phy_enable_legacy_access(struct phy_device *phydev)
64711ecf8c5SMichael Walle {
64811ecf8c5SMichael Walle 	return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087,
64911ecf8c5SMichael Walle 				   BCM54XX_ACCESS_MODE_LEGACY_EN);
65011ecf8c5SMichael Walle }
65111ecf8c5SMichael Walle 
_bcm_phy_cable_test_start(struct phy_device * phydev,bool is_rdb)65211ecf8c5SMichael Walle static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb)
65311ecf8c5SMichael Walle {
65411ecf8c5SMichael Walle 	u16 mask, set;
65511ecf8c5SMichael Walle 	int ret;
65611ecf8c5SMichael Walle 
65711ecf8c5SMichael Walle 	/* Auto-negotiation must be enabled for cable diagnostics to work, but
65811ecf8c5SMichael Walle 	 * don't advertise any capabilities.
65911ecf8c5SMichael Walle 	 */
66011ecf8c5SMichael Walle 	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
66111ecf8c5SMichael Walle 	phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
66211ecf8c5SMichael Walle 	phy_write(phydev, MII_CTRL1000, 0);
66311ecf8c5SMichael Walle 
66411ecf8c5SMichael Walle 	phy_lock_mdio_bus(phydev);
66511ecf8c5SMichael Walle 	if (is_rdb) {
66611ecf8c5SMichael Walle 		ret = __bcm_phy_enable_legacy_access(phydev);
66711ecf8c5SMichael Walle 		if (ret)
66811ecf8c5SMichael Walle 			goto out;
66911ecf8c5SMichael Walle 	}
67011ecf8c5SMichael Walle 
67111ecf8c5SMichael Walle 	mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK;
67211ecf8c5SMichael Walle 	set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
67311ecf8c5SMichael Walle 	      FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK,
67411ecf8c5SMichael Walle 			 BCM54XX_ECD_CTRL_UNIT_CM);
67511ecf8c5SMichael Walle 
67611ecf8c5SMichael Walle 	ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set);
67711ecf8c5SMichael Walle 
67811ecf8c5SMichael Walle out:
67911ecf8c5SMichael Walle 	/* re-enable the RDB access even if there was an error */
68011ecf8c5SMichael Walle 	if (is_rdb)
68111ecf8c5SMichael Walle 		ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
68211ecf8c5SMichael Walle 
68311ecf8c5SMichael Walle 	phy_unlock_mdio_bus(phydev);
68411ecf8c5SMichael Walle 
68511ecf8c5SMichael Walle 	return ret;
68611ecf8c5SMichael Walle }
68711ecf8c5SMichael Walle 
bcm_phy_cable_test_report_trans(int result)68811ecf8c5SMichael Walle static int bcm_phy_cable_test_report_trans(int result)
68911ecf8c5SMichael Walle {
69011ecf8c5SMichael Walle 	switch (result) {
69111ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_OK:
69211ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
69311ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_OPEN:
69411ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
69511ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
69611ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
69711ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
69811ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
69911ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_INVALID:
70011ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_BUSY:
70111ecf8c5SMichael Walle 	default:
70211ecf8c5SMichael Walle 		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
70311ecf8c5SMichael Walle 	}
70411ecf8c5SMichael Walle }
70511ecf8c5SMichael Walle 
bcm_phy_distance_valid(int result)70611ecf8c5SMichael Walle static bool bcm_phy_distance_valid(int result)
70711ecf8c5SMichael Walle {
70811ecf8c5SMichael Walle 	switch (result) {
70911ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_OPEN:
71011ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
71111ecf8c5SMichael Walle 	case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
71211ecf8c5SMichael Walle 		return true;
71311ecf8c5SMichael Walle 	}
71411ecf8c5SMichael Walle 	return false;
71511ecf8c5SMichael Walle }
71611ecf8c5SMichael Walle 
bcm_phy_report_length(struct phy_device * phydev,int pair)71711ecf8c5SMichael Walle static int bcm_phy_report_length(struct phy_device *phydev, int pair)
71811ecf8c5SMichael Walle {
71911ecf8c5SMichael Walle 	int val;
72011ecf8c5SMichael Walle 
72111ecf8c5SMichael Walle 	val = __bcm_phy_read_exp(phydev,
72211ecf8c5SMichael Walle 				 BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair);
72311ecf8c5SMichael Walle 	if (val < 0)
72411ecf8c5SMichael Walle 		return val;
72511ecf8c5SMichael Walle 
72611ecf8c5SMichael Walle 	if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID)
72711ecf8c5SMichael Walle 		return 0;
72811ecf8c5SMichael Walle 
72911ecf8c5SMichael Walle 	ethnl_cable_test_fault_length(phydev, pair, val);
73011ecf8c5SMichael Walle 
73111ecf8c5SMichael Walle 	return 0;
73211ecf8c5SMichael Walle }
73311ecf8c5SMichael Walle 
_bcm_phy_cable_test_get_status(struct phy_device * phydev,bool * finished,bool is_rdb)73411ecf8c5SMichael Walle static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
73511ecf8c5SMichael Walle 					  bool *finished, bool is_rdb)
73611ecf8c5SMichael Walle {
73711ecf8c5SMichael Walle 	int pair_a, pair_b, pair_c, pair_d, ret;
73811ecf8c5SMichael Walle 
73911ecf8c5SMichael Walle 	*finished = false;
74011ecf8c5SMichael Walle 
74111ecf8c5SMichael Walle 	phy_lock_mdio_bus(phydev);
74211ecf8c5SMichael Walle 
74311ecf8c5SMichael Walle 	if (is_rdb) {
74411ecf8c5SMichael Walle 		ret = __bcm_phy_enable_legacy_access(phydev);
74511ecf8c5SMichael Walle 		if (ret)
74611ecf8c5SMichael Walle 			goto out;
74711ecf8c5SMichael Walle 	}
74811ecf8c5SMichael Walle 
74911ecf8c5SMichael Walle 	ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL);
75011ecf8c5SMichael Walle 	if (ret < 0)
75111ecf8c5SMichael Walle 		goto out;
75211ecf8c5SMichael Walle 
75311ecf8c5SMichael Walle 	if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) {
75411ecf8c5SMichael Walle 		ret = 0;
75511ecf8c5SMichael Walle 		goto out;
75611ecf8c5SMichael Walle 	}
75711ecf8c5SMichael Walle 
75811ecf8c5SMichael Walle 	ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE);
75911ecf8c5SMichael Walle 	if (ret < 0)
76011ecf8c5SMichael Walle 		goto out;
76111ecf8c5SMichael Walle 
76211ecf8c5SMichael Walle 	pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret);
76311ecf8c5SMichael Walle 	pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret);
76411ecf8c5SMichael Walle 	pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret);
76511ecf8c5SMichael Walle 	pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret);
76611ecf8c5SMichael Walle 
76711ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
76811ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_a));
76911ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
77011ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_b));
77111ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
77211ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_c));
77311ecf8c5SMichael Walle 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
77411ecf8c5SMichael Walle 				bcm_phy_cable_test_report_trans(pair_d));
77511ecf8c5SMichael Walle 
77611ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_a))
77711ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 0);
77811ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_b))
77911ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 1);
78011ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_c))
78111ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 2);
78211ecf8c5SMichael Walle 	if (bcm_phy_distance_valid(pair_d))
78311ecf8c5SMichael Walle 		bcm_phy_report_length(phydev, 3);
78411ecf8c5SMichael Walle 
78511ecf8c5SMichael Walle 	ret = 0;
78611ecf8c5SMichael Walle 	*finished = true;
78711ecf8c5SMichael Walle out:
78811ecf8c5SMichael Walle 	/* re-enable the RDB access even if there was an error */
78911ecf8c5SMichael Walle 	if (is_rdb)
79011ecf8c5SMichael Walle 		ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
79111ecf8c5SMichael Walle 
79211ecf8c5SMichael Walle 	phy_unlock_mdio_bus(phydev);
79311ecf8c5SMichael Walle 
79411ecf8c5SMichael Walle 	return ret;
79511ecf8c5SMichael Walle }
79611ecf8c5SMichael Walle 
bcm_phy_cable_test_start(struct phy_device * phydev)79711ecf8c5SMichael Walle int bcm_phy_cable_test_start(struct phy_device *phydev)
79811ecf8c5SMichael Walle {
79911ecf8c5SMichael Walle 	return _bcm_phy_cable_test_start(phydev, false);
80011ecf8c5SMichael Walle }
80111ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start);
80211ecf8c5SMichael Walle 
bcm_phy_cable_test_get_status(struct phy_device * phydev,bool * finished)80311ecf8c5SMichael Walle int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished)
80411ecf8c5SMichael Walle {
80511ecf8c5SMichael Walle 	return _bcm_phy_cable_test_get_status(phydev, finished, false);
80611ecf8c5SMichael Walle }
80711ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status);
80811ecf8c5SMichael Walle 
80911ecf8c5SMichael Walle /* We assume that all PHYs which support RDB access can be switched to legacy
81011ecf8c5SMichael Walle  * mode. If, in the future, this is not true anymore, we have to re-implement
81111ecf8c5SMichael Walle  * this with RDB access.
81211ecf8c5SMichael Walle  */
bcm_phy_cable_test_start_rdb(struct phy_device * phydev)81311ecf8c5SMichael Walle int bcm_phy_cable_test_start_rdb(struct phy_device *phydev)
81411ecf8c5SMichael Walle {
81511ecf8c5SMichael Walle 	return _bcm_phy_cable_test_start(phydev, true);
81611ecf8c5SMichael Walle }
81711ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb);
81811ecf8c5SMichael Walle 
bcm_phy_cable_test_get_status_rdb(struct phy_device * phydev,bool * finished)81911ecf8c5SMichael Walle int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
82011ecf8c5SMichael Walle 				      bool *finished)
82111ecf8c5SMichael Walle {
82211ecf8c5SMichael Walle 	return _bcm_phy_cable_test_get_status(phydev, finished, true);
82311ecf8c5SMichael Walle }
82411ecf8c5SMichael Walle EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);
82511ecf8c5SMichael Walle 
8268baddaa9SFlorian Fainelli #define BCM54XX_WOL_SUPPORTED_MASK	(WAKE_UCAST | \
8278baddaa9SFlorian Fainelli 					 WAKE_MCAST | \
8288baddaa9SFlorian Fainelli 					 WAKE_BCAST | \
8298baddaa9SFlorian Fainelli 					 WAKE_MAGIC | \
8308baddaa9SFlorian Fainelli 					 WAKE_MAGICSECURE)
8318baddaa9SFlorian Fainelli 
bcm_phy_set_wol(struct phy_device * phydev,struct ethtool_wolinfo * wol)8328baddaa9SFlorian Fainelli int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
8338baddaa9SFlorian Fainelli {
8348baddaa9SFlorian Fainelli 	struct net_device *ndev = phydev->attached_dev;
8358baddaa9SFlorian Fainelli 	u8 da[ETH_ALEN], mask[ETH_ALEN];
8368baddaa9SFlorian Fainelli 	unsigned int i;
8378baddaa9SFlorian Fainelli 	u16 ctl;
8388baddaa9SFlorian Fainelli 	int ret;
8398baddaa9SFlorian Fainelli 
8408baddaa9SFlorian Fainelli 	/* Allow a MAC driver to play through its own Wake-on-LAN
8418baddaa9SFlorian Fainelli 	 * implementation
8428baddaa9SFlorian Fainelli 	 */
8438baddaa9SFlorian Fainelli 	if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK)
8448baddaa9SFlorian Fainelli 		return -EOPNOTSUPP;
8458baddaa9SFlorian Fainelli 
8468baddaa9SFlorian Fainelli 	/* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's
8478baddaa9SFlorian Fainelli 	 * ethtool only supports 6, for now.
8488baddaa9SFlorian Fainelli 	 */
8498baddaa9SFlorian Fainelli 	BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN);
8508baddaa9SFlorian Fainelli 
8518baddaa9SFlorian Fainelli 	/* Clear previous interrupts */
8528baddaa9SFlorian Fainelli 	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
8538baddaa9SFlorian Fainelli 	if (ret < 0)
8548baddaa9SFlorian Fainelli 		return ret;
8558baddaa9SFlorian Fainelli 
8568baddaa9SFlorian Fainelli 	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
8578baddaa9SFlorian Fainelli 	if (ret < 0)
8588baddaa9SFlorian Fainelli 		return ret;
8598baddaa9SFlorian Fainelli 
8608baddaa9SFlorian Fainelli 	ctl = ret;
8618baddaa9SFlorian Fainelli 
8628baddaa9SFlorian Fainelli 	if (!wol->wolopts) {
8638baddaa9SFlorian Fainelli 		if (phy_interrupt_is_valid(phydev))
8648baddaa9SFlorian Fainelli 			disable_irq_wake(phydev->irq);
8658baddaa9SFlorian Fainelli 
8668baddaa9SFlorian Fainelli 		/* Leave all interrupts disabled */
8678baddaa9SFlorian Fainelli 		ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK,
8688baddaa9SFlorian Fainelli 					BCM54XX_WOL_ALL_INTRS);
8698baddaa9SFlorian Fainelli 		if (ret < 0)
8708baddaa9SFlorian Fainelli 			return ret;
8718baddaa9SFlorian Fainelli 
8728baddaa9SFlorian Fainelli 		/* Disable the global Wake-on-LAN enable bit */
8738baddaa9SFlorian Fainelli 		ctl &= ~BCM54XX_WOL_EN;
8748baddaa9SFlorian Fainelli 
8758baddaa9SFlorian Fainelli 		return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
8768baddaa9SFlorian Fainelli 	}
8778baddaa9SFlorian Fainelli 
8788baddaa9SFlorian Fainelli 	/* Clear the previously configured mode and mask mode for Wake-on-LAN */
8798baddaa9SFlorian Fainelli 	ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT);
8808baddaa9SFlorian Fainelli 	ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT);
8818baddaa9SFlorian Fainelli 	ctl &= ~BCM54XX_WOL_DIR_PKT_EN;
8828baddaa9SFlorian Fainelli 	ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT);
8838baddaa9SFlorian Fainelli 
8848baddaa9SFlorian Fainelli 	/* When using WAKE_MAGIC, we program the magic pattern filter to match
8858baddaa9SFlorian Fainelli 	 * the device's MAC address and we accept any MAC DA in the Ethernet
8868baddaa9SFlorian Fainelli 	 * frame.
8878baddaa9SFlorian Fainelli 	 *
8888baddaa9SFlorian Fainelli 	 * When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the
8898baddaa9SFlorian Fainelli 	 * following:
8908baddaa9SFlorian Fainelli 	 * - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match
8918baddaa9SFlorian Fainelli 	 * - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care
8928baddaa9SFlorian Fainelli 	 * - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match
8938baddaa9SFlorian Fainelli 	 *
8948baddaa9SFlorian Fainelli 	 * Note that the Broadcast MAC DA is inherently going to match the
8958baddaa9SFlorian Fainelli 	 * multicast pattern being matched.
8968baddaa9SFlorian Fainelli 	 */
8978baddaa9SFlorian Fainelli 	memset(mask, 0, sizeof(mask));
8988baddaa9SFlorian Fainelli 
8998baddaa9SFlorian Fainelli 	if (wol->wolopts & WAKE_MCAST) {
9008baddaa9SFlorian Fainelli 		memset(da, 0, sizeof(da));
9018baddaa9SFlorian Fainelli 		memset(mask, 0xff, sizeof(mask));
9028baddaa9SFlorian Fainelli 		da[0] = 0x01;
9038baddaa9SFlorian Fainelli 		mask[0] = ~da[0];
9048baddaa9SFlorian Fainelli 	} else {
9058baddaa9SFlorian Fainelli 		if (wol->wolopts & WAKE_UCAST) {
9068baddaa9SFlorian Fainelli 			ether_addr_copy(da, ndev->dev_addr);
9078baddaa9SFlorian Fainelli 		} else if (wol->wolopts & WAKE_BCAST) {
9088baddaa9SFlorian Fainelli 			eth_broadcast_addr(da);
9098baddaa9SFlorian Fainelli 		} else if (wol->wolopts & WAKE_MAGICSECURE) {
9108baddaa9SFlorian Fainelli 			ether_addr_copy(da, wol->sopass);
9118baddaa9SFlorian Fainelli 		} else if (wol->wolopts & WAKE_MAGIC) {
9128baddaa9SFlorian Fainelli 			memset(da, 0, sizeof(da));
9138baddaa9SFlorian Fainelli 			memset(mask, 0xff, sizeof(mask));
9148baddaa9SFlorian Fainelli 		}
9158baddaa9SFlorian Fainelli 	}
9168baddaa9SFlorian Fainelli 
9178baddaa9SFlorian Fainelli 	for (i = 0; i < ETH_ALEN / 2; i++) {
9188baddaa9SFlorian Fainelli 		if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) {
9198baddaa9SFlorian Fainelli 			ret = bcm_phy_write_exp(phydev,
9208baddaa9SFlorian Fainelli 						BCM54XX_WOL_MPD_DATA1(2 - i),
9218baddaa9SFlorian Fainelli 						ndev->dev_addr[i * 2] << 8 |
9228baddaa9SFlorian Fainelli 						ndev->dev_addr[i * 2 + 1]);
9238baddaa9SFlorian Fainelli 			if (ret < 0)
9248baddaa9SFlorian Fainelli 				return ret;
9258baddaa9SFlorian Fainelli 		}
9268baddaa9SFlorian Fainelli 
9278baddaa9SFlorian Fainelli 		ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i),
9288baddaa9SFlorian Fainelli 					da[i * 2] << 8 | da[i * 2 + 1]);
9298baddaa9SFlorian Fainelli 		if (ret < 0)
9308baddaa9SFlorian Fainelli 			return ret;
9318baddaa9SFlorian Fainelli 
9328baddaa9SFlorian Fainelli 		ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i),
9338baddaa9SFlorian Fainelli 					mask[i * 2] << 8 | mask[i * 2 + 1]);
9348baddaa9SFlorian Fainelli 		if (ret)
9358baddaa9SFlorian Fainelli 			return ret;
9368baddaa9SFlorian Fainelli 	}
9378baddaa9SFlorian Fainelli 
9388baddaa9SFlorian Fainelli 	if (wol->wolopts & WAKE_MAGICSECURE) {
9398baddaa9SFlorian Fainelli 		ctl |= BCM54XX_WOL_SECKEY_OPT_6B <<
9408baddaa9SFlorian Fainelli 		       BCM54XX_WOL_SECKEY_OPT_SHIFT;
9418baddaa9SFlorian Fainelli 		ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT;
9428baddaa9SFlorian Fainelli 		ctl |= BCM54XX_WOL_MASK_MODE_DA_FF <<
9438baddaa9SFlorian Fainelli 		       BCM54XX_WOL_MASK_MODE_SHIFT;
9448baddaa9SFlorian Fainelli 	} else {
9458baddaa9SFlorian Fainelli 		if (wol->wolopts & WAKE_MAGIC)
9468baddaa9SFlorian Fainelli 			ctl |= BCM54XX_WOL_MODE_SINGLE_MPD;
9478baddaa9SFlorian Fainelli 		else
9488baddaa9SFlorian Fainelli 			ctl |= BCM54XX_WOL_DIR_PKT_EN;
9498baddaa9SFlorian Fainelli 		ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY <<
9508baddaa9SFlorian Fainelli 		       BCM54XX_WOL_MASK_MODE_SHIFT;
9518baddaa9SFlorian Fainelli 	}
9528baddaa9SFlorian Fainelli 
9538baddaa9SFlorian Fainelli 	/* Globally enable Wake-on-LAN */
9548baddaa9SFlorian Fainelli 	ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK;
9558baddaa9SFlorian Fainelli 
9568baddaa9SFlorian Fainelli 	ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
9578baddaa9SFlorian Fainelli 	if (ret < 0)
9588baddaa9SFlorian Fainelli 		return ret;
9598baddaa9SFlorian Fainelli 
9608baddaa9SFlorian Fainelli 	/* Enable WOL interrupt on LED4 */
9618baddaa9SFlorian Fainelli 	ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL);
9628baddaa9SFlorian Fainelli 	if (ret < 0)
9638baddaa9SFlorian Fainelli 		return ret;
9648baddaa9SFlorian Fainelli 
9658baddaa9SFlorian Fainelli 	ret |= BCM54XX_LED4_SEL_INTR;
9668baddaa9SFlorian Fainelli 	ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret);
9678baddaa9SFlorian Fainelli 	if (ret < 0)
9688baddaa9SFlorian Fainelli 		return ret;
9698baddaa9SFlorian Fainelli 
9708baddaa9SFlorian Fainelli 	/* Enable all Wake-on-LAN interrupt sources */
9718baddaa9SFlorian Fainelli 	ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0);
9728baddaa9SFlorian Fainelli 	if (ret < 0)
9738baddaa9SFlorian Fainelli 		return ret;
9748baddaa9SFlorian Fainelli 
9758baddaa9SFlorian Fainelli 	if (phy_interrupt_is_valid(phydev))
9768baddaa9SFlorian Fainelli 		enable_irq_wake(phydev->irq);
9778baddaa9SFlorian Fainelli 
9788baddaa9SFlorian Fainelli 	return 0;
9798baddaa9SFlorian Fainelli }
9808baddaa9SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_set_wol);
9818baddaa9SFlorian Fainelli 
bcm_phy_get_wol(struct phy_device * phydev,struct ethtool_wolinfo * wol)9828baddaa9SFlorian Fainelli void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
9838baddaa9SFlorian Fainelli {
9848baddaa9SFlorian Fainelli 	struct net_device *ndev = phydev->attached_dev;
9858baddaa9SFlorian Fainelli 	u8 da[ETH_ALEN];
9868baddaa9SFlorian Fainelli 	unsigned int i;
9878baddaa9SFlorian Fainelli 	int ret;
9888baddaa9SFlorian Fainelli 	u16 ctl;
9898baddaa9SFlorian Fainelli 
9908baddaa9SFlorian Fainelli 	wol->supported = BCM54XX_WOL_SUPPORTED_MASK;
9918baddaa9SFlorian Fainelli 	wol->wolopts = 0;
9928baddaa9SFlorian Fainelli 
9938baddaa9SFlorian Fainelli 	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
9948baddaa9SFlorian Fainelli 	if (ret < 0)
9958baddaa9SFlorian Fainelli 		return;
9968baddaa9SFlorian Fainelli 
9978baddaa9SFlorian Fainelli 	ctl = ret;
9988baddaa9SFlorian Fainelli 
9998baddaa9SFlorian Fainelli 	if (!(ctl & BCM54XX_WOL_EN))
10008baddaa9SFlorian Fainelli 		return;
10018baddaa9SFlorian Fainelli 
10028baddaa9SFlorian Fainelli 	for (i = 0; i < sizeof(da) / 2; i++) {
10038baddaa9SFlorian Fainelli 		ret = bcm_phy_read_exp(phydev,
10048baddaa9SFlorian Fainelli 				       BCM54XX_WOL_MPD_DATA2(2 - i));
10058baddaa9SFlorian Fainelli 		if (ret < 0)
10068baddaa9SFlorian Fainelli 			return;
10078baddaa9SFlorian Fainelli 
10088baddaa9SFlorian Fainelli 		da[i * 2] = ret >> 8;
10098baddaa9SFlorian Fainelli 		da[i * 2 + 1] = ret & 0xff;
10108baddaa9SFlorian Fainelli 	}
10118baddaa9SFlorian Fainelli 
10128baddaa9SFlorian Fainelli 	if (ctl & BCM54XX_WOL_DIR_PKT_EN) {
10138baddaa9SFlorian Fainelli 		if (is_broadcast_ether_addr(da))
10148baddaa9SFlorian Fainelli 			wol->wolopts |= WAKE_BCAST;
10158baddaa9SFlorian Fainelli 		else if (is_multicast_ether_addr(da))
10168baddaa9SFlorian Fainelli 			wol->wolopts |= WAKE_MCAST;
10178baddaa9SFlorian Fainelli 		else if (ether_addr_equal(da, ndev->dev_addr))
10188baddaa9SFlorian Fainelli 			wol->wolopts |= WAKE_UCAST;
10198baddaa9SFlorian Fainelli 	} else {
10208baddaa9SFlorian Fainelli 		ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK;
10218baddaa9SFlorian Fainelli 		switch (ctl) {
10228baddaa9SFlorian Fainelli 		case BCM54XX_WOL_MODE_SINGLE_MPD:
10238baddaa9SFlorian Fainelli 			wol->wolopts |= WAKE_MAGIC;
10248baddaa9SFlorian Fainelli 			break;
10258baddaa9SFlorian Fainelli 		case BCM54XX_WOL_MODE_SINGLE_MPDSEC:
10268baddaa9SFlorian Fainelli 			wol->wolopts |= WAKE_MAGICSECURE;
10278baddaa9SFlorian Fainelli 			memcpy(wol->sopass, da, sizeof(da));
10288baddaa9SFlorian Fainelli 			break;
10298baddaa9SFlorian Fainelli 		default:
10308baddaa9SFlorian Fainelli 			break;
10318baddaa9SFlorian Fainelli 		}
10328baddaa9SFlorian Fainelli 	}
10338baddaa9SFlorian Fainelli }
10348baddaa9SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_get_wol);
10358baddaa9SFlorian Fainelli 
bcm_phy_wol_isr(int irq,void * dev_id)10364781e965SFlorian Fainelli irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id)
10374781e965SFlorian Fainelli {
10384781e965SFlorian Fainelli 	return IRQ_HANDLED;
10394781e965SFlorian Fainelli }
10404781e965SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_wol_isr);
10414781e965SFlorian Fainelli 
bcm_phy_led_brightness_set(struct phy_device * phydev,u8 index,enum led_brightness value)1042*bd5736e1SFlorian Fainelli int bcm_phy_led_brightness_set(struct phy_device *phydev,
1043*bd5736e1SFlorian Fainelli 			       u8 index, enum led_brightness value)
1044*bd5736e1SFlorian Fainelli {
1045*bd5736e1SFlorian Fainelli 	u8 led_num;
1046*bd5736e1SFlorian Fainelli 	int ret;
1047*bd5736e1SFlorian Fainelli 	u16 reg;
1048*bd5736e1SFlorian Fainelli 
1049*bd5736e1SFlorian Fainelli 	if (index >= 4)
1050*bd5736e1SFlorian Fainelli 		return -EINVAL;
1051*bd5736e1SFlorian Fainelli 
1052*bd5736e1SFlorian Fainelli 	/* Two LEDS per register */
1053*bd5736e1SFlorian Fainelli 	led_num = index % 2;
1054*bd5736e1SFlorian Fainelli 	reg = index >= 2 ? BCM54XX_SHD_LEDS2 : BCM54XX_SHD_LEDS1;
1055*bd5736e1SFlorian Fainelli 
1056*bd5736e1SFlorian Fainelli 	ret = bcm_phy_read_shadow(phydev, reg);
1057*bd5736e1SFlorian Fainelli 	if (ret < 0)
1058*bd5736e1SFlorian Fainelli 		return ret;
1059*bd5736e1SFlorian Fainelli 
1060*bd5736e1SFlorian Fainelli 	ret &= ~(BCM_LED_SRC_MASK << BCM54XX_SHD_LEDS_SHIFT(led_num));
1061*bd5736e1SFlorian Fainelli 	if (value == LED_OFF)
1062*bd5736e1SFlorian Fainelli 		ret |= BCM_LED_SRC_OFF << BCM54XX_SHD_LEDS_SHIFT(led_num);
1063*bd5736e1SFlorian Fainelli 	else
1064*bd5736e1SFlorian Fainelli 		ret |= BCM_LED_SRC_ON << BCM54XX_SHD_LEDS_SHIFT(led_num);
1065*bd5736e1SFlorian Fainelli 	return bcm_phy_write_shadow(phydev, reg, ret);
1066*bd5736e1SFlorian Fainelli }
1067*bd5736e1SFlorian Fainelli EXPORT_SYMBOL_GPL(bcm_phy_led_brightness_set);
1068*bd5736e1SFlorian Fainelli 
1069b89eb1fcSArun Parameswaran MODULE_DESCRIPTION("Broadcom PHY Library");
1070b89eb1fcSArun Parameswaran MODULE_LICENSE("GPL v2");
1071b89eb1fcSArun Parameswaran MODULE_AUTHOR("Broadcom Corporation");
1072