1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2015-2017 Broadcom 4 */ 5 6 #include "bcm-phy-lib.h" 7 #include <linux/brcmphy.h> 8 #include <linux/export.h> 9 #include <linux/mdio.h> 10 #include <linux/module.h> 11 #include <linux/phy.h> 12 #include <linux/ethtool.h> 13 14 #define MII_BCM_CHANNEL_WIDTH 0x2000 15 #define BCM_CL45VEN_EEE_ADV 0x3c 16 17 int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val) 18 { 19 int rc; 20 21 rc = phy_write(phydev, MII_BCM54XX_EXP_SEL, reg); 22 if (rc < 0) 23 return rc; 24 25 return phy_write(phydev, MII_BCM54XX_EXP_DATA, val); 26 } 27 EXPORT_SYMBOL_GPL(bcm_phy_write_exp); 28 29 int bcm_phy_read_exp(struct phy_device *phydev, u16 reg) 30 { 31 int val; 32 33 val = phy_write(phydev, MII_BCM54XX_EXP_SEL, reg); 34 if (val < 0) 35 return val; 36 37 val = phy_read(phydev, MII_BCM54XX_EXP_DATA); 38 39 /* Restore default value. It's O.K. if this write fails. */ 40 phy_write(phydev, MII_BCM54XX_EXP_SEL, 0); 41 42 return val; 43 } 44 EXPORT_SYMBOL_GPL(bcm_phy_read_exp); 45 46 int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum) 47 { 48 /* The register must be written to both the Shadow Register Select and 49 * the Shadow Read Register Selector 50 */ 51 phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK | 52 regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT); 53 return phy_read(phydev, MII_BCM54XX_AUX_CTL); 54 } 55 EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read); 56 57 int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val) 58 { 59 return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val); 60 } 61 EXPORT_SYMBOL(bcm54xx_auxctl_write); 62 63 int bcm_phy_write_misc(struct phy_device *phydev, 64 u16 reg, u16 chl, u16 val) 65 { 66 int rc; 67 int tmp; 68 69 rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, 70 MII_BCM54XX_AUXCTL_SHDWSEL_MISC); 71 if (rc < 0) 72 return rc; 73 74 tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); 75 tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; 76 rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp); 77 if (rc < 0) 78 return rc; 79 80 tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg; 81 rc = bcm_phy_write_exp(phydev, tmp, val); 82 83 return rc; 84 } 85 EXPORT_SYMBOL_GPL(bcm_phy_write_misc); 86 87 int bcm_phy_read_misc(struct phy_device *phydev, 88 u16 reg, u16 chl) 89 { 90 int rc; 91 int tmp; 92 93 rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, 94 MII_BCM54XX_AUXCTL_SHDWSEL_MISC); 95 if (rc < 0) 96 return rc; 97 98 tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); 99 tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; 100 rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp); 101 if (rc < 0) 102 return rc; 103 104 tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg; 105 rc = bcm_phy_read_exp(phydev, tmp); 106 107 return rc; 108 } 109 EXPORT_SYMBOL_GPL(bcm_phy_read_misc); 110 111 int bcm_phy_ack_intr(struct phy_device *phydev) 112 { 113 int reg; 114 115 /* Clear pending interrupts. */ 116 reg = phy_read(phydev, MII_BCM54XX_ISR); 117 if (reg < 0) 118 return reg; 119 120 return 0; 121 } 122 EXPORT_SYMBOL_GPL(bcm_phy_ack_intr); 123 124 int bcm_phy_config_intr(struct phy_device *phydev) 125 { 126 int reg; 127 128 reg = phy_read(phydev, MII_BCM54XX_ECR); 129 if (reg < 0) 130 return reg; 131 132 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) 133 reg &= ~MII_BCM54XX_ECR_IM; 134 else 135 reg |= MII_BCM54XX_ECR_IM; 136 137 return phy_write(phydev, MII_BCM54XX_ECR, reg); 138 } 139 EXPORT_SYMBOL_GPL(bcm_phy_config_intr); 140 141 int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow) 142 { 143 phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow)); 144 return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD)); 145 } 146 EXPORT_SYMBOL_GPL(bcm_phy_read_shadow); 147 148 int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow, 149 u16 val) 150 { 151 return phy_write(phydev, MII_BCM54XX_SHD, 152 MII_BCM54XX_SHD_WRITE | 153 MII_BCM54XX_SHD_VAL(shadow) | 154 MII_BCM54XX_SHD_DATA(val)); 155 } 156 EXPORT_SYMBOL_GPL(bcm_phy_write_shadow); 157 158 int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down) 159 { 160 int val; 161 162 if (dll_pwr_down) { 163 val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3); 164 if (val < 0) 165 return val; 166 167 val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; 168 bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val); 169 } 170 171 val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD); 172 if (val < 0) 173 return val; 174 175 /* Clear APD bits */ 176 val &= BCM_APD_CLR_MASK; 177 178 if (phydev->autoneg == AUTONEG_ENABLE) 179 val |= BCM54XX_SHD_APD_EN; 180 else 181 val |= BCM_NO_ANEG_APD_EN; 182 183 /* Enable energy detect single link pulse for easy wakeup */ 184 val |= BCM_APD_SINGLELP_EN; 185 186 /* Enable Auto Power-Down (APD) for the PHY */ 187 return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val); 188 } 189 EXPORT_SYMBOL_GPL(bcm_phy_enable_apd); 190 191 int bcm_phy_set_eee(struct phy_device *phydev, bool enable) 192 { 193 int val; 194 195 /* Enable EEE at PHY level */ 196 val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL); 197 if (val < 0) 198 return val; 199 200 if (enable) 201 val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X; 202 else 203 val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X); 204 205 phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val); 206 207 /* Advertise EEE */ 208 val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV); 209 if (val < 0) 210 return val; 211 212 if (enable) 213 val |= (MDIO_EEE_100TX | MDIO_EEE_1000T); 214 else 215 val &= ~(MDIO_EEE_100TX | MDIO_EEE_1000T); 216 217 phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val); 218 219 return 0; 220 } 221 EXPORT_SYMBOL_GPL(bcm_phy_set_eee); 222 223 int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count) 224 { 225 int val; 226 227 val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); 228 if (val < 0) 229 return val; 230 231 /* Check if wirespeed is enabled or not */ 232 if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) { 233 *count = DOWNSHIFT_DEV_DISABLE; 234 return 0; 235 } 236 237 val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2); 238 if (val < 0) 239 return val; 240 241 /* Downgrade after one link attempt */ 242 if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) { 243 *count = 1; 244 } else { 245 /* Downgrade after configured retry count */ 246 val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; 247 val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK; 248 *count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET; 249 } 250 251 return 0; 252 } 253 EXPORT_SYMBOL_GPL(bcm_phy_downshift_get); 254 255 int bcm_phy_downshift_set(struct phy_device *phydev, u8 count) 256 { 257 int val = 0, ret = 0; 258 259 /* Range check the number given */ 260 if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET > 261 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK && 262 count != DOWNSHIFT_DEV_DEFAULT_COUNT) { 263 return -ERANGE; 264 } 265 266 val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); 267 if (val < 0) 268 return val; 269 270 /* Se the write enable bit */ 271 val |= MII_BCM54XX_AUXCTL_MISC_WREN; 272 273 if (count == DOWNSHIFT_DEV_DISABLE) { 274 val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN; 275 return bcm54xx_auxctl_write(phydev, 276 MII_BCM54XX_AUXCTL_SHDWSEL_MISC, 277 val); 278 } else { 279 val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN; 280 ret = bcm54xx_auxctl_write(phydev, 281 MII_BCM54XX_AUXCTL_SHDWSEL_MISC, 282 val); 283 if (ret < 0) 284 return ret; 285 } 286 287 val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2); 288 val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK << 289 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT | 290 BCM54XX_SHD_SCR2_WSPD_RTRY_DIS); 291 292 switch (count) { 293 case 1: 294 val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS; 295 break; 296 case DOWNSHIFT_DEV_DEFAULT_COUNT: 297 val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; 298 break; 299 default: 300 val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) << 301 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; 302 break; 303 } 304 305 return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val); 306 } 307 EXPORT_SYMBOL_GPL(bcm_phy_downshift_set); 308 309 struct bcm_phy_hw_stat { 310 const char *string; 311 u8 reg; 312 u8 shift; 313 u8 bits; 314 }; 315 316 /* Counters freeze at either 0xffff or 0xff, better than nothing */ 317 static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = { 318 { "phy_receive_errors", MII_BRCM_CORE_BASE12, 0, 16 }, 319 { "phy_serdes_ber_errors", MII_BRCM_CORE_BASE13, 8, 8 }, 320 { "phy_false_carrier_sense_errors", MII_BRCM_CORE_BASE13, 0, 8 }, 321 { "phy_local_rcvr_nok", MII_BRCM_CORE_BASE14, 8, 8 }, 322 { "phy_remote_rcv_nok", MII_BRCM_CORE_BASE14, 0, 8 }, 323 }; 324 325 int bcm_phy_get_sset_count(struct phy_device *phydev) 326 { 327 return ARRAY_SIZE(bcm_phy_hw_stats); 328 } 329 EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count); 330 331 void bcm_phy_get_strings(struct phy_device *phydev, u8 *data) 332 { 333 unsigned int i; 334 335 for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) 336 strlcpy(data + i * ETH_GSTRING_LEN, 337 bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN); 338 } 339 EXPORT_SYMBOL_GPL(bcm_phy_get_strings); 340 341 /* Caller is supposed to provide appropriate storage for the library code to 342 * access the shadow copy 343 */ 344 static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow, 345 unsigned int i) 346 { 347 struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i]; 348 int val; 349 u64 ret; 350 351 val = phy_read(phydev, stat.reg); 352 if (val < 0) { 353 ret = U64_MAX; 354 } else { 355 val >>= stat.shift; 356 val = val & ((1 << stat.bits) - 1); 357 shadow[i] += val; 358 ret = shadow[i]; 359 } 360 361 return ret; 362 } 363 364 void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow, 365 struct ethtool_stats *stats, u64 *data) 366 { 367 unsigned int i; 368 369 for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) 370 data[i] = bcm_phy_get_stat(phydev, shadow, i); 371 } 372 EXPORT_SYMBOL_GPL(bcm_phy_get_stats); 373 374 MODULE_DESCRIPTION("Broadcom PHY Library"); 375 MODULE_LICENSE("GPL v2"); 376 MODULE_AUTHOR("Broadcom Corporation"); 377