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 fwnode_handle *np, *child; 73 struct device *dev = &client->dev; 74 struct bd2606mvv_priv *priv; 75 struct fwnode_handle *led_fwnodes[BD2606_MAX_LEDS] = { 0 }; 76 int active_pairs[BD2606_MAX_LEDS / 2] = { 0 }; 77 int err, reg; 78 int i; 79 80 np = dev_fwnode(dev); 81 if (!np) 82 return -ENODEV; 83 84 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 85 if (!priv) 86 return -ENOMEM; 87 88 priv->regmap = devm_regmap_init_i2c(client, &bd2606mvv_regmap); 89 if (IS_ERR(priv->regmap)) { 90 err = PTR_ERR(priv->regmap); 91 dev_err(dev, "Failed to allocate register map: %d\n", err); 92 return err; 93 } 94 95 i2c_set_clientdata(client, priv); 96 97 fwnode_for_each_available_child_node(np, child) { 98 struct bd2606mvv_led *led; 99 100 err = fwnode_property_read_u32(child, "reg", ®); 101 if (err) { 102 fwnode_handle_put(child); 103 return err; 104 } 105 if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg]) { 106 fwnode_handle_put(child); 107 return -EINVAL; 108 } 109 led = &priv->leds[reg]; 110 led_fwnodes[reg] = child; 111 active_pairs[reg / 2]++; 112 led->priv = priv; 113 led->led_no = reg; 114 led->ldev.brightness_set_blocking = bd2606mvv_brightness_set; 115 led->ldev.max_brightness = BD2606_MAX_BRIGHTNESS; 116 } 117 118 for (i = 0; i < BD2606_MAX_LEDS; i++) { 119 struct led_init_data init_data = {}; 120 121 if (!led_fwnodes[i]) 122 continue; 123 124 init_data.fwnode = led_fwnodes[i]; 125 /* Check whether brightness can be independently adjusted. */ 126 if (active_pairs[i / 2] == 2) 127 priv->leds[i].ldev.max_brightness = 1; 128 129 err = devm_led_classdev_register_ext(dev, 130 &priv->leds[i].ldev, 131 &init_data); 132 if (err < 0) { 133 fwnode_handle_put(child); 134 return dev_err_probe(dev, err, 135 "couldn't register LED %s\n", 136 priv->leds[i].ldev.name); 137 } 138 } 139 return 0; 140 } 141 142 static const struct of_device_id __maybe_unused of_bd2606mvv_leds_match[] = { 143 { .compatible = "rohm,bd2606mvv", }, 144 {}, 145 }; 146 MODULE_DEVICE_TABLE(of, of_bd2606mvv_leds_match); 147 148 static struct i2c_driver bd2606mvv_driver = { 149 .driver = { 150 .name = "leds-bd2606mvv", 151 .of_match_table = of_match_ptr(of_bd2606mvv_leds_match), 152 }, 153 .probe_new = bd2606mvv_probe, 154 }; 155 156 module_i2c_driver(bd2606mvv_driver); 157 158 MODULE_AUTHOR("Andreas Kemnade <andreas@kemnade.info>"); 159 MODULE_DESCRIPTION("BD2606 LED driver"); 160 MODULE_LICENSE("GPL"); 161