xref: /linux/drivers/net/phy/bcm54140.c (revision 6937602ed3f9ebd46ed6a6b5e609c0ae4ed99008)
1*6937602eSMichael Walle // SPDX-License-Identifier: GPL-2.0+
2*6937602eSMichael Walle /* Broadcom BCM54140 Quad SGMII/QSGMII Copper/Fiber Gigabit PHY
3*6937602eSMichael Walle  *
4*6937602eSMichael Walle  * Copyright (c) 2020 Michael Walle <michael@walle.cc>
5*6937602eSMichael Walle  */
6*6937602eSMichael Walle 
7*6937602eSMichael Walle #include <linux/bitfield.h>
8*6937602eSMichael Walle #include <linux/brcmphy.h>
9*6937602eSMichael Walle #include <linux/module.h>
10*6937602eSMichael Walle #include <linux/phy.h>
11*6937602eSMichael Walle 
12*6937602eSMichael Walle #include "bcm-phy-lib.h"
13*6937602eSMichael Walle 
14*6937602eSMichael Walle /* RDB per-port registers
15*6937602eSMichael Walle  */
16*6937602eSMichael Walle #define BCM54140_RDB_ISR		0x00a	/* interrupt status */
17*6937602eSMichael Walle #define BCM54140_RDB_IMR		0x00b	/* interrupt mask */
18*6937602eSMichael Walle #define  BCM54140_RDB_INT_LINK		BIT(1)	/* link status changed */
19*6937602eSMichael Walle #define  BCM54140_RDB_INT_SPEED		BIT(2)	/* link speed change */
20*6937602eSMichael Walle #define  BCM54140_RDB_INT_DUPLEX	BIT(3)	/* duplex mode changed */
21*6937602eSMichael Walle #define BCM54140_RDB_SPARE1		0x012	/* spare control 1 */
22*6937602eSMichael Walle #define  BCM54140_RDB_SPARE1_LSLM	BIT(2)	/* link speed LED mode */
23*6937602eSMichael Walle #define BCM54140_RDB_SPARE2		0x014	/* spare control 2 */
24*6937602eSMichael Walle #define  BCM54140_RDB_SPARE2_WS_RTRY_DIS BIT(8) /* wirespeed retry disable */
25*6937602eSMichael Walle #define  BCM54140_RDB_SPARE2_WS_RTRY_LIMIT GENMASK(4, 2) /* retry limit */
26*6937602eSMichael Walle #define BCM54140_RDB_SPARE3		0x015	/* spare control 3 */
27*6937602eSMichael Walle #define  BCM54140_RDB_SPARE3_BIT0	BIT(0)
28*6937602eSMichael Walle #define BCM54140_RDB_LED_CTRL		0x019	/* LED control */
29*6937602eSMichael Walle #define  BCM54140_RDB_LED_CTRL_ACTLINK0	BIT(4)
30*6937602eSMichael Walle #define  BCM54140_RDB_LED_CTRL_ACTLINK1	BIT(8)
31*6937602eSMichael Walle #define BCM54140_RDB_C_APWR		0x01a	/* auto power down control */
32*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_SINGLE_PULSE	BIT(8)	/* single pulse */
33*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_APD_MODE_DIS	0 /* ADP disable */
34*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_APD_MODE_EN	1 /* ADP enable */
35*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_APD_MODE_DIS2	2 /* ADP disable */
36*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_APD_MODE_EN_ANEG	3 /* ADP enable w/ aneg */
37*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_APD_MODE_MASK	GENMASK(6, 5)
38*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_SLP_TIM_MASK BIT(4)/* sleep timer */
39*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_SLP_TIM_2_7 0	/* 2.7s */
40*6937602eSMichael Walle #define  BCM54140_RDB_C_APWR_SLP_TIM_5_4 1	/* 5.4s */
41*6937602eSMichael Walle #define BCM54140_RDB_C_PWR		0x02a	/* copper power control */
42*6937602eSMichael Walle #define  BCM54140_RDB_C_PWR_ISOLATE	BIT(5)	/* super isolate mode */
43*6937602eSMichael Walle #define BCM54140_RDB_C_MISC_CTRL	0x02f	/* misc copper control */
44*6937602eSMichael Walle #define  BCM54140_RDB_C_MISC_CTRL_WS_EN BIT(4)	/* wirespeed enable */
45*6937602eSMichael Walle 
46*6937602eSMichael Walle /* RDB global registers
47*6937602eSMichael Walle  */
48*6937602eSMichael Walle #define BCM54140_RDB_TOP_IMR		0x82d	/* interrupt mask */
49*6937602eSMichael Walle #define  BCM54140_RDB_TOP_IMR_PORT0	BIT(4)
50*6937602eSMichael Walle #define  BCM54140_RDB_TOP_IMR_PORT1	BIT(5)
51*6937602eSMichael Walle #define  BCM54140_RDB_TOP_IMR_PORT2	BIT(6)
52*6937602eSMichael Walle #define  BCM54140_RDB_TOP_IMR_PORT3	BIT(7)
53*6937602eSMichael Walle 
54*6937602eSMichael Walle #define BCM54140_DEFAULT_DOWNSHIFT 5
55*6937602eSMichael Walle #define BCM54140_MAX_DOWNSHIFT 9
56*6937602eSMichael Walle 
57*6937602eSMichael Walle struct bcm54140_priv {
58*6937602eSMichael Walle 	int port;
59*6937602eSMichael Walle 	int base_addr;
60*6937602eSMichael Walle };
61*6937602eSMichael Walle 
62*6937602eSMichael Walle static int bcm54140_base_read_rdb(struct phy_device *phydev, u16 rdb)
63*6937602eSMichael Walle {
64*6937602eSMichael Walle 	struct bcm54140_priv *priv = phydev->priv;
65*6937602eSMichael Walle 	struct mii_bus *bus = phydev->mdio.bus;
66*6937602eSMichael Walle 	int ret;
67*6937602eSMichael Walle 
68*6937602eSMichael Walle 	mutex_lock(&bus->mdio_lock);
69*6937602eSMichael Walle 	ret = __mdiobus_write(bus, priv->base_addr, MII_BCM54XX_RDB_ADDR, rdb);
70*6937602eSMichael Walle 	if (ret < 0)
71*6937602eSMichael Walle 		goto out;
72*6937602eSMichael Walle 
73*6937602eSMichael Walle 	ret = __mdiobus_read(bus, priv->base_addr, MII_BCM54XX_RDB_DATA);
74*6937602eSMichael Walle 
75*6937602eSMichael Walle out:
76*6937602eSMichael Walle 	mutex_unlock(&bus->mdio_lock);
77*6937602eSMichael Walle 	return ret;
78*6937602eSMichael Walle }
79*6937602eSMichael Walle 
80*6937602eSMichael Walle static int bcm54140_base_write_rdb(struct phy_device *phydev,
81*6937602eSMichael Walle 				   u16 rdb, u16 val)
82*6937602eSMichael Walle {
83*6937602eSMichael Walle 	struct bcm54140_priv *priv = phydev->priv;
84*6937602eSMichael Walle 	struct mii_bus *bus = phydev->mdio.bus;
85*6937602eSMichael Walle 	int ret;
86*6937602eSMichael Walle 
87*6937602eSMichael Walle 	mutex_lock(&bus->mdio_lock);
88*6937602eSMichael Walle 	ret = __mdiobus_write(bus, priv->base_addr, MII_BCM54XX_RDB_ADDR, rdb);
89*6937602eSMichael Walle 	if (ret < 0)
90*6937602eSMichael Walle 		goto out;
91*6937602eSMichael Walle 
92*6937602eSMichael Walle 	ret = __mdiobus_write(bus, priv->base_addr, MII_BCM54XX_RDB_DATA, val);
93*6937602eSMichael Walle 
94*6937602eSMichael Walle out:
95*6937602eSMichael Walle 	mutex_unlock(&bus->mdio_lock);
96*6937602eSMichael Walle 	return ret;
97*6937602eSMichael Walle }
98*6937602eSMichael Walle 
99*6937602eSMichael Walle /* Under some circumstances a core PLL may not lock, this will then prevent
100*6937602eSMichael Walle  * a successful link establishment. Restart the PLL after the voltages are
101*6937602eSMichael Walle  * stable to workaround this issue.
102*6937602eSMichael Walle  */
103*6937602eSMichael Walle static int bcm54140_b0_workaround(struct phy_device *phydev)
104*6937602eSMichael Walle {
105*6937602eSMichael Walle 	int spare3;
106*6937602eSMichael Walle 	int ret;
107*6937602eSMichael Walle 
108*6937602eSMichael Walle 	spare3 = bcm_phy_read_rdb(phydev, BCM54140_RDB_SPARE3);
109*6937602eSMichael Walle 	if (spare3 < 0)
110*6937602eSMichael Walle 		return spare3;
111*6937602eSMichael Walle 
112*6937602eSMichael Walle 	spare3 &= ~BCM54140_RDB_SPARE3_BIT0;
113*6937602eSMichael Walle 
114*6937602eSMichael Walle 	ret = bcm_phy_write_rdb(phydev, BCM54140_RDB_SPARE3, spare3);
115*6937602eSMichael Walle 	if (ret)
116*6937602eSMichael Walle 		return ret;
117*6937602eSMichael Walle 
118*6937602eSMichael Walle 	ret = phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN);
119*6937602eSMichael Walle 	if (ret)
120*6937602eSMichael Walle 		return ret;
121*6937602eSMichael Walle 
122*6937602eSMichael Walle 	ret = phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0);
123*6937602eSMichael Walle 	if (ret)
124*6937602eSMichael Walle 		return ret;
125*6937602eSMichael Walle 
126*6937602eSMichael Walle 	spare3 |= BCM54140_RDB_SPARE3_BIT0;
127*6937602eSMichael Walle 
128*6937602eSMichael Walle 	return bcm_phy_write_rdb(phydev, BCM54140_RDB_SPARE3, spare3);
129*6937602eSMichael Walle }
130*6937602eSMichael Walle 
131*6937602eSMichael Walle /* The BCM54140 is a quad PHY where only the first port has access to the
132*6937602eSMichael Walle  * global register. Thus we need to find out its PHY address.
133*6937602eSMichael Walle  *
134*6937602eSMichael Walle  */
135*6937602eSMichael Walle static int bcm54140_get_base_addr_and_port(struct phy_device *phydev)
136*6937602eSMichael Walle {
137*6937602eSMichael Walle 	struct bcm54140_priv *priv = phydev->priv;
138*6937602eSMichael Walle 	struct mii_bus *bus = phydev->mdio.bus;
139*6937602eSMichael Walle 	int addr, min_addr, max_addr;
140*6937602eSMichael Walle 	int step = 1;
141*6937602eSMichael Walle 	u32 phy_id;
142*6937602eSMichael Walle 	int tmp;
143*6937602eSMichael Walle 
144*6937602eSMichael Walle 	min_addr = phydev->mdio.addr;
145*6937602eSMichael Walle 	max_addr = phydev->mdio.addr;
146*6937602eSMichael Walle 	addr = phydev->mdio.addr;
147*6937602eSMichael Walle 
148*6937602eSMichael Walle 	/* We scan forward and backwards and look for PHYs which have the
149*6937602eSMichael Walle 	 * same phy_id like we do. Step 1 will scan forward, step 2
150*6937602eSMichael Walle 	 * backwards. Once we are finished, we have a min_addr and
151*6937602eSMichael Walle 	 * max_addr which resembles the range of PHY addresses of the same
152*6937602eSMichael Walle 	 * type of PHY. There is one caveat; there may be many PHYs of
153*6937602eSMichael Walle 	 * the same type, but we know that each PHY takes exactly 4
154*6937602eSMichael Walle 	 * consecutive addresses. Therefore we can deduce our offset
155*6937602eSMichael Walle 	 * to the base address of this quad PHY.
156*6937602eSMichael Walle 	 */
157*6937602eSMichael Walle 
158*6937602eSMichael Walle 	while (1) {
159*6937602eSMichael Walle 		if (step == 3) {
160*6937602eSMichael Walle 			break;
161*6937602eSMichael Walle 		} else if (step == 1) {
162*6937602eSMichael Walle 			max_addr = addr;
163*6937602eSMichael Walle 			addr++;
164*6937602eSMichael Walle 		} else {
165*6937602eSMichael Walle 			min_addr = addr;
166*6937602eSMichael Walle 			addr--;
167*6937602eSMichael Walle 		}
168*6937602eSMichael Walle 
169*6937602eSMichael Walle 		if (addr < 0 || addr >= PHY_MAX_ADDR) {
170*6937602eSMichael Walle 			addr = phydev->mdio.addr;
171*6937602eSMichael Walle 			step++;
172*6937602eSMichael Walle 			continue;
173*6937602eSMichael Walle 		}
174*6937602eSMichael Walle 
175*6937602eSMichael Walle 		/* read the PHY id */
176*6937602eSMichael Walle 		tmp = mdiobus_read(bus, addr, MII_PHYSID1);
177*6937602eSMichael Walle 		if (tmp < 0)
178*6937602eSMichael Walle 			return tmp;
179*6937602eSMichael Walle 		phy_id = tmp << 16;
180*6937602eSMichael Walle 		tmp = mdiobus_read(bus, addr, MII_PHYSID2);
181*6937602eSMichael Walle 		if (tmp < 0)
182*6937602eSMichael Walle 			return tmp;
183*6937602eSMichael Walle 		phy_id |= tmp;
184*6937602eSMichael Walle 
185*6937602eSMichael Walle 		/* see if it is still the same PHY */
186*6937602eSMichael Walle 		if ((phy_id & phydev->drv->phy_id_mask) !=
187*6937602eSMichael Walle 		    (phydev->drv->phy_id & phydev->drv->phy_id_mask)) {
188*6937602eSMichael Walle 			addr = phydev->mdio.addr;
189*6937602eSMichael Walle 			step++;
190*6937602eSMichael Walle 		}
191*6937602eSMichael Walle 	}
192*6937602eSMichael Walle 
193*6937602eSMichael Walle 	/* The range we get should be a multiple of four. Please note that both
194*6937602eSMichael Walle 	 * the min_addr and max_addr are inclusive. So we have to add one if we
195*6937602eSMichael Walle 	 * subtract them.
196*6937602eSMichael Walle 	 */
197*6937602eSMichael Walle 	if ((max_addr - min_addr + 1) % 4) {
198*6937602eSMichael Walle 		dev_err(&phydev->mdio.dev,
199*6937602eSMichael Walle 			"Detected Quad PHY IDs %d..%d doesn't make sense.\n",
200*6937602eSMichael Walle 			min_addr, max_addr);
201*6937602eSMichael Walle 		return -EINVAL;
202*6937602eSMichael Walle 	}
203*6937602eSMichael Walle 
204*6937602eSMichael Walle 	priv->port = (phydev->mdio.addr - min_addr) % 4;
205*6937602eSMichael Walle 	priv->base_addr = phydev->mdio.addr - priv->port;
206*6937602eSMichael Walle 
207*6937602eSMichael Walle 	return 0;
208*6937602eSMichael Walle }
209*6937602eSMichael Walle 
210*6937602eSMichael Walle static int bcm54140_probe(struct phy_device *phydev)
211*6937602eSMichael Walle {
212*6937602eSMichael Walle 	struct bcm54140_priv *priv;
213*6937602eSMichael Walle 	int ret;
214*6937602eSMichael Walle 
215*6937602eSMichael Walle 	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
216*6937602eSMichael Walle 	if (!priv)
217*6937602eSMichael Walle 		return -ENOMEM;
218*6937602eSMichael Walle 
219*6937602eSMichael Walle 	phydev->priv = priv;
220*6937602eSMichael Walle 
221*6937602eSMichael Walle 	ret = bcm54140_get_base_addr_and_port(phydev);
222*6937602eSMichael Walle 	if (ret)
223*6937602eSMichael Walle 		return ret;
224*6937602eSMichael Walle 
225*6937602eSMichael Walle 	phydev_dbg(phydev, "probed (port %d, base PHY address %d)\n",
226*6937602eSMichael Walle 		   priv->port, priv->base_addr);
227*6937602eSMichael Walle 
228*6937602eSMichael Walle 	return 0;
229*6937602eSMichael Walle }
230*6937602eSMichael Walle 
231*6937602eSMichael Walle static int bcm54140_config_init(struct phy_device *phydev)
232*6937602eSMichael Walle {
233*6937602eSMichael Walle 	u16 reg = 0xffff;
234*6937602eSMichael Walle 	int ret;
235*6937602eSMichael Walle 
236*6937602eSMichael Walle 	/* Apply hardware errata */
237*6937602eSMichael Walle 	ret = bcm54140_b0_workaround(phydev);
238*6937602eSMichael Walle 	if (ret)
239*6937602eSMichael Walle 		return ret;
240*6937602eSMichael Walle 
241*6937602eSMichael Walle 	/* Unmask events we are interested in. */
242*6937602eSMichael Walle 	reg &= ~(BCM54140_RDB_INT_DUPLEX |
243*6937602eSMichael Walle 		 BCM54140_RDB_INT_SPEED |
244*6937602eSMichael Walle 		 BCM54140_RDB_INT_LINK);
245*6937602eSMichael Walle 	ret = bcm_phy_write_rdb(phydev, BCM54140_RDB_IMR, reg);
246*6937602eSMichael Walle 	if (ret)
247*6937602eSMichael Walle 		return ret;
248*6937602eSMichael Walle 
249*6937602eSMichael Walle 	/* LED1=LINKSPD[1], LED2=LINKSPD[2], LED3=LINK/ACTIVITY */
250*6937602eSMichael Walle 	ret = bcm_phy_modify_rdb(phydev, BCM54140_RDB_SPARE1,
251*6937602eSMichael Walle 				 0, BCM54140_RDB_SPARE1_LSLM);
252*6937602eSMichael Walle 	if (ret)
253*6937602eSMichael Walle 		return ret;
254*6937602eSMichael Walle 
255*6937602eSMichael Walle 	ret = bcm_phy_modify_rdb(phydev, BCM54140_RDB_LED_CTRL,
256*6937602eSMichael Walle 				 0, BCM54140_RDB_LED_CTRL_ACTLINK0);
257*6937602eSMichael Walle 	if (ret)
258*6937602eSMichael Walle 		return ret;
259*6937602eSMichael Walle 
260*6937602eSMichael Walle 	/* disable super isolate mode */
261*6937602eSMichael Walle 	return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_PWR,
262*6937602eSMichael Walle 				  BCM54140_RDB_C_PWR_ISOLATE, 0);
263*6937602eSMichael Walle }
264*6937602eSMichael Walle 
265*6937602eSMichael Walle int bcm54140_did_interrupt(struct phy_device *phydev)
266*6937602eSMichael Walle {
267*6937602eSMichael Walle 	int ret;
268*6937602eSMichael Walle 
269*6937602eSMichael Walle 	ret = bcm_phy_read_rdb(phydev, BCM54140_RDB_ISR);
270*6937602eSMichael Walle 
271*6937602eSMichael Walle 	return (ret < 0) ? 0 : ret;
272*6937602eSMichael Walle }
273*6937602eSMichael Walle 
274*6937602eSMichael Walle int bcm54140_ack_intr(struct phy_device *phydev)
275*6937602eSMichael Walle {
276*6937602eSMichael Walle 	int reg;
277*6937602eSMichael Walle 
278*6937602eSMichael Walle 	/* clear pending interrupts */
279*6937602eSMichael Walle 	reg = bcm_phy_read_rdb(phydev, BCM54140_RDB_ISR);
280*6937602eSMichael Walle 	if (reg < 0)
281*6937602eSMichael Walle 		return reg;
282*6937602eSMichael Walle 
283*6937602eSMichael Walle 	return 0;
284*6937602eSMichael Walle }
285*6937602eSMichael Walle 
286*6937602eSMichael Walle int bcm54140_config_intr(struct phy_device *phydev)
287*6937602eSMichael Walle {
288*6937602eSMichael Walle 	struct bcm54140_priv *priv = phydev->priv;
289*6937602eSMichael Walle 	static const u16 port_to_imr_bit[] = {
290*6937602eSMichael Walle 		BCM54140_RDB_TOP_IMR_PORT0, BCM54140_RDB_TOP_IMR_PORT1,
291*6937602eSMichael Walle 		BCM54140_RDB_TOP_IMR_PORT2, BCM54140_RDB_TOP_IMR_PORT3,
292*6937602eSMichael Walle 	};
293*6937602eSMichael Walle 	int reg;
294*6937602eSMichael Walle 
295*6937602eSMichael Walle 	if (priv->port >= ARRAY_SIZE(port_to_imr_bit))
296*6937602eSMichael Walle 		return -EINVAL;
297*6937602eSMichael Walle 
298*6937602eSMichael Walle 	reg = bcm54140_base_read_rdb(phydev, BCM54140_RDB_TOP_IMR);
299*6937602eSMichael Walle 	if (reg < 0)
300*6937602eSMichael Walle 		return reg;
301*6937602eSMichael Walle 
302*6937602eSMichael Walle 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
303*6937602eSMichael Walle 		reg &= ~port_to_imr_bit[priv->port];
304*6937602eSMichael Walle 	else
305*6937602eSMichael Walle 		reg |= port_to_imr_bit[priv->port];
306*6937602eSMichael Walle 
307*6937602eSMichael Walle 	return bcm54140_base_write_rdb(phydev, BCM54140_RDB_TOP_IMR, reg);
308*6937602eSMichael Walle }
309*6937602eSMichael Walle 
310*6937602eSMichael Walle static int bcm54140_get_downshift(struct phy_device *phydev, u8 *data)
311*6937602eSMichael Walle {
312*6937602eSMichael Walle 	int val;
313*6937602eSMichael Walle 
314*6937602eSMichael Walle 	val = bcm_phy_read_rdb(phydev, BCM54140_RDB_C_MISC_CTRL);
315*6937602eSMichael Walle 	if (val < 0)
316*6937602eSMichael Walle 		return val;
317*6937602eSMichael Walle 
318*6937602eSMichael Walle 	if (!(val & BCM54140_RDB_C_MISC_CTRL_WS_EN)) {
319*6937602eSMichael Walle 		*data = DOWNSHIFT_DEV_DISABLE;
320*6937602eSMichael Walle 		return 0;
321*6937602eSMichael Walle 	}
322*6937602eSMichael Walle 
323*6937602eSMichael Walle 	val = bcm_phy_read_rdb(phydev, BCM54140_RDB_SPARE2);
324*6937602eSMichael Walle 	if (val < 0)
325*6937602eSMichael Walle 		return val;
326*6937602eSMichael Walle 
327*6937602eSMichael Walle 	if (val & BCM54140_RDB_SPARE2_WS_RTRY_DIS)
328*6937602eSMichael Walle 		*data = 1;
329*6937602eSMichael Walle 	else
330*6937602eSMichael Walle 		*data = FIELD_GET(BCM54140_RDB_SPARE2_WS_RTRY_LIMIT, val) + 2;
331*6937602eSMichael Walle 
332*6937602eSMichael Walle 	return 0;
333*6937602eSMichael Walle }
334*6937602eSMichael Walle 
335*6937602eSMichael Walle static int bcm54140_set_downshift(struct phy_device *phydev, u8 cnt)
336*6937602eSMichael Walle {
337*6937602eSMichael Walle 	u16 mask, set;
338*6937602eSMichael Walle 	int ret;
339*6937602eSMichael Walle 
340*6937602eSMichael Walle 	if (cnt > BCM54140_MAX_DOWNSHIFT && cnt != DOWNSHIFT_DEV_DEFAULT_COUNT)
341*6937602eSMichael Walle 		return -EINVAL;
342*6937602eSMichael Walle 
343*6937602eSMichael Walle 	if (!cnt)
344*6937602eSMichael Walle 		return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_MISC_CTRL,
345*6937602eSMichael Walle 					  BCM54140_RDB_C_MISC_CTRL_WS_EN, 0);
346*6937602eSMichael Walle 
347*6937602eSMichael Walle 	if (cnt == DOWNSHIFT_DEV_DEFAULT_COUNT)
348*6937602eSMichael Walle 		cnt = BCM54140_DEFAULT_DOWNSHIFT;
349*6937602eSMichael Walle 
350*6937602eSMichael Walle 	if (cnt == 1) {
351*6937602eSMichael Walle 		mask = 0;
352*6937602eSMichael Walle 		set = BCM54140_RDB_SPARE2_WS_RTRY_DIS;
353*6937602eSMichael Walle 	} else {
354*6937602eSMichael Walle 		mask = BCM54140_RDB_SPARE2_WS_RTRY_DIS;
355*6937602eSMichael Walle 		mask |= BCM54140_RDB_SPARE2_WS_RTRY_LIMIT;
356*6937602eSMichael Walle 		set = FIELD_PREP(BCM54140_RDB_SPARE2_WS_RTRY_LIMIT, cnt - 2);
357*6937602eSMichael Walle 	}
358*6937602eSMichael Walle 	ret = bcm_phy_modify_rdb(phydev, BCM54140_RDB_SPARE2,
359*6937602eSMichael Walle 				 mask, set);
360*6937602eSMichael Walle 	if (ret)
361*6937602eSMichael Walle 		return ret;
362*6937602eSMichael Walle 
363*6937602eSMichael Walle 	return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_MISC_CTRL,
364*6937602eSMichael Walle 				  0, BCM54140_RDB_C_MISC_CTRL_WS_EN);
365*6937602eSMichael Walle }
366*6937602eSMichael Walle 
367*6937602eSMichael Walle static int bcm54140_get_edpd(struct phy_device *phydev, u16 *tx_interval)
368*6937602eSMichael Walle {
369*6937602eSMichael Walle 	int val;
370*6937602eSMichael Walle 
371*6937602eSMichael Walle 	val = bcm_phy_read_rdb(phydev, BCM54140_RDB_C_APWR);
372*6937602eSMichael Walle 	if (val < 0)
373*6937602eSMichael Walle 		return val;
374*6937602eSMichael Walle 
375*6937602eSMichael Walle 	switch (FIELD_GET(BCM54140_RDB_C_APWR_APD_MODE_MASK, val)) {
376*6937602eSMichael Walle 	case BCM54140_RDB_C_APWR_APD_MODE_DIS:
377*6937602eSMichael Walle 	case BCM54140_RDB_C_APWR_APD_MODE_DIS2:
378*6937602eSMichael Walle 		*tx_interval = ETHTOOL_PHY_EDPD_DISABLE;
379*6937602eSMichael Walle 		break;
380*6937602eSMichael Walle 	case BCM54140_RDB_C_APWR_APD_MODE_EN:
381*6937602eSMichael Walle 	case BCM54140_RDB_C_APWR_APD_MODE_EN_ANEG:
382*6937602eSMichael Walle 		switch (FIELD_GET(BCM54140_RDB_C_APWR_SLP_TIM_MASK, val)) {
383*6937602eSMichael Walle 		case BCM54140_RDB_C_APWR_SLP_TIM_2_7:
384*6937602eSMichael Walle 			*tx_interval = 2700;
385*6937602eSMichael Walle 			break;
386*6937602eSMichael Walle 		case BCM54140_RDB_C_APWR_SLP_TIM_5_4:
387*6937602eSMichael Walle 			*tx_interval = 5400;
388*6937602eSMichael Walle 			break;
389*6937602eSMichael Walle 		}
390*6937602eSMichael Walle 	}
391*6937602eSMichael Walle 
392*6937602eSMichael Walle 	return 0;
393*6937602eSMichael Walle }
394*6937602eSMichael Walle 
395*6937602eSMichael Walle static int bcm54140_set_edpd(struct phy_device *phydev, u16 tx_interval)
396*6937602eSMichael Walle {
397*6937602eSMichael Walle 	u16 mask, set;
398*6937602eSMichael Walle 
399*6937602eSMichael Walle 	mask = BCM54140_RDB_C_APWR_APD_MODE_MASK;
400*6937602eSMichael Walle 	if (tx_interval == ETHTOOL_PHY_EDPD_DISABLE)
401*6937602eSMichael Walle 		set = FIELD_PREP(BCM54140_RDB_C_APWR_APD_MODE_MASK,
402*6937602eSMichael Walle 				 BCM54140_RDB_C_APWR_APD_MODE_DIS);
403*6937602eSMichael Walle 	else
404*6937602eSMichael Walle 		set = FIELD_PREP(BCM54140_RDB_C_APWR_APD_MODE_MASK,
405*6937602eSMichael Walle 				 BCM54140_RDB_C_APWR_APD_MODE_EN_ANEG);
406*6937602eSMichael Walle 
407*6937602eSMichael Walle 	/* enable single pulse mode */
408*6937602eSMichael Walle 	set |= BCM54140_RDB_C_APWR_SINGLE_PULSE;
409*6937602eSMichael Walle 
410*6937602eSMichael Walle 	/* set sleep timer */
411*6937602eSMichael Walle 	mask |= BCM54140_RDB_C_APWR_SLP_TIM_MASK;
412*6937602eSMichael Walle 	switch (tx_interval) {
413*6937602eSMichael Walle 	case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
414*6937602eSMichael Walle 	case ETHTOOL_PHY_EDPD_DISABLE:
415*6937602eSMichael Walle 	case 2700:
416*6937602eSMichael Walle 		set |= BCM54140_RDB_C_APWR_SLP_TIM_2_7;
417*6937602eSMichael Walle 		break;
418*6937602eSMichael Walle 	case 5400:
419*6937602eSMichael Walle 		set |= BCM54140_RDB_C_APWR_SLP_TIM_5_4;
420*6937602eSMichael Walle 		break;
421*6937602eSMichael Walle 	default:
422*6937602eSMichael Walle 		return -EINVAL;
423*6937602eSMichael Walle 	}
424*6937602eSMichael Walle 
425*6937602eSMichael Walle 	return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_APWR, mask, set);
426*6937602eSMichael Walle }
427*6937602eSMichael Walle 
428*6937602eSMichael Walle static int bcm54140_get_tunable(struct phy_device *phydev,
429*6937602eSMichael Walle 				struct ethtool_tunable *tuna, void *data)
430*6937602eSMichael Walle {
431*6937602eSMichael Walle 	switch (tuna->id) {
432*6937602eSMichael Walle 	case ETHTOOL_PHY_DOWNSHIFT:
433*6937602eSMichael Walle 		return bcm54140_get_downshift(phydev, data);
434*6937602eSMichael Walle 	case ETHTOOL_PHY_EDPD:
435*6937602eSMichael Walle 		return bcm54140_get_edpd(phydev, data);
436*6937602eSMichael Walle 	default:
437*6937602eSMichael Walle 		return -EOPNOTSUPP;
438*6937602eSMichael Walle 	}
439*6937602eSMichael Walle }
440*6937602eSMichael Walle 
441*6937602eSMichael Walle static int bcm54140_set_tunable(struct phy_device *phydev,
442*6937602eSMichael Walle 				struct ethtool_tunable *tuna, const void *data)
443*6937602eSMichael Walle {
444*6937602eSMichael Walle 	switch (tuna->id) {
445*6937602eSMichael Walle 	case ETHTOOL_PHY_DOWNSHIFT:
446*6937602eSMichael Walle 		return bcm54140_set_downshift(phydev, *(const u8 *)data);
447*6937602eSMichael Walle 	case ETHTOOL_PHY_EDPD:
448*6937602eSMichael Walle 		return bcm54140_set_edpd(phydev, *(const u16 *)data);
449*6937602eSMichael Walle 	default:
450*6937602eSMichael Walle 		return -EOPNOTSUPP;
451*6937602eSMichael Walle 	}
452*6937602eSMichael Walle }
453*6937602eSMichael Walle 
454*6937602eSMichael Walle static struct phy_driver bcm54140_drivers[] = {
455*6937602eSMichael Walle 	{
456*6937602eSMichael Walle 		.phy_id         = PHY_ID_BCM54140,
457*6937602eSMichael Walle 		.phy_id_mask    = 0xfffffff0,
458*6937602eSMichael Walle 		.name           = "Broadcom BCM54140",
459*6937602eSMichael Walle 		.features       = PHY_GBIT_FEATURES,
460*6937602eSMichael Walle 		.config_init    = bcm54140_config_init,
461*6937602eSMichael Walle 		.did_interrupt	= bcm54140_did_interrupt,
462*6937602eSMichael Walle 		.ack_interrupt  = bcm54140_ack_intr,
463*6937602eSMichael Walle 		.config_intr    = bcm54140_config_intr,
464*6937602eSMichael Walle 		.probe		= bcm54140_probe,
465*6937602eSMichael Walle 		.suspend	= genphy_suspend,
466*6937602eSMichael Walle 		.resume		= genphy_resume,
467*6937602eSMichael Walle 		.get_tunable	= bcm54140_get_tunable,
468*6937602eSMichael Walle 		.set_tunable	= bcm54140_set_tunable,
469*6937602eSMichael Walle 	},
470*6937602eSMichael Walle };
471*6937602eSMichael Walle module_phy_driver(bcm54140_drivers);
472*6937602eSMichael Walle 
473*6937602eSMichael Walle static struct mdio_device_id __maybe_unused bcm54140_tbl[] = {
474*6937602eSMichael Walle 	{ PHY_ID_BCM54140, 0xfffffff0 },
475*6937602eSMichael Walle 	{ }
476*6937602eSMichael Walle };
477*6937602eSMichael Walle 
478*6937602eSMichael Walle MODULE_AUTHOR("Michael Walle");
479*6937602eSMichael Walle MODULE_DESCRIPTION("Broadcom BCM54140 PHY driver");
480*6937602eSMichael Walle MODULE_DEVICE_TABLE(mdio, bcm54140_tbl);
481*6937602eSMichael Walle MODULE_LICENSE("GPL");
482