1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * NCP5623 Multi-LED Driver 4 * 5 * Author: Abdel Alkuor <alkuor@gmail.com> 6 * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf 7 */ 8 9 #include <linux/i2c.h> 10 #include <linux/module.h> 11 12 #include <linux/led-class-multicolor.h> 13 14 #define NCP5623_FUNCTION_OFFSET 0x5 15 #define NCP5623_REG(x) ((x) << NCP5623_FUNCTION_OFFSET) 16 17 #define NCP5623_SHUTDOWN_REG NCP5623_REG(0x0) 18 #define NCP5623_ILED_REG NCP5623_REG(0x1) 19 #define NCP5623_PWM_REG(index) NCP5623_REG(0x2 + (index)) 20 #define NCP5623_UPWARD_STEP_REG NCP5623_REG(0x5) 21 #define NCP5623_DOWNWARD_STEP_REG NCP5623_REG(0x6) 22 #define NCP5623_DIMMING_TIME_REG NCP5623_REG(0x7) 23 24 #define NCP5623_MAX_BRIGHTNESS 0x1f 25 #define NCP5623_MAX_DIM_TIME_MS 240 26 #define NCP5623_DIM_STEP_MS 8 27 28 struct ncp5623 { 29 struct i2c_client *client; 30 struct led_classdev_mc mc_dev; 31 struct mutex lock; 32 33 int current_brightness; 34 unsigned long delay; 35 }; 36 37 static int ncp5623_write(struct i2c_client *client, u8 reg, u8 data) 38 { 39 return i2c_smbus_write_byte_data(client, reg | data, 0); 40 } 41 42 static int ncp5623_brightness_set(struct led_classdev *cdev, 43 enum led_brightness brightness) 44 { 45 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 46 struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev); 47 int ret; 48 49 guard(mutex)(&ncp->lock); 50 51 if (ncp->delay && time_is_after_jiffies(ncp->delay)) 52 return -EBUSY; 53 54 ncp->delay = 0; 55 56 for (int i = 0; i < mc_cdev->num_colors; i++) { 57 ret = ncp5623_write(ncp->client, 58 NCP5623_PWM_REG(mc_cdev->subled_info[i].channel), 59 min(mc_cdev->subled_info[i].intensity, 60 NCP5623_MAX_BRIGHTNESS)); 61 if (ret) 62 return ret; 63 } 64 65 ret = ncp5623_write(ncp->client, NCP5623_DIMMING_TIME_REG, 0); 66 if (ret) 67 return ret; 68 69 ret = ncp5623_write(ncp->client, NCP5623_ILED_REG, brightness); 70 if (ret) 71 return ret; 72 73 ncp->current_brightness = brightness; 74 75 return 0; 76 } 77 78 static int ncp5623_pattern_set(struct led_classdev *cdev, 79 struct led_pattern *pattern, 80 u32 len, int repeat) 81 { 82 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 83 struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev); 84 int brightness_diff; 85 u8 reg; 86 int ret; 87 88 guard(mutex)(&ncp->lock); 89 90 if (ncp->delay && time_is_after_jiffies(ncp->delay)) 91 return -EBUSY; 92 93 ncp->delay = 0; 94 95 if (pattern[0].delta_t > NCP5623_MAX_DIM_TIME_MS || 96 (pattern[0].delta_t % NCP5623_DIM_STEP_MS) != 0) 97 return -EINVAL; 98 99 brightness_diff = pattern[0].brightness - ncp->current_brightness; 100 101 if (brightness_diff == 0) 102 return 0; 103 104 if (pattern[0].delta_t) { 105 if (brightness_diff > 0) 106 reg = NCP5623_UPWARD_STEP_REG; 107 else 108 reg = NCP5623_DOWNWARD_STEP_REG; 109 } else { 110 reg = NCP5623_ILED_REG; 111 } 112 113 ret = ncp5623_write(ncp->client, reg, 114 min(pattern[0].brightness, NCP5623_MAX_BRIGHTNESS)); 115 if (ret) 116 return ret; 117 118 ret = ncp5623_write(ncp->client, 119 NCP5623_DIMMING_TIME_REG, 120 pattern[0].delta_t / NCP5623_DIM_STEP_MS); 121 if (ret) 122 return ret; 123 124 /* 125 * During testing, when the brightness difference is 1, for some 126 * unknown reason, the time factor it takes to change to the new 127 * value is the longest time possible. Otherwise, the time factor 128 * is simply the brightness difference. 129 * 130 * For example: 131 * current_brightness = 20 and new_brightness = 21 then the time it 132 * takes to set the new brightness increments to the maximum possible 133 * brightness from 20 then from 0 to 21. 134 * time_factor = max_brightness - 20 + 21 135 */ 136 if (abs(brightness_diff) == 1) 137 ncp->delay = NCP5623_MAX_BRIGHTNESS + brightness_diff; 138 else 139 ncp->delay = abs(brightness_diff); 140 141 ncp->delay = msecs_to_jiffies(ncp->delay * pattern[0].delta_t) + jiffies; 142 143 ncp->current_brightness = pattern[0].brightness; 144 145 return 0; 146 } 147 148 static int ncp5623_pattern_clear(struct led_classdev *led_cdev) 149 { 150 return 0; 151 } 152 153 static int ncp5623_probe(struct i2c_client *client) 154 { 155 struct device *dev = &client->dev; 156 struct fwnode_handle *mc_node, *led_node; 157 struct led_init_data init_data = { }; 158 struct ncp5623 *ncp; 159 struct mc_subled *subled_info; 160 unsigned int num_subleds; 161 u32 color_index; 162 u32 reg; 163 int ret; 164 165 ncp = devm_kzalloc(dev, sizeof(*ncp), GFP_KERNEL); 166 if (!ncp) 167 return -ENOMEM; 168 169 ncp->client = client; 170 171 mc_node = device_get_named_child_node(dev, "multi-led"); 172 if (!mc_node) 173 return -EINVAL; 174 175 num_subleds = fwnode_get_child_node_count(mc_node); 176 177 subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL); 178 if (!subled_info) { 179 ret = -ENOMEM; 180 goto release_mc_node; 181 } 182 183 fwnode_for_each_available_child_node(mc_node, led_node) { 184 ret = fwnode_property_read_u32(led_node, "color", &color_index); 185 if (ret) 186 goto release_led_node; 187 188 ret = fwnode_property_read_u32(led_node, "reg", ®); 189 if (ret) 190 goto release_led_node; 191 192 subled_info[ncp->mc_dev.num_colors].channel = reg; 193 subled_info[ncp->mc_dev.num_colors++].color_index = color_index; 194 } 195 196 init_data.fwnode = mc_node; 197 198 ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS; 199 ncp->mc_dev.subled_info = subled_info; 200 ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set; 201 ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set; 202 ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear; 203 ncp->mc_dev.led_cdev.default_trigger = "pattern"; 204 205 mutex_init(&ncp->lock); 206 i2c_set_clientdata(client, ncp); 207 208 ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data); 209 if (ret) 210 goto destroy_lock; 211 212 return 0; 213 214 destroy_lock: 215 mutex_destroy(&ncp->lock); 216 217 release_mc_node: 218 fwnode_handle_put(mc_node); 219 220 return ret; 221 222 release_led_node: 223 fwnode_handle_put(led_node); 224 goto release_mc_node; 225 } 226 227 static void ncp5623_remove(struct i2c_client *client) 228 { 229 struct ncp5623 *ncp = i2c_get_clientdata(client); 230 231 mutex_lock(&ncp->lock); 232 ncp->delay = 0; 233 mutex_unlock(&ncp->lock); 234 235 ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0); 236 led_classdev_multicolor_unregister(&ncp->mc_dev); 237 mutex_destroy(&ncp->lock); 238 } 239 240 static void ncp5623_shutdown(struct i2c_client *client) 241 { 242 struct ncp5623 *ncp = i2c_get_clientdata(client); 243 244 if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN)) 245 ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0); 246 247 mutex_destroy(&ncp->lock); 248 } 249 250 static const struct of_device_id ncp5623_id[] = { 251 { .compatible = "onnn,ncp5623" }, 252 { } 253 }; 254 MODULE_DEVICE_TABLE(of, ncp5623_id); 255 256 static struct i2c_driver ncp5623_i2c_driver = { 257 .driver = { 258 .name = "ncp5623", 259 .of_match_table = ncp5623_id, 260 }, 261 .probe = ncp5623_probe, 262 .remove = ncp5623_remove, 263 .shutdown = ncp5623_shutdown, 264 }; 265 266 module_i2c_driver(ncp5623_i2c_driver); 267 268 MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>"); 269 MODULE_DESCRIPTION("NCP5623 Multi-LED driver"); 270 MODULE_LICENSE("GPL"); 271