Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.9.
  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
 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 6
 22#define BD2606_MAX_BRIGHTNESS 63
 23#define BD2606_REG_PWRCNT 3
 24#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 int
 38bd2606mvv_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", &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");