715 lines
18 KiB
C
715 lines
18 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/of.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/bitfield.h>
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_ETH_PRIVE)
|
|
#include <linux/amlogic/aml_phy_debug.h>
|
|
#endif
|
|
|
|
#define SPEC_MODE 18
|
|
#define SPEC_MII_MODE GENMASK(15, 14)
|
|
#define SPEC_CLKSELFREQ BIT(13)
|
|
#define SPEC_PHYADDR GENMASK(4, 0)
|
|
|
|
#define TSTCNTL 20
|
|
#define TSTCNTL_READ BIT(15)
|
|
#define TSTCNTL_WRITE BIT(14)
|
|
#define TSTCNTL_REG_BANK_SEL GENMASK(12, 11)
|
|
#define TSTCNTL_TEST_MODE BIT(10)
|
|
#define TSTCNTL_READ_ADDRESS GENMASK(9, 5)
|
|
#define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0)
|
|
#define TSTREAD1 21
|
|
#define TSTWRITE 23
|
|
#define INTSRC_FLAG 29
|
|
#define INTSRC_ANEG_PR BIT(1)
|
|
#define INTSRC_PARALLEL_FAULT BIT(2)
|
|
#define INTSRC_ANEG_LP_ACK BIT(3)
|
|
#define INTSRC_LINK_DOWN BIT(4)
|
|
#define INTSRC_REMOTE_FAULT BIT(5)
|
|
#define INTSRC_ANEG_COMPLETE BIT(6)
|
|
#define INTSRC_MASK 30
|
|
#define SPEC_CTRL_STATUS 31
|
|
#define SPEC_STATUS_SPEED GENMASK(4, 2)
|
|
#define SPEC_SPEED_10M_HALF 0x1
|
|
#define SPEC_SPEED_10M_FULL 0x5
|
|
#define SPEC_SPEED_100M_HALF 0x2
|
|
#define SPEC_SPEED_100M_FULL 0x6
|
|
|
|
#define BANK_ANALOG_DSP 0
|
|
#define BANK_WOL 1
|
|
#define BANK_BIST 3
|
|
|
|
/* WOL Registers */
|
|
#define LPI_STATUS 0xc
|
|
#define LPI_STATUS_RSV12 BIT(12)
|
|
|
|
/* BIST Registers */
|
|
#define FR_PLL_CONTROL 0x1b
|
|
#define FR_PLL_DIV0 0x1c
|
|
#define FR_PLL_DIV1 0x1d
|
|
|
|
struct meson_phy_priv {
|
|
int restore_lpa_for_eee;
|
|
bool actual_lpa;
|
|
};
|
|
|
|
static int meson_gxl_probe(struct phy_device *phydev)
|
|
{
|
|
struct device *dev = &phydev->mdio.dev;
|
|
struct meson_phy_priv *priv;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->restore_lpa_for_eee = -1;
|
|
phydev->priv = priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_gxl_open_banks(struct phy_device *phydev)
|
|
{
|
|
int ret;
|
|
|
|
/* Enable Analog and DSP register Bank access by
|
|
* toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
|
|
*/
|
|
ret = phy_write(phydev, TSTCNTL, 0);
|
|
if (ret)
|
|
return ret;
|
|
ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
|
|
if (ret)
|
|
return ret;
|
|
ret = phy_write(phydev, TSTCNTL, 0);
|
|
if (ret)
|
|
return ret;
|
|
return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
|
|
}
|
|
|
|
static void meson_gxl_close_banks(struct phy_device *phydev)
|
|
{
|
|
phy_write(phydev, TSTCNTL, 0);
|
|
}
|
|
|
|
static int meson_gxl_read_reg(struct phy_device *phydev,
|
|
unsigned int bank, unsigned int reg)
|
|
{
|
|
int ret;
|
|
|
|
ret = meson_gxl_open_banks(phydev);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
|
|
FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
|
|
TSTCNTL_TEST_MODE |
|
|
FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = phy_read(phydev, TSTREAD1);
|
|
out:
|
|
/* Close the bank access on our way out */
|
|
meson_gxl_close_banks(phydev);
|
|
return ret;
|
|
}
|
|
|
|
static int meson_gxl_write_reg(struct phy_device *phydev,
|
|
unsigned int bank, unsigned int reg,
|
|
uint16_t value)
|
|
{
|
|
int ret;
|
|
|
|
ret = meson_gxl_open_banks(phydev);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = phy_write(phydev, TSTWRITE, value);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
|
|
FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
|
|
TSTCNTL_TEST_MODE |
|
|
FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
|
|
|
|
out:
|
|
/* Close the bank access on our way out */
|
|
meson_gxl_close_banks(phydev);
|
|
return ret;
|
|
}
|
|
|
|
static int meson_gxl_config_init(struct phy_device *phydev)
|
|
{
|
|
int ret;
|
|
|
|
/* Enable fractional PLL */
|
|
ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Program fraction FR_PLL_DIV1 */
|
|
ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Program fraction FR_PLL_DIV1 */
|
|
ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This function is provided to cope with the possible failures of this phy
|
|
* during aneg process. When aneg fails, the PHY reports that aneg is done
|
|
* but the value found in MII_LPA is wrong:
|
|
* - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
|
|
* the link partner (LP) supports aneg but the LP never acked our base
|
|
* code word, it is likely that we never sent it to begin with.
|
|
* - Late failures: MII_LPA is filled with a value which seems to make sense
|
|
* but it actually is not what the LP is advertising. It seems that we
|
|
* can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
|
|
* If this particular bit is not set when aneg is reported being done,
|
|
* it means MII_LPA is likely to be wrong.
|
|
*
|
|
* In both case, forcing a restart of the aneg process solve the problem.
|
|
* When this failure happens, the first retry is usually successful but,
|
|
* in some cases, it may take up to 6 retries to get a decent result
|
|
*/
|
|
static int meson_gxl_read_status(struct phy_device *phydev)
|
|
{
|
|
int ret, wol, lpa, exp;
|
|
|
|
if (phydev->autoneg == AUTONEG_ENABLE) {
|
|
ret = genphy_aneg_done(phydev);
|
|
if (ret < 0)
|
|
return ret;
|
|
else if (!ret)
|
|
goto read_status_continue;
|
|
|
|
/* Aneg is done, let's check everything is fine */
|
|
wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
|
|
if (wol < 0)
|
|
return wol;
|
|
|
|
lpa = phy_read(phydev, MII_LPA);
|
|
if (lpa < 0)
|
|
return lpa;
|
|
|
|
exp = phy_read(phydev, MII_EXPANSION);
|
|
if (exp < 0)
|
|
return exp;
|
|
|
|
if (!(wol & LPI_STATUS_RSV12) ||
|
|
((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
|
|
/* Looks like aneg failed after all */
|
|
phydev_dbg(phydev, "LPA corruption - aneg restart\n");
|
|
return genphy_restart_aneg(phydev);
|
|
}
|
|
}
|
|
|
|
read_status_continue:
|
|
return genphy_read_status(phydev);
|
|
}
|
|
|
|
/* use restore_lpa_for_eee instead of MII_LPA for EEE, Compatible with non-EEE */
|
|
static int meson_genphy_read_lpa(struct phy_device *phydev)
|
|
{
|
|
struct meson_phy_priv *priv = phydev->priv;
|
|
|
|
if (phydev->autoneg == AUTONEG_ENABLE) {
|
|
if (!phydev->autoneg_complete) {
|
|
mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
|
|
0);
|
|
mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
|
|
return 0;
|
|
}
|
|
|
|
if (priv->restore_lpa_for_eee < 0)
|
|
return priv->restore_lpa_for_eee;
|
|
|
|
mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, priv->restore_lpa_for_eee);
|
|
} else {
|
|
linkmode_zero(phydev->lp_advertising);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void phy_resolve_aneg_linkmode_meson(struct phy_device *phydev)
|
|
{
|
|
struct meson_phy_priv *priv = phydev->priv;
|
|
int val = phy_read(phydev, SPEC_CTRL_STATUS);
|
|
|
|
switch ((val & SPEC_STATUS_SPEED) >> 2) {
|
|
case SPEC_SPEED_10M_HALF:
|
|
phydev->speed = SPEED_10;
|
|
phydev->duplex = DUPLEX_HALF;
|
|
break;
|
|
case SPEC_SPEED_10M_FULL:
|
|
phydev->speed = SPEED_10;
|
|
phydev->duplex = DUPLEX_FULL;
|
|
break;
|
|
case SPEC_SPEED_100M_HALF:
|
|
phydev->speed = SPEED_100;
|
|
phydev->duplex = DUPLEX_HALF;
|
|
break;
|
|
case SPEC_SPEED_100M_FULL:
|
|
phydev->speed = SPEED_100;
|
|
phydev->duplex = DUPLEX_FULL;
|
|
break;
|
|
}
|
|
|
|
if (phydev->duplex == DUPLEX_FULL) {
|
|
phydev->pause = (priv->restore_lpa_for_eee & LPA_PAUSE_CAP) ? 1 : 0;
|
|
phydev->asym_pause = (priv->restore_lpa_for_eee & LPA_PAUSE_ASYM) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
/* reference genphy_read_status() */
|
|
static int meson_g12a_read_status_for_eee(struct phy_device *phydev)
|
|
{
|
|
int err, old_link = phydev->link;
|
|
|
|
err = genphy_update_link(phydev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
|
|
return 0;
|
|
|
|
phydev->master_slave_get = MASTER_SLAVE_CFG_UNSUPPORTED;
|
|
phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
|
|
phydev->speed = SPEED_UNKNOWN;
|
|
phydev->duplex = DUPLEX_UNKNOWN;
|
|
phydev->pause = 0;
|
|
phydev->asym_pause = 0;
|
|
|
|
err = meson_genphy_read_lpa(phydev);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
|
|
phy_resolve_aneg_linkmode_meson(phydev);
|
|
} else if (phydev->autoneg == AUTONEG_DISABLE) {
|
|
int bmcr = phy_read(phydev, MII_BMCR);
|
|
|
|
if (bmcr < 0)
|
|
return bmcr;
|
|
|
|
if (bmcr & BMCR_FULLDPLX)
|
|
phydev->duplex = DUPLEX_FULL;
|
|
else
|
|
phydev->duplex = DUPLEX_HALF;
|
|
|
|
if (bmcr & BMCR_SPEED1000)
|
|
phydev->speed = SPEED_1000;
|
|
else if (bmcr & BMCR_SPEED100)
|
|
phydev->speed = SPEED_100;
|
|
else
|
|
phydev->speed = SPEED_10;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_g12a_read_status(struct phy_device *phydev)
|
|
{
|
|
struct aml_eth_priv *eth_priv = aml_get_eth_priv_by_pdev(phydev);
|
|
|
|
if (eth_priv->ephy_eee_support && eth_priv->inphy_eee_enable)
|
|
return meson_g12a_read_status_for_eee(phydev);
|
|
else
|
|
return genphy_read_status(phydev);
|
|
}
|
|
|
|
/* Check if the EEE settings are compatible
|
|
* with the device tree and the PHY capabilities.
|
|
*/
|
|
static int meson_check_eee_feature(struct phy_device *phydev)
|
|
{
|
|
struct aml_eth_priv *eth_priv = aml_get_eth_priv_by_pdev(phydev);
|
|
|
|
/* only check 100baseT_Full EEE feature */
|
|
if (eth_priv->ephy_eee_support !=
|
|
linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
|
|
phydev->supported_eee))
|
|
phydev_warn(phydev, "EEE support setting conflict\n");
|
|
|
|
/* PHY not support EEE, do nothing */
|
|
if (!eth_priv->ephy_eee_support)
|
|
return 0;
|
|
|
|
/* To avoid conflicts with the phy eee advertisement
|
|
* obtained by calling phy_probe, here we forcibly
|
|
* modify to maintain consistency.
|
|
*/
|
|
if (eth_priv->inphy_eee_enable != phydev->eee_enabled) {
|
|
phydev_warn(phydev, "EEE enable setting conflict, fix to %s\n",
|
|
eth_priv->inphy_eee_enable ? "Enable" : "Disable");
|
|
phydev->eee_enabled = (bool)eth_priv->inphy_eee_enable;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_g12a_config_aneg(struct phy_device *phydev)
|
|
{
|
|
int ret;
|
|
|
|
meson_check_eee_feature(phydev);
|
|
|
|
ret = genphy_config_aneg(phydev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_gxl_ack_interrupt(struct phy_device *phydev)
|
|
{
|
|
int ret = phy_read(phydev, INTSRC_FLAG);
|
|
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
static int meson_gxl_config_intr(struct phy_device *phydev)
|
|
{
|
|
u16 val;
|
|
int ret;
|
|
|
|
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
|
|
/* Ack any pending IRQ */
|
|
ret = meson_gxl_ack_interrupt(phydev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val = INTSRC_ANEG_PR
|
|
| INTSRC_PARALLEL_FAULT
|
|
| INTSRC_ANEG_LP_ACK
|
|
| INTSRC_LINK_DOWN
|
|
| INTSRC_REMOTE_FAULT
|
|
| INTSRC_ANEG_COMPLETE;
|
|
ret = phy_write(phydev, INTSRC_MASK, val);
|
|
} else {
|
|
val = 0;
|
|
ret = phy_write(phydev, INTSRC_MASK, val);
|
|
|
|
/* Ack any pending IRQ */
|
|
ret = meson_gxl_ack_interrupt(phydev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t meson_gxl_handle_interrupt(struct phy_device *phydev)
|
|
{
|
|
int irq_status;
|
|
struct aml_eth_priv *eth_priv = aml_get_eth_priv_by_pdev(phydev);
|
|
struct meson_phy_priv *priv = phydev->priv;
|
|
|
|
irq_status = phy_read(phydev, INTSRC_FLAG);
|
|
if (irq_status < 0) {
|
|
phy_error(phydev);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
if (irq_status == 0)
|
|
return IRQ_NONE;
|
|
|
|
if (eth_priv->ephy_eee_support && eth_priv->inphy_eee_enable) {
|
|
if (irq_status & INTSRC_LINK_DOWN)
|
|
priv->actual_lpa = 0;
|
|
|
|
if (irq_status & INTSRC_ANEG_PR) {
|
|
if (!priv->actual_lpa) {
|
|
/* LINK_DOWN -> ANEG restored first LPA REGISTER */
|
|
priv->restore_lpa_for_eee = phy_read(phydev, MII_LPA);
|
|
if (priv->restore_lpa_for_eee < 0) {
|
|
phy_error(phydev);
|
|
return IRQ_NONE;
|
|
}
|
|
priv->actual_lpa = 1;
|
|
}
|
|
}
|
|
}
|
|
phy_trigger_machine(phydev);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_ETH_PRIVE)
|
|
static unsigned int return_write_val(struct phy_device *phy_dev, int rd_addr)
|
|
{
|
|
int rd_data;
|
|
int rd_data_hi;
|
|
|
|
phy_write(phy_dev, 20,
|
|
((1 << 15) | (1 << 10) | ((rd_addr & 0x1f) << 5)));
|
|
rd_data = phy_read(phy_dev, 21);
|
|
rd_data_hi = phy_read(phy_dev, 22);
|
|
rd_data = ((rd_data_hi & 0xffff) << 16) | rd_data;
|
|
|
|
return rd_data;
|
|
}
|
|
|
|
static unsigned int phy_tst_write(struct phy_device *phy_dev, unsigned int wr_addr,
|
|
unsigned int wr_data)
|
|
{ /*init*/
|
|
phy_write(phy_dev, 20, 0x0000);
|
|
phy_write(phy_dev, 20, 0x0400);
|
|
phy_write(phy_dev, 20, 0x0000);
|
|
phy_write(phy_dev, 20, 0x0400);
|
|
|
|
if (wr_addr <= 31) {
|
|
phy_write(phy_dev, 23, (wr_data & 0xffff));
|
|
|
|
phy_write(phy_dev, 20,
|
|
((1 << 14) | (1 << 10) | ((wr_addr << 0) & 0x1f)));
|
|
|
|
pr_info("write phy tstcntl [reg_%d] 0x%x, 0x%x\n",
|
|
wr_addr, wr_data, return_write_val(phy_dev, wr_addr));
|
|
} else {
|
|
pr_info("Invalid parameter\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int merge_u32(unsigned int var1, unsigned int var2)
|
|
{
|
|
u16 low_part_var1 = (u16)var1;
|
|
u16 low_part_var2 = (u16)var2;
|
|
unsigned int val;
|
|
|
|
val = ((unsigned int)low_part_var2 << 16U) | (unsigned int)low_part_var1;
|
|
return val;
|
|
}
|
|
|
|
static unsigned int phy_tst_read(struct phy_device *phy_dev, unsigned int rd_addr)
|
|
{
|
|
unsigned int rd_data_hi;
|
|
unsigned int rd_data_lo;
|
|
unsigned int rd_data = 0;
|
|
|
|
/*init*/
|
|
phy_write(phy_dev, 20, 0x0000);
|
|
phy_write(phy_dev, 20, 0x0400);
|
|
phy_write(phy_dev, 20, 0x0000);
|
|
phy_write(phy_dev, 20, 0x0400);
|
|
|
|
if (rd_addr <= 31) {
|
|
phy_write(phy_dev, 20,
|
|
((1 << 15) | (1 << 10) | ((rd_addr & 0x1f) << 5)));
|
|
|
|
rd_data_lo = phy_read(phy_dev, 21);
|
|
rd_data_hi = phy_read(phy_dev, 22);
|
|
rd_data = merge_u32(rd_data_lo, rd_data_hi);
|
|
pr_info("read tstcntl phy [reg_%d] 0x%x\n", rd_addr, rd_data);
|
|
} else {
|
|
pr_info("Invalid parameter\n");
|
|
}
|
|
|
|
return rd_data;
|
|
}
|
|
|
|
static int custom_internal_config(struct phy_device *phydev)
|
|
{
|
|
unsigned int efuse_valid = 0;
|
|
unsigned int efuse_amp = 0;
|
|
unsigned int setup_amp = 0;
|
|
struct aml_eth_priv *eth_priv = aml_get_eth_priv_by_pdev(phydev);
|
|
|
|
efuse_amp = eth_priv->tx_amp_bl2;
|
|
efuse_valid = ((efuse_amp >> 4) & 0x3);
|
|
setup_amp = efuse_amp & 0xf;
|
|
|
|
if (efuse_valid) {
|
|
/*Enable Analog and DSP register Bank access by*/
|
|
phy_write(phydev, 0x14, 0x0000);
|
|
phy_write(phydev, 0x14, 0x0400);
|
|
phy_write(phydev, 0x14, 0x0000);
|
|
phy_write(phydev, 0x14, 0x0400);
|
|
phy_write(phydev, 0x17, setup_amp);
|
|
phy_write(phydev, 0x14, 0x4418);
|
|
pr_info("set phy setup_amp = %d\n", setup_amp);
|
|
} else {
|
|
/*env not set, efuse not valid return*/
|
|
pr_info("env not set, efuse also invalid\n");
|
|
}
|
|
/*voltage phy*/
|
|
/*t3x*/
|
|
if (eth_priv->phy_mode == 1) {
|
|
// phy_tst_write(phydev, 0x18, 0x8);
|
|
/*set A4 bit[12:14] as 0, all the setting from mail 2023-4-7 title:T3X Ethernet */
|
|
phy_tst_write(phydev, 0x15, phy_tst_read(phydev, 0x15) & 0x8fff);
|
|
pr_info("setup voltage phy %x\n", phy_tst_read(phydev, 0x15));
|
|
}
|
|
/*txhd2*/
|
|
if (eth_priv->phy_mode == 2) {
|
|
phy_tst_write(phydev, 0x16, 0x8402);
|
|
phy_tst_write(phydev, 0x15, 0x4408);
|
|
pr_debug("setup voltage phy reg16 %x reg15 %x\n",
|
|
phy_tst_read(phydev, 0x16),
|
|
phy_tst_read(phydev, 0x15));
|
|
}
|
|
/*s1a*/
|
|
if (eth_priv->phy_mode == 3) {
|
|
phy_tst_write(phydev, 0x16, 0x8402);
|
|
phy_tst_write(phydev, 0x15, 0x4408);
|
|
pr_debug("setup voltage phy reg16 %x reg15 %x\n",
|
|
phy_tst_read(phydev, 0x16),
|
|
phy_tst_read(phydev, 0x15));
|
|
}
|
|
/*s7*/
|
|
if (eth_priv->phy_mode == 4) {
|
|
phy_tst_write(phydev, 0x16, 0x8406);
|
|
phy_tst_write(phydev, 0x15, 0x4408);
|
|
pr_debug("setup voltage phy reg16 %x reg15 %x\n",
|
|
phy_tst_read(phydev, 0x16),
|
|
phy_tst_read(phydev, 0x15));
|
|
phy_tst_write(phydev, 0x1b, 0x40a0);
|
|
usleep_range(800, 1000);
|
|
phy_tst_write(phydev, 0x1b, 0x00a0);
|
|
}
|
|
/*s7d s6*/
|
|
if (eth_priv->phy_mode == 5 || eth_priv->phy_mode == 6) {
|
|
phy_tst_write(phydev, 0x16, 0x8402);
|
|
phy_tst_write(phydev, 0x15, 0x4408);
|
|
pr_debug("setup voltage phy reg16 %x reg15 %x\n",
|
|
phy_tst_read(phydev, 0x16),
|
|
phy_tst_read(phydev, 0x15));
|
|
phy_tst_write(phydev, 0x1b, 0x40a0);
|
|
usleep_range(800, 1000);
|
|
phy_tst_write(phydev, 0x1b, 0x00a0);
|
|
}
|
|
/*t6d*/
|
|
if (eth_priv->phy_mode == 7) {
|
|
phy_tst_write(phydev, 0x16, 0x8406);
|
|
phy_tst_write(phydev, 0x15, 0x4408);
|
|
pr_debug("setup voltage phy reg16 %x reg15 %x\n",
|
|
phy_tst_read(phydev, 0x16),
|
|
phy_tst_read(phydev, 0x15));
|
|
phy_tst_write(phydev, 0x1b, 0x40a0);
|
|
usleep_range(800, 1000);
|
|
phy_tst_write(phydev, 0x1b, 0x00a0);
|
|
}
|
|
/*t6x*/
|
|
if (eth_priv->phy_mode == 8) {
|
|
phy_tst_write(phydev, 0x16, 0x8406);
|
|
phy_tst_write(phydev, 0x15, 0x2408);
|
|
pr_info("setup voltage phy reg16 %x reg15 %x\n",
|
|
phy_tst_read(phydev, 0x16),
|
|
phy_tst_read(phydev, 0x15));
|
|
}
|
|
|
|
if (eth_priv->ephy_eee_support && eth_priv->inphy_eee_enable)
|
|
phy_write(phydev, SPEC_MODE, SPEC_CLKSELFREQ
|
|
| FIELD_PREP(SPEC_MII_MODE, 0x0)
|
|
| FIELD_PREP(SPEC_PHYADDR, 0x8)); // cover for mii
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int gxl_suspend(struct phy_device *phydev)
|
|
{
|
|
int rtn = 0;
|
|
struct aml_eth_priv *eth_priv = aml_get_eth_priv_by_pdev(phydev);
|
|
|
|
if (eth_priv->wol_switch_from_user == 0)
|
|
rtn = genphy_suspend(phydev);
|
|
|
|
return rtn;
|
|
}
|
|
|
|
static int gxl_resume(struct phy_device *phydev)
|
|
{
|
|
int rtn = 0;
|
|
struct aml_eth_priv *eth_priv = aml_get_eth_priv_by_pdev(phydev);
|
|
unsigned int an_advertise = 0;
|
|
|
|
if (eth_priv->wol_switch_from_user == 0)
|
|
rtn = genphy_resume(phydev);
|
|
|
|
an_advertise = phy_read(phydev, 4);
|
|
if ((an_advertise & 0x1) == 0) {
|
|
an_advertise |= 0x1;
|
|
phy_write(phydev, 4, an_advertise);
|
|
pr_debug("force 0x%x\n", an_advertise);
|
|
}
|
|
return rtn;
|
|
}
|
|
#endif
|
|
|
|
int gxl_resume_internal_registers(struct phy_device *phydev)
|
|
{
|
|
phy_write(phydev, 30, 0x7e);
|
|
return custom_internal_config(phydev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(gxl_resume_internal_registers);
|
|
#endif
|
|
static struct phy_driver meson_gxl_phy[] = {
|
|
{
|
|
PHY_ID_MATCH_EXACT(0x01814400),
|
|
.name = "Meson GXL Internal PHY",
|
|
.probe = meson_gxl_probe,
|
|
/* PHY_BASIC_FEATURES */
|
|
.flags = PHY_IS_INTERNAL,
|
|
.soft_reset = genphy_soft_reset,
|
|
.config_init = meson_gxl_config_init,
|
|
.read_status = meson_gxl_read_status,
|
|
.config_intr = meson_gxl_config_intr,
|
|
.handle_interrupt = meson_gxl_handle_interrupt,
|
|
.suspend = genphy_suspend,
|
|
.resume = genphy_resume,
|
|
}, {
|
|
PHY_ID_MATCH_EXACT(0x01803301),
|
|
.name = "Meson G12A Internal PHY",
|
|
.probe = meson_gxl_probe,
|
|
/* PHY_BASIC_FEATURES */
|
|
.flags = PHY_IS_INTERNAL,
|
|
.soft_reset = genphy_soft_reset,
|
|
.config_intr = meson_gxl_config_intr,
|
|
.handle_interrupt = meson_gxl_handle_interrupt,
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_ETH_PRIVE)
|
|
.config_init = custom_internal_config,
|
|
.read_status = meson_g12a_read_status,
|
|
.config_aneg = meson_g12a_config_aneg,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.suspend = gxl_suspend,
|
|
.resume = gxl_resume,
|
|
#endif
|
|
#else
|
|
.suspend = genphy_suspend,
|
|
.resume = genphy_resume,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
|
|
{ PHY_ID_MATCH_VENDOR(0x01814400) },
|
|
{ PHY_ID_MATCH_VENDOR(0x01803301) },
|
|
{ }
|
|
};
|
|
|
|
module_phy_driver(meson_gxl_phy);
|
|
|
|
MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
|
|
|
|
MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
|
|
MODULE_AUTHOR("Baoqi wang");
|
|
MODULE_AUTHOR("Neil Armstrong <[email protected]>");
|
|
MODULE_AUTHOR("Jerome Brunet <[email protected]>");
|
|
MODULE_LICENSE("GPL");
|