1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * CZ.NIC's Turris Omnia LEDs driver 4 * 5 * 2020, 2023 by Marek Behún <kabel@kernel.org> 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 /* MCU controller commands at I2C address 0x2a */ 19 #define OMNIA_MCU_I2C_ADDR 0x2a 20 21 #define CMD_GET_STATUS_WORD 0x01 22 #define STS_FEATURES_SUPPORTED BIT(2) 23 24 #define CMD_GET_FEATURES 0x10 25 #define FEAT_LED_GAMMA_CORRECTION BIT(5) 26 27 /* LED controller commands at I2C address 0x2b */ 28 #define CMD_LED_MODE 0x03 29 #define CMD_LED_MODE_LED(l) ((l) & 0x0f) 30 #define CMD_LED_MODE_USER 0x10 31 32 #define CMD_LED_STATE 0x04 33 #define CMD_LED_STATE_LED(l) ((l) & 0x0f) 34 #define CMD_LED_STATE_ON 0x10 35 36 #define CMD_LED_COLOR 0x05 37 #define CMD_LED_SET_BRIGHTNESS 0x07 38 #define CMD_LED_GET_BRIGHTNESS 0x08 39 40 #define CMD_SET_GAMMA_CORRECTION 0x30 41 #define CMD_GET_GAMMA_CORRECTION 0x31 42 43 struct omnia_led { 44 struct led_classdev_mc mc_cdev; 45 struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; 46 u8 cached_channels[OMNIA_LED_NUM_CHANNELS]; 47 bool on, hwtrig; 48 int reg; 49 }; 50 51 #define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev) 52 53 struct omnia_leds { 54 struct i2c_client *client; 55 struct mutex lock; 56 bool has_gamma_correction; 57 struct omnia_led leds[]; 58 }; 59 60 static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val) 61 { 62 u8 buf[2] = { cmd, val }; 63 int ret; 64 65 ret = i2c_master_send(client, buf, sizeof(buf)); 66 67 return ret < 0 ? ret : 0; 68 } 69 70 static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, 71 void *reply, size_t len) 72 { 73 struct i2c_msg msgs[2]; 74 int ret; 75 76 msgs[0].addr = addr; 77 msgs[0].flags = 0; 78 msgs[0].len = 1; 79 msgs[0].buf = &cmd; 80 msgs[1].addr = addr; 81 msgs[1].flags = I2C_M_RD; 82 msgs[1].len = len; 83 msgs[1].buf = reply; 84 85 ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); 86 if (likely(ret == ARRAY_SIZE(msgs))) 87 return 0; 88 else if (ret < 0) 89 return ret; 90 else 91 return -EIO; 92 } 93 94 static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) 95 { 96 u8 reply; 97 int err; 98 99 err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); 100 if (err) 101 return err; 102 103 return reply; 104 } 105 106 static int omnia_led_send_color_cmd(const struct i2c_client *client, 107 struct omnia_led *led) 108 { 109 char cmd[5]; 110 int ret; 111 112 cmd[0] = CMD_LED_COLOR; 113 cmd[1] = led->reg; 114 cmd[2] = led->subled_info[0].brightness; 115 cmd[3] = led->subled_info[1].brightness; 116 cmd[4] = led->subled_info[2].brightness; 117 118 /* Send the color change command */ 119 ret = i2c_master_send(client, cmd, 5); 120 if (ret < 0) 121 return ret; 122 123 /* Cache the RGB channel brightnesses */ 124 for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) 125 led->cached_channels[i] = led->subled_info[i].brightness; 126 127 return 0; 128 } 129 130 /* Determine if the computed RGB channels are different from the cached ones */ 131 static bool omnia_led_channels_changed(struct omnia_led *led) 132 { 133 for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) 134 if (led->subled_info[i].brightness != led->cached_channels[i]) 135 return true; 136 137 return false; 138 } 139 140 static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, 141 enum led_brightness brightness) 142 { 143 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 144 struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); 145 struct omnia_led *led = to_omnia_led(mc_cdev); 146 int err = 0; 147 148 mutex_lock(&leds->lock); 149 150 /* 151 * Only recalculate RGB brightnesses from intensities if brightness is 152 * non-zero (if it is zero and the LED is in HW blinking mode, we use 153 * max_brightness as brightness). Otherwise we won't be using them and 154 * we can save ourselves some software divisions (Omnia's CPU does not 155 * implement the division instruction). 156 */ 157 if (brightness || led->hwtrig) { 158 led_mc_calc_color_components(mc_cdev, brightness ?: 159 cdev->max_brightness); 160 161 /* 162 * Send color command only if brightness is non-zero and the RGB 163 * channel brightnesses changed. 164 */ 165 if (omnia_led_channels_changed(led)) 166 err = omnia_led_send_color_cmd(leds->client, led); 167 } 168 169 /* 170 * Send on/off state change only if (bool)brightness changed and the LED 171 * is not being blinked by HW. 172 */ 173 if (!err && !led->hwtrig && !brightness != !led->on) { 174 u8 state = CMD_LED_STATE_LED(led->reg); 175 176 if (brightness) 177 state |= CMD_LED_STATE_ON; 178 179 err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state); 180 if (!err) 181 led->on = !!brightness; 182 } 183 184 mutex_unlock(&leds->lock); 185 186 return err; 187 } 188 189 static struct led_hw_trigger_type omnia_hw_trigger_type; 190 191 static int omnia_hwtrig_activate(struct led_classdev *cdev) 192 { 193 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 194 struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); 195 struct omnia_led *led = to_omnia_led(mc_cdev); 196 int err = 0; 197 198 mutex_lock(&leds->lock); 199 200 if (!led->on) { 201 /* 202 * If the LED is off (brightness was set to 0), the last 203 * configured color was not necessarily sent to the MCU. 204 * Recompute with max_brightness and send if needed. 205 */ 206 led_mc_calc_color_components(mc_cdev, cdev->max_brightness); 207 208 if (omnia_led_channels_changed(led)) 209 err = omnia_led_send_color_cmd(leds->client, led); 210 } 211 212 if (!err) { 213 /* Put the LED into MCU controlled mode */ 214 err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE, 215 CMD_LED_MODE_LED(led->reg)); 216 if (!err) 217 led->hwtrig = true; 218 } 219 220 mutex_unlock(&leds->lock); 221 222 return err; 223 } 224 225 static void omnia_hwtrig_deactivate(struct led_classdev *cdev) 226 { 227 struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); 228 struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev)); 229 int err; 230 231 mutex_lock(&leds->lock); 232 233 led->hwtrig = false; 234 235 /* Put the LED into software mode */ 236 err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE, 237 CMD_LED_MODE_LED(led->reg) | 238 CMD_LED_MODE_USER); 239 240 mutex_unlock(&leds->lock); 241 242 if (err) 243 dev_err(cdev->dev, "Cannot put LED to software mode: %i\n", 244 err); 245 } 246 247 static struct led_trigger omnia_hw_trigger = { 248 .name = "omnia-mcu", 249 .activate = omnia_hwtrig_activate, 250 .deactivate = omnia_hwtrig_deactivate, 251 .trigger_type = &omnia_hw_trigger_type, 252 }; 253 254 static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, 255 struct device_node *np) 256 { 257 struct led_init_data init_data = {}; 258 struct device *dev = &client->dev; 259 struct led_classdev *cdev; 260 int ret, color; 261 262 ret = of_property_read_u32(np, "reg", &led->reg); 263 if (ret || led->reg >= OMNIA_BOARD_LEDS) { 264 dev_warn(dev, 265 "Node %pOF: must contain 'reg' property with values between 0 and %i\n", 266 np, OMNIA_BOARD_LEDS - 1); 267 return 0; 268 } 269 270 ret = of_property_read_u32(np, "color", &color); 271 if (ret || color != LED_COLOR_ID_RGB) { 272 dev_warn(dev, 273 "Node %pOF: must contain 'color' property with value LED_COLOR_ID_RGB\n", 274 np); 275 return 0; 276 } 277 278 led->subled_info[0].color_index = LED_COLOR_ID_RED; 279 led->subled_info[1].color_index = LED_COLOR_ID_GREEN; 280 led->subled_info[2].color_index = LED_COLOR_ID_BLUE; 281 282 /* Initial color is white */ 283 for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) { 284 led->subled_info[i].intensity = 255; 285 led->subled_info[i].brightness = 255; 286 led->subled_info[i].channel = i; 287 } 288 289 led->mc_cdev.subled_info = led->subled_info; 290 led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS; 291 292 init_data.fwnode = &np->fwnode; 293 294 cdev = &led->mc_cdev.led_cdev; 295 cdev->max_brightness = 255; 296 cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; 297 cdev->trigger_type = &omnia_hw_trigger_type; 298 /* 299 * Use the omnia-mcu trigger as the default trigger. It may be rewritten 300 * by LED class from the linux,default-trigger property. 301 */ 302 cdev->default_trigger = omnia_hw_trigger.name; 303 304 /* put the LED into software mode */ 305 ret = omnia_cmd_write_u8(client, CMD_LED_MODE, 306 CMD_LED_MODE_LED(led->reg) | 307 CMD_LED_MODE_USER); 308 if (ret) { 309 dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, 310 ret); 311 return ret; 312 } 313 314 /* disable the LED */ 315 ret = omnia_cmd_write_u8(client, CMD_LED_STATE, 316 CMD_LED_STATE_LED(led->reg)); 317 if (ret) { 318 dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); 319 return ret; 320 } 321 322 /* Set initial color and cache it */ 323 ret = omnia_led_send_color_cmd(client, led); 324 if (ret < 0) { 325 dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np, 326 ret); 327 return ret; 328 } 329 330 ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, 331 &init_data); 332 if (ret < 0) { 333 dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret); 334 return ret; 335 } 336 337 return 1; 338 } 339 340 /* 341 * On the front panel of the Turris Omnia router there is also a button which 342 * can be used to control the intensity of all the LEDs at once, so that if they 343 * are too bright, user can dim them. 344 * The microcontroller cycles between 8 levels of this global brightness (from 345 * 100% to 0%), but this setting can have any integer value between 0 and 100. 346 * It is therefore convenient to be able to change this setting from software. 347 * We expose this setting via a sysfs attribute file called "brightness". This 348 * file lives in the device directory of the LED controller, not an individual 349 * LED, so it should not confuse users. 350 */ 351 static ssize_t brightness_show(struct device *dev, struct device_attribute *a, 352 char *buf) 353 { 354 struct i2c_client *client = to_i2c_client(dev); 355 int ret; 356 357 ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS); 358 359 if (ret < 0) 360 return ret; 361 362 return sysfs_emit(buf, "%d\n", ret); 363 } 364 365 static ssize_t brightness_store(struct device *dev, struct device_attribute *a, 366 const char *buf, size_t count) 367 { 368 struct i2c_client *client = to_i2c_client(dev); 369 unsigned long brightness; 370 int err; 371 372 if (kstrtoul(buf, 10, &brightness)) 373 return -EINVAL; 374 375 if (brightness > 100) 376 return -EINVAL; 377 378 err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness); 379 380 return err ?: count; 381 } 382 static DEVICE_ATTR_RW(brightness); 383 384 static ssize_t gamma_correction_show(struct device *dev, 385 struct device_attribute *a, char *buf) 386 { 387 struct i2c_client *client = to_i2c_client(dev); 388 struct omnia_leds *leds = i2c_get_clientdata(client); 389 int ret; 390 391 if (leds->has_gamma_correction) { 392 ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION); 393 if (ret < 0) 394 return ret; 395 } else { 396 ret = 0; 397 } 398 399 return sysfs_emit(buf, "%d\n", !!ret); 400 } 401 402 static ssize_t gamma_correction_store(struct device *dev, 403 struct device_attribute *a, 404 const char *buf, size_t count) 405 { 406 struct i2c_client *client = to_i2c_client(dev); 407 struct omnia_leds *leds = i2c_get_clientdata(client); 408 bool val; 409 int err; 410 411 if (!leds->has_gamma_correction) 412 return -EOPNOTSUPP; 413 414 if (kstrtobool(buf, &val) < 0) 415 return -EINVAL; 416 417 err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); 418 419 return err ?: count; 420 } 421 static DEVICE_ATTR_RW(gamma_correction); 422 423 static struct attribute *omnia_led_controller_attrs[] = { 424 &dev_attr_brightness.attr, 425 &dev_attr_gamma_correction.attr, 426 NULL, 427 }; 428 ATTRIBUTE_GROUPS(omnia_led_controller); 429 430 static int omnia_mcu_get_features(const struct i2c_client *client) 431 { 432 u16 reply; 433 int err; 434 435 err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, 436 CMD_GET_STATUS_WORD, &reply, sizeof(reply)); 437 if (err) 438 return err; 439 440 /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ 441 if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED)) 442 return 0; 443 444 err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, 445 CMD_GET_FEATURES, &reply, sizeof(reply)); 446 if (err) 447 return err; 448 449 return le16_to_cpu(reply); 450 } 451 452 static int omnia_leds_probe(struct i2c_client *client) 453 { 454 struct device *dev = &client->dev; 455 struct device_node *np = dev_of_node(dev), *child; 456 struct omnia_leds *leds; 457 struct omnia_led *led; 458 int ret, count; 459 460 count = of_get_available_child_count(np); 461 if (!count) { 462 dev_err(dev, "LEDs are not defined in device tree!\n"); 463 return -ENODEV; 464 } else if (count > OMNIA_BOARD_LEDS) { 465 dev_err(dev, "Too many LEDs defined in device tree!\n"); 466 return -EINVAL; 467 } 468 469 leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL); 470 if (!leds) 471 return -ENOMEM; 472 473 leds->client = client; 474 i2c_set_clientdata(client, leds); 475 476 ret = omnia_mcu_get_features(client); 477 if (ret < 0) { 478 dev_err(dev, "Cannot determine MCU supported features: %d\n", 479 ret); 480 return ret; 481 } 482 483 leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION; 484 if (!leds->has_gamma_correction) { 485 dev_info(dev, 486 "Your board's MCU firmware does not support the LED gamma correction feature.\n"); 487 dev_info(dev, 488 "Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); 489 } 490 491 mutex_init(&leds->lock); 492 493 ret = devm_led_trigger_register(dev, &omnia_hw_trigger); 494 if (ret < 0) { 495 dev_err(dev, "Cannot register private LED trigger: %d\n", ret); 496 return ret; 497 } 498 499 led = &leds->leds[0]; 500 for_each_available_child_of_node(np, child) { 501 ret = omnia_led_register(client, led, child); 502 if (ret < 0) { 503 of_node_put(child); 504 return ret; 505 } 506 507 led += ret; 508 } 509 510 return 0; 511 } 512 513 static void omnia_leds_remove(struct i2c_client *client) 514 { 515 u8 buf[5]; 516 517 /* put all LEDs into default (HW triggered) mode */ 518 omnia_cmd_write_u8(client, CMD_LED_MODE, 519 CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); 520 521 /* set all LEDs color to [255, 255, 255] */ 522 buf[0] = CMD_LED_COLOR; 523 buf[1] = OMNIA_BOARD_LEDS; 524 buf[2] = 255; 525 buf[3] = 255; 526 buf[4] = 255; 527 528 i2c_master_send(client, buf, 5); 529 } 530 531 static const struct of_device_id of_omnia_leds_match[] = { 532 { .compatible = "cznic,turris-omnia-leds", }, 533 {}, 534 }; 535 536 static const struct i2c_device_id omnia_id[] = { 537 { "omnia", 0 }, 538 { } 539 }; 540 MODULE_DEVICE_TABLE(i2c, omnia_id); 541 542 static struct i2c_driver omnia_leds_driver = { 543 .probe = omnia_leds_probe, 544 .remove = omnia_leds_remove, 545 .id_table = omnia_id, 546 .driver = { 547 .name = "leds-turris-omnia", 548 .of_match_table = of_omnia_leds_match, 549 .dev_groups = omnia_led_controller_groups, 550 }, 551 }; 552 553 module_i2c_driver(omnia_leds_driver); 554 555 MODULE_AUTHOR("Marek Behun <kabel@kernel.org>"); 556 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs"); 557 MODULE_LICENSE("GPL v2"); 558