leds-cpcap.c 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Copyright (c) 2017 Sebastian Reichel <sre@kernel.org>
  4. */
  5. #include <linux/leds.h>
  6. #include <linux/mfd/motorola-cpcap.h>
  7. #include <linux/module.h>
  8. #include <linux/mutex.h>
  9. #include <linux/of_device.h>
  10. #include <linux/platform_device.h>
  11. #include <linux/regmap.h>
  12. #include <linux/regulator/consumer.h>
  13. #define CPCAP_LED_NO_CURRENT 0x0001
  14. struct cpcap_led_info {
  15. u16 reg;
  16. u16 mask;
  17. u16 limit;
  18. u16 init_mask;
  19. u16 init_val;
  20. };
  21. static const struct cpcap_led_info cpcap_led_red = {
  22. .reg = CPCAP_REG_REDC,
  23. .mask = 0x03FF,
  24. .limit = 31,
  25. };
  26. static const struct cpcap_led_info cpcap_led_green = {
  27. .reg = CPCAP_REG_GREENC,
  28. .mask = 0x03FF,
  29. .limit = 31,
  30. };
  31. static const struct cpcap_led_info cpcap_led_blue = {
  32. .reg = CPCAP_REG_BLUEC,
  33. .mask = 0x03FF,
  34. .limit = 31,
  35. };
  36. /* aux display light */
  37. static const struct cpcap_led_info cpcap_led_adl = {
  38. .reg = CPCAP_REG_ADLC,
  39. .mask = 0x000F,
  40. .limit = 1,
  41. .init_mask = 0x7FFF,
  42. .init_val = 0x5FF0,
  43. };
  44. /* camera privacy led */
  45. static const struct cpcap_led_info cpcap_led_cp = {
  46. .reg = CPCAP_REG_CLEDC,
  47. .mask = 0x0007,
  48. .limit = 1,
  49. .init_mask = 0x03FF,
  50. .init_val = 0x0008,
  51. };
  52. struct cpcap_led {
  53. struct led_classdev led;
  54. const struct cpcap_led_info *info;
  55. struct device *dev;
  56. struct regmap *regmap;
  57. struct mutex update_lock;
  58. struct regulator *vdd;
  59. bool powered;
  60. u32 current_limit;
  61. };
  62. static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle)
  63. {
  64. current_limit &= 0x1f; /* 5 bit */
  65. duty_cycle &= 0x0f; /* 4 bit */
  66. return current_limit << 4 | duty_cycle;
  67. }
  68. static int cpcap_led_set_power(struct cpcap_led *led, bool status)
  69. {
  70. int err;
  71. if (status == led->powered)
  72. return 0;
  73. if (status)
  74. err = regulator_enable(led->vdd);
  75. else
  76. err = regulator_disable(led->vdd);
  77. if (err) {
  78. dev_err(led->dev, "regulator failure: %d", err);
  79. return err;
  80. }
  81. led->powered = status;
  82. return 0;
  83. }
  84. static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value)
  85. {
  86. struct cpcap_led *led = container_of(ledc, struct cpcap_led, led);
  87. int brightness;
  88. int err;
  89. mutex_lock(&led->update_lock);
  90. if (value > LED_OFF) {
  91. err = cpcap_led_set_power(led, true);
  92. if (err)
  93. goto exit;
  94. }
  95. if (value == LED_OFF) {
  96. /* Avoid HW issue by turning off current before duty cycle */
  97. err = regmap_update_bits(led->regmap,
  98. led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT);
  99. if (err) {
  100. dev_err(led->dev, "regmap failed: %d", err);
  101. goto exit;
  102. }
  103. brightness = cpcap_led_val(value, LED_OFF);
  104. } else {
  105. brightness = cpcap_led_val(value, LED_ON);
  106. }
  107. err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask,
  108. brightness);
  109. if (err) {
  110. dev_err(led->dev, "regmap failed: %d", err);
  111. goto exit;
  112. }
  113. if (value == LED_OFF) {
  114. err = cpcap_led_set_power(led, false);
  115. if (err)
  116. goto exit;
  117. }
  118. exit:
  119. mutex_unlock(&led->update_lock);
  120. return err;
  121. }
  122. static const struct of_device_id cpcap_led_of_match[] = {
  123. { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red },
  124. { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green },
  125. { .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue },
  126. { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl },
  127. { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp },
  128. {},
  129. };
  130. MODULE_DEVICE_TABLE(of, cpcap_led_of_match);
  131. static int cpcap_led_probe(struct platform_device *pdev)
  132. {
  133. struct cpcap_led *led;
  134. int err;
  135. led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
  136. if (!led)
  137. return -ENOMEM;
  138. platform_set_drvdata(pdev, led);
  139. led->info = device_get_match_data(&pdev->dev);
  140. led->dev = &pdev->dev;
  141. if (led->info->reg == 0x0000) {
  142. dev_err(led->dev, "Unsupported LED");
  143. return -ENODEV;
  144. }
  145. led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
  146. if (!led->regmap)
  147. return -ENODEV;
  148. led->vdd = devm_regulator_get(&pdev->dev, "vdd");
  149. if (IS_ERR(led->vdd)) {
  150. err = PTR_ERR(led->vdd);
  151. dev_err(led->dev, "Couldn't get regulator: %d", err);
  152. return err;
  153. }
  154. err = device_property_read_string(&pdev->dev, "label", &led->led.name);
  155. if (err) {
  156. dev_err(led->dev, "Couldn't read LED label: %d", err);
  157. return err;
  158. }
  159. if (led->info->init_mask) {
  160. err = regmap_update_bits(led->regmap, led->info->reg,
  161. led->info->init_mask, led->info->init_val);
  162. if (err) {
  163. dev_err(led->dev, "regmap failed: %d", err);
  164. return err;
  165. }
  166. }
  167. mutex_init(&led->update_lock);
  168. led->led.max_brightness = led->info->limit;
  169. led->led.brightness_set_blocking = cpcap_led_set;
  170. err = devm_led_classdev_register(&pdev->dev, &led->led);
  171. if (err) {
  172. dev_err(led->dev, "Couldn't register LED: %d", err);
  173. return err;
  174. }
  175. return 0;
  176. }
  177. static struct platform_driver cpcap_led_driver = {
  178. .probe = cpcap_led_probe,
  179. .driver = {
  180. .name = "cpcap-led",
  181. .of_match_table = cpcap_led_of_match,
  182. },
  183. };
  184. module_platform_driver(cpcap_led_driver);
  185. MODULE_DESCRIPTION("CPCAP LED driver");
  186. MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
  187. MODULE_LICENSE("GPL");