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