Skip to content

Commit 1e264f9

Browse files
Ansueldavem330
authored andcommitted
net: dsa: qca8k: add LEDs basic support
Add LEDs basic support for qca8k Switch Family by adding basic brightness_set() support. Since these LEDs refelect port status, the default label is set to ":port". DT binding should describe the color and function of the LEDs using standard LEDs api. Each LED always have the device name as prefix. The device name is composed from the mii bus id and the PHY addr resulting in example names like: - qca8k-0.0:00:amber:lan - qca8k-0.0:00:white:lan - qca8k-0.0:01:amber:lan - qca8k-0.0:01:white:lan These LEDs supports only blocking variant of the brightness_set() function since they can sleep during access of the switch leds to set the brightness. While at it add to the qca8k header file each mode defined by the Switch Documentation for future use. Signed-off-by: Christian Marangi <[email protected]> Reviewed-by: Florian Fainelli <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 3e8b4d6 commit 1e264f9

File tree

6 files changed

+331
-0
lines changed

6 files changed

+331
-0
lines changed

drivers/net/dsa/qca/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ config NET_DSA_QCA8K
1515
help
1616
This enables support for the Qualcomm Atheros QCA8K Ethernet
1717
switch chips.
18+
19+
config NET_DSA_QCA8K_LEDS_SUPPORT
20+
bool "Qualcomm Atheros QCA8K Ethernet switch family LEDs support"
21+
depends on NET_DSA_QCA8K
22+
depends on LEDS_CLASS
23+
help
24+
This enabled support for LEDs present on the Qualcomm Atheros
25+
QCA8K Ethernet switch chips.

drivers/net/dsa/qca/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o
33
obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
44
qca8k-y += qca8k-common.o qca8k-8xxx.o
5+
ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
6+
qca8k-y += qca8k-leds.o
7+
endif

drivers/net/dsa/qca/qca8k-8xxx.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <linux/dsa/tag_qca.h>
2323

2424
#include "qca8k.h"
25+
#include "qca8k_leds.h"
2526

2627
static void
2728
qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
@@ -1782,6 +1783,10 @@ qca8k_setup(struct dsa_switch *ds)
17821783
if (ret)
17831784
return ret;
17841785

1786+
ret = qca8k_setup_led_ctrl(priv);
1787+
if (ret)
1788+
return ret;
1789+
17851790
qca8k_setup_pcs(priv, &priv->pcs_port_0, 0);
17861791
qca8k_setup_pcs(priv, &priv->pcs_port_6, 6);
17871792

drivers/net/dsa/qca/qca8k-leds.c

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include <linux/regmap.h>
3+
#include <net/dsa.h>
4+
5+
#include "qca8k.h"
6+
#include "qca8k_leds.h"
7+
8+
static int
9+
qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
10+
{
11+
switch (port_num) {
12+
case 0:
13+
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
14+
reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
15+
break;
16+
case 1:
17+
case 2:
18+
case 3:
19+
/* Port 123 are controlled on a different reg */
20+
reg_info->reg = QCA8K_LED_CTRL3_REG;
21+
reg_info->shift = QCA8K_LED_PHY123_PATTERN_EN_SHIFT(port_num, led_num);
22+
break;
23+
case 4:
24+
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
25+
reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
26+
break;
27+
default:
28+
return -EINVAL;
29+
}
30+
31+
return 0;
32+
}
33+
34+
static int
35+
qca8k_led_brightness_set(struct qca8k_led *led,
36+
enum led_brightness brightness)
37+
{
38+
struct qca8k_led_pattern_en reg_info;
39+
struct qca8k_priv *priv = led->priv;
40+
u32 mask, val;
41+
42+
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
43+
44+
val = QCA8K_LED_ALWAYS_OFF;
45+
if (brightness)
46+
val = QCA8K_LED_ALWAYS_ON;
47+
48+
/* HW regs to control brightness is special and port 1-2-3
49+
* are placed in a different reg.
50+
*
51+
* To control port 0 brightness:
52+
* - the 2 bit (15, 14) of:
53+
* - QCA8K_LED_CTRL0_REG for led1
54+
* - QCA8K_LED_CTRL1_REG for led2
55+
* - QCA8K_LED_CTRL2_REG for led3
56+
*
57+
* To control port 4:
58+
* - the 2 bit (31, 30) of:
59+
* - QCA8K_LED_CTRL0_REG for led1
60+
* - QCA8K_LED_CTRL1_REG for led2
61+
* - QCA8K_LED_CTRL2_REG for led3
62+
*
63+
* To control port 1:
64+
* - the 2 bit at (9, 8) of QCA8K_LED_CTRL3_REG are used for led1
65+
* - the 2 bit at (11, 10) of QCA8K_LED_CTRL3_REG are used for led2
66+
* - the 2 bit at (13, 12) of QCA8K_LED_CTRL3_REG are used for led3
67+
*
68+
* To control port 2:
69+
* - the 2 bit at (15, 14) of QCA8K_LED_CTRL3_REG are used for led1
70+
* - the 2 bit at (17, 16) of QCA8K_LED_CTRL3_REG are used for led2
71+
* - the 2 bit at (19, 18) of QCA8K_LED_CTRL3_REG are used for led3
72+
*
73+
* To control port 3:
74+
* - the 2 bit at (21, 20) of QCA8K_LED_CTRL3_REG are used for led1
75+
* - the 2 bit at (23, 22) of QCA8K_LED_CTRL3_REG are used for led2
76+
* - the 2 bit at (25, 24) of QCA8K_LED_CTRL3_REG are used for led3
77+
*
78+
* To abstract this and have less code, we use the port and led numm
79+
* to calculate the shift and the correct reg due to this problem of
80+
* not having a 1:1 map of LED with the regs.
81+
*/
82+
if (led->port_num == 0 || led->port_num == 4) {
83+
mask = QCA8K_LED_PATTERN_EN_MASK;
84+
val <<= QCA8K_LED_PATTERN_EN_SHIFT;
85+
} else {
86+
mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
87+
}
88+
89+
return regmap_update_bits(priv->regmap, reg_info.reg,
90+
mask << reg_info.shift,
91+
val << reg_info.shift);
92+
}
93+
94+
static int
95+
qca8k_cled_brightness_set_blocking(struct led_classdev *ldev,
96+
enum led_brightness brightness)
97+
{
98+
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
99+
100+
return qca8k_led_brightness_set(led, brightness);
101+
}
102+
103+
static enum led_brightness
104+
qca8k_led_brightness_get(struct qca8k_led *led)
105+
{
106+
struct qca8k_led_pattern_en reg_info;
107+
struct qca8k_priv *priv = led->priv;
108+
u32 val;
109+
int ret;
110+
111+
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
112+
113+
ret = regmap_read(priv->regmap, reg_info.reg, &val);
114+
if (ret)
115+
return 0;
116+
117+
val >>= reg_info.shift;
118+
119+
if (led->port_num == 0 || led->port_num == 4) {
120+
val &= QCA8K_LED_PATTERN_EN_MASK;
121+
val >>= QCA8K_LED_PATTERN_EN_SHIFT;
122+
} else {
123+
val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
124+
}
125+
126+
/* Assume brightness ON only when the LED is set to always ON */
127+
return val == QCA8K_LED_ALWAYS_ON;
128+
}
129+
130+
static int
131+
qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
132+
{
133+
struct fwnode_handle *led = NULL, *leds = NULL;
134+
struct led_init_data init_data = { };
135+
struct dsa_switch *ds = priv->ds;
136+
enum led_default_state state;
137+
struct qca8k_led *port_led;
138+
int led_num, led_index;
139+
int ret;
140+
141+
leds = fwnode_get_named_child_node(port, "leds");
142+
if (!leds) {
143+
dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n",
144+
port_num);
145+
return 0;
146+
}
147+
148+
fwnode_for_each_child_node(leds, led) {
149+
/* Reg represent the led number of the port.
150+
* Each port can have at most 3 leds attached
151+
* Commonly:
152+
* 1. is gigabit led
153+
* 2. is mbit led
154+
* 3. additional status led
155+
*/
156+
if (fwnode_property_read_u32(led, "reg", &led_num))
157+
continue;
158+
159+
if (led_num >= QCA8K_LED_PORT_COUNT) {
160+
dev_warn(priv->dev, "Invalid LED reg %d defined for port %d",
161+
led_num, port_num);
162+
continue;
163+
}
164+
165+
led_index = QCA8K_LED_PORT_INDEX(port_num, led_num);
166+
167+
port_led = &priv->ports_led[led_index];
168+
port_led->port_num = port_num;
169+
port_led->led_num = led_num;
170+
port_led->priv = priv;
171+
172+
state = led_init_default_state_get(led);
173+
switch (state) {
174+
case LEDS_DEFSTATE_ON:
175+
port_led->cdev.brightness = 1;
176+
qca8k_led_brightness_set(port_led, 1);
177+
break;
178+
case LEDS_DEFSTATE_KEEP:
179+
port_led->cdev.brightness =
180+
qca8k_led_brightness_get(port_led);
181+
break;
182+
default:
183+
port_led->cdev.brightness = 0;
184+
qca8k_led_brightness_set(port_led, 0);
185+
}
186+
187+
port_led->cdev.max_brightness = 1;
188+
port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
189+
init_data.default_label = ":port";
190+
init_data.fwnode = led;
191+
init_data.devname_mandatory = true;
192+
init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d", ds->slave_mii_bus->id,
193+
port_num);
194+
if (!init_data.devicename)
195+
return -ENOMEM;
196+
197+
ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data);
198+
if (ret)
199+
dev_warn(priv->dev, "Failed to init LED %d for port %d", led_num, port_num);
200+
201+
kfree(init_data.devicename);
202+
}
203+
204+
return 0;
205+
}
206+
207+
int
208+
qca8k_setup_led_ctrl(struct qca8k_priv *priv)
209+
{
210+
struct fwnode_handle *ports, *port;
211+
int port_num;
212+
int ret;
213+
214+
ports = device_get_named_child_node(priv->dev, "ports");
215+
if (!ports) {
216+
dev_info(priv->dev, "No ports node specified in device tree!");
217+
return 0;
218+
}
219+
220+
fwnode_for_each_child_node(ports, port) {
221+
if (fwnode_property_read_u32(port, "reg", &port_num))
222+
continue;
223+
224+
/* Skip checking for CPU port 0 and CPU port 6 as not supported */
225+
if (port_num == 0 || port_num == 6)
226+
continue;
227+
228+
/* Each port can have at most 3 different leds attached.
229+
* Switch port starts from 0 to 6, but port 0 and 6 are CPU
230+
* port. The port index needs to be decreased by one to identify
231+
* the correct port for LED setup.
232+
*/
233+
ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num));
234+
if (ret)
235+
return ret;
236+
}
237+
238+
return 0;
239+
}

drivers/net/dsa/qca/qca8k.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <linux/delay.h>
1212
#include <linux/regmap.h>
1313
#include <linux/gpio.h>
14+
#include <linux/leds.h>
1415
#include <linux/dsa/tag_qca.h>
1516

1617
#define QCA8K_ETHERNET_MDIO_PRIORITY 7
@@ -85,6 +86,51 @@
8586
#define QCA8K_MDIO_MASTER_DATA(x) FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
8687
#define QCA8K_MDIO_MASTER_MAX_PORTS 5
8788
#define QCA8K_MDIO_MASTER_MAX_REG 32
89+
90+
/* LED control register */
91+
#define QCA8K_LED_PORT_COUNT 3
92+
#define QCA8K_LED_COUNT ((QCA8K_NUM_PORTS - QCA8K_NUM_CPU_PORTS) * QCA8K_LED_PORT_COUNT)
93+
#define QCA8K_LED_RULE_COUNT 6
94+
#define QCA8K_LED_RULE_MAX 11
95+
#define QCA8K_LED_PORT_INDEX(_phy, _led) (((_phy) * QCA8K_LED_PORT_COUNT) + (_led))
96+
97+
#define QCA8K_LED_PHY123_PATTERN_EN_SHIFT(_phy, _led) ((((_phy) - 1) * 6) + 8 + (2 * (_led)))
98+
#define QCA8K_LED_PHY123_PATTERN_EN_MASK GENMASK(1, 0)
99+
100+
#define QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT 0
101+
#define QCA8K_LED_PHY4_CONTROL_RULE_SHIFT 16
102+
103+
#define QCA8K_LED_CTRL_REG(_i) (0x050 + (_i) * 4)
104+
#define QCA8K_LED_CTRL0_REG 0x50
105+
#define QCA8K_LED_CTRL1_REG 0x54
106+
#define QCA8K_LED_CTRL2_REG 0x58
107+
#define QCA8K_LED_CTRL3_REG 0x5C
108+
#define QCA8K_LED_CTRL_SHIFT(_i) (((_i) % 2) * 16)
109+
#define QCA8K_LED_CTRL_MASK GENMASK(15, 0)
110+
#define QCA8K_LED_RULE_MASK GENMASK(13, 0)
111+
#define QCA8K_LED_BLINK_FREQ_MASK GENMASK(1, 0)
112+
#define QCA8K_LED_BLINK_FREQ_SHITF 0
113+
#define QCA8K_LED_BLINK_2HZ 0
114+
#define QCA8K_LED_BLINK_4HZ 1
115+
#define QCA8K_LED_BLINK_8HZ 2
116+
#define QCA8K_LED_BLINK_AUTO 3
117+
#define QCA8K_LED_LINKUP_OVER_MASK BIT(2)
118+
#define QCA8K_LED_TX_BLINK_MASK BIT(4)
119+
#define QCA8K_LED_RX_BLINK_MASK BIT(5)
120+
#define QCA8K_LED_COL_BLINK_MASK BIT(7)
121+
#define QCA8K_LED_LINK_10M_EN_MASK BIT(8)
122+
#define QCA8K_LED_LINK_100M_EN_MASK BIT(9)
123+
#define QCA8K_LED_LINK_1000M_EN_MASK BIT(10)
124+
#define QCA8K_LED_POWER_ON_LIGHT_MASK BIT(11)
125+
#define QCA8K_LED_HALF_DUPLEX_MASK BIT(12)
126+
#define QCA8K_LED_FULL_DUPLEX_MASK BIT(13)
127+
#define QCA8K_LED_PATTERN_EN_MASK GENMASK(15, 14)
128+
#define QCA8K_LED_PATTERN_EN_SHIFT 14
129+
#define QCA8K_LED_ALWAYS_OFF 0
130+
#define QCA8K_LED_ALWAYS_BLINK_4HZ 1
131+
#define QCA8K_LED_ALWAYS_ON 2
132+
#define QCA8K_LED_RULE_CONTROLLED 3
133+
88134
#define QCA8K_GOL_MAC_ADDR0 0x60
89135
#define QCA8K_GOL_MAC_ADDR1 0x64
90136
#define QCA8K_MAX_FRAME_SIZE 0x78
@@ -382,6 +428,19 @@ struct qca8k_pcs {
382428
int port;
383429
};
384430

431+
struct qca8k_led_pattern_en {
432+
u32 reg;
433+
u8 shift;
434+
};
435+
436+
struct qca8k_led {
437+
u8 port_num;
438+
u8 led_num;
439+
u16 old_rule;
440+
struct qca8k_priv *priv;
441+
struct led_classdev cdev;
442+
};
443+
385444
struct qca8k_priv {
386445
u8 switch_id;
387446
u8 switch_revision;
@@ -406,6 +465,7 @@ struct qca8k_priv {
406465
struct qca8k_pcs pcs_port_0;
407466
struct qca8k_pcs pcs_port_6;
408467
const struct qca8k_match_data *info;
468+
struct qca8k_led ports_led[QCA8K_LED_COUNT];
409469
};
410470

411471
struct qca8k_mib_desc {

drivers/net/dsa/qca/qca8k_leds.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* SPDX-License-Identifier: GPL-2.0-only */
2+
3+
#ifndef __QCA8K_LEDS_H
4+
#define __QCA8K_LEDS_H
5+
6+
/* Leds Support function */
7+
#ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
8+
int qca8k_setup_led_ctrl(struct qca8k_priv *priv);
9+
#else
10+
static inline int qca8k_setup_led_ctrl(struct qca8k_priv *priv)
11+
{
12+
return 0;
13+
}
14+
#endif
15+
16+
#endif /* __QCA8K_LEDS_H */

0 commit comments

Comments
 (0)