mirror of
https://github.com/openwrt/openwrt.git
synced 2026-06-17 12:40:16 +04:00
c271123724
LINK_INBAND_ENABLE isn't valid for 5GBase-R/10GBase-R modes which by definition don't support any in-band an. Correctly report LINK_INBAND_DISABLE to fix 10G fiber SFP modules no longer working. While at it also get rid of downstream pn-swap properties in favor of using the upstream schema. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
613 lines
18 KiB
Diff
613 lines
18 KiB
Diff
From e9d2999f5d9d8e1b895350c569e930918b41ce92 Mon Sep 17 00:00:00 2001
|
|
From: Daniel Golle <daniel@makrotopia.org>
|
|
Date: Tue, 12 Dec 2023 03:47:47 +0000
|
|
Subject: [PATCH 2/2] 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 | 13 +
|
|
drivers/net/pcs/Kconfig.orig | 55 ++++
|
|
drivers/net/pcs/Makefile | 1 +
|
|
drivers/net/pcs/pcs-mtk-usxgmii.c | 490 ++++++++++++++++++++++++++++++
|
|
5 files changed, 561 insertions(+)
|
|
create mode 100644 drivers/net/pcs/Kconfig.orig
|
|
create mode 100644 drivers/net/pcs/pcs-mtk-usxgmii.c
|
|
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -15790,7 +15790,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 ETHERNET PHY DRIVERS
|
|
M: Daniel Golle <daniel@makrotopia.org>
|
|
--- a/drivers/net/pcs/Kconfig
|
|
+++ b/drivers/net/pcs/Kconfig
|
|
@@ -33,6 +33,19 @@ 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 FWNODE_PCS
|
|
+ select PCS_MTK_LYNXI
|
|
+ select PHY_COMMON_PROPS
|
|
+ select PHYLINK
|
|
+ imply PHY_MTK_PEXTP
|
|
+ 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 PCS.
|
|
+
|
|
config PCS_RZN1_MIIC
|
|
tristate "Renesas RZ/N1, RZ/N2H, RZ/T2H MII converter"
|
|
depends on OF
|
|
--- /dev/null
|
|
+++ b/drivers/net/pcs/Kconfig.orig
|
|
@@ -0,0 +1,55 @@
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
+#
|
|
+# PCS Layer Configuration
|
|
+#
|
|
+
|
|
+menu "PCS device drivers"
|
|
+
|
|
+config OF_PCS
|
|
+ tristate
|
|
+ depends on OF
|
|
+ depends on PHYLINK
|
|
+ help
|
|
+ OpenFirmware PCS accessors
|
|
+
|
|
+config PCS_XPCS
|
|
+ tristate "Synopsys DesignWare Ethernet XPCS"
|
|
+ select PHYLINK
|
|
+ help
|
|
+ This module provides a driver and helper functions for Synopsys
|
|
+ DesignWare XPCS controllers.
|
|
+
|
|
+config PCS_LYNX
|
|
+ tristate
|
|
+ help
|
|
+ This module provides helpers to phylink for managing the Lynx PCS
|
|
+ which is part of the Layerscape and QorIQ Ethernet SERDES.
|
|
+
|
|
+config PCS_MTK_LYNXI
|
|
+ tristate
|
|
+ select REGMAP
|
|
+ help
|
|
+ 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 OF_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)
|
|
+ help
|
|
+ This module provides a driver for the MII converter that is available
|
|
+ on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
|
|
+ pass-through mode for MII.
|
|
+
|
|
+endmenu
|
|
--- a/drivers/net/pcs/Makefile
|
|
+++ b/drivers/net/pcs/Makefile
|
|
@@ -8,4 +8,5 @@ pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.
|
|
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
|
|
--- /dev/null
|
|
+++ b/drivers/net/pcs/pcs-mtk-usxgmii.c
|
|
@@ -0,0 +1,490 @@
|
|
+// 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-provider.h>
|
|
+#include <linux/phy/phy-common-props.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/phylink.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/rtnetlink.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 control SerDes lane polarity */
|
|
+#define RG_PHY_TOP_CTRL0 0x82c
|
|
+#define USXGMII_PN_SWAP_MASK GENMASK(1, 0)
|
|
+#define USXGMII_PN_SWAP_RX BIT(1)
|
|
+#define USXGMII_PN_SWAP_TX BIT(0)
|
|
+
|
|
+/* 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
|
|
+ * @fwnode: Firmware node of the PCS, used to look up
|
|
+ * rx-polarity / tx-polarity properties
|
|
+ * @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;
|
|
+ struct phy *xfi_tphy;
|
|
+ struct fwnode_handle *fwnode;
|
|
+ phy_interface_t interface;
|
|
+ unsigned int neg_mode;
|
|
+ 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_config_polarity(struct mtk_usxgmii_pcs *mpcs,
|
|
+ phy_interface_t interface)
|
|
+{
|
|
+ unsigned int pol;
|
|
+ u32 val = 0;
|
|
+ int ret;
|
|
+
|
|
+ if (!mpcs->fwnode)
|
|
+ return 0;
|
|
+
|
|
+ ret = phy_get_rx_polarity(mpcs->fwnode, phy_modes(interface),
|
|
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
|
|
+ PHY_POL_NORMAL, &pol);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (pol == PHY_POL_INVERT)
|
|
+ val |= USXGMII_PN_SWAP_RX;
|
|
+
|
|
+ ret = phy_get_tx_polarity(mpcs->fwnode, phy_modes(interface),
|
|
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
|
|
+ PHY_POL_NORMAL, &pol);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (pol == PHY_POL_INVERT)
|
|
+ val |= USXGMII_PN_SWAP_TX;
|
|
+
|
|
+ mtk_m32(mpcs, RG_PHY_TOP_CTRL0, USXGMII_PN_SWAP_MASK, val);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+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;
|
|
+ int ret;
|
|
+
|
|
+ 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);
|
|
+
|
|
+ ret = mtk_usxgmii_config_polarity(mpcs, interface);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* 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);
|
|
+
|
|
+ /* 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));
|
|
+
|
|
+ /* 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)
|
|
+{
|
|
+ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
|
|
+
|
|
+ /* 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);
|
|
+ phy_reset(mpcs->xfi_tphy);
|
|
+ phy_set_mode_ext(mpcs->xfi_tphy, PHY_MODE_ETHERNET, interface);
|
|
+}
|
|
+
|
|
+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;
|
|
+ mpcs->neg_mode = -1;
|
|
+
|
|
+ phy_power_off(mpcs->xfi_tphy);
|
|
+}
|
|
+
|
|
+static unsigned int mtk_usxgmii_pcs_inband_caps(struct phylink_pcs *pcs,
|
|
+ phy_interface_t interface)
|
|
+{
|
|
+ switch (interface) {
|
|
+ case PHY_INTERFACE_MODE_USXGMII:
|
|
+ return LINK_INBAND_ENABLE;
|
|
+
|
|
+ case PHY_INTERFACE_MODE_5GBASER:
|
|
+ case PHY_INTERFACE_MODE_10GBASER:
|
|
+ return LINK_INBAND_DISABLE;
|
|
+
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct phylink_pcs_ops mtk_usxgmii_pcs_ops = {
|
|
+ .pcs_inband_caps = mtk_usxgmii_pcs_inband_caps,
|
|
+ .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->neg_mode = -1;
|
|
+ mpcs->fwnode = fwnode_handle_get(dev_fwnode(dev));
|
|
+
|
|
+ __set_bit(PHY_INTERFACE_MODE_5GBASER, mpcs->pcs.supported_interfaces);
|
|
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, mpcs->pcs.supported_interfaces);
|
|
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, mpcs->pcs.supported_interfaces);
|
|
+
|
|
+ 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 fwnode_pcs_add_provider(dev_fwnode(dev), fwnode_pcs_simple_get, &mpcs->pcs);
|
|
+}
|
|
+
|
|
+static void mtk_usxgmii_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct mtk_usxgmii_pcs *mpcs = platform_get_drvdata(pdev);
|
|
+
|
|
+ fwnode_pcs_del_provider(dev_fwnode(&pdev->dev));
|
|
+
|
|
+ rtnl_lock();
|
|
+ phylink_release_pcs(&mpcs->pcs);
|
|
+ rtnl_unlock();
|
|
+
|
|
+ fwnode_handle_put(mpcs->fwnode);
|
|
+};
|
|
+
|
|
+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-pcs-usxgmii",
|
|
+ .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>");
|