| From 05c42f841f6d47f888f11c0ae016c217c3c81c7f Mon Sep 17 00:00:00 2001 |
| From: Maso Huang <maso.huang@mediatek.com> |
| Date: Mon, 4 Dec 2023 19:16:12 +0800 |
| Subject: [PATCH] backport leds pwm multicolor from v6.4 |
| |
| --- |
| drivers/leds/Kconfig | 21 +++ |
| drivers/leds/Makefile | 2 + |
| drivers/leds/led-class-multicolor.c | 203 +++++++++++++++++++++++++++ |
| drivers/leds/leds-pwm-multicolor.c | 194 +++++++++++++++++++++++++ |
| include/linux/led-class-multicolor.h | 109 ++++++++++++++ |
| 5 files changed, 529 insertions(+) |
| create mode 100644 drivers/leds/led-class-multicolor.c |
| create mode 100644 drivers/leds/leds-pwm-multicolor.c |
| create mode 100644 include/linux/led-class-multicolor.h |
| |
| diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig |
| index db18084..eebfb8c 100644 |
| --- a/drivers/leds/Kconfig |
| +++ b/drivers/leds/Kconfig |
| @@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH |
| for the flash related features of a LED device. It can be built |
| as a module. |
| |
| +config LEDS_CLASS_MULTICOLOR |
| + tristate "LED Multicolor Class Support" |
| + depends on LEDS_CLASS |
| + help |
| + This option enables the multicolor LED sysfs class in /sys/class/leds. |
| + It wraps LED class and adds multicolor LED specific sysfs attributes |
| + and kernel internal API to it. You'll need this to provide support |
| + for multicolor LEDs that are grouped together. This class is not |
| + intended for single color LEDs. It can be built as a module. |
| + |
| config LEDS_BRIGHTNESS_HW_CHANGED |
| bool "LED Class brightness_hw_changed attribute support" |
| depends on LEDS_CLASS |
| @@ -518,6 +528,17 @@ config LEDS_PWM |
| help |
| This option enables support for pwm driven LEDs |
| |
| +config LEDS_PWM_MULTICOLOR |
| + tristate "PWM driven multi-color LED Support" |
| + depends on LEDS_CLASS_MULTICOLOR |
| + depends on PWM |
| + help |
| + This option enables support for PWM driven monochrome LEDs that are |
| + grouped into multicolor LEDs. |
| + |
| + To compile this driver as a module, choose M here: the module |
| + will be called leds-pwm-multicolor. |
| + |
| config LEDS_REGULATOR |
| tristate "REGULATOR driven LED support" |
| depends on LEDS_CLASS |
| diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile |
| index e648275..39885e5 100644 |
| --- a/drivers/leds/Makefile |
| +++ b/drivers/leds/Makefile |
| @@ -4,6 +4,7 @@ |
| obj-$(CONFIG_NEW_LEDS) += led-core.o |
| obj-$(CONFIG_LEDS_CLASS) += led-class.o |
| obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o |
| +obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o |
| obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o |
| |
| # LED Platform Drivers |
| @@ -54,6 +55,7 @@ obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o |
| obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o |
| obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o |
| obj-$(CONFIG_LEDS_PWM) += leds-pwm.o |
| +obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o |
| obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o |
| obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o |
| obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o |
| diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c |
| new file mode 100644 |
| index 0000000..16f3372 |
| --- /dev/null |
| +++ b/drivers/leds/led-class-multicolor.c |
| @@ -0,0 +1,203 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +// LED Multicolor class interface |
| +// Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/ |
| +// Author: Dan Murphy <dmurphy@ti.com> |
| + |
| +#include <linux/device.h> |
| +#include <linux/init.h> |
| +#include <linux/led-class-multicolor.h> |
| +#include <linux/module.h> |
| +#include <linux/slab.h> |
| +#include <linux/uaccess.h> |
| + |
| +#include "leds.h" |
| + |
| +int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, |
| + enum led_brightness brightness) |
| +{ |
| + struct led_classdev *led_cdev = &mcled_cdev->led_cdev; |
| + int i; |
| + |
| + for (i = 0; i < mcled_cdev->num_colors; i++) |
| + mcled_cdev->subled_info[i].brightness = brightness * |
| + mcled_cdev->subled_info[i].intensity / |
| + led_cdev->max_brightness; |
| + |
| + return 0; |
| +} |
| +EXPORT_SYMBOL_GPL(led_mc_calc_color_components); |
| + |
| +static ssize_t multi_intensity_store(struct device *dev, |
| + struct device_attribute *intensity_attr, |
| + const char *buf, size_t size) |
| +{ |
| + struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); |
| + int nrchars, offset = 0; |
| + int intensity_value[LED_COLOR_ID_MAX]; |
| + int i; |
| + ssize_t ret; |
| + |
| + mutex_lock(&led_cdev->led_access); |
| + |
| + for (i = 0; i < mcled_cdev->num_colors; i++) { |
| + ret = sscanf(buf + offset, "%i%n", |
| + &intensity_value[i], &nrchars); |
| + if (ret != 1) { |
| + ret = -EINVAL; |
| + goto err_out; |
| + } |
| + offset += nrchars; |
| + } |
| + |
| + offset++; |
| + if (offset < size) { |
| + ret = -EINVAL; |
| + goto err_out; |
| + } |
| + |
| + for (i = 0; i < mcled_cdev->num_colors; i++) |
| + mcled_cdev->subled_info[i].intensity = intensity_value[i]; |
| + |
| + led_set_brightness(led_cdev, led_cdev->brightness); |
| + ret = size; |
| +err_out: |
| + mutex_unlock(&led_cdev->led_access); |
| + return ret; |
| +} |
| + |
| +static ssize_t multi_intensity_show(struct device *dev, |
| + struct device_attribute *intensity_attr, |
| + char *buf) |
| +{ |
| + struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); |
| + int len = 0; |
| + int i; |
| + |
| + for (i = 0; i < mcled_cdev->num_colors; i++) { |
| + len += sprintf(buf + len, "%d", |
| + mcled_cdev->subled_info[i].intensity); |
| + if (i < mcled_cdev->num_colors - 1) |
| + len += sprintf(buf + len, " "); |
| + } |
| + |
| + buf[len++] = '\n'; |
| + return len; |
| +} |
| +static DEVICE_ATTR_RW(multi_intensity); |
| + |
| +static ssize_t multi_index_show(struct device *dev, |
| + struct device_attribute *multi_index_attr, |
| + char *buf) |
| +{ |
| + struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); |
| + int len = 0; |
| + int index; |
| + int i; |
| + |
| + for (i = 0; i < mcled_cdev->num_colors; i++) { |
| + index = mcled_cdev->subled_info[i].color_index; |
| + len += sprintf(buf + len, "%s", led_colors[index]); |
| + if (i < mcled_cdev->num_colors - 1) |
| + len += sprintf(buf + len, " "); |
| + } |
| + |
| + buf[len++] = '\n'; |
| + return len; |
| +} |
| +static DEVICE_ATTR_RO(multi_index); |
| + |
| +static struct attribute *led_multicolor_attrs[] = { |
| + &dev_attr_multi_intensity.attr, |
| + &dev_attr_multi_index.attr, |
| + NULL, |
| +}; |
| +ATTRIBUTE_GROUPS(led_multicolor); |
| + |
| +int led_classdev_multicolor_register_ext(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev, |
| + struct led_init_data *init_data) |
| +{ |
| + struct led_classdev *led_cdev; |
| + |
| + if (!mcled_cdev) |
| + return -EINVAL; |
| + |
| + if (mcled_cdev->num_colors <= 0) |
| + return -EINVAL; |
| + |
| + if (mcled_cdev->num_colors > LED_COLOR_ID_MAX) |
| + return -EINVAL; |
| + |
| + led_cdev = &mcled_cdev->led_cdev; |
| + mcled_cdev->led_cdev.groups = led_multicolor_groups; |
| + |
| + return led_classdev_register_ext(parent, led_cdev, init_data); |
| +} |
| +EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext); |
| + |
| +void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev) |
| +{ |
| + if (!mcled_cdev) |
| + return; |
| + |
| + led_classdev_unregister(&mcled_cdev->led_cdev); |
| +} |
| +EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister); |
| + |
| +static void devm_led_classdev_multicolor_release(struct device *dev, void *res) |
| +{ |
| + led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res); |
| +} |
| + |
| +int devm_led_classdev_multicolor_register_ext(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev, |
| + struct led_init_data *init_data) |
| +{ |
| + struct led_classdev_mc **dr; |
| + int ret; |
| + |
| + dr = devres_alloc(devm_led_classdev_multicolor_release, |
| + sizeof(*dr), GFP_KERNEL); |
| + if (!dr) |
| + return -ENOMEM; |
| + |
| + ret = led_classdev_multicolor_register_ext(parent, mcled_cdev, |
| + init_data); |
| + if (ret) { |
| + devres_free(dr); |
| + return ret; |
| + } |
| + |
| + *dr = mcled_cdev; |
| + devres_add(parent, dr); |
| + |
| + return 0; |
| +} |
| +EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext); |
| + |
| +static int devm_led_classdev_multicolor_match(struct device *dev, |
| + void *res, void *data) |
| +{ |
| + struct led_classdev_mc **p = res; |
| + |
| + if (WARN_ON(!p || !*p)) |
| + return 0; |
| + |
| + return *p == data; |
| +} |
| + |
| +void devm_led_classdev_multicolor_unregister(struct device *dev, |
| + struct led_classdev_mc *mcled_cdev) |
| +{ |
| + WARN_ON(devres_release(dev, |
| + devm_led_classdev_multicolor_release, |
| + devm_led_classdev_multicolor_match, mcled_cdev)); |
| +} |
| +EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister); |
| + |
| +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); |
| +MODULE_DESCRIPTION("Multicolor LED class interface"); |
| +MODULE_LICENSE("GPL v2"); |
| diff --git a/drivers/leds/leds-pwm-multicolor.c b/drivers/leds/leds-pwm-multicolor.c |
| new file mode 100644 |
| index 0000000..e1e2daa |
| --- /dev/null |
| +++ b/drivers/leds/leds-pwm-multicolor.c |
| @@ -0,0 +1,194 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * PWM-based multi-color LED control |
| + * |
| + * Copyright 2022 Sven Schwermer <sven.schwermer@disruptive-technologies.com> |
| + */ |
| + |
| +#include <linux/err.h> |
| +#include <linux/kernel.h> |
| +#include <linux/led-class-multicolor.h> |
| +#include <linux/leds.h> |
| +#include <linux/mod_devicetable.h> |
| +#include <linux/module.h> |
| +#include <linux/mutex.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/property.h> |
| +#include <linux/pwm.h> |
| + |
| +struct pwm_led { |
| + struct pwm_device *pwm; |
| + struct pwm_state state; |
| + bool active_low; |
| +}; |
| + |
| +struct pwm_mc_led { |
| + struct led_classdev_mc mc_cdev; |
| + struct mutex lock; |
| + struct pwm_led leds[]; |
| +}; |
| + |
| +static int led_pwm_mc_set(struct led_classdev *cdev, |
| + enum led_brightness brightness) |
| +{ |
| + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); |
| + struct pwm_mc_led *priv = container_of(mc_cdev, struct pwm_mc_led, mc_cdev); |
| + unsigned long long duty; |
| + int ret = 0; |
| + int i; |
| + |
| + led_mc_calc_color_components(mc_cdev, brightness); |
| + |
| + mutex_lock(&priv->lock); |
| + |
| + for (i = 0; i < mc_cdev->num_colors; i++) { |
| + duty = priv->leds[i].state.period; |
| + duty *= mc_cdev->subled_info[i].brightness; |
| + do_div(duty, cdev->max_brightness); |
| + |
| + if (priv->leds[i].active_low) |
| + duty = priv->leds[i].state.period - duty; |
| + |
| + priv->leds[i].state.duty_cycle = duty; |
| + priv->leds[i].state.enabled = duty > 0; |
| + ret = pwm_apply_state(priv->leds[i].pwm, |
| + &priv->leds[i].state); |
| + if (ret) |
| + break; |
| + } |
| + |
| + mutex_unlock(&priv->lock); |
| + |
| + return ret; |
| +} |
| + |
| +static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv, |
| + struct fwnode_handle *mcnode) |
| +{ |
| + struct mc_subled *subled = priv->mc_cdev.subled_info; |
| + struct fwnode_handle *fwnode; |
| + struct pwm_led *pwmled; |
| + u32 color; |
| + int ret; |
| + |
| + /* iterate over the nodes inside the multi-led node */ |
| + fwnode_for_each_child_node(mcnode, fwnode) { |
| + pwmled = &priv->leds[priv->mc_cdev.num_colors]; |
| + pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); |
| + if (IS_ERR(pwmled->pwm)) { |
| + ret = PTR_ERR(pwmled->pwm); |
| + dev_err(dev, "unable to request PWM: %d\n", ret); |
| + goto release_fwnode; |
| + } |
| + pwm_init_state(pwmled->pwm, &pwmled->state); |
| + pwmled->active_low = fwnode_property_read_bool(fwnode, "active-low"); |
| + |
| + ret = fwnode_property_read_u32(fwnode, "color", &color); |
| + if (ret) { |
| + dev_err(dev, "cannot read color: %d\n", ret); |
| + goto release_fwnode; |
| + } |
| + |
| + subled[priv->mc_cdev.num_colors].color_index = color; |
| + priv->mc_cdev.num_colors++; |
| + } |
| + |
| + return 0; |
| + |
| +release_fwnode: |
| + fwnode_handle_put(fwnode); |
| + return ret; |
| +} |
| + |
| +static int led_pwm_mc_probe(struct platform_device *pdev) |
| +{ |
| + struct fwnode_handle *mcnode, *fwnode; |
| + struct led_init_data init_data = {}; |
| + struct led_classdev *cdev; |
| + struct mc_subled *subled; |
| + struct pwm_mc_led *priv; |
| + int count = 0; |
| + int ret = 0; |
| + |
| + mcnode = device_get_named_child_node(&pdev->dev, "multi-led"); |
| + if (!mcnode) { |
| + ret = -ENODEV; |
| + dev_err(&pdev->dev, "expected multi-led node\n"); |
| + return ret; |
| + } |
| + |
| + /* count the nodes inside the multi-led node */ |
| + fwnode_for_each_child_node(mcnode, fwnode) |
| + count++; |
| + |
| + priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count), |
| + GFP_KERNEL); |
| + if (!priv) { |
| + ret = -ENOMEM; |
| + goto release_mcnode; |
| + } |
| + mutex_init(&priv->lock); |
| + |
| + subled = devm_kcalloc(&pdev->dev, count, sizeof(*subled), GFP_KERNEL); |
| + if (!subled) { |
| + ret = -ENOMEM; |
| + goto release_mcnode; |
| + } |
| + priv->mc_cdev.subled_info = subled; |
| + |
| + /* init the multicolor's LED class device */ |
| + cdev = &priv->mc_cdev.led_cdev; |
| + fwnode_property_read_u32(mcnode, "max-brightness", |
| + &cdev->max_brightness); |
| + cdev->flags = LED_CORE_SUSPENDRESUME; |
| + cdev->brightness_set_blocking = led_pwm_mc_set; |
| + |
| + ret = iterate_subleds(&pdev->dev, priv, mcnode); |
| + if (ret) |
| + goto release_mcnode; |
| + |
| + init_data.fwnode = mcnode; |
| + ret = devm_led_classdev_multicolor_register_ext(&pdev->dev, |
| + &priv->mc_cdev, |
| + &init_data); |
| + if (ret) { |
| + dev_err(&pdev->dev, |
| + "failed to register multicolor PWM led for %s: %d\n", |
| + cdev->name, ret); |
| + goto release_mcnode; |
| + } |
| + |
| + ret = led_pwm_mc_set(cdev, cdev->brightness); |
| + if (ret) { |
| + dev_err(&pdev->dev, |
| + "failed to set led PWM value for %s\n", cdev->name); |
| + return ret; |
| + } |
| + |
| + platform_set_drvdata(pdev, priv); |
| + return 0; |
| + |
| +release_mcnode: |
| + fwnode_handle_put(mcnode); |
| + return ret; |
| +} |
| + |
| +static const struct of_device_id of_pwm_leds_mc_match[] = { |
| + { .compatible = "pwm-leds-multicolor", }, |
| + {} |
| +}; |
| +MODULE_DEVICE_TABLE(of, of_pwm_leds_mc_match); |
| + |
| +static struct platform_driver led_pwm_mc_driver = { |
| + .probe = led_pwm_mc_probe, |
| + .driver = { |
| + .name = "leds_pwm_multicolor", |
| + .of_match_table = of_pwm_leds_mc_match, |
| + }, |
| +}; |
| +module_platform_driver(led_pwm_mc_driver); |
| + |
| +MODULE_AUTHOR("Sven Schwermer <sven.schwermer@disruptive-technologies.com>"); |
| +MODULE_DESCRIPTION("multi-color PWM LED driver"); |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_ALIAS("platform:leds-pwm-multicolor"); |
| diff --git a/include/linux/led-class-multicolor.h b/include/linux/led-class-multicolor.h |
| new file mode 100644 |
| index 0000000..210d57b |
| --- /dev/null |
| +++ b/include/linux/led-class-multicolor.h |
| @@ -0,0 +1,109 @@ |
| +/* SPDX-License-Identifier: GPL-2.0 */ |
| +/* LED Multicolor class interface |
| + * Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/ |
| + */ |
| + |
| +#ifndef _LINUX_MULTICOLOR_LEDS_H_INCLUDED |
| +#define _LINUX_MULTICOLOR_LEDS_H_INCLUDED |
| + |
| +#include <linux/leds.h> |
| +#include <dt-bindings/leds/common.h> |
| + |
| +struct mc_subled { |
| + unsigned int color_index; |
| + unsigned int brightness; |
| + unsigned int intensity; |
| + unsigned int channel; |
| +}; |
| + |
| +struct led_classdev_mc { |
| + /* led class device */ |
| + struct led_classdev led_cdev; |
| + unsigned int num_colors; |
| + |
| + struct mc_subled *subled_info; |
| +}; |
| + |
| +static inline struct led_classdev_mc *lcdev_to_mccdev( |
| + struct led_classdev *led_cdev) |
| +{ |
| + return container_of(led_cdev, struct led_classdev_mc, led_cdev); |
| +} |
| + |
| +#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR) |
| +/** |
| + * led_classdev_multicolor_register_ext - register a new object of led_classdev |
| + * class with support for multicolor LEDs |
| + * @parent: the multicolor LED to register |
| + * @mcled_cdev: the led_classdev_mc structure for this device |
| + * @init_data: the LED class multicolor device initialization data |
| + * |
| + * Returns: 0 on success or negative error value on failure |
| + */ |
| +int led_classdev_multicolor_register_ext(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev, |
| + struct led_init_data *init_data); |
| + |
| +/** |
| + * led_classdev_multicolor_unregister - unregisters an object of led_classdev |
| + * class with support for multicolor LEDs |
| + * @mcled_cdev: the multicolor LED to unregister |
| + * |
| + * Unregister a previously registered via led_classdev_multicolor_register |
| + * object |
| + */ |
| +void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev); |
| + |
| +/* Calculate brightness for the monochrome LED cluster */ |
| +int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, |
| + enum led_brightness brightness); |
| + |
| +int devm_led_classdev_multicolor_register_ext(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev, |
| + struct led_init_data *init_data); |
| + |
| +void devm_led_classdev_multicolor_unregister(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev); |
| +#else |
| + |
| +static inline int led_classdev_multicolor_register_ext(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev, |
| + struct led_init_data *init_data) |
| +{ |
| + return 0; |
| +} |
| + |
| +static inline void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev) {}; |
| +static inline int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, |
| + enum led_brightness brightness) |
| +{ |
| + return 0; |
| +} |
| + |
| +static inline int devm_led_classdev_multicolor_register_ext(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev, |
| + struct led_init_data *init_data) |
| +{ |
| + return 0; |
| +} |
| + |
| +static inline void devm_led_classdev_multicolor_unregister(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev) |
| +{}; |
| + |
| +#endif /* IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR) */ |
| + |
| +static inline int led_classdev_multicolor_register(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev) |
| +{ |
| + return led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL); |
| +} |
| + |
| +static inline int devm_led_classdev_multicolor_register(struct device *parent, |
| + struct led_classdev_mc *mcled_cdev) |
| +{ |
| + return devm_led_classdev_multicolor_register_ext(parent, mcled_cdev, |
| + NULL); |
| +} |
| + |
| +#endif /* _LINUX_MULTICOLOR_LEDS_H_INCLUDED */ |
| -- |
| 2.18.0 |
| |