Merge branch 'net-phy-motorcomm-add-cable-test-support-for-internal-yt861x-phys'

David Yang says:

====================
net: phy: motorcomm: Add cable test support for internal YT861x PHYs

The YT921x switches integrate GbE PHYs of the YT861x family (PHY ID
0x01e04281) on the switch-internal MDIO bus exposed by the yt921x DSA
driver. Add a driver entry and TDR cable test support.
====================

Link: https://patch.msgid.link/20261009175936.428256-1-mmyangfl@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-10-15 17:59:23 -07:00
commit 3268b2c2fd
3 changed files with 153 additions and 3 deletions

View File

@ -15,6 +15,7 @@ allOf:
properties:
compatible:
enum:
- ethernet-phy-id01e0.4281
- ethernet-phy-id4f51.e91a
- ethernet-phy-id4f51.e91b

View File

@ -363,7 +363,8 @@ config MOTORCOMM_PHY
tristate "Motorcomm PHYs"
help
Enables support for Motorcomm network PHYs.
Currently supports YT85xx Gigabit Ethernet PHYs.
Currently supports YT85xx Gigabit Ethernet PHYs, YT861x PHYs
integrated in YT921x switches, and YT8821 2.5Gbps PHYs.
config NATIONAL_PHY
tristate "National Semiconductor PHYs"

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Motorcomm 8511/8521/8522/8531/8531S/8821 PHY driver.
* Motorcomm 8511/8521/8522/8531/8531S/861x/8821 PHY driver.
*
* Author: Peter Geis <pgwipeout@gmail.com>
* Author: Frank <Frank.Sae@motor-comm.com>
@ -8,6 +8,7 @@
#include <linux/clk.h>
#include <linux/etherdevice.h>
#include <linux/ethtool_netlink.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/phy.h>
@ -18,6 +19,8 @@
#define PHY_ID_YT8522 0x4f51e928
#define PHY_ID_YT8531 0x4f51e91b
#define PHY_ID_YT8531S 0x4f51e91a
/* Live captured from YT921x switch internal MDIO bus */
#define PHY_ID_INT861X 0x01e04281
#define PHY_ID_YT8821 0x4f51ea19
/* YT8521/YT8531S/YT8821 Register Overview
* UTP Register space | FIBER Register space
@ -298,6 +301,19 @@
#define YT8531_SCR_CLK_SRC_REF_25M 4
#define YT8531_SCR_CLK_SRC_SSC_25M 5
/* TDR (cable diagnostic) test control */
#define YT861X_TDR_CTRL_REG 0x80
#define YT861X_TDR_CTRL_START BIT(0)
#define YT861X_TDR_STATUS_REG 0x84
#define YT861X_TDR_STATUS_BUSY BIT(15)
#define YT861X_TDR_STATUS_PAIR_OK 0
#define YT861X_TDR_STATUS_PAIR_UNKNOWN 1
#define YT861X_TDR_STATUS_PAIR_SHORT 2
#define YT861X_TDR_STATUS_PAIR_OPEN 3
#define YT861X_TDR_PAIR_LENGTH_REG(n) (0x87 + (n)) /* in cm */
#define YT8821_SDS_EXT_CSR_CTRL_REG 0x23
#define YT8821_SDS_EXT_CSR_VCO_LDO_EN BIT(15)
#define YT8821_SDS_EXT_CSR_VCO_BIAS_LPF_EN BIT(8)
@ -2538,6 +2554,126 @@ static int yt8521_get_features(struct phy_device *phydev)
return ret;
}
/**
* yt861x_config_init() - apply the auto sleep setting to the PHY
* @phydev: a pointer to a &struct phy_device
*
* Auto sleep is enabled by default and a BMCR soft reset does not restore it,
* so apply the setting from config_init. It can be disabled with the
* "motorcomm,auto-sleep-disabled" device property.
*
* Returns: 0 or negative errno code
*/
static int yt861x_config_init(struct phy_device *phydev)
{
bool sleep;
sleep = !device_property_read_bool(&phydev->mdio.dev,
"motorcomm,auto-sleep-disabled");
return ytphy_modify_ext_with_lock(phydev,
YT8521_EXTREG_SLEEP_CONTROL1_REG,
YT8521_ESC1R_SLEEP_SW,
sleep ? YT8521_ESC1R_SLEEP_SW : 0);
}
/**
* yt861x_cable_test_start() - start a cable diagnostic (TDR) test
* @phydev: a pointer to a &struct phy_device
*
* Returns: 0 or negative errno code
*/
static int yt861x_cable_test_start(struct phy_device *phydev)
{
int ret;
/* auto sleep would abort the TDR test */
ret = ytphy_modify_ext_with_lock(phydev,
YT8521_EXTREG_SLEEP_CONTROL1_REG,
YT8521_ESC1R_SLEEP_SW, 0);
if (ret)
return ret;
ret = ytphy_write_ext_with_lock(phydev, YT861X_TDR_CTRL_REG,
YT861X_TDR_CTRL_START);
if (ret) {
int err = yt861x_config_init(phydev);
if (err)
phydev_warn(phydev,
"Failed to restore auto sleep: %d\n",
err);
}
return ret;
}
/**
* yt861x_cable_test_get_status() - report cable diagnostic test results
* @phydev: a pointer to a &struct phy_device
* @finished: set to true when the test is complete
*
* Returns: 0 or negative errno code
*/
static int yt861x_cable_test_get_status(struct phy_device *phydev,
bool *finished)
{
int status;
int ret;
*finished = false;
status = ytphy_read_ext_with_lock(phydev, YT861X_TDR_STATUS_REG);
if (status < 0)
return status;
if (status & YT861X_TDR_STATUS_BUSY)
return 0;
for (int pair = ETHTOOL_A_CABLE_PAIR_A; pair <= ETHTOOL_A_CABLE_PAIR_D;
pair++) {
u8 code;
switch ((status >> (2 * pair)) & 0x3) {
case YT861X_TDR_STATUS_PAIR_OK:
code = ETHTOOL_A_CABLE_RESULT_CODE_OK;
break;
case YT861X_TDR_STATUS_PAIR_SHORT:
code = ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
break;
case YT861X_TDR_STATUS_PAIR_OPEN:
code = ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
break;
default:
code = ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
}
ethnl_cable_test_result(phydev, pair, code);
if (code != ETHTOOL_A_CABLE_RESULT_CODE_OK &&
code != ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC) {
ret = ytphy_read_ext_with_lock(phydev,
YT861X_TDR_PAIR_LENGTH_REG(pair));
if (ret >= 0)
ethnl_cable_test_fault_length(phydev, pair,
ret);
}
}
/* the reset below does not restore auto sleep by itself */
ret = yt861x_config_init(phydev);
if (ret)
return ret;
ret = genphy_soft_reset(phydev);
if (ret)
return ret;
*finished = true;
return 0;
}
/**
* yt8821_get_features - read mmd register to get 2.5G capability
* @phydev: target phy_device struct
@ -3173,6 +3309,17 @@ static struct phy_driver motorcomm_phy_drvs[] = {
.suspend = yt8521_suspend,
.resume = yt8521_resume,
},
{
PHY_ID_MATCH_EXACT(PHY_ID_INT861X),
.name = "YT861x Internal Gigabit Ethernet",
.flags = PHY_IS_INTERNAL,
.config_init = yt861x_config_init,
.soft_reset = genphy_soft_reset,
.suspend = genphy_suspend,
.resume = genphy_resume,
.cable_test_start = yt861x_cable_test_start,
.cable_test_get_status = yt861x_cable_test_get_status,
},
{
PHY_ID_MATCH_EXACT(PHY_ID_YT8821),
.name = "YT8821 2.5Gbps PHY",
@ -3194,7 +3341,7 @@ static struct phy_driver motorcomm_phy_drvs[] = {
module_phy_driver(motorcomm_phy_drvs);
MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821 PHY driver");
MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/861x/8821 PHY driver");
MODULE_AUTHOR("Peter Geis");
MODULE_AUTHOR("Frank");
MODULE_LICENSE("GPL");
@ -3205,6 +3352,7 @@ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8522) },
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531) },
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
{ PHY_ID_MATCH_EXACT(PHY_ID_INT861X) },
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8821) },
{ /* sentinel */ }
};