leds: Add PWM multicolor driver
authorSven Schwermer <sven.schwermer@disruptive-technologies.com>
Thu, 7 Apr 2022 07:32:25 +0000 (09:32 +0200)
committerPavel Machek <pavel@ucw.cz>
Thu, 5 May 2022 08:04:52 +0000 (10:04 +0200)
By allowing to group multiple monochrome PWM LEDs into multicolor LEDs,
all involved LEDs can be controlled in-sync. This enables using effects
using triggers, etc.

Signed-off-by: Sven Schwermer <sven.schwermer@disruptive-technologies.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/leds-pwm-multicolor.c [new file with mode: 0644]

index a49979f41eee76967c1abb008e51f1209f64e3ed..ea4481000bfe3df8a4950879f172dd0c45ce71ea 100644 (file)
@@ -552,6 +552,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
index 4fd2f92cd19811c29c84ce395237fa0e8c9b59c8..c6a147865705c4d9e2da9c5f82787872e6b8b20b 100644 (file)
@@ -73,6 +73,7 @@ obj-$(CONFIG_LEDS_PCA963X)            += leds-pca963x.o
 obj-$(CONFIG_LEDS_PM8058)              += leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)             += leds-powernv.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_S3C24XX)             += leds-s3c24xx.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)         += leds-sc27xx-bltc.o
diff --git a/drivers/leds/leds-pwm-multicolor.c b/drivers/leds/leds-pwm-multicolor.c
new file mode 100644 (file)
index 0000000..45e3870
--- /dev/null
@@ -0,0 +1,186 @@
+// 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;
+};
+
+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);
+
+               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);
+
+               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)
+               return dev_err_probe(&pdev->dev, -ENODEV,
+                                    "expected multi-led node\n");
+
+       /* 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)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to set led PWM value for %s: %d",
+                                    cdev->name, 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");