xref: /openbmc/linux/drivers/leds/leds-bd2606mvv.c (revision d9ff8a8e)
18325642dSAndreas Kemnade // SPDX-License-Identifier: GPL-2.0-only
28325642dSAndreas Kemnade /*
38325642dSAndreas Kemnade  * Copyright (C) 2023 Andreas Kemnade
48325642dSAndreas Kemnade  *
58325642dSAndreas Kemnade  * Datasheet:
68325642dSAndreas Kemnade  * https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/led_driver/bd2606mvv_1-e.pdf
78325642dSAndreas Kemnade  *
88325642dSAndreas Kemnade  * If LED brightness cannot be controlled independently due to shared
98325642dSAndreas Kemnade  * brightness registers, max_brightness is set to 1 and only on/off
108325642dSAndreas Kemnade  * is possible for the affected LED pair.
118325642dSAndreas Kemnade  */
128325642dSAndreas Kemnade 
138325642dSAndreas Kemnade #include <linux/i2c.h>
148325642dSAndreas Kemnade #include <linux/leds.h>
158325642dSAndreas Kemnade #include <linux/module.h>
168325642dSAndreas Kemnade #include <linux/mod_devicetable.h>
178325642dSAndreas Kemnade #include <linux/property.h>
188325642dSAndreas Kemnade #include <linux/regmap.h>
198325642dSAndreas Kemnade #include <linux/slab.h>
208325642dSAndreas Kemnade 
218325642dSAndreas Kemnade #define BD2606_MAX_LEDS 6
228325642dSAndreas Kemnade #define BD2606_MAX_BRIGHTNESS 63
238325642dSAndreas Kemnade #define BD2606_REG_PWRCNT 3
248325642dSAndreas Kemnade #define ldev_to_led(c)	container_of(c, struct bd2606mvv_led, ldev)
258325642dSAndreas Kemnade 
268325642dSAndreas Kemnade struct bd2606mvv_led {
278325642dSAndreas Kemnade 	unsigned int led_no;
288325642dSAndreas Kemnade 	struct led_classdev ldev;
298325642dSAndreas Kemnade 	struct bd2606mvv_priv *priv;
308325642dSAndreas Kemnade };
318325642dSAndreas Kemnade 
328325642dSAndreas Kemnade struct bd2606mvv_priv {
338325642dSAndreas Kemnade 	struct bd2606mvv_led leds[BD2606_MAX_LEDS];
348325642dSAndreas Kemnade 	struct regmap *regmap;
358325642dSAndreas Kemnade };
368325642dSAndreas Kemnade 
378325642dSAndreas Kemnade static int
bd2606mvv_brightness_set(struct led_classdev * led_cdev,enum led_brightness brightness)388325642dSAndreas Kemnade bd2606mvv_brightness_set(struct led_classdev *led_cdev,
398325642dSAndreas Kemnade 		      enum led_brightness brightness)
408325642dSAndreas Kemnade {
418325642dSAndreas Kemnade 	struct bd2606mvv_led *led = ldev_to_led(led_cdev);
428325642dSAndreas Kemnade 	struct bd2606mvv_priv *priv = led->priv;
438325642dSAndreas Kemnade 	int err;
448325642dSAndreas Kemnade 
458325642dSAndreas Kemnade 	if (brightness == 0)
468325642dSAndreas Kemnade 		return regmap_update_bits(priv->regmap,
478325642dSAndreas Kemnade 					  BD2606_REG_PWRCNT,
488325642dSAndreas Kemnade 					  1 << led->led_no,
498325642dSAndreas Kemnade 					  0);
508325642dSAndreas Kemnade 
518325642dSAndreas Kemnade 	/* shared brightness register */
528325642dSAndreas Kemnade 	err = regmap_write(priv->regmap, led->led_no / 2,
538325642dSAndreas Kemnade 			   led_cdev->max_brightness == 1 ?
548325642dSAndreas Kemnade 			   BD2606_MAX_BRIGHTNESS : brightness);
558325642dSAndreas Kemnade 	if (err)
568325642dSAndreas Kemnade 		return err;
578325642dSAndreas Kemnade 
588325642dSAndreas Kemnade 	return regmap_update_bits(priv->regmap,
598325642dSAndreas Kemnade 				  BD2606_REG_PWRCNT,
608325642dSAndreas Kemnade 				  1 << led->led_no,
618325642dSAndreas Kemnade 				  1 << led->led_no);
628325642dSAndreas Kemnade }
638325642dSAndreas Kemnade 
648325642dSAndreas Kemnade static const struct regmap_config bd2606mvv_regmap = {
658325642dSAndreas Kemnade 	.reg_bits = 8,
668325642dSAndreas Kemnade 	.val_bits = 8,
678325642dSAndreas Kemnade 	.max_register = 0x3,
688325642dSAndreas Kemnade };
698325642dSAndreas Kemnade 
bd2606mvv_probe(struct i2c_client * client)708325642dSAndreas Kemnade static int bd2606mvv_probe(struct i2c_client *client)
718325642dSAndreas Kemnade {
728325642dSAndreas Kemnade 	struct fwnode_handle *np, *child;
738325642dSAndreas Kemnade 	struct device *dev = &client->dev;
748325642dSAndreas Kemnade 	struct bd2606mvv_priv *priv;
758325642dSAndreas Kemnade 	struct fwnode_handle *led_fwnodes[BD2606_MAX_LEDS] = { 0 };
768325642dSAndreas Kemnade 	int active_pairs[BD2606_MAX_LEDS / 2] = { 0 };
778325642dSAndreas Kemnade 	int err, reg;
788325642dSAndreas Kemnade 	int i;
798325642dSAndreas Kemnade 
808325642dSAndreas Kemnade 	np = dev_fwnode(dev);
818325642dSAndreas Kemnade 	if (!np)
828325642dSAndreas Kemnade 		return -ENODEV;
838325642dSAndreas Kemnade 
848325642dSAndreas Kemnade 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
858325642dSAndreas Kemnade 	if (!priv)
868325642dSAndreas Kemnade 		return -ENOMEM;
878325642dSAndreas Kemnade 
888325642dSAndreas Kemnade 	priv->regmap = devm_regmap_init_i2c(client, &bd2606mvv_regmap);
898325642dSAndreas Kemnade 	if (IS_ERR(priv->regmap)) {
908325642dSAndreas Kemnade 		err = PTR_ERR(priv->regmap);
918325642dSAndreas Kemnade 		dev_err(dev, "Failed to allocate register map: %d\n", err);
928325642dSAndreas Kemnade 		return err;
938325642dSAndreas Kemnade 	}
948325642dSAndreas Kemnade 
958325642dSAndreas Kemnade 	i2c_set_clientdata(client, priv);
968325642dSAndreas Kemnade 
978325642dSAndreas Kemnade 	fwnode_for_each_available_child_node(np, child) {
988325642dSAndreas Kemnade 		struct bd2606mvv_led *led;
998325642dSAndreas Kemnade 
1008325642dSAndreas Kemnade 		err = fwnode_property_read_u32(child, "reg", &reg);
1018325642dSAndreas Kemnade 		if (err) {
1028325642dSAndreas Kemnade 			fwnode_handle_put(child);
1038325642dSAndreas Kemnade 			return err;
1048325642dSAndreas Kemnade 		}
1058325642dSAndreas Kemnade 		if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg]) {
1068325642dSAndreas Kemnade 			fwnode_handle_put(child);
1078325642dSAndreas Kemnade 			return -EINVAL;
1088325642dSAndreas Kemnade 		}
1098325642dSAndreas Kemnade 		led = &priv->leds[reg];
1108325642dSAndreas Kemnade 		led_fwnodes[reg] = child;
1118325642dSAndreas Kemnade 		active_pairs[reg / 2]++;
1128325642dSAndreas Kemnade 		led->priv = priv;
1138325642dSAndreas Kemnade 		led->led_no = reg;
1148325642dSAndreas Kemnade 		led->ldev.brightness_set_blocking = bd2606mvv_brightness_set;
1158325642dSAndreas Kemnade 		led->ldev.max_brightness = BD2606_MAX_BRIGHTNESS;
1168325642dSAndreas Kemnade 	}
1178325642dSAndreas Kemnade 
1188325642dSAndreas Kemnade 	for (i = 0; i < BD2606_MAX_LEDS; i++) {
1198325642dSAndreas Kemnade 		struct led_init_data init_data = {};
1208325642dSAndreas Kemnade 
1218325642dSAndreas Kemnade 		if (!led_fwnodes[i])
1228325642dSAndreas Kemnade 			continue;
1238325642dSAndreas Kemnade 
1248325642dSAndreas Kemnade 		init_data.fwnode = led_fwnodes[i];
1258325642dSAndreas Kemnade 		/* Check whether brightness can be independently adjusted. */
1268325642dSAndreas Kemnade 		if (active_pairs[i / 2] == 2)
1278325642dSAndreas Kemnade 			priv->leds[i].ldev.max_brightness = 1;
1288325642dSAndreas Kemnade 
1298325642dSAndreas Kemnade 		err = devm_led_classdev_register_ext(dev,
1308325642dSAndreas Kemnade 						     &priv->leds[i].ldev,
1318325642dSAndreas Kemnade 						     &init_data);
1328325642dSAndreas Kemnade 		if (err < 0) {
1338325642dSAndreas Kemnade 			fwnode_handle_put(child);
1348325642dSAndreas Kemnade 			return dev_err_probe(dev, err,
1358325642dSAndreas Kemnade 					     "couldn't register LED %s\n",
1368325642dSAndreas Kemnade 					     priv->leds[i].ldev.name);
1378325642dSAndreas Kemnade 		}
1388325642dSAndreas Kemnade 	}
1398325642dSAndreas Kemnade 	return 0;
1408325642dSAndreas Kemnade }
1418325642dSAndreas Kemnade 
1428325642dSAndreas Kemnade static const struct of_device_id __maybe_unused of_bd2606mvv_leds_match[] = {
1438325642dSAndreas Kemnade 	{ .compatible = "rohm,bd2606mvv", },
1448325642dSAndreas Kemnade 	{},
1458325642dSAndreas Kemnade };
1468325642dSAndreas Kemnade MODULE_DEVICE_TABLE(of, of_bd2606mvv_leds_match);
1478325642dSAndreas Kemnade 
1488325642dSAndreas Kemnade static struct i2c_driver bd2606mvv_driver = {
1498325642dSAndreas Kemnade 	.driver   = {
1508325642dSAndreas Kemnade 		.name    = "leds-bd2606mvv",
1518325642dSAndreas Kemnade 		.of_match_table = of_match_ptr(of_bd2606mvv_leds_match),
1528325642dSAndreas Kemnade 	},
153*d9ff8a8eSUwe Kleine-König 	.probe = bd2606mvv_probe,
1548325642dSAndreas Kemnade };
1558325642dSAndreas Kemnade 
1568325642dSAndreas Kemnade module_i2c_driver(bd2606mvv_driver);
1578325642dSAndreas Kemnade 
1588325642dSAndreas Kemnade MODULE_AUTHOR("Andreas Kemnade <andreas@kemnade.info>");
1598325642dSAndreas Kemnade MODULE_DESCRIPTION("BD2606 LED driver");
1608325642dSAndreas Kemnade MODULE_LICENSE("GPL");
161