[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250512191901.73823-1-stefano.radaelli21@gmail.com>
Date: Mon, 12 May 2025 21:18:56 +0200
From: Stefano Radaelli <stefano.radaelli21@...il.com>
To: netdev@...r.kernel.org,
linux-kernel@...r.kernel.org
Cc: stefano.radaelli21@...il.com,
Andrew Lunn <andrew@...n.ch>,
Heiner Kallweit <hkallweit1@...il.com>,
Russell King <linux@...linux.org.uk>,
"David S. Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>,
Jakub Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>,
Xu Liang <lxu@...linear.com>
Subject: [PATCH] net: phy: mxl-8611: add support for MaxLinear MxL86110/MxL86111 PHY
The MaxLinear MxL86110 is a low power Ethernet PHY transceiver IC
compliant with the IEEE 802.3 Ethernet standard. It offers a
cost-optimized solution suitable for routers, switches, and home
gateways, supporting 1000, 100, and 10 Mbit/s data rates over
CAT5e or higher twisted pair copper cable.
The driver for this PHY is based on initial code provided by MaxLinear
(Linux Driver V1.0.0).
Supported features:
----------------------------------------+----------+----------+
Feature | MxL86110 | MxL86111 |
----------------------------------------+----------+----------+
General Device Initialization | x | x |
Wake on LAN (WoL) | x | x |
LED Configuration | x | x |
RGMII Interface Configuration | x | x |
SyncE Clock Output Config | x | x |
Dual Media Mode (Fiber/Copper) | - | x |
----------------------------------------+----------+----------+
This driver was tested on a Variscite i.MX93-VAR-SOM.
Signed-off-by: Stefano Radaelli <stefano.radaelli21@...il.com>
---
MAINTAINERS | 1 +
drivers/net/phy/Kconfig | 11 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/mxl-8611x.c | 2040 +++++++++++++++++++++++++++++++++++
4 files changed, 2053 insertions(+)
create mode 100644 drivers/net/phy/mxl-8611x.c
diff --git a/MAINTAINERS b/MAINTAINERS
index f21f1dabb5fe..59bc74cd127c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14661,6 +14661,7 @@ MAXLINEAR ETHERNET PHY DRIVER
M: Xu Liang <lxu@...linear.com>
L: netdev@...r.kernel.org
S: Supported
+F: drivers/net/phy/mxl-8611x.c
F: drivers/net/phy/mxl-gpy.c
MCAN MMIO DEVICE DRIVER
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index d29f9f7fd2e1..f137b1a0e139 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -258,6 +258,17 @@ config MARVELL_88X2222_PHY
Support for the Marvell 88X2222 Dual-port Multi-speed Ethernet
Transceiver.
+config MAXLINEAR_8611X_PHY
+ tristate "MaxLinear MXL86110 and MXL86111 PHYs"
+ help
+ Support for the MaxLinear MXL86110 and MXL86111 Gigabit Ethernet
+ Physical Layer transceivers.
+ These PHYs are commonly found in networking equipment like
+ routers, switches, and embedded systems, providing the
+ physical interface for 10/100/1000 Mbps Ethernet connections
+ over copper media. If you are using a board with one of these
+ PHYs connected to your Ethernet MAC, you should enable this option.
+
config MAXLINEAR_GPHY
tristate "Maxlinear Ethernet PHYs"
select POLYNOMIAL if HWMON
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 23ce205ae91d..3899be6fb8f5 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_MARVELL_10G_PHY) += marvell10g.o
obj-$(CONFIG_MARVELL_PHY) += marvell.o
obj-$(CONFIG_MARVELL_88Q2XXX_PHY) += marvell-88q2xxx.o
obj-$(CONFIG_MARVELL_88X2222_PHY) += marvell-88x2222.o
+obj-$(CONFIG_MAXLINEAR_8611X_PHY) += mxl-8611x.o
obj-$(CONFIG_MAXLINEAR_GPHY) += mxl-gpy.o
obj-y += mediatek/
obj-$(CONFIG_MESON_GXL_PHY) += meson-gxl.o
diff --git a/drivers/net/phy/mxl-8611x.c b/drivers/net/phy/mxl-8611x.c
new file mode 100644
index 000000000000..686be29d4214
--- /dev/null
+++ b/drivers/net/phy/mxl-8611x.c
@@ -0,0 +1,2040 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PHY driver for MXL86110 and MXL86111
+ *
+ * V1.0.0
+ *
+ * Copyright 2023 MaxLinear Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+#include <linux/bitfield.h>
+
+#define MXL8611x_DRIVER_DESC "MXL86111 and MXL86110 PHY driver"
+#define MXL8611x_DRIVER_VER "1.0.0"
+
+/* PHY IDs */
+#define PHY_ID_MXL86110 0xC1335580
+#define PHY_ID_MXL86111 0xC1335588
+
+/* required to access extended registers */
+#define MXL8611X_EXTD_REG_ADDR_OFFSET 0x1E
+#define MXL8611X_EXTD_REG_ADDR_DATA 0x1F
+#define PHY_IRQ_ENABLE_REG 0x12
+#define PHY_IRQ_ENABLE_REG_WOL BIT(6)
+
+/* only 1 page for MXL86110 */
+#define MXL86110_DEFAULT_PAGE 0
+
+/* different pages for EXTD access for MXL86111*/
+/* SerDes/PHY Control Access Register - COM_EXT_SMI_SDS_PHY */
+#define MXL86111_EXT_SMI_SDS_PHY_REG 0xA000
+#define MXL86111_EXT_SMI_SDS_PHYSPACE_MASK BIT(1)
+#define MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE (0x1 << 1)
+#define MXL86111_EXT_SMI_SDS_PHYUTP_SPACE (0x0 << 1)
+#define MXL86111_EXT_SMI_SDS_PHY_AUTO (0xFF)
+
+/* SyncE Configuration Register - COM_EXT SYNCE_CFG */
+#define MXL8611X_EXT_SYNCE_CFG_REG 0xA012
+#define MXL8611X_EXT_SYNCE_CFG_CLK_FRE_SEL BIT(4)
+#define MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E_DURING_LNKDN BIT(5)
+#define MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E BIT(6)
+#define MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK GENMASK(3, 1)
+#define MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_125M_PLL 0
+#define MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_25M 4
+
+/* WOL registers */
+#define MXL86110_WOL_MAC_ADDR_HIGH_EXTD_REG 0xA007 /* high-> FF:FF */
+#define MXL86110_WOL_MAC_ADDR_MIDDLE_EXTD_REG 0xA008 /* middle-> :FF:FF <-middle */
+#define MXL86110_WOL_MAC_ADDR_LOW_EXTD_REG 0xA009 /* :FF:FF <-low */
+
+#define MXL8611X_EXT_WOL_CFG_REG 0xA00A
+#define MXL8611X_EXT_WOL_CFG_WOLE_MASK BIT(3)
+#define MXL8611X_EXT_WOL_CFG_WOLE_DISABLE 0
+#define MXL8611X_EXT_WOL_CFG_WOLE_ENABLE BIT(3)
+
+/* RGMII register */
+#define MXL8611X_EXT_RGMII_CFG1_REG 0xA003
+/* delay can be adjusted in steps of about 150ps */
+#define MXL8611X_EXT_RGMII_CFG1_NO_DELAY 0
+#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MAX (0xF << 10)
+#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MIN (0x1 << 10)
+#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT (0xF << 10)
+
+#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK GENMASK(13, 10)
+#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MAX (0xF << 0)
+#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MIN (0x1 << 0)
+#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK GENMASK(3, 0)
+#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_DEFAULT (0x1 << 0)
+
+#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MAX (0xF << 4)
+#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MIN (0x1 << 4)
+#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DEFAULT (0xF << 4)
+#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK GENMASK(7, 4)
+
+#define MXL8611X_EXT_RGMII_CFG1_FULL_MASK \
+ ((MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK) | \
+ (MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK) | \
+ (MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK))
+
+/* EXT Sleep Control register */
+#define MXL8611x_UTP_EXT_SLEEP_CTRL_REG 0x27
+#define MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_OFF 0
+#define MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_MASK BIT(15)
+
+/* RGMII In-Band Status and MDIO Configuration Register */
+#define MXL8611X_EXT_RGMII_MDIO_CFG 0xA005
+#define MXL8611X_EXT_RGMII_MDIO_CFG_EPA0_MASK GENMASK(6, 6)
+#define MXL8611X_EXT_RGMII_MDIO_CFG_EBA_MASK GENMASK(5, 5)
+#define MXL8611X_EXT_RGMII_MDIO_CFG_BA_MASK GENMASK(4, 0)
+
+/* LED registers and defines */
+#define MXL8611X_LED0_CFG_REG 0xA00C
+#define MXL8611X_LED1_CFG_REG 0xA00D
+#define MXL8611X_LED2_CFG_REG 0xA00E
+
+#define MXL8611X_LEDX_CFG_TRAFFIC_ACT_BLINK_IND BIT(13)
+#define MXL8611X_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON BIT(12)
+#define MXL8611X_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON BIT(11)
+#define MXL8611X_LEDX_CFG_LINK_UP_TX_ACT_ON BIT(10) /* LED 0,1,2 default */
+#define MXL8611X_LEDX_CFG_LINK_UP_RX_ACT_ON BIT(9) /* LED 0,1,2 default */
+#define MXL8611X_LEDX_CFG_LINK_UP_TX_ON BIT(8)
+#define MXL8611X_LEDX_CFG_LINK_UP_RX_ON BIT(7)
+#define MXL8611X_LEDX_CFG_LINK_UP_1GB_ON BIT(6) /* LED 2 default */
+#define MXL8611X_LEDX_CFG_LINK_UP_100MB_ON BIT(5) /* LED 1 default */
+#define MXL8611X_LEDX_CFG_LINK_UP_10MB_ON BIT(4) /* LED 0 default */
+#define MXL8611X_LEDX_CFG_LINK_UP_COLLISION BIT(3)
+#define MXL8611X_LEDX_CFG_LINK_UP_1GB_BLINK BIT(2)
+#define MXL8611X_LEDX_CFG_LINK_UP_100MB_BLINK BIT(1)
+#define MXL8611X_LEDX_CFG_LINK_UP_10MB_BLINK BIT(0)
+
+#define MXL8611X_LED_BLINK_CFG_REG 0xA00F
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_2HZ 0
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_4HZ BIT(0)
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_8HZ BIT(1)
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_16HZ (BIT(1) | BIT(0))
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_2HZ 0
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_4HZ BIT(2)
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_8HZ BIT(3)
+#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_16HZ (BIT(3) | BIT(2))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_50_PERC_ON 0
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_67_PERC_ON (BIT(4))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_75_PERC_ON (BIT(5))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_83_PERC_ON (BIT(5) | BIT(4))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_50_PERC_OFF (BIT(6))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_33_PERC_ON (BIT(6) | BIT(4))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_25_PERC_ON (BIT(6) | BIT(5))
+#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_17_PERC_ON (BIT(6) | BIT(5) | BIT(4))
+
+/* Specific Status Register - PHY_STAT */
+#define MXL86111_PHY_STAT_REG 0x11
+#define MXL86111_PHY_STAT_SPEED_MASK GENMASK(15, 14)
+#define MXL86111_PHY_STAT_SPEED_OFFSET 14
+#define MXL86111_PHY_STAT_SPEED_10M 0x0
+#define MXL86111_PHY_STAT_SPEED_100M 0x1
+#define MXL86111_PHY_STAT_SPEED_1000M 0x2
+#define MXL86111_PHY_STAT_DPX_OFFSET 13
+#define MXL86111_PHY_STAT_DPX BIT(13)
+#define MXL86111_PHY_STAT_LSRT BIT(10)
+
+/* 3 phy reg page modes,auto mode combines utp and fiber mode*/
+#define MXL86111_MODE_FIBER 0x1
+#define MXL86111_MODE_UTP 0x2
+#define MXL86111_MODE_AUTO 0x3
+
+/* FIBER Auto-Negotiation link partner ability - SDS_AN_LPA */
+#define MXL86111_SDS_AN_LPA_PAUSE (0x3 << 7)
+#define MXL86111_SDS_AN_LPA_ASYM_PAUSE (0x2 << 7)
+
+/* Chip Configuration Register - COM_EXT_CHIP_CFG */
+#define MXL86111_EXT_CHIP_CFG_REG 0xA001
+#define MXL86111_EXT_CHIP_CFG_RXDLY_ENABLE BIT(8)
+#define MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE BIT(15)
+
+#define MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK GENMASK(2, 0)
+#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII 0
+#define MXL86111_EXT_CHIP_CFG_MODE_FIBER_TO_RGMII 1
+#define MXL86111_EXT_CHIP_CFG_MODE_UTP_FIBER_TO_RGMII 2
+#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_SGMII 3
+#define MXL86111_EXT_CHIP_CFG_MODE_SGPHY_TO_RGMAC 4
+#define MXL86111_EXT_CHIP_CFG_MODE_SGMAC_TO_RGPHY 5
+#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_AUTO 6
+#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_FORCE 7
+
+/* Miscellaneous Control Register - COM_EXT _MISC_CFG */
+#define MXL86111_EXT_MISC_CONFIG_REG 0xA006
+#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL BIT(0)
+#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_1000BX (0x1 << 0)
+#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_100BX (0x0 << 0)
+
+/* Phy fiber Link timer cfg2 Register - EXT_SDS_LINK_TIMER_CFG2 */
+#define MXL86111_EXT_SDS_LINK_TIMER_CFG2_REG 0xA5
+#define MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN BIT(15)
+
+/* default values of PHY register, required for Dual Media mode */
+#define MII_BMSR_DEFAULT_VAL 0x7949
+#define MII_ESTATUS_DEFAULT_VAL 0x2000
+
+/* Timeout in ms for PHY SW reset check in STD_CTRL/SDS_CTRL */
+#define BMCR_RESET_TIMEOUT 500
+
+/* ******************************************************** */
+/* Customer specific configuration START */
+/* Adapt here if other than default values are required! */
+/* ******************************************************** */
+
+/* disable auto sleep feature */
+#define MXL8611x_UTP_DISABLE_AUTO_SLEEP_FEATURE_CUSTOM 0
+
+/* SYNCE/clkout feature */
+#define MXL8611X_CLOCK_DISABLE 0
+#define MXL8611X_CLOCK_FREQ_25M 1
+#define MXL8611X_CLOCK_FREQ_125M 2
+#define MXL8611X_CLOCK_DEFAULT 3
+
+#define MXL8611X_EXT_SYNCE_CFG_CLOCK_FREQ_CUSTOM MXL8611X_CLOCK_DEFAULT
+
+/* Adjust RGMII timing based on min/max range defined above */
+#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_CUSTOM \
+ MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT
+#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_CUSTOM \
+ MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_DEFAULT
+#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_CUSTOM \
+ MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DEFAULT
+
+/* ******************************************************** */
+/* Customer specific configuration END */
+/* ******************************************************** */
+
+/**
+ * mxlphy_write_extended_reg() - write to a PHY's extended register
+ * @phydev: pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * NOTE:The calling function must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative error code
+ */
+static int mxlphy_write_extended_reg(struct phy_device *phydev, u16 regnum, u16 val)
+{
+ int ret;
+
+ ret = __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum);
+ if (ret < 0)
+ return ret;
+
+ return __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_DATA, val);
+}
+
+/**
+ * mxlphy_locked_write_extended_reg() - write to a PHY's extended register
+ * @phydev: pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * returns 0 or negative error code
+ */
+static int mxlphy_locked_write_extended_reg(struct phy_device *phydev, u16 regnum,
+ u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = mxlphy_write_extended_reg(phydev, regnum, val);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * mxlphy_read_extended_reg() - write to a PHY's extended register
+ * @phydev: pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * NOTE:The calling function must have taken the MDIO bus lock.
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int mxlphy_read_extended_reg(struct phy_device *phydev, u16 regnum)
+{
+ int ret;
+
+ ret = __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum);
+ if (ret < 0)
+ return ret;
+ return __phy_read(phydev, MXL8611X_EXTD_REG_ADDR_DATA);
+}
+
+/**
+ * mxlphy_read_extended_reg() - write to a PHY's extended register
+ * @phydev: pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int mxlphy_locked_read_extended_reg(struct phy_device *phydev, u16 regnum)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = mxlphy_read_extended_reg(phydev, regnum);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * mxlphy_modify_extended_reg() - modify bits of a PHY's extended register
+ * @phydev: pointer to the phy_device
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: register value = (old register value & ~mask) | set.
+ * The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative error code
+ */
+static int mxlphy_modify_extended_reg(struct phy_device *phydev, u16 regnum, u16 mask,
+ u16 set)
+{
+ int ret;
+
+ ret = __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum);
+ if (ret < 0)
+ return ret;
+
+ return __phy_modify(phydev, MXL8611X_EXTD_REG_ADDR_DATA, mask, set);
+}
+
+/**
+ * mxlphy_locked_modify_extended_reg() - modify bits of a PHY's extended register
+ * @phydev: pointer to the phy_device
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: register value = (old register value & ~mask) | set.
+ *
+ * returns 0 or negative error code
+ */
+static int mxlphy_locked_modify_extended_reg(struct phy_device *phydev, u16 regnum,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = mxlphy_modify_extended_reg(phydev, regnum, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * mxlphy_get_wol() - report if wake-on-lan is enabled
+ * @phydev: pointer to the phy_device
+ * @wol: a pointer to a &struct ethtool_wolinfo
+ */
+static void mxlphy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ int value;
+
+ wol->supported = WAKE_MAGIC;
+ wol->wolopts = 0;
+ value = mxlphy_locked_read_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG);
+ if (value >= 0 && (value & MXL8611X_EXT_WOL_CFG_WOLE_MASK))
+ wol->wolopts |= WAKE_MAGIC;
+}
+
+/**
+ * mxlphy_set_wol() - enable/disable wake-on-lan
+ * @phydev: pointer to the phy_device
+ * @wol: a pointer to a &struct ethtool_wolinfo
+ *
+ * Configures the WOL Magic Packet MAC
+ * returns 0 or negative errno code
+ */
+static int mxlphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ struct net_device *netdev;
+ int page_to_restore;
+ const u8 *mac;
+ int ret = 0;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ netdev = phydev->attached_dev;
+ if (!netdev)
+ return -ENODEV;
+
+ mac = (const u8 *)netdev->dev_addr;
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
+
+ page_to_restore = phy_select_page(phydev, MXL86110_DEFAULT_PAGE);
+ if (page_to_restore < 0)
+ goto error;
+
+ /* Configure the MAC address of the WOL magic packet */
+ ret = mxlphy_write_extended_reg(phydev, MXL86110_WOL_MAC_ADDR_HIGH_EXTD_REG,
+ ((mac[0] << 8) | mac[1]));
+ if (ret < 0)
+ goto error;
+ ret = mxlphy_write_extended_reg(phydev, MXL86110_WOL_MAC_ADDR_MIDDLE_EXTD_REG,
+ ((mac[2] << 8) | mac[3]));
+ if (ret < 0)
+ goto error;
+ ret = mxlphy_write_extended_reg(phydev, MXL86110_WOL_MAC_ADDR_LOW_EXTD_REG,
+ ((mac[4] << 8) | mac[5]));
+ if (ret < 0)
+ goto error;
+
+ ret = mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG,
+ MXL8611X_EXT_WOL_CFG_WOLE_MASK,
+ MXL8611X_EXT_WOL_CFG_WOLE_ENABLE);
+ if (ret < 0)
+ goto error;
+
+ ret = __phy_modify(phydev, PHY_IRQ_ENABLE_REG, 0,
+ PHY_IRQ_ENABLE_REG_WOL);
+ if (ret < 0)
+ goto error;
+
+ phydev_info(phydev, "%s, WOL Magic packet MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
+ __func__, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+
+ } else {
+ page_to_restore = phy_select_page(phydev, MXL86110_DEFAULT_PAGE);
+ if (page_to_restore < 0)
+ goto error;
+
+ ret = mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG,
+ MXL8611X_EXT_WOL_CFG_WOLE_MASK,
+ MXL8611X_EXT_WOL_CFG_WOLE_DISABLE);
+
+ ret = __phy_modify(phydev, PHY_IRQ_ENABLE_REG,
+ PHY_IRQ_ENABLE_REG_WOL, 0);
+ if (ret < 0)
+ goto error;
+ }
+
+error:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxl86110_read_page() - read reg page
+ * @phydev: pointer to the phy_device
+ *
+ * returns current reg space of MxL86110
+ * (only MXL86111_EXT_SMI_SDS_PHYUTP_SPACE supported) or negative errno code
+ */
+static int mxl86110_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET);
+};
+
+/**
+ * mxl86110_write_page() - write reg page
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to write
+ *
+ * returns current reg space of MxL86110
+ * (only MXL86111_EXT_SMI_SDS_PHYUTP_SPACE supported) or negative errno code
+ */
+static int mxl86110_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, page);
+};
+
+/**
+ * mxl8611x_led_cfg() - applies LED configuration from device tree
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl8611x_led_cfg(struct phy_device *phydev)
+{
+ int ret = 0;
+ int i;
+ char propname[25];
+ struct device_node *node = phydev->mdio.dev.of_node;
+ u32 val;
+
+ /* Loop through three the LED registers */
+ for (i = 0; i < 3; i++) {
+ /* Read property from device tree */
+ snprintf(propname, 25, "mxl-8611x,led%d_cfg", i);
+ if (of_property_read_u32(node, propname, &val))
+ continue;
+
+ /* Update PHY LED register */
+ ret = mxlphy_write_extended_reg(phydev, MXL8611X_LED0_CFG_REG + i, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mxl8611x_broadcast_cfg() - applies broadcast configuration
+ * @phydev: pointer to the phy_device
+ *
+ * configures the broadcast setting for the PHY based on the device tree
+ * if the "mxl-8611x,broadcast-enabled" property is present the PHY broadcasts
+ * address 0 on the MDIO bus. This feature enables PHY to always respond to MDIO access
+ * returns 0 or negative errno code
+ */
+static int mxl8611x_broadcast_cfg(struct phy_device *phydev)
+{
+ int ret = 0;
+ struct device_node *node;
+ u32 val;
+
+ if (!phydev) {
+ pr_err("%s, Invalid phy_device pointer\n", __func__);
+ return -EINVAL;
+ }
+
+ node = phydev->mdio.dev.of_node;
+ if (!node) {
+ phydev_err(phydev, "%s, Invalid device tree node\n", __func__);
+ return -EINVAL;
+ }
+
+ val = mxlphy_read_extended_reg(phydev, MXL8611X_EXT_RGMII_MDIO_CFG);
+
+ if (of_property_read_bool(node, "mxl-8611x,broadcast-enabled"))
+ val |= MXL8611X_EXT_RGMII_MDIO_CFG_EPA0_MASK;
+ else
+ val &= ~MXL8611X_EXT_RGMII_MDIO_CFG_EPA0_MASK;
+
+ ret = mxlphy_write_extended_reg(phydev, MXL8611X_EXT_RGMII_MDIO_CFG, val);
+ if (ret) {
+ phydev_err(phydev, "%s, failed to write 0x%x to RGMII MDIO CFG register (0x%x): ret = %d\n",
+ __func__, val, MXL8611X_EXT_RGMII_MDIO_CFG, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mxl8611x_synce_clk_cfg() - applies syncE/clk output configuration
+ * @phydev: pointer to the phy_device
+ *
+ * Custom settings can be defined in custom config section of the driver
+ * returns 0 or negative errno code
+ */
+static int mxl8611x_synce_clk_cfg(struct phy_device *phydev)
+{
+ u16 mask = 0, value = 0;
+ int ret;
+
+ switch (MXL8611X_EXT_SYNCE_CFG_CLOCK_FREQ_CUSTOM) {
+ case MXL8611X_CLOCK_DISABLE:
+ mask = MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E;
+ value = 0;
+ break;
+ case MXL8611X_CLOCK_FREQ_25M:
+ value = MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E |
+ FIELD_PREP(MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK,
+ MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_25M);
+ mask = MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E |
+ MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK |
+ MXL8611X_EXT_SYNCE_CFG_CLK_FRE_SEL;
+ break;
+ case MXL8611X_CLOCK_FREQ_125M:
+ value = MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E |
+ MXL8611X_EXT_SYNCE_CFG_CLK_FRE_SEL |
+ FIELD_PREP(MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK,
+ MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_125M_PLL);
+ mask = MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E |
+ MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK |
+ MXL8611X_EXT_SYNCE_CFG_CLK_FRE_SEL;
+ break;
+ case MXL8611X_CLOCK_DEFAULT:
+ phydev_info(phydev, "%s, default clock cfg\n", __func__);
+ return 0;
+ default:
+ phydev_info(phydev, "%s, invalid clock cfg: %d\n", __func__,
+ MXL8611X_EXT_SYNCE_CFG_CLOCK_FREQ_CUSTOM);
+ return -EINVAL;
+ }
+
+ phydev_info(phydev, "%s, clock cfg mask:%d, value: %d\n", __func__, mask, value);
+
+ /* Write clock output configuration */
+ ret = mxlphy_locked_modify_extended_reg(phydev, MXL8611X_EXT_SYNCE_CFG_REG,
+ mask, value);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * mxl86110_config_init() - initialize the PHY
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86110_config_init(struct phy_device *phydev)
+{
+ int page_to_restore, ret = 0;
+ unsigned int val = 0;
+ bool disable_rxdly = false;
+
+ page_to_restore = phy_select_page(phydev, MXL86110_DEFAULT_PAGE);
+ if (page_to_restore < 0)
+ goto error;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ /* no delay, will write 0 */
+ val = MXL8611X_EXT_RGMII_CFG1_NO_DELAY;
+ disable_rxdly = true;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ val = MXL8611X_EXT_RGMII_CFG1_RX_DELAY_CUSTOM;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val = MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_CUSTOM |
+ MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_CUSTOM;
+ disable_rxdly = true;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ val = MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_CUSTOM |
+ MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_CUSTOM;
+ val |= MXL8611X_EXT_RGMII_CFG1_RX_DELAY_CUSTOM;
+ break;
+ default:
+ ret = -EINVAL;
+ goto error;
+ }
+ ret = mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_RGMII_CFG1_REG,
+ MXL8611X_EXT_RGMII_CFG1_FULL_MASK, val);
+ if (ret < 0)
+ goto error;
+
+ if (ret < 0)
+ goto error;
+
+ if (MXL8611x_UTP_DISABLE_AUTO_SLEEP_FEATURE_CUSTOM == 1) {
+ /* disable auto sleep */
+ ret = mxlphy_modify_extended_reg(phydev, MXL8611x_UTP_EXT_SLEEP_CTRL_REG,
+ MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_MASK,
+ MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_OFF);
+ if (ret < 0)
+ goto error;
+ }
+
+ /* Disable RXDLY (RGMII Rx Clock Delay) */
+ if (disable_rxdly) {
+ ret = mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG,
+ MXL86111_EXT_CHIP_CFG_RXDLY_ENABLE, 0);
+ if (ret < 0)
+ goto error;
+ }
+
+ ret = mxl8611x_led_cfg(phydev);
+ if (ret < 0)
+ goto error;
+
+ ret = mxl8611x_broadcast_cfg(phydev);
+ if (ret < 0)
+ goto error;
+
+error:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+struct mxl86111_priv {
+ /* dual_media_advertising used for Dual Media mode (MXL86111_EXT_SMI_SDS_PHY_AUTO) */
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(dual_media_advertising);
+
+ /* MXL86111_MODE_FIBER / MXL86111_MODE_UTP / MXL86111_MODE_AUTO*/
+ u8 reg_page_mode;
+ u8 strap_mode; /* 8 working modes */
+ /* current reg page of mxl86111 phy:
+ * MXL86111_EXT_SMI_SDS_PHYUTP_SPACE
+ * MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE
+ * MXL86111_EXT_SMI_SDS_PHY_AUTO
+ */
+ u8 reg_page;
+};
+
+/**
+ * mxl86111_read_page() - read reg page
+ * @phydev: pointer to the phy_device
+ *
+ * returns current reg space of mxl86111 (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/
+ * MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) or negative errno code
+ */
+static int mxl86111_read_page(struct phy_device *phydev)
+{
+ int old_page;
+
+ old_page = mxlphy_read_extended_reg(phydev, MXL86111_EXT_SMI_SDS_PHY_REG);
+ if (old_page < 0)
+ return old_page;
+
+ if ((old_page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK) == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE)
+ return MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE;
+
+ return MXL86111_EXT_SMI_SDS_PHYUTP_SPACE;
+};
+
+/**
+ * mxl86111_locked_read_page() - read reg page
+ * @phydev: pointer to the phy_device
+ *
+ * returns current reg space of mxl86111 (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/
+ * MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) or negative errno code
+ */
+static int mxl86111_locked_read_page(struct phy_device *phydev)
+{
+ int old_page;
+
+ phy_lock_mdio_bus(phydev);
+ old_page = mxl86111_read_page(phydev);
+ phy_unlock_mdio_bus(phydev);
+
+ return old_page;
+};
+
+/**
+ * mxl86111_write_page() - Set reg page
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to set
+ * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_write_page(struct phy_device *phydev, int page)
+{
+ int mask = MXL86111_EXT_SMI_SDS_PHYSPACE_MASK;
+ int set;
+
+ if ((page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK) == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE)
+ set = MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE;
+ else
+ set = MXL86111_EXT_SMI_SDS_PHYUTP_SPACE;
+
+ return mxlphy_modify_extended_reg(phydev, MXL86111_EXT_SMI_SDS_PHY_REG, mask, set);
+};
+
+/**
+ * mxl86111_modify_bmcr_paged - modify bits of the PHY's BMCR register of a given page
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to operate
+ * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: new register value = (old register value & ~mask) | set.
+ * MxL86111 has 2 register spaces (utp/fiber) and 3 modes (utp/fiber/auto).
+ * Each space has its MII_BMCR.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_modify_bmcr_paged(struct phy_device *phydev, int page,
+ u16 mask, u16 set)
+{
+ int bmcr_timeout = BMCR_RESET_TIMEOUT;
+ int page_to_restore;
+ int ret = 0;
+
+ page_to_restore = phy_select_page(phydev, page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK);
+ if (page_to_restore < 0)
+ goto err_restore_page;
+
+ ret = __phy_modify(phydev, MII_BMCR, mask, set);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* In case of BMCR_RESET, check until reset bit is cleared */
+ if (set == BMCR_RESET) {
+ while (bmcr_timeout--) {
+ usleep_range(1000, 1050);
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ if (!(ret & BMCR_RESET))
+ return phy_restore_page(phydev, page_to_restore, 0);
+ }
+ phydev_warn(phydev, "%s, BMCR reset not completed until timeout", __func__);
+ ret = -EBUSY;
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxl86111_modify_bmcr - modify bits of a PHY's BMCR register
+ * @phydev: pointer to the phy_device
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: new register value = (old register value & ~mask) | set.
+ * MxL86111 has 2 register spaces (utp/fiber) and 3 mode (utp/fiber/poll)
+ * each space has its MII_BMCR. poll mode combines utp and fiber.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_modify_bmcr(struct phy_device *phydev, u16 mask, u16 set)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int ret = 0;
+
+ if (priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ ret = mxl86111_modify_bmcr_paged(phydev, priv->reg_page, mask, set);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = mxl86111_modify_bmcr_paged(phydev, ret, mask, set);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * mxl86111_set_fiber_features() - setup fiber mode features.
+ * @phydev: pointer to the phy_device
+ * @dst: a pointer to store fiber mode features
+ */
+static void mxl86111_set_fiber_features(struct phy_device *phydev,
+ unsigned long *dst)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, dst);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, dst);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, dst);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, dst);
+}
+
+/**
+ * mxlphy_utp_read_abilities - read PHY abilities from Clause 22 registers
+ * @phydev: pointer to the phy_device
+ *
+ * NOTE: Read the PHY abilities and set phydev->supported.
+ * The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxlphy_utp_read_abilities(struct phy_device *phydev)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int val, page;
+
+ linkmode_set_bit_array(phy_basic_ports_array,
+ ARRAY_SIZE(phy_basic_ports_array),
+ phydev->supported);
+
+ val = __phy_read(phydev, MII_BMSR);
+ if (val < 0)
+ return val;
+
+ /* In case of dual mode media, we might not have access to the utp page.
+ * Therefore we use the default register setting of the device to
+ * 'extract' supported modes.
+ */
+ page = mxl86111_read_page(phydev);
+ if (page < 0)
+ return page;
+
+ if (priv->reg_page == MXL86111_EXT_SMI_SDS_PHY_AUTO &&
+ page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE)
+ val = MII_BMSR_DEFAULT_VAL;
+
+ phydev_info(phydev, "%s, MII_BMSR: 0x%04X\n", __func__, val);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported,
+ val & BMSR_ANEGCAPABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported,
+ val & BMSR_10FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported,
+ val & BMSR_10HALF);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported,
+ val & BMSR_100FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported,
+ val & BMSR_100HALF);
+
+ if (val & BMSR_ESTATEN) {
+ val = __phy_read(phydev, MII_ESTATUS);
+ if (val < 0)
+ return val;
+
+ if (priv->reg_page == MXL86111_EXT_SMI_SDS_PHY_AUTO &&
+ page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE)
+ val = MII_ESTATUS_DEFAULT_VAL;
+
+ phydev_info(phydev, "%s, MII_ESTATUS: 0x%04X\n", __func__, val);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported, val & ESTATUS_1000_TFULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->supported, val & ESTATUS_1000_THALF);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->supported, val & ESTATUS_1000_XFULL);
+ }
+
+ return 0;
+}
+
+/**
+ * mxl86111_get_features_paged() - read supported link modes for a given page
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to operate
+ * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_get_features_paged(struct phy_device *phydev, int page)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int page_to_restore;
+ int ret = 0;
+
+ page &= MXL86111_EXT_SMI_SDS_PHYSPACE_MASK;
+ page_to_restore = phy_select_page(phydev, page);
+ if (page_to_restore < 0)
+ goto err_restore_page;
+
+ if (page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE &&
+ priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ linkmode_zero(phydev->supported);
+ mxl86111_set_fiber_features(phydev, phydev->supported);
+ } else {
+ ret = mxlphy_utp_read_abilities(phydev);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxl86111_get_features - select the reg space then call mxl86111_get_features_paged
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_get_features(struct phy_device *phydev)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ ret = mxl86111_get_features_paged(phydev, priv->reg_page);
+ } else {
+ /* read current page in Dual Media mode,
+ * since PHY HW is controlling the page select
+ */
+ ret = mxl86111_locked_read_page(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = mxl86111_get_features_paged(phydev, ret);
+ if (ret < 0)
+ return ret;
+
+ /* add fiber mode features to phydev->supported */
+ mxl86111_set_fiber_features(phydev, phydev->supported);
+ }
+ return ret;
+}
+
+/**
+ * mxl86110_probe() - read chip config then set suitable reg_page_mode
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86110_probe(struct phy_device *phydev)
+{
+ int ret;
+
+ /* configure syncE / clk output */
+ ret = mxl8611x_synce_clk_cfg(phydev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * mxl86111_probe() - read chip config then set reg_page_mode, port and page
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct mxl86111_priv *priv;
+ int chip_config;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ // TM: Debugging of pinstrap mode
+ ret = mxlphy_locked_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG,
+ MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK,
+ MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII);
+ if (ret < 0)
+ return ret;
+ // TM: Debugging of pinstrap mode
+ ret = mxlphy_locked_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG,
+ MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE, 0);
+ if (ret < 0)
+ return ret;
+
+ chip_config = mxlphy_locked_read_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG);
+ if (chip_config < 0)
+ return chip_config;
+
+ priv->strap_mode = chip_config & MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK;
+ switch (priv->strap_mode) {
+ case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_SGMII:
+ case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII:
+ phydev->port = PORT_TP;
+ priv->reg_page = MXL86111_EXT_SMI_SDS_PHYUTP_SPACE;
+ priv->reg_page_mode = MXL86111_MODE_UTP;
+ break;
+ case MXL86111_EXT_CHIP_CFG_MODE_FIBER_TO_RGMII:
+ case MXL86111_EXT_CHIP_CFG_MODE_SGPHY_TO_RGMAC:
+ case MXL86111_EXT_CHIP_CFG_MODE_SGMAC_TO_RGPHY:
+ phydev->port = PORT_FIBRE;
+ priv->reg_page = MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE;
+ priv->reg_page_mode = MXL86111_MODE_FIBER;
+ break;
+ case MXL86111_EXT_CHIP_CFG_MODE_UTP_FIBER_TO_RGMII:
+ case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_AUTO:
+ case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_FORCE:
+ phydev->port = PORT_NONE;
+ priv->reg_page = MXL86111_EXT_SMI_SDS_PHY_AUTO;
+ priv->reg_page_mode = MXL86111_MODE_AUTO;
+ break;
+ }
+ /* set default reg space when it is not Dual Media mode */
+ if (priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ ret = mxlphy_locked_write_extended_reg(phydev,
+ MXL86111_EXT_SMI_SDS_PHY_REG,
+ priv->reg_page);
+ if (ret < 0)
+ return ret;
+ }
+
+ phydev_info(phydev, "%s, pinstrap mode: %d\n", __func__, priv->strap_mode);
+
+ /* configure syncE / clk output - can be defined in custom config section */
+ ret = mxl8611x_synce_clk_cfg(phydev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * mxlphy_check_and_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: pointer to the phy_device
+ * @restart: bool if aneg restart is requested
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * identical to genphy_check_and_restart_aneg, but phy_read without mdio lock
+ *
+ * returns 0 or negative errno code
+ */
+static int mxlphy_check_and_restart_aneg(struct phy_device *phydev, bool restart)
+{
+ int ret;
+
+ if (!restart) {
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ return ret;
+
+ /* Advertisement did not change, but aneg maybe was never to begin with
+ * or phy was in isolation state
+ */
+ if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE))
+ restart = true;
+ }
+ /* Enable and restart Autonegotiation */
+ if (restart)
+ return __phy_modify(phydev, MII_BMCR, BMCR_ISOLATE,
+ BMCR_ANENABLE | BMCR_ANRESTART);
+
+ return 0;
+}
+
+/**
+ * mxlphy_config_advert - sanitize and advertise auto-negotiation parameters
+ * @phydev: pointer to the phy_device
+ *
+ * NOTE: Writes MII_ADVERTISE with the appropriate values,
+ * Returns < 0 on error, 0 if the PHY's advertisement hasn't changed,
+ * and > 0 if it has changed.
+ *
+ * identical to genphy_config_advert, but phy_read without mdio lock
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxlphy_config_advert(struct phy_device *phydev)
+{
+ int err, bmsr, changed = 0;
+ u32 adv;
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
+
+ /* Setup standard advertisement */
+ err = __phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_100BASE4 |
+ ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ changed = 1;
+
+ bmsr = __phy_read(phydev, MII_BMSR);
+ if (bmsr < 0)
+ return bmsr;
+
+ /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all
+ * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a
+ * logical 1.
+ */
+ if (!(bmsr & BMSR_ESTATEN))
+ return changed;
+
+ adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+
+ err = __phy_modify_changed(phydev, MII_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ changed = 1;
+
+ return changed;
+}
+
+/**
+ * mxlphy_setup_master_slave - set master/slave capabilities
+ * @phydev: pointer to the phy_device
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * identical to genphy_setup_master_slave, but phy_read without mdio lock
+ *
+ * returns 0 or negative errno code
+ */
+static int mxlphy_setup_master_slave(struct phy_device *phydev)
+{
+ u16 ctl = 0;
+
+ if (!phydev->is_gigabit_capable)
+
+ return 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ ctl |= CTL1000_PREFER_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ break;
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl |= CTL1000_AS_MASTER;
+ fallthrough;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ ctl |= CTL1000_ENABLE_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ return __phy_modify_changed(phydev, MII_CTRL1000,
+ (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER |
+ CTL1000_PREFER_MASTER), ctl);
+}
+
+/**
+ * mxlphy_config_aneg_utp - restart auto-negotiation or write BMCR
+ * @phydev: pointer to the phy_device
+ * @changed: bool if autoneg is requested
+ *
+ * If auto-negotiation is enabled, advertising will be configure,
+ * and then auto-negotiation will be restarted. Otherwise write the BMCR.
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxlphy_config_aneg_utp(struct phy_device *phydev, bool changed)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int err;
+ u16 ctl;
+
+ /* identical to genphy_setup_master_slave, but phy_read without mdio lock */
+ err = mxlphy_setup_master_slave(phydev);
+ if (err < 0)
+ return err;
+ else if (err)
+ changed = true;
+
+ if (phydev->autoneg != AUTONEG_ENABLE) {
+ /* configures/forces speed/duplex, empty rate selection would result in 10M */
+ ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
+ /* In dual media mode prevent 1000BASE-T half duplex
+ * due to risk of permanent blocking
+ */
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO) {
+ if (ctl & BMCR_SPEED1000) {
+ phydev_err(phydev,
+ "%s, Error: no supported fixed UTP rate configured\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+
+ return __phy_modify(phydev, MII_BMCR, ~(BMCR_LOOPBACK |
+ BMCR_ISOLATE | BMCR_PDOWN), ctl);
+ }
+
+ /* At least one valid rate must be advertised in Dual Media mode, since it could
+ * lead to permanent blocking of the UTP path otherwise. The PHY HW is controlling
+ * the page select autonomously in this mode.
+ * If mode switches to Fiber reg page when a link partner is connected, while UTP
+ * link is down, it can never switch back to UTP if the link cannot be established
+ * anymore due to invalid configuration. A HW reset would be required to re-enable
+ * UTP mode. Therefore prevent this situation and ignore invalid speed configuration.
+ * Returning a negative return code (e.g. EINVAL) results in stack trace dumps from
+ * some PAL functions.
+ */
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO) {
+ ctl = 0;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->advertising))
+ ctl |= ADVERTISE_10HALF;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->advertising))
+ ctl |= ADVERTISE_10FULL;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->advertising))
+ ctl |= ADVERTISE_100HALF;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->advertising))
+ ctl |= ADVERTISE_100FULL;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->advertising))
+ ctl |= ADVERTISE_1000FULL;
+
+ if (ctl == 0) {
+ phydev_err(phydev,
+ "%s, Error: no supported UTP rate configured - setting ignored\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+
+ /* identical to genphy_config_advert, but phy_read without mdio lock */
+ err = mxlphy_config_advert(phydev);
+ if (err < 0)
+ return err;
+ else if (err)
+ changed = true;
+
+ return mxlphy_check_and_restart_aneg(phydev, changed);
+}
+
+/**
+ * mxl86111_fiber_setup_forced - configures/forces speed for fiber mode
+ * @phydev: pointer to the phy_device
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_fiber_setup_forced(struct phy_device *phydev)
+{
+ u16 val;
+ int ret;
+
+ if (phydev->speed == SPEED_1000)
+ val = MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_1000BX;
+ else if (phydev->speed == SPEED_100)
+ val = MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_100BX;
+ else
+ return -EINVAL;
+
+ ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = mxlphy_modify_extended_reg(phydev, MXL86111_EXT_MISC_CONFIG_REG,
+ MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL, val);
+ if (ret < 0)
+ return ret;
+
+ ret = mxlphy_modify_extended_reg(phydev, MXL86111_EXT_SDS_LINK_TIMER_CFG2_REG,
+ MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN, 0);
+ if (ret < 0)
+ return ret;
+
+ return mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG,
+ MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE, 0);
+}
+
+/**
+ * mxl86111_fiber_config_aneg - restart auto-negotiation or write
+ * MXL86111_EXT_MISC_CONFIG_REG.
+ * @phydev: pointer to the phy_device
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_fiber_config_aneg(struct phy_device *phydev)
+{
+ int ret, bmcr;
+ u16 adv;
+ bool changed = false;
+
+ if (phydev->autoneg != AUTONEG_ENABLE)
+ return mxl86111_fiber_setup_forced(phydev);
+
+ /* enable fiber auto sensing */
+ ret = mxlphy_modify_extended_reg(phydev, MXL86111_EXT_SDS_LINK_TIMER_CFG2_REG,
+ 0, MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN);
+ if (ret < 0)
+ return ret;
+
+ ret = mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG,
+ MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE, 0);
+ if (ret < 0)
+ return ret;
+
+ bmcr = __phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ /* For fiber forced mode, power down/up to re-aneg */
+ if (!(bmcr & BMCR_ANENABLE)) {
+ __phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN);
+ usleep_range(1000, 1050);
+ __phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0);
+ }
+
+ adv = linkmode_adv_to_mii_adv_x(phydev->advertising,
+ ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
+
+ /* Setup fiber advertisement */
+ ret = __phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_1000XHALF | ADVERTISE_1000XFULL |
+ ADVERTISE_1000XPAUSE |
+ ADVERTISE_1000XPSE_ASYM,
+ adv);
+ if (ret < 0)
+ return ret;
+
+ if (ret > 0)
+ changed = true;
+
+ /* identical to genphy_check_and_restart_aneg, but phy_read without mdio lock */
+ return mxlphy_check_and_restart_aneg(phydev, changed);
+}
+
+/**
+ * mxl86111_config_aneg_paged() - configure advertising for a givenpage
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to operate
+ * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_config_aneg_paged(struct phy_device *phydev, int page)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(fiber_supported);
+ struct mxl86111_priv *priv = phydev->priv;
+ int page_to_restore;
+ int ret = 0;
+
+ page &= MXL86111_EXT_SMI_SDS_PHYSPACE_MASK;
+
+ page_to_restore = phy_select_page(phydev, page);
+ if (page_to_restore < 0)
+ goto err_restore_page;
+
+ /* In case of Dual Media mode (MXL86111_EXT_SMI_SDS_PHY_AUTO),
+ * update phydev->advertising.
+ */
+ if (priv->reg_page == MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ /* prepare the general fiber supported modes */
+ linkmode_zero(fiber_supported);
+ mxl86111_set_fiber_features(phydev, fiber_supported);
+
+ /* configure fiber_supported, then prepare advertising */
+ if (page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ fiber_supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ fiber_supported);
+ linkmode_and(phydev->advertising,
+ priv->dual_media_advertising, fiber_supported);
+ } else {
+ /* ETHTOOL_LINK_MODE_Autoneg_BIT is also used in UTP mode */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ fiber_supported);
+ linkmode_andnot(phydev->advertising,
+ priv->dual_media_advertising,
+ fiber_supported);
+ }
+ }
+
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO) {
+ /* read current page in Dual Media mode as late as possible,
+ * since PHY HW is controlling the page select
+ */
+ ret = mxl86111_read_page(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret != page)
+ phydev_warn(phydev,
+ "%s, Dual Media mode: page changed during configuration: " \
+ "page: %d, new: %d, reg_page: %d\n",
+ __func__, page, ret, priv->reg_page);
+ }
+
+ if (page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE)
+ ret = mxl86111_fiber_config_aneg(phydev);
+ else
+ ret = mxlphy_config_aneg_utp(phydev, false);
+
+err_restore_page:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxl86111_config_aneg() - set reg page and then call mxl86111_config_aneg_paged
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_config_aneg(struct phy_device *phydev)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ ret = mxl86111_config_aneg_paged(phydev, priv->reg_page);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* In Dual Media mode (MXL86111_EXT_SMI_SDS_PHY_AUTO)
+ * phydev->advertising need to be saved at first run, since it contains the
+ * advertising which supported by both mac and mxl86111(utp and fiber).
+ */
+ if (linkmode_empty(priv->dual_media_advertising)) {
+ linkmode_copy(priv->dual_media_advertising,
+ phydev->advertising);
+ }
+ /* read current page in Dual Media mode,
+ * since PHY HW is controlling the page select
+ */
+ ret = mxl86111_locked_read_page(phydev);
+ if (ret < 0)
+ return ret;
+ ret = mxl86111_config_aneg_paged(phydev, ret);
+ if (ret < 0)
+ return ret;
+
+ /* we don't known which mode will link, so restore
+ * phydev->advertising as default value.
+ */
+ linkmode_copy(phydev->advertising, priv->dual_media_advertising);
+ }
+ return 0;
+}
+
+/**
+ * mxl86111_aneg_done_paged() - determines the auto negotiation result of a given page.
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to operate
+ * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ *
+ * returns 0 (no link) or 1 (fiber or utp link) or negative errno code
+ */
+static int mxl86111_aneg_done_paged(struct phy_device *phydev, int page)
+{
+ int page_to_restore;
+ int ret = 0;
+ int link;
+
+ page_to_restore = phy_select_page(phydev, page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK);
+ if (page_to_restore < 0)
+ goto err_restore_page;
+
+ ret = __phy_read(phydev, MXL86111_PHY_STAT_REG);
+ if (ret < 0)
+ goto err_restore_page;
+
+ link = !!(ret & MXL86111_PHY_STAT_LSRT);
+ ret = link;
+
+err_restore_page:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxl86111_aneg_done() - determines the auto negotiation result
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 (no link) or 1 (fiber or utp link) or negative errno code
+ */
+static int mxl86111_aneg_done(struct phy_device *phydev)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+
+ int link;
+ int ret;
+
+ if (priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ link = mxl86111_aneg_done_paged(phydev, priv->reg_page);
+ } else {
+ /* read current page in Dual Media mode,
+ * since PHY HW is controlling the page select
+ */
+ ret = mxl86111_locked_read_page(phydev);
+ if (ret < 0)
+ return ret;
+ link = mxl86111_aneg_done_paged(phydev, ret);
+ }
+
+ return link;
+}
+
+/**
+ * mxl86111_config_init() - initialize the PHY
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_config_init(struct phy_device *phydev)
+{
+ int page_to_restore, ret = 0;
+ unsigned int val = 0;
+ bool disable_rxdly = false;
+
+ page_to_restore = phy_select_page(phydev, MXL86111_EXT_SMI_SDS_PHYUTP_SPACE);
+ if (page_to_restore < 0)
+ goto err_restore_page;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ /* no delay, will write 0 */
+ val = MXL8611X_EXT_RGMII_CFG1_NO_DELAY;
+ disable_rxdly = true;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ val = MXL8611X_EXT_RGMII_CFG1_RX_DELAY_CUSTOM;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val = MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_CUSTOM |
+ MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_CUSTOM;
+ disable_rxdly = true;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ val = MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_CUSTOM |
+ MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_CUSTOM;
+ val |= MXL8611X_EXT_RGMII_CFG1_RX_DELAY_CUSTOM;
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ goto err_restore_page;
+ }
+
+ /* configure rgmii delay mode */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII) {
+ ret = mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_RGMII_CFG1_REG,
+ MXL8611X_EXT_RGMII_CFG1_FULL_MASK, val);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* Configure RXDLY (RGMII Rx Clock Delay) */
+ if (disable_rxdly) {
+ ret = mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG,
+ MXL86111_EXT_CHIP_CFG_RXDLY_ENABLE, 0);
+
+ if (ret < 0)
+ goto err_restore_page;
+ }
+ }
+
+ if (MXL8611x_UTP_DISABLE_AUTO_SLEEP_FEATURE_CUSTOM == 1) {
+ /* disable auto sleep */
+ ret = mxlphy_modify_extended_reg(phydev, MXL8611x_UTP_EXT_SLEEP_CTRL_REG,
+ MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_MASK,
+ MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_OFF);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+ ret = mxl8611x_led_cfg(phydev);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = mxl8611x_broadcast_cfg(phydev);
+ if (ret < 0)
+ goto err_restore_page;
+
+err_restore_page:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxlphy_read_lpa() - read LPA and setup lp_advertising for UTP mode
+ * @phydev: pointer to the phy_device
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * identical to genphy_check_read_lpa, but phy_read without mdio lock
+ *
+ * returns 0 or negative errno code
+ */
+static int mxlphy_read_lpa(struct phy_device *phydev)
+{
+ int lpa, lpagb;
+
+ 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 (phydev->is_gigabit_capable) {
+ lpagb = __phy_read(phydev, MII_STAT1000);
+ if (lpagb < 0)
+ return lpagb;
+
+ if (lpagb & LPA_1000MSFAIL) {
+ int adv = __phy_read(phydev, MII_CTRL1000);
+
+ if (adv < 0)
+ return adv;
+
+ if (adv & CTL1000_ENABLE_MASTER)
+ phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting manual settings?\n");
+ else
+ phydev_err(phydev, "Master/Slave resolution failed\n");
+ return -ENOLINK;
+ }
+
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
+ lpagb);
+ }
+
+ lpa = __phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa);
+ } else {
+ linkmode_zero(phydev->lp_advertising);
+ }
+
+ return 0;
+}
+
+/**
+ * mxl86111_adjust_status() - update speed and duplex to phydev. when in fiber
+ * mode, adjust speed and duplex.
+ * @phydev: pointer to the phy_device
+ * @status: mxl86111 status read from MXL86111_PHY_STAT_REG
+ * @is_utp: false(mxl86111 work in fiber mode) or true(mxl86111 work in utp mode)
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * returns 0
+ */
+static int mxl86111_adjust_status(struct phy_device *phydev, int status, bool is_utp)
+{
+ int speed_mode, duplex;
+ int speed;
+ int err;
+ int lpa;
+
+ if (is_utp)
+ duplex = (status & MXL86111_PHY_STAT_DPX) >> MXL86111_PHY_STAT_DPX_OFFSET;
+ else
+ duplex = DUPLEX_FULL; /* FIBER is always DUPLEX_FULL */
+
+ speed_mode = (status & MXL86111_PHY_STAT_SPEED_MASK) >>
+ MXL86111_PHY_STAT_SPEED_OFFSET;
+
+ switch (speed_mode) {
+ case MXL86111_PHY_STAT_SPEED_10M:
+ if (is_utp)
+ speed = SPEED_10;
+ else
+ /* not supported for FIBER mode */
+ speed = SPEED_UNKNOWN;
+ break;
+ case MXL86111_PHY_STAT_SPEED_100M:
+ speed = SPEED_100;
+ break;
+ case MXL86111_PHY_STAT_SPEED_1000M:
+ speed = SPEED_1000;
+ break;
+ default:
+ speed = SPEED_UNKNOWN;
+ break;
+ }
+
+ phydev->speed = speed;
+ phydev->duplex = duplex;
+
+ if (is_utp) {
+ err = mxlphy_read_lpa(phydev);
+ if (err < 0)
+ return err;
+
+ phy_resolve_aneg_pause(phydev);
+ } else {
+ lpa = __phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+ /* only support 1000baseX Full Duplex in Fiber mode */
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->lp_advertising, lpa & LPA_1000XFULL);
+ if (!(lpa & MXL86111_SDS_AN_LPA_PAUSE)) {
+ /* disable pause */
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ } else if ((lpa & MXL86111_SDS_AN_LPA_ASYM_PAUSE)) {
+ /* asymmetric pause */
+ phydev->pause = 1;
+ phydev->asym_pause = 1;
+ } else {
+ /* symmetric pause */
+ phydev->pause = 1;
+ phydev->asym_pause = 0;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * mxl86111_read_status_paged() - determines the speed and duplex of one page
+ * @phydev: pointer to the phy_device
+ * @page: The reg page to operate
+ * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ *
+ * returns 1 (utp or fiber link), 0 (no link) or negative errno code
+ */
+static int mxl86111_read_status_paged(struct phy_device *phydev, int page)
+{
+ int sds_stat_stored, sds_stat_curr;
+ int page_to_restore;
+ int ret = 0;
+ int status;
+ int link;
+
+ linkmode_zero(phydev->lp_advertising);
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->asym_pause = 0;
+ phydev->pause = 0;
+
+ /* MxL86111 uses two reg spaces (utp/fiber). In Dual Media mode,
+ * the PHY HW will do the switching autonomously upon link status.
+ * By default, utp has priority. In this mode the HW controlled
+ * reg page selection bit is read-only and can only be read.
+ * The reg space should be properly set before reading
+ * MXL86111_PHY_STAT_REG.
+ */
+
+ page &= MXL86111_EXT_SMI_SDS_PHYSPACE_MASK;
+ page_to_restore = phy_select_page(phydev, page);
+ if (page_to_restore < 0)
+ goto err_restore_page;
+
+ /* Read MXL86111_PHY_STAT_REG, which reports the actual speed
+ * and duplex.
+ */
+ ret = __phy_read(phydev, MXL86111_PHY_STAT_REG);
+ if (ret < 0)
+ goto err_restore_page;
+
+ status = ret;
+ link = !!(status & MXL86111_PHY_STAT_LSRT);
+
+ /* In fiber mode there is no link down from MXL86111_PHY_STAT_REG
+ * when speed switches from 1000 Mbps to 100Mbps.
+ * It is required to check MII_BMSR to identify such a situation.
+ */
+ if (page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) {
+ ret = __phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ goto err_restore_page;
+ sds_stat_stored = ret;
+
+ ret = __phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ goto err_restore_page;
+ sds_stat_curr = ret;
+
+ if (link && sds_stat_stored != sds_stat_curr) {
+ link = 0;
+ phydev_info(phydev,
+ "%s, fiber link down detected by SDS_STAT change from %04x to %04x\n",
+ __func__, sds_stat_stored, sds_stat_curr);
+ }
+ } else {
+ /* Read autonegotiation status */
+ ret = __phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ phydev->autoneg_complete = ret & BMSR_ANEGCOMPLETE ? 1 : 0;
+ }
+
+ if (link) {
+ if (page == MXL86111_EXT_SMI_SDS_PHYUTP_SPACE)
+ mxl86111_adjust_status(phydev, status, true);
+ else
+ mxl86111_adjust_status(phydev, status, false);
+ }
+ return phy_restore_page(phydev, page_to_restore, link);
+
+err_restore_page:
+ return phy_restore_page(phydev, page_to_restore, ret);
+}
+
+/**
+ * mxl86111_read_status() - determines the negotiated speed and duplex
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_read_status(struct phy_device *phydev)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int link;
+ int page;
+
+ page = mxl86111_locked_read_page(phydev);
+ if (page < 0)
+ return page;
+
+ if (priv->reg_page != MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ link = mxl86111_read_status_paged(phydev, priv->reg_page);
+ if (link < 0)
+ return link;
+ } else {
+ link = mxl86111_read_status_paged(phydev, page);
+ }
+
+ if (link) {
+ if (phydev->link == 0) {
+ /* set port type in Dual Media mode based on
+ * active page select (controlled by PHY HW).
+ */
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO &&
+ priv->reg_page == MXL86111_EXT_SMI_SDS_PHY_AUTO) {
+ priv->reg_page = page;
+
+ phydev->port = (page == MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) ?
+ PORT_FIBRE : PORT_TP;
+
+ phydev_info(phydev, "%s, link up, media: %s\n", __func__,
+ (phydev->port == PORT_TP) ? "UTP" : "Fiber");
+ }
+ phydev->link = 1;
+ } else {
+ if (priv->reg_page != page) {
+ /* PHY HW detected UTP port active in parallel to FIBER port.
+ * Since UTP has priority, the page will be hard switched from FIBER
+ * to UTP. This needs to be detected and FIBER link reported as down
+ */
+ phydev_info(phydev, "%s, link down, media: %s\n",
+ __func__, (phydev->port == PORT_TP) ? "UTP" : "Fiber");
+
+ /* When in MXL86111_MODE_AUTO mode,
+ * prepare to detect the next activation mode
+ * to support Dual Media mode
+ */
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO) {
+ priv->reg_page = MXL86111_EXT_SMI_SDS_PHY_AUTO;
+ phydev->port = PORT_NONE;
+ }
+ phydev->link = 0;
+ }
+ }
+ } else {
+ if (phydev->link == 1) {
+ phydev_info(phydev, "%s, link down, media: %s\n",
+ __func__, (phydev->port == PORT_TP) ? "UTP" : "Fiber");
+
+ /* When in MXL86111_MODE_AUTO mode, arbitration will be prepare
+ * to support Dual Media mode
+ */
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO) {
+ priv->reg_page = MXL86111_EXT_SMI_SDS_PHY_AUTO;
+ phydev->port = PORT_NONE;
+ }
+ }
+
+ phydev->link = 0;
+ }
+
+ return 0;
+}
+
+/**
+ * mxl86111_soft_reset() - issue a PHY software reset
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_soft_reset(struct phy_device *phydev)
+{
+ return mxl86111_modify_bmcr(phydev, 0, BMCR_RESET);
+}
+
+/**
+ * mxl86111_suspend() - suspend the PHY hardware
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_suspend(struct phy_device *phydev)
+{
+ struct mxl86111_priv *priv = phydev->priv;
+ int wol_config;
+
+ wol_config = mxlphy_locked_read_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG);
+ if (wol_config < 0)
+ return wol_config;
+
+ /* if wake-on-lan is enabled, do nothing */
+ if (wol_config & MXL8611X_EXT_WOL_CFG_WOLE_MASK)
+ return 0;
+
+ /* do not power down in Dual Media mode, since page is controlled by HW
+ * and the mode might not 'wake up' anymore later on when the other mode is active
+ */
+ if (priv->reg_page_mode == MXL86111_MODE_AUTO)
+ return 0;
+
+ return mxl86111_modify_bmcr(phydev, 0, BMCR_PDOWN);
+}
+
+/**
+ * mxl86111_resume() - resume the PHY hardware
+ * @phydev: pointer to the phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int mxl86111_resume(struct phy_device *phydev)
+{
+ int wol_config;
+
+ wol_config = mxlphy_locked_read_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG);
+ if (wol_config < 0)
+ return wol_config;
+
+ /* if wake-on-lan is enabled, do nothing */
+ if (wol_config & MXL8611X_EXT_WOL_CFG_WOLE_MASK)
+ return 0;
+
+ return mxl86111_modify_bmcr(phydev, BMCR_PDOWN, 0);
+}
+
+static struct phy_driver mxl_phy_drvs[] = {
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_MXL86110),
+ .name = "MXL86110 Gigabit Ethernet",
+ .probe = mxl86110_probe,
+ .config_init = mxl86110_config_init,
+ .config_aneg = genphy_config_aneg,
+ .read_page = mxl86110_read_page,
+ .write_page = mxl86110_write_page,
+ .read_status = genphy_read_status,
+ .get_wol = mxlphy_get_wol,
+ .set_wol = mxlphy_set_wol,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_MXL86111),
+ .name = "MXL86111 Gigabit Ethernet",
+ .get_features = mxl86111_get_features,
+ .probe = mxl86111_probe,
+ .config_init = mxl86111_config_init,
+ .config_aneg = mxl86111_config_aneg,
+ .aneg_done = mxl86111_aneg_done,
+ .read_status = mxl86111_read_status,
+ .read_page = mxl86111_read_page,
+ .write_page = mxl86111_write_page,
+ .get_wol = mxlphy_get_wol,
+ .set_wol = mxlphy_set_wol,
+ .suspend = mxl86111_suspend,
+ .resume = mxl86111_resume,
+ .soft_reset = mxl86111_soft_reset,
+ },
+};
+
+module_phy_driver(mxl_phy_drvs);
+
+MODULE_DESCRIPTION(MXL8611x_DRIVER_DESC);
+MODULE_VERSION(MXL8611x_DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+static const struct mdio_device_id __maybe_unused mxl_tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_MXL86110) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_MXL86111) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, mxl_tbl);
--
2.43.0
Powered by blists - more mailing lists