leds-bd2606mvv.c 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (C) 2023 Andreas Kemnade
  4. *
  5. * Datasheet:
  6. * https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/led_driver/bd2606mvv_1-e.pdf
  7. *
  8. * If LED brightness cannot be controlled independently due to shared
  9. * brightness registers, max_brightness is set to 1 and only on/off
  10. * is possible for the affected LED pair.
  11. */
  12. #include <linux/i2c.h>
  13. #include <linux/leds.h>
  14. #include <linux/module.h>
  15. #include <linux/mod_devicetable.h>
  16. #include <linux/property.h>
  17. #include <linux/regmap.h>
  18. #include <linux/slab.h>
  19. #define BD2606_MAX_LEDS 6
  20. #define BD2606_MAX_BRIGHTNESS 63
  21. #define BD2606_REG_PWRCNT 3
  22. #define ldev_to_led(c) container_of(c, struct bd2606mvv_led, ldev)
  23. struct bd2606mvv_led {
  24. unsigned int led_no;
  25. struct led_classdev ldev;
  26. struct bd2606mvv_priv *priv;
  27. };
  28. struct bd2606mvv_priv {
  29. struct bd2606mvv_led leds[BD2606_MAX_LEDS];
  30. struct regmap *regmap;
  31. };
  32. static int
  33. bd2606mvv_brightness_set(struct led_classdev *led_cdev,
  34. enum led_brightness brightness)
  35. {
  36. struct bd2606mvv_led *led = ldev_to_led(led_cdev);
  37. struct bd2606mvv_priv *priv = led->priv;
  38. int err;
  39. if (brightness == 0)
  40. return regmap_update_bits(priv->regmap,
  41. BD2606_REG_PWRCNT,
  42. 1 << led->led_no,
  43. 0);
  44. /* shared brightness register */
  45. err = regmap_write(priv->regmap, led->led_no / 2,
  46. led_cdev->max_brightness == 1 ?
  47. BD2606_MAX_BRIGHTNESS : brightness);
  48. if (err)
  49. return err;
  50. return regmap_update_bits(priv->regmap,
  51. BD2606_REG_PWRCNT,
  52. 1 << led->led_no,
  53. 1 << led->led_no);
  54. }
  55. static const struct regmap_config bd2606mvv_regmap = {
  56. .reg_bits = 8,
  57. .val_bits = 8,
  58. .max_register = 0x3,
  59. };
  60. static int bd2606mvv_probe(struct i2c_client *client)
  61. {
  62. struct device *dev = &client->dev;
  63. struct bd2606mvv_priv *priv;
  64. struct fwnode_handle *led_fwnodes[BD2606_MAX_LEDS] = { 0 };
  65. int active_pairs[BD2606_MAX_LEDS / 2] = { 0 };
  66. int err, reg;
  67. int i, j;
  68. if (!dev_fwnode(dev))
  69. return -ENODEV;
  70. priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
  71. if (!priv)
  72. return -ENOMEM;
  73. priv->regmap = devm_regmap_init_i2c(client, &bd2606mvv_regmap);
  74. if (IS_ERR(priv->regmap)) {
  75. err = PTR_ERR(priv->regmap);
  76. dev_err(dev, "Failed to allocate register map: %d\n", err);
  77. return err;
  78. }
  79. i2c_set_clientdata(client, priv);
  80. device_for_each_child_node_scoped(dev, child) {
  81. struct bd2606mvv_led *led;
  82. err = fwnode_property_read_u32(child, "reg", &reg);
  83. if (err)
  84. return err;
  85. if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg])
  86. return -EINVAL;
  87. led = &priv->leds[reg];
  88. led_fwnodes[reg] = fwnode_handle_get(child);
  89. active_pairs[reg / 2]++;
  90. led->priv = priv;
  91. led->led_no = reg;
  92. led->ldev.brightness_set_blocking = bd2606mvv_brightness_set;
  93. led->ldev.max_brightness = BD2606_MAX_BRIGHTNESS;
  94. }
  95. for (i = 0; i < BD2606_MAX_LEDS; i++) {
  96. struct led_init_data init_data = {};
  97. if (!led_fwnodes[i])
  98. continue;
  99. init_data.fwnode = led_fwnodes[i];
  100. /* Check whether brightness can be independently adjusted. */
  101. if (active_pairs[i / 2] == 2)
  102. priv->leds[i].ldev.max_brightness = 1;
  103. err = devm_led_classdev_register_ext(dev,
  104. &priv->leds[i].ldev,
  105. &init_data);
  106. if (err < 0) {
  107. for (j = i; j < BD2606_MAX_LEDS; j++)
  108. fwnode_handle_put(led_fwnodes[j]);
  109. return dev_err_probe(dev, err,
  110. "couldn't register LED %s\n",
  111. priv->leds[i].ldev.name);
  112. }
  113. }
  114. return 0;
  115. }
  116. static const struct of_device_id __maybe_unused of_bd2606mvv_leds_match[] = {
  117. { .compatible = "rohm,bd2606mvv", },
  118. {},
  119. };
  120. MODULE_DEVICE_TABLE(of, of_bd2606mvv_leds_match);
  121. static struct i2c_driver bd2606mvv_driver = {
  122. .driver = {
  123. .name = "leds-bd2606mvv",
  124. .of_match_table = of_match_ptr(of_bd2606mvv_leds_match),
  125. },
  126. .probe = bd2606mvv_probe,
  127. };
  128. module_i2c_driver(bd2606mvv_driver);
  129. MODULE_AUTHOR("Andreas Kemnade <andreas@kemnade.info>");
  130. MODULE_DESCRIPTION("BD2606 LED driver");
  131. MODULE_LICENSE("GPL");