[PATCH 2/2] leds: Add NCP5623 multi-led driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



NCP5623 is DC-DC multi-LEDs driver with 94% peak
efficiency. NCP5623 has three PWMs which can be
programmed up to 32 steps giving 32768 colors hue.

NCP5623 driver supports gradual dimming upward/downward
with programmable delay steps. Also, the driver supports
driving a single LED or multi-LED like RGB.

Signed-off-by: Abdel Alkuor <alkuor@xxxxxxxxx>
---
 .../sysfs-class-led-multicolor-driver-ncp5623 |  46 +++
 drivers/leds/rgb/Kconfig                      |  11 +
 drivers/leds/rgb/Makefile                     |   1 +
 drivers/leds/rgb/leds-ncp5623.c               | 320 ++++++++++++++++++
 4 files changed, 378 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-multicolor-driver-ncp5623
 create mode 100644 drivers/leds/rgb/leds-ncp5623.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor-driver-ncp5623 b/Documentation/ABI/testing/sysfs-class-led-multicolor-driver-ncp5623
new file mode 100644
index 000000000000..6b9f4849852b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-multicolor-driver-ncp5623
@@ -0,0 +1,46 @@
+What:		/sys/class/leds/<led>/direction
+Date:		Feb 2024
+KernelVersion:	6.8
+Contact:	Abdel Alkuor <alkuor@xxxxxxxxx>
+Description:
+		Set gradual dimming direction which
+		can be either none, up, or down.
+		After setting the direction, brightness
+		can be set using one of 31 steps.
+
+		==== ======== ==== ======== ==== ========
+		Step ILED(mA) Step ILED(mA) Step ILED(mA)
+		0    0        11   1.38      22   3.06
+		1    0.92     12   1.45      23   3.45
+		2    0.95     13   1.53      24   3.94
+		3    0.98     14   1.62      25   4.60
+		4    1.02     15   1.72      26   5.52
+		5    1.06     16   1.84      27   6.90
+		6    1.10     17   1.97      28   9.20
+		7    1.15     18   2.12      29   13.80
+		8    1.20     19   2.30      30   27.60
+		9    1.25     20   2.50      31   27.60
+		10   1.31     21   2.76
+		==== ======== ==== ======== ==== ========
+
+What:		/sys/class/leds/<led>/dim_step
+Date:		Feb 2024
+KernelVersion:	6.8
+Contact:	Abdel Alkuor <alkuor@xxxxxxxxx>
+Description:
+		Set gradual dimming time.
+
+		==== ======== ==== ======== ==== ========
+		Step Time(ms) Step Time(ms) Step Time(ms)
+		0     0       11   88       22   176
+		1     8       12   96       23   184
+		2     16      13   104      24   192
+		3     24      14   112      25   200
+		4     32      15   120      26   208
+		5     40      16   128      27   216
+		6     48      17   136      28   224
+		7     56      18   144      29   232
+		8     64      19   152      30   240
+		9     72      20   160      31   248
+		10    80      21   168
+		==== ======== ==== ======== ==== ========
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index a6a21f564673..81ab6a526a78 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -27,6 +27,17 @@ config LEDS_KTD202X
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-ktd202x.
 
+config LEDS_NCP5623
+	tristate "LED support for NCP5623"
+	depends on I2C
+	depends on OF
+	help
+	  This option enables support for ON semiconductor NCP5623
+	  Triple Output I2C Controlled RGB LED Driver.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called leds-ncp5623.
+
 config LEDS_PWM_MULTICOLOR
 	tristate "PWM driven multi-color LED Support"
 	depends on PWM
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
index 243f31e4d70d..a501fd27f179 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -2,6 +2,7 @@
 
 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR)	+= leds-group-multicolor.o
 obj-$(CONFIG_LEDS_KTD202X)		+= leds-ktd202x.o
+obj-$(CONFIG_LEDS_NCP5623)		+= leds-ncp5623.o
 obj-$(CONFIG_LEDS_PWM_MULTICOLOR)	+= leds-pwm-multicolor.o
 obj-$(CONFIG_LEDS_QCOM_LPG)		+= leds-qcom-lpg.o
 obj-$(CONFIG_LEDS_MT6370_RGB)		+= leds-mt6370-rgb.o
diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c
new file mode 100644
index 000000000000..e77dfca69ca3
--- /dev/null
+++ b/drivers/leds/rgb/leds-ncp5623.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * leds-ncp5623.c - Multi-LED Driver
+ *
+ * Author: Abdel Alkuor <alkuor@xxxxxxxxx>
+ * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/workqueue.h>
+
+#include <linux/led-class-multicolor.h>
+
+#define NCP5623_REG(x)			((x) << 0x5)
+
+#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
+
+enum {
+	NCP5623_DIR_NONE,
+	NCP5623_DIR_UPWARD,
+	NCP5623_DIR_DOWNWARD,
+};
+
+static const char *const directions[] = {
+	[NCP5623_DIR_NONE]     = "none",
+	[NCP5623_DIR_UPWARD]   = "up",
+	[NCP5623_DIR_DOWNWARD] = "down",
+};
+
+struct ncp5623 {
+	struct i2c_client *client;
+	struct led_classdev_mc mc_dev;
+	struct mutex lock;
+
+	u8 direction;
+	u8 dim_step;
+	u8 old_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 inline unsigned long
+ncp5623_get_completion_steps(u8 dir, int brightness, int old_brightness)
+{
+	int diff = brightness - old_brightness;
+
+	if (dir == NCP5623_DIR_UPWARD)
+		return diff <= 1 ? NCP5623_MAX_BRIGHTNESS + diff : diff;
+
+	return diff >= -1 ? NCP5623_MAX_BRIGHTNESS - diff : -diff;
+
+}
+
+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;
+	u8 reg;
+	int i;
+
+	guard(mutex)(&ncp->lock);
+
+	if (ncp->delay && time_is_after_jiffies(ncp->delay))
+		return -EBUSY;
+
+	ncp->delay = 0;
+
+	for (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;
+	}
+
+	switch (ncp->direction) {
+	case NCP5623_DIR_NONE:
+		reg = NCP5623_ILED_REG;
+		break;
+	case NCP5623_DIR_UPWARD:
+		reg = NCP5623_UPWARD_STEP_REG;
+		break;
+	case NCP5623_DIR_DOWNWARD:
+		reg = NCP5623_DOWNWARD_STEP_REG;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = ncp5623_write(ncp->client, reg, brightness);
+	if (ret)
+		return ret;
+
+	if ((ncp->direction != NCP5623_DIR_NONE) && ncp->dim_step) {
+		ret = ncp5623_write(ncp->client,
+				    NCP5623_DIMMING_TIME_REG, ncp->dim_step);
+		if (ret)
+			return ret;
+
+		ncp->delay = ncp5623_get_completion_steps(ncp->direction,
+							  brightness,
+							  ncp->old_brightness);
+		/* dim step resolution is 8ms */
+		ncp->delay *= ncp->dim_step * 8;
+		ncp->delay = msecs_to_jiffies(ncp->delay) + jiffies;
+	}
+
+	ncp->old_brightness = brightness;
+
+	return 0;
+}
+
+static ssize_t dim_step_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(dev_get_drvdata(dev));
+	struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
+	u8 value;
+	int ret;
+
+	ret = kstrtou8(buf, 0, &value);
+	if (ret)
+		return ret;
+
+	if (value > 0x1f)
+		return -EINVAL;
+
+	guard(mutex)(&ncp->lock);
+	ncp->dim_step = value;
+
+	return count;
+}
+
+static ssize_t dim_step_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(dev_get_drvdata(dev));
+	struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
+
+	guard(mutex)(&ncp->lock);
+
+	return sysfs_emit(buf, "%u\n", ncp->dim_step);
+}
+
+static ssize_t direction_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(dev_get_drvdata(dev));
+	struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
+	int ret;
+
+	ret = __sysfs_match_string(directions, ARRAY_SIZE(directions), buf);
+
+	switch (ret) {
+	case NCP5623_DIR_NONE:
+	case NCP5623_DIR_UPWARD:
+	case NCP5623_DIR_DOWNWARD:
+		guard(mutex)(&ncp->lock);
+		ncp->direction = ret;
+		return count;
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t direction_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(dev_get_drvdata(dev));
+	struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
+
+	guard(mutex)(&ncp->lock);
+
+	return sysfs_emit(buf, "%s\n", directions[ncp->direction]);
+}
+
+
+static DEVICE_ATTR_RW(direction);
+static DEVICE_ATTR_RW(dim_step);
+
+static struct attribute *ncp5623_led_attrs[] = {
+	&dev_attr_direction.attr,
+	&dev_attr_dim_step.attr,
+	NULL
+};
+
+static const struct attribute_group ncp5623_led_group = {
+	.attrs = ncp5623_led_attrs,
+};
+
+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 = { };
+	struct ncp5623 *ncp;
+	struct mc_subled *subled_info;
+	u32 color_index;
+	u32 reg;
+	int count = 0;
+	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)
+		count++;
+
+	subled_info = devm_kcalloc(dev, count, 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", &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;
+
+	mutex_init(&ncp->lock);
+	i2c_set_clientdata(client, ncp);
+
+	ret = devm_led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data);
+	if (ret)
+		goto destroy_lock;
+
+	ret = sysfs_update_group(&ncp->mc_dev.led_cdev.dev->kobj, &ncp5623_led_group);
+	if (ret)
+		goto destroy_lock;
+
+	fwnode_handle_put(mc_node);
+
+	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);
+
+	ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0);
+	mutex_destroy(&ncp->lock);
+}
+
+static void ncp5623_shutdown(struct i2c_client *client)
+{
+	ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0);
+}
+
+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 <alkuor@xxxxxxxxx>");
+MODULE_DESCRIPTION("NCP5623 Multi-LED driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux