summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2023-07-18 18:07:48 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:38:18 +0300
commit9991185ee796cae5271bf04a94fa062dbc7acf09 (patch)
treef65d562a57465f23ed460b5f1138ceb721cef01f
parentcc67886881f55520147cbdf37b8cd122fbbfb084 (diff)
downloadlinux-9991185ee796cae5271bf04a94fa062dbc7acf09.tar.gz
linux-9991185ee796cae5271bf04a94fa062dbc7acf09.zip
net: phy: marvell-88x2222: Add host- and line-side interrupts support
It's possible to have the controller reporting the device events via an IRQ line. It's polarity is active-low by default, but also can be configured by means of the Chip-level Global Interrupt Control register (out of this change scope). There are three types of events reported by the interrupt: line-side PCS link state, host-side PCS link state and GPIOs. This update concerns the first two events as they are of the particular PHY subsystem interest. As it's intended by the Network PHY subsystem two callbacks are implemented: config_intr() and handle_interrupt(). The former one is responsible for activating/de-activating the host-side (XAUI and 10GBase-R) and line-side (1000Base-X/SGMII and 10GBase-R) link state interrupts. The host-side IRQs are configured depending on the selected interface mode meanwhile all relevant line-side IRQs are just enabled since there is no info regarding the line-side link type at the moment of the configuration procedure and might get to be available only after the auto-negation is completed or when a SFP module is inserted. The later callback is responsible for the IRQs handling. If there is a MDIO-based communication is detected to be malfunction PHY will get to be halted by means of the phy_error() method. Otherwise the Port-level Interrupt Status value will be utilized to check whether the device has any pending interrupt. Any link state change or errors detection will cause the PHY state machine been triggered. Info regarding the detected errors will be also sent to the system log as a debug-message for now since the errors may happen due to normal link up/down procedures. Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
-rw-r--r--drivers/net/phy/marvell-88x2222.c358
1 files changed, 358 insertions, 0 deletions
diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c
index 9d21aa3e3d673..cb8025fb43cbe 100644
--- a/drivers/net/phy/marvell-88x2222.c
+++ b/drivers/net/phy/marvell-88x2222.c
@@ -9,6 +9,7 @@
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/irq.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/delay.h>
@@ -34,6 +35,12 @@
#define MV_HOST_RST_SW BIT(7)
#define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW)
+/* Port Interrupt Status */
+#define MV_PORT_INT_STAT 0xF040
+#define MV_PORT_INT_PCS_LINE BIT(0)
+#define MV_PORT_INT_PCS_HOST BIT(2)
+#define MV_PORT_INT_GPIO BIT(3)
+
/* PMD Receive Signal Detect */
#define MV_RX_SIGNAL_DETECT 0x000A
#define MV_RX_SIGNAL_DETECT_GLOBAL BIT(0)
@@ -47,6 +54,41 @@
/* 1000Base-X Auto-Negotiation Advertisement Register */
#define MV_1GBX_ADVERTISE (0x2000 + MII_ADVERTISE)
+/* 10GBase-R Interrupt Enable Register */
+#define MV_10GBR_INT_EN 0x8000
+#define MV_10GBR_INT_BLOCK_LOCK BIT(0)
+#define MV_10GBR_INT_HIGH_BER BIT(1)
+#define MV_10GBR_INT_LINK_STAT BIT(2)
+#define MV_10GBR_INT_LOCAL_RXFAULT BIT(10)
+#define MV_10GBR_INT_LOCAL_TXFAULT BIT(11)
+#define MV_10GBR_INT_MASK (MV_10GBR_INT_LINK_STAT | \
+ MV_10GBR_INT_LOCAL_RXFAULT | \
+ MV_10GBR_INT_LOCAL_TXFAULT)
+
+/* 10GBase-R Interrupt Status Register */
+#define MV_10GBR_INT_STAT 0x8001
+
+/* 1000Base-X Interrupt Enable Register */
+#define MV_1GBX_INT_EN 0xA001
+#define MV_1GBX_INT_FALSE_CARRIER BIT(7)
+#define MV_1GBX_INT_SYMBOL_ERROR BIT(8)
+#define MV_1GBX_INT_LINK_UP BIT(9)
+#define MV_1GBX_INT_LINK_DOWN BIT(10)
+#define MV_1GBX_INT_AN_COMPLETED BIT(11)
+#define MV_1GBX_INT_PAGE_RECEIVED BIT(12)
+#define MV_1GBX_INT_DUPLEX_CHANGED BIT(13)
+#define MV_1GBX_INT_SPEED_CHANGED BIT(14)
+#define MV_1GBX_INT_MASK (MV_1GBX_INT_FALSE_CARRIER | \
+ MV_1GBX_INT_SYMBOL_ERROR | \
+ MV_1GBX_INT_LINK_UP | \
+ MV_1GBX_INT_LINK_DOWN | \
+ MV_1GBX_INT_AN_COMPLETED | \
+ MV_1GBX_INT_DUPLEX_CHANGED | \
+ MV_1GBX_INT_SPEED_CHANGED)
+
+/* 1000Base-X Interrupt Status Register */
+#define MV_1GBX_INT_STAT 0xA002
+
/* 1000Base-X PHY Specific Status Register */
#define MV_1GBX_PHY_STAT 0xA003
#define MV_1GBX_PHY_STAT_AN_RESOLVED BIT(11)
@@ -60,6 +102,48 @@
/* Host-side XAUI PCS Status 1 Register */
#define MV_HOST_XAUI_PCS_STAT1 (0x1000 + MDIO_STAT1)
+/* Host-side 10GBase-R Interrupt Enable Register */
+#define MV_HOST_10GBR_INT_EN 0x8000
+#define MV_HOST_10GBR_INT_BLOCK_LOCK BIT(0)
+#define MV_HOST_10GBR_INT_HIGH_BER BIT(1)
+#define MV_HOST_10GBR_INT_LINK_STAT BIT(2)
+#define MV_HOST_10GBR_INT_LOCAL_RXFAULT BIT(10)
+#define MV_HOST_10GBR_INT_LOCAL_TXFAULT BIT(11)
+#define MV_HOST_10GBR_INT_MASK (MV_HOST_10GBR_INT_LINK_STAT | \
+ MV_HOST_10GBR_INT_LOCAL_RXFAULT | \
+ MV_HOST_10GBR_INT_LOCAL_TXFAULT)
+
+/* Host-side 10GBase-R Interrupt Status Register */
+#define MV_HOST_10GBR_INT_STAT 0x8001
+
+/* Host-side XAUI Interrupt Enable 1 Register */
+#define MV_HOST_XAUI_INT_EN1 0x9001
+#define MV_HOST_XAUI_INT_LINK_UP BIT(2)
+#define MV_HOST_XAUI_INT_LINK_DOWN BIT(3)
+#define MV_HOST_XAUI_INT_MASK1 (MV_HOST_XAUI_INT_LINK_UP | \
+ MV_HOST_XAUI_INT_LINK_DOWN)
+
+/* Host-side XAUI Interrupt Enable 2 Register */
+#define MV_HOST_XAUI_INT_EN2 0x9002
+#define MV_HOST_XAUI_INT_LANE0_SYNC BIT(0)
+#define MV_HOST_XAUI_INT_LANE1_SYNC BIT(1)
+#define MV_HOST_XAUI_INT_LANE2_SYNC BIT(2)
+#define MV_HOST_XAUI_INT_LANE3_SYNC BIT(3)
+#define MV_HOST_XAUI_INT_LANE0_ENERGY BIT(4)
+#define MV_HOST_XAUI_INT_LANE1_ENERGY BIT(5)
+#define MV_HOST_XAUI_INT_LANE2_ENERGY BIT(6)
+#define MV_HOST_XAUI_INT_LANE3_ENERGY BIT(7)
+#define MV_HOST_XAUI_INT_TXFAULT BIT(8)
+#define MV_HOST_XAUI_INT_RXFAULT BIT(9)
+#define MV_HOST_XAUI_INT_MASK2 (MV_HOST_XAUI_INT_TXFAULT | \
+ MV_HOST_XAUI_INT_RXFAULT)
+
+/* Host-side XAUI Interrupt Status 1 Register */
+#define MV_HOST_XAUI_INT_STAT1 0x9003
+
+/* Host-side XAUI Interrupt Status 2 Register */
+#define MV_HOST_XAUI_INT_STAT2 0x9004
+
#define AUTONEG_TIMEOUT 3
struct mv2222_data {
@@ -544,6 +628,276 @@ static int mv2222_read_status(struct phy_device *phydev)
return 0;
}
+static int mv2222_config_intr_host_10gbr(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_10GBASER &&
+ phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_STAT);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_EN,
+ MV_HOST_10GBR_INT_MASK);
+ } else {
+ ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_EN, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_STAT);
+ }
+
+ return ret;
+}
+
+static int mv2222_config_intr_host_xaui(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_XAUI &&
+ phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT1);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT2);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN1,
+ MV_HOST_XAUI_INT_MASK1);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN2,
+ MV_HOST_XAUI_INT_MASK2);
+ } else {
+ ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN2, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN1, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT2);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT1);
+ }
+
+ return ret;
+}
+
+static int mv2222_config_intr_10g(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_STAT);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_EN,
+ MV_10GBR_INT_MASK);
+ } else {
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_EN, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_STAT);
+ }
+
+ return ret;
+}
+
+static int mv2222_config_intr_1g(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_STAT);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_EN,
+ MV_1GBX_INT_MASK);
+ } else {
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_EN, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_STAT);
+ }
+
+ return ret;
+}
+
+static int mv2222_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = mv2222_config_intr_10g(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = mv2222_config_intr_1g(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = mv2222_config_intr_host_10gbr(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = mv2222_config_intr_host_xaui(phydev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mv2222_handle_interrupt_host_10gbr(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_STAT);
+ if (val < 0)
+ return val;
+
+ if (val & MV_HOST_10GBR_INT_LINK_STAT)
+ phydev_dbg(phydev, "Host link status changed\n");
+
+ if (val & MV_HOST_10GBR_INT_LOCAL_RXFAULT)
+ phydev_dbg(phydev, "Host Rx fault detected\n");
+
+ if (val & MV_HOST_10GBR_INT_LOCAL_TXFAULT)
+ phydev_dbg(phydev, "Host Tx fault detected\n");
+
+ return 0;
+}
+
+static int mv2222_handle_interrupt_host_xaui(struct phy_device *phydev)
+{
+ int val1, val2;
+
+ val1 = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT1);
+ if (val1 < 0)
+ return val1;
+
+ val2 = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT2);
+ if (val2 < 0)
+ return val2;
+
+ if (val1 & MV_HOST_XAUI_INT_LINK_UP)
+ phydev_dbg(phydev, "Host link is up\n");
+
+ if (val1 & MV_HOST_XAUI_INT_LINK_DOWN)
+ phydev_dbg(phydev, "Host link is down\n");
+
+ if (val2 & MV_HOST_XAUI_INT_TXFAULT)
+ phydev_dbg(phydev, "Host Tx fault detected\n");
+
+ if (val2 & MV_HOST_XAUI_INT_RXFAULT)
+ phydev_dbg(phydev, "Host Rx fault detected\n");
+
+ return 0;
+}
+
+static int mv2222_handle_interrupt_10g(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_STAT);
+ if (val < 0)
+ return val;
+
+ if (val & MV_10GBR_INT_LINK_STAT)
+ phydev_dbg(phydev, "Line link status changed\n");
+
+ if (val & MV_10GBR_INT_LOCAL_RXFAULT)
+ phydev_dbg(phydev, "Line Rx fault detected\n");
+
+ if (val & MV_10GBR_INT_LOCAL_TXFAULT)
+ phydev_dbg(phydev, "Line Tx fault detected\n");
+
+ return 0;
+}
+
+static int mv2222_handle_interrupt_1g(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_STAT);
+ if (val < 0)
+ return val;
+
+ if (val & MV_1GBX_INT_FALSE_CARRIER)
+ phydev_dbg(phydev, "Line false carrier detected\n");
+
+ if (val & MV_1GBX_INT_SYMBOL_ERROR)
+ phydev_dbg(phydev, "Line symbol error detected\n");
+
+ if (val & MV_1GBX_INT_LINK_UP)
+ phydev_dbg(phydev, "Line link is up\n");
+
+ if (val & MV_1GBX_INT_LINK_DOWN)
+ phydev_dbg(phydev, "Line link is down\n");
+
+ if (val & MV_1GBX_INT_AN_COMPLETED)
+ phydev_dbg(phydev, "Line AN is completed\n");
+
+ if (val & MV_1GBX_INT_DUPLEX_CHANGED)
+ phydev_dbg(phydev, "Line link duplex changed\n");
+
+ if (val & MV_1GBX_INT_SPEED_CHANGED)
+ phydev_dbg(phydev, "Line link speed changed\n");
+
+ return 0;
+}
+
+static irqreturn_t mv2222_handle_interrupt(struct phy_device *phydev)
+{
+ int val, ret;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_PORT_INT_STAT);
+ if (val < 0)
+ goto err_set_state;
+
+ if (!(val & (MV_PORT_INT_PCS_HOST | MV_PORT_INT_PCS_LINE)))
+ return IRQ_NONE;
+
+ if (val & MV_PORT_INT_PCS_HOST) {
+ ret = mv2222_handle_interrupt_host_10gbr(phydev);
+ if (ret < 0)
+ goto err_set_state;
+
+ ret = mv2222_handle_interrupt_host_xaui(phydev);
+ if (ret < 0)
+ goto err_set_state;
+ }
+
+ if (val & MV_PORT_INT_PCS_LINE) {
+ ret = mv2222_handle_interrupt_10g(phydev);
+ if (ret < 0)
+ goto err_set_state;
+
+ ret = mv2222_handle_interrupt_1g(phydev);
+ if (ret < 0)
+ goto err_set_state;
+ }
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+
+err_set_state:
+ phy_error(phydev);
+
+ return IRQ_NONE;
+}
+
static int mv2222_resume(struct phy_device *phydev)
{
return mv2222_tx_enable(phydev);
@@ -687,6 +1041,8 @@ static struct phy_driver mv2222_drivers[] = {
.soft_reset = mv2222_soft_reset,
.config_init = mv2222_config_init,
.config_aneg = mv2222_config_aneg,
+ .config_intr = mv2222_config_intr,
+ .handle_interrupt = mv2222_handle_interrupt,
.aneg_done = mv2222_aneg_done,
.probe = mv2222_probe,
.suspend = mv2222_suspend,
@@ -702,6 +1058,8 @@ static struct phy_driver mv2222_drivers[] = {
.soft_reset = mv2222_soft_reset,
.config_init = mv2222_config_init,
.config_aneg = mv2222_config_aneg,
+ .config_intr = mv2222_config_intr,
+ .handle_interrupt = mv2222_handle_interrupt,
.aneg_done = mv2222_aneg_done,
.probe = mv2222_probe,
.suspend = mv2222_suspend,