Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: turris-omnia: Add support for enabling/disabling HW gamma correction

If the MCU on Turris Omnia is running newer firmware versions, the LED
controller supports RGB gamma correction (and enables it by default for
newer boards).

Determine whether the gamma correction setting feature is supported and
add the ability to set it via sysfs attribute file.

Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-5-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Marek Behún and committed by
Lee Jones
43e9082f cbd6954f

+134 -17
+14
Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia
··· 12 12 able to change this setting from software. 13 13 14 14 Format: %i 15 + 16 + What: /sys/class/leds/<led>/device/gamma_correction 17 + Date: August 2023 18 + KernelVersion: 6.6 19 + Contact: Marek Behún <kabel@kernel.org> 20 + Description: (RW) Newer versions of the microcontroller firmware of the 21 + Turris Omnia router support gamma correction for the RGB LEDs. 22 + This feature can be enabled/disabled by writing to this file. 23 + 24 + If the feature is not supported because the MCU firmware is too 25 + old, the file always reads as 0, and writing to the file results 26 + in the EOPNOTSUPP error. 27 + 28 + Format: %i
+120 -17
drivers/leds/leds-turris-omnia.c
··· 15 15 #define OMNIA_BOARD_LEDS 12 16 16 #define OMNIA_LED_NUM_CHANNELS 3 17 17 18 - #define CMD_LED_MODE 3 19 - #define CMD_LED_MODE_LED(l) ((l) & 0x0f) 20 - #define CMD_LED_MODE_USER 0x10 18 + /* MCU controller commands at I2C address 0x2a */ 19 + #define OMNIA_MCU_I2C_ADDR 0x2a 21 20 22 - #define CMD_LED_STATE 4 23 - #define CMD_LED_STATE_LED(l) ((l) & 0x0f) 24 - #define CMD_LED_STATE_ON 0x10 21 + #define CMD_GET_STATUS_WORD 0x01 22 + #define STS_FEATURES_SUPPORTED BIT(2) 25 23 26 - #define CMD_LED_COLOR 5 27 - #define CMD_LED_SET_BRIGHTNESS 7 28 - #define CMD_LED_GET_BRIGHTNESS 8 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 29 42 30 43 struct omnia_led { 31 44 struct led_classdev_mc mc_cdev; ··· 53 40 struct omnia_leds { 54 41 struct i2c_client *client; 55 42 struct mutex lock; 43 + bool has_gamma_correction; 56 44 struct omnia_led leds[]; 57 45 }; 58 46 ··· 64 50 return i2c_master_send(client, buf, sizeof(buf)); 65 51 } 66 52 67 - static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) 53 + static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, 54 + void *reply, size_t len) 68 55 { 69 56 struct i2c_msg msgs[2]; 70 - u8 reply; 71 57 int ret; 72 58 73 - msgs[0].addr = client->addr; 59 + msgs[0].addr = addr; 74 60 msgs[0].flags = 0; 75 61 msgs[0].len = 1; 76 62 msgs[0].buf = &cmd; 77 - msgs[1].addr = client->addr; 63 + msgs[1].addr = addr; 78 64 msgs[1].flags = I2C_M_RD; 79 - msgs[1].len = 1; 80 - msgs[1].buf = &reply; 65 + msgs[1].len = len; 66 + msgs[1].buf = reply; 81 67 82 - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); 68 + ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); 83 69 if (likely(ret == ARRAY_SIZE(msgs))) 84 - return reply; 70 + return len; 85 71 else if (ret < 0) 86 72 return ret; 87 73 else 88 74 return -EIO; 75 + } 76 + 77 + static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) 78 + { 79 + u8 reply; 80 + int ret; 81 + 82 + ret = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); 83 + if (ret < 0) 84 + return ret; 85 + 86 + return reply; 89 87 } 90 88 91 89 static int omnia_led_send_color_cmd(const struct i2c_client *client, ··· 378 352 } 379 353 static DEVICE_ATTR_RW(brightness); 380 354 355 + static ssize_t gamma_correction_show(struct device *dev, 356 + struct device_attribute *a, char *buf) 357 + { 358 + struct i2c_client *client = to_i2c_client(dev); 359 + struct omnia_leds *leds = i2c_get_clientdata(client); 360 + int ret; 361 + 362 + if (leds->has_gamma_correction) { 363 + ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION); 364 + if (ret < 0) 365 + return ret; 366 + } else { 367 + ret = 0; 368 + } 369 + 370 + return sysfs_emit(buf, "%d\n", !!ret); 371 + } 372 + 373 + static ssize_t gamma_correction_store(struct device *dev, 374 + struct device_attribute *a, 375 + const char *buf, size_t count) 376 + { 377 + struct i2c_client *client = to_i2c_client(dev); 378 + struct omnia_leds *leds = i2c_get_clientdata(client); 379 + bool val; 380 + int ret; 381 + 382 + if (!leds->has_gamma_correction) 383 + return -EOPNOTSUPP; 384 + 385 + if (kstrtobool(buf, &val) < 0) 386 + return -EINVAL; 387 + 388 + ret = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); 389 + 390 + return ret < 0 ? ret : count; 391 + } 392 + static DEVICE_ATTR_RW(gamma_correction); 393 + 381 394 static struct attribute *omnia_led_controller_attrs[] = { 382 395 &dev_attr_brightness.attr, 396 + &dev_attr_gamma_correction.attr, 383 397 NULL, 384 398 }; 385 399 ATTRIBUTE_GROUPS(omnia_led_controller); 400 + 401 + static int omnia_mcu_get_features(const struct i2c_client *client) 402 + { 403 + u16 reply; 404 + int err; 405 + 406 + err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, 407 + CMD_GET_STATUS_WORD, &reply, sizeof(reply)); 408 + if (err < 0) 409 + return err; 410 + 411 + /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ 412 + if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED)) 413 + return 0; 414 + 415 + err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, 416 + CMD_GET_FEATURES, &reply, sizeof(reply)); 417 + if (err < 0) 418 + return err; 419 + 420 + return le16_to_cpu(reply); 421 + } 386 422 387 423 static int omnia_leds_probe(struct i2c_client *client) 388 424 { ··· 469 381 470 382 leds->client = client; 471 383 i2c_set_clientdata(client, leds); 384 + 385 + ret = omnia_mcu_get_features(client); 386 + if (ret < 0) { 387 + dev_err(dev, "Cannot determine MCU supported features: %d\n", 388 + ret); 389 + return ret; 390 + } 391 + 392 + leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION; 393 + if (!leds->has_gamma_correction) { 394 + dev_info(dev, 395 + "Your board's MCU firmware does not support the LED gamma correction feature.\n"); 396 + dev_info(dev, 397 + "Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); 398 + } 472 399 473 400 mutex_init(&leds->lock); 474 401