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