Files
infix/patches/linux/6.18.39/0043-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch
T

460 lines
14 KiB
Diff

From f84562d7c183ec174e8083d9ea4513658d96dc00 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Mon, 6 Apr 2026 14:14:23 +0200
Subject: [PATCH 43/50] net/pcs: add MediaTek MT7988 USXGMII PCS driver
Add a PCS driver for the USXGMII subsystem found in the MediaTek MT7988
SoC (usxgmiisys0 at 0x10080000, usxgmiisys1 at 0x10081000). The hardware
supports USXGMII (10G with in-band AN), 10GBase-R and 5GBase-R interface
modes.
The driver:
- Registers via devm_pcs_register() so the Ethernet MAC driver can find
it through a DT pcs-handle phandle
- Delegates SerDes initialisation to the xfi_tphy PHY driver
- Sets up PCS speed/AN control registers and handles link state polling
- Reports link state from pcs_get_state without re-running pcs_config;
the 10G SerDes needs several seconds to complete CDR lock after a reset
and must not be restarted on every poll cycle before it can lock
Based on work by Henry Yen <henry.yen@mediatek.com> and
Daniel Golle <daniel@makrotopia.org>.
---
drivers/net/pcs/Kconfig | 10 +
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs-mtk-usxgmii.c | 394 ++++++++++++++++++++++++++++++
3 files changed, 405 insertions(+)
create mode 100644 drivers/net/pcs/pcs-mtk-usxgmii.c
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index f6b4de7f972c..563c12d6aa9c 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -29,6 +29,16 @@ config PCS_MTK_LYNXI
This module provides helpers to phylink for managing the LynxI PCS
which is part of MediaTek's SoC and Ethernet switch ICs.
+config PCS_MTK_USXGMII
+ tristate "MediaTek USXGMII PCS"
+ select PCS_STANDALONE
+ select PHY_MTK_XFI_TPHY
+ select PHYLINK
+ help
+ This module provides a driver for MediaTek's USXGMII PCS found in
+ the MT7988 SoC, supporting USXGMII, 10GBase-R and 5GBase-R interface
+ modes.
+
config PCS_RZN1_MIIC
tristate "Renesas RZ/N1, RZ/N2H, RZ/T2H MII converter"
depends on OF
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 0cb0057f2b8e..b876cb2157b1 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -8,4 +8,5 @@ obj-$(CONFIG_PCS_STANDALONE) += pcs-standalone.o
obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
+obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
diff --git a/drivers/net/pcs/pcs-mtk-usxgmii.c b/drivers/net/pcs/pcs-mtk-usxgmii.c
new file mode 100644
index 000000000000..f0e7d418db38
--- /dev/null
+++ b/drivers/net/pcs/pcs-mtk-usxgmii.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ * Author: Henry Yen <henry.yen@mediatek.com>
+ * Daniel Golle <daniel@makrotopia.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mdio.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/reset.h>
+#include <linux/pcs/pcs-standalone.h>
+#include <linux/platform_device.h>
+
+/* USXGMII subsystem config registers */
+/* Register to control speed */
+#define RG_PHY_TOP_SPEED_CTRL1 0x80c
+#define USXGMII_RATE_UPDATE_MODE BIT(31)
+#define USXGMII_MAC_CK_GATED BIT(29)
+#define USXGMII_IF_FORCE_EN BIT(28)
+#define USXGMII_RATE_ADAPT_MODE GENMASK(10, 8)
+#define USXGMII_RATE_ADAPT_MODE_X1 0
+#define USXGMII_RATE_ADAPT_MODE_X2 1
+#define USXGMII_RATE_ADAPT_MODE_X4 2
+#define USXGMII_RATE_ADAPT_MODE_X10 3
+#define USXGMII_RATE_ADAPT_MODE_X100 4
+#define USXGMII_RATE_ADAPT_MODE_X5 5
+#define USXGMII_RATE_ADAPT_MODE_X50 6
+#define USXGMII_XFI_RX_MODE GENMASK(6, 4)
+#define USXGMII_XFI_TX_MODE GENMASK(2, 0)
+#define USXGMII_XFI_MODE_10G 0
+#define USXGMII_XFI_MODE_5G 1
+#define USXGMII_XFI_MODE_2P5G 3
+
+/* Register to control PCS AN */
+#define RG_PCS_AN_CTRL0 0x810
+#define USXGMII_AN_RESTART BIT(31)
+#define USXGMII_AN_SYNC_CNT GENMASK(30, 11)
+#define USXGMII_AN_ENABLE BIT(0)
+
+#define RG_PCS_AN_CTRL2 0x818
+#define USXGMII_LINK_TIMER_IDLE_DETECT GENMASK(29, 20)
+#define USXGMII_LINK_TIMER_COMP_ACK_DETECT GENMASK(19, 10)
+#define USXGMII_LINK_TIMER_AN_RESTART GENMASK(9, 0)
+
+/* Register to read PCS AN status */
+#define RG_PCS_AN_STS0 0x81c
+#define USXGMII_LPA GENMASK(15, 0)
+#define USXGMII_LPA_LATCH BIT(31)
+
+/* Register to read PCS link status */
+#define RG_PCS_RX_STATUS0 0x904
+#define RG_PCS_RX_STATUS_UPDATE BIT(16)
+#define RG_PCS_RX_LINK_STATUS BIT(2)
+
+/* struct mtk_usxgmii_pcs - This structure holds each usxgmii PCS
+ * @pcs: Phylink PCS structure
+ * @dev: Pointer to device structure
+ * @base: IO memory to access PCS hardware
+ * @clk: Pointer to USXGMII clk
+ * @reset: Pointer to USXGMII reset control
+ * @xfi_tphy: Pointer to XFI transceiver PHY
+ * @interface: Currently selected interface mode
+ * @node: List node
+ */
+struct mtk_usxgmii_pcs {
+ struct phylink_pcs pcs;
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *reset;
+ struct phy *xfi_tphy;
+ phy_interface_t interface;
+ struct list_head node;
+};
+
+static u32 mtk_r32(struct mtk_usxgmii_pcs *mpcs, unsigned int reg)
+{
+ return ioread32(mpcs->base + reg);
+}
+
+static void mtk_m32(struct mtk_usxgmii_pcs *mpcs, unsigned int reg, u32 mask, u32 set)
+{
+ u32 val;
+
+ val = ioread32(mpcs->base + reg);
+ val &= ~mask;
+ val |= set;
+ iowrite32(val, mpcs->base + reg);
+}
+
+static struct mtk_usxgmii_pcs *pcs_to_mtk_usxgmii_pcs(struct phylink_pcs *pcs)
+{
+ return container_of(pcs, struct mtk_usxgmii_pcs, pcs);
+}
+
+static void mtk_usxgmii_reset(struct mtk_usxgmii_pcs *mpcs)
+{
+ reset_control_assert(mpcs->reset);
+ udelay(100);
+ reset_control_deassert(mpcs->reset);
+
+ mdelay(10);
+}
+
+static int mtk_usxgmii_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+ unsigned int an_ctrl = 0, link_timer = 0, xfi_mode = 0, adapt_mode = 0;
+ bool mode_changed = false;
+
+ if (interface == PHY_INTERFACE_MODE_USXGMII) {
+ an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF) | USXGMII_AN_ENABLE;
+ link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
+ FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
+ FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
+ xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_10G) |
+ FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_10G);
+ } else if (interface == PHY_INTERFACE_MODE_10GBASER) {
+ an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF);
+ link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
+ FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
+ FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
+ xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_10G) |
+ FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_10G);
+ adapt_mode = USXGMII_RATE_UPDATE_MODE;
+ } else if (interface == PHY_INTERFACE_MODE_5GBASER) {
+ an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0xFF);
+ link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x3D) |
+ FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x3D) |
+ FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x3D);
+ xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_5G) |
+ FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_5G);
+ adapt_mode = USXGMII_RATE_UPDATE_MODE;
+ } else {
+ return -EINVAL;
+ }
+
+ adapt_mode |= FIELD_PREP(USXGMII_RATE_ADAPT_MODE, USXGMII_RATE_ADAPT_MODE_X1);
+
+ if (mpcs->interface != interface) {
+ mpcs->interface = interface;
+ mode_changed = true;
+ }
+
+ phy_reset(mpcs->xfi_tphy);
+ mtk_usxgmii_reset(mpcs);
+
+ /* Setup USXGMII AN ctrl */
+ mtk_m32(mpcs, RG_PCS_AN_CTRL0,
+ USXGMII_AN_SYNC_CNT | USXGMII_AN_ENABLE,
+ an_ctrl);
+
+ mtk_m32(mpcs, RG_PCS_AN_CTRL2,
+ USXGMII_LINK_TIMER_IDLE_DETECT |
+ USXGMII_LINK_TIMER_COMP_ACK_DETECT |
+ USXGMII_LINK_TIMER_AN_RESTART,
+ link_timer);
+
+ /* Gated MAC CK */
+ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
+ USXGMII_MAC_CK_GATED, USXGMII_MAC_CK_GATED);
+
+ /* Enable interface force mode */
+ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
+ USXGMII_IF_FORCE_EN, USXGMII_IF_FORCE_EN);
+
+ /* Setup USXGMII adapt mode */
+ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
+ USXGMII_RATE_UPDATE_MODE | USXGMII_RATE_ADAPT_MODE,
+ adapt_mode);
+
+ /* Setup USXGMII speed */
+ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
+ USXGMII_XFI_RX_MODE | USXGMII_XFI_TX_MODE,
+ xfi_mode);
+
+ usleep_range(1, 10);
+
+ /* Un-gated MAC CK */
+ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1, USXGMII_MAC_CK_GATED, 0);
+
+ usleep_range(1, 10);
+
+ /* Disable interface force mode for the AN mode */
+ if (an_ctrl & USXGMII_AN_ENABLE)
+ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1, USXGMII_IF_FORCE_EN, 0);
+
+ /* Setup PMA/PMD */
+ phy_set_mode_ext(mpcs->xfi_tphy, PHY_MODE_ETHERNET, interface);
+
+ return mode_changed;
+}
+
+static void mtk_usxgmii_pcs_get_fixed_speed(struct mtk_usxgmii_pcs *mpcs,
+ struct phylink_link_state *state)
+{
+ u32 val = mtk_r32(mpcs, RG_PHY_TOP_SPEED_CTRL1);
+ int speed;
+
+ /* Calculate speed from interface speed and rate adapt mode */
+ switch (FIELD_GET(USXGMII_XFI_RX_MODE, val)) {
+ case USXGMII_XFI_MODE_10G:
+ speed = 10000;
+ break;
+ case USXGMII_XFI_MODE_5G:
+ speed = 5000;
+ break;
+ case USXGMII_XFI_MODE_2P5G:
+ speed = 2500;
+ break;
+ default:
+ state->speed = SPEED_UNKNOWN;
+ return;
+ }
+
+ switch (FIELD_GET(USXGMII_RATE_ADAPT_MODE, val)) {
+ case USXGMII_RATE_ADAPT_MODE_X100:
+ speed /= 100;
+ break;
+ case USXGMII_RATE_ADAPT_MODE_X50:
+ speed /= 50;
+ break;
+ case USXGMII_RATE_ADAPT_MODE_X10:
+ speed /= 10;
+ break;
+ case USXGMII_RATE_ADAPT_MODE_X5:
+ speed /= 5;
+ break;
+ case USXGMII_RATE_ADAPT_MODE_X4:
+ speed /= 4;
+ break;
+ case USXGMII_RATE_ADAPT_MODE_X2:
+ speed /= 2;
+ break;
+ case USXGMII_RATE_ADAPT_MODE_X1:
+ break;
+ default:
+ state->speed = SPEED_UNKNOWN;
+ return;
+ }
+
+ state->speed = speed;
+ state->duplex = DUPLEX_FULL;
+}
+
+static void mtk_usxgmii_pcs_get_an_state(struct mtk_usxgmii_pcs *mpcs,
+ struct phylink_link_state *state)
+{
+ u16 lpa;
+
+ /* Refresh LPA by toggling LPA_LATCH */
+ mtk_m32(mpcs, RG_PCS_AN_STS0, USXGMII_LPA_LATCH, USXGMII_LPA_LATCH);
+ ndelay(1020);
+ mtk_m32(mpcs, RG_PCS_AN_STS0, USXGMII_LPA_LATCH, 0);
+ ndelay(1020);
+ lpa = FIELD_GET(USXGMII_LPA, mtk_r32(mpcs, RG_PCS_AN_STS0));
+
+ phylink_decode_usxgmii_word(state, lpa);
+}
+
+static void mtk_usxgmii_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+
+ /* Refresh USXGMII link status by toggling RG_PCS_AN_STATUS_UPDATE */
+ mtk_m32(mpcs, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE,
+ RG_PCS_RX_STATUS_UPDATE);
+ ndelay(1020);
+ mtk_m32(mpcs, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE, 0);
+ ndelay(1020);
+
+ /* Read USXGMII link status */
+ state->link = FIELD_GET(RG_PCS_RX_LINK_STATUS,
+ mtk_r32(mpcs, RG_PCS_RX_STATUS0));
+
+ if (!state->link)
+ return;
+
+ if (FIELD_GET(USXGMII_AN_ENABLE, mtk_r32(mpcs, RG_PCS_AN_CTRL0)))
+ mtk_usxgmii_pcs_get_an_state(mpcs, state);
+ else
+ mtk_usxgmii_pcs_get_fixed_speed(mpcs, state);
+}
+
+static void mtk_usxgmii_pcs_restart_an(struct phylink_pcs *pcs)
+{
+ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+
+ mtk_m32(mpcs, RG_PCS_AN_CTRL0, USXGMII_AN_RESTART, USXGMII_AN_RESTART);
+}
+
+static void mtk_usxgmii_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ int speed, int duplex)
+{
+ /* Reconfiguring USXGMII to ensure the quality of the RX signal
+ * after the line side link up.
+ */
+ mtk_usxgmii_pcs_config(pcs, neg_mode, interface, NULL, false);
+}
+
+static int mtk_usxgmii_pcs_enable(struct phylink_pcs *pcs)
+{
+ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+
+ phy_power_on(mpcs->xfi_tphy);
+
+ return 0;
+}
+
+static void mtk_usxgmii_pcs_disable(struct phylink_pcs *pcs)
+{
+ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+
+ mpcs->interface = PHY_INTERFACE_MODE_NA;
+
+ phy_power_off(mpcs->xfi_tphy);
+}
+
+static const struct phylink_pcs_ops mtk_usxgmii_pcs_ops = {
+ .pcs_config = mtk_usxgmii_pcs_config,
+ .pcs_get_state = mtk_usxgmii_pcs_get_state,
+ .pcs_an_restart = mtk_usxgmii_pcs_restart_an,
+ .pcs_link_up = mtk_usxgmii_pcs_link_up,
+ .pcs_enable = mtk_usxgmii_pcs_enable,
+ .pcs_disable = mtk_usxgmii_pcs_disable,
+};
+
+static int mtk_usxgmii_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_usxgmii_pcs *mpcs;
+
+ mpcs = devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL);
+ if (!mpcs)
+ return -ENOMEM;
+
+ mpcs->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mpcs->base))
+ return PTR_ERR(mpcs->base);
+
+ mpcs->dev = dev;
+ mpcs->pcs.ops = &mtk_usxgmii_pcs_ops;
+ mpcs->pcs.poll = true;
+ mpcs->interface = PHY_INTERFACE_MODE_NA;
+
+ mpcs->clk = devm_clk_get_enabled(mpcs->dev, NULL);
+ if (IS_ERR(mpcs->clk))
+ return PTR_ERR(mpcs->clk);
+
+ mpcs->xfi_tphy = devm_of_phy_get(mpcs->dev, dev->of_node, NULL);
+ if (IS_ERR(mpcs->xfi_tphy))
+ return PTR_ERR(mpcs->xfi_tphy);
+
+ mpcs->reset = devm_reset_control_get_shared(dev, NULL);
+ if (IS_ERR(mpcs->reset))
+ return PTR_ERR(mpcs->reset);
+
+ reset_control_deassert(mpcs->reset);
+
+ platform_set_drvdata(pdev, mpcs);
+
+ return devm_pcs_register(dev, &mpcs->pcs);
+}
+
+static const struct of_device_id mtk_usxgmii_of_mtable[] = {
+ { .compatible = "mediatek,mt7988-usxgmiisys" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mtk_usxgmii_of_mtable);
+
+static struct platform_driver mtk_usxgmii_driver = {
+ .driver = {
+ .name = "mtk_usxgmii",
+ .suppress_bind_attrs = true,
+ .of_match_table = mtk_usxgmii_of_mtable,
+ },
+ .probe = mtk_usxgmii_probe,
+};
+module_platform_driver(mtk_usxgmii_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek USXGMII PCS driver");
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");