summaryrefslogtreecommitdiff
path: root/drivers/leds/leds-pwm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-pwm.c')
-rw-r--r--drivers/leds/leds-pwm.c117
1 files changed, 93 insertions, 24 deletions
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index d71e9fa5c8de..6c1f2f50ff85 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -9,22 +9,25 @@
* based on leds-gpio.c by Raphael Assenat <raph@8d.com>
*/
-#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
-#include <linux/platform_device.h>
-#include <linux/of_platform.h>
#include <linux/leds.h>
-#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
struct led_pwm {
const char *name;
u8 active_low;
+ u8 default_state;
unsigned int max_brightness;
};
struct led_pwm_data {
+ struct gpio_desc *enable_gpio;
struct led_classdev cdev;
struct pwm_device *pwm;
struct pwm_state pwmstate;
@@ -50,9 +53,31 @@ static int led_pwm_set(struct led_classdev *led_cdev,
if (led_dat->active_low)
duty = led_dat->pwmstate.period - duty;
+ gpiod_set_value_cansleep(led_dat->enable_gpio, !!brightness);
+
led_dat->pwmstate.duty_cycle = duty;
- led_dat->pwmstate.enabled = duty > 0;
- return pwm_apply_state(led_dat->pwm, &led_dat->pwmstate);
+ /*
+ * Disabling a PWM doesn't guarantee that it emits the inactive level.
+ * So keep it on. Only for suspending the PWM should be disabled because
+ * otherwise it refuses to suspend. The possible downside is that the
+ * LED might stay (or even go) on.
+ */
+ led_dat->pwmstate.enabled = !(led_cdev->flags & LED_SUSPENDED);
+ return pwm_apply_might_sleep(led_dat->pwm, &led_dat->pwmstate);
+}
+
+static int led_pwm_default_brightness_get(struct fwnode_handle *fwnode,
+ int max_brightness)
+{
+ unsigned int default_brightness;
+ int ret;
+
+ ret = fwnode_property_read_u32(fwnode, "default-brightness",
+ &default_brightness);
+ if (ret < 0 || default_brightness > max_brightness)
+ default_brightness = max_brightness;
+
+ return default_brightness;
}
__attribute__((nonnull))
@@ -77,7 +102,54 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
led_data->cdev.brightness_set_blocking = led_pwm_set;
- pwm_init_state(led_data->pwm, &led_data->pwmstate);
+ /* init PWM state */
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_KEEP:
+ pwm_get_state(led_data->pwm, &led_data->pwmstate);
+ if (led_data->pwmstate.period)
+ break;
+ led->default_state = LEDS_DEFSTATE_OFF;
+ dev_warn(dev,
+ "failed to read period for %s, default to off",
+ led->name);
+ fallthrough;
+ default:
+ pwm_init_state(led_data->pwm, &led_data->pwmstate);
+ break;
+ }
+
+ /* set brightness */
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_ON:
+ led_data->cdev.brightness =
+ led_pwm_default_brightness_get(fwnode, led->max_brightness);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ {
+ uint64_t brightness;
+
+ brightness = led->max_brightness;
+ brightness *= led_data->pwmstate.duty_cycle;
+ do_div(brightness, led_data->pwmstate.period);
+ led_data->cdev.brightness = brightness;
+ }
+ break;
+ }
+
+ /*
+ * Claim the GPIO as GPIOD_ASIS and set the value
+ * later on to honor the different default states
+ */
+ led_data->enable_gpio = devm_fwnode_gpiod_get(dev, fwnode, "enable", GPIOD_ASIS, NULL);
+ if (IS_ERR(led_data->enable_gpio)) {
+ if (PTR_ERR(led_data->enable_gpio) == -ENOENT)
+ /* Enable GPIO is optional */
+ led_data->enable_gpio = NULL;
+ else
+ return PTR_ERR(led_data->enable_gpio);
+ }
+
+ gpiod_direction_output(led_data->enable_gpio, !!led_data->cdev.brightness);
ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data);
if (ret) {
@@ -86,11 +158,13 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
return ret;
}
- ret = led_pwm_set(&led_data->cdev, led_data->cdev.brightness);
- if (ret) {
- dev_err(dev, "failed to set led PWM value for %s: %d",
- led->name, ret);
- return ret;
+ if (led->default_state != LEDS_DEFSTATE_KEEP) {
+ ret = led_pwm_set(&led_data->cdev, led_data->cdev.brightness);
+ if (ret) {
+ dev_err(dev, "failed to set led PWM value for %s: %d",
+ led->name, ret);
+ return ret;
+ }
}
priv->num_leds++;
@@ -99,37 +173,32 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
{
- struct fwnode_handle *fwnode;
struct led_pwm led;
int ret;
- memset(&led, 0, sizeof(led));
+ device_for_each_child_node_scoped(dev, fwnode) {
+ memset(&led, 0, sizeof(led));
- device_for_each_child_node(dev, fwnode) {
ret = fwnode_property_read_string(fwnode, "label", &led.name);
if (ret && is_of_node(fwnode))
led.name = to_of_node(fwnode)->name;
- if (!led.name) {
- ret = EINVAL;
- goto err_child_out;
- }
+ if (!led.name)
+ return -EINVAL;
led.active_low = fwnode_property_read_bool(fwnode,
"active-low");
fwnode_property_read_u32(fwnode, "max-brightness",
&led.max_brightness);
+ led.default_state = led_init_default_state_get(fwnode);
+
ret = led_pwm_add(dev, priv, &led, fwnode);
if (ret)
- goto err_child_out;
+ return ret;
}
return 0;
-
-err_child_out:
- fwnode_handle_put(fwnode);
- return ret;
}
static int led_pwm_probe(struct platform_device *pdev)