linux/drivers/leds/leds-pwm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * linux/drivers/leds-pwm.c
   4 *
   5 * simple PWM based LED control
   6 *
   7 * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
   8 *
   9 * based on leds-gpio.c by Raphael Assenat <raph@8d.com>
  10 */
  11
  12#include <linux/module.h>
  13#include <linux/kernel.h>
  14#include <linux/platform_device.h>
  15#include <linux/of_platform.h>
  16#include <linux/leds.h>
  17#include <linux/err.h>
  18#include <linux/pwm.h>
  19#include <linux/slab.h>
  20#include "leds.h"
  21
  22struct led_pwm {
  23        const char      *name;
  24        u8              active_low;
  25        u8              default_state;
  26        unsigned int    max_brightness;
  27};
  28
  29struct led_pwm_data {
  30        struct led_classdev     cdev;
  31        struct pwm_device       *pwm;
  32        struct pwm_state        pwmstate;
  33        unsigned int            active_low;
  34};
  35
  36struct led_pwm_priv {
  37        int num_leds;
  38        struct led_pwm_data leds[];
  39};
  40
  41static int led_pwm_set(struct led_classdev *led_cdev,
  42                       enum led_brightness brightness)
  43{
  44        struct led_pwm_data *led_dat =
  45                container_of(led_cdev, struct led_pwm_data, cdev);
  46        unsigned int max = led_dat->cdev.max_brightness;
  47        unsigned long long duty = led_dat->pwmstate.period;
  48
  49        duty *= brightness;
  50        do_div(duty, max);
  51
  52        if (led_dat->active_low)
  53                duty = led_dat->pwmstate.period - duty;
  54
  55        led_dat->pwmstate.duty_cycle = duty;
  56        led_dat->pwmstate.enabled = duty > 0;
  57        return pwm_apply_state(led_dat->pwm, &led_dat->pwmstate);
  58}
  59
  60__attribute__((nonnull))
  61static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
  62                       struct led_pwm *led, struct fwnode_handle *fwnode)
  63{
  64        struct led_pwm_data *led_data = &priv->leds[priv->num_leds];
  65        struct led_init_data init_data = { .fwnode = fwnode };
  66        int ret;
  67
  68        led_data->active_low = led->active_low;
  69        led_data->cdev.name = led->name;
  70        led_data->cdev.brightness = LED_OFF;
  71        led_data->cdev.max_brightness = led->max_brightness;
  72        led_data->cdev.flags = LED_CORE_SUSPENDRESUME;
  73
  74        led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
  75        if (IS_ERR(led_data->pwm))
  76                return dev_err_probe(dev, PTR_ERR(led_data->pwm),
  77                                     "unable to request PWM for %s\n",
  78                                     led->name);
  79
  80        led_data->cdev.brightness_set_blocking = led_pwm_set;
  81
  82        /* init PWM state */
  83        switch (led->default_state) {
  84        case LEDS_DEFSTATE_KEEP:
  85                pwm_get_state(led_data->pwm, &led_data->pwmstate);
  86                if (led_data->pwmstate.period)
  87                        break;
  88                led->default_state = LEDS_DEFSTATE_OFF;
  89                dev_warn(dev,
  90                        "failed to read period for %s, default to off",
  91                        led->name);
  92                fallthrough;
  93        default:
  94                pwm_init_state(led_data->pwm, &led_data->pwmstate);
  95                break;
  96        }
  97
  98        /* set brightness */
  99        switch (led->default_state) {
 100        case LEDS_DEFSTATE_ON:
 101                led_data->cdev.brightness = led->max_brightness;
 102                break;
 103        case LEDS_DEFSTATE_KEEP:
 104                {
 105                uint64_t brightness;
 106
 107                brightness = led->max_brightness;
 108                brightness *= led_data->pwmstate.duty_cycle;
 109                do_div(brightness, led_data->pwmstate.period);
 110                led_data->cdev.brightness = brightness;
 111                }
 112                break;
 113        }
 114
 115        ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data);
 116        if (ret) {
 117                dev_err(dev, "failed to register PWM led for %s: %d\n",
 118                        led->name, ret);
 119                return ret;
 120        }
 121
 122        if (led->default_state != LEDS_DEFSTATE_KEEP) {
 123                ret = led_pwm_set(&led_data->cdev, led_data->cdev.brightness);
 124                if (ret) {
 125                        dev_err(dev, "failed to set led PWM value for %s: %d",
 126                                led->name, ret);
 127                        return ret;
 128                }
 129        }
 130
 131        priv->num_leds++;
 132        return 0;
 133}
 134
 135static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
 136{
 137        struct fwnode_handle *fwnode;
 138        struct led_pwm led;
 139        int ret;
 140
 141        memset(&led, 0, sizeof(led));
 142
 143        device_for_each_child_node(dev, fwnode) {
 144                ret = fwnode_property_read_string(fwnode, "label", &led.name);
 145                if (ret && is_of_node(fwnode))
 146                        led.name = to_of_node(fwnode)->name;
 147
 148                if (!led.name) {
 149                        ret = EINVAL;
 150                        goto err_child_out;
 151                }
 152
 153                led.active_low = fwnode_property_read_bool(fwnode,
 154                                                           "active-low");
 155                fwnode_property_read_u32(fwnode, "max-brightness",
 156                                         &led.max_brightness);
 157
 158                led.default_state = led_init_default_state_get(fwnode);
 159
 160                ret = led_pwm_add(dev, priv, &led, fwnode);
 161                if (ret)
 162                        goto err_child_out;
 163        }
 164
 165        return 0;
 166
 167err_child_out:
 168        fwnode_handle_put(fwnode);
 169        return ret;
 170}
 171
 172static int led_pwm_probe(struct platform_device *pdev)
 173{
 174        struct led_pwm_priv *priv;
 175        int ret = 0;
 176        int count;
 177
 178        count = device_get_child_node_count(&pdev->dev);
 179
 180        if (!count)
 181                return -EINVAL;
 182
 183        priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
 184                            GFP_KERNEL);
 185        if (!priv)
 186                return -ENOMEM;
 187
 188        ret = led_pwm_create_fwnode(&pdev->dev, priv);
 189
 190        if (ret)
 191                return ret;
 192
 193        platform_set_drvdata(pdev, priv);
 194
 195        return 0;
 196}
 197
 198static const struct of_device_id of_pwm_leds_match[] = {
 199        { .compatible = "pwm-leds", },
 200        {},
 201};
 202MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
 203
 204static struct platform_driver led_pwm_driver = {
 205        .probe          = led_pwm_probe,
 206        .driver         = {
 207                .name   = "leds_pwm",
 208                .of_match_table = of_pwm_leds_match,
 209        },
 210};
 211
 212module_platform_driver(led_pwm_driver);
 213
 214MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
 215MODULE_DESCRIPTION("generic PWM LED driver");
 216MODULE_LICENSE("GPL v2");
 217MODULE_ALIAS("platform:leds-pwm");
 218