1 /* 2 * Broadcom BCM7xxx internal transceivers support. 3 * 4 * Copyright (C) 2014, Broadcom Corporation 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 */ 11 12 #include <linux/module.h> 13 #include <linux/phy.h> 14 #include <linux/delay.h> 15 #include <linux/bitops.h> 16 #include <linux/brcmphy.h> 17 #include <linux/mdio.h> 18 19 /* Broadcom BCM7xxx internal PHY registers */ 20 #define MII_BCM7XXX_CHANNEL_WIDTH 0x2000 21 22 /* 40nm only register definitions */ 23 #define MII_BCM7XXX_100TX_AUX_CTL 0x10 24 #define MII_BCM7XXX_100TX_FALSE_CAR 0x13 25 #define MII_BCM7XXX_100TX_DISC 0x14 26 #define MII_BCM7XXX_AUX_MODE 0x1d 27 #define MII_BCM7XX_64CLK_MDIO BIT(12) 28 #define MII_BCM7XXX_CORE_BASE1E 0x1e 29 #define MII_BCM7XXX_TEST 0x1f 30 #define MII_BCM7XXX_SHD_MODE_2 BIT(2) 31 32 /* 28nm only register definitions */ 33 #define MISC_ADDR(base, channel) base, channel 34 35 #define DSP_TAP10 MISC_ADDR(0x0a, 0) 36 #define PLL_PLLCTRL_1 MISC_ADDR(0x32, 1) 37 #define PLL_PLLCTRL_2 MISC_ADDR(0x32, 2) 38 #define PLL_PLLCTRL_4 MISC_ADDR(0x33, 0) 39 40 #define AFE_RXCONFIG_0 MISC_ADDR(0x38, 0) 41 #define AFE_RXCONFIG_1 MISC_ADDR(0x38, 1) 42 #define AFE_RXCONFIG_2 MISC_ADDR(0x38, 2) 43 #define AFE_RX_LP_COUNTER MISC_ADDR(0x38, 3) 44 #define AFE_TX_CONFIG MISC_ADDR(0x39, 0) 45 #define AFE_VDCA_ICTRL_0 MISC_ADDR(0x39, 1) 46 #define AFE_VDAC_OTHERS_0 MISC_ADDR(0x39, 3) 47 #define AFE_HPF_TRIM_OTHERS MISC_ADDR(0x3a, 0) 48 49 #define CORE_EXPB0 0xb0 50 51 static void phy_write_exp(struct phy_device *phydev, 52 u16 reg, u16 value) 53 { 54 phy_write(phydev, MII_BCM54XX_EXP_SEL, MII_BCM54XX_EXP_SEL_ER | reg); 55 phy_write(phydev, MII_BCM54XX_EXP_DATA, value); 56 } 57 58 static void phy_write_misc(struct phy_device *phydev, 59 u16 reg, u16 chl, u16 value) 60 { 61 int tmp; 62 63 phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); 64 65 tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); 66 tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; 67 phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp); 68 69 tmp = (chl * MII_BCM7XXX_CHANNEL_WIDTH) | reg; 70 phy_write(phydev, MII_BCM54XX_EXP_SEL, tmp); 71 72 phy_write(phydev, MII_BCM54XX_EXP_DATA, value); 73 } 74 75 static void r_rc_cal_reset(struct phy_device *phydev) 76 { 77 /* Reset R_CAL/RC_CAL Engine */ 78 phy_write_exp(phydev, 0x00b0, 0x0010); 79 80 /* Disable Reset R_AL/RC_CAL Engine */ 81 phy_write_exp(phydev, 0x00b0, 0x0000); 82 } 83 84 static int bcm7xxx_28nm_b0_afe_config_init(struct phy_device *phydev) 85 { 86 /* Increase VCO range to prevent unlocking problem of PLL at low 87 * temp 88 */ 89 phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048); 90 91 /* Change Ki to 011 */ 92 phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b); 93 94 /* Disable loading of TVCO buffer to bandgap, set bandgap trim 95 * to 111 96 */ 97 phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20); 98 99 /* Adjust bias current trim by -3 */ 100 phy_write_misc(phydev, DSP_TAP10, 0x690b); 101 102 /* Switch to CORE_BASE1E */ 103 phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0xd); 104 105 r_rc_cal_reset(phydev); 106 107 /* write AFE_RXCONFIG_0 */ 108 phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19); 109 110 /* write AFE_RXCONFIG_1 */ 111 phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f); 112 113 /* write AFE_RX_LP_COUNTER */ 114 phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0); 115 116 /* write AFE_HPF_TRIM_OTHERS */ 117 phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b); 118 119 /* write AFTE_TX_CONFIG */ 120 phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800); 121 122 return 0; 123 } 124 125 static int bcm7xxx_28nm_d0_afe_config_init(struct phy_device *phydev) 126 { 127 /* AFE_RXCONFIG_0 */ 128 phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb15); 129 130 /* AFE_RXCONFIG_1 */ 131 phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9b2f); 132 133 /* AFE_RXCONFIG_2, set rCal offset for HT=0 code and LT=-2 code */ 134 phy_write_misc(phydev, AFE_RXCONFIG_2, 0x2003); 135 136 /* AFE_RX_LP_COUNTER, set RX bandwidth to maximum */ 137 phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0); 138 139 /* AFE_TX_CONFIG, set 100BT Cfeed=011 to improve rise/fall time */ 140 phy_write_misc(phydev, AFE_TX_CONFIG, 0x431); 141 142 /* AFE_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */ 143 phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da); 144 145 /* AFE_VDAC_OTHERS_0, set 1000BT Cidac=010 for all ports */ 146 phy_write_misc(phydev, AFE_VDAC_OTHERS_0, 0xa020); 147 148 /* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal 149 * offset for HT=0 code 150 */ 151 phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3); 152 153 /* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */ 154 phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0x0010); 155 156 /* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */ 157 phy_write_misc(phydev, DSP_TAP10, 0x011b); 158 159 /* Reset R_CAL/RC_CAL engine */ 160 r_rc_cal_reset(phydev); 161 162 return 0; 163 } 164 165 static int bcm7xxx_28nm_e0_plus_afe_config_init(struct phy_device *phydev) 166 { 167 /* AFE_RXCONFIG_1, provide more margin for INL/DNL measurement */ 168 phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9b2f); 169 170 /* AFE_TX_CONFIG, set 100BT Cfeed=011 to improve rise/fall time */ 171 phy_write_misc(phydev, AFE_TX_CONFIG, 0x431); 172 173 /* AFE_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */ 174 phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da); 175 176 /* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal 177 * offset for HT=0 code 178 */ 179 phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3); 180 181 /* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */ 182 phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0x0010); 183 184 /* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */ 185 phy_write_misc(phydev, DSP_TAP10, 0x011b); 186 187 /* Reset R_CAL/RC_CAL engine */ 188 r_rc_cal_reset(phydev); 189 190 return 0; 191 } 192 193 static int bcm7xxx_apd_enable(struct phy_device *phydev) 194 { 195 int val; 196 197 /* Enable powering down of the DLL during auto-power down */ 198 val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_SCR3); 199 if (val < 0) 200 return val; 201 202 val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; 203 bcm54xx_shadow_write(phydev, BCM54XX_SHD_SCR3, val); 204 205 /* Enable auto-power down */ 206 val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_APD); 207 if (val < 0) 208 return val; 209 210 val |= BCM54XX_SHD_APD_EN; 211 return bcm54xx_shadow_write(phydev, BCM54XX_SHD_APD, val); 212 } 213 214 static int bcm7xxx_eee_enable(struct phy_device *phydev) 215 { 216 int val; 217 218 val = phy_read_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL, 219 MDIO_MMD_AN, phydev->addr); 220 if (val < 0) 221 return val; 222 223 /* Enable general EEE feature at the PHY level */ 224 val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X; 225 226 phy_write_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL, 227 MDIO_MMD_AN, phydev->addr, val); 228 229 /* Advertise supported modes */ 230 val = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV, 231 MDIO_MMD_AN, phydev->addr); 232 233 val |= (MDIO_AN_EEE_ADV_100TX | MDIO_AN_EEE_ADV_1000T); 234 phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV, 235 MDIO_MMD_AN, phydev->addr, val); 236 237 return 0; 238 } 239 240 static int bcm7xxx_28nm_config_init(struct phy_device *phydev) 241 { 242 u8 rev = PHY_BRCM_7XXX_REV(phydev->dev_flags); 243 u8 patch = PHY_BRCM_7XXX_PATCH(phydev->dev_flags); 244 int ret = 0; 245 246 pr_info_once("%s: %s PHY revision: 0x%02x, patch: %d\n", 247 dev_name(&phydev->dev), phydev->drv->name, rev, patch); 248 249 switch (rev) { 250 case 0xb0: 251 ret = bcm7xxx_28nm_b0_afe_config_init(phydev); 252 break; 253 case 0xd0: 254 ret = bcm7xxx_28nm_d0_afe_config_init(phydev); 255 break; 256 case 0xe0: 257 case 0xf0: 258 /* Rev G0 introduces a roll over */ 259 case 0x10: 260 ret = bcm7xxx_28nm_e0_plus_afe_config_init(phydev); 261 break; 262 default: 263 break; 264 } 265 266 if (ret) 267 return ret; 268 269 ret = bcm7xxx_eee_enable(phydev); 270 if (ret) 271 return ret; 272 273 return bcm7xxx_apd_enable(phydev); 274 } 275 276 static int bcm7xxx_28nm_resume(struct phy_device *phydev) 277 { 278 int ret; 279 280 /* Re-apply workarounds coming out suspend/resume */ 281 ret = bcm7xxx_28nm_config_init(phydev); 282 if (ret) 283 return ret; 284 285 /* 28nm Gigabit PHYs come out of reset without any half-duplex 286 * or "hub" compliant advertised mode, fix that. This does not 287 * cause any problems with the PHY library since genphy_config_aneg() 288 * gracefully handles auto-negotiated and forced modes. 289 */ 290 return genphy_config_aneg(phydev); 291 } 292 293 static int phy_set_clr_bits(struct phy_device *dev, int location, 294 int set_mask, int clr_mask) 295 { 296 int v, ret; 297 298 v = phy_read(dev, location); 299 if (v < 0) 300 return v; 301 302 v &= ~clr_mask; 303 v |= set_mask; 304 305 ret = phy_write(dev, location, v); 306 if (ret < 0) 307 return ret; 308 309 return v; 310 } 311 312 static int bcm7xxx_config_init(struct phy_device *phydev) 313 { 314 int ret; 315 316 /* Enable 64 clock MDIO */ 317 phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO); 318 phy_read(phydev, MII_BCM7XXX_AUX_MODE); 319 320 /* Workaround only required for 100Mbits/sec capable PHYs */ 321 if (phydev->supported & PHY_GBIT_FEATURES) 322 return 0; 323 324 /* set shadow mode 2 */ 325 ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 326 MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2); 327 if (ret < 0) 328 return ret; 329 330 /* set iddq_clkbias */ 331 phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00); 332 udelay(10); 333 334 /* reset iddq_clkbias */ 335 phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00); 336 337 phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555); 338 339 /* reset shadow mode 2 */ 340 ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, MII_BCM7XXX_SHD_MODE_2, 0); 341 if (ret < 0) 342 return ret; 343 344 return 0; 345 } 346 347 /* Workaround for putting the PHY in IDDQ mode, required 348 * for all BCM7XXX 40nm and 65nm PHYs 349 */ 350 static int bcm7xxx_suspend(struct phy_device *phydev) 351 { 352 int ret; 353 const struct bcm7xxx_regs { 354 int reg; 355 u16 value; 356 } bcm7xxx_suspend_cfg[] = { 357 { MII_BCM7XXX_TEST, 0x008b }, 358 { MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 }, 359 { MII_BCM7XXX_100TX_DISC, 0x7000 }, 360 { MII_BCM7XXX_TEST, 0x000f }, 361 { MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 }, 362 { MII_BCM7XXX_TEST, 0x000b }, 363 }; 364 unsigned int i; 365 366 for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) { 367 ret = phy_write(phydev, 368 bcm7xxx_suspend_cfg[i].reg, 369 bcm7xxx_suspend_cfg[i].value); 370 if (ret) 371 return ret; 372 } 373 374 return 0; 375 } 376 377 static int bcm7xxx_dummy_config_init(struct phy_device *phydev) 378 { 379 return 0; 380 } 381 382 #define BCM7XXX_28NM_GPHY(_oui, _name) \ 383 { \ 384 .phy_id = (_oui), \ 385 .phy_id_mask = 0xfffffff0, \ 386 .name = _name, \ 387 .features = PHY_GBIT_FEATURES | \ 388 SUPPORTED_Pause | SUPPORTED_Asym_Pause, \ 389 .flags = PHY_IS_INTERNAL, \ 390 .config_init = bcm7xxx_28nm_config_init, \ 391 .config_aneg = genphy_config_aneg, \ 392 .read_status = genphy_read_status, \ 393 .resume = bcm7xxx_28nm_resume, \ 394 .driver = { .owner = THIS_MODULE }, \ 395 } 396 397 static struct phy_driver bcm7xxx_driver[] = { 398 BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"), 399 BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"), 400 BCM7XXX_28NM_GPHY(PHY_ID_BCM7366, "Broadcom BCM7366"), 401 BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"), 402 BCM7XXX_28NM_GPHY(PHY_ID_BCM7439_2, "Broadcom BCM7439 (2)"), 403 BCM7XXX_28NM_GPHY(PHY_ID_BCM7445, "Broadcom BCM7445"), 404 { 405 .phy_id = PHY_ID_BCM7425, 406 .phy_id_mask = 0xfffffff0, 407 .name = "Broadcom BCM7425", 408 .features = PHY_GBIT_FEATURES | 409 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 410 .flags = PHY_IS_INTERNAL, 411 .config_init = bcm7xxx_config_init, 412 .config_aneg = genphy_config_aneg, 413 .read_status = genphy_read_status, 414 .suspend = bcm7xxx_suspend, 415 .resume = bcm7xxx_config_init, 416 .driver = { .owner = THIS_MODULE }, 417 }, { 418 .phy_id = PHY_ID_BCM7429, 419 .phy_id_mask = 0xfffffff0, 420 .name = "Broadcom BCM7429", 421 .features = PHY_GBIT_FEATURES | 422 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 423 .flags = PHY_IS_INTERNAL, 424 .config_init = bcm7xxx_config_init, 425 .config_aneg = genphy_config_aneg, 426 .read_status = genphy_read_status, 427 .suspend = bcm7xxx_suspend, 428 .resume = bcm7xxx_config_init, 429 .driver = { .owner = THIS_MODULE }, 430 }, { 431 .phy_id = PHY_BCM_OUI_4, 432 .phy_id_mask = 0xffff0000, 433 .name = "Broadcom BCM7XXX 40nm", 434 .features = PHY_GBIT_FEATURES | 435 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 436 .flags = PHY_IS_INTERNAL, 437 .config_init = bcm7xxx_config_init, 438 .config_aneg = genphy_config_aneg, 439 .read_status = genphy_read_status, 440 .suspend = bcm7xxx_suspend, 441 .resume = bcm7xxx_config_init, 442 .driver = { .owner = THIS_MODULE }, 443 }, { 444 .phy_id = PHY_BCM_OUI_5, 445 .phy_id_mask = 0xffffff00, 446 .name = "Broadcom BCM7XXX 65nm", 447 .features = PHY_BASIC_FEATURES | 448 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 449 .flags = PHY_IS_INTERNAL, 450 .config_init = bcm7xxx_dummy_config_init, 451 .config_aneg = genphy_config_aneg, 452 .read_status = genphy_read_status, 453 .suspend = bcm7xxx_suspend, 454 .resume = bcm7xxx_config_init, 455 .driver = { .owner = THIS_MODULE }, 456 } }; 457 458 static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = { 459 { PHY_ID_BCM7250, 0xfffffff0, }, 460 { PHY_ID_BCM7364, 0xfffffff0, }, 461 { PHY_ID_BCM7366, 0xfffffff0, }, 462 { PHY_ID_BCM7425, 0xfffffff0, }, 463 { PHY_ID_BCM7429, 0xfffffff0, }, 464 { PHY_ID_BCM7439, 0xfffffff0, }, 465 { PHY_ID_BCM7445, 0xfffffff0, }, 466 { PHY_BCM_OUI_4, 0xffff0000 }, 467 { PHY_BCM_OUI_5, 0xffffff00 }, 468 { } 469 }; 470 471 module_phy_driver(bcm7xxx_driver); 472 473 MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl); 474 475 MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver"); 476 MODULE_LICENSE("GPL"); 477 MODULE_AUTHOR("Broadcom Corporation"); 478