// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2015-2017 Broadcom
*/
#include "bcm-phy-lib.h"
#include <linux/bitfield.h>
#include <linux/brcmphy.h>
#include <linux/etherdevice.h>
#include <linux/export.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>
#define MII_BCM_CHANNEL_WIDTH 0x2000
#define BCM_CL45VEN_EEE_ADV 0x3c
int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
{
int rc;
rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
if (rc < 0)
return rc;
return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
}
EXPORT_SYMBOL_GPL(__bcm_phy_write_exp);
int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
{
int rc;
phy_lock_mdio_bus(phydev);
rc = __bcm_phy_write_exp(phydev, reg, val);
phy_unlock_mdio_bus(phydev);
return rc;
}
EXPORT_SYMBOL_GPL(bcm_phy_write_exp);
int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
{
int val;
val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
if (val < 0)
return val;
val = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
/* Restore default value. It's O.K. if this write fails. */
__phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
return val;
}
EXPORT_SYMBOL_GPL(__bcm_phy_read_exp);
int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
{
int rc;
phy_lock_mdio_bus(phydev);
rc = __bcm_phy_read_exp(phydev, reg);
phy_unlock_mdio_bus(phydev);
return rc;
}
EXPORT_SYMBOL_GPL(bcm_phy_read_exp);
int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
{
int new, ret;
ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
if (ret < 0)
return ret;
ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
if (ret < 0)
return ret;
new = (ret & ~mask) | set;
if (new == ret)
return 0;
return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new);
}
EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp);
int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
{
int ret;
phy_lock_mdio_bus(phydev);
ret = __bcm_phy_modify_exp(phydev, reg, mask, set);
phy_unlock_mdio_bus(phydev);
return ret;
}
EXPORT_SYMBOL_GPL(bcm_phy_modify_exp);
int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum)
{
/* The register must be written to both the Shadow Register Select and
* the Shadow Read Register Selector
*/
phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK |
regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT);
return phy_read(phydev, MII_BCM54XX_AUX_CTL);
}
EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read);
int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
{
return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
}
EXPORT_SYMBOL(bcm54xx_auxctl_write);
int bcm_phy_write_misc(struct phy_device *phydev,
u16 reg, u16 chl, u16 val)
{
int rc;
int tmp;
rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
if (rc < 0)
return rc;
tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
if (rc < 0)
return rc;
tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
rc = bcm_phy_write_exp(phydev, tmp, val);
return rc;
}
EXPORT_SYMBOL_GPL(bcm_phy_write_misc);
int bcm_phy_read_misc(struct phy_device *phydev,
u16 reg, u16