blob: 427ac6013baeeff557063bbf7b376b5e82a9b976 [file] [log] [blame]
Andy Fleming9082eea2011-04-07 21:56:05 -05001/*
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 */
48static int bcm5461_config(struct phy_device *phydev)
49{
50 genphy_config_aneg(phydev);
51
52 phy_reset(phydev);
53
54 return 0;
55}
56
57static 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
99static 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 */
116static u32 bcm5482_read_wirespeed(struct phy_device *phydev, u32 reg)
117{
118 return (phy_read(phydev, MDIO_DEVAD_NONE, reg) & 0x8FFF) | 0x8010;
119}
120
121static 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 */
158static 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 */
192static 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 */
236static 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
251static 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
261static 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
271static 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
281int 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}