1 // SPDX-License-Identifier: GPL-2.0
3 * CZ.NIC's Turris Omnia LEDs driver
5 * 2020 by Marek Behun <marek.behun@nic.cz>
9 #include <linux/led-class-multicolor.h>
10 #include <linux/module.h>
11 #include <linux/mutex.h>
15 #define OMNIA_BOARD_LEDS 12
16 #define OMNIA_LED_NUM_CHANNELS 3
18 #define CMD_LED_MODE 3
19 #define CMD_LED_MODE_LED(l) ((l) & 0x0f)
20 #define CMD_LED_MODE_USER 0x10
22 #define CMD_LED_STATE 4
23 #define CMD_LED_STATE_LED(l) ((l) & 0x0f)
24 #define CMD_LED_STATE_ON 0x10
26 #define CMD_LED_COLOR 5
27 #define CMD_LED_SET_BRIGHTNESS 7
28 #define CMD_LED_GET_BRIGHTNESS 8
31 struct led_classdev_mc mc_cdev
;
32 struct mc_subled subled_info
[OMNIA_LED_NUM_CHANNELS
];
36 #define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev)
39 struct i2c_client
*client
;
41 struct omnia_led leds
[];
44 static int omnia_led_brightness_set_blocking(struct led_classdev
*cdev
,
45 enum led_brightness brightness
)
47 struct led_classdev_mc
*mc_cdev
= lcdev_to_mccdev(cdev
);
48 struct omnia_leds
*leds
= dev_get_drvdata(cdev
->dev
->parent
);
49 struct omnia_led
*led
= to_omnia_led(mc_cdev
);
53 mutex_lock(&leds
->lock
);
55 led_mc_calc_color_components(&led
->mc_cdev
, brightness
);
57 buf
[0] = CMD_LED_COLOR
;
59 buf
[2] = mc_cdev
->subled_info
[0].brightness
;
60 buf
[3] = mc_cdev
->subled_info
[1].brightness
;
61 buf
[4] = mc_cdev
->subled_info
[2].brightness
;
63 state
= CMD_LED_STATE_LED(led
->reg
);
64 if (buf
[2] || buf
[3] || buf
[4])
65 state
|= CMD_LED_STATE_ON
;
67 ret
= i2c_smbus_write_byte_data(leds
->client
, CMD_LED_STATE
, state
);
68 if (ret
>= 0 && (state
& CMD_LED_STATE_ON
))
69 ret
= i2c_master_send(leds
->client
, buf
, 5);
71 mutex_unlock(&leds
->lock
);
76 static int omnia_led_register(struct i2c_client
*client
, struct omnia_led
*led
,
77 struct device_node
*np
)
79 struct led_init_data init_data
= {};
80 struct device
*dev
= &client
->dev
;
81 struct led_classdev
*cdev
;
84 ret
= of_property_read_u32(np
, "reg", &led
->reg
);
85 if (ret
|| led
->reg
>= OMNIA_BOARD_LEDS
) {
87 "Node %pOF: must contain 'reg' property with values between 0 and %i\n",
88 np
, OMNIA_BOARD_LEDS
- 1);
92 ret
= of_property_read_u32(np
, "color", &color
);
93 if (ret
|| color
!= LED_COLOR_ID_RGB
) {
95 "Node %pOF: must contain 'color' property with value LED_COLOR_ID_RGB\n",
100 led
->subled_info
[0].color_index
= LED_COLOR_ID_RED
;
101 led
->subled_info
[0].channel
= 0;
102 led
->subled_info
[1].color_index
= LED_COLOR_ID_GREEN
;
103 led
->subled_info
[1].channel
= 1;
104 led
->subled_info
[2].color_index
= LED_COLOR_ID_BLUE
;
105 led
->subled_info
[2].channel
= 2;
107 led
->mc_cdev
.subled_info
= led
->subled_info
;
108 led
->mc_cdev
.num_colors
= OMNIA_LED_NUM_CHANNELS
;
110 init_data
.fwnode
= &np
->fwnode
;
112 cdev
= &led
->mc_cdev
.led_cdev
;
113 cdev
->max_brightness
= 255;
114 cdev
->brightness_set_blocking
= omnia_led_brightness_set_blocking
;
116 /* put the LED into software mode */
117 ret
= i2c_smbus_write_byte_data(client
, CMD_LED_MODE
,
118 CMD_LED_MODE_LED(led
->reg
) |
121 dev_err(dev
, "Cannot set LED %pOF to software mode: %i\n", np
,
126 /* disable the LED */
127 ret
= i2c_smbus_write_byte_data(client
, CMD_LED_STATE
,
128 CMD_LED_STATE_LED(led
->reg
));
130 dev_err(dev
, "Cannot set LED %pOF brightness: %i\n", np
, ret
);
134 ret
= devm_led_classdev_multicolor_register_ext(dev
, &led
->mc_cdev
,
137 dev_err(dev
, "Cannot register LED %pOF: %i\n", np
, ret
);
145 * On the front panel of the Turris Omnia router there is also a button which
146 * can be used to control the intensity of all the LEDs at once, so that if they
147 * are too bright, user can dim them.
148 * The microcontroller cycles between 8 levels of this global brightness (from
149 * 100% to 0%), but this setting can have any integer value between 0 and 100.
150 * It is therefore convenient to be able to change this setting from software.
151 * We expose this setting via a sysfs attribute file called "brightness". This
152 * file lives in the device directory of the LED controller, not an individual
153 * LED, so it should not confuse users.
155 static ssize_t
brightness_show(struct device
*dev
, struct device_attribute
*a
,
158 struct i2c_client
*client
= to_i2c_client(dev
);
159 struct omnia_leds
*leds
= i2c_get_clientdata(client
);
162 mutex_lock(&leds
->lock
);
163 ret
= i2c_smbus_read_byte_data(client
, CMD_LED_GET_BRIGHTNESS
);
164 mutex_unlock(&leds
->lock
);
169 return sprintf(buf
, "%d\n", ret
);
172 static ssize_t
brightness_store(struct device
*dev
, struct device_attribute
*a
,
173 const char *buf
, size_t count
)
175 struct i2c_client
*client
= to_i2c_client(dev
);
176 struct omnia_leds
*leds
= i2c_get_clientdata(client
);
177 unsigned long brightness
;
180 if (kstrtoul(buf
, 10, &brightness
))
183 if (brightness
> 100)
186 mutex_lock(&leds
->lock
);
187 ret
= i2c_smbus_write_byte_data(client
, CMD_LED_SET_BRIGHTNESS
,
189 mutex_unlock(&leds
->lock
);
196 static DEVICE_ATTR_RW(brightness
);
198 static struct attribute
*omnia_led_controller_attrs
[] = {
199 &dev_attr_brightness
.attr
,
202 ATTRIBUTE_GROUPS(omnia_led_controller
);
204 static int omnia_leds_probe(struct i2c_client
*client
,
205 const struct i2c_device_id
*id
)
207 struct device
*dev
= &client
->dev
;
208 struct device_node
*np
= dev_of_node(dev
), *child
;
209 struct omnia_leds
*leds
;
210 struct omnia_led
*led
;
213 count
= of_get_available_child_count(np
);
215 dev_err(dev
, "LEDs are not defined in device tree!\n");
217 } else if (count
> OMNIA_BOARD_LEDS
) {
218 dev_err(dev
, "Too many LEDs defined in device tree!\n");
222 leds
= devm_kzalloc(dev
, struct_size(leds
, leds
, count
), GFP_KERNEL
);
226 leds
->client
= client
;
227 i2c_set_clientdata(client
, leds
);
229 mutex_init(&leds
->lock
);
231 led
= &leds
->leds
[0];
232 for_each_available_child_of_node(np
, child
) {
233 ret
= omnia_led_register(client
, led
, child
);
242 if (devm_device_add_groups(dev
, omnia_led_controller_groups
))
243 dev_warn(dev
, "Could not add attribute group!\n");
248 static int omnia_leds_remove(struct i2c_client
*client
)
252 /* put all LEDs into default (HW triggered) mode */
253 i2c_smbus_write_byte_data(client
, CMD_LED_MODE
,
254 CMD_LED_MODE_LED(OMNIA_BOARD_LEDS
));
256 /* set all LEDs color to [255, 255, 255] */
257 buf
[0] = CMD_LED_COLOR
;
258 buf
[1] = OMNIA_BOARD_LEDS
;
263 i2c_master_send(client
, buf
, 5);
268 static const struct of_device_id of_omnia_leds_match
[] = {
269 { .compatible
= "cznic,turris-omnia-leds", },
273 static const struct i2c_device_id omnia_id
[] = {
278 static struct i2c_driver omnia_leds_driver
= {
279 .probe
= omnia_leds_probe
,
280 .remove
= omnia_leds_remove
,
281 .id_table
= omnia_id
,
283 .name
= "leds-turris-omnia",
284 .of_match_table
= of_omnia_leds_match
,
288 module_i2c_driver(omnia_leds_driver
);
290 MODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>");
291 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs");
292 MODULE_LICENSE("GPL v2");