1 /*
2  * Copyright (c) 2020 Seagate Technology LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT pwm_leds
8 
9 /**
10  * @file
11  * @brief PWM driven LEDs
12  */
13 
14 #include <zephyr/drivers/led.h>
15 #include <zephyr/drivers/pwm.h>
16 #include <zephyr/device.h>
17 #include <zephyr/pm/device.h>
18 #include <zephyr/kernel.h>
19 #include <zephyr/sys/math_extras.h>
20 
21 #include <zephyr/logging/log.h>
22 LOG_MODULE_REGISTER(led_pwm, CONFIG_LED_LOG_LEVEL);
23 
24 struct led_pwm_config {
25 	int num_leds;
26 	const struct pwm_dt_spec *led;
27 };
28 
led_pwm_blink(const struct device * dev,uint32_t led,uint32_t delay_on,uint32_t delay_off)29 static int led_pwm_blink(const struct device *dev, uint32_t led,
30 			 uint32_t delay_on, uint32_t delay_off)
31 {
32 	const struct led_pwm_config *config = dev->config;
33 	const struct pwm_dt_spec *dt_led;
34 	uint32_t period_usec, pulse_usec;
35 
36 	if (led >= config->num_leds) {
37 		return -EINVAL;
38 	}
39 
40 	/*
41 	 * Convert delays (in ms) into PWM period and pulse (in us) and check
42 	 * for overflows.
43 	 */
44 	if (u32_add_overflow(delay_on, delay_off, &period_usec) ||
45 	    u32_mul_overflow(period_usec, 1000, &period_usec) ||
46 	    u32_mul_overflow(delay_on, 1000, &pulse_usec)) {
47 		return -EINVAL;
48 	}
49 
50 	dt_led = &config->led[led];
51 
52 	return pwm_set_dt(dt_led, PWM_USEC(period_usec), PWM_USEC(pulse_usec));
53 }
54 
led_pwm_set_brightness(const struct device * dev,uint32_t led,uint8_t value)55 static int led_pwm_set_brightness(const struct device *dev,
56 				  uint32_t led, uint8_t value)
57 {
58 	const struct led_pwm_config *config = dev->config;
59 	const struct pwm_dt_spec *dt_led;
60 
61 	if (led >= config->num_leds || value > 100) {
62 		return -EINVAL;
63 	}
64 
65 	dt_led = &config->led[led];
66 
67 	return pwm_set_pulse_dt(&config->led[led],
68 			(uint32_t) ((uint64_t) dt_led->period * value / 100));
69 }
70 
led_pwm_on(const struct device * dev,uint32_t led)71 static int led_pwm_on(const struct device *dev, uint32_t led)
72 {
73 	return led_pwm_set_brightness(dev, led, 100);
74 }
75 
led_pwm_off(const struct device * dev,uint32_t led)76 static int led_pwm_off(const struct device *dev, uint32_t led)
77 {
78 	return led_pwm_set_brightness(dev, led, 0);
79 }
80 
led_pwm_init(const struct device * dev)81 static int led_pwm_init(const struct device *dev)
82 {
83 	const struct led_pwm_config *config = dev->config;
84 	int i;
85 
86 	if (!config->num_leds) {
87 		LOG_ERR("%s: no LEDs found (DT child nodes missing)",
88 			dev->name);
89 		return -ENODEV;
90 	}
91 
92 	for (i = 0; i < config->num_leds; i++) {
93 		const struct pwm_dt_spec *led = &config->led[i];
94 
95 		if (!device_is_ready(led->dev)) {
96 			LOG_ERR("%s: pwm device not ready", led->dev->name);
97 			return -ENODEV;
98 		}
99 	}
100 
101 	return 0;
102 }
103 
104 #ifdef CONFIG_PM_DEVICE
led_pwm_pm_action(const struct device * dev,enum pm_device_action action)105 static int led_pwm_pm_action(const struct device *dev,
106 			     enum pm_device_action action)
107 {
108 	const struct led_pwm_config *config = dev->config;
109 
110 	/* switch all underlying PWM devices to the new state */
111 	for (size_t i = 0; i < config->num_leds; i++) {
112 		int err;
113 		const struct pwm_dt_spec *led = &config->led[i];
114 
115 		LOG_DBG("PWM %p running pm action %" PRIu32, led->dev, action);
116 
117 		err = pm_device_action_run(led->dev, action);
118 		if (err && (err != -EALREADY)) {
119 			LOG_DBG("Cannot switch PWM %p power state (err = %d)", led->dev, err);
120 		}
121 	}
122 
123 	return 0;
124 }
125 #endif /* CONFIG_PM_DEVICE */
126 
127 static DEVICE_API(led, led_pwm_api) = {
128 	.on		= led_pwm_on,
129 	.off		= led_pwm_off,
130 	.blink		= led_pwm_blink,
131 	.set_brightness	= led_pwm_set_brightness,
132 };
133 
134 #define LED_PWM_DEVICE(id)					\
135 								\
136 static const struct pwm_dt_spec led_pwm_##id[] = {		\
137 	DT_INST_FOREACH_CHILD_SEP(id, PWM_DT_SPEC_GET, (,))	\
138 };								\
139 								\
140 static const struct led_pwm_config led_pwm_config_##id = {	\
141 	.num_leds	= ARRAY_SIZE(led_pwm_##id),		\
142 	.led		= led_pwm_##id,				\
143 };								\
144 								\
145 PM_DEVICE_DT_INST_DEFINE(id, led_pwm_pm_action);		\
146 								\
147 DEVICE_DT_INST_DEFINE(id, &led_pwm_init,			\
148 		      PM_DEVICE_DT_INST_GET(id), NULL,		\
149 		      &led_pwm_config_##id, POST_KERNEL,	\
150 		      CONFIG_LED_INIT_PRIORITY, &led_pwm_api);
151 
152 DT_INST_FOREACH_STATUS_OKAY(LED_PWM_DEVICE)
153