diff options
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 15 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/phy/aquantia_main.c | 128 | ||||
-rw-r--r-- | drivers/net/phy/at803x.c | 86 | ||||
-rw-r--r-- | drivers/net/phy/inphi.c | 594 | ||||
-rw-r--r-- | drivers/net/phy/mdio_bus.c | 8 | ||||
-rw-r--r-- | drivers/net/phy/mscc.c | 10 | ||||
-rw-r--r-- | drivers/net/phy/nxp-tja11xx.c | 173 | ||||
-rw-r--r-- | drivers/net/phy/phy-core.c | 4 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 25 | ||||
-rw-r--r-- | drivers/net/phy/phylink.c | 14 | ||||
-rw-r--r-- | drivers/net/phy/realtek.c | 80 | ||||
-rw-r--r-- | drivers/net/phy/swphy.c | 1 |
13 files changed, 1101 insertions, 38 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index dcf2051ef2c0..24b2581b8e1e 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -288,6 +288,16 @@ config AT803X_PHY ---help--- Currently supports the AT8030 and AT8035 model +config AT803X_PHY_SMART_EEE + depends on AT803X_PHY + default n + tristate "SmartEEE feature for AT803X PHYs" + ---help--- + Enables the Atheros SmartEEE feature (not IEEE 802.3az). When 2 PHYs + which support this feature are connected back-to-back, they may + negotiate a low-power sleep mode autonomously, without the Ethernet + controller's knowledge. May cause packet loss. + config BCM63XX_PHY tristate "Broadcom 63xx SOCs internal PHY" depends on BCM63XX || COMPILE_TEST @@ -380,6 +390,11 @@ config ICPLUS_PHY ---help--- Currently supports the IP175C and IP1001 PHYs. +config INPHI_PHY + tristate "Inphi CDR 10G/25G Ethernet PHY" + ---help--- + Currently supports the IN112525_S03 part @ 25G + config INTEL_XWAY_PHY tristate "Intel XWAY PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index a03437e091f3..c0bc15a8c177 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_DP83848_PHY) += dp83848.o obj-$(CONFIG_DP83867_PHY) += dp83867.o obj-$(CONFIG_FIXED_PHY) += fixed_phy.o obj-$(CONFIG_ICPLUS_PHY) += icplus.o +obj-$(CONFIG_INPHI_PHY) += inphi.o obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o obj-$(CONFIG_LXT_PHY) += lxt.o diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c index 975789d9349d..846c60c2cb80 100644 --- a/drivers/net/phy/aquantia_main.c +++ b/drivers/net/phy/aquantia_main.c @@ -22,6 +22,14 @@ #define PHY_ID_AQR107 0x03a1b4e0 #define PHY_ID_AQCS109 0x03a1b5c2 #define PHY_ID_AQR405 0x03a1b4b0 +#define PHY_ID_AQR112 0x03a1b662 +#define PHY_ID_AQR412 0x03a1b712 + +/* PCS counters */ +#define MDIO_C45_PCS_STAT_XFI_TX_GOOD_FRAMES 0xc860 +#define MDIO_C45_PCS_STAT_XFI_TX_BAD_FRAMES 0xc862 +#define MDIO_C45_PCS_STAT_XFI_RX_GOOD_FRAMES 0xe860 +#define MDIO_C45_PCS_STAT_XFI_RX_BAD_FRAMES 0xe862 #define MDIO_PHYXS_VEND_IF_STATUS 0xe812 #define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3) @@ -31,6 +39,9 @@ #define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII 6 #define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII 10 +#define MDIO_PHYXS_VEND_PROV2 0xC441 +#define MDIO_PHYXS_VEND_PROV2_USX_AN BIT(3) + #define MDIO_AN_VEND_PROV 0xc400 #define MDIO_AN_VEND_PROV_1000BASET_FULL BIT(15) #define MDIO_AN_VEND_PROV_1000BASET_HALF BIT(14) @@ -121,13 +132,39 @@ #define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1) #define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0) +/* registers in MDIO_MMD_VEND1 region */ +#define AQUANTIA_VND1_GLOBAL_SC 0x000 +#define AQUANTIA_VND1_GLOBAL_SC_LP BIT(0xb) + +/* global start rate, the protocol associated with this speed is used by default + * on SI. + */ +#define AQUANTIA_VND1_GSTART_RATE 0x31a +#define AQUANTIA_VND1_GSTART_RATE_OFF 0 +#define AQUANTIA_VND1_GSTART_RATE_100M 1 +#define AQUANTIA_VND1_GSTART_RATE_1G 2 +#define AQUANTIA_VND1_GSTART_RATE_10G 3 +#define AQUANTIA_VND1_GSTART_RATE_2_5G 4 +#define AQUANTIA_VND1_GSTART_RATE_5G 5 + +/* SYSCFG registers for 100M, 1G, 2.5G, 5G, 10G */ +#define AQUANTIA_VND1_GSYSCFG_BASE 0x31b +#define AQUANTIA_VND1_GSYSCFG_100M 0 +#define AQUANTIA_VND1_GSYSCFG_1G 1 +#define AQUANTIA_VND1_GSYSCFG_2_5G 2 +#define AQUANTIA_VND1_GSYSCFG_5G 3 +#define AQUANTIA_VND1_GSYSCFG_10G 4 + struct aqr107_hw_stat { const char *name; int reg; int size; + int devad; }; -#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s } +#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s, \ + MDIO_MMD_C22EXT} +#define C45_PCS_STAT(n, r, s) { n, MDIO_C45_PCS_STAT_ ## r, s, MDIO_MMD_PCS } static const struct aqr107_hw_stat aqr107_hw_stats[] = { SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26), SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26), @@ -139,6 +176,10 @@ static const struct aqr107_hw_stat aqr107_hw_stats[] = { SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8), SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16), SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22), + C45_PCS_STAT("xfi_rx_good_frames", XFI_RX_GOOD_FRAMES, 26), + C45_PCS_STAT("xfi_rx_bad_frames", XFI_RX_BAD_FRAMES, 26), + C45_PCS_STAT("xfi_tx_good_frames", XFI_TX_GOOD_FRAMES, 26), + C45_PCS_STAT("xfi_tx_bad_frames", XFI_TX_BAD_FRAMES, 26), }; #define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats) @@ -168,13 +209,13 @@ static u64 aqr107_get_stat(struct phy_device *phydev, int index) u64 ret; int val; - val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg); + val = phy_read_mmd(phydev, stat->devad, stat->reg); if (val < 0) return U64_MAX; ret = val & GENMASK(len_l - 1, 0); if (len_h) { - val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1); + val = phy_read_mmd(phydev, stat->devad, stat->reg + 1); if (val < 0) return U64_MAX; @@ -241,6 +282,61 @@ static int aqr_config_aneg(struct phy_device *phydev) return genphy_c45_check_and_restart_aneg(phydev, changed); } +static struct { + u16 syscfg; + int cnt; + u16 start_rate; +} aquantia_syscfg[PHY_INTERFACE_MODE_MAX] = { + [PHY_INTERFACE_MODE_SGMII] = {0x04b, AQUANTIA_VND1_GSYSCFG_1G, + AQUANTIA_VND1_GSTART_RATE_1G}, + [PHY_INTERFACE_MODE_2500BASEX] = {0x144, AQUANTIA_VND1_GSYSCFG_2_5G, + AQUANTIA_VND1_GSTART_RATE_2_5G}, + [PHY_INTERFACE_MODE_XGMII] = {0x100, AQUANTIA_VND1_GSYSCFG_10G, + AQUANTIA_VND1_GSTART_RATE_10G}, + [PHY_INTERFACE_MODE_USXGMII] = {0x080, AQUANTIA_VND1_GSYSCFG_10G, + AQUANTIA_VND1_GSTART_RATE_10G}, +}; + +/* Sets up protocol on system side before calling aqr_config_aneg */ +static int aqr_config_aneg_set_prot(struct phy_device *phydev) +{ + int if_type = phydev->interface; + int i; + + if (!aquantia_syscfg[if_type].cnt) + return 0; + + /* set PHY in low power mode so we can configure protocols */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GLOBAL_SC, + AQUANTIA_VND1_GLOBAL_SC_LP); + mdelay(10); + + /* set the default rate to enable the SI link */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GSTART_RATE, + aquantia_syscfg[if_type].start_rate); + + for (i = 0; i <= aquantia_syscfg[if_type].cnt; i++) { + u16 reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, + AQUANTIA_VND1_GSYSCFG_BASE + i); + if (!reg) + continue; + + phy_write_mmd(phydev, MDIO_MMD_VEND1, + AQUANTIA_VND1_GSYSCFG_BASE + i, + aquantia_syscfg[if_type].syscfg); + } + + if (if_type == PHY_INTERFACE_MODE_USXGMII) + phy_write_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_PROV2, + MDIO_PHYXS_VEND_PROV2_USX_AN); + + /* wake PHY back up */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GLOBAL_SC, 0); + mdelay(10); + + return aqr_config_aneg(phydev); +} + static int aqr_config_intr(struct phy_device *phydev) { bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED; @@ -682,6 +778,30 @@ static struct phy_driver aqr_driver[] = { .ack_interrupt = aqr_ack_interrupt, .read_status = aqr_read_status, }, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR112), + .name = "Aquantia AQR112", + .probe = aqr107_probe, + .config_aneg = aqr_config_aneg_set_prot, + .config_intr = aqr_config_intr, + .ack_interrupt = aqr_ack_interrupt, + .read_status = aqr_read_status, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR412), + .name = "Aquantia AQR412", + .probe = aqr107_probe, + .config_aneg = aqr_config_aneg_set_prot, + .config_intr = aqr_config_intr, + .ack_interrupt = aqr_ack_interrupt, + .read_status = aqr_read_status, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, +}, }; module_phy_driver(aqr_driver); @@ -694,6 +814,8 @@ static struct mdio_device_id __maybe_unused aqr_tbl[] = { { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) }, { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) }, { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR112) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR412) }, { } }; diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 1eb5d4fb8925..6808009f9512 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -44,8 +44,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 @@ -62,17 +67,26 @@ #define AT803X_DEBUG_REG_5 0x05 #define AT803X_DEBUG_TX_CLK_DLY_EN BIT(8) +#define AT803X_LPI_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 #define AT803X_PHY_ID_MASK 0xffffffef +#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"); struct at803x_priv { bool phy_reset:1; + u32 quirks; }; struct at803x_context { @@ -136,6 +150,39 @@ static int at803x_disable_tx_delay(struct phy_device *phydev) AT803X_DEBUG_TX_CLK_DLY_EN, 0); } +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) @@ -249,14 +296,41 @@ static int at803x_probe(struct phy_device *phydev) if (!priv) return -ENOMEM; + 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; + phydev->priv = priv; return 0; } +static void at803x_enable_smart_eee(struct phy_device *phydev, int on) +{ + int value; + + /* 5.1.11 Smart_eee control3 */ + value = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x805D); + if (on) + value |= AT803X_LPI_EN; + else + value &= ~AT803X_LPI_EN; + phy_write_mmd(phydev, MDIO_MMD_PCS, 0x805D, value); +} + static int at803x_config_init(struct phy_device *phydev) { int ret; + struct at803x_priv *priv = phydev->priv; + + +#ifdef CONFIG_AT803X_PHY_SMART_EEE + at803x_enable_smart_eee(phydev, 1); +#else + at803x_enable_smart_eee(phydev, 0); +#endif /* The RX and TX delay default is: * after HW reset: RX delay enabled and TX delay disabled @@ -277,6 +351,18 @@ static int at803x_config_init(struct phy_device *phydev) else ret = at803x_disable_tx_delay(phydev); + 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 ret; } diff --git a/drivers/net/phy/inphi.c b/drivers/net/phy/inphi.c new file mode 100644 index 000000000000..65d3059ceae4 --- /dev/null +++ b/drivers/net/phy/inphi.c @@ -0,0 +1,594 @@ +/* + * Copyright 2018 NXP + * Copyright 2018 INPHI + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Inphi is a registered trademark of Inphi Corporation + * + */ + +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/mdio.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/of_irq.h> +#include <linux/workqueue.h> +#include <linux/i2c.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#define PHY_ID_IN112525 0x02107440 + +#define INPHI_S03_DEVICE_ID_MSB 0x2 +#define INPHI_S03_DEVICE_ID_LSB 0x3 + +#define ALL_LANES 4 +#define INPHI_POLL_DELAY 2500 + +#define PHYCTRL_REG1 0x0012 +#define PHYCTRL_REG2 0x0014 +#define PHYCTRL_REG3 0x0120 +#define PHYCTRL_REG4 0x0121 +#define PHYCTRL_REG5 0x0180 +#define PHYCTRL_REG6 0x0580 +#define PHYCTRL_REG7 0x05C4 +#define PHYCTRL_REG8 0x01C8 +#define PHYCTRL_REG9 0x0521 + +#define PHYSTAT_REG1 0x0021 +#define PHYSTAT_REG2 0x0022 +#define PHYSTAT_REG3 0x0123 + +#define PHYMISC_REG1 0x0025 +#define PHYMISC_REG2 0x002c +#define PHYMISC_REG3 0x00b3 +#define PHYMISC_REG4 0x0181 +#define PHYMISC_REG5 0x019D +#define PHYMISC_REG6 0x0198 +#define PHYMISC_REG7 0x0199 +#define PHYMISC_REG8 0x0581 +#define PHYMISC_REG9 0x0598 +#define PHYMISC_REG10 0x059c +#define PHYMISC_REG20 0x01B0 +#define PHYMISC_REG21 0x01BC +#define PHYMISC_REG22 0x01C0 + +#define RX_VCO_CODE_OFFSET 5 +#define VCO_CODE 390 + +int vco_codes[ALL_LANES] = { + VCO_CODE, + VCO_CODE, + VCO_CODE, + VCO_CODE +}; + +static void mykmod_work_handler(struct work_struct *w); + +static struct workqueue_struct *wq; +static DECLARE_DELAYED_WORK(mykmod_work, mykmod_work_handler); +static unsigned long onesec; +struct phy_device *inphi_phydev; + +static int mdio_wr(u32 regnum, u16 val) +{ + regnum = MII_ADDR_C45 | (MDIO_MMD_VEND1 << 16) | (regnum & 0xffff); + + return mdiobus_write(inphi_phydev->mdio.bus, inphi_phydev->mdio.addr, + regnum, val); +} + +static int mdio_rd(u32 regnum) +{ + regnum = MII_ADDR_C45 | (MDIO_MMD_VEND1 << 16) | (regnum & 0xffff); + + return mdiobus_read(inphi_phydev->mdio.bus, inphi_phydev->mdio.addr, + regnum); +} + + +int bit_test(int value, int bit_field) +{ + int result; + int bit_mask = (1 << bit_field); + + result = ((value & bit_mask) == bit_mask); + return result; +} + +int tx_pll_lock_test(int lane) +{ + int i, val, locked = 1; + + if (lane == ALL_LANES) { + for (i = 0; i < ALL_LANES; i++) { + val = mdio_rd(i * 0x100 + PHYSTAT_REG3); + locked = locked & bit_test(val, 15); + } + } else { + val = mdio_rd(lane * 0x100 + PHYSTAT_REG3); + locked = locked & bit_test(val, 15); + } + + return locked; +} + +void rx_reset_assert(int lane) +{ + int mask, val; + + if (lane == ALL_LANES) { + val = mdio_rd(PHYMISC_REG2); + mask = (1 << 15); + mdio_wr(PHYMISC_REG2, val + mask); + } else { + val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); + mask = (1 << 6); + mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask); + } +} + +void rx_reset_de_assert(int lane) +{ + int mask, val; + + if (lane == ALL_LANES) { + val = mdio_rd(PHYMISC_REG2); + mask = 0xffff - (1 << 15); + mdio_wr(PHYMISC_REG2, val & mask); + } else { + val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); + mask = 0xffff - (1 << 6); + mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask); + } +} + +void rx_powerdown_assert(int lane) +{ + int mask, val; + + val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); + mask = (1 << 5); + mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask); +} + +void rx_powerdown_de_assert(int lane) +{ + int mask, val; + + val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); + mask = 0xffff - (1 << 5); + mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask); +} + +void tx_pll_assert(int lane) +{ + int val, recal; + + if (lane == ALL_LANES) { + val = mdio_rd(PHYMISC_REG2); + recal = (1 << 12); + mdio_wr(PHYMISC_REG2, val | recal); + } else { + val = mdio_rd(lane * 0x100 + PHYCTRL_REG4); + recal = (1 << 15); + mdio_wr(lane * 0x100 + PHYCTRL_REG4, val | recal); + } +} + +void tx_pll_de_assert(int lane) +{ + int recal, val; + + if (lane == ALL_LANES) { + val = mdio_rd(PHYMISC_REG2); + recal = 0xefff; + mdio_wr(PHYMISC_REG2, val & recal); + } else { + val = mdio_rd(lane * 0x100 + PHYCTRL_REG4); + recal = 0x7fff; + mdio_wr(lane * 0x100 + PHYCTRL_REG4, val & recal); + } +} + +void tx_core_assert(int lane) +{ + int recal, val, val2, core_reset; + + if (lane == 4) { + val = mdio_rd(PHYMISC_REG2); + recal = 1 << 10; + mdio_wr(PHYMISC_REG2, val | recal); + } else { + val2 = mdio_rd(PHYMISC_REG3); + core_reset = (1 << (lane + 8)); + mdio_wr(PHYMISC_REG3, val2 | core_reset); + } +} + +void lol_disable(int lane) +{ + int val, mask; + + val = mdio_rd(PHYMISC_REG3); + mask = 1 << (lane + 4); + mdio_wr(PHYMISC_REG3, val | mask); +} + +void tx_core_de_assert(int lane) +{ + int val, recal, val2, core_reset; + + if (lane == ALL_LANES) { + val = mdio_rd(PHYMISC_REG2); + recal = 0xffff - (1 << 10); + mdio_wr(PHYMISC_REG2, val & recal); + } else { + val2 = mdio_rd(PHYMISC_REG3); + core_reset = 0xffff - (1 << (lane + 8)); + mdio_wr(PHYMISC_REG3, val2 & core_reset); + } +} + +void tx_restart(int lane) +{ + tx_core_assert(lane); + tx_pll_assert(lane); + tx_pll_de_assert(lane); + usleep_range(1500, 1600); + tx_core_de_assert(lane); +} + +void disable_lane(int lane) +{ + rx_reset_assert(lane); + rx_powerdown_assert(lane); + tx_core_assert(lane); + lol_disable(lane); +} + +void toggle_reset(int lane) +{ + int reg, val, orig; + + if (lane == ALL_LANES) { + mdio_wr(PHYMISC_REG2, 0x8000); + udelay(100); + mdio_wr(PHYMISC_REG2, 0x0000); + } else { + reg = lane * 0x100 + PHYCTRL_REG8; + val = (1 << 6); + orig = mdio_rd(reg); + mdio_wr(reg, orig + val); + udelay(100); + mdio_wr(reg, orig); + } +} + +int az_complete_test(int lane) +{ + int success = 1, value; + + if (lane == 0 || lane == ALL_LANES) { + value = mdio_rd(PHYCTRL_REG5); + success = success & bit_test(value, 2); + } + if (lane == 1 || lane == ALL_LANES) { + value = mdio_rd(PHYCTRL_REG5 + 0x100); + success = success & bit_test(value, 2); + } + if (lane == 2 || lane == ALL_LANES) { + value = mdio_rd(PHYCTRL_REG5 + 0x200); + success = success & bit_test(value, 2); + } + if (lane == 3 || lane == ALL_LANES) { + value = mdio_rd(PHYCTRL_REG5 + 0x300); + success = success & bit_test(value, 2); + } + + return success; +} + +void save_az_offsets(int lane) +{ + int i; + +#define AZ_OFFSET_LANE_UPDATE(reg, lane) \ + mdio_wr((reg) + (lane) * 0x100, \ + (mdio_rd((reg) + (lane) * 0x100) >> 8)) + + if (lane == ALL_LANES) { + for (i = 0; i < ALL_LANES; i++) { + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, i); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, i); + } + } else { + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, lane); + AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, lane); + } + + mdio_wr(PHYCTRL_REG7, 0x0001); +} + +void save_vco_codes(int lane) +{ + int i; + + if (lane == ALL_LANES) { + for (i = 0; i < ALL_LANES; i++) { + vco_codes[i] = mdio_rd(PHYMISC_REG5 + i * 0x100); + mdio_wr(PHYMISC_REG5 + i * 0x100, + vco_codes[i] + RX_VCO_CODE_OFFSET); + } + } else { + vco_codes[lane] = mdio_rd(PHYMISC_REG5 + lane * 0x100); + mdio_wr(PHYMISC_REG5 + lane * 0x100, + vco_codes[lane] + RX_VCO_CODE_OFFSET); + } +} + +int inphi_lane_recovery(int lane) +{ + int i, value, az_pass; + + switch (lane) { + case 0: + case 1: + case 2: + case 3: + rx_reset_assert(lane); + mdelay(20); + break; + case ALL_LANES: + mdio_wr(PHYMISC_REG2, 0x9C00); + mdelay(20); + do { + value = mdio_rd(PHYMISC_REG2); + udelay(10); + } while (!bit_test(value, 4)); + break; + default: + dev_err(&inphi_phydev->mdio.dev, + "Incorrect usage of APIs in %s driver\n", + inphi_phydev->drv->name); + break; + } + + if (lane == ALL_LANES) { + for (i = 0; i < ALL_LANES; i++) + mdio_wr(PHYMISC_REG7 + i * 0x100, VCO_CODE); + } else { + mdio_wr(PHYMISC_REG7 + lane * 0x100, VCO_CODE); + } + + if (lane == ALL_LANES) + for (i = 0; i < ALL_LANES; i++) + mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0418); + else + mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0418); + + mdio_wr(PHYCTRL_REG7, 0x0000); + + rx_reset_de_assert(lane); + + if (lane == ALL_LANES) { + for (i = 0; i < ALL_LANES; i++) { + mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0410); + mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0412); + } + } else { + mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0410); + mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0412); + } + + for (i = 0; i < 64; i++) { + mdelay(100); + az_pass = az_complete_test(lane); + if (az_pass) { + save_az_offsets(lane); + break; + } + } + + if (!az_pass) { + pr_info("in112525: AZ calibration fail @ lane=%d\n", lane); + return -1; + } + + if (lane == ALL_LANES) { + mdio_wr(PHYMISC_REG8, 0x0002); + mdio_wr(PHYMISC_REG9, 0x2028); + mdio_wr(PHYCTRL_REG6, 0x0010); + usleep_range(1000, 1200); + mdio_wr(PHYCTRL_REG6, 0x0110); + mdelay(30); + mdio_wr(PHYMISC_REG9, 0x3020); + } else { + mdio_wr(PHYMISC_REG4 + lane * 0x100, 0x0002); + mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x2028); + mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0010); + usleep_range(1000, 1200); + mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0110); + mdelay(30); + mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x3020); + } + + if (lane == ALL_LANES) { + mdio_wr(PHYMISC_REG2, 0x1C00); + mdio_wr(PHYMISC_REG2, 0x0C00); + } else { + tx_restart(lane); + mdelay(11); + } + + if (lane == ALL_LANES) { + if (bit_test(mdio_rd(PHYMISC_REG2), 6) == 0) + return -1; + } else { + if (tx_pll_lock_test(lane) == 0) + return -1; + } + + save_vco_codes(lane); + + if (lane == ALL_LANES) { + mdio_wr(PHYMISC_REG2, 0x0400); + mdio_wr(PHYMISC_REG2, 0x0000); + value = mdio_rd(PHYCTRL_REG1); + value = value & 0xffbf; + mdio_wr(PHYCTRL_REG2, value); + } else { + tx_core_de_assert(lane); + } + + if (lane == ALL_LANES) { + mdio_wr(PHYMISC_REG1, 0x8000); + mdio_wr(PHYMISC_REG1, 0x0000); + } + mdio_rd(PHYMISC_REG1); + mdio_rd(PHYMISC_REG1); + usleep_range(1000, 1200); + mdio_rd(PHYSTAT_REG1); + mdio_rd(PHYSTAT_REG2); + + return 0; +} + +static void mykmod_work_handler(struct work_struct *w) +{ + int all_lanes_lock, lane0_lock, lane1_lock, lane2_lock, lane3_lock; + + lane0_lock = bit_test(mdio_rd(0x123), 15); + lane1_lock = bit_test(mdio_rd(0x223), 15); + lane2_lock = bit_test(mdio_rd(0x323), 15); + lane3_lock = bit_test(mdio_rd(0x423), 15); + + /* check if the chip had any successful lane lock from the previous + * stage (e.g. u-boot) + */ + all_lanes_lock = lane0_lock | lane1_lock | lane2_lock | lane3_lock; + + if (!all_lanes_lock) { + /* start fresh */ + inphi_lane_recovery(ALL_LANES); + } else { + if (!lane0_lock) + inphi_lane_recovery(0); + if (!lane1_lock) + inphi_lane_recovery(1); + if (!lane2_lock) + inphi_lane_recovery(2); + if (!lane3_lock) + inphi_lane_recovery(3); + } + + queue_delayed_work(wq, &mykmod_work, onesec); +} + +int inphi_probe(struct phy_device *phydev) +{ + int phy_id = 0, id_lsb = 0, id_msb = 0; + + /* setup the inphi_phydev ptr for mdio_rd/mdio_wr APIs */ + inphi_phydev = phydev; + + /* Read device id from phy registers */ + id_lsb = mdio_rd(INPHI_S03_DEVICE_ID_MSB); + if (id_lsb < 0) + return -ENXIO; + + phy_id = id_lsb << 16; + + id_msb = mdio_rd(INPHI_S03_DEVICE_ID_LSB); + if (id_msb < 0) + return -ENXIO; + + phy_id |= id_msb; + + /* Make sure the device tree binding matched the driver with the + * right device. + */ + if (phy_id != phydev->drv->phy_id) { + dev_err(&phydev->mdio.dev, + "Error matching phy with %s driver\n", + phydev->drv->name); + return -ENODEV; + } + + /* update the local phydev pointer, used inside all APIs */ + inphi_phydev = phydev; + onesec = msecs_to_jiffies(INPHI_POLL_DELAY); + + wq = create_singlethread_workqueue("inphi_kmod"); + if (wq) { + queue_delayed_work(wq, &mykmod_work, onesec); + } else { + dev_err(&phydev->mdio.dev, + "Error creating kernel workqueue for %s driver\n", + phydev->drv->name); + return -ENOMEM; + } + + return 0; +} + +static struct phy_driver inphi_driver[] = { +{ + .phy_id = PHY_ID_IN112525, + .phy_id_mask = 0x0ff0fff0, + .name = "Inphi 112525_S03", + .features = PHY_GBIT_FEATURES, + .probe = &inphi_probe, +}, +}; + +module_phy_driver(inphi_driver); + +static struct mdio_device_id __maybe_unused inphi_tbl[] = { + { PHY_ID_IN112525, 0x0ff0fff0}, + {}, +}; + +MODULE_DEVICE_TABLE(mdio, inphi_tbl); diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 5bf06eac04ba..e023def8a07c 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -408,8 +408,11 @@ int __mdiobus_register(struct mii_bus *bus, struct module *owner) gpiod_set_value_cansleep(gpiod, 0); } - if (bus->reset) - bus->reset(bus); + if (bus->reset) { + err = bus->reset(bus); + if (err) + goto reset_err; + } for (i = 0; i < PHY_MAX_ADDR; i++) { if ((bus->phy_mask & (1 << i)) == 0) { @@ -439,6 +442,7 @@ error: mdiodev->device_free(mdiodev); } +reset_err: /* Put PHYs in RESET to save power */ if (bus->reset_gpiod) gpiod_set_value_cansleep(bus->reset_gpiod, 1); diff --git a/drivers/net/phy/mscc.c b/drivers/net/phy/mscc.c index 2339b9381d21..9186a24a4405 100644 --- a/drivers/net/phy/mscc.c +++ b/drivers/net/phy/mscc.c @@ -176,6 +176,8 @@ enum rgmii_rx_clock_delay { #define SECURE_ON_PASSWD_LEN_4 0x4000 /* Extended Page 3 Registers */ +#define MSCC_PHY_SERDES_CON 16 +#define MSCC_PHY_SERDES_ANEG BIT(7) #define MSCC_PHY_SERDES_TX_VALID_CNT 21 #define MSCC_PHY_SERDES_TX_CRC_ERR_CNT 22 #define MSCC_PHY_SERDES_RX_VALID_CNT 28 @@ -2131,6 +2133,14 @@ static int vsc8514_config_init(struct phy_device *phydev) mutex_unlock(&phydev->mdio.bus->mdio_lock); + ret = phy_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_3); + if (ret) + return ret; + + ret = phy_set_bits(phydev, MSCC_PHY_SERDES_CON, MSCC_PHY_SERDES_ANEG); + if (ret) + return ret; + ret = phy_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD); if (ret) diff --git a/drivers/net/phy/nxp-tja11xx.c b/drivers/net/phy/nxp-tja11xx.c index b705d0bd798b..90eeff3986df 100644 --- a/drivers/net/phy/nxp-tja11xx.c +++ b/drivers/net/phy/nxp-tja11xx.c @@ -8,6 +8,7 @@ #include <linux/kernel.h> #include <linux/mii.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/phy.h> #include <linux/hwmon.h> #include <linux/bitfield.h> @@ -26,11 +27,15 @@ #define MII_ECTRL_WAKE_REQUEST BIT(0) #define MII_CFG1 18 +#define MII_CFG1_MASTER_SLAVE BIT(15) #define MII_CFG1_AUTO_OP BIT(14) +#define MII_CFG1_MII_MODE GENMASK(9, 8) #define MII_CFG1_SLEEP_CONFIRM BIT(6) #define MII_CFG1_LED_MODE_MASK GENMASK(5, 4) #define MII_CFG1_LED_MODE_LINKUP 0 #define MII_CFG1_LED_ENABLE BIT(3) +#define MII_CFG1_MODE_REFCLK_IN 0x100 +#define MII_CFG1_MODE_REFCLK_OUT 0x200 #define MII_CFG2 19 #define MII_CFG2_SLEEP_REQUEST_TO GENMASK(1, 0) @@ -49,9 +54,13 @@ #define MII_COMMCFG 27 #define MII_COMMCFG_AUTO_OP BIT(15) +#define TJA110X_REFCLK_IN (0x1 << 0) + struct tja11xx_priv { char *hwmon_name; struct device *hwmon_dev; + + u32 quirks; }; struct tja11xx_phy_stats { @@ -110,6 +119,11 @@ static int tja11xx_enable_link_control(struct phy_device *phydev) return phy_set_bits(phydev, MII_ECTRL, MII_ECTRL_LINK_CONTROL); } +static int tja11xx_disable_link_control(struct phy_device *phydev) +{ + return phy_clear_bits(phydev, MII_ECTRL, MII_ECTRL_LINK_CONTROL); +} + static int tja11xx_wakeup(struct phy_device *phydev) { int ret; @@ -169,6 +183,8 @@ static int tja11xx_soft_reset(struct phy_device *phydev) static int tja11xx_config_init(struct phy_device *phydev) { + struct tja11xx_priv *priv = phydev->priv; + int reg_mask, reg_val = 0; int ret; ret = tja11xx_enable_reg_write(phydev); @@ -181,15 +197,35 @@ static int tja11xx_config_init(struct phy_device *phydev) switch (phydev->phy_id & PHY_ID_MASK) { case PHY_ID_TJA1100: - ret = phy_modify(phydev, MII_CFG1, - MII_CFG1_AUTO_OP | MII_CFG1_LED_MODE_MASK | - MII_CFG1_LED_ENABLE, - MII_CFG1_AUTO_OP | MII_CFG1_LED_MODE_LINKUP | - MII_CFG1_LED_ENABLE); + reg_mask = MII_CFG1_AUTO_OP | MII_CFG1_LED_MODE_MASK | + MII_CFG1_LED_ENABLE; + reg_val = MII_CFG1_AUTO_OP | MII_CFG1_LED_MODE_LINKUP | + MII_CFG1_LED_ENABLE; + + reg_mask |= MII_CFG1_MII_MODE; + if (phydev->interface == PHY_INTERFACE_MODE_RMII) { + if (priv->quirks & TJA110X_REFCLK_IN) + reg_val |= MII_CFG1_MODE_REFCLK_IN; + else + reg_val |= MII_CFG1_MODE_REFCLK_OUT; + } + + ret = phy_modify(phydev, MII_CFG1, reg_mask, reg_val); if (ret) return ret; break; case PHY_ID_TJA1101: + reg_mask = MII_CFG1_MII_MODE; + if (phydev->interface == PHY_INTERFACE_MODE_RMII) { + if (priv->quirks & TJA110X_REFCLK_IN) + reg_val = MII_CFG1_MODE_REFCLK_IN; + else + reg_val = MII_CFG1_MODE_REFCLK_OUT; + } + ret = phy_modify(phydev, MII_CFG1, reg_mask, reg_val); + if (ret) + return ret; + ret = phy_set_bits(phydev, MII_COMMCFG, MII_COMMCFG_AUTO_OP); if (ret) return ret; @@ -327,11 +363,128 @@ static const struct hwmon_chip_info tja11xx_hwmon_chip_info = { .info = tja11xx_hwmon_info, }; +/* Helper function, configures phy as master or slave + * @param phydev the phy to be configured + * @param setmaster ==0: set to slave + * !=0: set to master + * @return 0 on success, error code on failure + */ +static int set_master_cfg(struct phy_device *phydev, int setmaster) +{ + int err; + + /* disable link control prior to master/slave cfg */ + tja11xx_disable_link_control(phydev); + + err = phy_modify(phydev, MII_CFG1, MII_CFG1_MASTER_SLAVE, + setmaster ? MII_CFG1_MASTER_SLAVE : 0); + if (err < 0) + goto phy_configure_error; + + /* enable link control after master/slave cfg was set */ + tja11xx_enable_link_control(phydev); + + return 0; + +/* error handling */ +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error\n"); + return err; +} + +/* Helper function, reads master/slave configuration of phy + * @param phydev the phy to be read + * + * @return ==0: is slave + * !=0: is master + */ +static int get_master_cfg(struct phy_device *phydev) +{ + int reg_val; + + /* read the current configuration */ + reg_val = phy_read(phydev, MII_CFG1); + if (reg_val < 0) + goto phy_read_error; + + return reg_val & MII_CFG1_MASTER_SLAVE; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error\n"); + return reg_val; +} + +/* This function handles read accesses to the node 'master_cfg' in + * sysfs. + * Depending on current configuration of the phy, the node reads + * 'master' or 'slave' + */ +static ssize_t master_cfg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int is_master; + struct phy_device *phydev = to_phy_device(dev); + + is_master = get_master_cfg(phydev); + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, "%s\n", + is_master ? "master" : "slave"); +} + +/* This function handles write accesses to the node 'master_cfg' in sysfs. + * Depending on the value written to it, the phy is configured as + * master or slave + */ +static ssize_t master_cfg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int setmaster; + struct phy_device *phydev = to_phy_device(dev); + + /* parse the buffer */ + err = kstrtoint(buf, 10, &setmaster); + if (err < 0) + goto phy_parse_error; + + /* write configuration to the phy */ + err = set_master_cfg(phydev, setmaster); + if (err < 0) + goto phy_cfg_error; + + return count; + +/* error handling */ +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse failed\n"); + return err; + +phy_cfg_error: + dev_err(&phydev->mdio.dev, "phy cfg error\n"); + return err; +} + +static DEVICE_ATTR_RW(master_cfg); + +static struct attribute *nxp_sysfs_entries[] = { + &dev_attr_master_cfg.attr, + NULL +}; + +static struct attribute_group nxp_attribute_group = { + .name = "configuration", + .attrs = nxp_sysfs_entries, +}; + static int tja11xx_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct tja11xx_priv *priv; int i; + int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -345,6 +498,16 @@ static int tja11xx_probe(struct phy_device *phydev) if (hwmon_is_bad_char(priv->hwmon_name[i])) priv->hwmon_name[i] = '_'; + if (dev->of_node && + of_property_read_bool(dev->of_node, "tja110x,refclk_in")) + priv->quirks |= TJA110X_REFCLK_IN; + + /* register sysfs files */ + phydev->priv = priv; + ret = sysfs_create_group(&phydev->mdio.dev.kobj, &nxp_attribute_group); + if (ret) + return ret; + priv->hwmon_dev = devm_hwmon_device_register_with_info(dev, priv->hwmon_name, phydev, diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 9412669b579c..790a5c81e533 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -379,7 +379,7 @@ int __phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) if (regnum > (u16)~0 || devad > 32) return -EINVAL; - if (phydev->drv->read_mmd) { + if (phydev->drv && phydev->drv->read_mmd) { val = phydev->drv->read_mmd(phydev, devad, regnum); } else if (phydev->is_c45) { u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff); @@ -436,7 +436,7 @@ int __phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) if (regnum > (u16)~0 || devad > 32) return -EINVAL; - if (phydev->drv->write_mmd) { + if (phydev->drv && phydev->drv->write_mmd) { ret = phydev->drv->write_mmd(phydev, devad, regnum, val); } else if (phydev->is_c45) { u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 9d0a306f0562..c02612787bfd 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -240,13 +240,23 @@ static bool mdio_bus_phy_may_suspend(struct phy_device *phydev) if (!drv || !phydrv->suspend) return false; - /* PHY not attached? May suspend if the PHY has not already been - * suspended as part of a prior call to phy_disconnect() -> - * phy_detach() -> phy_suspend() because the parent netdev might be the - * MDIO bus driver and clock gated at this point. - */ + /* netdev is NULL has three cases: + * - phy is not found + * - phy is found, match to general phy driver + * - phy is found, match to specifical phy driver + * + * Case 1: phy is not found, cannot communicate by MDIO bus. + * Case 2: phy is found: + * if phy dev driver probe/bind err, netdev is not __open__ + * status, mdio bus is unregistered. + * if phy is detached, phy had entered suspended status. + * Case 3: phy is found, phy is detached, phy had entered suspended + * status. + * + * So, in here, it shouldn't set phy to suspend by calling mdio bus. + */ if (!netdev) - goto out; + return false; if (netdev->wol_enabled) return false; @@ -266,8 +276,7 @@ static bool mdio_bus_phy_may_suspend(struct phy_device *phydev) if (device_may_wakeup(&netdev->dev)) return false; -out: - return !phydev->suspended; + return true; } static int mdio_bus_phy_suspend(struct device *dev) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index bf5bbb565cf5..6f9802d7415f 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -279,6 +279,7 @@ static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode) switch (pl->link_config.interface) { case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: phylink_set(pl->supported, 10baseT_Half); phylink_set(pl->supported, 10baseT_Full); phylink_set(pl->supported, 100baseT_Half); @@ -295,6 +296,16 @@ static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode) phylink_set(pl->supported, 2500baseX_Full); break; + case PHY_INTERFACE_MODE_USXGMII: + phylink_set(pl->supported, 10baseT_Half); + phylink_set(pl->supported, 10baseT_Full); + phylink_set(pl->supported, 100baseT_Half); + phylink_set(pl->supported, 100baseT_Full); + phylink_set(pl->supported, 1000baseT_Half); + phylink_set(pl->supported, 1000baseT_Full); + phylink_set(pl->supported, 2500baseX_Full); + break; + case PHY_INTERFACE_MODE_10GKR: phylink_set(pl->supported, 10baseT_Half); phylink_set(pl->supported, 10baseT_Full); @@ -1009,7 +1020,8 @@ void phylink_start(struct phylink *pl) if (irq <= 0) mod_timer(&pl->link_poll, jiffies + HZ); } - if (pl->link_an_mode == MLO_AN_FIXED && pl->get_fixed_state) + if ((pl->link_an_mode == MLO_AN_FIXED && pl->get_fixed_state) || + pl->config->pcs_poll) mod_timer(&pl->link_poll, jiffies + HZ); if (pl->phydev) phy_start(pl->phydev); diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c index 879ca37c8508..248ae3bb1e1f 100644 --- a/drivers/net/phy/realtek.c +++ b/drivers/net/phy/realtek.c @@ -9,6 +9,7 @@ * Copyright (c) 2004 Freescale Semiconductor, Inc. */ #include <linux/bitops.h> +#include <linux/of.h> #include <linux/phy.h> #include <linux/module.h> @@ -28,6 +29,7 @@ #define RTL8211F_INSR 0x1d +#define RTL8211F_RX_DELAY BIT(3) #define RTL8211F_TX_DELAY BIT(8) #define RTL8211E_TX_DELAY BIT(1) #define RTL8211E_RX_DELAY BIT(2) @@ -49,10 +51,20 @@ #define RTL_GENERIC_PHYID 0x001cc800 +/* page 0xa43, register 0x19 */ +#define RTL8211F_PHYCR2 0x19 +#define RTL8211F_CLKOUT_EN BIT(0) + +#define RTL821X_CLKOUT_EN_FEATURE (1 << 0) + MODULE_DESCRIPTION("Realtek PHY driver"); MODULE_AUTHOR("Johnson Leung"); MODULE_LICENSE("GPL"); +struct rtl821x_priv { + u32 quirks; +}; + static int rtl821x_read_page(struct phy_device *phydev) { return __phy_read(phydev, RTL821x_PAGE_SELECT); @@ -63,6 +75,23 @@ static int rtl821x_write_page(struct phy_device *phydev, int page) return __phy_write(phydev, RTL821x_PAGE_SELECT, page); } +static int rtl821x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct rtl821x_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (of_property_read_bool(dev->of_node, "rtl821x,clkout_en")) + priv->quirks |= RTL821X_CLKOUT_EN_FEATURE; + + phydev->priv = priv; + + return 0; +} + static int rtl8201_ack_interrupt(struct phy_device *phydev) { int err; @@ -171,42 +200,58 @@ static int rtl8211c_config_init(struct phy_device *phydev) static int rtl8211f_config_init(struct phy_device *phydev) { - struct device *dev = &phydev->mdio.dev; - u16 val; + u16 txdly = 0; + u16 rxdly = 0; int ret; + struct rtl821x_priv *priv = phydev->priv; /* enable TX-delay for rgmii-{id,txid}, and disable it for rgmii and * rgmii-rxid. The RX-delay can be enabled by the external RXDLY pin. */ switch (phydev->interface) { - case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + rxdly = RTL8211F_RX_DELAY; + txdly = RTL8211F_TX_DELAY; + break; case PHY_INTERFACE_MODE_RGMII_RXID: - val = 0; + rxdly = RTL8211F_RX_DELAY; break; - case PHY_INTERFACE_MODE_RGMII_ID: case PHY_INTERFACE_MODE_RGMII_TXID: - val = RTL8211F_TX_DELAY; + txdly = RTL8211F_TX_DELAY; break; default: /* the rest of the modes imply leaving delay as is. */ return 0; } - ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, - val); + ret = phy_modify_paged(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, txdly); if (ret < 0) { - dev_err(dev, "Failed to update the TX delay register\n"); + dev_err(&phydev->mdio.dev, "tx delay set failed\n"); return ret; - } else if (ret) { - dev_dbg(dev, - "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n", - val ? "Enabling" : "Disabling"); + } + + ret = phy_modify_paged(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY, rxdly); + if (ret < 0) { + dev_err(&phydev->mdio.dev, "rx delay set failed\n"); + return ret; + } + + if (priv->quirks & RTL821X_CLKOUT_EN_FEATURE) { + ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2, + RTL8211F_CLKOUT_EN, RTL8211F_CLKOUT_EN); + if (ret < 0) { + dev_err(&phydev->mdio.dev, "clkout enable failed\n"); + return ret; + } } else { - dev_dbg(dev, - "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", - val ? "enabled" : "disabled"); + ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2, + RTL8211F_CLKOUT_EN, 0); + if (ret < 0) { + dev_err(&phydev->mdio.dev, "clkout disable failed\n"); + return ret; + } } - return 0; + return ret; } static int rtl8211e_config_init(struct phy_device *phydev) @@ -514,6 +559,7 @@ static struct phy_driver realtek_drvs[] = { }, { PHY_ID_MATCH_EXACT(0x001cc916), .name = "RTL8211F Gigabit Ethernet", + .probe = rtl821x_probe, .config_init = &rtl8211f_config_init, .ack_interrupt = &rtl8211f_ack_interrupt, .config_intr = &rtl8211f_config_intr, diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c index 53c214a22b95..17a6a0d28d5d 100644 --- a/drivers/net/phy/swphy.c +++ b/drivers/net/phy/swphy.c @@ -71,6 +71,7 @@ static const struct swmii_regs duplex[] = { static int swphy_decode_speed(int speed) { switch (speed) { + case 10000: case 1000: return SWMII_SPEED_1000; case 100: |