leds-syscon.c 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Generic Syscon LEDs Driver
  4. *
  5. * Copyright (c) 2014, Linaro Limited
  6. * Author: Linus Walleij <[email protected]>
  7. */
  8. #include <linux/io.h>
  9. #include <linux/init.h>
  10. #include <linux/of_device.h>
  11. #include <linux/of_address.h>
  12. #include <linux/platform_device.h>
  13. #include <linux/stat.h>
  14. #include <linux/slab.h>
  15. #include <linux/mfd/syscon.h>
  16. #include <linux/regmap.h>
  17. #include <linux/leds.h>
  18. /**
  19. * struct syscon_led - state container for syscon based LEDs
  20. * @cdev: LED class device for this LED
  21. * @map: regmap to access the syscon device backing this LED
  22. * @offset: the offset into the syscon regmap for the LED register
  23. * @mask: the bit in the register corresponding to the LED
  24. * @state: current state of the LED
  25. */
  26. struct syscon_led {
  27. struct led_classdev cdev;
  28. struct regmap *map;
  29. u32 offset;
  30. u32 mask;
  31. bool state;
  32. };
  33. static void syscon_led_set(struct led_classdev *led_cdev,
  34. enum led_brightness value)
  35. {
  36. struct syscon_led *sled =
  37. container_of(led_cdev, struct syscon_led, cdev);
  38. u32 val;
  39. int ret;
  40. if (value == LED_OFF) {
  41. val = 0;
  42. sled->state = false;
  43. } else {
  44. val = sled->mask;
  45. sled->state = true;
  46. }
  47. ret = regmap_update_bits(sled->map, sled->offset, sled->mask, val);
  48. if (ret < 0)
  49. dev_err(sled->cdev.dev, "error updating LED status\n");
  50. }
  51. static int syscon_led_probe(struct platform_device *pdev)
  52. {
  53. struct led_init_data init_data = {};
  54. struct device *dev = &pdev->dev;
  55. struct device_node *np = dev_of_node(dev);
  56. struct device *parent;
  57. struct regmap *map;
  58. struct syscon_led *sled;
  59. const char *state;
  60. int ret;
  61. parent = dev->parent;
  62. if (!parent) {
  63. dev_err(dev, "no parent for syscon LED\n");
  64. return -ENODEV;
  65. }
  66. map = syscon_node_to_regmap(dev_of_node(parent));
  67. if (IS_ERR(map)) {
  68. dev_err(dev, "no regmap for syscon LED parent\n");
  69. return PTR_ERR(map);
  70. }
  71. sled = devm_kzalloc(dev, sizeof(*sled), GFP_KERNEL);
  72. if (!sled)
  73. return -ENOMEM;
  74. sled->map = map;
  75. if (of_property_read_u32(np, "offset", &sled->offset))
  76. return -EINVAL;
  77. if (of_property_read_u32(np, "mask", &sled->mask))
  78. return -EINVAL;
  79. state = of_get_property(np, "default-state", NULL);
  80. if (state) {
  81. if (!strcmp(state, "keep")) {
  82. u32 val;
  83. ret = regmap_read(map, sled->offset, &val);
  84. if (ret < 0)
  85. return ret;
  86. sled->state = !!(val & sled->mask);
  87. } else if (!strcmp(state, "on")) {
  88. sled->state = true;
  89. ret = regmap_update_bits(map, sled->offset,
  90. sled->mask,
  91. sled->mask);
  92. if (ret < 0)
  93. return ret;
  94. } else {
  95. sled->state = false;
  96. ret = regmap_update_bits(map, sled->offset,
  97. sled->mask, 0);
  98. if (ret < 0)
  99. return ret;
  100. }
  101. }
  102. sled->cdev.brightness_set = syscon_led_set;
  103. init_data.fwnode = of_fwnode_handle(np);
  104. ret = devm_led_classdev_register_ext(dev, &sled->cdev, &init_data);
  105. if (ret < 0)
  106. return ret;
  107. platform_set_drvdata(pdev, sled);
  108. dev_info(dev, "registered LED %s\n", sled->cdev.name);
  109. return 0;
  110. }
  111. static const struct of_device_id of_syscon_leds_match[] = {
  112. { .compatible = "register-bit-led", },
  113. {},
  114. };
  115. static struct platform_driver syscon_led_driver = {
  116. .probe = syscon_led_probe,
  117. .driver = {
  118. .name = "leds-syscon",
  119. .of_match_table = of_syscon_leds_match,
  120. .suppress_bind_attrs = true,
  121. },
  122. };
  123. builtin_platform_driver(syscon_led_driver);