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