| /* |
| * |
| * Copyright (c) 2024 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "zephyr_pwm_pool.h" |
| #include <stdarg.h> |
| #include <stdlib.h> |
| |
| /* Auxiliary data to support blink & breath */ |
| struct pwm_pool_aux_data |
| { |
| /* common */ |
| enum pwm_state state; |
| k_ticks_t t_event; |
| bool current_state; |
| union |
| { |
| struct blink |
| { |
| k_timeout_t t_on; |
| k_timeout_t t_off; |
| } blink; |
| struct breath |
| { |
| uint32_t period; |
| uint32_t current_pulse; |
| uint32_t step_pulse; |
| } breath; |
| } mode; |
| }; |
| |
| /* Get timeout to next event in auxiliary data */ |
| static k_timeout_t pwm_pool_aux_timeout(const struct pwm_pool_data * pwm_pool) |
| { |
| k_timeout_t timeout = { .ticks = K_TICKS_FOREVER }; |
| const struct pwm_pool_aux_data * pwm_pool_aux = (const struct pwm_pool_aux_data *) pwm_pool->aux; |
| size_t pwm_pool_aux_len = pwm_pool->out_len; |
| |
| for (size_t i = 0; i < pwm_pool_aux_len; i++) |
| { |
| if (pwm_pool_aux[i].state == PWM_BLINK) |
| { |
| k_ticks_t t_next = pwm_pool_aux[i].t_event + |
| (pwm_pool_aux[i].current_state ? pwm_pool_aux[i].mode.blink.t_on.ticks : pwm_pool_aux[i].mode.blink.t_off.ticks); |
| k_ticks_t t_now = sys_clock_tick_get(); |
| k_timeout_t this = { .ticks = t_next > t_now ? t_next - t_now : 0 }; |
| if (timeout.ticks == K_TICKS_FOREVER || this.ticks < timeout.ticks) |
| { |
| timeout.ticks = this.ticks; |
| } |
| } |
| else if (pwm_pool_aux[i].state == PWM_BREATH) |
| { |
| k_ticks_t t_next = pwm_pool_aux[i].t_event + K_NSEC(pwm_pool_aux[i].mode.breath.period).ticks; |
| k_ticks_t t_now = sys_clock_tick_get(); |
| k_timeout_t this = { .ticks = t_next > t_now ? t_next - t_now : 0 }; |
| if (timeout.ticks == K_TICKS_FOREVER || this.ticks < timeout.ticks) |
| { |
| timeout.ticks = this.ticks; |
| } |
| } |
| } |
| return timeout; |
| } |
| |
| /* Process events in auxiliary data */ |
| static void pwm_pool_aux_update(const struct pwm_pool_data * pwm_pool) |
| { |
| struct pwm_pool_aux_data * pwm_pool_aux = (struct pwm_pool_aux_data *) pwm_pool->aux; |
| size_t pwm_pool_aux_len = pwm_pool->out_len; |
| |
| for (size_t i = 0; i < pwm_pool_aux_len; i++) |
| { |
| if (pwm_pool_aux[i].state == PWM_BLINK) |
| { |
| k_ticks_t t_next = pwm_pool_aux[i].t_event + |
| (pwm_pool_aux[i].current_state ? pwm_pool_aux[i].mode.blink.t_on.ticks : pwm_pool_aux[i].mode.blink.t_off.ticks); |
| k_ticks_t t_now = sys_clock_tick_get(); |
| |
| if (t_next <= t_now) |
| { |
| pwm_pool_aux[i].t_event = t_now; |
| pwm_pool_aux[i].current_state = !pwm_pool_aux[i].current_state; |
| (void) pwm_set_dt(&pwm_pool->out[i], pwm_pool->out[i].period, |
| pwm_pool_aux[i].current_state ? pwm_pool->out[i].period : 0); |
| } |
| } |
| else if (pwm_pool_aux[i].state == PWM_BREATH) |
| { |
| k_ticks_t t_next = pwm_pool_aux[i].t_event + K_NSEC(pwm_pool_aux[i].mode.breath.period).ticks; |
| k_ticks_t t_now = sys_clock_tick_get(); |
| |
| if (t_next <= t_now) |
| { |
| pwm_pool_aux[i].t_event = t_now; |
| if (pwm_pool_aux[i].current_state) |
| { |
| if (pwm_pool_aux[i].mode.breath.current_pulse <= |
| pwm_pool_aux[i].mode.breath.period - pwm_pool_aux[i].mode.breath.step_pulse) |
| { |
| pwm_pool_aux[i].mode.breath.current_pulse += pwm_pool_aux[i].mode.breath.step_pulse; |
| } |
| else |
| { |
| pwm_pool_aux[i].mode.breath.current_pulse = pwm_pool_aux[i].mode.breath.period; |
| pwm_pool_aux[i].current_state = false; |
| } |
| } |
| else |
| { |
| if (pwm_pool_aux[i].mode.breath.current_pulse >= pwm_pool_aux[i].mode.breath.step_pulse) |
| { |
| pwm_pool_aux[i].mode.breath.current_pulse -= pwm_pool_aux[i].mode.breath.step_pulse; |
| } |
| else |
| { |
| pwm_pool_aux[i].mode.breath.current_pulse = 0; |
| pwm_pool_aux[i].current_state = true; |
| } |
| } |
| (void) pwm_set_dt(&pwm_pool->out[i], pwm_pool->out[i].period, pwm_pool_aux[i].mode.breath.current_pulse); |
| } |
| } |
| } |
| } |
| |
| /* Pwm pool worker */ |
| static void pwm_pool_event_work(struct k_work * item) |
| { |
| struct pwm_pool_data * pwm_pool = CONTAINER_OF(item, struct pwm_pool_data, work); |
| |
| pwm_pool_aux_update(pwm_pool); |
| (void) k_work_reschedule(&pwm_pool->work, pwm_pool_aux_timeout(pwm_pool)); |
| } |
| |
| /* Public APIs */ |
| |
| bool pwm_pool_init(struct pwm_pool_data * pwm_pool) |
| { |
| bool result = true; |
| |
| do |
| { |
| if (!pwm_pool->out_len) |
| { |
| result = false; |
| break; |
| } |
| /* check if all PWMs are ready */ |
| for (size_t i = 0; i < pwm_pool->out_len; i++) |
| { |
| if (!device_is_ready(pwm_pool->out[i].dev)) |
| { |
| result = false; |
| break; |
| } |
| } |
| if (!result) |
| { |
| break; |
| } |
| /* init all PWMs are ready */ |
| for (size_t i = 0; i < pwm_pool->out_len; i++) |
| { |
| if (pwm_set_dt(&pwm_pool->out[i], pwm_pool->out[i].period, 0)) |
| { |
| result = false; |
| break; |
| } |
| } |
| if (!result) |
| { |
| break; |
| } |
| /* set auxiliary blink/breath structure */ |
| struct pwm_pool_aux_data * pwm_pool_aux = |
| (struct pwm_pool_aux_data *) malloc(sizeof(struct pwm_pool_aux_data) * pwm_pool->out_len); |
| |
| if (!pwm_pool_aux) |
| { |
| result = false; |
| break; |
| } |
| |
| for (size_t i = 0; i < pwm_pool->out_len; i++) |
| { |
| pwm_pool_aux[i].state = PWM_OFF; |
| } |
| pwm_pool->aux = pwm_pool_aux; |
| /* init work */ |
| k_work_init_delayable(&pwm_pool->work, pwm_pool_event_work); |
| if (k_work_reschedule(&pwm_pool->work, pwm_pool_aux_timeout(pwm_pool)) < 0) |
| { |
| result = false; |
| break; |
| } |
| /* all done */ |
| } while (0); |
| |
| return result; |
| } |
| |
| bool pwm_pool_set(struct pwm_pool_data * pwm_pool, size_t pwm, enum pwm_state state, ...) |
| { |
| bool result = false; |
| |
| if (pwm < pwm_pool->out_len) |
| { |
| if (state == PWM_ON || state == PWM_OFF) |
| { |
| if (!pwm_set_dt(&pwm_pool->out[pwm], pwm_pool->out[pwm].period, state ? pwm_pool->out[pwm].period : 0)) |
| { |
| struct pwm_pool_aux_data * pwm_pool_aux = pwm_pool->aux; |
| |
| pwm_pool_aux[pwm].state = state; |
| if (k_work_reschedule(&pwm_pool->work, pwm_pool_aux_timeout(pwm_pool)) >= 0) |
| { |
| result = true; |
| } |
| } |
| } |
| else if (state == PWM_FIXED) |
| { |
| va_list argptr; |
| |
| va_start(argptr, state); |
| uint32_t permille = va_arg(argptr, uint32_t); |
| |
| va_end(argptr); |
| if (permille <= PERMILLE_MAX) |
| { |
| if (!pwm_set_dt(&pwm_pool->out[pwm], pwm_pool->out[pwm].period, |
| ((uint64_t) permille * pwm_pool->out[pwm].period) / PERMILLE_MAX)) |
| { |
| struct pwm_pool_aux_data * pwm_pool_aux = pwm_pool->aux; |
| |
| pwm_pool_aux[pwm].state = state; |
| if (k_work_reschedule(&pwm_pool->work, pwm_pool_aux_timeout(pwm_pool)) >= 0) |
| { |
| result = true; |
| } |
| } |
| } |
| } |
| else if (state == PWM_BLINK) |
| { |
| va_list argptr; |
| |
| va_start(argptr, state); |
| k_timeout_t t_on = va_arg(argptr, k_timeout_t); |
| k_timeout_t t_off = va_arg(argptr, k_timeout_t); |
| |
| va_end(argptr); |
| if (t_on.ticks >= K_NSEC(pwm_pool->out[pwm].period).ticks && t_off.ticks >= K_NSEC(pwm_pool->out[pwm].period).ticks) |
| { |
| if (!pwm_set_dt(&pwm_pool->out[pwm], pwm_pool->out[pwm].period, pwm_pool->out[pwm].period)) |
| { |
| struct pwm_pool_aux_data * pwm_pool_aux = pwm_pool->aux; |
| |
| pwm_pool_aux[pwm].state = state; |
| pwm_pool_aux[pwm].t_event = sys_clock_tick_get(); |
| pwm_pool_aux[pwm].current_state = true; |
| pwm_pool_aux[pwm].mode.blink.t_on = t_on; |
| pwm_pool_aux[pwm].mode.blink.t_off = t_off; |
| |
| if (k_work_reschedule(&pwm_pool->work, pwm_pool_aux_timeout(pwm_pool)) >= 0) |
| { |
| result = true; |
| } |
| } |
| } |
| } |
| else if (state == PWM_BREATH) |
| { |
| va_list argptr; |
| |
| va_start(argptr, state); |
| k_timeout_t t_breath = va_arg(argptr, k_timeout_t); |
| |
| va_end(argptr); |
| if (t_breath.ticks >= K_NSEC(pwm_pool->out[pwm].period).ticks * 2) |
| { |
| if (!pwm_set_dt(&pwm_pool->out[pwm], pwm_pool->out[pwm].period, 0)) |
| { |
| struct pwm_pool_aux_data * pwm_pool_aux = pwm_pool->aux; |
| |
| pwm_pool_aux[pwm].state = state; |
| pwm_pool_aux[pwm].t_event = sys_clock_tick_get(); |
| /* |
| * set new value now because pwm will be updated |
| * with its value when current cycle will finished |
| */ |
| if (pwm_pool_aux[pwm].t_event > K_NSEC(pwm_pool->out[pwm].period).ticks) |
| { |
| pwm_pool_aux[pwm].t_event -= K_NSEC(pwm_pool->out[pwm].period).ticks; |
| } |
| else |
| { |
| pwm_pool_aux[pwm].t_event = 0; |
| } |
| pwm_pool_aux[pwm].current_state = true; |
| pwm_pool_aux[pwm].mode.breath.period = pwm_pool->out[pwm].period; |
| pwm_pool_aux[pwm].mode.breath.current_pulse = 0; |
| pwm_pool_aux[pwm].mode.breath.step_pulse = |
| ((uint64_t) pwm_pool->out[pwm].period * pwm_pool->out[pwm].period * 2) / |
| k_ticks_to_ns_ceil64(t_breath.ticks); |
| if (k_work_reschedule(&pwm_pool->work, pwm_pool_aux_timeout(pwm_pool)) >= 0) |
| { |
| result = true; |
| } |
| } |
| } |
| } |
| } |
| return result; |
| } |