mediatek: switch to pending XFI 10G Ethernet drivers

Replace previous patch adding paths and SerDes modes with patch series
pending upstream adding dedicated drivers for XFI T-PHY and USXGMII PCS,
extends LynxI PCS to be a standalone platform driver and as a consequence
makes much less changes to the actual Ethernet driver mtk_eth_soc.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle
2024-01-28 03:55:15 +00:00
parent c36de2e73a
commit 4cb6bd9a6d
28 changed files with 2087 additions and 1112 deletions

View File

@@ -4838,6 +4838,7 @@ CONFIG_PCI_SYSCALL=y
# CONFIG_PCNET32 is not set
# CONFIG_PCPU_DEV_REFCNT is not set
# CONFIG_PCSPKR_PLATFORM is not set
# CONFIG_PCS_MTK_USXGMII is not set
# CONFIG_PCS_XPCS is not set
# CONFIG_PD6729 is not set
# CONFIG_PDA_POWER is not set
@@ -4872,6 +4873,7 @@ CONFIG_PCI_SYSCALL=y
# CONFIG_PHY_MIXEL_MIPI_DPHY is not set
# CONFIG_PHY_MTK_HDMI is not set
# CONFIG_PHY_MTK_MIPI_DSI is not set
# CONFIG_PHY_MTK_XFI_TPHY is not set
# CONFIG_PHY_MVEBU_CP110_UTMI is not set
# CONFIG_PHY_OCELOT_SERDES is not set
# CONFIG_PHY_PISTACHIO_USB is not set

View File

@@ -13,7 +13,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
@@ -92,14 +92,23 @@ static void mtk_pcs_lynxi_get_state(stru
@@ -114,14 +114,23 @@ static void mtk_pcs_lynxi_get_state(stru
struct phylink_link_state *state)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
@@ -40,8 +40,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ phylink_mii_c22_pcs_decode_state(state, bmsr, FIELD_GET(SGMII_LPA, adv));
}
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
@@ -130,7 +139,8 @@ static int mtk_pcs_lynxi_config(struct p
static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
@@ -163,7 +172,8 @@ static int mtk_pcs_lynxi_config(struct p
if (neg_mode & PHYLINK_PCS_NEG_INBAND)
sgm_mode |= SGMII_REMOTE_FAULT_DIS;

View File

@@ -32,7 +32,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -4885,7 +4885,10 @@ static int mtk_probe(struct platform_dev
@@ -4963,7 +4963,10 @@ static int mtk_probe(struct platform_dev
}
if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA)) {

View File

@@ -0,0 +1,136 @@
From patchwork Thu Feb 1 21:52:20 2024
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Daniel Golle <daniel@makrotopia.org>
X-Patchwork-Id: 13541842
Date: Thu, 1 Feb 2024 21:52:20 +0000
From: Daniel Golle <daniel@makrotopia.org>
To: Bc-bocun Chen <bc-bocun.chen@mediatek.com>,
Steven Liu <steven.liu@mediatek.com>,
John Crispin <john@phrozen.org>,
Chunfeng Yun <chunfeng.yun@mediatek.com>,
Vinod Koul <vkoul@kernel.org>,
Kishon Vijay Abraham I <kishon@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
Conor Dooley <conor+dt@kernel.org>,
Daniel Golle <daniel@makrotopia.org>,
Qingfang Deng <dqfext@gmail.com>,
SkyLake Huang <SkyLake.Huang@mediatek.com>,
Matthias Brugger <matthias.bgg@gmail.com>,
AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-phy@lists.infradead.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
netdev@vger.kernel.org
Subject: [PATCH 1/2] dt-bindings: phy: mediatek,xfi-tphy: add new bindings
Message-ID:
<702afb0c1246d95c90b22e57105304028bdd3083.1706823233.git.daniel@makrotopia.org>
MIME-Version: 1.0
Content-Disposition: inline
List-Id: Linux Phy Mailing list <linux-phy.lists.infradead.org>
Add bindings for the MediaTek XFI T-PHY Ethernet SerDes PHY found in the
MediaTek MT7988 SoC which can operate at various interfaces modes:
via USXGMII PCS:
* USXGMII
* 10GBase-R
* 5GBase-R
via LynxI SGMII PCS:
* 2500Base-X
* 1000Base-X
* Cisco SGMII (MAC side)
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
.../bindings/phy/mediatek,xfi-tphy.yaml | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/mediatek,xfi-tphy.yaml
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/mediatek,xfi-tphy.yaml
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/mediatek,xfi-tphy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek XFI T-PHY
+
+maintainers:
+ - Daniel Golle <daniel@makrotopia.org>
+
+description:
+ The MediaTek XFI SerDes T-PHY provides the physical SerDes lanes
+ used by the (10G/5G) USXGMII PCS and (1G/2.5G) LynxI PCS found in
+ MediaTek's 10G-capabale SoCs.
+
+properties:
+ $nodename:
+ pattern: "^phy@[0-9a-f]+$"
+
+ compatible:
+ const: mediatek,mt7988-xfi-tphy
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: XFI PHY clock
+ - description: XFI register clock
+
+ clock-names:
+ items:
+ - const: xfipll
+ - const: topxtal
+
+ resets:
+ items:
+ - description: PEXTP reset
+
+ mediatek,usxgmii-performance-errata:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ One instance of the T-PHY on MT7988 suffers from a performance
+ problem in 10GBase-R mode which needs a work-around in the driver.
+ The work-around is enabled using this flag.
+
+ "#phy-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - resets
+ - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/mediatek,mt7988-clk.h>
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ phy@11f20000 {
+ compatible = "mediatek,mt7988-xfi-tphy";
+ reg = <0 0x11f20000 0 0x10000>;
+ clocks = <&xfi_pll CLK_XFIPLL_PLL_EN>,
+ <&topckgen CLK_TOP_XFI_PHY_0_XTAL_SEL>;
+ clock-names = "xfipll", "topxtal";
+ resets = <&watchdog 14>;
+ mediatek,usxgmii-performance-errata;
+ #phy-cells = <0>;
+ };
+ };
+
+...

View File

@@ -0,0 +1,497 @@
From patchwork Thu Feb 1 21:53:06 2024
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Daniel Golle <daniel@makrotopia.org>
X-Patchwork-Id: 13541843
Date: Thu, 1 Feb 2024 21:53:06 +0000
From: Daniel Golle <daniel@makrotopia.org>
To: Bc-bocun Chen <bc-bocun.chen@mediatek.com>,
Chunfeng Yun <chunfeng.yun@mediatek.com>,
Vinod Koul <vkoul@kernel.org>,
Kishon Vijay Abraham I <kishon@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
Conor Dooley <conor+dt@kernel.org>,
Daniel Golle <daniel@makrotopia.org>,
Qingfang Deng <dqfext@gmail.com>,
SkyLake Huang <SkyLake.Huang@mediatek.com>,
Matthias Brugger <matthias.bgg@gmail.com>,
AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-phy@lists.infradead.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
netdev@vger.kernel.org
Subject: [PATCH 2/2] phy: add driver for MediaTek XFI T-PHY
Message-ID:
<dd6b40ea1f7f8459a9a2cfe7fa60c1108332ade6.1706823233.git.daniel@makrotopia.org>
References:
<702afb0c1246d95c90b22e57105304028bdd3083.1706823233.git.daniel@makrotopia.org>
MIME-Version: 1.0
Content-Disposition: inline
In-Reply-To:
<702afb0c1246d95c90b22e57105304028bdd3083.1706823233.git.daniel@makrotopia.org>
List-Id: Linux Phy Mailing list <linux-phy.lists.infradead.org>
Add driver for MediaTek's XFI T-PHY, 10 Gigabit/s Ethernet SerDes PHY
which can be found in the MT7988 SoC.
The PHY can operates only in PHY_MODE_ETHERNET, the submode is one of
PHY_INTERFACE_MODE_* corresponding to the supported modes:
* USXGMII \
* 10GBase-R }- USXGMII PCS - XGDM \
* 5GBase-R / \
}- Ethernet MAC
* 2500Base-X \ /
* 1000Base-X }- LynxI PCS - GDM /
* Cisco SGMII (MAC side) /
In order to work-around a performance issue present on the first of
two XFI T-PHYs present in MT7988, special tuning is applied which can be
selected by adding the 'mediatek,usxgmii-performance-errata' property to
the device tree node.
There is no documentation for most registers used for the
analog/tuning part, however, most of the registers have been partially
reverse-engineered from MediaTek's SDK implementation (an opaque
sequence of 32-bit register writes) and descriptions for all relevant
digital registers and bits such as resets and muxes have been supplied
by MediaTek.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
MAINTAINERS | 1 +
drivers/phy/mediatek/Kconfig | 12 +
drivers/phy/mediatek/Makefile | 1 +
drivers/phy/mediatek/phy-mtk-xfi-tphy.c | 392 ++++++++++++++++++++++++
4 files changed, 406 insertions(+)
create mode 100644 drivers/phy/mediatek/phy-mtk-xfi-tphy.c
--- a/drivers/phy/mediatek/Kconfig
+++ b/drivers/phy/mediatek/Kconfig
@@ -13,6 +13,18 @@ config PHY_MTK_PCIE
callback for PCIe GEN3 port, it supports software efuse
initialization.
+config PHY_MTK_XFI_TPHY
+ tristate "MediaTek XFI T-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF && OF_ADDRESS
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Say 'Y' here to add support for MediaTek XFI T-PHY driver.
+ The driver provides access to the Ethernet SerDes T-PHY supporting
+ 1GE and 2.5GE modes via the LynxI PCS, and 5GE and 10GE modes
+ via the USXGMII PCS found in MediaTek SoCs with 10G Ethernet.
+
config PHY_MTK_TPHY
tristate "MediaTek T-PHY Driver"
depends on ARCH_MEDIATEK || COMPILE_TEST
--- a/drivers/phy/mediatek/Makefile
+++ b/drivers/phy/mediatek/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_PHY_MTK_PCIE) += phy-mtk-p
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
+obj-$(CONFIG_PHY_MTK_XFI_TPHY) += phy-mtk-xfi-tphy.o
phy-mtk-hdmi-drv-y := phy-mtk-hdmi.o
phy-mtk-hdmi-drv-y += phy-mtk-hdmi-mt2701.o
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-xfi-tphy.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* MediaTek 10GE SerDes PHY driver
+ *
+ * Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
+ * Bc-bocun Chen <bc-bocun.chen@mediatek.com>
+ * based on mtk_usxgmii.c found in MediaTek's SDK released under GPL-2.0
+ * Copyright (c) 2022 MediaTek Inc.
+ * Author: Henry Yen <henry.yen@mediatek.com>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+
+#define MTK_XFI_TPHY_NUM_CLOCKS 2
+
+#define REG_DIG_GLB_70 0x0070
+#define XTP_PCS_RX_EQ_IN_PROGRESS(x) FIELD_PREP(GENMASK(25, 24), (x))
+#define XTP_PCS_MODE_MASK GENMASK(17, 16)
+#define XTP_PCS_MODE(x) FIELD_PREP(GENMASK(17, 16), (x))
+#define XTP_PCS_RST_B BIT(15)
+#define XTP_FRC_PCS_RST_B BIT(14)
+#define XTP_PCS_PWD_SYNC_MASK GENMASK(13, 12)
+#define XTP_PCS_PWD_SYNC(x) FIELD_PREP(XTP_PCS_PWD_SYNC_MASK, (x))
+#define XTP_PCS_PWD_ASYNC_MASK GENMASK(11, 10)
+#define XTP_PCS_PWD_ASYNC(x) FIELD_PREP(XTP_PCS_PWD_ASYNC_MASK, (x))
+#define XTP_FRC_PCS_PWD_ASYNC BIT(8)
+#define XTP_PCS_UPDT BIT(4)
+#define XTP_PCS_IN_FR_RG BIT(0)
+
+#define REG_DIG_GLB_F4 0x00f4
+#define XFI_DPHY_PCS_SEL BIT(0)
+#define XFI_DPHY_PCS_SEL_SGMII FIELD_PREP(XFI_DPHY_PCS_SEL, 1)
+#define XFI_DPHY_PCS_SEL_USXGMII FIELD_PREP(XFI_DPHY_PCS_SEL, 0)
+#define XFI_DPHY_AD_SGDT_FRC_EN BIT(5)
+
+#define REG_DIG_LN_TRX_40 0x3040
+#define XTP_LN_FRC_TX_DATA_EN BIT(29)
+#define XTP_LN_TX_DATA_EN BIT(28)
+
+#define REG_DIG_LN_TRX_B0 0x30b0
+#define XTP_LN_FRC_TX_MACCK_EN BIT(5)
+#define XTP_LN_TX_MACCK_EN BIT(4)
+
+#define REG_ANA_GLB_D0 0x90d0
+#define XTP_GLB_USXGMII_SEL_MASK GENMASK(3, 1)
+#define XTP_GLB_USXGMII_SEL(x) FIELD_PREP(GENMASK(3, 1), (x))
+#define XTP_GLB_USXGMII_EN BIT(0)
+
+struct mtk_xfi_tphy {
+ void __iomem *base;
+ struct device *dev;
+ struct reset_control *reset;
+ struct clk_bulk_data clocks[MTK_XFI_TPHY_NUM_CLOCKS];
+ bool da_war;
+};
+
+static void mtk_xfi_tphy_write(struct mtk_xfi_tphy *xfi_tphy, u16 reg,
+ u32 value)
+{
+ iowrite32(value, xfi_tphy->base + reg);
+}
+
+static void mtk_xfi_tphy_rmw(struct mtk_xfi_tphy *xfi_tphy, u16 reg,
+ u32 clr, u32 set)
+{
+ u32 val;
+
+ val = ioread32(xfi_tphy->base + reg);
+ val &= ~clr;
+ val |= set;
+ iowrite32(val, xfi_tphy->base + reg);
+}
+
+static void mtk_xfi_tphy_set(struct mtk_xfi_tphy *xfi_tphy, u16 reg,
+ u32 set)
+{
+ mtk_xfi_tphy_rmw(xfi_tphy, reg, 0, set);
+}
+
+static void mtk_xfi_tphy_clear(struct mtk_xfi_tphy *xfi_tphy, u16 reg,
+ u32 clr)
+{
+ mtk_xfi_tphy_rmw(xfi_tphy, reg, clr, 0);
+}
+
+static void mtk_xfi_tphy_setup(struct mtk_xfi_tphy *xfi_tphy,
+ phy_interface_t interface)
+{
+ bool is_2p5g = (interface == PHY_INTERFACE_MODE_2500BASEX);
+ bool is_1g = (interface == PHY_INTERFACE_MODE_1000BASEX ||
+ interface == PHY_INTERFACE_MODE_SGMII);
+ bool is_10g = (interface == PHY_INTERFACE_MODE_10GBASER ||
+ interface == PHY_INTERFACE_MODE_USXGMII);
+ bool is_5g = (interface == PHY_INTERFACE_MODE_5GBASER);
+ bool is_xgmii = (is_10g || is_5g);
+
+ dev_dbg(xfi_tphy->dev, "setting up for mode %s\n", phy_modes(interface));
+
+ /* Setup PLL setting */
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x9024, 0x100000, is_10g ? 0x0 : 0x100000);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x2020, 0x202000, is_5g ? 0x202000 : 0x0);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x2030, 0x500, is_1g ? 0x0 : 0x500);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x2034, 0xa00, is_1g ? 0x0 : 0xa00);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x2040, 0x340000, is_1g ? 0x200000 :
+ 0x140000);
+
+ /* Setup RXFE BW setting */
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x50f0, 0xc10, is_1g ? 0x410 :
+ is_5g ? 0x800 : 0x400);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x50e0, 0x4000, is_5g ? 0x0 : 0x4000);
+
+ /* Setup RX CDR setting */
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x506c, 0x30000, is_5g ? 0x0 : 0x30000);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x5070, 0x670000, is_5g ? 0x620000 : 0x50000);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x5074, 0x180000, is_5g ? 0x180000 : 0x0);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x5078, 0xf000400, is_5g ? 0x8000000 :
+ 0x7000400);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x507c, 0x5000500, is_5g ? 0x4000400 :
+ 0x1000100);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x5080, 0x1410, is_1g ? 0x400 :
+ is_5g ? 0x1010 : 0x0);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x5084, 0x30300, is_1g ? 0x30300 :
+ is_5g ? 0x30100 :
+ 0x100);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x5088, 0x60200, is_1g ? 0x20200 :
+ is_5g ? 0x40000 :
+ 0x20000);
+
+ /* Setting RXFE adaptation range setting */
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x50e4, 0xc0000, is_5g ? 0x0 : 0xc0000);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x50e8, 0x40000, is_5g ? 0x0 : 0x40000);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x50ec, 0xa00, is_1g ? 0x200 : 0x800);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x50a8, 0xee0000, is_5g ? 0x800000 :
+ 0x6e0000);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x6004, 0x190000, is_5g ? 0x0 : 0x190000);
+ if (is_10g)
+ mtk_xfi_tphy_write(xfi_tphy, 0x00f8, 0x01423342);
+ else if (is_5g)
+ mtk_xfi_tphy_write(xfi_tphy, 0x00f8, 0x00a132a1);
+ else if (is_2p5g)
+ mtk_xfi_tphy_write(xfi_tphy, 0x00f8, 0x009c329c);
+ else
+ mtk_xfi_tphy_write(xfi_tphy, 0x00f8, 0x00fa32fa);
+
+ /* Force SGDT_OUT off and select PCS */
+ mtk_xfi_tphy_rmw(xfi_tphy, REG_DIG_GLB_F4,
+ XFI_DPHY_AD_SGDT_FRC_EN | XFI_DPHY_PCS_SEL,
+ XFI_DPHY_AD_SGDT_FRC_EN |
+ (is_xgmii ? XFI_DPHY_PCS_SEL_USXGMII :
+ XFI_DPHY_PCS_SEL_SGMII));
+
+
+ /* Force GLB_CKDET_OUT */
+ mtk_xfi_tphy_set(xfi_tphy, 0x0030, 0xc00);
+
+ /* Force AEQ on */
+ mtk_xfi_tphy_write(xfi_tphy, REG_DIG_GLB_70,
+ XTP_PCS_RX_EQ_IN_PROGRESS(2) |
+ XTP_PCS_PWD_SYNC(2) |
+ XTP_PCS_PWD_ASYNC(2));
+
+ usleep_range(1, 5);
+
+ /* Setup TX DA default value */
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x30b0, 0x30, 0x20);
+ mtk_xfi_tphy_write(xfi_tphy, 0x3028, 0x00008a01);
+ mtk_xfi_tphy_write(xfi_tphy, 0x302c, 0x0000a884);
+ mtk_xfi_tphy_write(xfi_tphy, 0x3024, 0x00083002);
+
+ /* Setup RG default value */
+ if (is_xgmii) {
+ mtk_xfi_tphy_write(xfi_tphy, 0x3010, 0x00022220);
+ mtk_xfi_tphy_write(xfi_tphy, 0x5064, 0x0f020a01);
+ mtk_xfi_tphy_write(xfi_tphy, 0x50b4, 0x06100600);
+ if (interface == PHY_INTERFACE_MODE_USXGMII)
+ mtk_xfi_tphy_write(xfi_tphy, 0x3048, 0x40704000);
+ else
+ mtk_xfi_tphy_write(xfi_tphy, 0x3048, 0x47684100);
+ } else {
+ mtk_xfi_tphy_write(xfi_tphy, 0x3010, 0x00011110);
+ mtk_xfi_tphy_write(xfi_tphy, 0x3048, 0x40704000);
+ }
+
+ if (is_1g)
+ mtk_xfi_tphy_write(xfi_tphy, 0x3064, 0x0000c000);
+
+ /* Setup RX EQ initial value */
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x3050, 0xa8000000,
+ (interface != PHY_INTERFACE_MODE_10GBASER) ?
+ 0xa8000000 : 0x0);
+ mtk_xfi_tphy_rmw(xfi_tphy, 0x3054, 0xaa,
+ (interface != PHY_INTERFACE_MODE_10GBASER) ?
+ 0xaa : 0x0);
+
+ if (is_xgmii)
+ mtk_xfi_tphy_write(xfi_tphy, 0x306c, 0x00000f00);
+ else if (is_2p5g)
+ mtk_xfi_tphy_write(xfi_tphy, 0x306c, 0x22000f00);
+ else
+ mtk_xfi_tphy_write(xfi_tphy, 0x306c, 0x20200f00);
+
+ if (interface == PHY_INTERFACE_MODE_10GBASER && xfi_tphy->da_war)
+ mtk_xfi_tphy_rmw(xfi_tphy, 0xa008, 0x10000, 0x10000);
+
+ mtk_xfi_tphy_rmw(xfi_tphy, 0xa060, 0x50000, is_xgmii ? 0x40000 :
+ 0x50000);
+
+ /* Setup PHYA speed */
+ mtk_xfi_tphy_rmw(xfi_tphy, REG_ANA_GLB_D0,
+ XTP_GLB_USXGMII_SEL_MASK | XTP_GLB_USXGMII_EN,
+ is_10g ? XTP_GLB_USXGMII_SEL(0) :
+ is_5g ? XTP_GLB_USXGMII_SEL(1) :
+ is_2p5g ? XTP_GLB_USXGMII_SEL(2) :
+ XTP_GLB_USXGMII_SEL(3));
+ mtk_xfi_tphy_set(xfi_tphy, REG_ANA_GLB_D0, XTP_GLB_USXGMII_EN);
+
+ /* Release reset */
+ mtk_xfi_tphy_set(xfi_tphy, REG_DIG_GLB_70,
+ XTP_PCS_RST_B | XTP_FRC_PCS_RST_B);
+ usleep_range(150, 500);
+
+ /* Switch to P0 */
+ mtk_xfi_tphy_rmw(xfi_tphy, REG_DIG_GLB_70,
+ XTP_PCS_PWD_SYNC_MASK |
+ XTP_PCS_PWD_ASYNC_MASK,
+ XTP_FRC_PCS_PWD_ASYNC |
+ XTP_PCS_UPDT | XTP_PCS_IN_FR_RG);
+ usleep_range(1, 5);
+
+ mtk_xfi_tphy_clear(xfi_tphy, REG_DIG_GLB_70, XTP_PCS_UPDT);
+ usleep_range(15, 50);
+
+ if (is_xgmii) {
+ /* Switch to Gen3 */
+ mtk_xfi_tphy_rmw(xfi_tphy, REG_DIG_GLB_70,
+ XTP_PCS_MODE_MASK | XTP_PCS_UPDT,
+ XTP_PCS_MODE(2) | XTP_PCS_UPDT);
+ } else {
+ /* Switch to Gen2 */
+ mtk_xfi_tphy_rmw(xfi_tphy, REG_DIG_GLB_70,
+ XTP_PCS_MODE_MASK | XTP_PCS_UPDT,
+ XTP_PCS_MODE(1) | XTP_PCS_UPDT);
+ }
+ usleep_range(1, 5);
+
+ mtk_xfi_tphy_clear(xfi_tphy, REG_DIG_GLB_70, XTP_PCS_UPDT);
+
+ usleep_range(100, 500);
+
+ /* Enable MAC CK */
+ mtk_xfi_tphy_set(xfi_tphy, REG_DIG_LN_TRX_B0, XTP_LN_TX_MACCK_EN);
+ mtk_xfi_tphy_clear(xfi_tphy, REG_DIG_GLB_F4, XFI_DPHY_AD_SGDT_FRC_EN);
+
+ /* Enable TX data */
+ mtk_xfi_tphy_set(xfi_tphy, REG_DIG_LN_TRX_40,
+ XTP_LN_FRC_TX_DATA_EN | XTP_LN_TX_DATA_EN);
+ usleep_range(400, 1000);
+}
+
+static int mtk_xfi_tphy_set_mode(struct phy *phy, enum phy_mode mode, int
+ submode)
+{
+ struct mtk_xfi_tphy *xfi_tphy = phy_get_drvdata(phy);
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EINVAL;
+
+ switch (submode) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_5GBASER:
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_USXGMII:
+ mtk_xfi_tphy_setup(xfi_tphy, submode);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mtk_xfi_tphy_reset(struct phy *phy)
+{
+ struct mtk_xfi_tphy *xfi_tphy = phy_get_drvdata(phy);
+
+ reset_control_assert(xfi_tphy->reset);
+ usleep_range(100, 500);
+ reset_control_deassert(xfi_tphy->reset);
+ usleep_range(1, 10);
+
+ return 0;
+}
+
+static int mtk_xfi_tphy_power_on(struct phy *phy)
+{
+ struct mtk_xfi_tphy *xfi_tphy = phy_get_drvdata(phy);
+
+ return clk_bulk_prepare_enable(MTK_XFI_TPHY_NUM_CLOCKS, xfi_tphy->clocks);
+}
+
+static int mtk_xfi_tphy_power_off(struct phy *phy)
+{
+ struct mtk_xfi_tphy *xfi_tphy = phy_get_drvdata(phy);
+
+ clk_bulk_disable_unprepare(MTK_XFI_TPHY_NUM_CLOCKS, xfi_tphy->clocks);
+
+ return 0;
+}
+
+static const struct phy_ops mtk_xfi_tphy_ops = {
+ .power_on = mtk_xfi_tphy_power_on,
+ .power_off = mtk_xfi_tphy_power_off,
+ .set_mode = mtk_xfi_tphy_set_mode,
+ .reset = mtk_xfi_tphy_reset,
+ .owner = THIS_MODULE,
+};
+
+static int mtk_xfi_tphy_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct phy_provider *phy_provider;
+ struct mtk_xfi_tphy *xfi_tphy;
+ struct phy *phy;
+
+ if (!np)
+ return -ENODEV;
+
+ xfi_tphy = devm_kzalloc(&pdev->dev, sizeof(*xfi_tphy), GFP_KERNEL);
+ if (!xfi_tphy)
+ return -ENOMEM;
+
+ xfi_tphy->base = devm_of_iomap(&pdev->dev, np, 0, NULL);
+ if (!xfi_tphy->base)
+ return -EIO;
+
+ xfi_tphy->dev = &pdev->dev;
+
+ xfi_tphy->clocks[0].id = "topxtal";
+ xfi_tphy->clocks[0].clk = devm_clk_get(&pdev->dev, xfi_tphy->clocks[0].id);
+ if (IS_ERR(xfi_tphy->clocks[0].clk))
+ return PTR_ERR(xfi_tphy->clocks[0].clk);
+
+ xfi_tphy->clocks[1].id = "xfipll";
+ xfi_tphy->clocks[1].clk = devm_clk_get(&pdev->dev, xfi_tphy->clocks[1].id);
+ if (IS_ERR(xfi_tphy->clocks[1].clk))
+ return PTR_ERR(xfi_tphy->clocks[1].clk);
+
+ xfi_tphy->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(xfi_tphy->reset))
+ return PTR_ERR(xfi_tphy->reset);
+
+ xfi_tphy->da_war = of_property_read_bool(np,
+ "mediatek,usxgmii-performance-errata");
+
+ phy = devm_phy_create(&pdev->dev, NULL, &mtk_xfi_tphy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, xfi_tphy);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev,
+ of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id mtk_xfi_tphy_match[] = {
+ { .compatible = "mediatek,mt7988-xfi-tphy", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mtk_xfi_tphy_match);
+
+static struct platform_driver mtk_xfi_tphy_driver = {
+ .probe = mtk_xfi_tphy_probe,
+ .driver = {
+ .name = "mtk-xfi-tphy",
+ .of_match_table = mtk_xfi_tphy_match,
+ },
+};
+module_platform_driver(mtk_xfi_tphy_driver);
+
+MODULE_DESCRIPTION("MediaTek XFI T-PHY driver");
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+MODULE_AUTHOR("Bc-bocun Chen <bc-bocun.chen@mediatek.com>");
+MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,371 @@
From 4b1a2716299c0e96a698044aebf3f80513509ae7 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 12 Dec 2023 03:47:18 +0000
Subject: [PATCH 3/5] net: pcs: pcs-mtk-lynxi: add platform driver for MT7988
Introduce a proper platform MFD driver for the LynxI (H)SGMII PCS which
is going to initially be used for the MT7988 SoC.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/pcs/pcs-mtk-lynxi.c | 227 ++++++++++++++++++++++++++++--
include/linux/pcs/pcs-mtk-lynxi.h | 11 ++
2 files changed, 227 insertions(+), 11 deletions(-)
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018-2019 MediaTek Inc.
-/* A library for MediaTek SGMII circuit
+/* A library and platform driver for the MediaTek LynxI SGMII circuit
*
* Author: Sean Wang <sean.wang@mediatek.com>
* Author: Alexander Couzens <lynxis@fe80.eu>
@@ -8,11 +8,17 @@
*
*/
+#include <linux/clk.h>
#include <linux/mdio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
#include <linux/phylink.h>
+#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
/* SGMII subsystem config registers */
/* BMCR (low 16) BMSR (high 16) */
@@ -65,6 +71,8 @@
#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
+#define MTK_NETSYS_V3_AMA_RGC3 0x128
+
/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
* data
* @regmap: The register map pointing at the range used to setup
@@ -74,15 +82,29 @@
* @interface: Currently configured interface mode
* @pcs: Phylink PCS structure
* @flags: Flags indicating hardware properties
+ * @rstc: Reset controller
+ * @sgmii_sel: SGMII Register Clock
+ * @sgmii_rx: SGMII RX Clock
+ * @sgmii_tx: SGMII TX Clock
+ * @node: List node
*/
struct mtk_pcs_lynxi {
struct regmap *regmap;
+ struct device *dev;
u32 ana_rgc3;
phy_interface_t interface;
struct phylink_pcs pcs;
u32 flags;
+ struct reset_control *rstc;
+ struct clk *sgmii_sel;
+ struct clk *sgmii_rx;
+ struct clk *sgmii_tx;
+ struct list_head node;
};
+static LIST_HEAD(mtk_pcs_lynxi_instances);
+static DEFINE_MUTEX(instance_mutex);
+
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mtk_pcs_lynxi, pcs);
@@ -102,6 +124,17 @@ static void mtk_pcs_lynxi_get_state(stru
FIELD_GET(SGMII_LPA, adv));
}
+static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
+{
+ if (!mpcs->rstc)
+ return;
+
+ reset_control_assert(mpcs->rstc);
+ udelay(100);
+ reset_control_deassert(mpcs->rstc);
+ mdelay(1);
+}
+
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
@@ -148,6 +181,7 @@ static int mtk_pcs_lynxi_config(struct p
SGMII_PHYA_PWD);
/* Reset SGMII PCS state */
+ mtk_sgmii_reset(mpcs);
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
@@ -234,10 +268,29 @@ static void mtk_pcs_lynxi_link_up(struct
}
}
+static int mtk_pcs_lynxi_enable(struct phylink_pcs *pcs)
+{
+ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+
+ if (mpcs->sgmii_tx && mpcs->sgmii_rx) {
+ clk_prepare_enable(mpcs->sgmii_rx);
+ clk_prepare_enable(mpcs->sgmii_tx);
+ }
+
+ return 0;
+}
+
static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+ regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, SGMII_PHYA_PWD);
+
+ if (mpcs->sgmii_tx && mpcs->sgmii_rx) {
+ clk_disable_unprepare(mpcs->sgmii_tx);
+ clk_disable_unprepare(mpcs->sgmii_rx);
+ }
+
mpcs->interface = PHY_INTERFACE_MODE_NA;
}
@@ -247,11 +300,12 @@ static const struct phylink_pcs_ops mtk_
.pcs_an_restart = mtk_pcs_lynxi_restart_an,
.pcs_link_up = mtk_pcs_lynxi_link_up,
.pcs_disable = mtk_pcs_lynxi_disable,
+ .pcs_enable = mtk_pcs_lynxi_enable,
};
-struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
- struct regmap *regmap, u32 ana_rgc3,
- u32 flags)
+static struct phylink_pcs *mtk_pcs_lynxi_init(struct device *dev, struct regmap *regmap,
+ u32 ana_rgc3, u32 flags,
+ struct mtk_pcs_lynxi *prealloc)
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
@@ -259,29 +313,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create
ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
if (ret < 0)
- return NULL;
+ return ERR_PTR(ret);
if (id != SGMII_LYNXI_DEV_ID) {
dev_err(dev, "unknown PCS device id %08x\n", id);
- return NULL;
+ return ERR_PTR(-ENODEV);
}
ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
if (ret < 0)
- return NULL;
+ return ERR_PTR(ret);
ver = FIELD_GET(SGMII_DEV_VERSION, ver);
if (ver != 0x1) {
dev_err(dev, "unknown PCS device version %04x\n", ver);
- return NULL;
+ return ERR_PTR(-ENODEV);
}
dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
ver);
- mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
- if (!mpcs)
- return NULL;
+ if (prealloc) {
+ mpcs = prealloc;
+ } else {
+ mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
+ if (!mpcs)
+ return ERR_PTR(-ENOMEM);
+ };
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
@@ -292,6 +350,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create
mpcs->interface = PHY_INTERFACE_MODE_NA;
return &mpcs->pcs;
+};
+
+struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
+ struct regmap *regmap, u32 ana_rgc3,
+ u32 flags)
+{
+ return mtk_pcs_lynxi_init(dev, regmap, ana_rgc3, flags, NULL);
}
EXPORT_SYMBOL(mtk_pcs_lynxi_create);
@@ -304,4 +369,144 @@ void mtk_pcs_lynxi_destroy(struct phylin
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
+static int mtk_pcs_lynxi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct mtk_pcs_lynxi *mpcs;
+ struct phylink_pcs *pcs;
+ struct regmap *regmap;
+ u32 flags = 0;
+
+ mpcs = devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL);
+ if (!mpcs)
+ return -ENOMEM;
+
+ mpcs->dev = dev;
+ regmap = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ if (of_property_read_bool(np->parent, "mediatek,pnswap"))
+ flags |= MTK_SGMII_FLAG_PN_SWAP;
+
+ mpcs->rstc = of_reset_control_get_shared(np->parent, NULL);
+ if (IS_ERR(mpcs->rstc))
+ return PTR_ERR(mpcs->rstc);
+
+ reset_control_deassert(mpcs->rstc);
+ mpcs->sgmii_sel = devm_clk_get_enabled(dev, "sgmii_sel");
+ if (IS_ERR(mpcs->sgmii_sel))
+ return PTR_ERR(mpcs->sgmii_sel);
+
+ mpcs->sgmii_rx = devm_clk_get(dev, "sgmii_rx");
+ if (IS_ERR(mpcs->sgmii_rx))
+ return PTR_ERR(mpcs->sgmii_rx);
+
+ mpcs->sgmii_tx = devm_clk_get(dev, "sgmii_tx");
+ if (IS_ERR(mpcs->sgmii_tx))
+ return PTR_ERR(mpcs->sgmii_tx);
+
+ pcs = mtk_pcs_lynxi_init(dev, regmap, (uintptr_t)of_device_get_match_data(dev),
+ flags, mpcs);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+
+ regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, SGMII_PHYA_PWD);
+
+ platform_set_drvdata(pdev, mpcs);
+
+ mutex_lock(&instance_mutex);
+ list_add_tail(&mpcs->node, &mtk_pcs_lynxi_instances);
+ mutex_unlock(&instance_mutex);
+
+ return 0;
+}
+
+static int mtk_pcs_lynxi_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_pcs_lynxi *cur, *tmp;
+
+ mutex_lock(&instance_mutex);
+ list_for_each_entry_safe(cur, tmp, &mtk_pcs_lynxi_instances, node)
+ if (cur->dev == dev) {
+ list_del(&cur->node);
+ kfree(cur);
+ break;
+ }
+ mutex_unlock(&instance_mutex);
+
+ return 0;
+}
+
+static const struct of_device_id mtk_pcs_lynxi_of_match[] = {
+ { .compatible = "mediatek,mt7988-sgmii", .data = (void *)MTK_NETSYS_V3_AMA_RGC3 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mtk_pcs_lynxi_of_match);
+
+struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_node *np)
+{
+ struct platform_device *pdev;
+ struct mtk_pcs_lynxi *mpcs;
+
+ if (!np)
+ return NULL;
+
+ if (!of_device_is_available(np))
+ return ERR_PTR(-ENODEV);
+
+ if (!of_match_node(mtk_pcs_lynxi_of_match, np))
+ return ERR_PTR(-EINVAL);
+
+ pdev = of_find_device_by_node(np);
+ if (!pdev || !platform_get_drvdata(pdev)) {
+ if (pdev)
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ mpcs = platform_get_drvdata(pdev);
+ device_link_add(dev, mpcs->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+
+ return &mpcs->pcs;
+}
+EXPORT_SYMBOL(mtk_pcs_lynxi_get);
+
+void mtk_pcs_lynxi_put(struct phylink_pcs *pcs)
+{
+ struct mtk_pcs_lynxi *cur, *mpcs = NULL;
+
+ if (!pcs)
+ return;
+
+ mutex_lock(&instance_mutex);
+ list_for_each_entry(cur, &mtk_pcs_lynxi_instances, node)
+ if (pcs == &cur->pcs) {
+ mpcs = cur;
+ break;
+ }
+ mutex_unlock(&instance_mutex);
+
+ if (WARN_ON(!mpcs))
+ return;
+
+ put_device(mpcs->dev);
+}
+EXPORT_SYMBOL(mtk_pcs_lynxi_put);
+
+static struct platform_driver mtk_pcs_lynxi_driver = {
+ .driver = {
+ .name = "mtk-pcs-lynxi",
+ .suppress_bind_attrs = true,
+ .of_match_table = mtk_pcs_lynxi_of_match,
+ },
+ .probe = mtk_pcs_lynxi_probe,
+ .remove = mtk_pcs_lynxi_remove,
+};
+module_platform_driver(mtk_pcs_lynxi_driver);
+
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+MODULE_DESCRIPTION("MediaTek LynxI HSGMII PCS");
MODULE_LICENSE("GPL");
--- a/include/linux/pcs/pcs-mtk-lynxi.h
+++ b/include/linux/pcs/pcs-mtk-lynxi.h
@@ -10,4 +10,15 @@ struct phylink_pcs *mtk_pcs_lynxi_create
struct regmap *regmap,
u32 ana_rgc3, u32 flags);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);
+
+#if IS_ENABLED(CONFIG_PCS_MTK_LYNXI)
+struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_node *np);
+void mtk_pcs_lynxi_put(struct phylink_pcs *pcs);
+#else
+static inline struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_node *np)
+{
+ return NULL;
+}
+static inline void mtk_pcs_lynxi_put(struct phylink_pcs *pcs) { }
+#endif /* IS_ENABLED(CONFIG_PCS_MTK_LYNXI) */
#endif

View File

@@ -0,0 +1,81 @@
From 7d88d79c0f65b27a92754d7547f7af098b3de67b Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 12 Dec 2023 03:47:31 +0000
Subject: [PATCH 4/5] dt-bindings: net: pcs: add bindings for MediaTek USXGMII
PCS
MediaTek's USXGMII can be found in the MT7988 SoC. We need to access
it in order to configure and monitor the Ethernet SerDes link in
USXGMII, 10GBase-R and 5GBase-R mode. By including a wrapped
legacy 1000Base-X/2500Base-X/Cisco SGMII LynxI PCS as well, those
interface modes are also available.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
.../bindings/net/pcs/mediatek,usxgmii.yaml | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/pcs/mediatek,usxgmii.yaml
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pcs/mediatek,usxgmii.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/pcs/mediatek,usxgmii.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek USXGMII PCS
+
+maintainers:
+ - Daniel Golle <daniel@makrotopia.org>
+
+description:
+ The MediaTek USXGMII PCS provides physical link control and status
+ for USXGMII, 10GBase-R and 5GBase-R links on the SerDes interfaces
+ provided by the PEXTP PHY.
+ In order to also support legacy 2500Base-X, 1000Base-X and Cisco
+ SGMII an existing mediatek,*-sgmiisys LynxI PCS is wrapped to
+ provide those interfaces modes on the same SerDes interfaces shared
+ with the USXGMII PCS.
+
+properties:
+ $nodename:
+ pattern: "^pcs@[0-9a-f]+$"
+
+ compatible:
+ const: mediatek,mt7988-usxgmiisys
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: USXGMII top-level clock
+
+ resets:
+ items:
+ - description: XFI reset
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - resets
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/mediatek,mt7988-clk.h>
+ #define MT7988_TOPRGU_XFI0_GRST 12
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ usxgmiisys0: pcs@10080000 {
+ compatible = "mediatek,mt7988-usxgmiisys";
+ reg = <0 0x10080000 0 0x1000>;
+ clocks = <&topckgen CLK_TOP_USXGMII_SBUS_0_SEL>;
+ resets = <&watchdog MT7988_TOPRGU_XFI0_GRST>;
+ };
+ };

View File

@@ -0,0 +1,547 @@
From dde0e95fff92e9f5009f3bea75278e0e34a48822 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 12 Dec 2023 03:47:47 +0000
Subject: [PATCH 5/5] net: pcs: add driver for MediaTek USXGMII PCS
Add driver for USXGMII PCS found in the MediaTek MT7988 SoC and supporting
USXGMII, 10GBase-R and 5GBase-R interface modes.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
MAINTAINERS | 2 +
drivers/net/pcs/Kconfig | 11 +
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs-mtk-usxgmii.c | 456 ++++++++++++++++++++++++++++
include/linux/pcs/pcs-mtk-usxgmii.h | 27 ++
5 files changed, 497 insertions(+)
create mode 100644 drivers/net/pcs/pcs-mtk-usxgmii.c
create mode 100644 include/linux/pcs/pcs-mtk-usxgmii.h
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12934,7 +12934,9 @@ M: Daniel Golle <daniel@makrotopia.org>
L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/pcs/pcs-mtk-lynxi.c
+F: drivers/net/pcs/pcs-mtk-usxgmii.c
F: include/linux/pcs/pcs-mtk-lynxi.h
+F: include/linux/pcs/pcs-mtk-usxgmii.h
MEDIATEK I2C CONTROLLER DRIVER
M: Qii Wang <qii.wang@mediatek.com>
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -18,6 +18,17 @@ config PCS_LYNX
This module provides helpers to phylink for managing the Lynx PCS
which is part of the Layerscape and QorIQ Ethernet SERDES.
+config PCS_MTK_USXGMII
+ tristate "MediaTek USXGMII PCS"
+ select PCS_MTK_LYNXI
+ select PHY_MTK_PEXTP
+ select PHYLINK
+ help
+ This module provides a driver for MediaTek's USXGMII PCS supporting
+ 10GBase-R, 5GBase-R and USXGMII interface modes.
+ 1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
+ differential pairs via an embedded LynxI PHY.
+
config PCS_RZN1_MIIC
tristate "Renesas RZ/N1 MII converter"
depends on OF && (ARCH_RZN1 || COMPILE_TEST)
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
obj-$(CONFIG_PCS_ALTERA_TSE) += pcs-altera-tse.o
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
+obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
--- /dev/null
+++ b/drivers/net/pcs/pcs-mtk-usxgmii.c
@@ -0,0 +1,456 @@
+// 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-mtk-usxgmii.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
+ * @interface: Currently selected interface mode
+ * @neg_mode: Currently used phylink neg_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;
+ phy_interface_t interface;
+ unsigned int neg_mode;
+ struct list_head node;
+};
+
+static LIST_HEAD(mtk_usxgmii_pcs_instances);
+static DEFINE_MUTEX(instance_mutex);
+
+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;
+ }
+
+ 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);
+
+ mpcs->neg_mode = neg_mode;
+
+ /* 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);
+
+ 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,
+ 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));
+
+ /* Continuously repeat re-configuration sequence until link comes up */
+ if (!state->link) {
+ mtk_usxgmii_pcs_config(pcs, mpcs->neg_mode,
+ state->interface, NULL, false);
+ 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 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;
+ mpcs->neg_mode = -1;
+}
+
+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_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->pcs.neg_mode = true;
+ mpcs->interface = PHY_INTERFACE_MODE_NA;
+ mpcs->neg_mode = -1;
+
+ mpcs->clk = devm_clk_get_enabled(mpcs->dev, NULL);
+ if (IS_ERR(mpcs->clk))
+ return PTR_ERR(mpcs->clk);
+
+ 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);
+
+ mutex_lock(&instance_mutex);
+ list_add_tail(&mpcs->node, &mtk_usxgmii_pcs_instances);
+ mutex_unlock(&instance_mutex);
+
+ return 0;
+}
+
+static int mtk_usxgmii_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_usxgmii_pcs *cur, *tmp;
+
+ mutex_lock(&instance_mutex);
+ list_for_each_entry_safe(cur, tmp, &mtk_usxgmii_pcs_instances, node)
+ if (cur->dev == dev) {
+ list_del(&cur->node);
+ break;
+ }
+ mutex_unlock(&instance_mutex);
+
+ return 0;
+}
+
+static const struct of_device_id mtk_usxgmii_of_mtable[] = {
+ { .compatible = "mediatek,mt7988-usxgmiisys" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mtk_usxgmii_of_mtable);
+
+struct phylink_pcs *mtk_usxgmii_pcs_get(struct device *dev, struct device_node *np)
+{
+ struct platform_device *pdev;
+ struct mtk_usxgmii_pcs *mpcs;
+
+ if (!np)
+ return NULL;
+
+ if (!of_device_is_available(np))
+ return ERR_PTR(-ENODEV);
+
+ if (!of_match_node(mtk_usxgmii_of_mtable, np))
+ return ERR_PTR(-EINVAL);
+
+ pdev = of_find_device_by_node(np);
+ if (!pdev || !platform_get_drvdata(pdev)) {
+ if (pdev)
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ mpcs = platform_get_drvdata(pdev);
+ device_link_add(dev, mpcs->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+
+ return &mpcs->pcs;
+}
+EXPORT_SYMBOL(mtk_usxgmii_pcs_get);
+
+void mtk_usxgmii_pcs_put(struct phylink_pcs *pcs)
+{
+ struct mtk_usxgmii_pcs *cur, *mpcs = NULL;
+
+ if (!pcs)
+ return;
+
+ mutex_lock(&instance_mutex);
+ list_for_each_entry(cur, &mtk_usxgmii_pcs_instances, node)
+ if (pcs == &cur->pcs) {
+ mpcs = cur;
+ break;
+ }
+ mutex_unlock(&instance_mutex);
+
+ if (WARN_ON(!mpcs))
+ return;
+
+ put_device(mpcs->dev);
+}
+EXPORT_SYMBOL(mtk_usxgmii_pcs_put);
+
+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,
+ .remove = mtk_usxgmii_remove,
+};
+module_platform_driver(mtk_usxgmii_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek USXGMII PCS driver");
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
--- /dev/null
+++ b/include/linux/pcs/pcs-mtk-usxgmii.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LINUX_PCS_MTK_USXGMII_H
+#define __LINUX_PCS_MTK_USXGMII_H
+
+#include <linux/phylink.h>
+
+/**
+ * mtk_usxgmii_select_pcs() - Get MediaTek PCS instance
+ * @np: Pointer to device node indentifying a MediaTek USXGMII PCS
+ * @mode: Ethernet PHY interface mode
+ *
+ * Return PCS identified by a device node and the PHY interface mode in use
+ *
+ * Return: Pointer to phylink PCS instance of NULL
+ */
+#if IS_ENABLED(CONFIG_PCS_MTK_USXGMII)
+struct phylink_pcs *mtk_usxgmii_pcs_get(struct device *dev, struct device_node *np);
+void mtk_usxgmii_pcs_put(struct phylink_pcs *pcs);
+#else
+static inline struct phylink_pcs *mtk_usxgmii_pcs_get(struct device *dev, struct device_node *np)
+{
+ return NULL;
+}
+static inline void mtk_usxgmii_pcs_put(struct phylink_pcs *pcs) { }
+#endif /* IS_ENABLED(CONFIG_PCS_MTK_USXGMII) */
+
+#endif /* __LINUX_PCS_MTK_USXGMII_H */