1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
241c42ff5SLuotao Fu /*
341c42ff5SLuotao Fu * linux/drivers/leds-pwm.c
441c42ff5SLuotao Fu *
541c42ff5SLuotao Fu * simple PWM based LED control
641c42ff5SLuotao Fu *
741c42ff5SLuotao Fu * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
841c42ff5SLuotao Fu *
941c42ff5SLuotao Fu * based on leds-gpio.c by Raphael Assenat <raph@8d.com>
1041c42ff5SLuotao Fu */
1141c42ff5SLuotao Fu
1241c42ff5SLuotao Fu #include <linux/module.h>
1341c42ff5SLuotao Fu #include <linux/kernel.h>
1441c42ff5SLuotao Fu #include <linux/platform_device.h>
153192f141SRob Herring #include <linux/of.h>
1641c42ff5SLuotao Fu #include <linux/leds.h>
1741c42ff5SLuotao Fu #include <linux/err.h>
1841c42ff5SLuotao Fu #include <linux/pwm.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
203d3d65bdSDenis Osterland-Heim #include "leds.h"
2141c42ff5SLuotao Fu
22141f15c6SDenis Osterland-Heim struct led_pwm {
23141f15c6SDenis Osterland-Heim const char *name;
24141f15c6SDenis Osterland-Heim u8 active_low;
253d3d65bdSDenis Osterland-Heim u8 default_state;
26141f15c6SDenis Osterland-Heim unsigned int max_brightness;
27141f15c6SDenis Osterland-Heim };
28141f15c6SDenis Osterland-Heim
2941c42ff5SLuotao Fu struct led_pwm_data {
3041c42ff5SLuotao Fu struct led_classdev cdev;
3141c42ff5SLuotao Fu struct pwm_device *pwm;
32dd47a834SUwe Kleine-König struct pwm_state pwmstate;
3341c42ff5SLuotao Fu unsigned int active_low;
3441c42ff5SLuotao Fu };
3541c42ff5SLuotao Fu
360f86815aSPeter Ujfalusi struct led_pwm_priv {
370f86815aSPeter Ujfalusi int num_leds;
387bbec6c4SGustavo A. R. Silva struct led_pwm_data leds[];
390f86815aSPeter Ujfalusi };
400f86815aSPeter Ujfalusi
led_pwm_set(struct led_classdev * led_cdev,enum led_brightness brightness)41247bde13SThierry Reding static int led_pwm_set(struct led_classdev *led_cdev,
4241c42ff5SLuotao Fu enum led_brightness brightness)
4341c42ff5SLuotao Fu {
4441c42ff5SLuotao Fu struct led_pwm_data *led_dat =
4541c42ff5SLuotao Fu container_of(led_cdev, struct led_pwm_data, cdev);
46e4590620SLars-Peter Clausen unsigned int max = led_dat->cdev.max_brightness;
47dd47a834SUwe Kleine-König unsigned long long duty = led_dat->pwmstate.period;
4841c42ff5SLuotao Fu
49fc1aee03SXiubo Li duty *= brightness;
50fc1aee03SXiubo Li do_div(duty, max);
51d19a8a70SRussell King
52d19a8a70SRussell King if (led_dat->active_low)
53dd47a834SUwe Kleine-König duty = led_dat->pwmstate.period - duty;
54d19a8a70SRussell King
55dd47a834SUwe Kleine-König led_dat->pwmstate.duty_cycle = duty;
56*034968dbSUwe Kleine-König /*
57*034968dbSUwe Kleine-König * Disabling a PWM doesn't guarantee that it emits the inactive level.
58*034968dbSUwe Kleine-König * So keep it on. Only for suspending the PWM should be disabled because
59*034968dbSUwe Kleine-König * otherwise it refuses to suspend. The possible downside is that the
60*034968dbSUwe Kleine-König * LED might stay (or even go) on.
61*034968dbSUwe Kleine-König */
62*034968dbSUwe Kleine-König led_dat->pwmstate.enabled = !(led_cdev->flags & LED_SUSPENDED);
63a10c3d5fSSean Young return pwm_apply_might_sleep(led_dat->pwm, &led_dat->pwmstate);
649aa07625SJacek Anaszewski }
659aa07625SJacek Anaszewski
6619d2e0ceSAlexander Dahl __attribute__((nonnull))
led_pwm_add(struct device * dev,struct led_pwm_priv * priv,struct led_pwm * led,struct fwnode_handle * fwnode)675f7b03dcSRussell King static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
683f467ebeSNikolaus Voss struct led_pwm *led, struct fwnode_handle *fwnode)
6908541cbcSPeter Ujfalusi {
705f7b03dcSRussell King struct led_pwm_data *led_data = &priv->leds[priv->num_leds];
71de73f275SAlexander Dahl struct led_init_data init_data = { .fwnode = fwnode };
72aa1a6d6dSPeter Ujfalusi int ret;
7308541cbcSPeter Ujfalusi
745f7b03dcSRussell King led_data->active_low = led->active_low;
755f7b03dcSRussell King led_data->cdev.name = led->name;
765f7b03dcSRussell King led_data->cdev.brightness = LED_OFF;
775f7b03dcSRussell King led_data->cdev.max_brightness = led->max_brightness;
785f7b03dcSRussell King led_data->cdev.flags = LED_CORE_SUSPENDRESUME;
7908541cbcSPeter Ujfalusi
803f467ebeSNikolaus Voss led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
817e8da605SKrzysztof Kozlowski if (IS_ERR(led_data->pwm))
827e8da605SKrzysztof Kozlowski return dev_err_probe(dev, PTR_ERR(led_data->pwm),
837e8da605SKrzysztof Kozlowski "unable to request PWM for %s\n",
847e8da605SKrzysztof Kozlowski led->name);
8508541cbcSPeter Ujfalusi
86247bde13SThierry Reding led_data->cdev.brightness_set_blocking = led_pwm_set;
8708541cbcSPeter Ujfalusi
883d3d65bdSDenis Osterland-Heim /* init PWM state */
893d3d65bdSDenis Osterland-Heim switch (led->default_state) {
903d3d65bdSDenis Osterland-Heim case LEDS_DEFSTATE_KEEP:
913d3d65bdSDenis Osterland-Heim pwm_get_state(led_data->pwm, &led_data->pwmstate);
923d3d65bdSDenis Osterland-Heim if (led_data->pwmstate.period)
933d3d65bdSDenis Osterland-Heim break;
943d3d65bdSDenis Osterland-Heim led->default_state = LEDS_DEFSTATE_OFF;
953d3d65bdSDenis Osterland-Heim dev_warn(dev,
963d3d65bdSDenis Osterland-Heim "failed to read period for %s, default to off",
973d3d65bdSDenis Osterland-Heim led->name);
983d3d65bdSDenis Osterland-Heim fallthrough;
993d3d65bdSDenis Osterland-Heim default:
100dd47a834SUwe Kleine-König pwm_init_state(led_data->pwm, &led_data->pwmstate);
1013d3d65bdSDenis Osterland-Heim break;
1023d3d65bdSDenis Osterland-Heim }
1033d3d65bdSDenis Osterland-Heim
1043d3d65bdSDenis Osterland-Heim /* set brightness */
1053d3d65bdSDenis Osterland-Heim switch (led->default_state) {
1063d3d65bdSDenis Osterland-Heim case LEDS_DEFSTATE_ON:
1073d3d65bdSDenis Osterland-Heim led_data->cdev.brightness = led->max_brightness;
1083d3d65bdSDenis Osterland-Heim break;
1093d3d65bdSDenis Osterland-Heim case LEDS_DEFSTATE_KEEP:
1103d3d65bdSDenis Osterland-Heim {
1113d3d65bdSDenis Osterland-Heim uint64_t brightness;
1123d3d65bdSDenis Osterland-Heim
1133d3d65bdSDenis Osterland-Heim brightness = led->max_brightness;
1143d3d65bdSDenis Osterland-Heim brightness *= led_data->pwmstate.duty_cycle;
1153d3d65bdSDenis Osterland-Heim do_div(brightness, led_data->pwmstate.period);
1163d3d65bdSDenis Osterland-Heim led_data->cdev.brightness = brightness;
1173d3d65bdSDenis Osterland-Heim }
1183d3d65bdSDenis Osterland-Heim break;
1193d3d65bdSDenis Osterland-Heim }
1201b50673dSBoris Brezillon
121de73f275SAlexander Dahl ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data);
12244c606b0SDenis Osterland-Heim if (ret) {
1235f7b03dcSRussell King dev_err(dev, "failed to register PWM led for %s: %d\n",
1245f7b03dcSRussell King led->name, ret);
12544c606b0SDenis Osterland-Heim return ret;
12608541cbcSPeter Ujfalusi }
12708541cbcSPeter Ujfalusi
1283d3d65bdSDenis Osterland-Heim if (led->default_state != LEDS_DEFSTATE_KEEP) {
12944c606b0SDenis Osterland-Heim ret = led_pwm_set(&led_data->cdev, led_data->cdev.brightness);
13044c606b0SDenis Osterland-Heim if (ret) {
13144c606b0SDenis Osterland-Heim dev_err(dev, "failed to set led PWM value for %s: %d",
13244c606b0SDenis Osterland-Heim led->name, ret);
1335f7b03dcSRussell King return ret;
1345f7b03dcSRussell King }
1353d3d65bdSDenis Osterland-Heim }
1365f7b03dcSRussell King
13744c606b0SDenis Osterland-Heim priv->num_leds++;
13844c606b0SDenis Osterland-Heim return 0;
13944c606b0SDenis Osterland-Heim }
14044c606b0SDenis Osterland-Heim
led_pwm_create_fwnode(struct device * dev,struct led_pwm_priv * priv)1413f467ebeSNikolaus Voss static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
14208541cbcSPeter Ujfalusi {
1433f467ebeSNikolaus Voss struct fwnode_handle *fwnode;
144b795e6d9SRussell King struct led_pwm led;
14595138e01SAndy Shevchenko int ret;
14608541cbcSPeter Ujfalusi
147d8960dfbSHui Wang device_for_each_child_node(dev, fwnode) {
148b795e6d9SRussell King memset(&led, 0, sizeof(led));
14908541cbcSPeter Ujfalusi
1503f467ebeSNikolaus Voss ret = fwnode_property_read_string(fwnode, "label", &led.name);
1513f467ebeSNikolaus Voss if (ret && is_of_node(fwnode))
1523f467ebeSNikolaus Voss led.name = to_of_node(fwnode)->name;
15308541cbcSPeter Ujfalusi
1543f467ebeSNikolaus Voss if (!led.name) {
155cadb2de2SDan Carpenter ret = -EINVAL;
15695138e01SAndy Shevchenko goto err_child_out;
1573f467ebeSNikolaus Voss }
1583f467ebeSNikolaus Voss
1593f467ebeSNikolaus Voss led.active_low = fwnode_property_read_bool(fwnode,
1603f467ebeSNikolaus Voss "active-low");
1613f467ebeSNikolaus Voss fwnode_property_read_u32(fwnode, "max-brightness",
162b795e6d9SRussell King &led.max_brightness);
16308541cbcSPeter Ujfalusi
1643d3d65bdSDenis Osterland-Heim led.default_state = led_init_default_state_get(fwnode);
1653d3d65bdSDenis Osterland-Heim
1663f467ebeSNikolaus Voss ret = led_pwm_add(dev, priv, &led, fwnode);
16795138e01SAndy Shevchenko if (ret)
16895138e01SAndy Shevchenko goto err_child_out;
16908541cbcSPeter Ujfalusi }
17008541cbcSPeter Ujfalusi
17195138e01SAndy Shevchenko return 0;
17295138e01SAndy Shevchenko
17395138e01SAndy Shevchenko err_child_out:
17495138e01SAndy Shevchenko fwnode_handle_put(fwnode);
175aa1a6d6dSPeter Ujfalusi return ret;
17608541cbcSPeter Ujfalusi }
17708541cbcSPeter Ujfalusi
led_pwm_probe(struct platform_device * pdev)17841c42ff5SLuotao Fu static int led_pwm_probe(struct platform_device *pdev)
17941c42ff5SLuotao Fu {
1800f86815aSPeter Ujfalusi struct led_pwm_priv *priv;
181aa1a6d6dSPeter Ujfalusi int ret = 0;
18219d2e0ceSAlexander Dahl int count;
18341c42ff5SLuotao Fu
1843f467ebeSNikolaus Voss count = device_get_child_node_count(&pdev->dev);
185aa1a6d6dSPeter Ujfalusi
186aa1a6d6dSPeter Ujfalusi if (!count)
187aa1a6d6dSPeter Ujfalusi return -EINVAL;
188aa1a6d6dSPeter Ujfalusi
189d4b02200SGustavo A. R. Silva priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
19041c42ff5SLuotao Fu GFP_KERNEL);
1910f86815aSPeter Ujfalusi if (!priv)
19241c42ff5SLuotao Fu return -ENOMEM;
19341c42ff5SLuotao Fu
1943f467ebeSNikolaus Voss ret = led_pwm_create_fwnode(&pdev->dev, priv);
1955f7b03dcSRussell King
196e5a0436dSKrzysztof Kozlowski if (ret)
197aa1a6d6dSPeter Ujfalusi return ret;
19841c42ff5SLuotao Fu
1990f86815aSPeter Ujfalusi platform_set_drvdata(pdev, priv);
20041c42ff5SLuotao Fu
20141c42ff5SLuotao Fu return 0;
20241c42ff5SLuotao Fu }
20341c42ff5SLuotao Fu
20408541cbcSPeter Ujfalusi static const struct of_device_id of_pwm_leds_match[] = {
20508541cbcSPeter Ujfalusi { .compatible = "pwm-leds", },
20608541cbcSPeter Ujfalusi {},
20708541cbcSPeter Ujfalusi };
20808541cbcSPeter Ujfalusi MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
20908541cbcSPeter Ujfalusi
21041c42ff5SLuotao Fu static struct platform_driver led_pwm_driver = {
21141c42ff5SLuotao Fu .probe = led_pwm_probe,
21241c42ff5SLuotao Fu .driver = {
21341c42ff5SLuotao Fu .name = "leds_pwm",
2141e08f72dSSachin Kamat .of_match_table = of_pwm_leds_match,
21541c42ff5SLuotao Fu },
21641c42ff5SLuotao Fu };
21741c42ff5SLuotao Fu
218892a8843SAxel Lin module_platform_driver(led_pwm_driver);
21941c42ff5SLuotao Fu
22041c42ff5SLuotao Fu MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
22149651c6cSUwe Kleine-König MODULE_DESCRIPTION("generic PWM LED driver");
22249651c6cSUwe Kleine-König MODULE_LICENSE("GPL v2");
22341c42ff5SLuotao Fu MODULE_ALIAS("platform:leds-pwm");
224