815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. From a64c3c1357275b1fd61bc9734f638cdb5d8a8bbb Mon Sep 17 00:00:00 2001
  2. From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <[email protected]>
  3. Date: Mon, 18 Sep 2023 18:11:02 +0200
  4. Subject: [PATCH 4/6] leds: turris-omnia: Make set_brightness() more efficient
  5. MIME-Version: 1.0
  6. Content-Type: text/plain; charset=UTF-8
  7. Content-Transfer-Encoding: 8bit
  8. Implement caching of the LED color and state values that are sent to MCU
  9. in order to make the set_brightness() operation more efficient by
  10. avoiding I2C transactions which are not needed.
  11. On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
  12. has a RGB color, and a ON/OFF state, which are configurable via I2C
  13. commands CMD_LED_COLOR and CMD_LED_STATE.
  14. The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
  15. bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
  16. this allows ~1670 color changing commands and ~3200 state changing
  17. commands per second (or around 1000 color + state changes per second).
  18. This may seem more than enough, but the issue is that the I2C bus is
  19. shared with another peripheral, the MCU. The MCU exposes an interrupt
  20. interface, and it can trigger hundreds of interrupts per second. Each
  21. time, we need to read the interrupt state register over this I2C bus.
  22. Whenever we are sending a LED color/state changing command, the
  23. interrupt reading is waiting.
  24. Currently, every time LED brightness or LED multi intensity is changed,
  25. we send a CMD_LED_STATE command, and if the computed color (brightness
  26. adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
  27. command.
  28. Consider for example the situation when we have a netdev trigger enabled
  29. for a LED. The netdev trigger does not change the LED color, only the
  30. brightness (either to 0 or to currently configured brightness), and so
  31. there is no need to send the CMD_LED_COLOR command. But each change of
  32. brightness to 0 sends one CMD_LED_STATE command, and each change of
  33. brightness to max_brightness sends one CMD_LED_STATE command and one
  34. CMD_LED_COLOR command:
  35. set_brightness(0) -> CMD_LED_STATE
  36. set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
  37. (unnecessary)
  38. We can avoid the unnecessary I2C transactions if we cache the values of
  39. state and color that are sent to the controller. If the color does not
  40. change from the one previously sent, there is no need to do the
  41. CMD_LED_COLOR I2C transaction, and if the state does not change, there
  42. is no need to do the CMD_LED_STATE transaction.
  43. Because we need to make sure that our cached values are consistent with
  44. the controller state, add explicit setting of the LED color to white at
  45. probe time (this is the default setting when MCU resets, but does not
  46. necessarily need to be the case, for example if U-Boot played with the
  47. LED colors).
  48. Signed-off-by: Marek Behún <[email protected]>
  49. Link: https://lore.kernel.org/r/[email protected]
  50. Signed-off-by: Lee Jones <[email protected]>
  51. ---
  52. drivers/leds/leds-turris-omnia.c | 96 ++++++++++++++++++++++++++------
  53. 1 file changed, 78 insertions(+), 18 deletions(-)
  54. --- a/drivers/leds/leds-turris-omnia.c
  55. +++ b/drivers/leds/leds-turris-omnia.c
  56. @@ -30,6 +30,8 @@
  57. struct omnia_led {
  58. struct led_classdev_mc mc_cdev;
  59. struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
  60. + u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
  61. + bool on;
  62. int reg;
  63. };
  64. @@ -72,36 +74,82 @@ static int omnia_cmd_read_u8(const struc
  65. return -EIO;
  66. }
  67. +static int omnia_led_send_color_cmd(const struct i2c_client *client,
  68. + struct omnia_led *led)
  69. +{
  70. + char cmd[5];
  71. + int ret;
  72. +
  73. + cmd[0] = CMD_LED_COLOR;
  74. + cmd[1] = led->reg;
  75. + cmd[2] = led->subled_info[0].brightness;
  76. + cmd[3] = led->subled_info[1].brightness;
  77. + cmd[4] = led->subled_info[2].brightness;
  78. +
  79. + /* Send the color change command */
  80. + ret = i2c_master_send(client, cmd, 5);
  81. + if (ret < 0)
  82. + return ret;
  83. +
  84. + /* Cache the RGB channel brightnesses */
  85. + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
  86. + led->cached_channels[i] = led->subled_info[i].brightness;
  87. +
  88. + return 0;
  89. +}
  90. +
  91. +/* Determine if the computed RGB channels are different from the cached ones */
  92. +static bool omnia_led_channels_changed(struct omnia_led *led)
  93. +{
  94. + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
  95. + if (led->subled_info[i].brightness != led->cached_channels[i])
  96. + return true;
  97. +
  98. + return false;
  99. +}
  100. +
  101. static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
  102. enum led_brightness brightness)
  103. {
  104. struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
  105. struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
  106. struct omnia_led *led = to_omnia_led(mc_cdev);
  107. - u8 buf[5], state;
  108. - int ret;
  109. + int err = 0;
  110. mutex_lock(&leds->lock);
  111. - led_mc_calc_color_components(&led->mc_cdev, brightness);
  112. + /*
  113. + * Only recalculate RGB brightnesses from intensities if brightness is
  114. + * non-zero. Otherwise we won't be using them and we can save ourselves
  115. + * some software divisions (Omnia's CPU does not implement the division
  116. + * instruction).
  117. + */
  118. + if (brightness) {
  119. + led_mc_calc_color_components(mc_cdev, brightness);
  120. +
  121. + /*
  122. + * Send color command only if brightness is non-zero and the RGB
  123. + * channel brightnesses changed.
  124. + */
  125. + if (omnia_led_channels_changed(led))
  126. + err = omnia_led_send_color_cmd(leds->client, led);
  127. + }
  128. - buf[0] = CMD_LED_COLOR;
  129. - buf[1] = led->reg;
  130. - buf[2] = mc_cdev->subled_info[0].brightness;
  131. - buf[3] = mc_cdev->subled_info[1].brightness;
  132. - buf[4] = mc_cdev->subled_info[2].brightness;
  133. -
  134. - state = CMD_LED_STATE_LED(led->reg);
  135. - if (buf[2] || buf[3] || buf[4])
  136. - state |= CMD_LED_STATE_ON;
  137. -
  138. - ret = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
  139. - if (ret >= 0 && (state & CMD_LED_STATE_ON))
  140. - ret = i2c_master_send(leds->client, buf, 5);
  141. + /* Send on/off state change only if (bool)brightness changed */
  142. + if (!err && !brightness != !led->on) {
  143. + u8 state = CMD_LED_STATE_LED(led->reg);
  144. +
  145. + if (brightness)
  146. + state |= CMD_LED_STATE_ON;
  147. +
  148. + err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
  149. + if (!err)
  150. + led->on = !!brightness;
  151. + }
  152. mutex_unlock(&leds->lock);
  153. - return ret;
  154. + return err;
  155. }
  156. static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
  157. @@ -129,11 +177,15 @@ static int omnia_led_register(struct i2c
  158. }
  159. led->subled_info[0].color_index = LED_COLOR_ID_RED;
  160. - led->subled_info[0].channel = 0;
  161. led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
  162. - led->subled_info[1].channel = 1;
  163. led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
  164. - led->subled_info[2].channel = 2;
  165. +
  166. + /* Initial color is white */
  167. + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) {
  168. + led->subled_info[i].intensity = 255;
  169. + led->subled_info[i].brightness = 255;
  170. + led->subled_info[i].channel = i;
  171. + }
  172. led->mc_cdev.subled_info = led->subled_info;
  173. led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
  174. @@ -162,6 +214,14 @@ static int omnia_led_register(struct i2c
  175. return ret;
  176. }
  177. + /* Set initial color and cache it */
  178. + ret = omnia_led_send_color_cmd(client, led);
  179. + if (ret < 0) {
  180. + dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np,
  181. + ret);
  182. + return ret;
  183. + }
  184. +
  185. ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
  186. &init_data);
  187. if (ret < 0) {