xref: /openbmc/u-boot/drivers/net/phy/broadcom.c (revision 98a48c5d)
1 /*
2  * Broadcom PHY drivers
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of
7  * the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
17  * MA 02111-1307 USA
18  *
19  * Copyright 2010-2011 Freescale Semiconductor, Inc.
20  * author Andy Fleming
21  *
22  */
23 #include <config.h>
24 #include <common.h>
25 #include <phy.h>
26 
27 /* Broadcom BCM54xx -- taken from linux sungem_phy */
28 #define MIIM_BCM54xx_AUXCNTL			0x18
29 #define MIIM_BCM54xx_AUXCNTL_ENCODE(val) (((val & 0x7) << 12)|(val & 0x7))
30 #define MIIM_BCM54xx_AUXSTATUS			0x19
31 #define MIIM_BCM54xx_AUXSTATUS_LINKMODE_MASK	0x0700
32 #define MIIM_BCM54xx_AUXSTATUS_LINKMODE_SHIFT	8
33 
34 #define MIIM_BCM54XX_SHD			0x1c
35 #define MIIM_BCM54XX_SHD_WRITE			0x8000
36 #define MIIM_BCM54XX_SHD_VAL(x)			((x & 0x1f) << 10)
37 #define MIIM_BCM54XX_SHD_DATA(x)		((x & 0x3ff) << 0)
38 #define MIIM_BCM54XX_SHD_WR_ENCODE(val, data)	\
39 	(MIIM_BCM54XX_SHD_WRITE | MIIM_BCM54XX_SHD_VAL(val) | \
40 	 MIIM_BCM54XX_SHD_DATA(data))
41 
42 #define MIIM_BCM54XX_EXP_DATA		0x15	/* Expansion register data */
43 #define MIIM_BCM54XX_EXP_SEL		0x17	/* Expansion register select */
44 #define MIIM_BCM54XX_EXP_SEL_SSD	0x0e00	/* Secondary SerDes select */
45 #define MIIM_BCM54XX_EXP_SEL_ER		0x0f00	/* Expansion register select */
46 
47 /* Broadcom BCM5461S */
48 static int bcm5461_config(struct phy_device *phydev)
49 {
50 	genphy_config_aneg(phydev);
51 
52 	phy_reset(phydev);
53 
54 	return 0;
55 }
56 
57 static int bcm54xx_parse_status(struct phy_device *phydev)
58 {
59 	unsigned int mii_reg;
60 
61 	mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXSTATUS);
62 
63 	switch ((mii_reg & MIIM_BCM54xx_AUXSTATUS_LINKMODE_MASK) >>
64 			MIIM_BCM54xx_AUXSTATUS_LINKMODE_SHIFT) {
65 	case 1:
66 		phydev->duplex = DUPLEX_HALF;
67 		phydev->speed = SPEED_10;
68 		break;
69 	case 2:
70 		phydev->duplex = DUPLEX_FULL;
71 		phydev->speed = SPEED_10;
72 		break;
73 	case 3:
74 		phydev->duplex = DUPLEX_HALF;
75 		phydev->speed = SPEED_100;
76 		break;
77 	case 5:
78 		phydev->duplex = DUPLEX_FULL;
79 		phydev->speed = SPEED_100;
80 		break;
81 	case 6:
82 		phydev->duplex = DUPLEX_HALF;
83 		phydev->speed = SPEED_1000;
84 		break;
85 	case 7:
86 		phydev->duplex = DUPLEX_FULL;
87 		phydev->speed = SPEED_1000;
88 		break;
89 	default:
90 		printf("Auto-neg error, defaulting to 10BT/HD\n");
91 		phydev->duplex = DUPLEX_HALF;
92 		phydev->speed = SPEED_10;
93 		break;
94 	}
95 
96 	return 0;
97 }
98 
99 static int bcm54xx_startup(struct phy_device *phydev)
100 {
101 	/* Read the Status (2x to make sure link is right) */
102 	genphy_update_link(phydev);
103 	bcm54xx_parse_status(phydev);
104 
105 	return 0;
106 }
107 
108 /* Broadcom BCM5482S */
109 /*
110  * "Ethernet@Wirespeed" needs to be enabled to achieve link in certain
111  * circumstances.  eg a gigabit TSEC connected to a gigabit switch with
112  * a 4-wire ethernet cable.  Both ends advertise gigabit, but can't
113  * link.  "Ethernet@Wirespeed" reduces advertised speed until link
114  * can be achieved.
115  */
116 static u32 bcm5482_read_wirespeed(struct phy_device *phydev, u32 reg)
117 {
118 	return (phy_read(phydev, MDIO_DEVAD_NONE, reg) & 0x8FFF) | 0x8010;
119 }
120 
121 static int bcm5482_config(struct phy_device *phydev)
122 {
123 	unsigned int reg;
124 
125 	/* reset the PHY */
126 	reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
127 	reg |= BMCR_RESET;
128 	phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, reg);
129 
130 	/* Setup read from auxilary control shadow register 7 */
131 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL,
132 			MIIM_BCM54xx_AUXCNTL_ENCODE(7));
133 	/* Read Misc Control register and or in Ethernet@Wirespeed */
134 	reg = bcm5482_read_wirespeed(phydev, MIIM_BCM54xx_AUXCNTL);
135 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, reg);
136 
137 	/* Initial config/enable of secondary SerDes interface */
138 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_SHD,
139 			MIIM_BCM54XX_SHD_WR_ENCODE(0x14, 0xf));
140 	/* Write intial value to secondary SerDes Contol */
141 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL,
142 			MIIM_BCM54XX_EXP_SEL_SSD | 0);
143 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA,
144 			BMCR_ANRESTART);
145 	/* Enable copper/fiber auto-detect */
146 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_SHD,
147 			MIIM_BCM54XX_SHD_WR_ENCODE(0x1e, 0x201));
148 
149 	genphy_config_aneg(phydev);
150 
151 	return 0;
152 }
153 
154 /*
155  * Find out if PHY is in copper or serdes mode by looking at Expansion Reg
156  * 0x42 - "Operating Mode Status Register"
157  */
158 static int bcm5482_is_serdes(struct phy_device *phydev)
159 {
160 	u16 val;
161 	int serdes = 0;
162 
163 	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL,
164 			MIIM_BCM54XX_EXP_SEL_ER | 0x42);
165 	val = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA);
166 
167 	switch (val & 0x1f) {
168 	case 0x0d:	/* RGMII-to-100Base-FX */
169 	case 0x0e:	/* RGMII-to-SGMII */
170 	case 0x0f:	/* RGMII-to-SerDes */
171 	case 0x12:	/* SGMII-to-SerDes */
172 	case 0x13:	/* SGMII-to-100Base-FX */
173 	case 0x16:	/* SerDes-to-Serdes */
174 		serdes = 1;
175 		break;
176 	case 0x6:	/* RGMII-to-Copper */
177 	case 0x14:	/* SGMII-to-Copper */
178 	case 0x17:	/* SerDes-to-Copper */
179 		break;
180 	default:
181 		printf("ERROR, invalid PHY mode (0x%x\n)", val);
182 		break;
183 	}
184 
185 	return serdes;
186 }
187 
188 /*
189  * Determine SerDes link speed and duplex from Expansion reg 0x42 "Operating
190  * Mode Status Register"
191  */
192 static u32 bcm5482_parse_serdes_sr(struct phy_device *phydev)
193 {
194 	u16 val;
195 	int i = 0;
196 
197 	/* Wait 1s for link - Clause 37 autonegotiation happens very fast */
198 	while (1) {
199 		phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL,
200 				MIIM_BCM54XX_EXP_SEL_ER | 0x42);
201 		val = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA);
202 
203 		if (val & 0x8000)
204 			break;
205 
206 		if (i++ > 1000) {
207 			phydev->link = 0;
208 			return 1;
209 		}
210 
211 		udelay(1000);	/* 1 ms */
212 	}
213 
214 	phydev->link = 1;
215 	switch ((val >> 13) & 0x3) {
216 	case (0x00):
217 		phydev->speed = 10;
218 		break;
219 	case (0x01):
220 		phydev->speed = 100;
221 		break;
222 	case (0x02):
223 		phydev->speed = 1000;
224 		break;
225 	}
226 
227 	phydev->duplex = (val & 0x1000) == 0x1000;
228 
229 	return 0;
230 }
231 
232 /*
233  * Figure out if BCM5482 is in serdes or copper mode and determine link
234  * configuration accordingly
235  */
236 static int bcm5482_startup(struct phy_device *phydev)
237 {
238 	if (bcm5482_is_serdes(phydev)) {
239 		bcm5482_parse_serdes_sr(phydev);
240 		phydev->port = PORT_FIBRE;
241 	} else {
242 		/* Wait for auto-negotiation to complete or fail */
243 		genphy_update_link(phydev);
244 		/* Parse BCM54xx copper aux status register */
245 		bcm54xx_parse_status(phydev);
246 	}
247 
248 	return 0;
249 }
250 
251 static struct phy_driver BCM5461S_driver = {
252 	.name = "Broadcom BCM5461S",
253 	.uid = 0x2060c0,
254 	.mask = 0xfffff0,
255 	.features = PHY_GBIT_FEATURES,
256 	.config = &bcm5461_config,
257 	.startup = &bcm54xx_startup,
258 	.shutdown = &genphy_shutdown,
259 };
260 
261 static struct phy_driver BCM5464S_driver = {
262 	.name = "Broadcom BCM5464S",
263 	.uid = 0x2060b0,
264 	.mask = 0xfffff0,
265 	.features = PHY_GBIT_FEATURES,
266 	.config = &bcm5461_config,
267 	.startup = &bcm54xx_startup,
268 	.shutdown = &genphy_shutdown,
269 };
270 
271 static struct phy_driver BCM5482S_driver = {
272 	.name = "Broadcom BCM5482S",
273 	.uid = 0x143bcb0,
274 	.mask = 0xffffff0,
275 	.features = PHY_GBIT_FEATURES,
276 	.config = &bcm5482_config,
277 	.startup = &bcm5482_startup,
278 	.shutdown = &genphy_shutdown,
279 };
280 
281 int phy_broadcom_init(void)
282 {
283 	phy_register(&BCM5482S_driver);
284 	phy_register(&BCM5464S_driver);
285 	phy_register(&BCM5461S_driver);
286 
287 	return 0;
288 }
289