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 <drivers/led.h>
15 #include <drivers/pwm.h>
16 #include <device.h>
17 #include <zephyr.h>
18 #include <sys/math_extras.h>
19 
20 #include <logging/log.h>
21 LOG_MODULE_REGISTER(led_pwm, CONFIG_LED_LOG_LEVEL);
22 
23 #define DEV_CFG(dev)	((const struct led_pwm_config *) ((dev)->config))
24 
25 struct led_pwm {
26 	const struct device *dev;
27 	uint32_t channel;
28 	uint32_t period;
29 	pwm_flags_t flags;
30 };
31 
32 struct led_pwm_config {
33 	int num_leds;
34 	const struct led_pwm *led;
35 };
36 
led_pwm_blink(const struct device * dev,uint32_t led,uint32_t delay_on,uint32_t delay_off)37 static int led_pwm_blink(const struct device *dev, uint32_t led,
38 			 uint32_t delay_on, uint32_t delay_off)
39 {
40 	const struct led_pwm_config *config = DEV_CFG(dev);
41 	const struct led_pwm *led_pwm;
42 	uint32_t period_usec, pulse_usec;
43 
44 	if (led >= config->num_leds) {
45 		return -EINVAL;
46 	}
47 
48 	/*
49 	 * Convert delays (in ms) into PWM period and pulse (in us) and check
50 	 * for overflows.
51 	 */
52 	if (u32_add_overflow(delay_on, delay_off, &period_usec) ||
53 	    u32_mul_overflow(period_usec, 1000, &period_usec) ||
54 	    u32_mul_overflow(delay_on, 1000, &pulse_usec)) {
55 		return -EINVAL;
56 	}
57 
58 	led_pwm = &config->led[led];
59 
60 	return pwm_pin_set_usec(led_pwm->dev, led_pwm->channel,
61 				period_usec, pulse_usec, led_pwm->flags);
62 }
63 
led_pwm_set_brightness(const struct device * dev,uint32_t led,uint8_t value)64 static int led_pwm_set_brightness(const struct device *dev,
65 				  uint32_t led, uint8_t value)
66 {
67 	const struct led_pwm_config *config = DEV_CFG(dev);
68 	const struct led_pwm *led_pwm;
69 	uint32_t pulse;
70 
71 	if (led >= config->num_leds || value > 100) {
72 		return -EINVAL;
73 	}
74 
75 	led_pwm = &config->led[led];
76 
77 	pulse = led_pwm->period * value / 100;
78 
79 	return pwm_pin_set_cycles(led_pwm->dev, led_pwm->channel,
80 				  led_pwm->period, pulse, led_pwm->flags);
81 }
82 
led_pwm_on(const struct device * dev,uint32_t led)83 static int led_pwm_on(const struct device *dev, uint32_t led)
84 {
85 	return led_pwm_set_brightness(dev, led, 100);
86 }
87 
led_pwm_off(const struct device * dev,uint32_t led)88 static int led_pwm_off(const struct device *dev, uint32_t led)
89 {
90 	return led_pwm_set_brightness(dev, led, 0);
91 }
92 
led_pwm_init(const struct device * dev)93 static int led_pwm_init(const struct device *dev)
94 {
95 	const struct led_pwm_config *config = DEV_CFG(dev);
96 	int i;
97 
98 	if (!config->num_leds) {
99 		LOG_ERR("%s: no LEDs found (DT child nodes missing)",
100 			dev->name);
101 		return -ENODEV;
102 	}
103 
104 	for (i = 0; i < config->num_leds; i++) {
105 		const struct led_pwm *led = &config->led[i];
106 
107 		if (!device_is_ready(led->dev)) {
108 			LOG_ERR("%s: pwm device not ready", dev->name);
109 			return -ENODEV;
110 		}
111 	}
112 
113 	return 0;
114 }
115 
116 #ifdef CONFIG_PM_DEVICE
led_pwm_pm_control(const struct device * dev,enum pm_device_action action)117 static int led_pwm_pm_control(const struct device *dev,
118 			      enum pm_device_action action)
119 {
120 	const struct led_pwm_config *config = DEV_CFG(dev);
121 
122 	/* switch all underlying PWM devices to the new state */
123 	for (size_t i = 0; i < config->num_leds; i++) {
124 		int err;
125 		enum pm_device_state state;
126 		const struct led_pwm *led_pwm = &config->led[i];
127 
128 		LOG_DBG("Switching PWM %p to state %" PRIu32, led_pwm->dev, state);
129 
130 		/* NOTE: temporary solution, deserves proper fix */
131 		switch (action) {
132 		case PM_DEVICE_ACTION_RESUME:
133 			state = PM_DEVICE_STATE_ACTIVE;
134 			break;
135 		case PM_DEVICE_ACTION_SUSPEND:
136 			state = PM_DEVICE_STATE_SUSPENDED;
137 			break;
138 		default:
139 			return -ENOTSUP;
140 		}
141 
142 		err = pm_device_state_set(led_pwm->dev, state);
143 		if (err) {
144 			LOG_ERR("Cannot switch PWM %p power state", led_pwm->dev);
145 		}
146 	}
147 
148 	return 0;
149 }
150 #endif /* CONFIG_PM_DEVICE */
151 
152 static const struct led_driver_api led_pwm_api = {
153 	.on		= led_pwm_on,
154 	.off		= led_pwm_off,
155 	.blink		= led_pwm_blink,
156 	.set_brightness	= led_pwm_set_brightness,
157 };
158 
159 #define LED_PWM(led_node_id)						\
160 {									\
161 	.dev		= DEVICE_DT_GET(DT_PWMS_CTLR(led_node_id)),	\
162 	.channel	= DT_PWMS_CHANNEL(led_node_id),			\
163 	.period		= DT_PHA_OR(led_node_id, pwms, period, 100),	\
164 	.flags		= DT_PHA_OR(led_node_id, pwms, flags,		\
165 				    PWM_POLARITY_NORMAL),		\
166 },
167 
168 #define LED_PWM_DEVICE(id)					\
169 								\
170 static const struct led_pwm led_pwm_##id[] = {			\
171 	DT_INST_FOREACH_CHILD(id, LED_PWM)			\
172 };								\
173 								\
174 static const struct led_pwm_config led_pwm_config_##id = {	\
175 	.num_leds	= ARRAY_SIZE(led_pwm_##id),		\
176 	.led		= led_pwm_##id,				\
177 };								\
178 								\
179 DEVICE_DT_INST_DEFINE(id, &led_pwm_init, led_pwm_pm_control,	\
180 		      NULL, &led_pwm_config_##id, POST_KERNEL,	\
181 		      CONFIG_LED_INIT_PRIORITY, &led_pwm_api);
182 
183 DT_INST_FOREACH_STATUS_OKAY(LED_PWM_DEVICE)
184