xref: /openbmc/linux/drivers/net/phy/bcm7xxx.c (revision 59e33c2b)
1b560a58cSFlorian Fainelli /*
2b560a58cSFlorian Fainelli  * Broadcom BCM7xxx internal transceivers support.
3b560a58cSFlorian Fainelli  *
4b560a58cSFlorian Fainelli  * Copyright (C) 2014, Broadcom Corporation
5b560a58cSFlorian Fainelli  *
6b560a58cSFlorian Fainelli  * This program is free software; you can redistribute it and/or
7b560a58cSFlorian Fainelli  * modify it under the terms of the GNU General Public License
8b560a58cSFlorian Fainelli  * as published by the Free Software Foundation; either version
9b560a58cSFlorian Fainelli  * 2 of the License, or (at your option) any later version.
10b560a58cSFlorian Fainelli  */
11b560a58cSFlorian Fainelli 
12b560a58cSFlorian Fainelli #include <linux/module.h>
13b560a58cSFlorian Fainelli #include <linux/phy.h>
14b560a58cSFlorian Fainelli #include <linux/delay.h>
15b560a58cSFlorian Fainelli #include <linux/bitops.h>
16b560a58cSFlorian Fainelli #include <linux/brcmphy.h>
17b8f9a029SFlorian Fainelli #include <linux/mdio.h>
18b560a58cSFlorian Fainelli 
19b560a58cSFlorian Fainelli /* Broadcom BCM7xxx internal PHY registers */
20b560a58cSFlorian Fainelli #define MII_BCM7XXX_CHANNEL_WIDTH	0x2000
21b560a58cSFlorian Fainelli 
22b560a58cSFlorian Fainelli /* 40nm only register definitions */
23b560a58cSFlorian Fainelli #define MII_BCM7XXX_100TX_AUX_CTL	0x10
24b560a58cSFlorian Fainelli #define MII_BCM7XXX_100TX_FALSE_CAR	0x13
25b560a58cSFlorian Fainelli #define MII_BCM7XXX_100TX_DISC		0x14
26b560a58cSFlorian Fainelli #define MII_BCM7XXX_AUX_MODE		0x1d
27b560a58cSFlorian Fainelli #define  MII_BCM7XX_64CLK_MDIO		BIT(12)
28b560a58cSFlorian Fainelli #define MII_BCM7XXX_CORE_BASE1E		0x1e
29b560a58cSFlorian Fainelli #define MII_BCM7XXX_TEST		0x1f
30b560a58cSFlorian Fainelli #define  MII_BCM7XXX_SHD_MODE_2		BIT(2)
31b560a58cSFlorian Fainelli 
32a3622f2cSFlorian Fainelli /* 28nm only register definitions */
33a3622f2cSFlorian Fainelli #define MISC_ADDR(base, channel)	base, channel
34a3622f2cSFlorian Fainelli 
35a3622f2cSFlorian Fainelli #define DSP_TAP10			MISC_ADDR(0x0a, 0)
36a3622f2cSFlorian Fainelli #define PLL_PLLCTRL_1			MISC_ADDR(0x32, 1)
37a3622f2cSFlorian Fainelli #define PLL_PLLCTRL_2			MISC_ADDR(0x32, 2)
38a3622f2cSFlorian Fainelli #define PLL_PLLCTRL_4			MISC_ADDR(0x33, 0)
39a3622f2cSFlorian Fainelli 
40a3622f2cSFlorian Fainelli #define AFE_RXCONFIG_0			MISC_ADDR(0x38, 0)
41a3622f2cSFlorian Fainelli #define AFE_RXCONFIG_1			MISC_ADDR(0x38, 1)
42a490631fSFlorian Fainelli #define AFE_RXCONFIG_2			MISC_ADDR(0x38, 2)
43a3622f2cSFlorian Fainelli #define AFE_RX_LP_COUNTER		MISC_ADDR(0x38, 3)
44a3622f2cSFlorian Fainelli #define AFE_TX_CONFIG			MISC_ADDR(0x39, 0)
45a490631fSFlorian Fainelli #define AFE_VDCA_ICTRL_0		MISC_ADDR(0x39, 1)
46a490631fSFlorian Fainelli #define AFE_VDAC_OTHERS_0		MISC_ADDR(0x39, 3)
47a3622f2cSFlorian Fainelli #define AFE_HPF_TRIM_OTHERS		MISC_ADDR(0x3a, 0)
48a3622f2cSFlorian Fainelli 
49a3622f2cSFlorian Fainelli #define CORE_EXPB0			0xb0
50a3622f2cSFlorian Fainelli 
51b560a58cSFlorian Fainelli static void phy_write_exp(struct phy_device *phydev,
52b560a58cSFlorian Fainelli 					u16 reg, u16 value)
53b560a58cSFlorian Fainelli {
54b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_EXP_SEL, MII_BCM54XX_EXP_SEL_ER | reg);
55b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_EXP_DATA, value);
56b560a58cSFlorian Fainelli }
57b560a58cSFlorian Fainelli 
58b560a58cSFlorian Fainelli static void phy_write_misc(struct phy_device *phydev,
59b560a58cSFlorian Fainelli 					u16 reg, u16 chl, u16 value)
60b560a58cSFlorian Fainelli {
61b560a58cSFlorian Fainelli 	int tmp;
62b560a58cSFlorian Fainelli 
63b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
64b560a58cSFlorian Fainelli 
65b560a58cSFlorian Fainelli 	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
66b560a58cSFlorian Fainelli 	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
67b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
68b560a58cSFlorian Fainelli 
69b560a58cSFlorian Fainelli 	tmp = (chl * MII_BCM7XXX_CHANNEL_WIDTH) | reg;
70b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_EXP_SEL, tmp);
71b560a58cSFlorian Fainelli 
72b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM54XX_EXP_DATA, value);
73b560a58cSFlorian Fainelli }
74b560a58cSFlorian Fainelli 
759c41f2baSFlorian Fainelli static void r_rc_cal_reset(struct phy_device *phydev)
769c41f2baSFlorian Fainelli {
779c41f2baSFlorian Fainelli 	/* Reset R_CAL/RC_CAL Engine */
789c41f2baSFlorian Fainelli 	phy_write_exp(phydev, 0x00b0, 0x0010);
799c41f2baSFlorian Fainelli 
809c41f2baSFlorian Fainelli 	/* Disable Reset R_AL/RC_CAL Engine */
819c41f2baSFlorian Fainelli 	phy_write_exp(phydev, 0x00b0, 0x0000);
829c41f2baSFlorian Fainelli }
839c41f2baSFlorian Fainelli 
842a9df742SFlorian Fainelli static int bcm7xxx_28nm_b0_afe_config_init(struct phy_device *phydev)
85b560a58cSFlorian Fainelli {
86b560a58cSFlorian Fainelli 	/* Increase VCO range to prevent unlocking problem of PLL at low
87b560a58cSFlorian Fainelli 	 * temp
88b560a58cSFlorian Fainelli 	 */
89a3622f2cSFlorian Fainelli 	phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048);
90b560a58cSFlorian Fainelli 
91b560a58cSFlorian Fainelli 	/* Change Ki to 011 */
92a3622f2cSFlorian Fainelli 	phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b);
93b560a58cSFlorian Fainelli 
94b560a58cSFlorian Fainelli 	/* Disable loading of TVCO buffer to bandgap, set bandgap trim
95b560a58cSFlorian Fainelli 	 * to 111
96b560a58cSFlorian Fainelli 	 */
97a3622f2cSFlorian Fainelli 	phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20);
98b560a58cSFlorian Fainelli 
99b560a58cSFlorian Fainelli 	/* Adjust bias current trim by -3 */
100a3622f2cSFlorian Fainelli 	phy_write_misc(phydev, DSP_TAP10, 0x690b);
101b560a58cSFlorian Fainelli 
102b560a58cSFlorian Fainelli 	/* Switch to CORE_BASE1E */
103b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0xd);
104b560a58cSFlorian Fainelli 
1059c41f2baSFlorian Fainelli 	r_rc_cal_reset(phydev);
106b560a58cSFlorian Fainelli 
1079918542eSFlorian Fainelli 	/* write AFE_RXCONFIG_0 */
1089918542eSFlorian Fainelli 	phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19);
1099918542eSFlorian Fainelli 
1109918542eSFlorian Fainelli 	/* write AFE_RXCONFIG_1 */
1119918542eSFlorian Fainelli 	phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f);
1129918542eSFlorian Fainelli 
1139918542eSFlorian Fainelli 	/* write AFE_RX_LP_COUNTER */
114a62ea5a7SFlorian Fainelli 	phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
1159918542eSFlorian Fainelli 
1169918542eSFlorian Fainelli 	/* write AFE_HPF_TRIM_OTHERS */
1179918542eSFlorian Fainelli 	phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b);
1189918542eSFlorian Fainelli 
1199918542eSFlorian Fainelli 	/* write AFTE_TX_CONFIG */
1209918542eSFlorian Fainelli 	phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800);
1219918542eSFlorian Fainelli 
122b560a58cSFlorian Fainelli 	return 0;
123b560a58cSFlorian Fainelli }
124b560a58cSFlorian Fainelli 
125a490631fSFlorian Fainelli static int bcm7xxx_28nm_d0_afe_config_init(struct phy_device *phydev)
126a490631fSFlorian Fainelli {
127a490631fSFlorian Fainelli 	/* AFE_RXCONFIG_0 */
128a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb15);
129a490631fSFlorian Fainelli 
130a490631fSFlorian Fainelli 	/* AFE_RXCONFIG_1 */
131a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9b2f);
132a490631fSFlorian Fainelli 
133a490631fSFlorian Fainelli 	/* AFE_RXCONFIG_2, set rCal offset for HT=0 code and LT=-2 code */
134a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_RXCONFIG_2, 0x2003);
135a490631fSFlorian Fainelli 
136a490631fSFlorian Fainelli 	/* AFE_RX_LP_COUNTER, set RX bandwidth to maximum */
137a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
138a490631fSFlorian Fainelli 
139a490631fSFlorian Fainelli 	/* AFE_TX_CONFIG, set 1000BT Cfeed=110 for all ports */
140a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_TX_CONFIG, 0x0061);
141a490631fSFlorian Fainelli 
142a490631fSFlorian Fainelli 	/* AFE_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */
143a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da);
144a490631fSFlorian Fainelli 
145a490631fSFlorian Fainelli 	/* AFE_VDAC_OTHERS_0, set 1000BT Cidac=010 for all ports */
146a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_VDAC_OTHERS_0, 0xa020);
147a490631fSFlorian Fainelli 
148a490631fSFlorian Fainelli 	/* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal
149a490631fSFlorian Fainelli 	 * offset for HT=0 code
150a490631fSFlorian Fainelli 	 */
151a490631fSFlorian Fainelli 	phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3);
152a490631fSFlorian Fainelli 
153a490631fSFlorian Fainelli 	/* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */
154a490631fSFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0x0010);
155a490631fSFlorian Fainelli 
156a490631fSFlorian Fainelli 	/* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */
157a490631fSFlorian Fainelli 	phy_write_misc(phydev, DSP_TAP10, 0x011b);
158a490631fSFlorian Fainelli 
159a490631fSFlorian Fainelli 	/* Reset R_CAL/RC_CAL engine */
160a490631fSFlorian Fainelli 	r_rc_cal_reset(phydev);
161a490631fSFlorian Fainelli 
162a490631fSFlorian Fainelli 	return 0;
163a490631fSFlorian Fainelli }
164a490631fSFlorian Fainelli 
1650c2fdc25SFlorian Fainelli static int bcm7xxx_28nm_e0_plus_afe_config_init(struct phy_device *phydev)
1660c2fdc25SFlorian Fainelli {
1670c2fdc25SFlorian Fainelli 	/* AFE_RXCONFIG_1, provide more margin for INL/DNL measurement */
1680c2fdc25SFlorian Fainelli 	phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9b2f);
1690c2fdc25SFlorian Fainelli 
1700c2fdc25SFlorian Fainelli 	/* AFE_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */
1710c2fdc25SFlorian Fainelli 	phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da);
1720c2fdc25SFlorian Fainelli 
1730c2fdc25SFlorian Fainelli 	/* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal
1740c2fdc25SFlorian Fainelli 	 * offset for HT=0 code
1750c2fdc25SFlorian Fainelli 	 */
1760c2fdc25SFlorian Fainelli 	phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3);
1770c2fdc25SFlorian Fainelli 
1780c2fdc25SFlorian Fainelli 	/* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */
1790c2fdc25SFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0x0010);
1800c2fdc25SFlorian Fainelli 
1810c2fdc25SFlorian Fainelli 	/* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */
1820c2fdc25SFlorian Fainelli 	phy_write_misc(phydev, DSP_TAP10, 0x011b);
1830c2fdc25SFlorian Fainelli 
1840c2fdc25SFlorian Fainelli 	/* Reset R_CAL/RC_CAL engine */
1850c2fdc25SFlorian Fainelli 	r_rc_cal_reset(phydev);
1860c2fdc25SFlorian Fainelli 
1870c2fdc25SFlorian Fainelli 	return 0;
1880c2fdc25SFlorian Fainelli }
1890c2fdc25SFlorian Fainelli 
1909df54ddaSFlorian Fainelli static int bcm7xxx_apd_enable(struct phy_device *phydev)
1919df54ddaSFlorian Fainelli {
1929df54ddaSFlorian Fainelli 	int val;
1939df54ddaSFlorian Fainelli 
1949df54ddaSFlorian Fainelli 	/* Enable powering down of the DLL during auto-power down */
1959df54ddaSFlorian Fainelli 	val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_SCR3);
1969df54ddaSFlorian Fainelli 	if (val < 0)
1979df54ddaSFlorian Fainelli 		return val;
1989df54ddaSFlorian Fainelli 
1999df54ddaSFlorian Fainelli 	val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
2009df54ddaSFlorian Fainelli 	bcm54xx_shadow_write(phydev, BCM54XX_SHD_SCR3, val);
2019df54ddaSFlorian Fainelli 
2029df54ddaSFlorian Fainelli 	/* Enable auto-power down */
2039df54ddaSFlorian Fainelli 	val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_APD);
2049df54ddaSFlorian Fainelli 	if (val < 0)
2059df54ddaSFlorian Fainelli 		return val;
2069df54ddaSFlorian Fainelli 
2079df54ddaSFlorian Fainelli 	val |= BCM54XX_SHD_APD_EN;
2089df54ddaSFlorian Fainelli 	return bcm54xx_shadow_write(phydev, BCM54XX_SHD_APD, val);
2099df54ddaSFlorian Fainelli }
2109df54ddaSFlorian Fainelli 
211b8f9a029SFlorian Fainelli static int bcm7xxx_eee_enable(struct phy_device *phydev)
212b8f9a029SFlorian Fainelli {
213b8f9a029SFlorian Fainelli 	int val;
214b8f9a029SFlorian Fainelli 
215b8f9a029SFlorian Fainelli 	val = phy_read_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL,
216b8f9a029SFlorian Fainelli 				    MDIO_MMD_AN, phydev->addr);
217b8f9a029SFlorian Fainelli 	if (val < 0)
218b8f9a029SFlorian Fainelli 		return val;
219b8f9a029SFlorian Fainelli 
220b8f9a029SFlorian Fainelli 	/* Enable general EEE feature at the PHY level */
221b8f9a029SFlorian Fainelli 	val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X;
222b8f9a029SFlorian Fainelli 
223b8f9a029SFlorian Fainelli 	phy_write_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL,
224b8f9a029SFlorian Fainelli 			       MDIO_MMD_AN, phydev->addr, val);
225b8f9a029SFlorian Fainelli 
226b8f9a029SFlorian Fainelli 	/* Advertise supported modes */
227b8f9a029SFlorian Fainelli 	val = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV,
228b8f9a029SFlorian Fainelli 				    MDIO_MMD_AN, phydev->addr);
229b8f9a029SFlorian Fainelli 
230b8f9a029SFlorian Fainelli 	val |= (MDIO_AN_EEE_ADV_100TX | MDIO_AN_EEE_ADV_1000T);
231b8f9a029SFlorian Fainelli 	phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV,
232b8f9a029SFlorian Fainelli 			       MDIO_MMD_AN, phydev->addr, val);
233b8f9a029SFlorian Fainelli 
234b8f9a029SFlorian Fainelli 	return 0;
235b8f9a029SFlorian Fainelli }
236b8f9a029SFlorian Fainelli 
237b560a58cSFlorian Fainelli static int bcm7xxx_28nm_config_init(struct phy_device *phydev)
238b560a58cSFlorian Fainelli {
239d8ebfed3SFlorian Fainelli 	u8 rev = PHY_BRCM_7XXX_REV(phydev->dev_flags);
240d8ebfed3SFlorian Fainelli 	u8 patch = PHY_BRCM_7XXX_PATCH(phydev->dev_flags);
241d8ebfed3SFlorian Fainelli 	int ret = 0;
242b560a58cSFlorian Fainelli 
2436ec259c1SFlorian Fainelli 	pr_info_once("%s: %s PHY revision: 0x%02x, patch: %d\n",
2446ec259c1SFlorian Fainelli 		     dev_name(&phydev->dev), phydev->drv->name, rev, patch);
245d8ebfed3SFlorian Fainelli 
246d8ebfed3SFlorian Fainelli 	switch (rev) {
247d8ebfed3SFlorian Fainelli 	case 0xb0:
2482a9df742SFlorian Fainelli 		ret = bcm7xxx_28nm_b0_afe_config_init(phydev);
249d8ebfed3SFlorian Fainelli 		break;
250a490631fSFlorian Fainelli 	case 0xd0:
251a490631fSFlorian Fainelli 		ret = bcm7xxx_28nm_d0_afe_config_init(phydev);
252a490631fSFlorian Fainelli 		break;
2530c2fdc25SFlorian Fainelli 	case 0xe0:
2540c2fdc25SFlorian Fainelli 	case 0xf0:
25560efff0cSFlorian Fainelli 	/* Rev G0 introduces a roll over */
25660efff0cSFlorian Fainelli 	case 0x10:
2570c2fdc25SFlorian Fainelli 		ret = bcm7xxx_28nm_e0_plus_afe_config_init(phydev);
2580c2fdc25SFlorian Fainelli 		break;
259d8ebfed3SFlorian Fainelli 	default:
260d8ebfed3SFlorian Fainelli 		break;
261d8ebfed3SFlorian Fainelli 	}
262d8ebfed3SFlorian Fainelli 
2639df54ddaSFlorian Fainelli 	if (ret)
2649df54ddaSFlorian Fainelli 		return ret;
2659df54ddaSFlorian Fainelli 
266b8f9a029SFlorian Fainelli 	ret = bcm7xxx_eee_enable(phydev);
267b8f9a029SFlorian Fainelli 	if (ret)
268b8f9a029SFlorian Fainelli 		return ret;
269b8f9a029SFlorian Fainelli 
2709df54ddaSFlorian Fainelli 	return bcm7xxx_apd_enable(phydev);
271b560a58cSFlorian Fainelli }
272b560a58cSFlorian Fainelli 
2734fd14e0bSFlorian Fainelli static int bcm7xxx_28nm_resume(struct phy_device *phydev)
2744fd14e0bSFlorian Fainelli {
2754fd14e0bSFlorian Fainelli 	int ret;
2764fd14e0bSFlorian Fainelli 
2774fd14e0bSFlorian Fainelli 	/* Re-apply workarounds coming out suspend/resume */
2784fd14e0bSFlorian Fainelli 	ret = bcm7xxx_28nm_config_init(phydev);
2794fd14e0bSFlorian Fainelli 	if (ret)
2804fd14e0bSFlorian Fainelli 		return ret;
2814fd14e0bSFlorian Fainelli 
2824fd14e0bSFlorian Fainelli 	/* 28nm Gigabit PHYs come out of reset without any half-duplex
2834fd14e0bSFlorian Fainelli 	 * or "hub" compliant advertised mode, fix that. This does not
2844fd14e0bSFlorian Fainelli 	 * cause any problems with the PHY library since genphy_config_aneg()
2854fd14e0bSFlorian Fainelli 	 * gracefully handles auto-negotiated and forced modes.
2864fd14e0bSFlorian Fainelli 	 */
2874fd14e0bSFlorian Fainelli 	return genphy_config_aneg(phydev);
2884fd14e0bSFlorian Fainelli }
2894fd14e0bSFlorian Fainelli 
290b560a58cSFlorian Fainelli static int phy_set_clr_bits(struct phy_device *dev, int location,
291b560a58cSFlorian Fainelli 					int set_mask, int clr_mask)
292b560a58cSFlorian Fainelli {
293b560a58cSFlorian Fainelli 	int v, ret;
294b560a58cSFlorian Fainelli 
295b560a58cSFlorian Fainelli 	v = phy_read(dev, location);
296b560a58cSFlorian Fainelli 	if (v < 0)
297b560a58cSFlorian Fainelli 		return v;
298b560a58cSFlorian Fainelli 
299b560a58cSFlorian Fainelli 	v &= ~clr_mask;
300b560a58cSFlorian Fainelli 	v |= set_mask;
301b560a58cSFlorian Fainelli 
302b560a58cSFlorian Fainelli 	ret = phy_write(dev, location, v);
303b560a58cSFlorian Fainelli 	if (ret < 0)
304b560a58cSFlorian Fainelli 		return ret;
305b560a58cSFlorian Fainelli 
306b560a58cSFlorian Fainelli 	return v;
307b560a58cSFlorian Fainelli }
308b560a58cSFlorian Fainelli 
309b560a58cSFlorian Fainelli static int bcm7xxx_config_init(struct phy_device *phydev)
310b560a58cSFlorian Fainelli {
311b560a58cSFlorian Fainelli 	int ret;
312b560a58cSFlorian Fainelli 
313b560a58cSFlorian Fainelli 	/* Enable 64 clock MDIO */
314b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO);
315b560a58cSFlorian Fainelli 	phy_read(phydev, MII_BCM7XXX_AUX_MODE);
316b560a58cSFlorian Fainelli 
317e18556eeSFlorian Fainelli 	/* Workaround only required for 100Mbits/sec capable PHYs */
318e18556eeSFlorian Fainelli 	if (phydev->supported & PHY_GBIT_FEATURES)
319b560a58cSFlorian Fainelli 		return 0;
320b560a58cSFlorian Fainelli 
321b560a58cSFlorian Fainelli 	/* set shadow mode 2 */
322b560a58cSFlorian Fainelli 	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
323b560a58cSFlorian Fainelli 			MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2);
324b560a58cSFlorian Fainelli 	if (ret < 0)
325b560a58cSFlorian Fainelli 		return ret;
326b560a58cSFlorian Fainelli 
327b560a58cSFlorian Fainelli 	/* set iddq_clkbias */
328b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00);
329b560a58cSFlorian Fainelli 	udelay(10);
330b560a58cSFlorian Fainelli 
331b560a58cSFlorian Fainelli 	/* reset iddq_clkbias */
332b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00);
333b560a58cSFlorian Fainelli 
334b560a58cSFlorian Fainelli 	phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555);
335b560a58cSFlorian Fainelli 
336b560a58cSFlorian Fainelli 	/* reset shadow mode 2 */
337b560a58cSFlorian Fainelli 	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, MII_BCM7XXX_SHD_MODE_2, 0);
338b560a58cSFlorian Fainelli 	if (ret < 0)
339b560a58cSFlorian Fainelli 		return ret;
340b560a58cSFlorian Fainelli 
341b560a58cSFlorian Fainelli 	return 0;
342b560a58cSFlorian Fainelli }
343b560a58cSFlorian Fainelli 
344b560a58cSFlorian Fainelli /* Workaround for putting the PHY in IDDQ mode, required
34582c084f5SFlorian Fainelli  * for all BCM7XXX 40nm and 65nm PHYs
346b560a58cSFlorian Fainelli  */
347b560a58cSFlorian Fainelli static int bcm7xxx_suspend(struct phy_device *phydev)
348b560a58cSFlorian Fainelli {
349b560a58cSFlorian Fainelli 	int ret;
350b560a58cSFlorian Fainelli 	const struct bcm7xxx_regs {
351b560a58cSFlorian Fainelli 		int reg;
352b560a58cSFlorian Fainelli 		u16 value;
353b560a58cSFlorian Fainelli 	} bcm7xxx_suspend_cfg[] = {
354b560a58cSFlorian Fainelli 		{ MII_BCM7XXX_TEST, 0x008b },
355b560a58cSFlorian Fainelli 		{ MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 },
356b560a58cSFlorian Fainelli 		{ MII_BCM7XXX_100TX_DISC, 0x7000 },
357b560a58cSFlorian Fainelli 		{ MII_BCM7XXX_TEST, 0x000f },
358b560a58cSFlorian Fainelli 		{ MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 },
359b560a58cSFlorian Fainelli 		{ MII_BCM7XXX_TEST, 0x000b },
360b560a58cSFlorian Fainelli 	};
361b560a58cSFlorian Fainelli 	unsigned int i;
362b560a58cSFlorian Fainelli 
363b560a58cSFlorian Fainelli 	for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) {
364b560a58cSFlorian Fainelli 		ret = phy_write(phydev,
365b560a58cSFlorian Fainelli 				bcm7xxx_suspend_cfg[i].reg,
366b560a58cSFlorian Fainelli 				bcm7xxx_suspend_cfg[i].value);
367b560a58cSFlorian Fainelli 		if (ret)
368b560a58cSFlorian Fainelli 			return ret;
369b560a58cSFlorian Fainelli 	}
370b560a58cSFlorian Fainelli 
371b560a58cSFlorian Fainelli 	return 0;
372b560a58cSFlorian Fainelli }
373b560a58cSFlorian Fainelli 
374b560a58cSFlorian Fainelli static int bcm7xxx_dummy_config_init(struct phy_device *phydev)
375b560a58cSFlorian Fainelli {
376b560a58cSFlorian Fainelli 	return 0;
377b560a58cSFlorian Fainelli }
378b560a58cSFlorian Fainelli 
379153df3c7SFlorian Fainelli #define BCM7XXX_28NM_GPHY(_oui, _name)					\
380153df3c7SFlorian Fainelli {									\
381153df3c7SFlorian Fainelli 	.phy_id		= (_oui),					\
382153df3c7SFlorian Fainelli 	.phy_id_mask	= 0xfffffff0,					\
383153df3c7SFlorian Fainelli 	.name		= _name,					\
384153df3c7SFlorian Fainelli 	.features	= PHY_GBIT_FEATURES |				\
385153df3c7SFlorian Fainelli 			  SUPPORTED_Pause | SUPPORTED_Asym_Pause,	\
386153df3c7SFlorian Fainelli 	.flags		= PHY_IS_INTERNAL,				\
3872a9df742SFlorian Fainelli 	.config_init	= bcm7xxx_28nm_config_init,			\
388153df3c7SFlorian Fainelli 	.config_aneg	= genphy_config_aneg,				\
389153df3c7SFlorian Fainelli 	.read_status	= genphy_read_status,				\
390153df3c7SFlorian Fainelli 	.resume		= bcm7xxx_28nm_resume,				\
391153df3c7SFlorian Fainelli 	.driver		= { .owner = THIS_MODULE },			\
392153df3c7SFlorian Fainelli }
393153df3c7SFlorian Fainelli 
394b560a58cSFlorian Fainelli static struct phy_driver bcm7xxx_driver[] = {
395430ad68fSFlorian Fainelli 	BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"),
396430ad68fSFlorian Fainelli 	BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"),
397153df3c7SFlorian Fainelli 	BCM7XXX_28NM_GPHY(PHY_ID_BCM7366, "Broadcom BCM7366"),
398153df3c7SFlorian Fainelli 	BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"),
39959e33c2bSFlorian Fainelli 	BCM7XXX_28NM_GPHY(PHY_ID_BCM7439_2, "Broadcom BCM7439 (2)"),
400153df3c7SFlorian Fainelli 	BCM7XXX_28NM_GPHY(PHY_ID_BCM7445, "Broadcom BCM7445"),
401b560a58cSFlorian Fainelli {
402d068b02cSPetri Gynther 	.phy_id         = PHY_ID_BCM7425,
403d068b02cSPetri Gynther 	.phy_id_mask    = 0xfffffff0,
404d068b02cSPetri Gynther 	.name           = "Broadcom BCM7425",
405d068b02cSPetri Gynther 	.features       = PHY_GBIT_FEATURES |
406d068b02cSPetri Gynther 			  SUPPORTED_Pause | SUPPORTED_Asym_Pause,
407d068b02cSPetri Gynther 	.flags          = 0,
408d068b02cSPetri Gynther 	.config_init    = bcm7xxx_config_init,
409d068b02cSPetri Gynther 	.config_aneg    = genphy_config_aneg,
410d068b02cSPetri Gynther 	.read_status    = genphy_read_status,
411d068b02cSPetri Gynther 	.suspend        = bcm7xxx_suspend,
412d068b02cSPetri Gynther 	.resume         = bcm7xxx_config_init,
413d068b02cSPetri Gynther 	.driver         = { .owner = THIS_MODULE },
414d068b02cSPetri Gynther }, {
415d068b02cSPetri Gynther 	.phy_id         = PHY_ID_BCM7429,
416d068b02cSPetri Gynther 	.phy_id_mask    = 0xfffffff0,
417d068b02cSPetri Gynther 	.name           = "Broadcom BCM7429",
418d068b02cSPetri Gynther 	.features       = PHY_GBIT_FEATURES |
419d068b02cSPetri Gynther 			  SUPPORTED_Pause | SUPPORTED_Asym_Pause,
420d068b02cSPetri Gynther 	.flags          = PHY_IS_INTERNAL,
421d068b02cSPetri Gynther 	.config_init    = bcm7xxx_config_init,
422d068b02cSPetri Gynther 	.config_aneg    = genphy_config_aneg,
423d068b02cSPetri Gynther 	.read_status    = genphy_read_status,
424d068b02cSPetri Gynther 	.suspend        = bcm7xxx_suspend,
425d068b02cSPetri Gynther 	.resume         = bcm7xxx_config_init,
426d068b02cSPetri Gynther 	.driver         = { .owner = THIS_MODULE },
427d068b02cSPetri Gynther }, {
428b560a58cSFlorian Fainelli 	.phy_id		= PHY_BCM_OUI_4,
429b560a58cSFlorian Fainelli 	.phy_id_mask	= 0xffff0000,
430b560a58cSFlorian Fainelli 	.name		= "Broadcom BCM7XXX 40nm",
431b560a58cSFlorian Fainelli 	.features	= PHY_GBIT_FEATURES |
432b560a58cSFlorian Fainelli 			  SUPPORTED_Pause | SUPPORTED_Asym_Pause,
433b560a58cSFlorian Fainelli 	.flags		= PHY_IS_INTERNAL,
434b560a58cSFlorian Fainelli 	.config_init	= bcm7xxx_config_init,
435b560a58cSFlorian Fainelli 	.config_aneg	= genphy_config_aneg,
436b560a58cSFlorian Fainelli 	.read_status	= genphy_read_status,
437b560a58cSFlorian Fainelli 	.suspend	= bcm7xxx_suspend,
438b560a58cSFlorian Fainelli 	.resume		= bcm7xxx_config_init,
439b560a58cSFlorian Fainelli 	.driver		= { .owner = THIS_MODULE },
440b560a58cSFlorian Fainelli }, {
441b560a58cSFlorian Fainelli 	.phy_id		= PHY_BCM_OUI_5,
442b560a58cSFlorian Fainelli 	.phy_id_mask	= 0xffffff00,
443b560a58cSFlorian Fainelli 	.name		= "Broadcom BCM7XXX 65nm",
444b560a58cSFlorian Fainelli 	.features	= PHY_BASIC_FEATURES |
445b560a58cSFlorian Fainelli 			  SUPPORTED_Pause | SUPPORTED_Asym_Pause,
446b560a58cSFlorian Fainelli 	.flags		= PHY_IS_INTERNAL,
447b560a58cSFlorian Fainelli 	.config_init	= bcm7xxx_dummy_config_init,
448b560a58cSFlorian Fainelli 	.config_aneg	= genphy_config_aneg,
449b560a58cSFlorian Fainelli 	.read_status	= genphy_read_status,
450b560a58cSFlorian Fainelli 	.suspend	= bcm7xxx_suspend,
451b560a58cSFlorian Fainelli 	.resume		= bcm7xxx_config_init,
452b560a58cSFlorian Fainelli 	.driver		= { .owner = THIS_MODULE },
453b560a58cSFlorian Fainelli } };
454b560a58cSFlorian Fainelli 
455b560a58cSFlorian Fainelli static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
456430ad68fSFlorian Fainelli 	{ PHY_ID_BCM7250, 0xfffffff0, },
457430ad68fSFlorian Fainelli 	{ PHY_ID_BCM7364, 0xfffffff0, },
458b560a58cSFlorian Fainelli 	{ PHY_ID_BCM7366, 0xfffffff0, },
459d068b02cSPetri Gynther 	{ PHY_ID_BCM7425, 0xfffffff0, },
460d068b02cSPetri Gynther 	{ PHY_ID_BCM7429, 0xfffffff0, },
461b560a58cSFlorian Fainelli 	{ PHY_ID_BCM7439, 0xfffffff0, },
462b560a58cSFlorian Fainelli 	{ PHY_ID_BCM7445, 0xfffffff0, },
463b560a58cSFlorian Fainelli 	{ PHY_BCM_OUI_4, 0xffff0000 },
464b560a58cSFlorian Fainelli 	{ PHY_BCM_OUI_5, 0xffffff00 },
465b560a58cSFlorian Fainelli 	{ }
466b560a58cSFlorian Fainelli };
467b560a58cSFlorian Fainelli 
46850fd7150SJohan Hovold module_phy_driver(bcm7xxx_driver);
469b560a58cSFlorian Fainelli 
470b560a58cSFlorian Fainelli MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl);
471b560a58cSFlorian Fainelli 
472b560a58cSFlorian Fainelli MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver");
473b560a58cSFlorian Fainelli MODULE_LICENSE("GPL");
474b560a58cSFlorian Fainelli MODULE_AUTHOR("Broadcom Corporation");
475