File Index
Files and directories
Sign in
FILE

leds-pwm-multicolor.c

File preview with syntax highlighting.

linux-6.19.14/drivers/leds/rgb/leds-pwm-multicolor.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * PWM-based multi-color LED control
  4 *
  5 * Copyright 2022 Sven Schwermer <sven.schwermer@disruptive-technologies.com>
  6 */
  7
  8#include <linux/err.h>
  9#include <linux/kernel.h>
 10#include <linux/led-class-multicolor.h>
 11#include <linux/leds.h>
 12#include <linux/mod_devicetable.h>
 13#include <linux/module.h>
 14#include <linux/mutex.h>
 15#include <linux/platform_device.h>
 16#include <linux/property.h>
 17#include <linux/pwm.h>
 18
 19struct pwm_led {
 20	struct pwm_device *pwm;
 21	struct pwm_state state;
 22	bool active_low;
 23};
 24
 25struct pwm_mc_led {
 26	struct led_classdev_mc mc_cdev;
 27	struct mutex lock;
 28	struct pwm_led leds[];
 29};
 30
 31static int led_pwm_mc_set(struct led_classdev *cdev,
 32			  enum led_brightness brightness)
 33{
 34	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
 35	struct pwm_mc_led *priv = container_of(mc_cdev, struct pwm_mc_led, mc_cdev);
 36	unsigned long long duty;
 37	int ret = 0;
 38	int i;
 39
 40	led_mc_calc_color_components(mc_cdev, brightness);
 41
 42	mutex_lock(&priv->lock);
 43
 44	for (i = 0; i < mc_cdev->num_colors; i++) {
 45		duty = priv->leds[i].state.period;
 46		duty *= mc_cdev->subled_info[i].brightness;
 47		do_div(duty, cdev->max_brightness);
 48
 49		if (priv->leds[i].active_low)
 50			duty = priv->leds[i].state.period - duty;
 51
 52		priv->leds[i].state.duty_cycle = duty;
 53		/*
 54		 * Disabling a PWM doesn't guarantee that it emits the inactive level.
 55		 * So keep it on. Only for suspending the PWM should be disabled because
 56		 * otherwise it refuses to suspend. The possible downside is that the
 57		 * LED might stay (or even go) on.
 58		 */
 59		priv->leds[i].state.enabled = !(cdev->flags & LED_SUSPENDED);
 60		ret = pwm_apply_might_sleep(priv->leds[i].pwm,
 61					    &priv->leds[i].state);
 62		if (ret)
 63			break;
 64	}
 65
 66	mutex_unlock(&priv->lock);
 67
 68	return ret;
 69}
 70
 71static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv,
 72			   struct fwnode_handle *mcnode)
 73{
 74	struct mc_subled *subled = priv->mc_cdev.subled_info;
 75	struct fwnode_handle *fwnode;
 76	struct pwm_led *pwmled;
 77	u32 color;
 78	int ret;
 79
 80	/* iterate over the nodes inside the multi-led node */
 81	fwnode_for_each_child_node(mcnode, fwnode) {
 82		pwmled = &priv->leds[priv->mc_cdev.num_colors];
 83		pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
 84		if (IS_ERR(pwmled->pwm)) {
 85			ret = dev_err_probe(dev, PTR_ERR(pwmled->pwm), "unable to request PWM\n");
 86			goto release_fwnode;
 87		}
 88		pwm_init_state(pwmled->pwm, &pwmled->state);
 89		pwmled->active_low = fwnode_property_read_bool(fwnode, "active-low");
 90
 91		ret = fwnode_property_read_u32(fwnode, "color", &color);
 92		if (ret) {
 93			dev_err(dev, "cannot read color: %d\n", ret);
 94			goto release_fwnode;
 95		}
 96
 97		subled[priv->mc_cdev.num_colors].color_index = color;
 98		priv->mc_cdev.num_colors++;
 99	}
100
101	return 0;
102
103release_fwnode:
104	fwnode_handle_put(fwnode);
105	return ret;
106}
107
108static int led_pwm_mc_probe(struct platform_device *pdev)
109{
110	struct fwnode_handle *mcnode;
111	struct led_init_data init_data = {};
112	struct led_classdev *cdev;
113	struct mc_subled *subled;
114	struct pwm_mc_led *priv;
115	unsigned int count;
116	int ret = 0;
117
118	mcnode = device_get_named_child_node(&pdev->dev, "multi-led");
119	if (!mcnode)
120		return dev_err_probe(&pdev->dev, -ENODEV,
121				     "expected multi-led node\n");
122
123	/* count the nodes inside the multi-led node */
124	count = fwnode_get_child_node_count(mcnode);
125
126	priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
127			    GFP_KERNEL);
128	if (!priv) {
129		ret = -ENOMEM;
130		goto release_mcnode;
131	}
132	mutex_init(&priv->lock);
133
134	subled = devm_kcalloc(&pdev->dev, count, sizeof(*subled), GFP_KERNEL);
135	if (!subled) {
136		ret = -ENOMEM;
137		goto release_mcnode;
138	}
139	priv->mc_cdev.subled_info = subled;
140
141	/* init the multicolor's LED class device */
142	cdev = &priv->mc_cdev.led_cdev;
143	ret = fwnode_property_read_u32(mcnode, "max-brightness",
144				 &cdev->max_brightness);
145	if (ret)
146		goto release_mcnode;
147
148	cdev->flags = LED_CORE_SUSPENDRESUME;
149	cdev->brightness_set_blocking = led_pwm_mc_set;
150
151	ret = iterate_subleds(&pdev->dev, priv, mcnode);
152	if (ret)
153		goto release_mcnode;
154
155	init_data.fwnode = mcnode;
156	ret = devm_led_classdev_multicolor_register_ext(&pdev->dev,
157							&priv->mc_cdev,
158							&init_data);
159	if (ret) {
160		dev_err(&pdev->dev,
161			"failed to register multicolor PWM led for %s: %d\n",
162			cdev->name, ret);
163		goto release_mcnode;
164	}
165
166	ret = led_pwm_mc_set(cdev, cdev->brightness);
167	if (ret)
168		return dev_err_probe(&pdev->dev, ret,
169				     "failed to set led PWM value for %s\n",
170				     cdev->name);
171
172	platform_set_drvdata(pdev, priv);
173	return 0;
174
175release_mcnode:
176	fwnode_handle_put(mcnode);
177	return ret;
178}
179
180static const struct of_device_id of_pwm_leds_mc_match[] = {
181	{ .compatible = "pwm-leds-multicolor", },
182	{}
183};
184MODULE_DEVICE_TABLE(of, of_pwm_leds_mc_match);
185
186static struct platform_driver led_pwm_mc_driver = {
187	.probe		= led_pwm_mc_probe,
188	.driver		= {
189		.name	= "leds_pwm_multicolor",
190		.of_match_table = of_pwm_leds_mc_match,
191	},
192};
193module_platform_driver(led_pwm_mc_driver);
194
195MODULE_AUTHOR("Sven Schwermer <sven.schwermer@disruptive-technologies.com>");
196MODULE_DESCRIPTION("multi-color PWM LED driver");
197MODULE_LICENSE("GPL v2");
198MODULE_ALIAS("platform:leds-pwm-multicolor");
~/ Read-only file preview.