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