// SPDX-License-Identifier: GPL-2.0
#include <linux/property.h>
#include <linux/regmap.h>
#include <net/dsa.h>
#include "qca8k.h"
#include "qca8k_leds.h"
static u32 qca8k_phy_to_port(int phy)
{
/* Internal PHY 0 has port at index 1.
* Internal PHY 1 has port at index 2 .
* Internal PHY 2 has port at index 3 .
* Internal PHY 3 has port at index 4 .
* Internal PHY 4 has port at index 5 .
*/
return phy + 1 ;
}
static int
qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
{
switch (port_num) {
case 0 :
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
break ;
case 1 :
case 2 :
case 3 :
/* Port 123 are controlled on a different reg */
reg_info->reg = QCA8K_LED_CTRL3_REG;
reg_info->shift = QCA8K_LED_PHY123_PATTERN_EN_SHIFT(port_num, led_num);
break ;
case 4 :
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
break ;
default :
return -EINVAL;
}
return 0 ;
}
static int
qca8k_get_control_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
{
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
/* 6 total control rule:
* 3 control rules for phy0 - 3 that applies to all their leds
* 3 control rules for phy4
*/
if (port_num == 4 )
reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
else
reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
return 0 ;
}
static int
qca8k_parse_netdev(unsigned long rules, u32 *offload_trigger)
{
/* Parsing specific to netdev trigger */
if (test_bit(TRIGGER_NETDEV_TX, &rules))
*offload_trigger |= QCA8K_LED_TX_BLINK_MASK;
if (test_bit(TRIGGER_NETDEV_RX, &rules))
*offload_trigger |= QCA8K_LED_RX_BLINK_MASK;
if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
*offload_trigger |= QCA8K_LED_LINK_10M_EN_MASK;
if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
*offload_trigger |= QCA8K_LED_LINK_100M_EN_MASK;
if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
*offload_trigger |= QCA8K_LED_LINK_1000M_EN_MASK;
if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
*offload_trigger |= QCA8K_LED_HALF_DUPLEX_MASK;
if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
*offload_trigger |= QCA8K_LED_FULL_DUPLEX_MASK;
if (rules && !*offload_trigger)
return -EOPNOTSUPP;
/* Enable some default rule by default to the requested mode:
* - Blink at 4 Hz by default
*/
*offload_trigger |= QCA8K_LED_BLINK_4HZ;
return 0 ;
}
static int
qca8k_led_brightness_set(struct qca8k_led *led,
enum led_brightness brightness)
{
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 mask, val;
qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
val = QCA8K_LED_ALWAYS_OFF;
if (brightness)
val = QCA8K_LED_ALWAYS_ON;
/* HW regs to control brightness is special and port 1-2-3
* are placed in a different reg .
*
* To control port 0 brightness :
* - the 2 bit ( 15 , 14 ) of :
* - QCA8K_LED_CTRL0_REG for led1
* - QCA8K_LED_CTRL1_REG for led2
* - QCA8K_LED_CTRL2_REG for led3
*
* To control port 4 :
* - the 2 bit ( 31 , 30 ) of :
* - QCA8K_LED_CTRL0_REG for led1
* - QCA8K_LED_CTRL1_REG for led2
* - QCA8K_LED_CTRL2_REG for led3
*
* To control port 1 :
* - the 2 bit at ( 9 , 8 ) of QCA8K_LED_CTRL3_REG are used for led1
* - the 2 bit at ( 11 , 10 ) of QCA8K_LED_CTRL3_REG are used for led2
* - the 2 bit at ( 13 , 12 ) of QCA8K_LED_CTRL3_REG are used for led3
*
* To control port 2 :
* - the 2 bit at ( 15 , 14 ) of QCA8K_LED_CTRL3_REG are used for led1
* - the 2 bit at ( 17 , 16 ) of QCA8K_LED_CTRL3_REG are used for led2
* - the 2 bit at ( 19 , 18 ) of QCA8K_LED_CTRL3_REG are used for led3
*
* To control port 3 :
* - the 2 bit at ( 21 , 20 ) of QCA8K_LED_CTRL3_REG are used for led1
* - the 2 bit at ( 23 , 22 ) of QCA8K_LED_CTRL3_REG are used for led2
* - the 2 bit at ( 25 , 24 ) of QCA8K_LED_CTRL3_REG are used for led3
*
* To abstract this and have less code , we use the port and led numm
* to calculate the shift and the correct reg due to this problem of
* not having a 1 : 1 map of LED with the regs .
*/
if (led->port_num == 0 || led->port_num == 4 ) {
mask = QCA8K_LED_PATTERN_EN_MASK;
val <<= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
return regmap_update_bits(priv->regmap, reg_info.reg,
mask << reg_info.shift,
val << reg_info.shift);
}
static int
qca8k_cled_brightness_set_blocking(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
return qca8k_led_brightness_set(led, brightness);
}
static enum led_brightness
qca8k_led_brightness_get(struct qca8k_led *led)
{
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 val;
int ret;
qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
ret = regmap_read(priv->regmap, reg_info.reg, &val);
if (ret)
return 0 ;
val >>= reg_info.shift;
if (led->port_num == 0 || led->port_num == 4 ) {
val &= QCA8K_LED_PATTERN_EN_MASK;
val >>= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
/* Assume brightness ON only when the LED is set to always ON */
return val == QCA8K_LED_ALWAYS_ON;
}
static int
qca8k_cled_blink_set(struct led_classdev *ldev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
u32 mask, val = QCA8K_LED_ALWAYS_BLINK_4HZ;
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
if (*delay_on == 0 && *delay_off == 0 ) {
*delay_on = 125 ;
*delay_off = 125 ;
}
if (*delay_on != 125 || *delay_off != 125 ) {
/* The hardware only supports blinking at 4Hz. Fall back
* to software implementation in other cases .
*/
return -EINVAL;
}
qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
if (led->port_num == 0 || led->port_num == 4 ) {
mask = QCA8K_LED_PATTERN_EN_MASK;
val <<= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
val << reg_info.shift);
return 0 ;
}
static int
qca8k_cled_trigger_offload(struct led_classdev *ldev, bool enable)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 mask, val = QCA8K_LED_ALWAYS_OFF;
qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
if (enable)
val = QCA8K_LED_RULE_CONTROLLED;
if (led->port_num == 0 || led->port_num == 4 ) {
mask = QCA8K_LED_PATTERN_EN_MASK;
val <<= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
return regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
val << reg_info.shift);
}
static bool
qca8k_cled_hw_control_status(struct led_classdev *ldev)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 val;
qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
regmap_read(priv->regmap, reg_info.reg, &val);
val >>= reg_info.shift;
if (led->port_num == 0 || led->port_num == 4 ) {
val &= QCA8K_LED_PATTERN_EN_MASK;
val >>= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
return val == QCA8K_LED_RULE_CONTROLLED;
}
static int
qca8k_cled_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
{
u32 offload_trigger = 0 ;
return qca8k_parse_netdev(rules, &offload_trigger);
}
static int
qca8k_cled_hw_control_set(struct led_classdev *ldev, unsigned long rules)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 offload_trigger = 0 ;
int ret;
ret = qca8k_parse_netdev(rules, &offload_trigger);
if (ret)
return ret;
ret = qca8k_cled_trigger_offload(ldev, true );
if (ret)
return ret;
qca8k_get_control_led_reg(led->port_num, led->led_num, ®_info);
return regmap_update_bits(priv->regmap, reg_info.reg,
QCA8K_LED_RULE_MASK << reg_info.shift,
offload_trigger << reg_info.shift);
}
static int
qca8k_cled_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 val;
int ret;
/* With hw control not active return err */
if (!qca8k_cled_hw_control_status(ldev))
return -EINVAL;
qca8k_get_control_led_reg(led->port_num, led->led_num, ®_info);
ret = regmap_read(priv->regmap, reg_info.reg, &val);
if (ret)
return ret;
val >>= reg_info.shift;
val &= QCA8K_LED_RULE_MASK;
/* Parsing specific to netdev trigger */
if (val & QCA8K_LED_TX_BLINK_MASK)
set_bit(TRIGGER_NETDEV_TX, rules);
if (val & QCA8K_LED_RX_BLINK_MASK)
set_bit(TRIGGER_NETDEV_RX, rules);
if (val & QCA8K_LED_LINK_10M_EN_MASK)
set_bit(TRIGGER_NETDEV_LINK_10, rules);
if (val & QCA8K_LED_LINK_100M_EN_MASK)
set_bit(TRIGGER_NETDEV_LINK_100, rules);
if (val & QCA8K_LED_LINK_1000M_EN_MASK)
set_bit(TRIGGER_NETDEV_LINK_1000, rules);
if (val & QCA8K_LED_HALF_DUPLEX_MASK)
set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
if (val & QCA8K_LED_FULL_DUPLEX_MASK)
set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
return 0 ;
}
static struct device *qca8k_cled_hw_control_get_device(struct led_classdev *ldev)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
struct qca8k_priv *priv = led->priv;
struct dsa_port *dp;
dp = dsa_to_port(priv->ds, qca8k_phy_to_port(led->port_num));
if (!dp)
return NULL;
if (dp->user)
return &dp->user->dev;
return NULL;
}
static int
qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
{
struct fwnode_handle *led = NULL, *leds = NULL;
struct led_init_data init_data = { };
enum led_default_state state;
struct qca8k_led *port_led;
int led_num, led_index;
int ret;
leds = fwnode_get_named_child_node(port, "leds" );
if (!leds) {
dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n" ,
port_num);
return 0 ;
}
fwnode_for_each_child_node(leds, led) {
/* Reg represent the led number of the port.
* Each port can have at most 3 leds attached
* Commonly :
* 1 . is gigabit led
* 2 . is mbit led
* 3 . additional status led
*/
if (fwnode_property_read_u32(led, "reg" , &led_num))
continue ;
if (led_num >= QCA8K_LED_PORT_COUNT) {
dev_warn(priv->dev, "Invalid LED reg %d defined for port %d" ,
led_num, port_num);
continue ;
}
led_index = QCA8K_LED_PORT_INDEX(port_num, led_num);
port_led = &priv->ports_led[led_index];
port_led->port_num = port_num;
port_led->led_num = led_num;
port_led->priv = priv;
state = led_init_default_state_get(led);
switch (state) {
case LEDS_DEFSTATE_ON:
port_led->cdev.brightness = 1 ;
qca8k_led_brightness_set(port_led, 1 );
break ;
case LEDS_DEFSTATE_KEEP:
port_led->cdev.brightness =
qca8k_led_brightness_get(port_led);
break ;
default :
port_led->cdev.brightness = 0 ;
qca8k_led_brightness_set(port_led, 0 );
}
port_led->cdev.max_brightness = 1 ;
port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
port_led->cdev.blink_set = qca8k_cled_blink_set;
port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
port_led->cdev.hw_control_get_device = qca8k_cled_hw_control_get_device;
port_led->cdev.hw_control_trigger = "netdev" ;
init_data.default_label = ":port" ;
init_data.fwnode = led;
init_data.devname_mandatory = true ;
init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d" ,
priv->internal_mdio_bus->id,
port_num);
if (!init_data.devicename) {
fwnode_handle_put(led);
fwnode_handle_put(leds);
return -ENOMEM;
}
ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data);
if (ret)
dev_warn(priv->dev, "Failed to init LED %d for port %d" , led_num, port_num);
kfree(init_data.devicename);
}
fwnode_handle_put(leds);
return 0 ;
}
int
qca8k_setup_led_ctrl(struct qca8k_priv *priv)
{
struct fwnode_handle *ports, *port;
int port_num;
int ret;
ports = device_get_named_child_node(priv->dev, "ports" );
if (!ports) {
dev_info(priv->dev, "No ports node specified in device tree!" );
return 0 ;
}
fwnode_for_each_child_node(ports, port) {
if (fwnode_property_read_u32(port, "reg" , &port_num))
continue ;
/* Skip checking for CPU port 0 and CPU port 6 as not supported */
if (port_num == 0 || port_num == 6 )
continue ;
/* Each port can have at most 3 different leds attached.
* Switch port starts from 0 to 6 , but port 0 and 6 are CPU
* port . The port index needs to be decreased by one to identify
* the correct port for LED setup .
*/
ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num));
if (ret) {
fwnode_handle_put(port);
fwnode_handle_put(ports);
return ret;
}
}
fwnode_handle_put(ports);
return 0 ;
}
Messung V0.5 in Prozent C=95 H=96 G=95
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland