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