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 1000BT Cfeed=110 for all ports */ 140 phy_write_misc(phydev, AFE_TX_CONFIG, 0x0061); 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_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */ 171 phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da); 172 173 /* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal 174 * offset for HT=0 code 175 */ 176 phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3); 177 178 /* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */ 179 phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0x0010); 180 181 /* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */ 182 phy_write_misc(phydev, DSP_TAP10, 0x011b); 183 184 /* Reset R_CAL/RC_CAL engine */ 185 r_rc_cal_reset(phydev); 186 187 return 0; 188 } 189 190 static int bcm7xxx_apd_enable(struct phy_device *phydev) 191 { 192 int val; 193 194 /* Enable powering down of the DLL during auto-power down */ 195 val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_SCR3); 196 if (val < 0) 197 return val; 198 199 val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; 200 bcm54xx_shadow_write(phydev, BCM54XX_SHD_SCR3, val); 201 202 /* Enable auto-power down */ 203 val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_APD); 204 if (val < 0) 205 return val; 206 207 val |= BCM54XX_SHD_APD_EN; 208 return bcm54xx_shadow_write(phydev, BCM54XX_SHD_APD, val); 209 } 210 211 static int bcm7xxx_eee_enable(struct phy_device *phydev) 212 { 213 int val; 214 215 val = phy_read_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL, 216 MDIO_MMD_AN, phydev->addr); 217 if (val < 0) 218 return val; 219 220 /* Enable general EEE feature at the PHY level */ 221 val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X; 222 223 phy_write_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL, 224 MDIO_MMD_AN, phydev->addr, val); 225 226 /* Advertise supported modes */ 227 val = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV, 228 MDIO_MMD_AN, phydev->addr); 229 230 val |= (MDIO_AN_EEE_ADV_100TX | MDIO_AN_EEE_ADV_1000T); 231 phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV, 232 MDIO_MMD_AN, phydev->addr, val); 233 234 return 0; 235 } 236 237 static int bcm7xxx_28nm_config_init(struct phy_device *phydev) 238 { 239 u8 rev = PHY_BRCM_7XXX_REV(phydev->dev_flags); 240 u8 patch = PHY_BRCM_7XXX_PATCH(phydev->dev_flags); 241 int ret = 0; 242 243 pr_info_once("%s: %s PHY revision: 0x%02x, patch: %d\n", 244 dev_name(&phydev->dev), phydev->drv->name, rev, patch); 245 246 switch (rev) { 247 case 0xb0: 248 ret = bcm7xxx_28nm_b0_afe_config_init(phydev); 249 break; 250 case 0xd0: 251 ret = bcm7xxx_28nm_d0_afe_config_init(phydev); 252 break; 253 case 0xe0: 254 case 0xf0: 255 /* Rev G0 introduces a roll over */ 256 case 0x10: 257 ret = bcm7xxx_28nm_e0_plus_afe_config_init(phydev); 258 break; 259 default: 260 break; 261 } 262 263 if (ret) 264 return ret; 265 266 ret = bcm7xxx_eee_enable(phydev); 267 if (ret) 268 return ret; 269 270 return bcm7xxx_apd_enable(phydev); 271 } 272 273 static int bcm7xxx_28nm_resume(struct phy_device *phydev) 274 { 275 int ret; 276 277 /* Re-apply workarounds coming out suspend/resume */ 278 ret = bcm7xxx_28nm_config_init(phydev); 279 if (ret) 280 return ret; 281 282 /* 28nm Gigabit PHYs come out of reset without any half-duplex 283 * or "hub" compliant advertised mode, fix that. This does not 284 * cause any problems with the PHY library since genphy_config_aneg() 285 * gracefully handles auto-negotiated and forced modes. 286 */ 287 return genphy_config_aneg(phydev); 288 } 289 290 static int phy_set_clr_bits(struct phy_device *dev, int location, 291 int set_mask, int clr_mask) 292 { 293 int v, ret; 294 295 v = phy_read(dev, location); 296 if (v < 0) 297 return v; 298 299 v &= ~clr_mask; 300 v |= set_mask; 301 302 ret = phy_write(dev, location, v); 303 if (ret < 0) 304 return ret; 305 306 return v; 307 } 308 309 static int bcm7xxx_config_init(struct phy_device *phydev) 310 { 311 int ret; 312 313 /* Enable 64 clock MDIO */ 314 phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO); 315 phy_read(phydev, MII_BCM7XXX_AUX_MODE); 316 317 /* Workaround only required for 100Mbits/sec capable PHYs */ 318 if (phydev->supported & PHY_GBIT_FEATURES) 319 return 0; 320 321 /* set shadow mode 2 */ 322 ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 323 MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2); 324 if (ret < 0) 325 return ret; 326 327 /* set iddq_clkbias */ 328 phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00); 329 udelay(10); 330 331 /* reset iddq_clkbias */ 332 phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00); 333 334 phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555); 335 336 /* reset shadow mode 2 */ 337 ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, MII_BCM7XXX_SHD_MODE_2, 0); 338 if (ret < 0) 339 return ret; 340 341 return 0; 342 } 343 344 /* Workaround for putting the PHY in IDDQ mode, required 345 * for all BCM7XXX 40nm and 65nm PHYs 346 */ 347 static int bcm7xxx_suspend(struct phy_device *phydev) 348 { 349 int ret; 350 const struct bcm7xxx_regs { 351 int reg; 352 u16 value; 353 } bcm7xxx_suspend_cfg[] = { 354 { MII_BCM7XXX_TEST, 0x008b }, 355 { MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 }, 356 { MII_BCM7XXX_100TX_DISC, 0x7000 }, 357 { MII_BCM7XXX_TEST, 0x000f }, 358 { MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 }, 359 { MII_BCM7XXX_TEST, 0x000b }, 360 }; 361 unsigned int i; 362 363 for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) { 364 ret = phy_write(phydev, 365 bcm7xxx_suspend_cfg[i].reg, 366 bcm7xxx_suspend_cfg[i].value); 367 if (ret) 368 return ret; 369 } 370 371 return 0; 372 } 373 374 static int bcm7xxx_dummy_config_init(struct phy_device *phydev) 375 { 376 return 0; 377 } 378 379 #define BCM7XXX_28NM_GPHY(_oui, _name) \ 380 { \ 381 .phy_id = (_oui), \ 382 .phy_id_mask = 0xfffffff0, \ 383 .name = _name, \ 384 .features = PHY_GBIT_FEATURES | \ 385 SUPPORTED_Pause | SUPPORTED_Asym_Pause, \ 386 .flags = PHY_IS_INTERNAL, \ 387 .config_init = bcm7xxx_28nm_config_init, \ 388 .config_aneg = genphy_config_aneg, \ 389 .read_status = genphy_read_status, \ 390 .resume = bcm7xxx_28nm_resume, \ 391 .driver = { .owner = THIS_MODULE }, \ 392 } 393 394 static struct phy_driver bcm7xxx_driver[] = { 395 BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"), 396 BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"), 397 BCM7XXX_28NM_GPHY(PHY_ID_BCM7366, "Broadcom BCM7366"), 398 BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"), 399 BCM7XXX_28NM_GPHY(PHY_ID_BCM7439_2, "Broadcom BCM7439 (2)"), 400 BCM7XXX_28NM_GPHY(PHY_ID_BCM7445, "Broadcom BCM7445"), 401 { 402 .phy_id = PHY_ID_BCM7425, 403 .phy_id_mask = 0xfffffff0, 404 .name = "Broadcom BCM7425", 405 .features = PHY_GBIT_FEATURES | 406 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 407 .flags = 0, 408 .config_init = bcm7xxx_config_init, 409 .config_aneg = genphy_config_aneg, 410 .read_status = genphy_read_status, 411 .suspend = bcm7xxx_suspend, 412 .resume = bcm7xxx_config_init, 413 .driver = { .owner = THIS_MODULE }, 414 }, { 415 .phy_id = PHY_ID_BCM7429, 416 .phy_id_mask = 0xfffffff0, 417 .name = "Broadcom BCM7429", 418 .features = PHY_GBIT_FEATURES | 419 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 420 .flags = PHY_IS_INTERNAL, 421 .config_init = bcm7xxx_config_init, 422 .config_aneg = genphy_config_aneg, 423 .read_status = genphy_read_status, 424 .suspend = bcm7xxx_suspend, 425 .resume = bcm7xxx_config_init, 426 .driver = { .owner = THIS_MODULE }, 427 }, { 428 .phy_id = PHY_BCM_OUI_4, 429 .phy_id_mask = 0xffff0000, 430 .name = "Broadcom BCM7XXX 40nm", 431 .features = PHY_GBIT_FEATURES | 432 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 433 .flags = PHY_IS_INTERNAL, 434 .config_init = bcm7xxx_config_init, 435 .config_aneg = genphy_config_aneg, 436 .read_status = genphy_read_status, 437 .suspend = bcm7xxx_suspend, 438 .resume = bcm7xxx_config_init, 439 .driver = { .owner = THIS_MODULE }, 440 }, { 441 .phy_id = PHY_BCM_OUI_5, 442 .phy_id_mask = 0xffffff00, 443 .name = "Broadcom BCM7XXX 65nm", 444 .features = PHY_BASIC_FEATURES | 445 SUPPORTED_Pause | SUPPORTED_Asym_Pause, 446 .flags = PHY_IS_INTERNAL, 447 .config_init = bcm7xxx_dummy_config_init, 448 .config_aneg = genphy_config_aneg, 449 .read_status = genphy_read_status, 450 .suspend = bcm7xxx_suspend, 451 .resume = bcm7xxx_config_init, 452 .driver = { .owner = THIS_MODULE }, 453 } }; 454 455 static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = { 456 { PHY_ID_BCM7250, 0xfffffff0, }, 457 { PHY_ID_BCM7364, 0xfffffff0, }, 458 { PHY_ID_BCM7366, 0xfffffff0, }, 459 { PHY_ID_BCM7425, 0xfffffff0, }, 460 { PHY_ID_BCM7429, 0xfffffff0, }, 461 { PHY_ID_BCM7439, 0xfffffff0, }, 462 { PHY_ID_BCM7445, 0xfffffff0, }, 463 { PHY_BCM_OUI_4, 0xffff0000 }, 464 { PHY_BCM_OUI_5, 0xffffff00 }, 465 { } 466 }; 467 468 module_phy_driver(bcm7xxx_driver); 469 470 MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl); 471 472 MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver"); 473 MODULE_LICENSE("GPL"); 474 MODULE_AUTHOR("Broadcom Corporation"); 475