00773ab0f63293fd96a3797ee4b6de8c5f477de1
[openwrt/staging/blocktrron.git] /
1 From e965e0f6de60874fc0a0caed9a9e0122999e0c7b Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
3 Date: Mon, 18 Sep 2023 18:11:03 +0200
4 Subject: [PATCH 5/6] leds: turris-omnia: Support HW controlled mode via
5 private trigger
6 MIME-Version: 1.0
7 Content-Type: text/plain; charset=UTF-8
8 Content-Transfer-Encoding: 8bit
9
10 Add support for enabling MCU controlled mode of the Turris Omnia LEDs
11 via a LED private trigger called "omnia-mcu". Recall that private LED
12 triggers will only be listed in the sysfs trigger file for LEDs that
13 support them (currently there is no user of this mechanism).
14
15 When in MCU controlled mode, the user can still set LED color, but the
16 blinking is done by MCU, which does different things for different LEDs:
17 - WAN LED is blinked according to the LED[0] pin of the WAN PHY
18 - LAN LEDs are blinked according to the LED[0] output of the
19 corresponding port of the LAN switch
20 - PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
21 LED pins
22
23 In the future I want to make the netdev trigger to transparently offload
24 the blinking to the HW if user sets compatible settings for the netdev
25 trigger (for LEDs associated with network devices).
26 There was some work on this already, and hopefully we will be able to
27 complete it sometime, but for now there are still multiple blockers for
28 this, and even if there weren't, we still would not be able to configure
29 HW controlled mode for the LEDs associated with MiniPCIe ports.
30
31 In the meantime let's support HW controlled mode via the private LED
32 trigger mechanism. If, in the future, we manage to complete the netdev
33 trigger offloading, we can still keep this private trigger for backwards
34 compatibility, if needed.
35
36 We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
37 control until the user first wants to take over it. If a different
38 default trigger is specified in device-tree via the
39 'linux,default-trigger' property, LED class will overwrite
40 cdev->default_trigger, and so the DT property will be respected.
41
42 Signed-off-by: Marek BehĂșn <kabel@kernel.org>
43 Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
44 Signed-off-by: Lee Jones <lee@kernel.org>
45 ---
46 drivers/leds/Kconfig | 1 +
47 drivers/leds/leds-turris-omnia.c | 98 +++++++++++++++++++++++++++++---
48 2 files changed, 91 insertions(+), 8 deletions(-)
49
50 --- a/drivers/leds/Kconfig
51 +++ b/drivers/leds/Kconfig
52 @@ -164,6 +164,7 @@ config LEDS_TURRIS_OMNIA
53 depends on I2C
54 depends on MACH_ARMADA_38X || COMPILE_TEST
55 depends on OF
56 + select LEDS_TRIGGERS
57 help
58 This option enables basic support for the LEDs found on the front
59 side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
60 --- a/drivers/leds/leds-turris-omnia.c
61 +++ b/drivers/leds/leds-turris-omnia.c
62 @@ -31,7 +31,7 @@ struct omnia_led {
63 struct led_classdev_mc mc_cdev;
64 struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
65 u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
66 - bool on;
67 + bool on, hwtrig;
68 int reg;
69 };
70
71 @@ -120,12 +120,14 @@ static int omnia_led_brightness_set_bloc
72
73 /*
74 * Only recalculate RGB brightnesses from intensities if brightness is
75 - * non-zero. Otherwise we won't be using them and we can save ourselves
76 - * some software divisions (Omnia's CPU does not implement the division
77 - * instruction).
78 + * non-zero (if it is zero and the LED is in HW blinking mode, we use
79 + * max_brightness as brightness). Otherwise we won't be using them and
80 + * we can save ourselves some software divisions (Omnia's CPU does not
81 + * implement the division instruction).
82 */
83 - if (brightness) {
84 - led_mc_calc_color_components(mc_cdev, brightness);
85 + if (brightness || led->hwtrig) {
86 + led_mc_calc_color_components(mc_cdev, brightness ?:
87 + cdev->max_brightness);
88
89 /*
90 * Send color command only if brightness is non-zero and the RGB
91 @@ -135,8 +137,11 @@ static int omnia_led_brightness_set_bloc
92 err = omnia_led_send_color_cmd(leds->client, led);
93 }
94
95 - /* Send on/off state change only if (bool)brightness changed */
96 - if (!err && !brightness != !led->on) {
97 + /*
98 + * Send on/off state change only if (bool)brightness changed and the LED
99 + * is not being blinked by HW.
100 + */
101 + if (!err && !led->hwtrig && !brightness != !led->on) {
102 u8 state = CMD_LED_STATE_LED(led->reg);
103
104 if (brightness)
105 @@ -152,6 +157,71 @@ static int omnia_led_brightness_set_bloc
106 return err;
107 }
108
109 +static struct led_hw_trigger_type omnia_hw_trigger_type;
110 +
111 +static int omnia_hwtrig_activate(struct led_classdev *cdev)
112 +{
113 + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
114 + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
115 + struct omnia_led *led = to_omnia_led(mc_cdev);
116 + int err = 0;
117 +
118 + mutex_lock(&leds->lock);
119 +
120 + if (!led->on) {
121 + /*
122 + * If the LED is off (brightness was set to 0), the last
123 + * configured color was not necessarily sent to the MCU.
124 + * Recompute with max_brightness and send if needed.
125 + */
126 + led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
127 +
128 + if (omnia_led_channels_changed(led))
129 + err = omnia_led_send_color_cmd(leds->client, led);
130 + }
131 +
132 + if (!err) {
133 + /* Put the LED into MCU controlled mode */
134 + err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
135 + CMD_LED_MODE_LED(led->reg));
136 + if (!err)
137 + led->hwtrig = true;
138 + }
139 +
140 + mutex_unlock(&leds->lock);
141 +
142 + return err;
143 +}
144 +
145 +static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
146 +{
147 + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
148 + struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
149 + int err;
150 +
151 + mutex_lock(&leds->lock);
152 +
153 + led->hwtrig = false;
154 +
155 + /* Put the LED into software mode */
156 + err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
157 + CMD_LED_MODE_LED(led->reg) |
158 + CMD_LED_MODE_USER);
159 +
160 + mutex_unlock(&leds->lock);
161 +
162 + if (err < 0)
163 + dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
164 + err);
165 +}
166 +
167 +static struct led_trigger omnia_hw_trigger = {
168 + .name = "omnia-mcu",
169 + .activate = omnia_hwtrig_activate,
170 + .deactivate = omnia_hwtrig_deactivate,
171 + .trigger_type = &omnia_hw_trigger_type,
172 +};
173 +
174 static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
175 struct device_node *np)
176 {
177 @@ -195,6 +265,12 @@ static int omnia_led_register(struct i2c
178 cdev = &led->mc_cdev.led_cdev;
179 cdev->max_brightness = 255;
180 cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
181 + cdev->trigger_type = &omnia_hw_trigger_type;
182 + /*
183 + * Use the omnia-mcu trigger as the default trigger. It may be rewritten
184 + * by LED class from the linux,default-trigger property.
185 + */
186 + cdev->default_trigger = omnia_hw_trigger.name;
187
188 /* put the LED into software mode */
189 ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
190 @@ -309,6 +385,12 @@ static int omnia_leds_probe(struct i2c_c
191
192 mutex_init(&leds->lock);
193
194 + ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
195 + if (ret < 0) {
196 + dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
197 + return ret;
198 + }
199 +
200 led = &leds->leds[0];
201 for_each_available_child_of_node(np, child) {
202 ret = omnia_led_register(client, led, child);