leds-turris-omnia.c 14 KB

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