summaryrefslogtreecommitdiff
path: root/drivers/net/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy')
-rw-r--r--drivers/net/phy/Kconfig15
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/aquantia_main.c128
-rw-r--r--drivers/net/phy/at803x.c86
-rw-r--r--drivers/net/phy/inphi.c594
-rw-r--r--drivers/net/phy/mdio_bus.c8
-rw-r--r--drivers/net/phy/mscc.c10
-rw-r--r--drivers/net/phy/nxp-tja11xx.c173
-rw-r--r--drivers/net/phy/phy-core.c4
-rw-r--r--drivers/net/phy/phy_device.c25
-rw-r--r--drivers/net/phy/phylink.c14
-rw-r--r--drivers/net/phy/realtek.c80
-rw-r--r--drivers/net/phy/swphy.c1
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: