18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2015 Broadcom Corporation 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci/* Broadcom Cygnus SoC internal transceivers support. */ 78c2ecf20Sopenharmony_ci#include "bcm-phy-lib.h" 88c2ecf20Sopenharmony_ci#include <linux/brcmphy.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 118c2ecf20Sopenharmony_ci#include <linux/phy.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cistruct bcm_omega_phy_priv { 148c2ecf20Sopenharmony_ci u64 *stats; 158c2ecf20Sopenharmony_ci}; 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci/* Broadcom Cygnus Phy specific registers */ 188c2ecf20Sopenharmony_ci#define MII_BCM_CYGNUS_AFE_VDAC_ICTRL_0 0x91E5 /* VDAL Control register */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic int bcm_cygnus_afe_config(struct phy_device *phydev) 218c2ecf20Sopenharmony_ci{ 228c2ecf20Sopenharmony_ci int rc; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci /* ensure smdspclk is enabled */ 258c2ecf20Sopenharmony_ci rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, 0x0c30); 268c2ecf20Sopenharmony_ci if (rc < 0) 278c2ecf20Sopenharmony_ci return rc; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci /* AFE_VDAC_ICTRL_0 bit 7:4 Iq=1100 for 1g 10bt, normal modes */ 308c2ecf20Sopenharmony_ci rc = bcm_phy_write_misc(phydev, 0x39, 0x01, 0xA7C8); 318c2ecf20Sopenharmony_ci if (rc < 0) 328c2ecf20Sopenharmony_ci return rc; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci /* AFE_HPF_TRIM_OTHERS bit11=1, short cascode enable for all modes*/ 358c2ecf20Sopenharmony_ci rc = bcm_phy_write_misc(phydev, 0x3A, 0x00, 0x0803); 368c2ecf20Sopenharmony_ci if (rc < 0) 378c2ecf20Sopenharmony_ci return rc; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* AFE_TX_CONFIG_1 bit 7:4 Iq=1100 for test modes */ 408c2ecf20Sopenharmony_ci rc = bcm_phy_write_misc(phydev, 0x3A, 0x01, 0xA740); 418c2ecf20Sopenharmony_ci if (rc < 0) 428c2ecf20Sopenharmony_ci return rc; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci /* AFE TEMPSEN_OTHERS rcal_HT, rcal_LT 10000 */ 458c2ecf20Sopenharmony_ci rc = bcm_phy_write_misc(phydev, 0x3A, 0x03, 0x8400); 468c2ecf20Sopenharmony_ci if (rc < 0) 478c2ecf20Sopenharmony_ci return rc; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* AFE_FUTURE_RSV bit 2:0 rccal <2:0>=100 */ 508c2ecf20Sopenharmony_ci rc = bcm_phy_write_misc(phydev, 0x3B, 0x00, 0x0004); 518c2ecf20Sopenharmony_ci if (rc < 0) 528c2ecf20Sopenharmony_ci return rc; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci /* Adjust bias current trim to overcome digital offSet */ 558c2ecf20Sopenharmony_ci rc = phy_write(phydev, MII_BRCM_CORE_BASE1E, 0x02); 568c2ecf20Sopenharmony_ci if (rc < 0) 578c2ecf20Sopenharmony_ci return rc; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* make rcal=100, since rdb default is 000 */ 608c2ecf20Sopenharmony_ci rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB1, 0x10); 618c2ecf20Sopenharmony_ci if (rc < 0) 628c2ecf20Sopenharmony_ci return rc; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* CORE_EXPB0, Reset R_CAL/RC_CAL Engine */ 658c2ecf20Sopenharmony_ci rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, 0x10); 668c2ecf20Sopenharmony_ci if (rc < 0) 678c2ecf20Sopenharmony_ci return rc; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci /* CORE_EXPB0, Disable Reset R_CAL/RC_CAL Engine */ 708c2ecf20Sopenharmony_ci rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, 0x00); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return 0; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic int bcm_cygnus_config_init(struct phy_device *phydev) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci int reg, rc; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci reg = phy_read(phydev, MII_BCM54XX_ECR); 808c2ecf20Sopenharmony_ci if (reg < 0) 818c2ecf20Sopenharmony_ci return reg; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* Mask interrupts globally. */ 848c2ecf20Sopenharmony_ci reg |= MII_BCM54XX_ECR_IM; 858c2ecf20Sopenharmony_ci rc = phy_write(phydev, MII_BCM54XX_ECR, reg); 868c2ecf20Sopenharmony_ci if (rc) 878c2ecf20Sopenharmony_ci return rc; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* Unmask events of interest */ 908c2ecf20Sopenharmony_ci reg = ~(MII_BCM54XX_INT_DUPLEX | 918c2ecf20Sopenharmony_ci MII_BCM54XX_INT_SPEED | 928c2ecf20Sopenharmony_ci MII_BCM54XX_INT_LINK); 938c2ecf20Sopenharmony_ci rc = phy_write(phydev, MII_BCM54XX_IMR, reg); 948c2ecf20Sopenharmony_ci if (rc) 958c2ecf20Sopenharmony_ci return rc; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci /* Apply AFE settings for the PHY */ 988c2ecf20Sopenharmony_ci rc = bcm_cygnus_afe_config(phydev); 998c2ecf20Sopenharmony_ci if (rc) 1008c2ecf20Sopenharmony_ci return rc; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Advertise EEE */ 1038c2ecf20Sopenharmony_ci rc = bcm_phy_set_eee(phydev, true); 1048c2ecf20Sopenharmony_ci if (rc) 1058c2ecf20Sopenharmony_ci return rc; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci /* Enable APD */ 1088c2ecf20Sopenharmony_ci return bcm_phy_enable_apd(phydev, false); 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic int bcm_cygnus_resume(struct phy_device *phydev) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci int rc; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci genphy_resume(phydev); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* Re-initialize the PHY to apply AFE work-arounds and 1188c2ecf20Sopenharmony_ci * configurations when coming out of suspend. 1198c2ecf20Sopenharmony_ci */ 1208c2ecf20Sopenharmony_ci rc = bcm_cygnus_config_init(phydev); 1218c2ecf20Sopenharmony_ci if (rc) 1228c2ecf20Sopenharmony_ci return rc; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci /* restart auto negotiation with the new settings */ 1258c2ecf20Sopenharmony_ci return genphy_config_aneg(phydev); 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int bcm_omega_config_init(struct phy_device *phydev) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci u8 count, rev; 1318c2ecf20Sopenharmony_ci int ret = 0; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci rev = phydev->phy_id & ~phydev->drv->phy_id_mask; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci pr_info_once("%s: %s PHY revision: 0x%02x\n", 1368c2ecf20Sopenharmony_ci phydev_name(phydev), phydev->drv->name, rev); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* Dummy read to a register to workaround an issue upon reset where the 1398c2ecf20Sopenharmony_ci * internal inverter may not allow the first MDIO transaction to pass 1408c2ecf20Sopenharmony_ci * the MDIO management controller and make us return 0xffff for such 1418c2ecf20Sopenharmony_ci * reads. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_ci phy_read(phydev, MII_BMSR); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci switch (rev) { 1468c2ecf20Sopenharmony_ci case 0x00: 1478c2ecf20Sopenharmony_ci ret = bcm_phy_28nm_a0b0_afe_config_init(phydev); 1488c2ecf20Sopenharmony_ci break; 1498c2ecf20Sopenharmony_ci default: 1508c2ecf20Sopenharmony_ci break; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (ret) 1548c2ecf20Sopenharmony_ci return ret; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci ret = bcm_phy_downshift_get(phydev, &count); 1578c2ecf20Sopenharmony_ci if (ret) 1588c2ecf20Sopenharmony_ci return ret; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci /* Only enable EEE if Wirespeed/downshift is disabled */ 1618c2ecf20Sopenharmony_ci ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE); 1628c2ecf20Sopenharmony_ci if (ret) 1638c2ecf20Sopenharmony_ci return ret; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return bcm_phy_enable_apd(phydev, true); 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic int bcm_omega_resume(struct phy_device *phydev) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci int ret; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* Re-apply workarounds coming out suspend/resume */ 1738c2ecf20Sopenharmony_ci ret = bcm_omega_config_init(phydev); 1748c2ecf20Sopenharmony_ci if (ret) 1758c2ecf20Sopenharmony_ci return ret; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* 28nm Gigabit PHYs come out of reset without any half-duplex 1788c2ecf20Sopenharmony_ci * or "hub" compliant advertised mode, fix that. This does not 1798c2ecf20Sopenharmony_ci * cause any problems with the PHY library since genphy_config_aneg() 1808c2ecf20Sopenharmony_ci * gracefully handles auto-negotiated and forced modes. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ci return genphy_config_aneg(phydev); 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic int bcm_omega_get_tunable(struct phy_device *phydev, 1868c2ecf20Sopenharmony_ci struct ethtool_tunable *tuna, void *data) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci switch (tuna->id) { 1898c2ecf20Sopenharmony_ci case ETHTOOL_PHY_DOWNSHIFT: 1908c2ecf20Sopenharmony_ci return bcm_phy_downshift_get(phydev, (u8 *)data); 1918c2ecf20Sopenharmony_ci default: 1928c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1938c2ecf20Sopenharmony_ci } 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic int bcm_omega_set_tunable(struct phy_device *phydev, 1978c2ecf20Sopenharmony_ci struct ethtool_tunable *tuna, 1988c2ecf20Sopenharmony_ci const void *data) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci u8 count = *(u8 *)data; 2018c2ecf20Sopenharmony_ci int ret; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci switch (tuna->id) { 2048c2ecf20Sopenharmony_ci case ETHTOOL_PHY_DOWNSHIFT: 2058c2ecf20Sopenharmony_ci ret = bcm_phy_downshift_set(phydev, count); 2068c2ecf20Sopenharmony_ci break; 2078c2ecf20Sopenharmony_ci default: 2088c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci if (ret) 2128c2ecf20Sopenharmony_ci return ret; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci /* Disable EEE advertisement since this prevents the PHY 2158c2ecf20Sopenharmony_ci * from successfully linking up, trigger auto-negotiation restart 2168c2ecf20Sopenharmony_ci * to let the MAC decide what to do. 2178c2ecf20Sopenharmony_ci */ 2188c2ecf20Sopenharmony_ci ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE); 2198c2ecf20Sopenharmony_ci if (ret) 2208c2ecf20Sopenharmony_ci return ret; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return genphy_restart_aneg(phydev); 2238c2ecf20Sopenharmony_ci} 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistatic void bcm_omega_get_phy_stats(struct phy_device *phydev, 2268c2ecf20Sopenharmony_ci struct ethtool_stats *stats, u64 *data) 2278c2ecf20Sopenharmony_ci{ 2288c2ecf20Sopenharmony_ci struct bcm_omega_phy_priv *priv = phydev->priv; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci bcm_phy_get_stats(phydev, priv->stats, stats, data); 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic int bcm_omega_probe(struct phy_device *phydev) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci struct bcm_omega_phy_priv *priv; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); 2388c2ecf20Sopenharmony_ci if (!priv) 2398c2ecf20Sopenharmony_ci return -ENOMEM; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci phydev->priv = priv; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci priv->stats = devm_kcalloc(&phydev->mdio.dev, 2448c2ecf20Sopenharmony_ci bcm_phy_get_sset_count(phydev), sizeof(u64), 2458c2ecf20Sopenharmony_ci GFP_KERNEL); 2468c2ecf20Sopenharmony_ci if (!priv->stats) 2478c2ecf20Sopenharmony_ci return -ENOMEM; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci return 0; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic struct phy_driver bcm_cygnus_phy_driver[] = { 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci .phy_id = PHY_ID_BCM_CYGNUS, 2558c2ecf20Sopenharmony_ci .phy_id_mask = 0xfffffff0, 2568c2ecf20Sopenharmony_ci .name = "Broadcom Cygnus PHY", 2578c2ecf20Sopenharmony_ci /* PHY_GBIT_FEATURES */ 2588c2ecf20Sopenharmony_ci .config_init = bcm_cygnus_config_init, 2598c2ecf20Sopenharmony_ci .ack_interrupt = bcm_phy_ack_intr, 2608c2ecf20Sopenharmony_ci .config_intr = bcm_phy_config_intr, 2618c2ecf20Sopenharmony_ci .suspend = genphy_suspend, 2628c2ecf20Sopenharmony_ci .resume = bcm_cygnus_resume, 2638c2ecf20Sopenharmony_ci}, { 2648c2ecf20Sopenharmony_ci .phy_id = PHY_ID_BCM_OMEGA, 2658c2ecf20Sopenharmony_ci .phy_id_mask = 0xfffffff0, 2668c2ecf20Sopenharmony_ci .name = "Broadcom Omega Combo GPHY", 2678c2ecf20Sopenharmony_ci /* PHY_GBIT_FEATURES */ 2688c2ecf20Sopenharmony_ci .flags = PHY_IS_INTERNAL, 2698c2ecf20Sopenharmony_ci .config_init = bcm_omega_config_init, 2708c2ecf20Sopenharmony_ci .suspend = genphy_suspend, 2718c2ecf20Sopenharmony_ci .resume = bcm_omega_resume, 2728c2ecf20Sopenharmony_ci .get_tunable = bcm_omega_get_tunable, 2738c2ecf20Sopenharmony_ci .set_tunable = bcm_omega_set_tunable, 2748c2ecf20Sopenharmony_ci .get_sset_count = bcm_phy_get_sset_count, 2758c2ecf20Sopenharmony_ci .get_strings = bcm_phy_get_strings, 2768c2ecf20Sopenharmony_ci .get_stats = bcm_omega_get_phy_stats, 2778c2ecf20Sopenharmony_ci .probe = bcm_omega_probe, 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci}; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cistatic struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = { 2828c2ecf20Sopenharmony_ci { PHY_ID_BCM_CYGNUS, 0xfffffff0, }, 2838c2ecf20Sopenharmony_ci { PHY_ID_BCM_OMEGA, 0xfffffff0, }, 2848c2ecf20Sopenharmony_ci { } 2858c2ecf20Sopenharmony_ci}; 2868c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, bcm_cygnus_phy_tbl); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cimodule_phy_driver(bcm_cygnus_phy_driver); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Broadcom Cygnus internal PHY driver"); 2918c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2928c2ecf20Sopenharmony_ciMODULE_AUTHOR("Broadcom Corporation"); 293