1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Broadcom PHY drivers 4 * 5 * Copyright 2010-2011 Freescale Semiconductor, Inc. 6 * author Andy Fleming 7 */ 8#include <common.h> 9#include <phy.h> 10#include <linux/delay.h> 11 12/* Broadcom BCM54xx -- taken from linux sungem_phy */ 13#define MIIM_BCM54xx_AUXCNTL 0x18 14#define MIIM_BCM54xx_AUXCNTL_ENCODE(val) (((val & 0x7) << 12)|(val & 0x7)) 15#define MIIM_BCM54xx_AUXSTATUS 0x19 16#define MIIM_BCM54xx_AUXSTATUS_LINKMODE_MASK 0x0700 17#define MIIM_BCM54xx_AUXSTATUS_LINKMODE_SHIFT 8 18 19#define MIIM_BCM54XX_SHD 0x1c 20#define MIIM_BCM54XX_SHD_WRITE 0x8000 21#define MIIM_BCM54XX_SHD_VAL(x) ((x & 0x1f) << 10) 22#define MIIM_BCM54XX_SHD_DATA(x) ((x & 0x3ff) << 0) 23#define MIIM_BCM54XX_SHD_WR_ENCODE(val, data) \ 24 (MIIM_BCM54XX_SHD_WRITE | MIIM_BCM54XX_SHD_VAL(val) | \ 25 MIIM_BCM54XX_SHD_DATA(data)) 26 27#define MIIM_BCM54XX_EXP_DATA 0x15 /* Expansion register data */ 28#define MIIM_BCM54XX_EXP_SEL 0x17 /* Expansion register select */ 29#define MIIM_BCM54XX_EXP_SEL_SSD 0x0e00 /* Secondary SerDes select */ 30#define MIIM_BCM54XX_EXP_SEL_ER 0x0f00 /* Expansion register select */ 31 32#define MIIM_BCM_AUXCNTL_SHDWSEL_MISC 0x0007 33#define MIIM_BCM_AUXCNTL_SHDWSEL_MISC_WIRESPEED_EN 0x0010 34#define MIIM_BCM_AUXCNTL_SHDWSEL_MISC_RGMII_EN 0x0080 35#define MIIM_BCM_AUXCNTL_SHDWSEL_MISC_RGMII_SKEW_EN 0x0100 36#define MIIM_BCM_AUXCNTL_MISC_FORCE_AMDIX 0x0200 37#define MIIM_BCM_AUXCNTL_ACTL_SMDSP_EN 0x0800 38#define MIIM_BCM_AUXCNTL_MISC_WREN 0x8000 39 40#define MIIM_BCM_CHANNEL_WIDTH 0x2000 41 42#define BCM54810_SHD_CLK_CTL 0x3 43#define BCM54810_SHD_CLK_CTL_GTXCLK_EN BIT(9) 44 45#define BCM54XX_SHD_LEDS1 0x0d 46#define BCM_LED_SRC_LINKSPD2 0x1 47#define BCM_LED_SRC_ACTIVITYLED 0x3 48#define BCM54XX_SHD_LEDS1_LED3(src) (((src) & 0xf) << 4) 49#define BCM54XX_SHD_LEDS1_LED1(src) (((src) & 0xf) << 0) 50 51static int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum) 52{ 53 /* The register must be written to both the Shadow Register Select and 54 * the Shadow Read Register Selector 55 */ 56 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, 57 MIIM_BCM54xx_AUXCNTL_ENCODE(regnum)); 58 return phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL); 59} 60 61static int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val) 62{ 63 return phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, regnum | val); 64} 65 66static int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow) 67{ 68 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_SHD, 69 MIIM_BCM54XX_SHD_VAL(shadow)); 70 return MIIM_BCM54XX_SHD_DATA(phy_read(phydev, MDIO_DEVAD_NONE, 71 MIIM_BCM54XX_SHD)); 72} 73 74static int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow, u16 val) 75{ 76 return phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_SHD, 77 MIIM_BCM54XX_SHD_WR_ENCODE(shadow, val)); 78} 79 80static int bcm54xx_config_clock_delay(struct phy_device *phydev) 81{ 82 int rc, val; 83 84 /* handling PHY's internal RX clock delay */ 85 val = bcm54xx_auxctl_read(phydev, MIIM_BCM_AUXCNTL_SHDWSEL_MISC); 86 val |= MIIM_BCM_AUXCNTL_MISC_WREN; 87 if (phydev->interface == PHY_INTERFACE_MODE_RGMII || 88 phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { 89 /* Disable RGMII RXC-RXD skew */ 90 val &= ~MIIM_BCM_AUXCNTL_SHDWSEL_MISC_RGMII_SKEW_EN; 91 } 92 if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || 93 phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { 94 /* Enable RGMII RXC-RXD skew */ 95 val |= MIIM_BCM_AUXCNTL_SHDWSEL_MISC_RGMII_SKEW_EN; 96 } 97 rc = bcm54xx_auxctl_write(phydev, MIIM_BCM_AUXCNTL_SHDWSEL_MISC, val); 98 if (rc < 0) 99 return rc; 100 101 /* handling PHY's internal TX clock delay */ 102 val = bcm_phy_read_shadow(phydev, BCM54810_SHD_CLK_CTL); 103 if (phydev->interface == PHY_INTERFACE_MODE_RGMII || 104 phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { 105 /* Disable internal TX clock delay */ 106 val &= ~BCM54810_SHD_CLK_CTL_GTXCLK_EN; 107 } 108 if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || 109 phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { 110 /* Enable internal TX clock delay */ 111 val |= BCM54810_SHD_CLK_CTL_GTXCLK_EN; 112 } 113 rc = bcm_phy_write_shadow(phydev, BCM54810_SHD_CLK_CTL, val); 114 if (rc < 0) 115 return rc; 116 117 return 0; 118} 119 120static void bcm_phy_write_misc(struct phy_device *phydev, 121 u16 reg, u16 chl, u16 value) 122{ 123 int reg_val; 124 125 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, 126 MIIM_BCM_AUXCNTL_SHDWSEL_MISC); 127 128 reg_val = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL); 129 reg_val |= MIIM_BCM_AUXCNTL_ACTL_SMDSP_EN; 130 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, reg_val); 131 132 reg_val = (chl * MIIM_BCM_CHANNEL_WIDTH) | reg; 133 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL, reg_val); 134 135 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA, value); 136} 137 138/* Broadcom BCM5461S */ 139static int bcm5461_config(struct phy_device *phydev) 140{ 141 genphy_config_aneg(phydev); 142 143 phy_reset(phydev); 144 145 return 0; 146} 147 148/* Broadcom BCM54210E */ 149static int bcm54210e_config(struct phy_device *phydev) 150{ 151 int ret; 152 153 ret = bcm54xx_config_clock_delay(phydev); 154 if (ret < 0) 155 return ret; 156 157 ret = bcm5461_config(phydev); 158 if (ret < 0) 159 return ret; 160 161 /* Configure LEDs to blink. */ 162 bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS1, 163 BCM54XX_SHD_LEDS1_LED1(BCM_LED_SRC_ACTIVITYLED) | 164 BCM54XX_SHD_LEDS1_LED3(BCM_LED_SRC_LINKSPD2)); 165 166 return 0; 167} 168 169static int bcm54xx_parse_status(struct phy_device *phydev) 170{ 171 unsigned int mii_reg; 172 173 mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXSTATUS); 174 175 switch ((mii_reg & MIIM_BCM54xx_AUXSTATUS_LINKMODE_MASK) >> 176 MIIM_BCM54xx_AUXSTATUS_LINKMODE_SHIFT) { 177 case 1: 178 phydev->duplex = DUPLEX_HALF; 179 phydev->speed = SPEED_10; 180 break; 181 case 2: 182 phydev->duplex = DUPLEX_FULL; 183 phydev->speed = SPEED_10; 184 break; 185 case 3: 186 phydev->duplex = DUPLEX_HALF; 187 phydev->speed = SPEED_100; 188 break; 189 case 5: 190 phydev->duplex = DUPLEX_FULL; 191 phydev->speed = SPEED_100; 192 break; 193 case 6: 194 phydev->duplex = DUPLEX_HALF; 195 phydev->speed = SPEED_1000; 196 break; 197 case 7: 198 phydev->duplex = DUPLEX_FULL; 199 phydev->speed = SPEED_1000; 200 break; 201 default: 202 printf("Auto-neg error, defaulting to 10BT/HD\n"); 203 phydev->duplex = DUPLEX_HALF; 204 phydev->speed = SPEED_10; 205 break; 206 } 207 208 return 0; 209} 210 211static int bcm54xx_startup(struct phy_device *phydev) 212{ 213 int ret; 214 215 /* Read the Status (2x to make sure link is right) */ 216 ret = genphy_update_link(phydev); 217 if (ret) 218 return ret; 219 220 return bcm54xx_parse_status(phydev); 221} 222 223/* Broadcom BCM5482S */ 224/* 225 * "Ethernet@Wirespeed" needs to be enabled to achieve link in certain 226 * circumstances. eg a gigabit TSEC connected to a gigabit switch with 227 * a 4-wire ethernet cable. Both ends advertise gigabit, but can't 228 * link. "Ethernet@Wirespeed" reduces advertised speed until link 229 * can be achieved. 230 */ 231static u32 bcm5482_read_wirespeed(struct phy_device *phydev, u32 reg) 232{ 233 return (phy_read(phydev, MDIO_DEVAD_NONE, reg) & 0x8FFF) | 0x8010; 234} 235 236static int bcm5482_config(struct phy_device *phydev) 237{ 238 unsigned int reg; 239 240 /* reset the PHY */ 241 reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); 242 reg |= BMCR_RESET; 243 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, reg); 244 245 /* Setup read from auxilary control shadow register 7 */ 246 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, 247 MIIM_BCM54xx_AUXCNTL_ENCODE(7)); 248 /* Read Misc Control register and or in Ethernet@Wirespeed */ 249 reg = bcm5482_read_wirespeed(phydev, MIIM_BCM54xx_AUXCNTL); 250 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, reg); 251 252 /* Initial config/enable of secondary SerDes interface */ 253 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_SHD, 254 MIIM_BCM54XX_SHD_WR_ENCODE(0x14, 0xf)); 255 /* Write intial value to secondary SerDes Contol */ 256 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL, 257 MIIM_BCM54XX_EXP_SEL_SSD | 0); 258 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA, 259 BMCR_ANRESTART); 260 /* Enable copper/fiber auto-detect */ 261 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_SHD, 262 MIIM_BCM54XX_SHD_WR_ENCODE(0x1e, 0x201)); 263 264 genphy_config_aneg(phydev); 265 266 return 0; 267} 268 269static void bcm_cygnus_afe(struct phy_device *phydev) 270{ 271 /* ensures smdspclk is enabled */ 272 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54xx_AUXCNTL, 0x0c30); 273 274 /* AFE_VDAC_ICTRL_0 bit 7:4 Iq=1100 for 1g 10bt, normal modes */ 275 bcm_phy_write_misc(phydev, 0x39, 0x01, 0xA7C8); 276 277 /* AFE_HPF_TRIM_OTHERS bit11=1, short cascode for all modes*/ 278 bcm_phy_write_misc(phydev, 0x3A, 0x00, 0x0803); 279 280 /* AFE_TX_CONFIG_1 bit 7:4 Iq=1100 for test modes */ 281 bcm_phy_write_misc(phydev, 0x3A, 0x01, 0xA740); 282 283 /* AFE TEMPSEN_OTHERS rcal_HT, rcal_LT 10000 */ 284 bcm_phy_write_misc(phydev, 0x3A, 0x03, 0x8400); 285 286 /* AFE_FUTURE_RSV bit 2:0 rccal <2:0>=100 */ 287 bcm_phy_write_misc(phydev, 0x3B, 0x00, 0x0004); 288 289 /* Adjust bias current trim to overcome digital offSet */ 290 phy_write(phydev, MDIO_DEVAD_NONE, 0x1E, 0x02); 291 292 /* make rcal=100, since rdb default is 000 */ 293 phy_write(phydev, MDIO_DEVAD_NONE, 0x17, 0x00B1); 294 phy_write(phydev, MDIO_DEVAD_NONE, 0x15, 0x0010); 295 296 /* CORE_EXPB0, Reset R_CAL/RC_CAL Engine */ 297 phy_write(phydev, MDIO_DEVAD_NONE, 0x17, 0x00B0); 298 phy_write(phydev, MDIO_DEVAD_NONE, 0x15, 0x0010); 299 300 /* CORE_EXPB0, Disable Reset R_CAL/RC_CAL Engine */ 301 phy_write(phydev, MDIO_DEVAD_NONE, 0x17, 0x00B0); 302 phy_write(phydev, MDIO_DEVAD_NONE, 0x15, 0x0000); 303} 304 305static int bcm_cygnus_config(struct phy_device *phydev) 306{ 307 genphy_config_aneg(phydev); 308 phy_reset(phydev); 309 /* AFE settings for PHY stability */ 310 bcm_cygnus_afe(phydev); 311 /* Forcing aneg after applying the AFE settings */ 312 genphy_restart_aneg(phydev); 313 314 return 0; 315} 316 317/* 318 * Find out if PHY is in copper or serdes mode by looking at Expansion Reg 319 * 0x42 - "Operating Mode Status Register" 320 */ 321static int bcm5482_is_serdes(struct phy_device *phydev) 322{ 323 u16 val; 324 int serdes = 0; 325 326 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL, 327 MIIM_BCM54XX_EXP_SEL_ER | 0x42); 328 val = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA); 329 330 switch (val & 0x1f) { 331 case 0x0d: /* RGMII-to-100Base-FX */ 332 case 0x0e: /* RGMII-to-SGMII */ 333 case 0x0f: /* RGMII-to-SerDes */ 334 case 0x12: /* SGMII-to-SerDes */ 335 case 0x13: /* SGMII-to-100Base-FX */ 336 case 0x16: /* SerDes-to-Serdes */ 337 serdes = 1; 338 break; 339 case 0x6: /* RGMII-to-Copper */ 340 case 0x14: /* SGMII-to-Copper */ 341 case 0x17: /* SerDes-to-Copper */ 342 break; 343 default: 344 printf("ERROR, invalid PHY mode (0x%x\n)", val); 345 break; 346 } 347 348 return serdes; 349} 350 351/* 352 * Determine SerDes link speed and duplex from Expansion reg 0x42 "Operating 353 * Mode Status Register" 354 */ 355static u32 bcm5482_parse_serdes_sr(struct phy_device *phydev) 356{ 357 u16 val; 358 int i = 0; 359 360 /* Wait 1s for link - Clause 37 autonegotiation happens very fast */ 361 while (1) { 362 phy_write(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_SEL, 363 MIIM_BCM54XX_EXP_SEL_ER | 0x42); 364 val = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_BCM54XX_EXP_DATA); 365 366 if (val & 0x8000) 367 break; 368 369 if (i++ > 1000) { 370 phydev->link = 0; 371 return 1; 372 } 373 374 udelay(1000); /* 1 ms */ 375 } 376 377 phydev->link = 1; 378 switch ((val >> 13) & 0x3) { 379 case (0x00): 380 phydev->speed = 10; 381 break; 382 case (0x01): 383 phydev->speed = 100; 384 break; 385 case (0x02): 386 phydev->speed = 1000; 387 break; 388 } 389 390 phydev->duplex = (val & 0x1000) == 0x1000; 391 392 return 0; 393} 394 395/* 396 * Figure out if BCM5482 is in serdes or copper mode and determine link 397 * configuration accordingly 398 */ 399static int bcm5482_startup(struct phy_device *phydev) 400{ 401 int ret; 402 403 if (bcm5482_is_serdes(phydev)) { 404 bcm5482_parse_serdes_sr(phydev); 405 phydev->port = PORT_FIBRE; 406 return 0; 407 } 408 409 /* Wait for auto-negotiation to complete or fail */ 410 ret = genphy_update_link(phydev); 411 if (ret) 412 return ret; 413 414 /* Parse BCM54xx copper aux status register */ 415 return bcm54xx_parse_status(phydev); 416} 417 418U_BOOT_PHY_DRIVER(bcm54210e) = { 419 .name = "Broadcom BCM54210E", 420 .uid = 0x600d84a0, 421 .mask = 0xfffffff0, 422 .features = PHY_GBIT_FEATURES, 423 .config = &bcm54210e_config, 424 .startup = &bcm54xx_startup, 425 .shutdown = &genphy_shutdown, 426}; 427 428U_BOOT_PHY_DRIVER(bcm5461s) = { 429 .name = "Broadcom BCM5461S", 430 .uid = 0x2060c0, 431 .mask = 0xfffff0, 432 .features = PHY_GBIT_FEATURES, 433 .config = &bcm5461_config, 434 .startup = &bcm54xx_startup, 435 .shutdown = &genphy_shutdown, 436}; 437 438U_BOOT_PHY_DRIVER(bcm5464s) = { 439 .name = "Broadcom BCM5464S", 440 .uid = 0x2060b0, 441 .mask = 0xfffff0, 442 .features = PHY_GBIT_FEATURES, 443 .config = &bcm5461_config, 444 .startup = &bcm54xx_startup, 445 .shutdown = &genphy_shutdown, 446}; 447 448U_BOOT_PHY_DRIVER(bcm5482s) = { 449 .name = "Broadcom BCM5482S", 450 .uid = 0x143bcb0, 451 .mask = 0xffffff0, 452 .features = PHY_GBIT_FEATURES, 453 .config = &bcm5482_config, 454 .startup = &bcm5482_startup, 455 .shutdown = &genphy_shutdown, 456}; 457 458U_BOOT_PHY_DRIVER(bcm_cygnus) = { 459 .name = "Broadcom CYGNUS GPHY", 460 .uid = 0xae025200, 461 .mask = 0xfffff0, 462 .features = PHY_GBIT_FEATURES, 463 .config = &bcm_cygnus_config, 464 .startup = &genphy_startup, 465 .shutdown = &genphy_shutdown, 466}; 467