[][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
+