[][kernel][common][leds][Add backport leds pwm multicolor from kernel 6.4]
[Description]
Add backport leds pwm multicolor and dependency leds class multicolor
from kernel 6.4 which supports for PWM driven monochrome LEDs
that are grouped into multicolor LEDs.
[Release-log]
N/A
Change-Id: I0bac4664bf39699369b6a0aa4af68bd49c60061d
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/8372496
diff --git a/target/linux/mediatek/patches-5.4/999-1804-v6.4-leds-pwm-multicolor.patch b/target/linux/mediatek/patches-5.4/999-1804-v6.4-leds-pwm-multicolor.patch
new file mode 100644
index 0000000..ea1ab5c
--- /dev/null
+++ b/target/linux/mediatek/patches-5.4/999-1804-v6.4-leds-pwm-multicolor.patch
@@ -0,0 +1,602 @@
+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
+