mirror of
https://github.com/openwrt/openwrt.git
synced 2026-06-17 12:40:16 +04:00
6369c9e5c7
The functionality/support for 5G and 10G PHYs was extracted from the realtek-phy driver and ported to the upstream Linux realtek PHY driver. These PHY chips need a sequence of register writes (and similar operations) for initialization. These sequences are provided as firmware files which are interpreted/applied by a new register patch engine. By switching to the upstream driver, it should be possible to get rid of a large chunk of (from OpenWrt perspective) unmaintained code from Realtek. The actual Linux phy-core infrastructure from Linux can be mostly used and only the Realtek specific quirks need to be handled. The files which need to be provided are depending on the PHY: * rtl8261n.bin (package "rtl8261n-firmware" or "rtl8261n-lp-firmware") - RTL8251L 5Gbps PHY - RTL8261BE 10Gbps PHY - RTL8261N 10Gbps PHY * rtl8264b.bin (package "rtl8264b-firmware") - RTL8254B 5Gbps PHY - RTL8264 10Gbps PHY - RTL8264B 10Gbps PHY Files which are affected by this change (DEVICE_PACKAGES dependencies, hwmon paths, default kernel configurations, refresh of patches, ...) are updated at the same times. Signed-off-by: Balázs Triszka <info@balika011.hu> Co-authored-by: Semih Baskan <strst.gs@gmail.com> Co-authored-by: Jonas Jelonek <jelonek.jonas@gmail.com> Co-authored-by: Gilly1970 <gilroyscott@hotmail.com> Co-authored-by: Aleksander Jan Bajkowski <olek2@wp.pl> Co-authored-by: Carlo Szelinsky <github@szelinsky.de> [sven: rebase, integrate suggestions from PR, add device packages, split] Signed-off-by: Sven Eckelmann <sven@narfation.org> [daniel: stripped to Linux 6.18 only, dropped unrelated changes] Signed-off-by: Daniel Golle <daniel@makrotopia.org>
1220 lines
30 KiB
Diff
1220 lines
30 KiB
Diff
From: Balázs Triszka <info@balika011.hu>
|
|
Date: Sat, 18 Oct 2025 23:09:23 +0200
|
|
Subject: net: phy: realtek: add 5G and 10G PHY support
|
|
|
|
The functionality was extracted from the realtek-phy driver and ported to
|
|
the upstream Linux realtek PHY driver.
|
|
|
|
These PHY chips need a sequence of register writes for initialization.
|
|
These are provided as firmware files which are interpreted/applied by a
|
|
new register patch engine.
|
|
|
|
The files which need to be provided are depending on the PHY:
|
|
|
|
* rtl8261n.bin
|
|
- RTL8251L 5Gbps PHY
|
|
- RTL8261BE 10Gbps PHY
|
|
- RTL8261N 10Gbps PHY
|
|
* rtl8264b.bin
|
|
- RTL8254B 5Gbps PHY
|
|
- RTL8264 10Gbps PHY
|
|
- RTL8264B 10Gbps PHY
|
|
|
|
Signed-off-by: Balázs Triszka <info@balika011.hu>
|
|
|
|
--- a/drivers/net/phy/realtek/Makefile
|
|
+++ b/drivers/net/phy/realtek/Makefile
|
|
@@ -1,4 +1,6 @@
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
realtek-y += realtek_main.o
|
|
+realtek-y += phy_patch.o
|
|
+realtek-y += phy_patch_rtl826x.o
|
|
realtek-$(CONFIG_REALTEK_PHY_HWMON) += realtek_hwmon.o
|
|
obj-$(CONFIG_REALTEK_PHY) += realtek.o
|
|
--- a/drivers/net/phy/realtek/realtek.h
|
|
+++ b/drivers/net/phy/realtek/realtek.h
|
|
@@ -5,6 +5,14 @@
|
|
|
|
#include <linux/phy.h>
|
|
|
|
+#include "phy_patch.h"
|
|
+#include "phy_patch_rtl826x.h"
|
|
+
|
|
+struct rtl826x_priv {
|
|
+ struct rtlgen_phy_patch_db *patch;
|
|
+ bool enable_pma_low_power:1;
|
|
+};
|
|
+
|
|
int rtl822x_hwmon_init(struct phy_device *phydev);
|
|
|
|
#endif /* REALTEK_H */
|
|
--- a/drivers/net/phy/realtek/realtek_main.c
|
|
+++ b/drivers/net/phy/realtek/realtek_main.c
|
|
@@ -11,11 +11,13 @@
|
|
#include <linux/ethtool_netlink.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy.h>
|
|
+#include <linux/phy/phy-common-props.h>
|
|
#include <linux/pm_wakeirq.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/clk.h>
|
|
+#include <linux/crc32.h>
|
|
#include <linux/string_choices.h>
|
|
#include <net/phy/realtek_phy.h>
|
|
|
|
@@ -205,10 +207,26 @@
|
|
#define RTL_8221B_VM_CG 0x001cc84a
|
|
#define RTL_8251B 0x001cc862
|
|
#define RTL_8261C 0x001cc890
|
|
+#define RTL_8261N 0x001ccaf3
|
|
+#define RTL_8264 0x001ccaf2
|
|
+#define RTL_8264B 0x001cc813
|
|
|
|
/* RTL8211E and RTL8211F support up to three LEDs */
|
|
#define RTL8211x_LED_COUNT 3
|
|
|
|
+#define RTL826X_VEND1_SERDES_GLOBAL_CFG 0xc1
|
|
+#define RTL826X_VEND1_SERDES_GLOBAL_CFG_HSI_INV BIT(6)
|
|
+#define RTL826X_VEND1_SERDES_GLOBAL_CFG_HSO_INV BIT(7)
|
|
+
|
|
+#define RTL826X_VEND1_PKG_MODEL 0x103
|
|
+#define RTL826X_VEND1_VERSION_ID 0x104
|
|
+
|
|
+#define RTL826X_VND2_INER 0xA424
|
|
+#define RTL826X_VND2_INER_LINK_STATUS BIT(4)
|
|
+#define RTL826X_VND2_INER_PME BIT(7)
|
|
+
|
|
+#define RTL826X_VND2_INSR 0xA43A
|
|
+
|
|
MODULE_DESCRIPTION("Realtek PHY driver");
|
|
MODULE_AUTHOR("Johnson Leung");
|
|
MODULE_LICENSE("GPL");
|
|
@@ -1993,6 +2011,125 @@ static int rtl8251b_c45_match_phy_device
|
|
return rtlgen_is_c45_match(phydev, RTL_8251B, true);
|
|
}
|
|
|
|
+static int rtl8251l_match_phy_device(struct phy_device *phydev,
|
|
+ const struct phy_driver *phydrv)
|
|
+{
|
|
+ int data;
|
|
+
|
|
+ if (!rtlgen_is_c45_match(phydev, RTL_8261N, true))
|
|
+ return 0;
|
|
+
|
|
+ if (!phydev->mdio.bus->read_c45)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_PKG_MODEL);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if (data != 0x8251)
|
|
+ return 0;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int rtl8254b_match_phy_device(struct phy_device *phydev,
|
|
+ const struct phy_driver *phydrv)
|
|
+{
|
|
+ int data;
|
|
+
|
|
+ if (!rtlgen_is_c45_match(phydev, RTL_8264B, true))
|
|
+ return 0;
|
|
+
|
|
+ if (!phydev->mdio.bus->read_c45)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_PKG_MODEL);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if (data != 0x8254)
|
|
+ return 0;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int rtl8261be_match_phy_device(struct phy_device *phydev,
|
|
+ const struct phy_driver *phydrv)
|
|
+{
|
|
+ int data;
|
|
+
|
|
+ if (!rtlgen_is_c45_match(phydev, RTL_8261N, true))
|
|
+ return 0;
|
|
+
|
|
+ if (!phydev->mdio.bus->read_c45)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_PKG_MODEL);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if (data == 0x8251)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_VERSION_ID);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if ((data & 0xFFC0) != 0x1140)
|
|
+ return 0;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int rtl8261n_match_phy_device(struct phy_device *phydev,
|
|
+ const struct phy_driver *phydrv)
|
|
+{
|
|
+ int data;
|
|
+
|
|
+ if (!rtlgen_is_c45_match(phydev, RTL_8261N, true))
|
|
+ return 0;
|
|
+
|
|
+ if (!phydev->mdio.bus->read_c45)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_PKG_MODEL);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if (data == 0x8251)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_VERSION_ID);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if ((data & 0xFFC0) == 0x1140)
|
|
+ return 0;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int rtl8264b_match_phy_device(struct phy_device *phydev,
|
|
+ const struct phy_driver *phydrv)
|
|
+{
|
|
+ int data;
|
|
+
|
|
+ if (!rtlgen_is_c45_match(phydev, RTL_8264B, true))
|
|
+ return 0;
|
|
+
|
|
+ if (!phydev->mdio.bus->read_c45)
|
|
+ return 0;
|
|
+
|
|
+ data = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_PKG_MODEL);
|
|
+ if (data < 0)
|
|
+ return 0;
|
|
+
|
|
+ if (data == 0x8254)
|
|
+ return 0;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
static int rtlgen_resume(struct phy_device *phydev)
|
|
{
|
|
int ret = genphy_resume(phydev);
|
|
@@ -2214,6 +2351,493 @@ static int rtlgen_sfp_config_aneg(struct
|
|
return 0;
|
|
}
|
|
|
|
+static int rtl826x_probe(struct phy_device *phydev)
|
|
+{
|
|
+ struct device *dev = &phydev->mdio.dev;
|
|
+ struct rtl826x_priv *priv;
|
|
+
|
|
+ priv = devm_kzalloc(dev, sizeof(struct rtl826x_priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ priv->enable_pma_low_power = device_property_read_bool(dev, "realtek,enable-pma-low-power");
|
|
+ phydev->priv = priv;
|
|
+
|
|
+ if (!priv->enable_pma_low_power)
|
|
+ phydev_warn(phydev, "PMA low-power suspend disabled\n");
|
|
+
|
|
+ /* Disable EEE due to link stability issues */
|
|
+ phy_disable_eee_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Full_BIT);
|
|
+ phy_disable_eee_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Full_BIT);
|
|
+
|
|
+ if (IS_ENABLED(CONFIG_REALTEK_PHY_HWMON))
|
|
+ return rtl822x_hwmon_init(phydev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl8261n_probe(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = rtl826x_probe(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = rtl8261n_phy_patch_db_init(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl8264b_probe(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = rtl826x_probe(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = rtl8264b_phy_patch_db_init(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl826x_config_serdes_polarity(struct phy_device *phydev)
|
|
+{
|
|
+ struct device *dev = &phydev->mdio.dev;
|
|
+ unsigned int pol;
|
|
+ u16 mask = 0;
|
|
+ u16 set = 0;
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_get_rx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
|
|
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
|
|
+ PHY_POL_NORMAL, &pol);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mask |= RTL826X_VEND1_SERDES_GLOBAL_CFG_HSI_INV;
|
|
+ if (pol == PHY_POL_INVERT)
|
|
+ set |= RTL826X_VEND1_SERDES_GLOBAL_CFG_HSI_INV;
|
|
+
|
|
+ ret = phy_get_tx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
|
|
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
|
|
+ PHY_POL_NORMAL, &pol);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mask |= RTL826X_VEND1_SERDES_GLOBAL_CFG_HSO_INV;
|
|
+ if (pol == PHY_POL_INVERT)
|
|
+ set |= RTL826X_VEND1_SERDES_GLOBAL_CFG_HSO_INV;
|
|
+
|
|
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, RTL826X_VEND1_SERDES_GLOBAL_CFG,
|
|
+ mask, set);
|
|
+}
|
|
+
|
|
+static int rtl826x_config_init(struct phy_device *phydev)
|
|
+{
|
|
+ const unsigned int max_dereset_tries = 5;
|
|
+ unsigned int tries = 0;
|
|
+ int ret;
|
|
+
|
|
+ /* Suppress PHY interrupt output before the hardware reset below.
|
|
+ * The RTL8261N asserts its interrupt pin during power-on reset, which
|
|
+ * races with phylib registering phy_interrupt. If the IRQ fires first
|
|
+ * the kernel disables it permanently. rtl826x_config_intr() will
|
|
+ * re-enable the interrupt correctly once phylib is ready.
|
|
+ */
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL826X_VND2_INER, 0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0xE1, BIT(0));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* toggle reset */
|
|
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x145, BIT(0));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ do {
|
|
+ msleep(30);
|
|
+ tries++;
|
|
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x145, BIT(0));
|
|
+ } while (ret < 0 && tries <= max_dereset_tries);
|
|
+
|
|
+ if (ret < 0) {
|
|
+ phydev_err(phydev, "deassert reset failed %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ msleep(30);
|
|
+
|
|
+ ret = rtlgen_phy_patch(phydev);
|
|
+ if (ret) {
|
|
+ phydev_err(phydev, "patch failed!! %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return rtl826x_config_serdes_polarity(phydev);
|
|
+}
|
|
+
|
|
+static int rtl826x_suspend(struct phy_device *phydev)
|
|
+{
|
|
+ struct rtl826x_priv *priv = phydev->priv;
|
|
+
|
|
+ if (!priv->enable_pma_low_power)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ return genphy_c45_pma_suspend(phydev);
|
|
+}
|
|
+
|
|
+static int rtl826x_get_features(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = genphy_c45_pma_read_abilities(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* not support 10M modes */
|
|
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
|
|
+ phydev->supported);
|
|
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
|
|
+ phydev->supported);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl825xb_get_features(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = genphy_c45_pma_read_abilities(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* not support 10M modes */
|
|
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
|
|
+ phydev->supported);
|
|
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
|
|
+ phydev->supported);
|
|
+
|
|
+ /* faulty rtl826x silicon having issues with 10G, sold as only 5G phy */
|
|
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
|
|
+ phydev->supported);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl826x_config_aneg(struct phy_device *phydev)
|
|
+{
|
|
+ bool changed = false;
|
|
+ u16 reg;
|
|
+ int ret;
|
|
+
|
|
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
|
|
+ if (phydev->autoneg == AUTONEG_DISABLE)
|
|
+ return genphy_c45_pma_setup_forced(phydev);
|
|
+
|
|
+ ret = genphy_c45_an_config_aneg(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ if (ret > 0)
|
|
+ changed = true;
|
|
+
|
|
+ reg = 0;
|
|
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
|
|
+ phydev->advertising))
|
|
+ reg |= ADVERTISE_1000FULL;
|
|
+
|
|
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
|
|
+ phydev->advertising))
|
|
+ reg |= ADVERTISE_1000HALF;
|
|
+
|
|
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
|
|
+ RTL822X_VND2_C22_REG(MII_CTRL1000),
|
|
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF, reg);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ if (ret > 0)
|
|
+ changed = true;
|
|
+
|
|
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
|
|
+}
|
|
+
|
|
+static int rtl826x_read_status(struct phy_device *phydev)
|
|
+{
|
|
+ int status;
|
|
+ int ret;
|
|
+
|
|
+ phydev->speed = SPEED_UNKNOWN;
|
|
+ phydev->duplex = DUPLEX_UNKNOWN;
|
|
+ phydev->pause = 0;
|
|
+ phydev->asym_pause = 0;
|
|
+
|
|
+ ret = genphy_c45_read_link(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (phydev->autoneg == AUTONEG_ENABLE) {
|
|
+ ret = genphy_c45_read_lpa(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_C22_REG(MII_STAT1000));
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, status);
|
|
+
|
|
+ phy_resolve_aneg_linkmode(phydev);
|
|
+ } else {
|
|
+ ret = genphy_c45_read_pma(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return genphy_c45_read_mdix(phydev);
|
|
+}
|
|
+
|
|
+static int rtl826x_config_intr(struct phy_device *phydev)
|
|
+{
|
|
+ u16 interrupts;
|
|
+ int ret;
|
|
+
|
|
+ /* Disable all IMR */
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xE1, 0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xE3, 0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* source */
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xE4, 0x1);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xE0, 0x2F);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* init common link change & WOL */
|
|
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
|
+ interrupts = RTL826X_VND2_INER_LINK_STATUS | RTL826X_VND2_INER_PME;
|
|
+ else
|
|
+ interrupts = 0;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL826X_VND2_INER, interrupts);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* clear status */
|
|
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x2DC, 0xFF);
|
|
+
|
|
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, 0xE1, BIT(0),
|
|
+ phydev->interrupts == PHY_INTERRUPT_ENABLED);
|
|
+}
|
|
+
|
|
+static irqreturn_t rtl826x_handle_interrupt(struct phy_device *phydev)
|
|
+{
|
|
+ int irq_enabled;
|
|
+ int irq_status;
|
|
+
|
|
+ irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL826X_VND2_INSR);
|
|
+ if (irq_status < 0) {
|
|
+ phy_error(phydev);
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ if (phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x2DC, 0xFF) < 0) {
|
|
+ phy_error(phydev);
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ irq_enabled = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL826X_VND2_INER);
|
|
+ if (irq_enabled < 0) {
|
|
+ phy_error(phydev);
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ if (!(irq_status & irq_enabled))
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ if (irq_status & RTL826X_VND2_INER_LINK_STATUS) {
|
|
+ phydev_dbg(phydev, "RTK_PHY_INTR_LINK_CHANGE\n");
|
|
+ phy_mac_interrupt(phydev);
|
|
+ }
|
|
+
|
|
+ if (irq_status & RTL826X_VND2_INER_PME) {
|
|
+ phydev_dbg(phydev, "RTK_PHY_INTR_WOL\n");
|
|
+
|
|
+ if (phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0xD8A2, BIT(15)) < 0)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ if (phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, 0xD8A2, BIT(15)) < 0)
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int rtl826x_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
|
|
+{
|
|
+ static const u32 cfg_reg[4] = {0xD8C6, 0xD8C8, 0xD8CA, 0xD8CC};
|
|
+ struct net_device *ndev = phydev->attached_dev;
|
|
+ struct netdev_hw_addr *ha;
|
|
+ u16 rtk_wolopts;
|
|
+ size_t idx;
|
|
+ u32 offset;
|
|
+ int ret;
|
|
+
|
|
+ if (!ndev)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_UCAST)) {
|
|
+ const u8 *mac_addr = ndev->dev_addr;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xD8C0,
|
|
+ mac_addr[1] << 8 | mac_addr[0]);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xD8C2,
|
|
+ mac_addr[3] << 8 | mac_addr[2]);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xD8C4,
|
|
+ mac_addr[5] << 8 | mac_addr[4]);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (wol->wolopts & WAKE_MCAST) {
|
|
+ for (idx = 0; idx < ARRAY_SIZE(cfg_reg); idx++) {
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, cfg_reg[idx], 0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (!netdev_mc_empty(ndev)) {
|
|
+ netdev_for_each_mc_addr(ha, ndev) {
|
|
+ phydev_dbg(phydev, "mac: %pM\n", ha->addr);
|
|
+
|
|
+ offset = crc32_be(~0, ha->addr, 6) >> 26;
|
|
+ idx = offset >> 4;
|
|
+
|
|
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, cfg_reg[idx],
|
|
+ BIT(offset & 0xF));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ rtk_wolopts = 0;
|
|
+ if (wol->wolopts & WAKE_PHY)
|
|
+ rtk_wolopts |= BIT(13);
|
|
+ if (wol->wolopts & WAKE_MAGIC)
|
|
+ rtk_wolopts |= BIT(12);
|
|
+ if (wol->wolopts & WAKE_UCAST)
|
|
+ rtk_wolopts |= BIT(10);
|
|
+ if (wol->wolopts & WAKE_MCAST)
|
|
+ rtk_wolopts |= BIT(9);
|
|
+ if (wol->wolopts & WAKE_BCAST)
|
|
+ rtk_wolopts |= BIT(8);
|
|
+
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xD8A0,
|
|
+ GENMASK(13, 12) | GENMASK(10, 8), rtk_wolopts);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void rtl826x_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
|
|
+{
|
|
+ int rtk_wolopts;
|
|
+
|
|
+ wol->supported = WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST;
|
|
+ wol->wolopts = 0;
|
|
+
|
|
+ rtk_wolopts = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xD8A0);
|
|
+ if (rtk_wolopts < 0)
|
|
+ return;
|
|
+
|
|
+ if (rtk_wolopts & BIT(13))
|
|
+ wol->wolopts |= WAKE_PHY;
|
|
+ if (rtk_wolopts & BIT(12))
|
|
+ wol->wolopts |= WAKE_MAGIC;
|
|
+ if (rtk_wolopts & BIT(10))
|
|
+ wol->wolopts |= WAKE_UCAST;
|
|
+ if (rtk_wolopts & BIT(9))
|
|
+ wol->wolopts |= WAKE_MCAST;
|
|
+ if (rtk_wolopts & BIT(8))
|
|
+ wol->wolopts |= WAKE_BCAST;
|
|
+}
|
|
+
|
|
+static int rtl826x_get_tunable(struct phy_device *phydev, struct ethtool_tunable *tuna, void *data)
|
|
+{
|
|
+ int val;
|
|
+
|
|
+ switch (tuna->id) {
|
|
+ case ETHTOOL_PHY_EDPD:
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_PHYCR1);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ *(u16 *)data = (!(val & RTL8221B_PHYCR1_ALDPS_EN)) ? ETHTOOL_PHY_EDPD_DISABLE :
|
|
+ ETHTOOL_PHY_EDPD_DFLT_TX_MSECS;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl826x_set_tunable(struct phy_device *phydev,
|
|
+ struct ethtool_tunable *tuna, const void *data)
|
|
+{
|
|
+ int ret;
|
|
+ u16 val;
|
|
+
|
|
+ switch (tuna->id) {
|
|
+ case ETHTOOL_PHY_EDPD:
|
|
+ switch (*(const u16 *)data) {
|
|
+ case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
|
|
+ val = RTL8221B_PHYCR1_ALDPS_EN;
|
|
+ break;
|
|
+ case ETHTOOL_PHY_EDPD_DISABLE:
|
|
+ val = 0;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_PHYCR1,
|
|
+ RTL8221B_PHYCR1_ALDPS_EN, val);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static struct phy_driver realtek_drvs[] = {
|
|
{
|
|
PHY_ID_MATCH_EXACT(0x00008201),
|
|
@@ -2520,6 +3144,108 @@ static struct phy_driver realtek_drvs[]
|
|
.resume = genphy_resume,
|
|
.read_mmd = genphy_read_mmd_unsupported,
|
|
.write_mmd = genphy_write_mmd_unsupported,
|
|
+ }, {
|
|
+ .name = "RTL8251L 5Gbps PHY",
|
|
+ .config_init = rtl826x_config_init,
|
|
+ .probe = rtl8261n_probe,
|
|
+ .get_features = rtl825xb_get_features,
|
|
+ .suspend = rtl826x_suspend,
|
|
+ .resume = rtlgen_c45_resume,
|
|
+ .config_aneg = rtl826x_config_aneg,
|
|
+ .aneg_done = genphy_c45_aneg_done,
|
|
+ .read_status = rtl826x_read_status,
|
|
+ .config_intr = rtl826x_config_intr,
|
|
+ .handle_interrupt = rtl826x_handle_interrupt,
|
|
+ .match_phy_device = rtl8251l_match_phy_device,
|
|
+ .set_wol = rtl826x_set_wol,
|
|
+ .get_wol = rtl826x_get_wol,
|
|
+ .get_tunable = rtl826x_get_tunable,
|
|
+ .set_tunable = rtl826x_set_tunable,
|
|
+ }, {
|
|
+ .name = "RTL8254B 5Gbps PHY",
|
|
+ .config_init = rtl826x_config_init,
|
|
+ .probe = rtl8264b_probe,
|
|
+ .get_features = rtl825xb_get_features,
|
|
+ .suspend = rtl826x_suspend,
|
|
+ .resume = rtlgen_c45_resume,
|
|
+ .config_aneg = rtl826x_config_aneg,
|
|
+ .aneg_done = genphy_c45_aneg_done,
|
|
+ .read_status = rtl826x_read_status,
|
|
+ .config_intr = rtl826x_config_intr,
|
|
+ .handle_interrupt = rtl826x_handle_interrupt,
|
|
+ .match_phy_device = rtl8254b_match_phy_device,
|
|
+ .set_wol = rtl826x_set_wol,
|
|
+ .get_wol = rtl826x_get_wol,
|
|
+ .get_tunable = rtl826x_get_tunable,
|
|
+ .set_tunable = rtl826x_set_tunable,
|
|
+ }, {
|
|
+ .name = "RTL8261BE 10Gbps PHY",
|
|
+ .config_init = rtl826x_config_init,
|
|
+ .probe = rtl8261n_probe,
|
|
+ .get_features = rtl826x_get_features,
|
|
+ .suspend = rtl826x_suspend,
|
|
+ .resume = rtlgen_c45_resume,
|
|
+ .config_aneg = rtl826x_config_aneg,
|
|
+ .aneg_done = genphy_c45_aneg_done,
|
|
+ .read_status = rtl826x_read_status,
|
|
+ .config_intr = rtl826x_config_intr,
|
|
+ .handle_interrupt = rtl826x_handle_interrupt,
|
|
+ .match_phy_device = rtl8261be_match_phy_device,
|
|
+ .set_wol = rtl826x_set_wol,
|
|
+ .get_wol = rtl826x_get_wol,
|
|
+ .get_tunable = rtl826x_get_tunable,
|
|
+ .set_tunable = rtl826x_set_tunable,
|
|
+ }, {
|
|
+ .name = "RTL8261N 10Gbps PHY",
|
|
+ .config_init = rtl826x_config_init,
|
|
+ .probe = rtl8261n_probe,
|
|
+ .get_features = rtl826x_get_features,
|
|
+ .suspend = rtl826x_suspend,
|
|
+ .resume = rtlgen_c45_resume,
|
|
+ .config_aneg = rtl826x_config_aneg,
|
|
+ .aneg_done = genphy_c45_aneg_done,
|
|
+ .read_status = rtl826x_read_status,
|
|
+ .config_intr = rtl826x_config_intr,
|
|
+ .handle_interrupt = rtl826x_handle_interrupt,
|
|
+ .match_phy_device = rtl8261n_match_phy_device,
|
|
+ .set_wol = rtl826x_set_wol,
|
|
+ .get_wol = rtl826x_get_wol,
|
|
+ .get_tunable = rtl826x_get_tunable,
|
|
+ .set_tunable = rtl826x_set_tunable,
|
|
+ }, {
|
|
+ PHY_ID_MATCH_EXACT(RTL_8264),
|
|
+ .name = "RTL8264 10Gbps PHY",
|
|
+ .config_init = rtl826x_config_init,
|
|
+ .probe = rtl8264b_probe,
|
|
+ .get_features = rtl826x_get_features,
|
|
+ .suspend = rtl826x_suspend,
|
|
+ .resume = rtlgen_c45_resume,
|
|
+ .config_aneg = rtl826x_config_aneg,
|
|
+ .aneg_done = genphy_c45_aneg_done,
|
|
+ .read_status = rtl826x_read_status,
|
|
+ .config_intr = rtl826x_config_intr,
|
|
+ .handle_interrupt = rtl826x_handle_interrupt,
|
|
+ .set_wol = rtl826x_set_wol,
|
|
+ .get_wol = rtl826x_get_wol,
|
|
+ .get_tunable = rtl826x_get_tunable,
|
|
+ .set_tunable = rtl826x_set_tunable,
|
|
+ }, {
|
|
+ .name = "RTL8264B 10Gbps PHY",
|
|
+ .config_init = rtl826x_config_init,
|
|
+ .probe = rtl8264b_probe,
|
|
+ .get_features = rtl826x_get_features,
|
|
+ .suspend = rtl826x_suspend,
|
|
+ .resume = rtlgen_c45_resume,
|
|
+ .config_aneg = rtl826x_config_aneg,
|
|
+ .aneg_done = genphy_c45_aneg_done,
|
|
+ .read_status = rtl826x_read_status,
|
|
+ .config_intr = rtl826x_config_intr,
|
|
+ .handle_interrupt = rtl826x_handle_interrupt,
|
|
+ .match_phy_device = rtl8264b_match_phy_device,
|
|
+ .set_wol = rtl826x_set_wol,
|
|
+ .get_wol = rtl826x_get_wol,
|
|
+ .get_tunable = rtl826x_get_tunable,
|
|
+ .set_tunable = rtl826x_set_tunable,
|
|
},
|
|
};
|
|
|
|
--- a/drivers/net/phy/realtek/Kconfig
|
|
+++ b/drivers/net/phy/realtek/Kconfig
|
|
@@ -1,6 +1,7 @@
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
config REALTEK_PHY
|
|
tristate "Realtek PHYs"
|
|
+ select PHY_COMMON_PROPS
|
|
help
|
|
Currently supports RTL821x/RTL822x and fast ethernet PHYs
|
|
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/realtek/phy_patch.c
|
|
@@ -0,0 +1,35 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+#include "phy_patch.h"
|
|
+
|
|
+#include <linux/errno.h>
|
|
+#include <linux/phy.h>
|
|
+
|
|
+#include "realtek.h"
|
|
+
|
|
+int rtlgen_phy_patch(struct phy_device *phydev)
|
|
+{
|
|
+ struct rtl826x_priv *priv = phydev->priv;
|
|
+ struct rtlgen_phy_patch_db *patch_db = priv->patch;
|
|
+ size_t i;
|
|
+ int ret;
|
|
+
|
|
+ if (!patch_db || !patch_db->patch_op_exec) {
|
|
+ phydev_err(phydev, "patch db or exec callback is NULL\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < patch_db->count; i++) {
|
|
+ ret = patch_db->patch_op_exec(phydev, &patch_db->conf[i]);
|
|
+ if (ret < 0) {
|
|
+ phydev_err(phydev, "patch failed! %zu[%u][0x%X][0x%X][0x%X] ret=%d\n",
|
|
+ i, patch_db->conf[i].patch_op,
|
|
+ le16_to_cpu(patch_db->conf[i].pagemmd),
|
|
+ le16_to_cpu(patch_db->conf[i].addr),
|
|
+ le16_to_cpu(patch_db->conf[i].data), ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/realtek/phy_patch.h
|
|
@@ -0,0 +1,42 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+
|
|
+#ifndef __PHY_PATCH_H__
|
|
+#define __PHY_PATCH_H__
|
|
+
|
|
+#include <linux/phy.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+enum rtlgen_phypatch_op {
|
|
+ RTK_PATCH_OP_PHY,
|
|
+ RTK_PATCH_OP_PHY_WAIT,
|
|
+ RTK_PATCH_OP_PHY_WAIT_NOT,
|
|
+ RTK_PATCH_OP_PHYOCP,
|
|
+ RTK_PATCH_OP_PHYOCP_BC62,
|
|
+ RTK_PATCH_OP_TOP,
|
|
+ RTK_PATCH_OP_TOPOCP, // TODO unimplemented
|
|
+ RTK_PATCH_OP_PSDS0,
|
|
+ RTK_PATCH_OP_PSDS1, // TODO unimplemented
|
|
+ RTK_PATCH_OP_MSDS, // TODO unimplemented
|
|
+ RTK_PATCH_OP_MAC, // TODO unimplemented
|
|
+ RTK_PATCH_OP_DELAY_MS
|
|
+};
|
|
+
|
|
+struct rtlgen_hwpatch {
|
|
+ u8 patch_op;
|
|
+ u8 portmask;
|
|
+ __le16 pagemmd;
|
|
+ __le16 addr;
|
|
+ u8 msb;
|
|
+ u8 lsb;
|
|
+ __le16 data;
|
|
+} __packed;
|
|
+
|
|
+struct rtlgen_phy_patch_db {
|
|
+ int (*patch_op_exec)(struct phy_device *phydev, const struct rtlgen_hwpatch *patch);
|
|
+ size_t count;
|
|
+ struct rtlgen_hwpatch conf[] __counted_by(count);
|
|
+};
|
|
+
|
|
+int rtlgen_phy_patch(struct phy_device *phydev);
|
|
+
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/realtek/phy_patch_rtl826x.c
|
|
@@ -0,0 +1,287 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+#include "phy_patch_rtl826x.h"
|
|
+
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/bits.h>
|
|
+#include <linux/byteorder/generic.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/gfp.h>
|
|
+#include <linux/ktime.h>
|
|
+#include <linux/mdio.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/overflow.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/stddef.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include "phy_patch.h"
|
|
+#include "realtek.h"
|
|
+
|
|
+#define PHY_PATCH_WAIT_TIMEOUT 10000000
|
|
+
|
|
+static u16 rtl826x_mmd_convert(u16 page, u16 addr)
|
|
+{
|
|
+ if (addr < 16)
|
|
+ return 0xA400 + (page * 2);
|
|
+
|
|
+ if (addr < 24)
|
|
+ return (16 * page) + ((addr - 16) * 2);
|
|
+
|
|
+ return 0xA430 + ((addr - 24) * 2);
|
|
+}
|
|
+
|
|
+static int rtl826x_phy_patch_wait(struct phy_device *phydev, u32 mmd_addr, u32 mmd_reg,
|
|
+ u16 data, u16 mask, bool wait_equal)
|
|
+{
|
|
+ s64 us_diff = 0;
|
|
+ ktime_t start;
|
|
+ ktime_t now;
|
|
+ bool equal;
|
|
+ int ret;
|
|
+
|
|
+ start = ktime_get();
|
|
+
|
|
+ do {
|
|
+ ret = phy_read_mmd(phydev, mmd_addr, mmd_reg);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ equal = (ret & mask) == data;
|
|
+ if (equal == wait_equal)
|
|
+ break;
|
|
+
|
|
+ msleep(1);
|
|
+
|
|
+ now = ktime_get();
|
|
+ us_diff = ktime_to_us(ktime_sub(now, start));
|
|
+ } while (us_diff < PHY_PATCH_WAIT_TIMEOUT);
|
|
+
|
|
+ if (us_diff >= PHY_PATCH_WAIT_TIMEOUT) {
|
|
+ phydev_err(phydev, "826xb patch wait[%u,0x%X,0x%X,0x%X]:0x%X\n",
|
|
+ mmd_addr, mmd_reg, mask, data, ret);
|
|
+ return -ETIME;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rtl826x_phy_patch_sds_get(struct phy_device *phydev, u32 sds_page, u32 sds_reg)
|
|
+{
|
|
+ u32 sds_addr = 0x8000 + (sds_reg << 6) + sds_page;
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x143, sds_addr);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = rtl826x_phy_patch_wait(phydev, MDIO_MMD_VEND1, 0x143, 0, BIT(15), true);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x142);
|
|
+}
|
|
+
|
|
+static int rtl826x_phy_patch_sds_set(struct phy_device *phydev, u32 sds_page, u32 sds_reg,
|
|
+ u32 data)
|
|
+{
|
|
+ u32 sds_addr = 0x8800 + (sds_reg << 6) + sds_page;
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x141, data);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x143, sds_addr);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return rtl826x_phy_patch_wait(phydev, MDIO_MMD_VEND1, 0x143, 0, BIT(15), true);
|
|
+}
|
|
+
|
|
+static int rtl826x_phy_patch_sds_modify(struct phy_device *phydev, u32 sds_page, u32 sds_reg,
|
|
+ u32 mask, u32 data)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (mask != GENMASK(15, 0)) {
|
|
+ ret = rtl826x_phy_patch_sds_get(phydev, sds_page, sds_reg);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return rtl826x_phy_patch_sds_set(phydev, sds_page, sds_reg, (ret & ~mask) | data);
|
|
+}
|
|
+
|
|
+static int rtl826x_phy_patch_op(struct phy_device *phydev, const struct rtlgen_hwpatch *patch)
|
|
+{
|
|
+ u32 mask = GENMASK(patch->msb, patch->lsb);
|
|
+ u16 pagemmd = le16_to_cpu(patch->pagemmd);
|
|
+ u16 addr = le16_to_cpu(patch->addr);
|
|
+ u16 data = le16_to_cpu(patch->data);
|
|
+ bool wait_equal;
|
|
+ int ret = 0;
|
|
+ int val;
|
|
+ int cnt;
|
|
+ u16 reg;
|
|
+
|
|
+ phydev_dbg(phydev, "patch: op: %u pm: %x mmd: %x addr: %x mask: [%u, %u] data: %x\n",
|
|
+ patch->patch_op, patch->portmask, pagemmd, addr,
|
|
+ patch->msb, patch->lsb, data);
|
|
+
|
|
+ switch (patch->patch_op) {
|
|
+ case RTK_PATCH_OP_PHY:
|
|
+ reg = rtl826x_mmd_convert(pagemmd, addr);
|
|
+ if (mask != GENMASK(15, 0)) {
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, reg, mask,
|
|
+ data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ } else {
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, reg,
|
|
+ data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case RTK_PATCH_OP_PHY_WAIT:
|
|
+ case RTK_PATCH_OP_PHY_WAIT_NOT:
|
|
+ wait_equal = patch->patch_op == RTK_PATCH_OP_PHY_WAIT;
|
|
+ reg = rtl826x_mmd_convert(pagemmd, addr);
|
|
+ ret = rtl826x_phy_patch_wait(phydev, MDIO_MMD_VEND2, reg,
|
|
+ data << patch->lsb, mask, wait_equal);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ break;
|
|
+
|
|
+ case RTK_PATCH_OP_PHYOCP:
|
|
+ if (mask != GENMASK(15, 0)) {
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, addr, mask,
|
|
+ data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ } else {
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, addr,
|
|
+ data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case RTK_PATCH_OP_PHYOCP_BC62:
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbc62);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ val = (val >> 8) & 0x1f;
|
|
+ for (cnt = 0; cnt <= val; cnt++) {
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc62,
|
|
+ GENMASK(12, 8), cnt << 8);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case RTK_PATCH_OP_TOP:
|
|
+ if (addr < 16)
|
|
+ return -EINVAL;
|
|
+
|
|
+ reg = (pagemmd * 8) + (addr - 16);
|
|
+ if (mask != GENMASK(15, 0)) {
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg, mask,
|
|
+ data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ } else {
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case RTK_PATCH_OP_PSDS0:
|
|
+ ret = rtl826x_phy_patch_sds_modify(phydev, pagemmd, addr,
|
|
+ mask, data << patch->lsb);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ break;
|
|
+
|
|
+ case RTK_PATCH_OP_DELAY_MS:
|
|
+ msleep(data);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ phydev_err(phydev, "%s: %u not implemented yet!\n", __func__, patch->patch_op);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int rtl826x_phy_patch_db_init(struct phy_device *phydev, const char *fw_name)
|
|
+{
|
|
+ struct rtl826x_priv *priv = phydev->priv;
|
|
+ struct device *dev = &phydev->mdio.dev;
|
|
+ struct rtlgen_phy_patch_db *patch_db;
|
|
+ struct rtlgen_hwpatch *patches;
|
|
+ const struct firmware *fw;
|
|
+ size_t count;
|
|
+ int ret;
|
|
+
|
|
+ ret = request_firmware_direct(&fw, fw_name, dev);
|
|
+ if (ret) {
|
|
+ if (ret == -ENOENT) {
|
|
+ phydev_dbg(phydev, "Failed to request %s: defer probing\n", fw_name);
|
|
+ return -EPROBE_DEFER;
|
|
+ } else {
|
|
+ phydev_err(phydev, "Failed to request %s: %d\n", fw_name, ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!fw->size || fw->size % sizeof(*patches) != 0) {
|
|
+ phydev_err(phydev, "Invalid firmware size %zu for %s\n",
|
|
+ fw->size, fw_name);
|
|
+ ret = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ patches = (struct rtlgen_hwpatch *)fw->data;
|
|
+ count = fw->size / sizeof(*patches);
|
|
+
|
|
+ patch_db = devm_kzalloc(dev, struct_size(patch_db, conf, count), GFP_KERNEL);
|
|
+ if (!patch_db) {
|
|
+ ret = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ patch_db->count = count;
|
|
+ patch_db->patch_op_exec = rtl826x_phy_patch_op;
|
|
+
|
|
+ memcpy(patch_db->conf, patches, flex_array_size(patch_db, conf, count));
|
|
+
|
|
+ priv->patch = patch_db;
|
|
+
|
|
+out:
|
|
+ release_firmware(fw);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int rtl8261n_phy_patch_db_init(struct phy_device *phydev)
|
|
+{
|
|
+ return rtl826x_phy_patch_db_init(phydev, "rtl8261n.bin");
|
|
+}
|
|
+
|
|
+int rtl8264b_phy_patch_db_init(struct phy_device *phydev)
|
|
+{
|
|
+ return rtl826x_phy_patch_db_init(phydev, "rtl8264b.bin");
|
|
+}
|
|
+
|
|
+MODULE_FIRMWARE("rtl8261n.bin");
|
|
+MODULE_FIRMWARE("rtl8264b.bin");
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/realtek/phy_patch_rtl826x.h
|
|
@@ -0,0 +1,11 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+
|
|
+#ifndef __PHY_PATCH_RTL826X_H__
|
|
+#define __PHY_PATCH_RTL826X_H__
|
|
+
|
|
+#include <linux/phy.h>
|
|
+
|
|
+int rtl8261n_phy_patch_db_init(struct phy_device *phydev);
|
|
+int rtl8264b_phy_patch_db_init(struct phy_device *phydev);
|
|
+
|
|
+#endif
|