| /* |
| * Copyright (c) 2023 Andriy Gelman <andriy.gelman@gmail.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT pwm_clock |
| |
| #include <stdint.h> |
| |
| #include <zephyr/device.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/pwm.h> |
| #include <zephyr/dt-bindings/pwm/pwm.h> |
| #include <zephyr/kernel.h> |
| |
| #define LOG_LEVEL CONFIG_CLOCK_CONTROL_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(clock_control_pwm); |
| |
| BUILD_ASSERT(CONFIG_CLOCK_CONTROL_PWM_INIT_PRIORITY > CONFIG_PWM_INIT_PRIORITY, |
| "PWM must have a higher priority than PWM clock control"); |
| |
| #define NUM_PWM_CLOCKS 1 |
| |
| struct clock_control_pwm_config { |
| const struct pwm_dt_spec pwm_dt; |
| const uint16_t pwm_on_delay; |
| }; |
| |
| struct clock_control_pwm_data { |
| uint32_t clock_frequency; |
| bool is_enabled; |
| }; |
| |
| static int clock_control_pwm_on(const struct device *dev, clock_control_subsys_t sys) |
| { |
| struct clock_control_pwm_data *data = dev->data; |
| const struct clock_control_pwm_config *config = dev->config; |
| const struct pwm_dt_spec *spec; |
| int id = (int)sys; |
| int ret; |
| |
| if (id >= NUM_PWM_CLOCKS) { |
| return -EINVAL; |
| } |
| |
| spec = &config->pwm_dt; |
| if (data->clock_frequency == 0) { |
| ret = pwm_set_dt(spec, spec->period, spec->period / 2); |
| } else { |
| uint64_t cycles_per_sec; |
| uint32_t period_cycles; |
| |
| ret = pwm_get_cycles_per_sec(spec->dev, spec->channel, &cycles_per_sec); |
| if (ret) { |
| return ret; |
| } |
| |
| if (cycles_per_sec % data->clock_frequency > 0) { |
| LOG_WRN("Target clock frequency cannot be expressed in PWM clock ticks"); |
| } |
| |
| period_cycles = cycles_per_sec / data->clock_frequency; |
| ret = pwm_set_cycles(spec->dev, spec->channel, period_cycles, period_cycles / 2, |
| spec->flags); |
| } |
| |
| if (ret) { |
| return ret; |
| } |
| |
| k_busy_wait(config->pwm_on_delay); |
| |
| data->is_enabled = true; |
| |
| return 0; |
| } |
| |
| static int clock_control_pwm_get_rate(const struct device *dev, clock_control_subsys_t sys, |
| uint32_t *rate) |
| { |
| struct clock_control_pwm_data *data = dev->data; |
| const struct clock_control_pwm_config *config = dev->config; |
| int id = (int)sys; |
| |
| if (id >= NUM_PWM_CLOCKS) { |
| return -EINVAL; |
| } |
| |
| if (data->clock_frequency > 0) { |
| *rate = data->clock_frequency; |
| } else { |
| *rate = NSEC_PER_SEC / config->pwm_dt.period; |
| } |
| |
| return 0; |
| } |
| |
| static int clock_control_pwm_set_rate(const struct device *dev, clock_control_subsys_rate_t sys, |
| clock_control_subsys_rate_t rate) |
| { |
| struct clock_control_pwm_data *data = dev->data; |
| uint32_t rate_hz = (uint32_t)rate; |
| int id = (int)sys; |
| |
| if (id >= NUM_PWM_CLOCKS) { |
| return -EINVAL; |
| } |
| |
| if (data->clock_frequency == rate_hz && data->is_enabled) { |
| return -EALREADY; |
| } |
| |
| data->clock_frequency = rate_hz; |
| |
| return clock_control_pwm_on(dev, sys); |
| } |
| |
| static int clock_control_pwm_init(const struct device *dev) |
| { |
| const struct clock_control_pwm_config *config = dev->config; |
| |
| if (!device_is_ready(config->pwm_dt.dev)) { |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static const struct clock_control_driver_api clock_control_pwm_api = { |
| .on = clock_control_pwm_on, |
| .get_rate = clock_control_pwm_get_rate, |
| .set_rate = clock_control_pwm_set_rate, |
| }; |
| |
| #define PWM_CLOCK_INIT(i) \ |
| \ |
| BUILD_ASSERT(DT_INST_PROP_LEN(i, pwms) <= 1, \ |
| "One PWM per clock control node is supported"); \ |
| \ |
| BUILD_ASSERT(DT_INST_PROP(i, pwm_on_delay) <= UINT16_MAX, \ |
| "Maximum pwm-on-delay is 65535 usec"); \ |
| \ |
| static const struct clock_control_pwm_config clock_control_pwm_config_##i = { \ |
| .pwm_dt = PWM_DT_SPEC_INST_GET(i), \ |
| .pwm_on_delay = DT_INST_PROP(i, pwm_on_delay), \ |
| }; \ |
| \ |
| static struct clock_control_pwm_data clock_control_pwm_data_##i = { \ |
| .clock_frequency = DT_INST_PROP_OR(i, clock_frequency, 0), \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(i, clock_control_pwm_init, NULL, &clock_control_pwm_data_##i, \ |
| &clock_control_pwm_config_##i, POST_KERNEL, \ |
| CONFIG_CLOCK_CONTROL_PWM_INIT_PRIORITY, &clock_control_pwm_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PWM_CLOCK_INIT) |