leds-cpcap.c 5.4 KB

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