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 int num_subleds = 0; 159 struct ncp5623 *ncp; 160 struct mc_subled *subled_info; 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 fwnode_for_each_child_node(mc_node, led_node) 176 num_subleds++; 177 178 subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL); 179 if (!subled_info) { 180 ret = -ENOMEM; 181 goto release_mc_node; 182 } 183 184 fwnode_for_each_available_child_node(mc_node, led_node) { 185 ret = fwnode_property_read_u32(led_node, "color", &color_index); 186 if (ret) { 187 fwnode_handle_put(led_node); 188 goto release_mc_node; 189 } 190 191 ret = fwnode_property_read_u32(led_node, "reg", ®); 192 if (ret) { 193 fwnode_handle_put(led_node); 194 goto release_mc_node; 195 } 196 197 subled_info[ncp->mc_dev.num_colors].channel = reg; 198 subled_info[ncp->mc_dev.num_colors++].color_index = color_index; 199 } 200 201 init_data.fwnode = mc_node; 202 203 ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS; 204 ncp->mc_dev.subled_info = subled_info; 205 ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set; 206 ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set; 207 ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear; 208 ncp->mc_dev.led_cdev.default_trigger = "pattern"; 209 210 mutex_init(&ncp->lock); 211 i2c_set_clientdata(client, ncp); 212 213 ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data); 214 if (ret) 215 goto destroy_lock; 216 217 return 0; 218 219 destroy_lock: 220 mutex_destroy(&ncp->lock); 221 222 release_mc_node: 223 fwnode_handle_put(mc_node); 224 225 return ret; 226 } 227 228 static void ncp5623_remove(struct i2c_client *client) 229 { 230 struct ncp5623 *ncp = i2c_get_clientdata(client); 231 232 mutex_lock(&ncp->lock); 233 ncp->delay = 0; 234 mutex_unlock(&ncp->lock); 235 236 ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0); 237 led_classdev_multicolor_unregister(&ncp->mc_dev); 238 mutex_destroy(&ncp->lock); 239 } 240 241 static void ncp5623_shutdown(struct i2c_client *client) 242 { 243 struct ncp5623 *ncp = i2c_get_clientdata(client); 244 245 if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN)) 246 ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0); 247 248 mutex_destroy(&ncp->lock); 249 } 250 251 static const struct of_device_id ncp5623_id[] = { 252 { .compatible = "onnn,ncp5623" }, 253 { } 254 }; 255 MODULE_DEVICE_TABLE(of, ncp5623_id); 256 257 static struct i2c_driver ncp5623_i2c_driver = { 258 .driver = { 259 .name = "ncp5623", 260 .of_match_table = ncp5623_id, 261 }, 262 .probe = ncp5623_probe, 263 .remove = ncp5623_remove, 264 .shutdown = ncp5623_shutdown, 265 }; 266 267 module_i2c_driver(ncp5623_i2c_driver); 268 269 MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>"); 270 MODULE_DESCRIPTION("NCP5623 Multi-LED driver"); 271 MODULE_LICENSE("GPL"); 272