blob: 339bb78bbc8902f0a197206ffa54f3f874cf980c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* drivers/net/phy/realtek.c
*
* Driver for Realtek PHYs
*
* Author: Johnson Leung <r58129@freescale.com>
*
* Copyright (c) 2004 Freescale Semiconductor, Inc.
*/
#include <linux/bitops.h>
#include <linux/phy.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
//#LYNQ_MODFIY modify for task-1618 2025/6/10 start
#define DEVICE_NAME "mdio_misc"
struct rtl9000bf_mdio_dev {
struct phy_device *phydev;
struct mutex lock;
int rst_gpio;
//#LYNQ_MODFIY modify for task-1618 2025/6/19 start
int power_gpio;
//#LYNQ_MODFIY modify for task-1618 2025/6/19 end
};
struct mdio_reg_op {
uint32_t reg;
uint32_t val;
};
static struct rtl9000bf_mdio_dev *s_rtl9000bf_mdio_dev;
static u16 param_check[27] = {0, 0x8017, 0xFB03, 0, 0x8092, 0x6000, 0, 0x80AF, 0x6000, \
0, 0x807D, 0x4443, 0, 0x809A, 0x4443, \
0, 0x81A3, 0x0F00, 0x0A81, 0x12, 0x0004, 0, \
0x83C8, 0x0005, 0x0A5C, 0x12, 0x0003};
static u16 param_check2[5] = {0x401C, 0xA610, 0x8520, 0xA510, 0x21F4};
static u16 param_check3[3] = {0, 0xA000, 0x11EF};
#define RTL9000BF_INIT_TIMES_OUT 10
#define RTL9000BF_ACCESS_TIMES_OUT 10
//#LYNQ_MODFIY modify for task-1618 2025/6/10 end
#define RTL821x_PHYSR 0x11
#define RTL821x_PHYSR_DUPLEX BIT(13)
#define RTL821x_PHYSR_SPEED GENMASK(15, 14)
#define RTL821x_INER 0x12
#define RTL8211B_INER_INIT 0x6400
#define RTL8211E_INER_LINK_STATUS BIT(10)
#define RTL8211F_INER_LINK_STATUS BIT(4)
#define RTL821x_INSR 0x13
#define RTL821x_EXT_PAGE_SELECT 0x1e
#define RTL821x_PAGE_SELECT 0x1f
#define RTL8211F_INSR 0x1d
#define RTL8211F_TX_DELAY BIT(8)
#define RTL8211E_TX_DELAY BIT(1)
#define RTL8211E_RX_DELAY BIT(2)
#define RTL8211E_MODE_MII_GMII BIT(3)
#define RTL8201F_ISR 0x1e
#define RTL8201F_IER 0x13
#define RTL8366RB_POWER_SAVE 0x15
#define RTL8366RB_POWER_SAVE_ON BIT(12)
#define RTL_SUPPORTS_5000FULL BIT(14)
#define RTL_SUPPORTS_2500FULL BIT(13)
#define RTL_SUPPORTS_10000FULL BIT(0)
#define RTL_ADV_2500FULL BIT(7)
#define RTL_LPADV_10000FULL BIT(11)
#define RTL_LPADV_5000FULL BIT(6)
#define RTL_LPADV_2500FULL BIT(5)
#define RTLGEN_SPEED_MASK 0x0630
#define RTL_GENERIC_PHYID 0x001cc800
MODULE_DESCRIPTION("Realtek PHY driver");
MODULE_AUTHOR("Johnson Leung");
MODULE_LICENSE("GPL");
static int rtl821x_read_page(struct phy_device *phydev)
{
return __phy_read(phydev, RTL821x_PAGE_SELECT);
}
static int rtl821x_write_page(struct phy_device *phydev, int page)
{
return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
}
static int rtl8201_ack_interrupt(struct phy_device *phydev)
{
int err;
err = phy_read(phydev, RTL8201F_ISR);
return (err < 0) ? err : 0;
}
static int rtl821x_ack_interrupt(struct phy_device *phydev)
{
int err;
err = phy_read(phydev, RTL821x_INSR);
return (err < 0) ? err : 0;
}
static int rtl8211f_ack_interrupt(struct phy_device *phydev)
{
int err;
err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
return (err < 0) ? err : 0;
}
static int rtl8201_config_intr(struct phy_device *phydev)
{
u16 val;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
val = BIT(13) | BIT(12) | BIT(11);
else
val = 0;
return phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
}
static int rtl8211b_config_intr(struct phy_device *phydev)
{
int err;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
err = phy_write(phydev, RTL821x_INER,
RTL8211B_INER_INIT);
else
err = phy_write(phydev, RTL821x_INER, 0);
return err;
}
static int rtl8211e_config_intr(struct phy_device *phydev)
{
int err;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
err = phy_write(phydev, RTL821x_INER,
RTL8211E_INER_LINK_STATUS);
else
err = phy_write(phydev, RTL821x_INER, 0);
return err;
}
static int rtl8211f_config_intr(struct phy_device *phydev)
{
u16 val;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
val = RTL8211F_INER_LINK_STATUS;
else
val = 0;
return phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
}
static int rtl8211_config_aneg(struct phy_device *phydev)
{
int ret;
ret = genphy_config_aneg(phydev);
if (ret < 0)
return ret;
/* Quirk was copied from vendor driver. Unfortunately it includes no
* description of the magic numbers.
*/
if (phydev->speed == SPEED_100 && phydev->autoneg == AUTONEG_DISABLE) {
phy_write(phydev, 0x17, 0x2138);
phy_write(phydev, 0x0e, 0x0260);
} else {
phy_write(phydev, 0x17, 0x2108);
phy_write(phydev, 0x0e, 0x0000);
}
return 0;
}
static int rtl8211c_config_init(struct phy_device *phydev)
{
/* RTL8211C has an issue when operating in Gigabit slave mode */
return phy_set_bits(phydev, MII_CTRL1000,
CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
}
static int rtl8211f_config_init(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
u16 val;
int ret;
/* enable TX-delay for rgmii-{id,txid}, and disable it for rgmii and
* rgmii-rxid. The RX-delay can be enabled by the external RXDLY pin.
*/
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_RXID:
val = 0;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_TXID:
val = RTL8211F_TX_DELAY;
break;
default: /* the rest of the modes imply leaving delay as is. */
return 0;
}
ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY,
val);
if (ret < 0) {
dev_err(dev, "Failed to update the TX delay register\n");
return ret;
} else if (ret) {
dev_dbg(dev,
"%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
val ? "Enabling" : "Disabling");
} else {
dev_dbg(dev,
"2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
val ? "enabled" : "disabled");
}
return 0;
}
static int rtl8211e_config_init(struct phy_device *phydev)
{
int ret = 0, oldpage;
u16 val;
/* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII:
val = 0;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
val = RTL8211E_TX_DELAY | RTL8211E_RX_DELAY;
break;
case PHY_INTERFACE_MODE_RGMII_RXID:
val = RTL8211E_RX_DELAY;
break;
case PHY_INTERFACE_MODE_RGMII_TXID:
val = RTL8211E_TX_DELAY;
break;
default: /* the rest of the modes imply leaving delays as is. */
return 0;
}
/* According to a sample driver there is a 0x1c config register on the
* 0xa4 extension page (0x7) layout. It can be used to disable/enable
* the RX/TX delays otherwise controlled by RXDLY/TXDLY pins. It can
* also be used to customize the whole configuration register:
* 8:6 = PHY Address, 5:4 = Auto-Negotiation, 3 = Interface Mode Select,
* 2 = RX Delay, 1 = TX Delay, 0 = SELRGV (see original PHY datasheet
* for details).
*/
oldpage = phy_select_page(phydev, 0x7);
if (oldpage < 0)
goto err_restore_page;
ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0xa4);
if (ret)
goto err_restore_page;
ret = __phy_modify(phydev, 0x1c, RTL8211E_TX_DELAY | RTL8211E_RX_DELAY,
val);
err_restore_page:
return phy_restore_page(phydev, oldpage, ret);
}
static int rtl8211b_suspend(struct phy_device *phydev)
{
phy_write(phydev, MII_MMD_DATA, BIT(9));
return genphy_suspend(phydev);
}
static int rtl8211b_resume(struct phy_device *phydev)
{
phy_write(phydev, MII_MMD_DATA, 0);
return genphy_resume(phydev);
}
static int rtl8366rb_config_init(struct phy_device *phydev)
{
int ret;
ret = phy_set_bits(phydev, RTL8366RB_POWER_SAVE,
RTL8366RB_POWER_SAVE_ON);
if (ret) {
dev_err(&phydev->mdio.dev,
"error enabling power management\n");
}
return ret;
}
/* get actual speed to cover the downshift case */
static int rtlgen_get_speed(struct phy_device *phydev)
{
int val;
if (!phydev->link)
return 0;
val = phy_read_paged(phydev, 0xa43, 0x12);
if (val < 0)
return val;
switch (val & RTLGEN_SPEED_MASK) {
case 0x0000:
phydev->speed = SPEED_10;
break;
case 0x0010:
phydev->speed = SPEED_100;
break;
case 0x0020:
phydev->speed = SPEED_1000;
break;
case 0x0200:
phydev->speed = SPEED_10000;
break;
case 0x0210:
phydev->speed = SPEED_2500;
break;
case 0x0220:
phydev->speed = SPEED_5000;
break;
default:
break;
}
return 0;
}
static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
{
int ret;
if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
rtl821x_write_page(phydev, 0xa5c);
ret = __phy_read(phydev, 0x12);
rtl821x_write_page(phydev, 0);
} else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
rtl821x_write_page(phydev, 0xa5d);
ret = __phy_read(phydev, 0x10);
rtl821x_write_page(phydev, 0);
} else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) {
rtl821x_write_page(phydev, 0xa5d);
ret = __phy_read(phydev, 0x11);
rtl821x_write_page(phydev, 0);
} else {
ret = -EOPNOTSUPP;
}
return ret;
}
static int rtlgen_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
u16 val)
{
int ret;
if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
rtl821x_write_page(phydev, 0xa5d);
ret = __phy_write(phydev, 0x10, val);
rtl821x_write_page(phydev, 0);
} else {
ret = -EOPNOTSUPP;
}
return ret;
}
static int rtl8125_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
{
int ret = rtlgen_read_mmd(phydev, devnum, regnum);
if (ret != -EOPNOTSUPP)
return ret;
if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2) {
rtl821x_write_page(phydev, 0xa6e);
ret = __phy_read(phydev, 0x16);
rtl821x_write_page(phydev, 0);
} else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
rtl821x_write_page(phydev, 0xa6d);
ret = __phy_read(phydev, 0x12);
rtl821x_write_page(phydev, 0);
} else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2) {
rtl821x_write_page(phydev, 0xa6d);
ret = __phy_read(phydev, 0x10);
rtl821x_write_page(phydev, 0);
}
return ret;
}
static int rtl8125_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
u16 val)
{
int ret = rtlgen_write_mmd(phydev, devnum, regnum, val);
if (ret != -EOPNOTSUPP)
return ret;
if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
rtl821x_write_page(phydev, 0xa6d);
ret = __phy_write(phydev, 0x12, val);
rtl821x_write_page(phydev, 0);
}
return ret;
}
static int rtl8125_get_features(struct phy_device *phydev)
{
int val;
val = phy_read_paged(phydev, 0xa61, 0x13);
if (val < 0)
return val;
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->supported, val & RTL_SUPPORTS_2500FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
phydev->supported, val & RTL_SUPPORTS_5000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
phydev->supported, val & RTL_SUPPORTS_10000FULL);
return genphy_read_abilities(phydev);
}
static int rtl8125_config_aneg(struct phy_device *phydev)
{
int ret = 0;
if (phydev->autoneg == AUTONEG_ENABLE) {
u16 adv2500 = 0;
if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->advertising))
adv2500 = RTL_ADV_2500FULL;
ret = phy_modify_paged_changed(phydev, 0xa5d, 0x12,
RTL_ADV_2500FULL, adv2500);
if (ret < 0)
return ret;
}
return __genphy_config_aneg(phydev, ret);
}
static int rtl8125_read_status(struct phy_device *phydev)
{
if (phydev->autoneg == AUTONEG_ENABLE) {
int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
if (lpadv < 0)
return lpadv;
linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
phydev->lp_advertising, lpadv & RTL_LPADV_10000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
phydev->lp_advertising, lpadv & RTL_LPADV_5000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->lp_advertising, lpadv & RTL_LPADV_2500FULL);
}
return genphy_read_status(phydev);
}
static int rtl822x_config_init(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
int ret;
int reg;
switch (phydev->interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_2500BASEX:
break;
default:
return 0;
}
/* set serdes mode */
reg = phy_read_mmd(phydev, 30, 0x697a);
dev_info(dev, "MMD30:0x697a = 0x%x\n", reg);
/* change to 2500Base-X + SGMII mode*/
phy_modify_mmd_changed(phydev, 30, 0x75F3, 0x1, 0);
ret = phy_modify_mmd_changed(phydev, 30, 0x697a, 0x1f, 0x0);
if (ret < 0) {
dev_err(dev, "Failed to update serdes option mode\n");
return ret;
} else if (ret) {
dev_info(dev, "serdes option mode set\n");
/* make changes take effect */
phy_modify_mmd_changed(phydev, 31, 0xA400, 0x1 << 14, 0x1 << 14);
do {
reg = phy_read_mmd(phydev, 31, 0xA434);
if (reg & (0x1 << 2))
break;
} while(1);
phy_modify_mmd_changed(phydev, 31, 0xA400, 0x1 << 14, 0);
} else {
dev_info(dev, "serdes option mode already set\n");
}
/* disabled hisgmii auto-negotiation */
phy_write_mmd(phydev, 30, 0x7588, 0x2);
phy_write_mmd(phydev, 30, 0x7589, 0x71d0);
phy_write_mmd(phydev, 30, 0x7587, 0x3);
dev_info(dev, "start to wait disable hisgmii AN finish\n");
do {
reg = phy_read_mmd(phydev, 30, 0x7587);
if ((reg & 0x1) == 0)
break;
} while(1);
return 0;
}
static int rtl822x_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
{
int ret;
if (devnum == MDIO_MMD_VEND2) {
int page, regad;
page = regnum >> 4;
regad = 16 + (regnum % 16) / 2;
rtl821x_write_page(phydev, page);
ret = __phy_read(phydev, regad);
rtl821x_write_page(phydev, 0);
} else {
/* Write the desired MMD Devad */
__phy_write(phydev, MII_MMD_CTRL, devnum);
/* Write the desired MMD register address */
__phy_write(phydev, MII_MMD_DATA, regnum);
/* Select the Function : DATA with no post increment */
__phy_write(phydev, MII_MMD_CTRL, devnum | MII_MMD_CTRL_NOINCR);
/* Read the content of the MMD's selected register */
ret = __phy_read(phydev, MII_MMD_DATA);
}
return ret;
}
static int rtl822x_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
u16 val)
{
int ret;
if (devnum == MDIO_MMD_VEND2) {
int page, regad;
page = regnum >> 4;
regad = 16 + (regnum % 16) / 2;
rtl821x_write_page(phydev, page);
ret = __phy_write(phydev, regad, val);
rtl821x_write_page(phydev, 0);
} else {
/* Write the desired MMD Devad */
__phy_write(phydev, MII_MMD_CTRL, devnum);
/* Write the desired MMD register address */
__phy_write(phydev, MII_MMD_DATA, regnum);
/* Select the Function : DATA with no post increment */
__phy_write(phydev, MII_MMD_CTRL, devnum | MII_MMD_CTRL_NOINCR);
/* Write the data into MMD's selected register */
ret = __phy_write(phydev, MII_MMD_DATA, val);
}
return ret;
}
static int rtl822x_get_features(struct phy_device *phydev)
{
int val;
val = phy_read_paged(phydev, 0xa61, 0x13);
if (val < 0)
return val;
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->supported, val & RTL_SUPPORTS_2500FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
phydev->supported, val & RTL_SUPPORTS_5000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
phydev->supported, val & RTL_SUPPORTS_10000FULL);
return genphy_read_abilities(phydev);
}
static int rtl822x_config_aneg(struct phy_device *phydev)
{
int ret = 0;
if (phydev->autoneg == AUTONEG_ENABLE) {
u16 adv2500 = 0;
if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->advertising))
adv2500 = RTL_ADV_2500FULL;
ret = phy_modify_paged_changed(phydev, 0xa5d, 0x12,
RTL_ADV_2500FULL, adv2500);
if (ret < 0)
return ret;
}
return __genphy_config_aneg(phydev, ret);
}
static int rtlgen_get_serdes_mode(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
int ret;
ret = phy_read_mmd(phydev, 30, 0x7580);
if (ret < 0)
return ret;
switch (ret & 0x1f) {
case 0x2:
phydev->interface = PHY_INTERFACE_MODE_SGMII;
dev_dbg(dev, "==> Serdes mode: SGMII\n");
break;
case 0x12:
phydev->interface = PHY_INTERFACE_MODE_NA;
dev_dbg(dev, "==> Serdes mode: HiSGMII\n");
break;
case 0x16:
phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
dev_dbg(dev, "==> Serdes mode: 2500BASE-X\n");
break;
case 0x1f:
dev_dbg(dev, "==> Serdes mode: off\n");
break;
default:
break;
}
return 0;
}
static int rtl822x_read_status(struct phy_device *phydev)
{
int ret;
if (phydev->autoneg == AUTONEG_ENABLE) {
int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
if (lpadv < 0)
return lpadv;
linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
phydev->lp_advertising, lpadv & RTL_LPADV_10000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
phydev->lp_advertising, lpadv & RTL_LPADV_5000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->lp_advertising, lpadv & RTL_LPADV_2500FULL);
}
ret = genphy_read_status(phydev);
if (ret < 0)
return ret;
ret = rtlgen_get_serdes_mode(phydev);
if (ret < 0)
return ret;
return rtlgen_get_speed(phydev);
}
static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
{
int val;
phy_write(phydev, RTL821x_PAGE_SELECT, 0xa61);
val = phy_read(phydev, 0x13);
phy_write(phydev, RTL821x_PAGE_SELECT, 0);
return val >= 0 && val & RTL_SUPPORTS_2500FULL;
}
static int rtlgen_match_phy_device(struct phy_device *phydev)
{
return phydev->phy_id == RTL_GENERIC_PHYID &&
!rtlgen_supports_2_5gbps(phydev);
}
static int rtl8125_match_phy_device(struct phy_device *phydev)
{
return phydev->phy_id == RTL_GENERIC_PHYID &&
rtlgen_supports_2_5gbps(phydev);
}
static int rtlgen_resume(struct phy_device *phydev)
{
int ret = genphy_resume(phydev);
/* Internal PHY's from RTL8168h up may not be instantly ready */
msleep(20);
return ret;
}
//#LYNQ_MODFIY modify for task-1618 2025/6/10 start
static int mdio_misc_open(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t mdio_misc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct rtl9000bf_mdio_dev *dev = s_rtl9000bf_mdio_dev;
struct mdio_reg_op reg_op;
if (count < sizeof(struct mdio_reg_op))
return -EINVAL;
if (copy_from_user(&reg_op, buf, sizeof(struct mdio_reg_op)))
return -EFAULT;
mutex_lock(&dev->lock);
reg_op.val = phy_read(dev->phydev, reg_op.reg);
mutex_unlock(&dev->lock);
if (copy_to_user(buf, &reg_op, sizeof(struct mdio_reg_op)))
return -EFAULT;
return sizeof(reg_op);
}
static ssize_t mdio_misc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct rtl9000bf_mdio_dev *dev = s_rtl9000bf_mdio_dev;
struct mdio_reg_op reg_op;
int ret;
if (count < sizeof(struct mdio_reg_op))
return -EINVAL;
if (copy_from_user(&reg_op, buf, sizeof(struct mdio_reg_op)))
return -EFAULT;
mutex_lock(&dev->lock);
ret = phy_write(dev->phydev, reg_op.reg, reg_op.val);
mutex_unlock(&dev->lock);
*f_pos += count;
return ret ? ret : count;
}
static const struct file_operations mdio_misc_fops = {
.owner = THIS_MODULE,
.open = mdio_misc_open,
.read = mdio_misc_read,
.write = mdio_misc_write,
};
static struct miscdevice mdio_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &mdio_misc_fops,
};
static int mdio_misc_init(struct phy_device *phydev)
{
s_rtl9000bf_mdio_dev->phydev = phydev;
mutex_init(&s_rtl9000bf_mdio_dev->lock);
return misc_register(&mdio_misc_device);
}
static s16 RTL9000Bx_Initial_Configuration(struct phy_device *phydev)
{
u16 reg_data = 0;
u32 timer = 2000; // set a 2ms timer
//power down
phy_write(phydev, 0, 0x800);
mdelay(10);
phy_write(phydev, 27, 0x8017);
phy_write(phydev, 28, 0xFB03);
phy_write(phydev, 27, 0x8092);
phy_write(phydev, 28, 0x6000);
phy_write(phydev, 27, 0x80AF);
phy_write(phydev, 28, 0x6000);
phy_write(phydev, 27, 0x807D);
phy_write(phydev, 28, 0x4443);
phy_write(phydev, 27, 0x809A);
phy_write(phydev, 28, 0x4443);
phy_write(phydev, 27, 0x81A3);
phy_write(phydev, 28, 0x0F00);
phy_write(phydev, 31, 0x0a81);
phy_write(phydev, 18, 0x0004);
phy_write(phydev, 27, 0x83c8);
phy_write(phydev, 28, 0x0005);
phy_write(phydev, 31, 0x0a5c);
phy_write(phydev, 18, 0x0003);
phy_write(phydev, 27, 0xB820);
phy_write(phydev, 28, 0x0010);
phy_write(phydev, 27, 0xB830);
phy_write(phydev, 28, 0x8000);
phy_write(phydev, 27, 0xB800);
do {
reg_data = ((u16)phy_read(phydev, 28) & 0x0040);
timer--;
if (timer == 0)
{
return -1;
}
} while (reg_data != 0x0040);
phy_write(phydev, 27, 0x8020);
phy_write(phydev, 28, 0x3300);
phy_write(phydev, 27, 0xb82e);
phy_write(phydev, 28, 0x0001);
phy_write(phydev, 27, 0xb820);
phy_write(phydev, 28, 0x0290);
phy_write(phydev, 27, 0xa012);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0xa014);
phy_write(phydev, 28, 0x401c);
phy_write(phydev, 28, 0xa610);
phy_write(phydev, 28, 0x8520);
phy_write(phydev, 28, 0xa510);
phy_write(phydev, 28, 0x21f4);
phy_write(phydev, 27, 0xa01a);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0xa000);
phy_write(phydev, 28, 0x11ef);
phy_write(phydev, 27, 0xb820);
phy_write(phydev, 28, 0x0210);
phy_write(phydev, 27, 0xb82e);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0x8020);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0xb820);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0xb800);
timer = 2000; // set a 2ms timer
do {
reg_data = ((u16)phy_read(phydev, 28) & 0x0040);
timer--;
if (timer == 0) {
return -1;
}
} while (reg_data != 0x0000);
phy_write(phydev, 0, 0x2100); //power on
phy_write(phydev, 9, 0x0); //set slave mode
return 0;
}
static s16 RTL9000Bx_Initial_Configuration_Check(struct phy_device *phydev)
{
u16 reg_data = 0, reg_data_temp;
u16 reg_data_chk = 0;
u32 timer = 2000; // set a 2ms timer
u16 page;
u16 reg, i, param_address;
for (i = 0; i < 27; i = i + 3) {
page = param_check[i];
reg = param_check[i + 1];
reg_data_chk = param_check[i + 2];
if (page == 0) {
phy_write(phydev, 27, reg);
reg_data = phy_read(phydev, 28);
}
else {
phy_write(phydev, 31, page);
reg_data = phy_read(phydev, reg);
}
if (reg_data_chk != reg_data) {
printk(KERN_ERR "%dth param error page=0x%04X reg=0x%04X data=0x%04X\r\n", i / 3, page, reg, reg_data);
return -1;
}
}
phy_write(phydev, 27, 0xB820);
phy_write(phydev, 28, 0x0010);
phy_write(phydev, 27, 0xB830);
phy_write(phydev, 28, 0x8000);
phy_write(phydev, 27, 0xB800);
do {
reg_data = ((u16)phy_read(phydev, 28) & 0x0040);
timer--;
if (timer == 0){
return -1;
}
} while (reg_data != 0x0040);
phy_write(phydev, 27, 0x8020);
phy_write(phydev, 28, 0x3300);
phy_write(phydev, 27, 0xB82E);
phy_write(phydev, 28, 0x0001);
param_address = 0;
for (i = 0; i < 5; i++) {
phy_write(phydev, 31, 0xA01);
reg_data_temp = phy_read(phydev, 17);
reg_data_temp &= ~(0x1ff);
param_address &= 0x1FF;
phy_write(phydev, 17, (reg_data_temp | param_address));
phy_write(phydev, 31, 0xA01);
reg_data = phy_read(phydev, 18);
reg_data_chk = param_check2[i];
if (reg_data_chk != reg_data){
printk(KERN_ERR "%dth param error data=0x%04X expected_data=0x%04X\r\n", i, reg_data, reg_data_chk);
return -1;
}
param_address++;
}
for (i = 0; i < 3; i = i + 3) {
page = param_check3[i];
reg = param_check3[i + 1];
reg_data_chk = param_check3[i + 2];
if (page == 0) {
phy_write(phydev, 27, reg);
reg_data = phy_read(phydev, 28);
}
else {
phy_write(phydev, 31, page);
reg_data = phy_read(phydev, reg);
}
if (reg_data_chk != reg_data) {
printk(KERN_ERR "%dth param error page=0x%04X reg=0x%04X data=0x%04X\r\n", i / 3, page, reg, reg_data);
return -1;
}
}
phy_write(phydev, 27, 0xB82E);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0x8020);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0xB820);
phy_write(phydev, 28, 0x0000);
phy_write(phydev, 27, 0xB800);
timer = 2000; // set a 2ms timer
do {
reg_data = ((u16)phy_read(phydev, 28) & 0x0040);
timer--;
if (timer == 0){
return -1;
}
} while (reg_data != 0x0000);
return 0;
}
//#LYNQ_MODFIY modify for task-1618 2025/7/7 start
static void RTL9000Bx_xMII_driving_strength(struct phy_device *phydev)
{
// Typical_xMII_1V8
phy_write(phydev, 0x1B, 0xD414);
phy_write(phydev, 0x1C, 0x0201);
phy_write(phydev, 0x1B, 0xD416);
phy_write(phydev, 0x1C, 0x0101);
phy_write(phydev, 0x1B, 0xD418);
phy_write(phydev, 0x1C, 0x0200);
phy_write(phydev, 0x1B, 0xD41A);
phy_write(phydev, 0x1C, 0x0100);
phy_write(phydev, 0x1B, 0xD42E);
phy_write(phydev, 0x1C, 0xC8C8);
}
static s16 RTL9000Bx_Soft_Reset(struct phy_device *phydev)
{
u16 reg_data = 0;
u32 timer = 2000; // set a 2ms timer
phy_write(phydev, 0, 0x8000); // PHY soft-reset
do
{ // Check soft-reset complete
reg_data = phy_read(phydev, 0);
if (reg_data == 0xFFFF)
return -1;
timer--;
if (timer == 0)
{
return -1;
}
} while (reg_data != 0x2100);
return 0;
}
//#LYNQ_MODFIY modify for task-1618 2025/7/7 end
//#LYNQ_MODFIY modify for task-1618 2025/6/19 satrt
static int rtl9000Bf_power_set(int gpio, int power_en)
{
int ret;
ret = gpio_direction_output(gpio, power_en);
mdelay(10);
return ret;
}
static void rtl9000Bf_reset(int gpio)
{
//#LYNQ_MODFIY modify for task-1618 2025/6/24 start
gpio_direction_output(gpio, 0);
mdelay(10);
gpio_set_value(gpio, 1);
mdelay(10);
//#LYNQ_MODFIY modify for task-1618 2025/6/24 end
}
static int rtl9000Bf_config_init(struct phy_device *phydev)
{
u32 mdio_data = 0;
u32 ret;
int times= 1;
struct device_node *np;
np = phydev->mdio.dev.of_node;
if (!np) {
dev_err(&phydev->mdio.dev, "No device tree node found\n");
return -ENODEV;
}
if (!s_rtl9000bf_mdio_dev) {
s_rtl9000bf_mdio_dev = kzalloc(sizeof(*s_rtl9000bf_mdio_dev), GFP_KERNEL);
if (!s_rtl9000bf_mdio_dev)
return -ENOMEM;
s_rtl9000bf_mdio_dev->rst_gpio = of_get_named_gpio(np, "rst-gpio", 0);
if (s_rtl9000bf_mdio_dev->rst_gpio < 0) {
dev_err(&phydev->mdio.dev, "Failed to get reset gpio: %d\n", s_rtl9000bf_mdio_dev->rst_gpio);
kfree(s_rtl9000bf_mdio_dev);
s_rtl9000bf_mdio_dev = NULL;
return s_rtl9000bf_mdio_dev->rst_gpio;
}
gpio_request(s_rtl9000bf_mdio_dev->rst_gpio, "phy-reset");
s_rtl9000bf_mdio_dev->power_gpio = of_get_named_gpio(np, "power-en-gpio", 0);
if (s_rtl9000bf_mdio_dev->power_gpio < 0) {
dev_err(&phydev->mdio.dev, "Failed to get power gpio: %d\n", s_rtl9000bf_mdio_dev->power_gpio);
kfree(s_rtl9000bf_mdio_dev);
s_rtl9000bf_mdio_dev = NULL;
return s_rtl9000bf_mdio_dev->power_gpio;
}
ret = mdio_misc_init(phydev);
if(ret){
goto err;
}
gpio_request(s_rtl9000bf_mdio_dev->power_gpio, "phy-power");
}
rtl9000Bf_power_set(s_rtl9000bf_mdio_dev->power_gpio, 1);
rtl9000Bf_reset(s_rtl9000bf_mdio_dev->rst_gpio);
//check PHY accessible
while(times <= RTL9000BF_ACCESS_TIMES_OUT) {
mdio_data = phy_read(phydev, 0x10);
mdio_data = mdio_data & 0x0007;
if(mdio_data == 0x0003) {
printk(KERN_INFO "phy_info: phy is accessible \n");
break;
} else {
printk(KERN_INFO "phy_info: phy is not accessible times:%d \n", times);
rtl9000Bf_reset(s_rtl9000bf_mdio_dev->rst_gpio);
}
times++;
mdelay(1000);
}
if(times >= RTL9000BF_ACCESS_TIMES_OUT) {
printk(KERN_ERR "phy_info: phy init error times:%d \n", times);
ret = -1;
goto err;
}
times = 1;
//init phy init and check phy init
while(times <= RTL9000BF_INIT_TIMES_OUT) {
ret = RTL9000Bx_Initial_Configuration(phydev);
if(0 == ret) {
printk(KERN_INFO "phy_info: phy init success times:%d \n", times);
ret = RTL9000Bx_Initial_Configuration_Check(phydev);
if(0 == ret) {
printk(KERN_INFO "phy_info: phy init_check success times:%d \n", times);
break;
} else {
printk(KERN_INFO "phy_info: phy init_check error times:%d \n", times);
}
} else {
printk(KERN_INFO "phy_info: phy init error times:%d \n", times);
//phy hard reset
rtl9000Bf_reset(s_rtl9000bf_mdio_dev->rst_gpio);
}
times++;
}
if(times >= RTL9000BF_INIT_TIMES_OUT) {
printk(KERN_ERR "phy_info: phy init error times:%d \n", times);
ret = -1;
goto err;
}
//#LYNQ_MODFIY modify for task-1618 2025/7/7 start
RTL9000Bx_xMII_driving_strength(phydev);
/* I/O Power Sllection */
//change page to default value
phy_write(phydev, 0x1F, 0x0A4C);
mdio_data = phy_read(phydev, 0x12);
mdio_data &= 0xC7FF;
mdio_data |= 4 << 11;
phy_write(phydev, 0x12, mdio_data);
printk(KERN_INFO "phy_info: set rgmii driving strengths is 1.8v \n");
ret = RTL9000Bx_Soft_Reset(phydev);
if(0 != ret) {
goto err;
}
printk(KERN_INFO "phy_info: phy soft-reset over \n");
//#LYNQ_MODFIY modify for task-1618 2025/7/7 end
phydev->autoneg = AUTONEG_DISABLE;
phydev->duplex = DUPLEX_FULL;
return 0;
err:
gpio_free(s_rtl9000bf_mdio_dev->rst_gpio);
gpio_free(s_rtl9000bf_mdio_dev->power_gpio);
kfree(s_rtl9000bf_mdio_dev);
s_rtl9000bf_mdio_dev = NULL;
return ret;
}
//#LYNQ_MODFIY modify for task-1618 2025/6/19 end
static void rtl9000Bf_remove(struct phy_device *phydev)
{
if (s_rtl9000bf_mdio_dev && s_rtl9000bf_mdio_dev->phydev == phydev) {
misc_deregister(&mdio_misc_device);
gpio_free(s_rtl9000bf_mdio_dev->rst_gpio);
//#LYNQ_MODFIY modify for task-1618 2025/6/19 start
gpio_free(s_rtl9000bf_mdio_dev->power_gpio);
//#LYNQ_MODFIY modify for task-1618 2025/6/19 end
kfree(s_rtl9000bf_mdio_dev);
s_rtl9000bf_mdio_dev = NULL;
}
}
static int rtl9000Bf_read_status(struct phy_device *phydev)
{
phydev->duplex = DUPLEX_FULL;
phydev->speed = SPEED_100;
phydev->pause = 0;
phydev->asym_pause = 0;
phydev->link = 1;
return 0;
}
static int rtl9000Bf_soft_reset(struct phy_device *phydev)
{
return 0;
}
//#LYNQ_MODFIY modify for task-1618 2025/6/10 end
//#LYNQ_MODFIY modify for task-1618 2025/6/24 start
static int rtl9000Bf_suspend(struct phy_device *phydev)
{
int ret;
ret = gpio_direction_output(s_rtl9000bf_mdio_dev->rst_gpio, 0);
if(ret != 0){
printk(KERN_ERR "phy_error:reset phy error\n");
return ret;
}
mdelay(10);
return gpio_direction_output(s_rtl9000bf_mdio_dev->power_gpio, 0);
}
//#LYNQ_MODFIY modify for task-1618 2025/6/24 end
static struct phy_driver realtek_drvs[] = {
{
PHY_ID_MATCH_EXACT(0x00008201),
.name = "RTL8201CP Ethernet",
}, {
PHY_ID_MATCH_EXACT(0x001cc816),
.name = "RTL8201F Fast Ethernet",
.ack_interrupt = &rtl8201_ack_interrupt,
.config_intr = &rtl8201_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_MODEL(0x001cc880),
.name = "RTL8208 Fast Ethernet",
.read_mmd = genphy_read_mmd_unsupported,
.write_mmd = genphy_write_mmd_unsupported,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_EXACT(0x001cc910),
.name = "RTL8211 Gigabit Ethernet",
.config_aneg = rtl8211_config_aneg,
.read_mmd = &genphy_read_mmd_unsupported,
.write_mmd = &genphy_write_mmd_unsupported,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_EXACT(0x001cc912),
.name = "RTL8211B Gigabit Ethernet",
.ack_interrupt = &rtl821x_ack_interrupt,
.config_intr = &rtl8211b_config_intr,
.read_mmd = &genphy_read_mmd_unsupported,
.write_mmd = &genphy_write_mmd_unsupported,
.suspend = rtl8211b_suspend,
.resume = rtl8211b_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_EXACT(0x001cc913),
.name = "RTL8211C Gigabit Ethernet",
.config_init = rtl8211c_config_init,
.read_mmd = &genphy_read_mmd_unsupported,
.write_mmd = &genphy_write_mmd_unsupported,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_EXACT(0x001cc914),
.name = "RTL8211DN Gigabit Ethernet",
.ack_interrupt = rtl821x_ack_interrupt,
.config_intr = rtl8211e_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_EXACT(0x001cc915),
.name = "RTL8211E Gigabit Ethernet",
.config_init = &rtl8211e_config_init,
.ack_interrupt = &rtl821x_ack_interrupt,
.config_intr = &rtl8211e_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
PHY_ID_MATCH_EXACT(0x001cc916),
.name = "RTL8211F Gigabit Ethernet",
.config_init = &rtl8211f_config_init,
.ack_interrupt = &rtl8211f_ack_interrupt,
.config_intr = &rtl8211f_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
.name = "Generic FE-GE Realtek PHY",
.match_phy_device = rtlgen_match_phy_device,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
.read_mmd = rtlgen_read_mmd,
.write_mmd = rtlgen_write_mmd,
}, {
.name = "RTL8125 2.5Gbps internal",
.match_phy_device = rtl8125_match_phy_device,
.get_features = rtl8125_get_features,
.config_aneg = rtl8125_config_aneg,
.read_status = rtl8125_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
.read_mmd = rtl8125_read_mmd,
.write_mmd = rtl8125_write_mmd,
}, {
PHY_ID_MATCH_EXACT(0x001cc961),
.name = "RTL8366RB Gigabit Ethernet",
.config_init = &rtl8366rb_config_init,
/* These interrupts are handled by the irq controller
* embedded inside the RTL8366RB, they get unmasked when the
* irq is requested and ACKed by reading the status register,
* which is done by the irqchip code.
*/
.ack_interrupt = genphy_no_ack_interrupt,
.config_intr = genphy_no_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
PHY_ID_MATCH_EXACT(0x001cc849),
.name = "RTL8221B-VB-CG 2.5Gbps PHY",
.config_init = &rtl822x_config_init,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.read_status = rtl822x_read_status,
.suspend = genphy_suspend,
.resume = rtlgen_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
.read_mmd = rtl822x_read_mmd,
.write_mmd = rtl822x_write_mmd,
}, {
PHY_ID_MATCH_EXACT(0x001cc84a),
.name = "RTL8221B-VM-CG 2.5Gbps PHY",
.config_init = &rtl822x_config_init,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.read_status = rtl822x_read_status,
.suspend = genphy_suspend,
.resume = rtlgen_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
.read_mmd = rtl822x_read_mmd,
.write_mmd = rtl822x_write_mmd,
},
//#LYNQ_MODFIY modify for task-1618 2025/6/10 start
{
PHY_ID_MATCH_EXACT(0x001ccb00),
.name = "RTL90000Bf PHY",
.config_init = &rtl9000Bf_config_init,
.remove = &rtl9000Bf_remove,
.read_status = rtl9000Bf_read_status,
//#LYNQ_MODFIY modify for task-1618 2025/6/19 start
.suspend = rtl9000Bf_suspend,
//#LYNQ_MODFIY modify for task-1618 2025/6/19 end
.soft_reset = rtl9000Bf_soft_reset,
},
//#LYNQ_MODFIY modify for task-1618 2025/6/10 end
};
module_phy_driver(realtek_drvs);
static const struct mdio_device_id __maybe_unused realtek_tbl[] = {
{ PHY_ID_MATCH_VENDOR(0x001cc800) },
//#LYNQ_MODFIY modify for task-1618 2025/6/10 start
{ 0x001ccb00, 0x001fffff },
//#LYNQ_MODFIY modify for task-1618 2025/6/10 end
{ }
};
MODULE_DEVICE_TABLE(mdio, realtek_tbl);