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