led_bl.c 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Copyright (C) 2015-2019 Texas Instruments Incorporated - http://www.ti.com/
  4. * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
  5. *
  6. * Based on pwm_bl.c
  7. */
  8. #include <linux/backlight.h>
  9. #include <linux/leds.h>
  10. #include <linux/module.h>
  11. #include <linux/of.h>
  12. #include <linux/platform_device.h>
  13. struct led_bl_data {
  14. struct device *dev;
  15. struct backlight_device *bl_dev;
  16. struct led_classdev **leds;
  17. bool enabled;
  18. int nb_leds;
  19. unsigned int *levels;
  20. unsigned int default_brightness;
  21. unsigned int max_brightness;
  22. };
  23. static void led_bl_set_brightness(struct led_bl_data *priv, int level)
  24. {
  25. int i;
  26. int bkl_brightness;
  27. if (priv->levels)
  28. bkl_brightness = priv->levels[level];
  29. else
  30. bkl_brightness = level;
  31. for (i = 0; i < priv->nb_leds; i++)
  32. led_set_brightness(priv->leds[i], bkl_brightness);
  33. priv->enabled = true;
  34. }
  35. static void led_bl_power_off(struct led_bl_data *priv)
  36. {
  37. int i;
  38. if (!priv->enabled)
  39. return;
  40. for (i = 0; i < priv->nb_leds; i++)
  41. led_set_brightness(priv->leds[i], LED_OFF);
  42. priv->enabled = false;
  43. }
  44. static int led_bl_update_status(struct backlight_device *bl)
  45. {
  46. struct led_bl_data *priv = bl_get_data(bl);
  47. int brightness = backlight_get_brightness(bl);
  48. if (brightness > 0)
  49. led_bl_set_brightness(priv, brightness);
  50. else
  51. led_bl_power_off(priv);
  52. return 0;
  53. }
  54. static const struct backlight_ops led_bl_ops = {
  55. .update_status = led_bl_update_status,
  56. };
  57. static int led_bl_get_leds(struct device *dev,
  58. struct led_bl_data *priv)
  59. {
  60. int i, nb_leds, ret;
  61. struct device_node *node = dev->of_node;
  62. struct led_classdev **leds;
  63. unsigned int max_brightness;
  64. unsigned int default_brightness;
  65. ret = of_count_phandle_with_args(node, "leds", NULL);
  66. if (ret < 0) {
  67. dev_err(dev, "Unable to get led count\n");
  68. return -EINVAL;
  69. }
  70. nb_leds = ret;
  71. if (nb_leds < 1) {
  72. dev_err(dev, "At least one LED must be specified!\n");
  73. return -EINVAL;
  74. }
  75. leds = devm_kcalloc(dev, nb_leds, sizeof(struct led_classdev *),
  76. GFP_KERNEL);
  77. if (!leds)
  78. return -ENOMEM;
  79. for (i = 0; i < nb_leds; i++) {
  80. leds[i] = devm_of_led_get(dev, i);
  81. if (IS_ERR(leds[i]))
  82. return PTR_ERR(leds[i]);
  83. }
  84. /* check that the LEDs all have the same brightness range */
  85. max_brightness = leds[0]->max_brightness;
  86. for (i = 1; i < nb_leds; i++) {
  87. if (max_brightness != leds[i]->max_brightness) {
  88. dev_err(dev, "LEDs must have identical ranges\n");
  89. return -EINVAL;
  90. }
  91. }
  92. /* get the default brightness from the first LED from the list */
  93. default_brightness = leds[0]->brightness;
  94. priv->nb_leds = nb_leds;
  95. priv->leds = leds;
  96. priv->max_brightness = max_brightness;
  97. priv->default_brightness = default_brightness;
  98. return 0;
  99. }
  100. static int led_bl_parse_levels(struct device *dev,
  101. struct led_bl_data *priv)
  102. {
  103. struct device_node *node = dev->of_node;
  104. int num_levels;
  105. u32 value;
  106. int ret;
  107. if (!node)
  108. return -ENODEV;
  109. num_levels = of_property_count_u32_elems(node, "brightness-levels");
  110. if (num_levels > 1) {
  111. int i;
  112. unsigned int db;
  113. u32 *levels = NULL;
  114. levels = devm_kcalloc(dev, num_levels, sizeof(u32),
  115. GFP_KERNEL);
  116. if (!levels)
  117. return -ENOMEM;
  118. ret = of_property_read_u32_array(node, "brightness-levels",
  119. levels,
  120. num_levels);
  121. if (ret < 0)
  122. return ret;
  123. /*
  124. * Try to map actual LED brightness to backlight brightness
  125. * level
  126. */
  127. db = priv->default_brightness;
  128. for (i = 0 ; i < num_levels; i++) {
  129. if ((i && db > levels[i-1]) && db <= levels[i])
  130. break;
  131. }
  132. priv->default_brightness = i;
  133. priv->max_brightness = num_levels - 1;
  134. priv->levels = levels;
  135. } else if (num_levels >= 0)
  136. dev_warn(dev, "Not enough levels defined\n");
  137. ret = of_property_read_u32(node, "default-brightness-level", &value);
  138. if (!ret && value <= priv->max_brightness)
  139. priv->default_brightness = value;
  140. else if (!ret && value > priv->max_brightness)
  141. dev_warn(dev, "Invalid default brightness. Ignoring it\n");
  142. return 0;
  143. }
  144. static int led_bl_probe(struct platform_device *pdev)
  145. {
  146. struct backlight_properties props;
  147. struct led_bl_data *priv;
  148. int ret, i;
  149. priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
  150. if (!priv)
  151. return -ENOMEM;
  152. platform_set_drvdata(pdev, priv);
  153. priv->dev = &pdev->dev;
  154. ret = led_bl_get_leds(&pdev->dev, priv);
  155. if (ret)
  156. return ret;
  157. ret = led_bl_parse_levels(&pdev->dev, priv);
  158. if (ret < 0) {
  159. dev_err(&pdev->dev, "Failed to parse DT data\n");
  160. return ret;
  161. }
  162. memset(&props, 0, sizeof(struct backlight_properties));
  163. props.type = BACKLIGHT_RAW;
  164. props.max_brightness = priv->max_brightness;
  165. props.brightness = priv->default_brightness;
  166. props.power = (priv->default_brightness > 0) ? BACKLIGHT_POWER_OFF :
  167. BACKLIGHT_POWER_ON;
  168. priv->bl_dev = backlight_device_register(dev_name(&pdev->dev),
  169. &pdev->dev, priv, &led_bl_ops, &props);
  170. if (IS_ERR(priv->bl_dev)) {
  171. dev_err(&pdev->dev, "Failed to register backlight\n");
  172. return PTR_ERR(priv->bl_dev);
  173. }
  174. for (i = 0; i < priv->nb_leds; i++) {
  175. struct device_link *link;
  176. link = device_link_add(&pdev->dev, priv->leds[i]->dev->parent,
  177. DL_FLAG_AUTOREMOVE_CONSUMER);
  178. if (!link) {
  179. dev_err(&pdev->dev, "Failed to add devlink (consumer %s, supplier %s)\n",
  180. dev_name(&pdev->dev), dev_name(priv->leds[i]->dev->parent));
  181. backlight_device_unregister(priv->bl_dev);
  182. return -EINVAL;
  183. }
  184. }
  185. for (i = 0; i < priv->nb_leds; i++) {
  186. mutex_lock(&priv->leds[i]->led_access);
  187. led_sysfs_disable(priv->leds[i]);
  188. mutex_unlock(&priv->leds[i]->led_access);
  189. }
  190. backlight_update_status(priv->bl_dev);
  191. return 0;
  192. }
  193. static void led_bl_remove(struct platform_device *pdev)
  194. {
  195. struct led_bl_data *priv = platform_get_drvdata(pdev);
  196. struct backlight_device *bl = priv->bl_dev;
  197. int i;
  198. backlight_device_unregister(bl);
  199. led_bl_power_off(priv);
  200. for (i = 0; i < priv->nb_leds; i++) {
  201. mutex_lock(&priv->leds[i]->led_access);
  202. led_sysfs_enable(priv->leds[i]);
  203. mutex_unlock(&priv->leds[i]->led_access);
  204. }
  205. }
  206. static const struct of_device_id led_bl_of_match[] = {
  207. { .compatible = "led-backlight" },
  208. { }
  209. };
  210. MODULE_DEVICE_TABLE(of, led_bl_of_match);
  211. static struct platform_driver led_bl_driver = {
  212. .driver = {
  213. .name = "led-backlight",
  214. .of_match_table = led_bl_of_match,
  215. },
  216. .probe = led_bl_probe,
  217. .remove = led_bl_remove,
  218. };
  219. module_platform_driver(led_bl_driver);
  220. MODULE_DESCRIPTION("LED based Backlight Driver");
  221. MODULE_LICENSE("GPL");
  222. MODULE_ALIAS("platform:led-backlight");