net: phy: dp83822: Add support for PHY LEDs on DP83822

The DP83822 supports up to three configurable Light Emitting Diode (LED)
pins: LED_0, LED_1 (GPIO1), COL (GPIO2) and RX_D3 (GPIO3). Several
functions can be multiplexed onto the LEDs for different modes of
operation. LED_0 and COL (GPIO2) use the MLED function. MLED can be routed
to only one of these two pins at a time. Add minimal LED controller driver
supporting the most common uses with the 'netdev' trigger.

Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20250107-dp83822-leds-v2-1-5b260aad874f@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Dimitri Fedrau
2025-01-07 09:23:04 +01:00
committed by Jakub Kicinski
parent 7dc8f809b8
commit c5a9657018
+275 -2
View File
@@ -30,6 +30,9 @@
#define MII_DP83822_FCSCR 0x14
#define MII_DP83822_RCSR 0x17
#define MII_DP83822_RESET_CTRL 0x1f
#define MII_DP83822_MLEDCR 0x25
#define MII_DP83822_LEDCFG1 0x460
#define MII_DP83822_IOCTRL1 0x462
#define MII_DP83822_IOCTRL2 0x463
#define MII_DP83822_GENCFG 0x465
#define MII_DP83822_SOR1 0x467
@@ -105,10 +108,26 @@
#define DP83822_RX_CLK_SHIFT BIT(12)
#define DP83822_TX_CLK_SHIFT BIT(11)
/* MLEDCR bits */
#define DP83822_MLEDCR_CFG GENMASK(6, 3)
#define DP83822_MLEDCR_ROUTE GENMASK(1, 0)
#define DP83822_MLEDCR_ROUTE_LED_0 DP83822_MLEDCR_ROUTE
/* LEDCFG1 bits */
#define DP83822_LEDCFG1_LED1_CTRL GENMASK(11, 8)
#define DP83822_LEDCFG1_LED3_CTRL GENMASK(7, 4)
/* IOCTRL1 bits */
#define DP83822_IOCTRL1_GPIO3_CTRL GENMASK(10, 8)
#define DP83822_IOCTRL1_GPIO3_CTRL_LED3 BIT(0)
#define DP83822_IOCTRL1_GPIO1_CTRL GENMASK(2, 0)
#define DP83822_IOCTRL1_GPIO1_CTRL_LED_1 BIT(0)
/* IOCTRL2 bits */
#define DP83822_IOCTRL2_GPIO2_CLK_SRC GENMASK(6, 4)
#define DP83822_IOCTRL2_GPIO2_CTRL GENMASK(2, 0)
#define DP83822_IOCTRL2_GPIO2_CTRL_CLK_REF GENMASK(1, 0)
#define DP83822_IOCTRL2_GPIO2_CTRL_MLED BIT(0)
#define DP83822_CLK_SRC_MAC_IF 0x0
#define DP83822_CLK_SRC_XI 0x1
@@ -117,6 +136,22 @@
#define DP83822_CLK_SRC_FREE_RUNNING 0x6
#define DP83822_CLK_SRC_RECOVERED 0x7
#define DP83822_LED_FN_LINK 0x0 /* Link established */
#define DP83822_LED_FN_RX_TX 0x1 /* Receive or Transmit activity */
#define DP83822_LED_FN_TX 0x2 /* Transmit activity */
#define DP83822_LED_FN_RX 0x3 /* Receive activity */
#define DP83822_LED_FN_COLLISION 0x4 /* Collision detected */
#define DP83822_LED_FN_LINK_100_BTX 0x5 /* 100 BTX link established */
#define DP83822_LED_FN_LINK_10_BT 0x6 /* 10BT link established */
#define DP83822_LED_FN_FULL_DUPLEX 0x7 /* Full duplex */
#define DP83822_LED_FN_LINK_RX_TX 0x8 /* Link established, blink for rx or tx activity */
#define DP83822_LED_FN_ACTIVE_STRETCH 0x9 /* Active Stretch Signal */
#define DP83822_LED_FN_MII_LINK 0xa /* MII LINK (100BT+FD) */
#define DP83822_LED_FN_LPI_MODE 0xb /* LPI Mode (EEE) */
#define DP83822_LED_FN_RX_TX_ERR 0xc /* TX/RX MII Error */
#define DP83822_LED_FN_LINK_LOST 0xd /* Link Lost */
#define DP83822_LED_FN_PRBS_ERR 0xe /* Blink for PRBS error */
/* SOR1 mode */
#define DP83822_STRAP_MODE1 0
#define DP83822_STRAP_MODE2 BIT(0)
@@ -145,6 +180,13 @@
ADVERTISED_FIBRE | \
ADVERTISED_Pause | ADVERTISED_Asym_Pause)
#define DP83822_MAX_LED_PINS 4
#define DP83822_LED_INDEX_LED_0 0
#define DP83822_LED_INDEX_LED_1_GPIO1 1
#define DP83822_LED_INDEX_COL_GPIO2 2
#define DP83822_LED_INDEX_RX_D3_GPIO3 3
struct dp83822_private {
bool fx_signal_det_low;
int fx_enabled;
@@ -154,6 +196,7 @@ struct dp83822_private {
struct ethtool_wolinfo wol;
bool set_gpio2_clk_out;
u32 gpio2_clk_out;
bool led_pin_enable[DP83822_MAX_LED_PINS];
};
static int dp83822_config_wol(struct phy_device *phydev,
@@ -418,6 +461,48 @@ static int dp83822_read_status(struct phy_device *phydev)
return 0;
}
static int dp83822_config_init_leds(struct phy_device *phydev)
{
struct dp83822_private *dp83822 = phydev->priv;
int ret;
if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_0]) {
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR,
DP83822_MLEDCR_ROUTE,
FIELD_PREP(DP83822_MLEDCR_ROUTE,
DP83822_MLEDCR_ROUTE_LED_0));
if (ret)
return ret;
} else if (dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2]) {
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL2,
DP83822_IOCTRL2_GPIO2_CTRL,
FIELD_PREP(DP83822_IOCTRL2_GPIO2_CTRL,
DP83822_IOCTRL2_GPIO2_CTRL_MLED));
if (ret)
return ret;
}
if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_1_GPIO1]) {
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL1,
DP83822_IOCTRL1_GPIO1_CTRL,
FIELD_PREP(DP83822_IOCTRL1_GPIO1_CTRL,
DP83822_IOCTRL1_GPIO1_CTRL_LED_1));
if (ret)
return ret;
}
if (dp83822->led_pin_enable[DP83822_LED_INDEX_RX_D3_GPIO3]) {
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL1,
DP83822_IOCTRL1_GPIO3_CTRL,
FIELD_PREP(DP83822_IOCTRL1_GPIO3_CTRL,
DP83822_IOCTRL1_GPIO3_CTRL_LED3));
if (ret)
return ret;
}
return 0;
}
static int dp83822_config_init(struct phy_device *phydev)
{
struct dp83822_private *dp83822 = phydev->priv;
@@ -437,6 +522,10 @@ static int dp83822_config_init(struct phy_device *phydev)
FIELD_PREP(DP83822_IOCTRL2_GPIO2_CLK_SRC,
dp83822->gpio2_clk_out));
err = dp83822_config_init_leds(phydev);
if (err)
return err;
if (phy_interface_is_rgmii(phydev)) {
rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0,
true);
@@ -631,6 +720,61 @@ static int dp83822_phy_reset(struct phy_device *phydev)
}
#ifdef CONFIG_OF_MDIO
static int dp83822_of_init_leds(struct phy_device *phydev)
{
struct device_node *node = phydev->mdio.dev.of_node;
struct dp83822_private *dp83822 = phydev->priv;
struct device_node *leds;
u32 index;
int err;
if (!node)
return 0;
leds = of_get_child_by_name(node, "leds");
if (!leds)
return 0;
for_each_available_child_of_node_scoped(leds, led) {
err = of_property_read_u32(led, "reg", &index);
if (err) {
of_node_put(leds);
return err;
}
if (index <= DP83822_LED_INDEX_RX_D3_GPIO3) {
dp83822->led_pin_enable[index] = true;
} else {
of_node_put(leds);
return -EINVAL;
}
}
of_node_put(leds);
/* LED_0 and COL(GPIO2) use the MLED function. MLED can be routed to
* only one of these two pins at a time.
*/
if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_0] &&
dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2]) {
phydev_err(phydev, "LED_0 and COL(GPIO2) cannot be used as LED output at the same time\n");
return -EINVAL;
}
if (dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2] &&
dp83822->set_gpio2_clk_out) {
phydev_err(phydev, "COL(GPIO2) cannot be used as LED outout, already used as clock output\n");
return -EINVAL;
}
if (dp83822->led_pin_enable[DP83822_LED_INDEX_RX_D3_GPIO3] &&
phydev->interface != PHY_INTERFACE_MODE_RMII) {
phydev_err(phydev, "RX_D3 can only be used as LED output when in RMII mode\n");
return -EINVAL;
}
return 0;
}
static int dp83822_of_init(struct phy_device *phydev)
{
struct dp83822_private *dp83822 = phydev->priv;
@@ -671,7 +815,7 @@ static int dp83822_of_init(struct phy_device *phydev)
dp83822->set_gpio2_clk_out = true;
}
return 0;
return dp83822_of_init_leds(phydev);
}
static int dp83826_to_dac_minus_one_regval(int percent)
@@ -769,7 +913,9 @@ static int dp83822_probe(struct phy_device *phydev)
if (ret)
return ret;
dp83822_of_init(phydev);
ret = dp83822_of_init(phydev);
if (ret)
return ret;
if (dp83822->fx_enabled)
phydev->port = PORT_FIBRE;
@@ -816,6 +962,130 @@ static int dp83822_resume(struct phy_device *phydev)
return 0;
}
static int dp83822_led_mode(u8 index, unsigned long rules)
{
switch (rules) {
case BIT(TRIGGER_NETDEV_LINK):
return DP83822_LED_FN_LINK;
case BIT(TRIGGER_NETDEV_LINK_10):
return DP83822_LED_FN_LINK_10_BT;
case BIT(TRIGGER_NETDEV_LINK_100):
return DP83822_LED_FN_LINK_100_BTX;
case BIT(TRIGGER_NETDEV_FULL_DUPLEX):
return DP83822_LED_FN_FULL_DUPLEX;
case BIT(TRIGGER_NETDEV_TX):
return DP83822_LED_FN_TX;
case BIT(TRIGGER_NETDEV_RX):
return DP83822_LED_FN_RX;
case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX):
return DP83822_LED_FN_RX_TX;
case BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR):
return DP83822_LED_FN_RX_TX_ERR;
case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX):
return DP83822_LED_FN_LINK_RX_TX;
default:
return -EOPNOTSUPP;
}
}
static int dp83822_led_hw_is_supported(struct phy_device *phydev, u8 index,
unsigned long rules)
{
int mode;
mode = dp83822_led_mode(index, rules);
if (mode < 0)
return mode;
return 0;
}
static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
unsigned long rules)
{
int mode;
mode = dp83822_led_mode(index, rules);
if (mode < 0)
return mode;
if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
FIELD_PREP(DP83822_MLEDCR_CFG, mode));
else if (index == DP83822_LED_INDEX_LED_1_GPIO1)
return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
MII_DP83822_LEDCFG1,
DP83822_LEDCFG1_LED1_CTRL,
FIELD_PREP(DP83822_LEDCFG1_LED1_CTRL,
mode));
else
return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
MII_DP83822_LEDCFG1,
DP83822_LEDCFG1_LED3_CTRL,
FIELD_PREP(DP83822_LEDCFG1_LED3_CTRL,
mode));
}
static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index,
unsigned long *rules)
{
int val;
if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2) {
val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR);
if (val < 0)
return val;
val = FIELD_GET(DP83822_MLEDCR_CFG, val);
} else {
val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_LEDCFG1);
if (val < 0)
return val;
if (index == DP83822_LED_INDEX_LED_1_GPIO1)
val = FIELD_GET(DP83822_LEDCFG1_LED1_CTRL, val);
else
val = FIELD_GET(DP83822_LEDCFG1_LED3_CTRL, val);
}
switch (val) {
case DP83822_LED_FN_LINK:
*rules = BIT(TRIGGER_NETDEV_LINK);
break;
case DP83822_LED_FN_LINK_10_BT:
*rules = BIT(TRIGGER_NETDEV_LINK_10);
break;
case DP83822_LED_FN_LINK_100_BTX:
*rules = BIT(TRIGGER_NETDEV_LINK_100);
break;
case DP83822_LED_FN_FULL_DUPLEX:
*rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX);
break;
case DP83822_LED_FN_TX:
*rules = BIT(TRIGGER_NETDEV_TX);
break;
case DP83822_LED_FN_RX:
*rules = BIT(TRIGGER_NETDEV_RX);
break;
case DP83822_LED_FN_RX_TX:
*rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);
break;
case DP83822_LED_FN_RX_TX_ERR:
*rules = BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR);
break;
case DP83822_LED_FN_LINK_RX_TX:
*rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) |
BIT(TRIGGER_NETDEV_RX);
break;
default:
*rules = 0;
break;
}
return 0;
}
#define DP83822_PHY_DRIVER(_id, _name) \
{ \
PHY_ID_MATCH_MODEL(_id), \
@@ -831,6 +1101,9 @@ static int dp83822_resume(struct phy_device *phydev)
.handle_interrupt = dp83822_handle_interrupt, \
.suspend = dp83822_suspend, \
.resume = dp83822_resume, \
.led_hw_is_supported = dp83822_led_hw_is_supported, \
.led_hw_control_set = dp83822_led_hw_control_set, \
.led_hw_control_get = dp83822_led_hw_control_get, \
}
#define DP83825_PHY_DRIVER(_id, _name) \