rtl83xx: configure leds based on device-tree configuration

Reads the port- and port-led configuration from the device-tree and configures the
switch soc to represent the requested initial configuration.

Signed-off-by: Peter Körner <peter@mazdermind.de>
This commit is contained in:
Peter Körner 2024-10-12 23:56:14 +02:00
parent fbb5bce971
commit b079e0b5b8
4 changed files with 203 additions and 15 deletions

View File

@ -254,6 +254,7 @@ static int rtl83xx_setup(struct dsa_switch *ds)
pr_debug("Please wait until PHY is settled\n");
msleep(1000);
priv->r->pie_init(priv);
priv->r->led_init(priv);
return 0;
}

View File

@ -1,8 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <asm/mach-rtl838x/mach-rtl83xx.h>
#include <dt-bindings/leds/rtl838x-leds.h>
#include <linux/etherdevice.h>
#include <linux/iopoll.h>
#include <linux/leds.h>
#include <net/nexthop.h>
#include "rtl83xx.h"
@ -1687,6 +1689,176 @@ void rtl838x_set_receive_management_action(int port, rma_ctrl_t type, action_typ
}
}
static void rtl838x_led_analyze_portgroup_config(struct rtl838x_switch_priv *priv, struct fwnode_handle *port, struct fwnode_handle *port_leds, struct rtl838x_portgroup_led_config *portgroup_config) {
struct fwnode_handle *led;
u32 port_index = 0;
fwnode_property_read_u32(port, "reg", &port_index);
dev_dbg(priv->dev, "Analyzing led-config of port %d", port_index);
fwnode_for_each_child_node(port_leds, led) {
u32 led_index = 0;
u32 trigger = RTL838X_LEDS_TRIGGER_DISABLED;
fwnode_property_read_u32(led, "reg", &led_index);
if(led_index > 3) {
dev_warn(priv->dev, "Led %d of Port %d out of range for led configuration, ignoring", led_index, port_index);
goto next;
}
fwnode_property_read_u32(led, "realtek,port-led-trigger", &trigger);
dev_dbg(priv->dev, "Found led-config of led %d of port %d with port-led-trigger %08x", led_index, port_index, trigger);
portgroup_config->installed_leds_mask |= BIT(led_index);
portgroup_config->led_triggers[led_index] = trigger;
next:
if(led) fwnode_handle_put(led);
}
}
static void rtl838x_led_analyze_config(struct rtl838x_switch_priv *priv, struct fwnode_handle *ports, struct rtl838x_led_config *led_config) {
struct fwnode_handle *port;
u32 port_index = 0;
bool low_ports_done = false, high_ports_done = false;
dev_dbg(priv->dev, "Analyzing led-config");
fwnode_for_each_child_node(ports, port) {
struct fwnode_handle *port_leds;
bool is_low_port;
fwnode_property_read_u32(port, "reg", &port_index);
if(port_index > 27) {
// port 28 is the cpu-port
dev_dbg(priv->dev, "Port %d out of range for led configuration, ignoring", port_index);
goto next;
}
is_low_port = (port_index < 24);
port_leds = fwnode_get_named_child_node(port, "leds");
if (!port_leds) {
dev_dbg(priv->dev, "No leds node for port %d, ignoring", port_index);
goto next;
}
led_config->enabled_ports_mask |= BIT(port_index);
// the leds are configured in two groups: low (port 0-23, copper) and high (ports 24-27, fiber/dual personality)
// for all ports in a group only one setting can be applied. We select the one from the device tree per group as
// representative and use its configuration. For the rtl838x devices it is expected that all ports in a group are
// configured the same way (same leds, same colors).
if(is_low_port) {
if(!low_ports_done) {
dev_dbg(priv->dev, "Analyzing led-config of port %d as representative of the low ports", port_index);
rtl838x_led_analyze_portgroup_config(priv, port, port_leds, &led_config->low_ports);
}
low_ports_done = true;
}
else {
if(!high_ports_done) {
dev_dbg(priv->dev, "Analyzing led-config of port %d as representative of the high ports", port_index);
rtl838x_led_analyze_portgroup_config(priv, port, port_leds, &led_config->high_ports);
}
high_ports_done = true;
}
next:
if(port_leds) fwnode_handle_put(port_leds);
if(port) fwnode_handle_put(port);
}
}
// initializes the port-leds to a known state according to the device tree
static void rtl838x_led_init(struct rtl838x_switch_priv *priv) {
struct fwnode_handle *global_port_leds_config, *ports;
// rtl838x suppoprts multiple ways to connect the leds to the SoC
// this value is loaded from the device tree.
u32 led_control_mode = RTL838X_LEDS_CONTROL_MODE_DISABLED;
// perform a single blink of all LEDs on startup
u32 power_on_blink = true;
// LED cotnrol registers of the rtl838x
// see https://svanheule.net/realtek/maple/feature/led
// this init routine calculates the correct values for these registers by
// looking at the device tree and finally sets them accordingly.
u32 led_mode_sel = 0x0, led_mode_ctrl = 0x0;
u32 led_glb_ctrl = 0x0, led_p_en_ctrl = 0x0;
// initial led modes
struct rtl838x_led_config led_config = {0};
// global leds configuration
global_port_leds_config = device_get_named_child_node(priv->dev, "port-leds");
if (!global_port_leds_config) {
dev_info(priv->dev, "No port-leds node in device-tree, not configuring port-leds");
goto out;
}
fwnode_property_read_u32(global_port_leds_config, "led-control-mode", &led_control_mode);
if(led_control_mode == RTL838X_LEDS_CONTROL_MODE_DISABLED) {
dev_info(priv->dev, "led_control_mode == DISABLED in device-tree, not configuring port-leds");
goto out;
}
ports = device_get_named_child_node(priv->dev, "ports");
if (!ports) {
dev_info(priv->dev, "No ports node in device-tree, not configuring port-leds");
goto out;
}
// analyze port-leds configuration
rtl838x_led_analyze_config(priv, ports, &led_config);
dev_dbg(priv->dev, "Analyzed configuraton in device-tree: "
"enabled_ports_mask=%08x "
"installed_leds_mask=%08x / %08x "
"led_triggers=[%d,%d,%d] / [%d,%d,%d]",
led_config.enabled_ports_mask,
led_config.low_ports.installed_leds_mask,
led_config.high_ports.installed_leds_mask,
led_config.low_ports.led_triggers[0], led_config.low_ports.led_triggers[1], led_config.low_ports.led_triggers[2],
led_config.high_ports.led_triggers[0], led_config.high_ports.led_triggers[1], led_config.high_ports.led_triggers[2]);
// calculate register values
led_glb_ctrl =
// using low_ports twice here is not an accident -- according to the docs the enabled-leds for the high ports
// should be the same as for the low ports, even if the high ports are not used, and in fact setting
// the high-port led-count to 0 results in a broken led configuration, even on a device without high ports.
(led_config.low_ports.installed_leds_mask << 3) |
(led_config.low_ports.installed_leds_mask << 0);
led_mode_sel = (power_on_blink << 2) | led_control_mode;
led_mode_ctrl =
(led_config.high_ports.led_triggers[2] << 25) |
(led_config.high_ports.led_triggers[1] << 20) |
(led_config.high_ports.led_triggers[0] << 15) |
(led_config.low_ports.led_triggers[2] << 10) |
(led_config.low_ports.led_triggers[1] << 5) |
(led_config.low_ports.led_triggers[0] << 0);
led_p_en_ctrl = led_config.enabled_ports_mask;
dev_dbg(priv->dev, "led_glb_ctrl=%08x/%08x led_mode_sel=%08x "
"led_p_en_ctrl=%08x led_mode_ctrl=%08x",
led_glb_ctrl, (u32)GENMASK(5, 0), led_mode_sel,
led_p_en_ctrl, led_mode_ctrl);
// write config registers in the right order
dev_info(priv->dev, "Configuring leds");
sw_w32_mask(GENMASK(5, 0), led_glb_ctrl, RTL838X_LED_GLB_CTRL);
sw_w32(led_mode_sel, RTL838X_LED_MODE_SEL);
sw_w32(led_mode_ctrl, RTL838X_LED_MODE_CTRL);
sw_w32(led_p_en_ctrl, RTL838X_LED_P_EN_CTRL);
out:
if(ports) fwnode_handle_put(ports);
if(global_port_leds_config) fwnode_handle_put(global_port_leds_config);
}
const struct rtl838x_reg rtl838x_reg = {
.mask_port_reg_be = rtl838x_mask_port_reg,
.set_port_reg_be = rtl838x_set_port_reg,
@ -1772,6 +1944,7 @@ const struct rtl838x_reg rtl838x_reg = {
.l3_setup = rtl838x_l3_setup,
.set_distribution_algorithm = rtl838x_set_distribution_algorithm,
.set_receive_management_action = rtl838x_set_receive_management_action,
.led_init = rtl838x_led_init,
};
irqreturn_t rtl838x_switch_irq(int irq, void *dev_id)

View File

@ -632,6 +632,19 @@ enum pbvlan_mode {
PBVLAN_MODE_ALL_PKT,
};
struct rtl838x_portgroup_led_config {
u32 installed_leds_mask;
// trigger of led 0, 1 and 2
u32 led_triggers[3];
};
struct rtl838x_led_config {
u32 enabled_ports_mask;
struct rtl838x_portgroup_led_config low_ports;
struct rtl838x_portgroup_led_config high_ports;
};
struct rtl838x_port {
bool enable;
u64 pm;

View File

@ -11,20 +11,21 @@
#define RTL838X_LEDS_CONTROL_MODE_BI_COLOR_SCAN (0x2)
#define RTL838X_LEDS_CONTROL_MODE_DISABLED (0x3)
#define RTL838X_LEDS_FUNCTION_LINK_ACT "link_act"
#define RTL838X_LEDS_FUNCTION_LINK "link"
#define RTL838X_LEDS_FUNCTION_ACT "act"
#define RTL838X_LEDS_FUNCTION_ACT_RX "act_rx"
#define RTL838X_LEDS_FUNCTION_ACT_TX "act_tx"
#define RTL838X_LEDS_FUNCTION_LINK_1G "link_1g"
#define RTL838X_LEDS_FUNCTION_LINK_100M "link_100m"
#define RTL838X_LEDS_FUNCTION_LINK_10M "link_10m"
#define RTL838X_LEDS_FUNCTION_LINK_ACT_1G "link_act_1g"
#define RTL838X_LEDS_FUNCTION_LINK_ACT_100M "link_act_100m"
#define RTL838X_LEDS_FUNCTION_LINK_ACT_10M "link_act_10m"
#define RTL838X_LEDS_FUNCTION_LINK_ACT_1G_100M "link_act_1g_100m"
#define RTL838X_LEDS_FUNCTION_LINK_ACT_1G_10M "link_act_1g_10m"
#define RTL838X_LEDS_FUNCTION_LINK_ACT_100M_10M "link_act_100m_10m"
#define RTL838X_LEDS_FUNCTION_DISABLED "disabled"
// https://svanheule.net/realtek/maple/register/led_mode_ctrl
#define RTL838X_LEDS_TRIGGER_LINK_ACT (0)
#define RTL838X_LEDS_TRIGGER_LINK (1)
#define RTL838X_LEDS_TRIGGER_ACT (2)
#define RTL838X_LEDS_TRIGGER_ACT_RX (3)
#define RTL838X_LEDS_TRIGGER_ACT_TX (4)
#define RTL838X_LEDS_TRIGGER_LINK_1G (7)
#define RTL838X_LEDS_TRIGGER_LINK_100M (8)
#define RTL838X_LEDS_TRIGGER_LINK_10M (9)
#define RTL838X_LEDS_TRIGGER_LINK_ACT_1G (10)
#define RTL838X_LEDS_TRIGGER_LINK_ACT_100M (11)
#define RTL838X_LEDS_TRIGGER_LINK_ACT_10M (12)
#define RTL838X_LEDS_TRIGGER_LINK_ACT_1G_100M (13)
#define RTL838X_LEDS_TRIGGER_LINK_ACT_1G_10M (14)
#define RTL838X_LEDS_TRIGGER_LINK_ACT_100M_10M (15)
#define RTL838X_LEDS_TRIGGER_DISABLED (31)
#endif /* __DT_BINDINGS_LEDS_RTL83XX_H */