// SPDX-License-Identifier: GPL-2.0-only /* * NCP5623 Multi-LED Driver * * Author: Abdel Alkuor * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf */ #include #include #include #define NCP5623_FUNCTION_OFFSET 0x5 #define NCP5623_REG(x) ((x) << NCP5623_FUNCTION_OFFSET) #define NCP5623_SHUTDOWN_REG NCP5623_REG(0x0) #define NCP5623_ILED_REG NCP5623_REG(0x1) #define NCP5623_PWM_REG(index) NCP5623_REG(0x2 + (index)) #define NCP5623_UPWARD_STEP_REG NCP5623_REG(0x5) #define NCP5623_DOWNWARD_STEP_REG NCP5623_REG(0x6) #define NCP5623_DIMMING_TIME_REG NCP5623_REG(0x7) #define NCP5623_MAX_BRIGHTNESS 0x1f #define NCP5623_MAX_DIM_TIME_MS 240 #define NCP5623_DIM_STEP_MS 8 struct ncp5623 { struct i2c_client *client; struct led_classdev_mc mc_dev; struct mutex lock; int current_brightness; unsigned long delay; }; static int ncp5623_write(struct i2c_client *client, u8 reg, u8 data) { return i2c_smbus_write_byte_data(client, reg | data, 0); } static int ncp5623_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev); int ret; guard(mutex)(&ncp->lock); if (ncp->delay && time_is_after_jiffies(ncp->delay)) return -EBUSY; ncp->delay = 0; for (int i = 0; i < mc_cdev->num_colors; i++) { ret = ncp5623_write(ncp->client, NCP5623_PWM_REG(mc_cdev->subled_info[i].channel), min(mc_cdev->subled_info[i].intensity, NCP5623_MAX_BRIGHTNESS)); if (ret) return ret; } ret = ncp5623_write(ncp->client, NCP5623_DIMMING_TIME_REG, 0); if (ret) return ret; ret = ncp5623_write(ncp->client, NCP5623_ILED_REG, brightness); if (ret) return ret; ncp->current_brightness = brightness; return 0; } static int ncp5623_pattern_set(struct led_classdev *cdev, struct led_pattern *pattern, u32 len, int repeat) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev); int brightness_diff; u8 reg; int ret; guard(mutex)(&ncp->lock); if (ncp->delay && time_is_after_jiffies(ncp->delay)) return -EBUSY; ncp->delay = 0; if (pattern[0].delta_t > NCP5623_MAX_DIM_TIME_MS || (pattern[0].delta_t % NCP5623_DIM_STEP_MS) != 0) return -EINVAL; brightness_diff = pattern[0].brightness - ncp->current_brightness; if (brightness_diff == 0) return 0; if (pattern[0].delta_t) { if (brightness_diff > 0) reg = NCP5623_UPWARD_STEP_REG; else reg = NCP5623_DOWNWARD_STEP_REG; } else { reg = NCP5623_ILED_REG; } ret = ncp5623_write(ncp->client, reg, min(pattern[0].brightness, NCP5623_MAX_BRIGHTNESS)); if (ret) return ret; ret = ncp5623_write(ncp->client, NCP5623_DIMMING_TIME_REG, pattern[0].delta_t / NCP5623_DIM_STEP_MS); if (ret) return ret; /* * During testing, when the brightness difference is 1, for some * unknown reason, the time factor it takes to change to the new * value is the longest time possible. Otherwise, the time factor * is simply the brightness difference. * * For example: * current_brightness = 20 and new_brightness = 21 then the time it * takes to set the new brightness increments to the maximum possible * brightness from 20 then from 0 to 21. * time_factor = max_brightness - 20 + 21 */ if (abs(brightness_diff) == 1) ncp->delay = NCP5623_MAX_BRIGHTNESS + brightness_diff; else ncp->delay = abs(brightness_diff); ncp->delay = msecs_to_jiffies(ncp->delay * pattern[0].delta_t) + jiffies; ncp->current_brightness = pattern[0].brightness; return 0; } static int ncp5623_pattern_clear(struct led_classdev *led_cdev) { return 0; } static int ncp5623_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct fwnode_handle *mc_node, *led_node; struct led_init_data init_data = { }; int num_subleds = 0; struct ncp5623 *ncp; struct mc_subled *subled_info; u32 color_index; u32 reg; int ret; ncp = devm_kzalloc(dev, sizeof(*ncp), GFP_KERNEL); if (!ncp) return -ENOMEM; ncp->client = client; mc_node = device_get_named_child_node(dev, "multi-led"); if (!mc_node) return -EINVAL; fwnode_for_each_child_node(mc_node, led_node) num_subleds++; subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL); if (!subled_info) { ret = -ENOMEM; goto release_mc_node; } fwnode_for_each_available_child_node(mc_node, led_node) { ret = fwnode_property_read_u32(led_node, "color", &color_index); if (ret) { fwnode_handle_put(led_node); goto release_mc_node; } ret = fwnode_property_read_u32(led_node, "reg", ®); if (ret) { fwnode_handle_put(led_node); goto release_mc_node; } subled_info[ncp->mc_dev.num_colors].channel = reg; subled_info[ncp->mc_dev.num_colors++].color_index = color_index; } init_data.fwnode = mc_node; ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS; ncp->mc_dev.subled_info = subled_info; ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set; ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set; ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear; ncp->mc_dev.led_cdev.default_trigger = "pattern"; mutex_init(&ncp->lock); i2c_set_clientdata(client, ncp); ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data); if (ret) goto destroy_lock; return 0; destroy_lock: mutex_destroy(&ncp->lock); release_mc_node: fwnode_handle_put(mc_node); return ret; } static void ncp5623_remove(struct i2c_client *client) { struct ncp5623 *ncp = i2c_get_clientdata(client); mutex_lock(&ncp->lock); ncp->delay = 0; mutex_unlock(&ncp->lock); ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0); led_classdev_multicolor_unregister(&ncp->mc_dev); mutex_destroy(&ncp->lock); } static void ncp5623_shutdown(struct i2c_client *client) { struct ncp5623 *ncp = i2c_get_clientdata(client); if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN)) ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0); mutex_destroy(&ncp->lock); } static const struct of_device_id ncp5623_id[] = { { .compatible = "onnn,ncp5623" }, { } }; MODULE_DEVICE_TABLE(of, ncp5623_id); static struct i2c_driver ncp5623_i2c_driver = { .driver = { .name = "ncp5623", .of_match_table = ncp5623_id, }, .probe = ncp5623_probe, .remove = ncp5623_remove, .shutdown = ncp5623_shutdown, }; module_i2c_driver(ncp5623_i2c_driver); MODULE_AUTHOR("Abdel Alkuor "); MODULE_DESCRIPTION("NCP5623 Multi-LED driver"); MODULE_LICENSE("GPL");