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 38 struct 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 46 struct omnia_leds { 47 struct i2c_client *client; 48 struct mutex lock; 49 struct omnia_led leds[]; 50 }; 51 52 static 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 84 static 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 */ 162 static 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 178 static 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 } 201 static DEVICE_ATTR_RW(brightness); 202 203 static struct attribute *omnia_led_controller_attrs[] = { 204 &dev_attr_brightness.attr, 205 NULL, 206 }; 207 ATTRIBUTE_GROUPS(omnia_led_controller); 208 209 static 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 251 static 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 271 static const struct of_device_id of_omnia_leds_match[] = { 272 { .compatible = "cznic,turris-omnia-leds", }, 273 {}, 274 }; 275 276 static const struct i2c_device_id omnia_id[] = { 277 { "omnia", 0 }, 278 { } 279 }; 280 281 static 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 291 module_i2c_driver(omnia_leds_driver); 292 293 MODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>"); 294 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs"); 295 MODULE_LICENSE("GPL v2"); 296