1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0 28e185d69SArun Parameswaran /* 38e185d69SArun Parameswaran * Copyright (C) 2015 Broadcom Corporation 48e185d69SArun Parameswaran */ 58e185d69SArun Parameswaran 68e185d69SArun Parameswaran /* Broadcom Cygnus SoC internal transceivers support. */ 78e185d69SArun Parameswaran #include "bcm-phy-lib.h" 88e185d69SArun Parameswaran #include <linux/brcmphy.h> 98e185d69SArun Parameswaran #include <linux/module.h> 108e185d69SArun Parameswaran #include <linux/netdevice.h> 118e185d69SArun Parameswaran #include <linux/phy.h> 128e185d69SArun Parameswaran 1317cc9821SFlorian Fainelli struct bcm_omega_phy_priv { 1417cc9821SFlorian Fainelli u64 *stats; 1517cc9821SFlorian Fainelli }; 1617cc9821SFlorian Fainelli 178e185d69SArun Parameswaran /* Broadcom Cygnus Phy specific registers */ 188e185d69SArun Parameswaran #define MII_BCM_CYGNUS_AFE_VDAC_ICTRL_0 0x91E5 /* VDAL Control register */ 198e185d69SArun Parameswaran 208e185d69SArun Parameswaran static int bcm_cygnus_afe_config(struct phy_device *phydev) 218e185d69SArun Parameswaran { 228e185d69SArun Parameswaran int rc; 238e185d69SArun Parameswaran 248e185d69SArun Parameswaran /* ensure smdspclk is enabled */ 258e185d69SArun Parameswaran rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, 0x0c30); 268e185d69SArun Parameswaran if (rc < 0) 278e185d69SArun Parameswaran return rc; 288e185d69SArun Parameswaran 298e185d69SArun Parameswaran /* AFE_VDAC_ICTRL_0 bit 7:4 Iq=1100 for 1g 10bt, normal modes */ 308e185d69SArun Parameswaran rc = bcm_phy_write_misc(phydev, 0x39, 0x01, 0xA7C8); 318e185d69SArun Parameswaran if (rc < 0) 328e185d69SArun Parameswaran return rc; 338e185d69SArun Parameswaran 348e185d69SArun Parameswaran /* AFE_HPF_TRIM_OTHERS bit11=1, short cascode enable for all modes*/ 358e185d69SArun Parameswaran rc = bcm_phy_write_misc(phydev, 0x3A, 0x00, 0x0803); 368e185d69SArun Parameswaran if (rc < 0) 378e185d69SArun Parameswaran return rc; 388e185d69SArun Parameswaran 398e185d69SArun Parameswaran /* AFE_TX_CONFIG_1 bit 7:4 Iq=1100 for test modes */ 408e185d69SArun Parameswaran rc = bcm_phy_write_misc(phydev, 0x3A, 0x01, 0xA740); 418e185d69SArun Parameswaran if (rc < 0) 428e185d69SArun Parameswaran return rc; 438e185d69SArun Parameswaran 448e185d69SArun Parameswaran /* AFE TEMPSEN_OTHERS rcal_HT, rcal_LT 10000 */ 458e185d69SArun Parameswaran rc = bcm_phy_write_misc(phydev, 0x3A, 0x03, 0x8400); 468e185d69SArun Parameswaran if (rc < 0) 478e185d69SArun Parameswaran return rc; 488e185d69SArun Parameswaran 498e185d69SArun Parameswaran /* AFE_FUTURE_RSV bit 2:0 rccal <2:0>=100 */ 508e185d69SArun Parameswaran rc = bcm_phy_write_misc(phydev, 0x3B, 0x00, 0x0004); 518e185d69SArun Parameswaran if (rc < 0) 528e185d69SArun Parameswaran return rc; 538e185d69SArun Parameswaran 548e185d69SArun Parameswaran /* Adjust bias current trim to overcome digital offSet */ 558e185d69SArun Parameswaran rc = phy_write(phydev, MII_BRCM_CORE_BASE1E, 0x02); 568e185d69SArun Parameswaran if (rc < 0) 578e185d69SArun Parameswaran return rc; 588e185d69SArun Parameswaran 598e185d69SArun Parameswaran /* make rcal=100, since rdb default is 000 */ 6079fb218dSFlorian Fainelli rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB1, 0x10); 618e185d69SArun Parameswaran if (rc < 0) 628e185d69SArun Parameswaran return rc; 638e185d69SArun Parameswaran 648e185d69SArun Parameswaran /* CORE_EXPB0, Reset R_CAL/RC_CAL Engine */ 6579fb218dSFlorian Fainelli rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, 0x10); 668e185d69SArun Parameswaran if (rc < 0) 678e185d69SArun Parameswaran return rc; 688e185d69SArun Parameswaran 698e185d69SArun Parameswaran /* CORE_EXPB0, Disable Reset R_CAL/RC_CAL Engine */ 7079fb218dSFlorian Fainelli rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, 0x00); 718e185d69SArun Parameswaran 728e185d69SArun Parameswaran return 0; 738e185d69SArun Parameswaran } 748e185d69SArun Parameswaran 758e185d69SArun Parameswaran static int bcm_cygnus_config_init(struct phy_device *phydev) 768e185d69SArun Parameswaran { 778e185d69SArun Parameswaran int reg, rc; 788e185d69SArun Parameswaran 798e185d69SArun Parameswaran reg = phy_read(phydev, MII_BCM54XX_ECR); 808e185d69SArun Parameswaran if (reg < 0) 818e185d69SArun Parameswaran return reg; 828e185d69SArun Parameswaran 838e185d69SArun Parameswaran /* Mask interrupts globally. */ 848e185d69SArun Parameswaran reg |= MII_BCM54XX_ECR_IM; 858e185d69SArun Parameswaran rc = phy_write(phydev, MII_BCM54XX_ECR, reg); 868e185d69SArun Parameswaran if (rc) 878e185d69SArun Parameswaran return rc; 888e185d69SArun Parameswaran 898e185d69SArun Parameswaran /* Unmask events of interest */ 908e185d69SArun Parameswaran reg = ~(MII_BCM54XX_INT_DUPLEX | 918e185d69SArun Parameswaran MII_BCM54XX_INT_SPEED | 928e185d69SArun Parameswaran MII_BCM54XX_INT_LINK); 938e185d69SArun Parameswaran rc = phy_write(phydev, MII_BCM54XX_IMR, reg); 948e185d69SArun Parameswaran if (rc) 958e185d69SArun Parameswaran return rc; 968e185d69SArun Parameswaran 978e185d69SArun Parameswaran /* Apply AFE settings for the PHY */ 988e185d69SArun Parameswaran rc = bcm_cygnus_afe_config(phydev); 998e185d69SArun Parameswaran if (rc) 1008e185d69SArun Parameswaran return rc; 1018e185d69SArun Parameswaran 1028e185d69SArun Parameswaran /* Advertise EEE */ 10399cec8a4SFlorian Fainelli rc = bcm_phy_set_eee(phydev, true); 1048e185d69SArun Parameswaran if (rc) 1058e185d69SArun Parameswaran return rc; 1068e185d69SArun Parameswaran 1078e185d69SArun Parameswaran /* Enable APD */ 1088e185d69SArun Parameswaran return bcm_phy_enable_apd(phydev, false); 1098e185d69SArun Parameswaran } 1108e185d69SArun Parameswaran 1118e185d69SArun Parameswaran static int bcm_cygnus_resume(struct phy_device *phydev) 1128e185d69SArun Parameswaran { 1138e185d69SArun Parameswaran int rc; 1148e185d69SArun Parameswaran 1158e185d69SArun Parameswaran genphy_resume(phydev); 1168e185d69SArun Parameswaran 1178e185d69SArun Parameswaran /* Re-initialize the PHY to apply AFE work-arounds and 1188e185d69SArun Parameswaran * configurations when coming out of suspend. 1198e185d69SArun Parameswaran */ 1208e185d69SArun Parameswaran rc = bcm_cygnus_config_init(phydev); 1218e185d69SArun Parameswaran if (rc) 1228e185d69SArun Parameswaran return rc; 1238e185d69SArun Parameswaran 1248e185d69SArun Parameswaran /* restart auto negotiation with the new settings */ 1258e185d69SArun Parameswaran return genphy_config_aneg(phydev); 1268e185d69SArun Parameswaran } 1278e185d69SArun Parameswaran 12817cc9821SFlorian Fainelli static int bcm_omega_config_init(struct phy_device *phydev) 12917cc9821SFlorian Fainelli { 13017cc9821SFlorian Fainelli u8 count, rev; 13117cc9821SFlorian Fainelli int ret = 0; 13217cc9821SFlorian Fainelli 13317cc9821SFlorian Fainelli rev = phydev->phy_id & ~phydev->drv->phy_id_mask; 13417cc9821SFlorian Fainelli 13517cc9821SFlorian Fainelli pr_info_once("%s: %s PHY revision: 0x%02x\n", 13617cc9821SFlorian Fainelli phydev_name(phydev), phydev->drv->name, rev); 13717cc9821SFlorian Fainelli 13817cc9821SFlorian Fainelli /* Dummy read to a register to workaround an issue upon reset where the 13917cc9821SFlorian Fainelli * internal inverter may not allow the first MDIO transaction to pass 14017cc9821SFlorian Fainelli * the MDIO management controller and make us return 0xffff for such 14117cc9821SFlorian Fainelli * reads. 14217cc9821SFlorian Fainelli */ 14317cc9821SFlorian Fainelli phy_read(phydev, MII_BMSR); 14417cc9821SFlorian Fainelli 14517cc9821SFlorian Fainelli switch (rev) { 14617cc9821SFlorian Fainelli case 0x00: 14717cc9821SFlorian Fainelli ret = bcm_phy_28nm_a0b0_afe_config_init(phydev); 14817cc9821SFlorian Fainelli break; 14917cc9821SFlorian Fainelli default: 15017cc9821SFlorian Fainelli break; 15117cc9821SFlorian Fainelli } 15217cc9821SFlorian Fainelli 15317cc9821SFlorian Fainelli if (ret) 15417cc9821SFlorian Fainelli return ret; 15517cc9821SFlorian Fainelli 15617cc9821SFlorian Fainelli ret = bcm_phy_downshift_get(phydev, &count); 15717cc9821SFlorian Fainelli if (ret) 15817cc9821SFlorian Fainelli return ret; 15917cc9821SFlorian Fainelli 16017cc9821SFlorian Fainelli /* Only enable EEE if Wirespeed/downshift is disabled */ 16117cc9821SFlorian Fainelli ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE); 16217cc9821SFlorian Fainelli if (ret) 16317cc9821SFlorian Fainelli return ret; 16417cc9821SFlorian Fainelli 16517cc9821SFlorian Fainelli return bcm_phy_enable_apd(phydev, true); 16617cc9821SFlorian Fainelli } 16717cc9821SFlorian Fainelli 16817cc9821SFlorian Fainelli static int bcm_omega_resume(struct phy_device *phydev) 16917cc9821SFlorian Fainelli { 17017cc9821SFlorian Fainelli int ret; 17117cc9821SFlorian Fainelli 17217cc9821SFlorian Fainelli /* Re-apply workarounds coming out suspend/resume */ 17317cc9821SFlorian Fainelli ret = bcm_omega_config_init(phydev); 17417cc9821SFlorian Fainelli if (ret) 17517cc9821SFlorian Fainelli return ret; 17617cc9821SFlorian Fainelli 17717cc9821SFlorian Fainelli /* 28nm Gigabit PHYs come out of reset without any half-duplex 17817cc9821SFlorian Fainelli * or "hub" compliant advertised mode, fix that. This does not 17917cc9821SFlorian Fainelli * cause any problems with the PHY library since genphy_config_aneg() 18017cc9821SFlorian Fainelli * gracefully handles auto-negotiated and forced modes. 18117cc9821SFlorian Fainelli */ 18217cc9821SFlorian Fainelli return genphy_config_aneg(phydev); 18317cc9821SFlorian Fainelli } 18417cc9821SFlorian Fainelli 18517cc9821SFlorian Fainelli static int bcm_omega_get_tunable(struct phy_device *phydev, 18617cc9821SFlorian Fainelli struct ethtool_tunable *tuna, void *data) 18717cc9821SFlorian Fainelli { 18817cc9821SFlorian Fainelli switch (tuna->id) { 18917cc9821SFlorian Fainelli case ETHTOOL_PHY_DOWNSHIFT: 19017cc9821SFlorian Fainelli return bcm_phy_downshift_get(phydev, (u8 *)data); 19117cc9821SFlorian Fainelli default: 19217cc9821SFlorian Fainelli return -EOPNOTSUPP; 19317cc9821SFlorian Fainelli } 19417cc9821SFlorian Fainelli } 19517cc9821SFlorian Fainelli 19617cc9821SFlorian Fainelli static int bcm_omega_set_tunable(struct phy_device *phydev, 19717cc9821SFlorian Fainelli struct ethtool_tunable *tuna, 19817cc9821SFlorian Fainelli const void *data) 19917cc9821SFlorian Fainelli { 20017cc9821SFlorian Fainelli u8 count = *(u8 *)data; 20117cc9821SFlorian Fainelli int ret; 20217cc9821SFlorian Fainelli 20317cc9821SFlorian Fainelli switch (tuna->id) { 20417cc9821SFlorian Fainelli case ETHTOOL_PHY_DOWNSHIFT: 20517cc9821SFlorian Fainelli ret = bcm_phy_downshift_set(phydev, count); 20617cc9821SFlorian Fainelli break; 20717cc9821SFlorian Fainelli default: 20817cc9821SFlorian Fainelli return -EOPNOTSUPP; 20917cc9821SFlorian Fainelli } 21017cc9821SFlorian Fainelli 21117cc9821SFlorian Fainelli if (ret) 21217cc9821SFlorian Fainelli return ret; 21317cc9821SFlorian Fainelli 21417cc9821SFlorian Fainelli /* Disable EEE advertisement since this prevents the PHY 21517cc9821SFlorian Fainelli * from successfully linking up, trigger auto-negotiation restart 21617cc9821SFlorian Fainelli * to let the MAC decide what to do. 21717cc9821SFlorian Fainelli */ 21817cc9821SFlorian Fainelli ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE); 21917cc9821SFlorian Fainelli if (ret) 22017cc9821SFlorian Fainelli return ret; 22117cc9821SFlorian Fainelli 22217cc9821SFlorian Fainelli return genphy_restart_aneg(phydev); 22317cc9821SFlorian Fainelli } 22417cc9821SFlorian Fainelli 22517cc9821SFlorian Fainelli static void bcm_omega_get_phy_stats(struct phy_device *phydev, 22617cc9821SFlorian Fainelli struct ethtool_stats *stats, u64 *data) 22717cc9821SFlorian Fainelli { 22817cc9821SFlorian Fainelli struct bcm_omega_phy_priv *priv = phydev->priv; 22917cc9821SFlorian Fainelli 23017cc9821SFlorian Fainelli bcm_phy_get_stats(phydev, priv->stats, stats, data); 23117cc9821SFlorian Fainelli } 23217cc9821SFlorian Fainelli 23317cc9821SFlorian Fainelli static int bcm_omega_probe(struct phy_device *phydev) 23417cc9821SFlorian Fainelli { 23517cc9821SFlorian Fainelli struct bcm_omega_phy_priv *priv; 23617cc9821SFlorian Fainelli 23717cc9821SFlorian Fainelli priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); 23817cc9821SFlorian Fainelli if (!priv) 23917cc9821SFlorian Fainelli return -ENOMEM; 24017cc9821SFlorian Fainelli 24117cc9821SFlorian Fainelli phydev->priv = priv; 24217cc9821SFlorian Fainelli 24317cc9821SFlorian Fainelli priv->stats = devm_kcalloc(&phydev->mdio.dev, 24417cc9821SFlorian Fainelli bcm_phy_get_sset_count(phydev), sizeof(u64), 24517cc9821SFlorian Fainelli GFP_KERNEL); 24617cc9821SFlorian Fainelli if (!priv->stats) 24717cc9821SFlorian Fainelli return -ENOMEM; 24817cc9821SFlorian Fainelli 24917cc9821SFlorian Fainelli return 0; 25017cc9821SFlorian Fainelli } 25117cc9821SFlorian Fainelli 2528e185d69SArun Parameswaran static struct phy_driver bcm_cygnus_phy_driver[] = { 2538e185d69SArun Parameswaran { 2548e185d69SArun Parameswaran .phy_id = PHY_ID_BCM_CYGNUS, 2558e185d69SArun Parameswaran .phy_id_mask = 0xfffffff0, 2568e185d69SArun Parameswaran .name = "Broadcom Cygnus PHY", 257dcdecdcfSHeiner Kallweit /* PHY_GBIT_FEATURES */ 2588e185d69SArun Parameswaran .config_init = bcm_cygnus_config_init, 2598e185d69SArun Parameswaran .ack_interrupt = bcm_phy_ack_intr, 2608e185d69SArun Parameswaran .config_intr = bcm_phy_config_intr, 261*4567d5c3SIoana Ciornei .handle_interrupt = bcm_phy_handle_interrupt, 2628e185d69SArun Parameswaran .suspend = genphy_suspend, 2638e185d69SArun Parameswaran .resume = bcm_cygnus_resume, 26417cc9821SFlorian Fainelli }, { 26517cc9821SFlorian Fainelli .phy_id = PHY_ID_BCM_OMEGA, 26617cc9821SFlorian Fainelli .phy_id_mask = 0xfffffff0, 26717cc9821SFlorian Fainelli .name = "Broadcom Omega Combo GPHY", 268dcdecdcfSHeiner Kallweit /* PHY_GBIT_FEATURES */ 26917cc9821SFlorian Fainelli .flags = PHY_IS_INTERNAL, 27017cc9821SFlorian Fainelli .config_init = bcm_omega_config_init, 27117cc9821SFlorian Fainelli .suspend = genphy_suspend, 27217cc9821SFlorian Fainelli .resume = bcm_omega_resume, 27317cc9821SFlorian Fainelli .get_tunable = bcm_omega_get_tunable, 27417cc9821SFlorian Fainelli .set_tunable = bcm_omega_set_tunable, 27517cc9821SFlorian Fainelli .get_sset_count = bcm_phy_get_sset_count, 27617cc9821SFlorian Fainelli .get_strings = bcm_phy_get_strings, 27717cc9821SFlorian Fainelli .get_stats = bcm_omega_get_phy_stats, 27817cc9821SFlorian Fainelli .probe = bcm_omega_probe, 27917cc9821SFlorian Fainelli } 28017cc9821SFlorian Fainelli }; 2818e185d69SArun Parameswaran 2828e185d69SArun Parameswaran static struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = { 2838e185d69SArun Parameswaran { PHY_ID_BCM_CYGNUS, 0xfffffff0, }, 28417cc9821SFlorian Fainelli { PHY_ID_BCM_OMEGA, 0xfffffff0, }, 2858e185d69SArun Parameswaran { } 2868e185d69SArun Parameswaran }; 2878e185d69SArun Parameswaran MODULE_DEVICE_TABLE(mdio, bcm_cygnus_phy_tbl); 2888e185d69SArun Parameswaran 2898e185d69SArun Parameswaran module_phy_driver(bcm_cygnus_phy_driver); 2908e185d69SArun Parameswaran 2918e185d69SArun Parameswaran MODULE_DESCRIPTION("Broadcom Cygnus internal PHY driver"); 2928e185d69SArun Parameswaran MODULE_LICENSE("GPL v2"); 2938e185d69SArun Parameswaran MODULE_AUTHOR("Broadcom Corporation"); 294