1 /*
2  * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * @file
9  * @brief Generate PWM signals in different configurations and use a GPIO
10  * input pin to check the programmed timing. This test uses the systimer as
11  * benchmark, so it assumes the system tick is verified and precise.
12  */
13 
14 #include <zephyr/device.h>
15 #include <zephyr/devicetree.h>
16 #include <zephyr/drivers/pwm.h>
17 #include <zephyr/kernel.h>
18 #include <zephyr/ztest.h>
19 #include <zephyr/drivers/gpio.h>
20 #include <stdlib.h>
21 #include <math.h>
22 
23 static struct gpio_callback gpio_cb;
24 
25 #define TEST_PWM_COUNT                      DT_PROP_LEN(DT_PATH(zephyr_user), pwms)
26 #define TEST_PWM_CONFIG_ENTRY(idx, node_id) PWM_DT_SPEC_GET_BY_IDX(node_id, idx)
27 #define TEST_PWM_CONFIG_ARRAY(node_id)                                                             \
28 	{                                                                                          \
29 		LISTIFY(TEST_PWM_COUNT, TEST_PWM_CONFIG_ENTRY, (,), node_id)                       \
30 	}
31 
32 #define TEST_GPIO_COUNT                      DT_PROP_LEN(DT_PATH(zephyr_user), gpios)
33 #define TEST_GPIO_CONFIG_ENTRY(idx, node_id) GPIO_DT_SPEC_GET_BY_IDX(node_id, gpios, idx)
34 #define TEST_GPIO_CONFIG_ARRAY(node_id)                                                            \
35 	{                                                                                          \
36 		LISTIFY(TEST_GPIO_COUNT, TEST_GPIO_CONFIG_ENTRY, (,), node_id)                     \
37 	}
38 
39 static const struct pwm_dt_spec pwms_dt[] = TEST_PWM_CONFIG_ARRAY(DT_PATH(zephyr_user));
40 static const struct gpio_dt_spec gpios_dt[] = TEST_GPIO_CONFIG_ARRAY(DT_PATH(zephyr_user));
41 
42 static struct test_context {
43 	uint32_t last_edge_time;
44 	uint32_t high_time;
45 	uint32_t low_time;
46 	bool sampling_done;
47 	uint8_t skip_cnt;
48 } ctx;
49 
gpio_edge_isr(const struct device * dev,struct gpio_callback * cb,uint32_t pins)50 static void gpio_edge_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
51 {
52 	int pin_state;
53 	uint32_t current_time = k_cycle_get_32();
54 
55 	if (ctx.sampling_done || ++ctx.skip_cnt < CONFIG_SKIP_EDGE_NUM) {
56 		return;
57 	}
58 
59 	if (!ctx.last_edge_time) {
60 		/* init last_edge_time for first delta */
61 		ctx.last_edge_time = current_time;
62 		return;
63 	}
64 
65 	uint32_t elapsed_time = current_time - ctx.last_edge_time;
66 
67 	int pin = __builtin_ffs(pins) - 1;
68 
69 	if (pin >= 0) {
70 		pin_state = gpio_pin_get(dev, pin);
71 	} else {
72 		return;
73 	}
74 
75 	if (pin_state) {
76 		ctx.low_time = elapsed_time;
77 	} else {
78 		ctx.high_time = elapsed_time;
79 	}
80 
81 	/* sampling is done when both high and low times were stored */
82 	if (ctx.high_time && ctx.low_time) {
83 		ctx.sampling_done = true;
84 	}
85 
86 	ctx.last_edge_time = current_time;
87 }
88 
setup_edge_detect(void)89 static void setup_edge_detect(void)
90 {
91 	ctx.last_edge_time = 0;
92 	ctx.high_time = 0;
93 	ctx.low_time = 0;
94 	ctx.sampling_done = false;
95 	ctx.skip_cnt = 0;
96 }
97 
config_gpio(const struct gpio_dt_spec * gpio_dt)98 static void config_gpio(const struct gpio_dt_spec *gpio_dt)
99 {
100 	gpio_pin_configure_dt(gpio_dt, GPIO_INPUT);
101 	gpio_init_callback(&gpio_cb, gpio_edge_isr, BIT(gpio_dt->pin));
102 	gpio_add_callback(gpio_dt->port, &gpio_cb);
103 }
104 
enable_int_gpio(const struct gpio_dt_spec * gpio_dt,bool enable)105 static void enable_int_gpio(const struct gpio_dt_spec *gpio_dt, bool enable)
106 {
107 	if (enable) {
108 		gpio_pin_interrupt_configure(gpio_dt->port, gpio_dt->pin, GPIO_INT_EDGE_BOTH);
109 		gpio_cb.pin_mask = BIT(gpio_dt->pin);
110 	} else {
111 		gpio_pin_interrupt_configure(gpio_dt->port, gpio_dt->pin, GPIO_INT_DISABLE);
112 	}
113 }
114 
check_range(float refval,float measval)115 static bool check_range(float refval, float measval)
116 {
117 	float delta = fabsf(refval - measval);
118 	float allowed_deviation = (refval * (float)CONFIG_ALLOWED_DEVIATION) / 100;
119 
120 	return delta <= allowed_deviation;
121 }
122 
check_timing(const struct pwm_dt_spec * pwm_dt,const struct gpio_dt_spec * gpio_dt,uint8_t duty)123 static int check_timing(const struct pwm_dt_spec *pwm_dt, const struct gpio_dt_spec *gpio_dt,
124 			uint8_t duty)
125 {
126 	uint64_t cycles_s_sys, cycles_s_pwm;
127 	int pin_state;
128 	bool inverted = (pwm_dt->flags & PWM_POLARITY_INVERTED) ? true : false;
129 
130 	/* reset parameters for edge detection */
131 	setup_edge_detect();
132 
133 	/* wait for sampling */
134 	k_sleep(K_MSEC(CONFIG_SAMPLING_TIME));
135 
136 	/* store pin state for duty == 100% or 0% checks */
137 	pin_state = gpio_pin_get_dt(gpio_dt);
138 
139 	if (inverted) {
140 		pin_state = !pin_state;
141 	}
142 
143 	cycles_s_sys = (uint64_t)sys_clock_hw_cycles_per_sec();
144 	pwm_get_cycles_per_sec(pwm_dt->dev, pwm_dt->channel, &cycles_s_pwm);
145 
146 	/* sampling_done should be false for 0 and 100% duty (no switching) */
147 	TC_PRINT("Sampling done: %s\n", ctx.sampling_done ? "true" : "false");
148 
149 	if (duty == 100) {
150 		if ((pin_state == 1) && !ctx.sampling_done) {
151 			return TC_PASS;
152 		} else {
153 			return TC_FAIL;
154 		}
155 	} else if (duty == 0) {
156 		if ((pin_state == 0) && !ctx.sampling_done) {
157 			return TC_PASS;
158 		} else {
159 			return TC_FAIL;
160 		}
161 	} else {
162 		uint32_t measured_period = ctx.high_time + ctx.low_time;
163 		uint32_t measured_period_ns = (measured_period * 1e9) / cycles_s_sys;
164 		uint32_t pulse_time = inverted ? ctx.low_time : ctx.high_time;
165 		float measured_duty = (pulse_time * 100.0f) / measured_period;
166 		uint32_t measured_duty_2p = (uint32_t)(measured_duty * 100);
167 		uint32_t period_deviation_2p =
168 			(uint64_t)10000 * abs(measured_period_ns - pwm_dt->period) / pwm_dt->period;
169 		uint32_t duty_deviation_2p =
170 			(uint32_t)10000 * fabs(measured_duty - (float)duty) / duty;
171 
172 		TC_PRINT("Measured period: %u cycles, high: %u, low: %u [unit: systimer ticks]\n",
173 			 measured_period, ctx.high_time, ctx.low_time);
174 		TC_PRINT("Measured period: %u ns, deviation: %d.%d%%\n", measured_period_ns,
175 			 period_deviation_2p / 100, period_deviation_2p % 100);
176 		TC_PRINT("Measured duty: %d.%d%%, deviation: %d.%d%%\n", measured_duty_2p / 100,
177 			 measured_duty_2p % 100, duty_deviation_2p / 100, duty_deviation_2p % 100);
178 
179 		/* Compare measured values with expected ones */
180 		if (check_range(measured_period_ns, pwm_dt->period) &&
181 		    check_range(measured_duty, duty)) {
182 			TC_PRINT("PWM output matches the programmed values\n");
183 			return TC_PASS;
184 		}
185 
186 		TC_PRINT("PWM output does NOT match the programmed values\n");
187 		return TC_FAIL;
188 	}
189 }
190 
test_run(const struct pwm_dt_spec * pwm_dt,const struct gpio_dt_spec * gpio_dt,uint8_t duty,bool set_channel)191 static void test_run(const struct pwm_dt_spec *pwm_dt, const struct gpio_dt_spec *gpio_dt,
192 		     uint8_t duty, bool set_channel)
193 {
194 	int result;
195 	uint32_t pulse = (uint32_t)((pwm_dt->period * duty) / 100);
196 	bool inverted = (pwm_dt->flags & PWM_POLARITY_INVERTED) ? true : false;
197 
198 	TC_PRINT("Test case: [Channel: %" PRIu32 "] [Period: %" PRIu32 "] [Pulse: %" PRIu32
199 		 "] [Inverted: %s]\n",
200 		 pwm_dt->channel, pwm_dt->period, pulse, inverted ? "Yes" : "No");
201 
202 	if (set_channel) {
203 		result = pwm_set_dt(pwm_dt, pwm_dt->period, pulse);
204 		zassert_false(result, "Failed on pwm_set() call");
205 	}
206 
207 	enable_int_gpio(gpio_dt, true);
208 
209 	result = check_timing(pwm_dt, gpio_dt, duty);
210 
211 	enable_int_gpio(gpio_dt, false);
212 
213 	zassert_equal(result, TC_PASS, "Test case failed");
214 }
215 
ZTEST(pwm_gpio_loopback,test_pwm)216 ZTEST(pwm_gpio_loopback, test_pwm)
217 {
218 	for (int i = 0; i < TEST_PWM_COUNT; i++) {
219 		zassert_true(device_is_ready(pwms_dt[i].dev), "PWM device is not ready");
220 
221 		/* Test case: [Duty: 25%] */
222 		test_run(&pwms_dt[i], &gpios_dt[i], 25, true);
223 
224 		/* Test case: [Duty: 100%] */
225 		test_run(&pwms_dt[i], &gpios_dt[i], 100, true);
226 
227 		/* Test case: [Duty: 0%] */
228 		test_run(&pwms_dt[i], &gpios_dt[i], 0, true);
229 
230 		/* Test case: [Duty: 80%] */
231 		test_run(&pwms_dt[i], &gpios_dt[i], 80, true);
232 	}
233 }
234 
ZTEST(pwm_gpio_loopback,test_pwm_cross)235 ZTEST(pwm_gpio_loopback, test_pwm_cross)
236 {
237 	for (int i = 0; i < TEST_PWM_COUNT; i++) {
238 		/* Test case: [Duty: 40%] */
239 		test_run(&pwms_dt[i], &gpios_dt[i], 40, true);
240 	}
241 
242 	/* Set all channels and check if they retain the original
243 	 * configuration without calling pwm_set again
244 	 */
245 	for (int i = 0; i < TEST_PWM_COUNT; i++) {
246 		test_run(&pwms_dt[i], &gpios_dt[i], 40, false);
247 	}
248 }
249 
pwm_gpio_loopback_setup(void)250 static void *pwm_gpio_loopback_setup(void)
251 {
252 	for (int i = 0; i < TEST_GPIO_COUNT; i++) {
253 		if (device_is_ready(gpios_dt[i].port)) {
254 			/* Configure GPIO pin for edge detection */
255 			config_gpio(&gpios_dt[i]);
256 		} else {
257 			TC_PRINT("GPIO device %s is not ready", gpios_dt[i].port->name);
258 		}
259 	}
260 
261 	return NULL;
262 }
263 
264 ZTEST_SUITE(pwm_gpio_loopback, NULL, pwm_gpio_loopback_setup, NULL, NULL, NULL);
265