mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 17:08:10 +00:00
d19a8a7078
Some PWM outputs are wired such that the LED they're controlling is connected to supply rather than ground. These PWMs may not support output inversion, or when they do, disabling the PWM may set the PWM output low, causing a "brightness" value of zero to turn the LED fully on. The platform data for this driver already indicates that this was thought about, and we have the "active_low" property there already. However, the implementation for this is missing. Add the trivial implementation for this feature. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Acked-by: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Bryan Wu <cooloney@gmail.com>
241 lines
5.3 KiB
C
241 lines
5.3 KiB
C
/*
|
|
* linux/drivers/leds-pwm.c
|
|
*
|
|
* simple PWM based LED control
|
|
*
|
|
* Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
|
|
*
|
|
* based on leds-gpio.c by Raphael Assenat <raph@8d.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/fb.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/err.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/leds_pwm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
struct led_pwm_data {
|
|
struct led_classdev cdev;
|
|
struct pwm_device *pwm;
|
|
struct work_struct work;
|
|
unsigned int active_low;
|
|
unsigned int period;
|
|
int duty;
|
|
bool can_sleep;
|
|
};
|
|
|
|
struct led_pwm_priv {
|
|
int num_leds;
|
|
struct led_pwm_data leds[0];
|
|
};
|
|
|
|
static void __led_pwm_set(struct led_pwm_data *led_dat)
|
|
{
|
|
int new_duty = led_dat->duty;
|
|
|
|
pwm_config(led_dat->pwm, new_duty, led_dat->period);
|
|
|
|
if (new_duty == 0)
|
|
pwm_disable(led_dat->pwm);
|
|
else
|
|
pwm_enable(led_dat->pwm);
|
|
}
|
|
|
|
static void led_pwm_work(struct work_struct *work)
|
|
{
|
|
struct led_pwm_data *led_dat =
|
|
container_of(work, struct led_pwm_data, work);
|
|
|
|
__led_pwm_set(led_dat);
|
|
}
|
|
|
|
static void led_pwm_set(struct led_classdev *led_cdev,
|
|
enum led_brightness brightness)
|
|
{
|
|
struct led_pwm_data *led_dat =
|
|
container_of(led_cdev, struct led_pwm_data, cdev);
|
|
unsigned int max = led_dat->cdev.max_brightness;
|
|
unsigned long long duty = led_dat->period;
|
|
|
|
duty *= brightness;
|
|
do_div(duty, max);
|
|
|
|
if (led_dat->active_low)
|
|
duty = led_dat->period - duty;
|
|
|
|
led_dat->duty = duty;
|
|
|
|
if (led_dat->can_sleep)
|
|
schedule_work(&led_dat->work);
|
|
else
|
|
__led_pwm_set(led_dat);
|
|
}
|
|
|
|
static inline size_t sizeof_pwm_leds_priv(int num_leds)
|
|
{
|
|
return sizeof(struct led_pwm_priv) +
|
|
(sizeof(struct led_pwm_data) * num_leds);
|
|
}
|
|
|
|
static void led_pwm_cleanup(struct led_pwm_priv *priv)
|
|
{
|
|
while (priv->num_leds--) {
|
|
led_classdev_unregister(&priv->leds[priv->num_leds].cdev);
|
|
if (priv->leds[priv->num_leds].can_sleep)
|
|
cancel_work_sync(&priv->leds[priv->num_leds].work);
|
|
}
|
|
}
|
|
|
|
static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
|
|
struct led_pwm *led, struct device_node *child)
|
|
{
|
|
struct led_pwm_data *led_data = &priv->leds[priv->num_leds];
|
|
int ret;
|
|
|
|
led_data->active_low = led->active_low;
|
|
led_data->period = led->pwm_period_ns;
|
|
led_data->cdev.name = led->name;
|
|
led_data->cdev.default_trigger = led->default_trigger;
|
|
led_data->cdev.brightness_set = led_pwm_set;
|
|
led_data->cdev.brightness = LED_OFF;
|
|
led_data->cdev.max_brightness = led->max_brightness;
|
|
led_data->cdev.flags = LED_CORE_SUSPENDRESUME;
|
|
|
|
if (child)
|
|
led_data->pwm = devm_of_pwm_get(dev, child, NULL);
|
|
else
|
|
led_data->pwm = devm_pwm_get(dev, led->name);
|
|
if (IS_ERR(led_data->pwm)) {
|
|
ret = PTR_ERR(led_data->pwm);
|
|
dev_err(dev, "unable to request PWM for %s: %d\n",
|
|
led->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (child)
|
|
led_data->period = pwm_get_period(led_data->pwm);
|
|
|
|
led_data->can_sleep = pwm_can_sleep(led_data->pwm);
|
|
if (led_data->can_sleep)
|
|
INIT_WORK(&led_data->work, led_pwm_work);
|
|
|
|
ret = led_classdev_register(dev, &led_data->cdev);
|
|
if (ret == 0) {
|
|
priv->num_leds++;
|
|
} else {
|
|
dev_err(dev, "failed to register PWM led for %s: %d\n",
|
|
led->name, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int led_pwm_create_of(struct device *dev, struct led_pwm_priv *priv)
|
|
{
|
|
struct device_node *child;
|
|
struct led_pwm led;
|
|
int ret = 0;
|
|
|
|
memset(&led, 0, sizeof(led));
|
|
|
|
for_each_child_of_node(dev->of_node, child) {
|
|
led.name = of_get_property(child, "label", NULL) ? :
|
|
child->name;
|
|
|
|
led.default_trigger = of_get_property(child,
|
|
"linux,default-trigger", NULL);
|
|
of_property_read_u32(child, "max-brightness",
|
|
&led.max_brightness);
|
|
|
|
ret = led_pwm_add(dev, priv, &led, child);
|
|
if (ret) {
|
|
of_node_put(child);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int led_pwm_probe(struct platform_device *pdev)
|
|
{
|
|
struct led_pwm_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
|
struct led_pwm_priv *priv;
|
|
int count, i;
|
|
int ret = 0;
|
|
|
|
if (pdata)
|
|
count = pdata->num_leds;
|
|
else
|
|
count = of_get_child_count(pdev->dev.of_node);
|
|
|
|
if (!count)
|
|
return -EINVAL;
|
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof_pwm_leds_priv(count),
|
|
GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
if (pdata) {
|
|
for (i = 0; i < count; i++) {
|
|
ret = led_pwm_add(&pdev->dev, priv, &pdata->leds[i],
|
|
NULL);
|
|
if (ret)
|
|
break;
|
|
}
|
|
} else {
|
|
ret = led_pwm_create_of(&pdev->dev, priv);
|
|
}
|
|
|
|
if (ret) {
|
|
led_pwm_cleanup(priv);
|
|
return ret;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int led_pwm_remove(struct platform_device *pdev)
|
|
{
|
|
struct led_pwm_priv *priv = platform_get_drvdata(pdev);
|
|
|
|
led_pwm_cleanup(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id of_pwm_leds_match[] = {
|
|
{ .compatible = "pwm-leds", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
|
|
|
|
static struct platform_driver led_pwm_driver = {
|
|
.probe = led_pwm_probe,
|
|
.remove = led_pwm_remove,
|
|
.driver = {
|
|
.name = "leds_pwm",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_pwm_leds_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(led_pwm_driver);
|
|
|
|
MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
|
|
MODULE_DESCRIPTION("PWM LED driver for PXA");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:leds-pwm");
|