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 26 struct bd2606mvv_led { 27 unsigned int led_no; 28 struct led_classdev ldev; 29 struct bd2606mvv_priv *priv; 30 }; 31 32 struct bd2606mvv_priv { 33 struct bd2606mvv_led leds[BD2606_MAX_LEDS]; 34 struct regmap *regmap; 35 }; 36 37 static int 38 bd2606mvv_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 64 static const struct regmap_config bd2606mvv_regmap = { 65 .reg_bits = 8, 66 .val_bits = 8, 67 .max_register = 0x3, 68 }; 69 70 static 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 139 static const struct of_device_id __maybe_unused of_bd2606mvv_leds_match[] = { 140 { .compatible = "rohm,bd2606mvv", }, 141 {}, 142 }; 143 MODULE_DEVICE_TABLE(of, of_bd2606mvv_leds_match); 144 145 static 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 153 module_i2c_driver(bd2606mvv_driver); 154 155 MODULE_AUTHOR("Andreas Kemnade <andreas@kemnade.info>"); 156 MODULE_DESCRIPTION("BD2606 LED driver"); 157 MODULE_LICENSE("GPL"); 158