diff options
Diffstat (limited to 'drivers/net/phy/at803x.c')
-rw-r--r-- | drivers/net/phy/at803x.c | 103 |
1 files changed, 102 insertions, 1 deletions
diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 3603eec7217f..3133a05c56a8 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -18,6 +18,7 @@ #include <linux/etherdevice.h> #include <linux/of_gpio.h> #include <linux/gpio/consumer.h> +#include <linux/phy.h> #define AT803X_INTR_ENABLE 0x12 #define AT803X_INTR_ENABLE_AUTONEG_ERR BIT(15) @@ -39,11 +40,13 @@ #define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C #define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B #define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A +#define AT803X_SMARTEEE_CTL3_OFFSET 0x805D #define AT803X_MMD_ACCESS_CONTROL 0x0D #define AT803X_MMD_ACCESS_CONTROL_DATA 0x0E #define AT803X_FUNC_DATA 0x4003 #define AT803X_REG_CHIP_CONFIG 0x1f #define AT803X_BT_BX_REG_SEL 0x8000 +#define AT803X_SMARTEEE_DISABLED_VAL 0x1000 #define AT803X_DEBUG_ADDR 0x1D #define AT803X_DEBUG_DATA 0x1E @@ -60,10 +63,21 @@ #define AT803X_DEBUG_REG_5 0x05 #define AT803X_DEBUG_TX_CLK_DLY_EN BIT(8) +#define AT803X_DEBUG_REG_31 0x1f +#define AT803X_VDDIO_1P8V_EN 0x8 + #define ATH8030_PHY_ID 0x004dd076 #define ATH8031_PHY_ID 0x004dd074 #define ATH8035_PHY_ID 0x004dd072 +/* LED_ACT is busy on blinding even if no any frame transferring, + * it may cause by PHY/RJ45 power supply issue, the fixup flag just + * to do sw workaround for the issue. + */ +#define AT803X_LED_ACT_BLINDING_WORKAROUND (1 << 0) +#define AT803X_EEE_FEATURE_DISABLE (1 << 1) +#define AT803X_VDDIO_1P8V (1 << 2) + MODULE_DESCRIPTION("Atheros 803x PHY driver"); MODULE_AUTHOR("Matus Ujhelyi"); MODULE_LICENSE("GPL"); @@ -71,6 +85,7 @@ MODULE_LICENSE("GPL"); struct at803x_priv { bool phy_reset:1; struct gpio_desc *gpiod_reset; + u32 quirks; }; struct at803x_context { @@ -122,6 +137,39 @@ static inline int at803x_enable_tx_delay(struct phy_device *phydev) AT803X_DEBUG_TX_CLK_DLY_EN); } +static inline int at803x_set_vddio_1p8v(struct phy_device *phydev) +{ + return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_31, 0, + AT803X_VDDIO_1P8V_EN); +} + +static int at803x_disable_eee(struct phy_device *phydev) +{ + int ret; + + ret = phy_write(phydev, AT803X_MMD_ACCESS_CONTROL, + AT803X_DEVICE_ADDR); + if (ret < 0) + return ret; + + ret = phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA, + AT803X_SMARTEEE_CTL3_OFFSET); + if (ret < 0) + return ret; + + ret = phy_write(phydev, AT803X_MMD_ACCESS_CONTROL, + AT803X_FUNC_DATA); + if (ret < 0) + return ret; + + ret = phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA, + AT803X_SMARTEEE_DISABLED_VAL); + if (ret < 0) + return ret; + + return 0; +} + /* save relevant PHY registers to private copy */ static void at803x_context_save(struct phy_device *phydev, struct at803x_context *context) @@ -259,6 +307,15 @@ static int at803x_probe(struct phy_device *phydev) if (!priv) return -ENOMEM; + if (of_property_read_bool(dev->of_node, "at803x,led-act-blind-workaround")) + priv->quirks |= AT803X_LED_ACT_BLINDING_WORKAROUND; + + if (of_property_read_bool(dev->of_node, "at803x,eee-disabled")) + priv->quirks |= AT803X_EEE_FEATURE_DISABLE; + + if (of_property_read_bool(dev->of_node, "at803x,vddio-1p8v")) + priv->quirks |= AT803X_VDDIO_1P8V; + if (phydev->drv->phy_id != ATH8030_PHY_ID) goto does_not_require_reset_workaround; @@ -277,6 +334,7 @@ does_not_require_reset_workaround: static int at803x_config_init(struct phy_device *phydev) { int ret; + struct at803x_priv *priv = phydev->priv; ret = genphy_config_init(phydev); if (ret < 0) @@ -296,6 +354,18 @@ static int at803x_config_init(struct phy_device *phydev) return ret; } + if (priv->quirks & AT803X_VDDIO_1P8V) { + ret = at803x_set_vddio_1p8v(phydev); + if (ret < 0) + return ret; + } + + if (priv->quirks & AT803X_EEE_FEATURE_DISABLE) { + ret = at803x_disable_eee(phydev); + if (ret < 0) + return ret; + } + return 0; } @@ -363,6 +433,37 @@ static void at803x_link_change_notify(struct phy_device *phydev) } } +int at803x_config_aneg(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + int result; + + /* Only restart aneg if we are advertising something different + * than we were before. + */ + result = genphy_config_aneg_check(phydev); + if (result > 0) { + /* do autonegotiation here */ + result = phy_read(phydev, MII_BMCR); + if (result < 0) + return result; + + /* firstly power down here */ + if (priv->quirks & AT803X_LED_ACT_BLINDING_WORKAROUND) { + phy_write(phydev, MII_BMCR, BMCR_PDOWN); + msleep(1); + } + + result |= BMCR_ANENABLE | BMCR_ANRESTART; + /* Don't isolate the PHY if we're negotiating */ + result &= ~BMCR_ISOLATE; + + result = phy_write(phydev, MII_BMCR, result); + } + + return result; +} + static int at803x_aneg_done(struct phy_device *phydev) { int ccr; @@ -442,7 +543,7 @@ static struct phy_driver at803x_driver[] = { .resume = at803x_resume, .features = PHY_GBIT_FEATURES, .flags = PHY_HAS_INTERRUPT, - .config_aneg = genphy_config_aneg, + .config_aneg = at803x_config_aneg, .read_status = genphy_read_status, .aneg_done = at803x_aneg_done, .ack_interrupt = &at803x_ack_interrupt, |