/* * Copyright (c) 2017 Vitor Massaru Iha * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT espressif_esp32_ledc #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(pwm_ledc_esp32, CONFIG_PWM_LOG_LEVEL); static const int global_clks[] = LEDC_LL_GLOBAL_CLOCKS; #if SOC_LEDC_HAS_TIMER_SPECIFIC_MUX static const int timer_specific_clks[] = LEDC_LL_TIMER_SPECIFIC_CLOCKS; static int lowspd_clks[ARRAY_SIZE(global_clks) + ARRAY_SIZE(timer_specific_clks)]; #endif #if SOC_LEDC_SUPPORT_HS_MODE static const int highspd_clks[] = {LEDC_APB_CLK, LEDC_REF_TICK}; #endif struct pwm_ledc_esp32_data { ledc_hal_context_t hal; struct k_sem cmd_sem; }; struct pwm_ledc_esp32_channel_config { const uint8_t idx; const uint8_t channel_num; const uint8_t timer_num; uint32_t freq; const ledc_mode_t speed_mode; uint8_t resolution; ledc_clk_src_t clock_src; uint32_t clock_src_hz; uint32_t duty_val; bool inverted; }; struct pwm_ledc_esp32_config { const struct pinctrl_dev_config *pincfg; const struct device *clock_dev; const clock_control_subsys_t clock_subsys; struct pwm_ledc_esp32_channel_config *channel_config; const int channel_len; }; static struct pwm_ledc_esp32_channel_config *get_channel_config(const struct device *dev, int channel_id) { struct pwm_ledc_esp32_config *config = (struct pwm_ledc_esp32_config *)dev->config; for (uint8_t i = 0; i < config->channel_len; i++) { if (config->channel_config[i].idx == channel_id) { return &config->channel_config[i]; } } return NULL; } static void pwm_led_esp32_start(struct pwm_ledc_esp32_data *data, struct pwm_ledc_esp32_channel_config *channel) { ledc_hal_set_sig_out_en(&data->hal, channel->channel_num, true); ledc_hal_set_duty_start(&data->hal, channel->channel_num, true); if (channel->speed_mode == LEDC_LOW_SPEED_MODE) { ledc_hal_ls_channel_update(&data->hal, channel->channel_num); } } static void pwm_led_esp32_stop(struct pwm_ledc_esp32_data *data, struct pwm_ledc_esp32_channel_config *channel, bool idle_level) { ledc_hal_set_idle_level(&data->hal, channel->channel_num, idle_level); ledc_hal_set_sig_out_en(&data->hal, channel->channel_num, false); ledc_hal_set_duty_start(&data->hal, channel->channel_num, false); if (channel->speed_mode == LEDC_LOW_SPEED_MODE) { ledc_hal_ls_channel_update(&data->hal, channel->channel_num); } } static void pwm_led_esp32_duty_set(const struct device *dev, struct pwm_ledc_esp32_channel_config *channel) { struct pwm_ledc_esp32_data *data = (struct pwm_ledc_esp32_data *const)(dev)->data; ledc_hal_set_hpoint(&data->hal, channel->channel_num, 0); ledc_hal_set_duty_int_part(&data->hal, channel->channel_num, channel->duty_val); ledc_hal_set_duty_direction(&data->hal, channel->channel_num, 1); ledc_hal_set_duty_num(&data->hal, channel->channel_num, 1); ledc_hal_set_duty_cycle(&data->hal, channel->channel_num, 1); ledc_hal_set_duty_scale(&data->hal, channel->channel_num, 0); } static int pwm_led_esp32_calculate_max_resolution(struct pwm_ledc_esp32_channel_config *channel) { /** * Max duty resolution can be obtained with * max_res = log2(CLK_FREQ/FREQ) */ uint32_t max_precision_n = channel->clock_src_hz / channel->freq; for (uint8_t i = 0; i <= SOC_LEDC_TIMER_BIT_WIDTH; i++) { max_precision_n /= 2; if (!max_precision_n) { channel->resolution = i; return 0; } } return -EINVAL; } static int pwm_led_esp32_timer_config(struct pwm_ledc_esp32_channel_config *channel) { const int *clock_src; int clock_src_num; if (channel->speed_mode == LEDC_LOW_SPEED_MODE) { #ifdef SOC_LEDC_HAS_TIMER_SPECIFIC_MUX clock_src = lowspd_clks; clock_src_num = ARRAY_SIZE(lowspd_clks); #else clock_src = global_clks; clock_src_num = ARRAY_SIZE(global_clks); #endif } #ifdef SOC_LEDC_SUPPORT_HS_MODE else { clock_src = highspd_clks; clock_src_num = ARRAY_SIZE(highspd_clks); } #endif /** * Calculate max resolution based on the given frequency and the pwm clock. * Try each clock source available depending on the device and channel type. */ for (int i = 0; i < clock_src_num; i++) { if (clock_src[i] == LEDC_SLOW_CLK_RC_FAST) { /* RC_FAST requires enabling and calibrating */ if (!rtc_dig_8m_enabled() && !periph_rtc_dig_clk8m_enable()) { /* skip RC_FAST as clock source */ continue; } channel->clock_src = clock_src[i]; channel->clock_src_hz = periph_rtc_dig_clk8m_get_freq(); } else { channel->clock_src = clock_src[i]; esp_clk_tree_src_get_freq_hz(channel->clock_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &channel->clock_src_hz); } if (!pwm_led_esp32_calculate_max_resolution(channel)) { return 0; } } /* Frequency is too low for this device, so even though best precision can't * be achieved we can set max resolution and consider that the previous * loop selects clock from fastest to slowest, so this is the best * configuration achievable. */ channel->resolution = SOC_LEDC_TIMER_BIT_WIDTH; return 0; } static int pwm_led_esp32_timer_set(const struct device *dev, struct pwm_ledc_esp32_channel_config *channel) { int prescaler = 0; uint32_t precision = (0x1 << channel->resolution); struct pwm_ledc_esp32_data *data = (struct pwm_ledc_esp32_data *const)(dev)->data; __ASSERT_NO_MSG(channel->freq > 0); prescaler = ((uint64_t)channel->clock_src_hz << 8) / channel->freq / precision; if (prescaler < 0x100 || prescaler > 0x3FFFF) { LOG_ERR("Prescaler out of range: %#X", prescaler); return -EINVAL; } if (channel->speed_mode == LEDC_LOW_SPEED_MODE) { ledc_hal_set_slow_clk_sel(&data->hal, channel->clock_src); } ledc_hal_set_clock_divider(&data->hal, channel->timer_num, prescaler); ledc_hal_set_duty_resolution(&data->hal, channel->timer_num, channel->resolution); ledc_hal_set_clock_source(&data->hal, channel->timer_num, channel->clock_src); if (channel->speed_mode == LEDC_LOW_SPEED_MODE) { ledc_hal_ls_timer_update(&data->hal, channel->timer_num); } LOG_DBG("channel_num=%d, speed_mode=%d, timer_num=%d, clock_src=%d, prescaler=%d, " "resolution=%d\n", channel->channel_num, channel->speed_mode, channel->timer_num, channel->clock_src, prescaler, channel->resolution); return 0; } static int pwm_led_esp32_get_cycles_per_sec(const struct device *dev, uint32_t channel_idx, uint64_t *cycles) { struct pwm_ledc_esp32_channel_config *channel = get_channel_config(dev, channel_idx); if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } *cycles = (uint64_t)channel->clock_src_hz; return 0; } static int pwm_led_esp32_channel_update_frequency(const struct device *dev, struct pwm_ledc_esp32_channel_config *channel, uint32_t period_cycles) { const struct pwm_ledc_esp32_config *config = dev->config; uint32_t current_freq = channel->freq; uint64_t clk_freq; int ret; ret = pwm_led_esp32_get_cycles_per_sec(dev, channel->idx, &clk_freq); if (ret < 0) { return ret; } channel->freq = (uint32_t)(clk_freq / period_cycles); if (!channel->freq) { channel->freq = 1; } if (channel->freq == current_freq) { /* No need to reconfigure timer */ return 0; } /* Check whether another channel is using the same timer. * Timers can only be shared if the same frequency is used, so * first set operation will take precedence. */ for (int i = 0; i < config->channel_len; ++i) { struct pwm_ledc_esp32_channel_config *ch = &config->channel_config[i]; if (ch->freq && (channel->channel_num != ch->channel_num) && (channel->timer_num == ch->timer_num) && (channel->speed_mode == ch->speed_mode) && (channel->freq != ch->freq)) { LOG_ERR("Timer can't be shared and different frequency be " "requested"); channel->freq = 0; return -EINVAL; } } pwm_led_esp32_timer_config(channel); ret = pwm_led_esp32_timer_set(dev, channel); if (ret < 0) { LOG_ERR("Error setting timer for channel %d", channel->idx); return ret; } return 0; } static int pwm_led_esp32_set_cycles(const struct device *dev, uint32_t channel_idx, uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags) { struct pwm_ledc_esp32_data *data = (struct pwm_ledc_esp32_data *const)(dev)->data; struct pwm_ledc_esp32_channel_config *channel = get_channel_config(dev, channel_idx); int ret = 0; if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } k_sem_take(&data->cmd_sem, K_FOREVER); if (flags & PWM_POLARITY_INVERTED) { pulse_cycles = period_cycles - pulse_cycles; channel->inverted = true; } else { channel->inverted = false; } ledc_hal_init(&data->hal, channel->speed_mode); if ((pulse_cycles == period_cycles) || (pulse_cycles == 0)) { /* For duty 0% and 100% stop PWM, set output level and return */ pwm_led_esp32_stop(data, channel, (pulse_cycles == period_cycles)); goto sem_give; } ret = pwm_led_esp32_channel_update_frequency(dev, channel, period_cycles); if (ret < 0) { LOG_ERR("Error updating frequency of channel %d", channel_idx); goto sem_give; } /* Update PWM duty */ double duty_cycle = (double)pulse_cycles / (double)period_cycles; channel->duty_val = (uint32_t)((double)(1 << channel->resolution) * duty_cycle); pwm_led_esp32_duty_set(dev, channel); pwm_led_esp32_start(data, channel); sem_give: k_sem_give(&data->cmd_sem); return ret; } int pwm_led_esp32_init(const struct device *dev) { const struct pwm_ledc_esp32_config *config = dev->config; struct pwm_ledc_esp32_data *data = dev->data; struct pwm_ledc_esp32_channel_config *channel; int ret = 0; if (!device_is_ready(config->clock_dev)) { LOG_ERR("clock control device not ready"); return -ENODEV; } /* Enable peripheral */ clock_control_on(config->clock_dev, config->clock_subsys); #if SOC_LEDC_HAS_TIMER_SPECIFIC_MUX /* Combine clock sources to include timer specific sources */ memcpy(lowspd_clks, global_clks, sizeof(global_clks)); memcpy(&lowspd_clks[ARRAY_SIZE(global_clks)], timer_specific_clks, sizeof(timer_specific_clks)); #endif for (int i = 0; i < config->channel_len; ++i) { channel = &config->channel_config[i]; ledc_hal_init(&data->hal, channel->speed_mode); if (channel->speed_mode == LEDC_LOW_SPEED_MODE) { channel->clock_src = global_clks[0]; ledc_hal_set_slow_clk_sel(&data->hal, channel->clock_src); } #ifdef SOC_LEDC_SUPPORT_HS_MODE else { channel->clock_src = highspd_clks[0]; } #endif ledc_hal_set_clock_source(&data->hal, channel->timer_num, channel->clock_src); esp_clk_tree_src_get_freq_hz(channel->clock_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &channel->clock_src_hz); ledc_hal_bind_channel_timer(&data->hal, channel->channel_num, channel->timer_num); pwm_led_esp32_stop(data, channel, channel->inverted); ledc_hal_timer_rst(&data->hal, channel->timer_num); } ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); if (ret < 0) { LOG_ERR("PWM pinctrl setup failed (%d)", ret); return ret; } return 0; } static DEVICE_API(pwm, pwm_led_esp32_api) = { .set_cycles = pwm_led_esp32_set_cycles, .get_cycles_per_sec = pwm_led_esp32_get_cycles_per_sec, }; PINCTRL_DT_INST_DEFINE(0); #define CHANNEL_CONFIG(node_id) \ { \ .idx = DT_REG_ADDR(node_id), \ .channel_num = DT_REG_ADDR(node_id) % 8, \ .timer_num = DT_PROP(node_id, timer), \ .speed_mode = DT_REG_ADDR(node_id) < SOC_LEDC_CHANNEL_NUM ? LEDC_LOW_SPEED_MODE \ : !LEDC_LOW_SPEED_MODE, \ .inverted = DT_PROP(node_id, inverted), \ }, static struct pwm_ledc_esp32_channel_config channel_config[] = { DT_INST_FOREACH_CHILD(0, CHANNEL_CONFIG)}; static struct pwm_ledc_esp32_config pwm_ledc_esp32_config = { .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(0)), .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(0, offset), .channel_config = channel_config, .channel_len = ARRAY_SIZE(channel_config), }; static struct pwm_ledc_esp32_data pwm_ledc_esp32_data = { .hal = { .dev = (ledc_dev_t *) DT_INST_REG_ADDR(0), }, .cmd_sem = Z_SEM_INITIALIZER(pwm_ledc_esp32_data.cmd_sem, 1, 1), }; DEVICE_DT_INST_DEFINE(0, &pwm_led_esp32_init, NULL, &pwm_ledc_esp32_data, &pwm_ledc_esp32_config, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &pwm_led_esp32_api);