net: dsa: qca8k: implement hw_control ops

Implement hw_control ops to drive Switch LEDs based on hardware events.

Netdev trigger is the declared supported trigger for hw control
operation and supports the following mode:
- tx
- rx

When hw_control_set is called, LEDs are set to follow the requested
mode.
Each LEDs will blink at 4Hz by default.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Christian Marangi 2023-05-29 18:32:42 +02:00 committed by David S. Miller
parent 947acacab5
commit e0256648c8

View file

@ -31,6 +31,43 @@ qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en
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 (rules && !*offload_trigger)
return -EOPNOTSUPP;
/* Enable some default rule by default to the requested mode:
* - Blink at 4Hz by default
*/
*offload_trigger |= QCA8K_LED_BLINK_4HZ;
return 0;
}
static int
qca8k_led_brightness_set(struct qca8k_led *led,
enum led_brightness brightness)
@ -164,6 +201,119 @@ qca8k_cled_blink_set(struct led_classdev *ldev,
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, &reg_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, &reg_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, &reg_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, &reg_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);
return 0;
}
static int
qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
{
@ -224,6 +374,10 @@ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int p
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_trigger = "netdev";
init_data.default_label = ":port";
init_data.fwnode = led;
init_data.devname_mandatory = true;