]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blame - drivers/leds/leds-turris-omnia.c
Merge tag 'f2fs-for-5.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk...
[mirror_ubuntu-jammy-kernel.git] / drivers / leds / leds-turris-omnia.c
CommitLineData
089381b2
MB
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * CZ.NIC's Turris Omnia LEDs driver
4 *
5 * 2020 by Marek Behun <marek.behun@nic.cz>
6 */
7
8#include <linux/i2c.h>
9#include <linux/led-class-multicolor.h>
10#include <linux/module.h>
11#include <linux/mutex.h>
12#include <linux/of.h>
13#include "leds.h"
14
15#define OMNIA_BOARD_LEDS 12
16#define OMNIA_LED_NUM_CHANNELS 3
17
18#define CMD_LED_MODE 3
19#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
20#define CMD_LED_MODE_USER 0x10
21
22#define CMD_LED_STATE 4
23#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
24#define CMD_LED_STATE_ON 0x10
25
26#define CMD_LED_COLOR 5
27#define CMD_LED_SET_BRIGHTNESS 7
28#define CMD_LED_GET_BRIGHTNESS 8
29
30#define OMNIA_CMD 0
31
32#define OMNIA_CMD_LED_COLOR_LED 1
33#define OMNIA_CMD_LED_COLOR_R 2
34#define OMNIA_CMD_LED_COLOR_G 3
35#define OMNIA_CMD_LED_COLOR_B 4
36#define OMNIA_CMD_LED_COLOR_LEN 5
37
38struct omnia_led {
39 struct led_classdev_mc mc_cdev;
40 struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
41 int reg;
42};
43
44#define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev)
45
46struct omnia_leds {
47 struct i2c_client *client;
48 struct mutex lock;
49 struct omnia_led leds[];
50};
51
52static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
53 enum led_brightness brightness)
54{
55 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
56 struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
57 struct omnia_led *led = to_omnia_led(mc_cdev);
58 u8 buf[OMNIA_CMD_LED_COLOR_LEN], state;
59 int ret;
60
61 mutex_lock(&leds->lock);
62
63 led_mc_calc_color_components(&led->mc_cdev, brightness);
64
65 buf[OMNIA_CMD] = CMD_LED_COLOR;
66 buf[OMNIA_CMD_LED_COLOR_LED] = led->reg;
67 buf[OMNIA_CMD_LED_COLOR_R] = mc_cdev->subled_info[0].brightness;
68 buf[OMNIA_CMD_LED_COLOR_G] = mc_cdev->subled_info[1].brightness;
69 buf[OMNIA_CMD_LED_COLOR_B] = mc_cdev->subled_info[2].brightness;
70
71 state = CMD_LED_STATE_LED(led->reg);
72 if (buf[OMNIA_CMD_LED_COLOR_R] || buf[OMNIA_CMD_LED_COLOR_G] || buf[OMNIA_CMD_LED_COLOR_B])
73 state |= CMD_LED_STATE_ON;
74
75 ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state);
76 if (ret >= 0 && (state & CMD_LED_STATE_ON))
77 ret = i2c_master_send(leds->client, buf, 5);
78
79 mutex_unlock(&leds->lock);
80
81 return ret;
82}
83
84static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
85 struct device_node *np)
86{
87 struct led_init_data init_data = {};
88 struct device *dev = &client->dev;
89 struct led_classdev *cdev;
90 int ret, color;
91
92 ret = of_property_read_u32(np, "reg", &led->reg);
93 if (ret || led->reg >= OMNIA_BOARD_LEDS) {
94 dev_warn(dev,
95 "Node %pOF: must contain 'reg' property with values between 0 and %i\n",
96 np, OMNIA_BOARD_LEDS - 1);
97 return 0;
98 }
99
100 ret = of_property_read_u32(np, "color", &color);
101 if (ret || color != LED_COLOR_ID_MULTI) {
102 dev_warn(dev,
103 "Node %pOF: must contain 'color' property with value LED_COLOR_ID_MULTI\n",
104 np);
105 return 0;
106 }
107
108 led->subled_info[0].color_index = LED_COLOR_ID_RED;
109 led->subled_info[0].channel = 0;
110 led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
111 led->subled_info[1].channel = 1;
112 led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
113 led->subled_info[2].channel = 2;
114
115 led->mc_cdev.subled_info = led->subled_info;
116 led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
117
118 init_data.fwnode = &np->fwnode;
119
120 cdev = &led->mc_cdev.led_cdev;
121 cdev->max_brightness = 255;
122 cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
123
124 of_property_read_string(np, "linux,default-trigger", &cdev->default_trigger);
125
126 /* put the LED into software mode */
127 ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE,
128 CMD_LED_MODE_LED(led->reg) |
129 CMD_LED_MODE_USER);
130 if (ret < 0) {
131 dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, ret);
132 return ret;
133 }
134
135 /* disable the LED */
136 ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, CMD_LED_STATE_LED(led->reg));
137 if (ret < 0) {
138 dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret);
139 return ret;
140 }
141
142 ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
143 if (ret < 0) {
144 dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret);
145 return ret;
146 }
147
148 return 1;
149}
150
151/*
152 * On the front panel of the Turris Omnia router there is also a button which
153 * can be used to control the intensity of all the LEDs at once, so that if they
154 * are too bright, user can dim them.
155 * The microcontroller cycles between 8 levels of this global brightness (from
156 * 100% to 0%), but this setting can have any integer value between 0 and 100.
157 * It is therefore convenient to be able to change this setting from software.
158 * We expose this setting via a sysfs attribute file called "brightness". This
159 * file lives in the device directory of the LED controller, not an individual
160 * LED, so it should not confuse users.
161 */
162static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf)
163{
164 struct i2c_client *client = to_i2c_client(dev);
165 struct omnia_leds *leds = i2c_get_clientdata(client);
166 int ret;
167
168 mutex_lock(&leds->lock);
169 ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS);
170 mutex_unlock(&leds->lock);
171
172 if (ret < 0)
173 return ret;
174
175 return sprintf(buf, "%d\n", ret);
176}
177
178static ssize_t brightness_store(struct device *dev, struct device_attribute *a, const char *buf,
179 size_t count)
180{
181 struct i2c_client *client = to_i2c_client(dev);
182 struct omnia_leds *leds = i2c_get_clientdata(client);
183 unsigned int brightness;
184 int ret;
185
186 if (sscanf(buf, "%u", &brightness) != 1)
187 return -EINVAL;
188
189 if (brightness > 100)
190 return -EINVAL;
191
192 mutex_lock(&leds->lock);
193 ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, (u8) brightness);
194 mutex_unlock(&leds->lock);
195
196 if (ret < 0)
197 return ret;
198
199 return count;
200}
201static DEVICE_ATTR_RW(brightness);
202
203static struct attribute *omnia_led_controller_attrs[] = {
204 &dev_attr_brightness.attr,
205 NULL,
206};
207ATTRIBUTE_GROUPS(omnia_led_controller);
208
209static int omnia_leds_probe(struct i2c_client *client,
210 const struct i2c_device_id *id)
211{
212 struct device *dev = &client->dev;
213 struct device_node *np = dev->of_node, *child;
214 struct omnia_leds *leds;
215 struct omnia_led *led;
216 int ret, count;
217
218 count = of_get_available_child_count(np);
219 if (!count) {
220 dev_err(dev, "LEDs are not defined in device tree!\n");
221 return -ENODEV;
222 } else if (count > OMNIA_BOARD_LEDS) {
223 dev_err(dev, "Too many LEDs defined in device tree!\n");
224 return -EINVAL;
225 }
226
227 leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL);
228 if (!leds)
229 return -ENOMEM;
230
231 leds->client = client;
232 i2c_set_clientdata(client, leds);
233
234 mutex_init(&leds->lock);
235
236 led = &leds->leds[0];
237 for_each_available_child_of_node(np, child) {
238 ret = omnia_led_register(client, led, child);
239 if (ret < 0)
240 return ret;
241
242 led += ret;
243 }
244
245 if (devm_device_add_groups(dev, omnia_led_controller_groups))
246 dev_warn(dev, "Could not add attribute group!\n");
247
248 return 0;
249}
250
251static int omnia_leds_remove(struct i2c_client *client)
252{
253 u8 buf[OMNIA_CMD_LED_COLOR_LEN];
254
255 /* put all LEDs into default (HW triggered) mode */
256 i2c_smbus_write_byte_data(client, CMD_LED_MODE,
257 CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
258
259 /* set all LEDs color to [255, 255, 255] */
260 buf[OMNIA_CMD] = CMD_LED_COLOR;
261 buf[OMNIA_CMD_LED_COLOR_LED] = OMNIA_BOARD_LEDS;
262 buf[OMNIA_CMD_LED_COLOR_R] = 255;
263 buf[OMNIA_CMD_LED_COLOR_G] = 255;
264 buf[OMNIA_CMD_LED_COLOR_B] = 255;
265
266 i2c_master_send(client, buf, 5);
267
268 return 0;
269}
270
271static const struct of_device_id of_omnia_leds_match[] = {
272 { .compatible = "cznic,turris-omnia-leds", },
273 {},
274};
275
276static const struct i2c_device_id omnia_id[] = {
277 { "omnia", 0 },
278 { }
279};
280
281static struct i2c_driver omnia_leds_driver = {
282 .probe = omnia_leds_probe,
283 .remove = omnia_leds_remove,
284 .id_table = omnia_id,
285 .driver = {
286 .name = "leds-turris-omnia",
287 .of_match_table = of_omnia_leds_match,
288 },
289};
290
291module_i2c_driver(omnia_leds_driver);
292
293MODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>");
294MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs");
295MODULE_LICENSE("GPL v2");