/* * Copyright (c) 2022 Espressif Systems (Shanghai) Co., Ltd. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT espressif_esp32_mcpwm #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_PWM_CAPTURE #include #endif /* CONFIG_PWM_CAPTURE */ #include LOG_MODULE_REGISTER(mcpwm_esp32, CONFIG_PWM_LOG_LEVEL); #define SOC_MCPWM_BASE_CLK_HZ (160000000U) #ifdef CONFIG_PWM_CAPTURE #define SKIP_IRQ_NUM 4U #define MCPWM_INTR_CAP0 BIT(0) #define MCPWM_INTR_CAP1 BIT(1) #define MCPWM_INTR_CAP2 BIT(2) #define MCPWM_CHANNEL_NUM 8U #define CAPTURE_CHANNEL_IDX 6U #else #define MCPWM_CHANNEL_NUM 6U #endif /* CONFIG_PWM_CAPTURE */ struct mcpwm_esp32_data { mcpwm_hal_context_t hal; mcpwm_hal_init_config_t init_config; struct k_sem cmd_sem; }; #ifdef CONFIG_PWM_CAPTURE struct capture_data { uint32_t value; mcpwm_capture_on_edge_t edge; }; struct mcpwm_esp32_capture_config { uint8_t capture_signal; pwm_capture_callback_handler_t callback; void *user_data; uint32_t period; uint32_t pulse; uint32_t overflows; uint8_t skip_irq; bool capture_period; bool capture_pulse; bool continuous; struct capture_data capture_data[SKIP_IRQ_NUM]; }; #endif /* CONFIG_PWM_CAPTURE */ struct mcpwm_esp32_channel_config { uint8_t idx; uint8_t timer_id; uint8_t operator_id; uint8_t generator_id; uint32_t freq; uint32_t duty; uint8_t prescale; bool inverted; #ifdef CONFIG_PWM_CAPTURE struct mcpwm_esp32_capture_config capture; #endif /* CONFIG_PWM_CAPTURE */ }; struct mcpwm_esp32_config { const uint8_t index; const struct pinctrl_dev_config *pincfg; const struct device *clock_dev; const clock_control_subsys_t clock_subsys; uint8_t prescale; uint8_t prescale_timer0; uint8_t prescale_timer1; uint8_t prescale_timer2; struct mcpwm_esp32_channel_config channel_config[MCPWM_CHANNEL_NUM]; #ifdef CONFIG_PWM_CAPTURE int (*irq_config_func)(const struct device *dev); #endif /* CONFIG_PWM_CAPTURE */ }; static void mcpwm_esp32_duty_set(const struct device *dev, struct mcpwm_esp32_channel_config *channel) { struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; mcpwm_duty_type_t duty_type; uint32_t set_duty; if (channel->inverted) { duty_type = channel->duty == 0 ? MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH : channel->duty == 100 ? MCPWM_HAL_GENERATOR_MODE_FORCE_LOW : MCPWM_DUTY_MODE_1; } else { duty_type = channel->duty == 0 ? MCPWM_HAL_GENERATOR_MODE_FORCE_LOW : channel->duty == 100 ? MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH : MCPWM_DUTY_MODE_0; } set_duty = mcpwm_ll_timer_get_peak(data->hal.dev, channel->timer_id, false) * channel->duty / 100; mcpwm_ll_operator_connect_timer(data->hal.dev, channel->operator_id, channel->timer_id); mcpwm_ll_operator_set_compare_value(data->hal.dev, channel->operator_id, channel->generator_id, set_duty); mcpwm_ll_operator_enable_update_compare_on_tez(data->hal.dev, channel->operator_id, channel->generator_id, true); if (duty_type == MCPWM_DUTY_MODE_0) { mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH); mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_KEEP); mcpwm_ll_generator_set_action_on_compare_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, channel->generator_id, MCPWM_ACTION_FORCE_LOW); } else if (duty_type == MCPWM_DUTY_MODE_1) { mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW); mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_NO_CHANGE); mcpwm_ll_generator_set_action_on_compare_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, channel->generator_id, MCPWM_ACTION_FORCE_HIGH); } else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_LOW) { mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_LOW); mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_LOW); mcpwm_ll_generator_set_action_on_compare_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, channel->generator_id, MCPWM_ACTION_FORCE_LOW); } else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH) { mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_HIGH); mcpwm_ll_generator_set_action_on_timer_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_HIGH); mcpwm_ll_generator_set_action_on_compare_event( data->hal.dev, channel->operator_id, channel->generator_id, MCPWM_TIMER_DIRECTION_UP, channel->generator_id, MCPWM_ACTION_FORCE_HIGH); } } static int mcpwm_esp32_configure_pinctrl(const struct device *dev) { int ret; struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; 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 int mcpwm_esp32_timer_set(const struct device *dev, struct mcpwm_esp32_channel_config *channel) { struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; __ASSERT_NO_MSG(channel->freq > 0); mcpwm_ll_timer_set_clock_prescale(data->hal.dev, channel->timer_id, channel->prescale); mcpwm_ll_timer_set_count_mode(data->hal.dev, channel->timer_id, MCPWM_TIMER_COUNT_MODE_UP); mcpwm_ll_timer_update_period_at_once(data->hal.dev, channel->timer_id); int real_group_prescale = mcpwm_ll_group_get_clock_prescale(data->hal.dev); uint32_t real_timer_clk_hz = SOC_MCPWM_BASE_CLK_HZ / real_group_prescale / mcpwm_ll_timer_get_clock_prescale(data->hal.dev, channel->timer_id); mcpwm_ll_timer_set_peak(data->hal.dev, channel->timer_id, real_timer_clk_hz / channel->freq, false); return 0; } static int mcpwm_esp32_get_cycles_per_sec(const struct device *dev, uint32_t channel_idx, uint64_t *cycles) { struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_channel_config *channel = &config->channel_config[channel_idx]; if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } #ifdef CONFIG_PWM_CAPTURE if (channel->idx >= CAPTURE_CHANNEL_IDX) { *cycles = (uint64_t)APB_CLK_FREQ; return 0; } #endif /* CONFIG_PWM_CAPTURE */ *cycles = (uint64_t)SOC_MCPWM_BASE_CLK_HZ / (config->prescale + 1) / (channel->prescale + 1); return 0; } static int mcpwm_esp32_set_cycles(const struct device *dev, uint32_t channel_idx, uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags) { int ret = 0; uint64_t clk_freq; struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; struct mcpwm_esp32_channel_config *channel = &config->channel_config[channel_idx]; if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } /* Update PWM frequency according to period_cycles */ mcpwm_esp32_get_cycles_per_sec(dev, channel_idx, &clk_freq); channel->freq = (uint32_t)(clk_freq / period_cycles); if (!channel->freq) { return -EINVAL; } k_sem_take(&data->cmd_sem, K_FOREVER); ret = mcpwm_esp32_timer_set(dev, channel); if (ret < 0) { k_sem_give(&data->cmd_sem); return ret; } double duty_cycle = (double)pulse_cycles * 100 / (double)period_cycles; channel->duty = (uint32_t)duty_cycle; channel->inverted = (flags & PWM_POLARITY_INVERTED); mcpwm_esp32_duty_set(dev, channel); ret = mcpwm_esp32_configure_pinctrl(dev); if (ret < 0) { k_sem_give(&data->cmd_sem); return ret; } mcpwm_ll_timer_set_start_stop_command(data->hal.dev, channel->timer_id, MCPWM_TIMER_START_NO_STOP); k_sem_give(&data->cmd_sem); return ret; } #ifdef CONFIG_PWM_CAPTURE static int mcpwm_esp32_configure_capture(const struct device *dev, uint32_t channel_idx, pwm_flags_t flags, pwm_capture_callback_handler_t cb, void *user_data) { struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; struct mcpwm_esp32_channel_config *channel = &config->channel_config[channel_idx]; struct mcpwm_esp32_capture_config *capture = &channel->capture; if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } if ((channel->idx < CAPTURE_CHANNEL_IDX) || (channel->idx > CAPTURE_CHANNEL_IDX + 2)) { LOG_ERR("PWM capture only supported on channels 6, 7 and 8"); return -EINVAL; } if (data->hal.dev->cap_chn_cfg[capture->capture_signal].capn_en) { LOG_ERR("PWM Capture already in progress"); return -EBUSY; } if (!(flags & PWM_CAPTURE_TYPE_MASK)) { LOG_ERR("No PWM capture type specified"); return -EINVAL; } channel->inverted = (flags & PWM_POLARITY_INVERTED); capture->capture_signal = channel->idx - CAPTURE_CHANNEL_IDX; capture->callback = cb; capture->user_data = user_data; capture->capture_period = (flags & PWM_CAPTURE_TYPE_PERIOD); capture->capture_pulse = (flags & PWM_CAPTURE_TYPE_PULSE); capture->continuous = (flags & PWM_CAPTURE_MODE_CONTINUOUS); return 0; } static int mcpwm_esp32_disable_capture(const struct device *dev, uint32_t channel_idx) { struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; struct mcpwm_esp32_channel_config *channel = &config->channel_config[channel_idx]; struct mcpwm_esp32_capture_config *capture = &channel->capture; if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } if ((channel->idx < CAPTURE_CHANNEL_IDX) || (channel->idx > CAPTURE_CHANNEL_IDX + 2)) { LOG_ERR("PWM capture only supported on channels 6, 7 and 8"); return -EINVAL; } mcpwm_ll_capture_enable_channel(data->hal.dev, capture->capture_signal, false); mcpwm_ll_intr_enable(data->hal.dev, MCPWM_LL_EVENT_CAPTURE(capture->capture_signal), false); return 0; } static int mcpwm_esp32_enable_capture(const struct device *dev, uint32_t channel_idx) { struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; struct mcpwm_esp32_channel_config *channel = &config->channel_config[channel_idx]; struct mcpwm_esp32_capture_config *capture = &channel->capture; if (!channel) { LOG_ERR("Error getting channel %d", channel_idx); return -EINVAL; } if (!capture->callback) { LOG_ERR("Capture not configured"); return -EINVAL; } if ((channel->idx < CAPTURE_CHANNEL_IDX) || (channel->idx > CAPTURE_CHANNEL_IDX + 2)) { LOG_ERR("PWM capture only supported on channels 6, 7 and 8"); return -EINVAL; } if (data->hal.dev->cap_chn_cfg[capture->capture_signal].capn_en) { LOG_ERR("PWM Capture already in progress"); return -EBUSY; } /** * Capture prescale is different from other modules as it is applied to the input * signal, not the timer source. It is disabled by default. */ mcpwm_capture_config_t cap_conf = { .cap_edge = MCPWM_BOTH_EDGE, .cap_prescale = 1, }; mcpwm_hal_init(&data->hal, &data->init_config); mcpwm_ll_group_set_clock_prescale(data->hal.dev, config->prescale); mcpwm_ll_group_enable_shadow_mode(data->hal.dev); mcpwm_ll_group_flush_shadow(data->hal.dev); mcpwm_ll_capture_enable_timer(data->hal.dev, true); mcpwm_ll_capture_enable_channel(data->hal.dev, capture->capture_signal, true); mcpwm_ll_capture_enable_negedge(data->hal.dev, capture->capture_signal, cap_conf.cap_edge & MCPWM_NEG_EDGE); mcpwm_ll_capture_enable_posedge(data->hal.dev, capture->capture_signal, cap_conf.cap_edge & MCPWM_POS_EDGE); mcpwm_ll_capture_set_prescale(data->hal.dev, capture->capture_signal, cap_conf.cap_prescale); mcpwm_ll_intr_enable(data->hal.dev, MCPWM_LL_EVENT_CAPTURE(capture->capture_signal), true); mcpwm_ll_intr_clear_capture_status(data->hal.dev, 1 << capture->capture_signal); capture->skip_irq = 0; return 0; } #endif /* CONFIG_PWM_CAPTURE */ static void channel_init(const struct device *dev) { struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_channel_config *channel; for (uint8_t i = 0; i < MCPWM_CHANNEL_NUM; i++) { channel = &config->channel_config[i]; channel->idx = i; channel->timer_id = i < 2 ? 0 : i < 4 ? 1 : 2; channel->operator_id = i < 2 ? 0 : i < 4 ? 1 : 2; channel->generator_id = i % 2 ? 1 : 0; channel->prescale = i < 2 ? config->prescale_timer0 : i < 4 ? config->prescale_timer1 : config->prescale_timer2; } } int mcpwm_esp32_init(const struct device *dev) { int ret; struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; if (!device_is_ready(config->clock_dev)) { LOG_ERR("clock control device not ready"); return -ENODEV; } /* Enable peripheral */ ret = clock_control_on(config->clock_dev, config->clock_subsys); if (ret < 0) { LOG_ERR("Could not initialize clock (%d)", ret); return ret; } channel_init(dev); mcpwm_hal_init(&data->hal, &data->init_config); mcpwm_ll_group_set_clock_prescale(data->hal.dev, config->prescale); mcpwm_ll_group_enable_shadow_mode(data->hal.dev); mcpwm_ll_group_flush_shadow(data->hal.dev); #ifdef CONFIG_PWM_CAPTURE ret = config->irq_config_func(dev); if (ret != 0) { LOG_ERR("could not allocate interrupt (err %d)", ret); } #endif /* CONFIG_PWM_CAPTURE */ return ret; } #ifdef CONFIG_PWM_CAPTURE static void IRAM_ATTR mcpwm_esp32_isr(const struct device *dev) { struct mcpwm_esp32_config *config = (struct mcpwm_esp32_config *)dev->config; struct mcpwm_esp32_data *data = (struct mcpwm_esp32_data *const)(dev)->data; struct mcpwm_esp32_channel_config *channel; struct mcpwm_esp32_capture_config *capture; uint32_t mcpwm_intr_status; mcpwm_intr_status = mcpwm_ll_intr_get_capture_status(data->hal.dev); mcpwm_ll_intr_clear_capture_status(data->hal.dev, mcpwm_intr_status); if (mcpwm_intr_status & MCPWM_INTR_CAP0) { channel = &config->channel_config[CAPTURE_CHANNEL_IDX]; } else if (mcpwm_intr_status & MCPWM_INTR_CAP1) { channel = &config->channel_config[CAPTURE_CHANNEL_IDX + 1]; } else if (mcpwm_intr_status & MCPWM_INTR_CAP2) { channel = &config->channel_config[CAPTURE_CHANNEL_IDX + 2]; } else { return; } if (!channel) { return; } capture = &channel->capture; /* We need to wait at least 4 (2 positive edges and 2 negative edges) interrupts to * calculate the period */ if (capture->skip_irq < SKIP_IRQ_NUM) { capture->capture_data[capture->skip_irq].value = mcpwm_ll_capture_get_value(data->hal.dev, capture->capture_signal); capture->capture_data[capture->skip_irq].edge = mcpwm_ll_capture_get_edge(data->hal.dev, capture->capture_signal) == MCPWM_CAP_EDGE_NEG ? MCPWM_NEG_EDGE : MCPWM_POS_EDGE; capture->skip_irq++; } else { /** * The capture timer is a 32-bit counter incrementing continuously, once enabled. * On the input it has an APB clock running typically at 80 MHz */ capture->period = channel->inverted ? capture->capture_data[0].edge == MCPWM_NEG_EDGE ? (capture->capture_data[2].value - capture->capture_data[0].value) : (capture->capture_data[3].value - capture->capture_data[1].value) : capture->capture_data[0].edge == MCPWM_POS_EDGE ? (capture->capture_data[2].value - capture->capture_data[0].value) : (capture->capture_data[3].value - capture->capture_data[1].value); capture->pulse = channel->inverted ? capture->capture_data[0].edge == MCPWM_NEG_EDGE ? (capture->capture_data[1].value - capture->capture_data[0].value) : (capture->capture_data[2].value - capture->capture_data[1].value) : capture->capture_data[0].edge == MCPWM_POS_EDGE ? (capture->capture_data[1].value - capture->capture_data[0].value) : (capture->capture_data[2].value - capture->capture_data[1].value); capture->skip_irq = 0; if (!capture->continuous) { mcpwm_esp32_disable_capture(dev, channel->idx); } if (capture->callback) { capture->callback(dev, capture->capture_signal + CAPTURE_CHANNEL_IDX, capture->capture_period ? capture->period : 0u, capture->capture_pulse ? capture->pulse : 0u, 0u, capture->user_data); } } } #endif /* CONFIG_PWM_CAPTURE */ static const struct pwm_driver_api mcpwm_esp32_api = { .set_cycles = mcpwm_esp32_set_cycles, .get_cycles_per_sec = mcpwm_esp32_get_cycles_per_sec, #ifdef CONFIG_PWM_CAPTURE .configure_capture = mcpwm_esp32_configure_capture, .enable_capture = mcpwm_esp32_enable_capture, .disable_capture = mcpwm_esp32_disable_capture, #endif /* CONFIG_PWM_CAPTURE */ }; #ifdef CONFIG_PWM_CAPTURE #define IRQ_CONFIG_FUNC(idx) \ static int mcpwm_esp32_irq_config_func_##idx(const struct device *dev) \ { \ int ret; \ ret = esp_intr_alloc(DT_INST_IRQ_BY_IDX(idx, 0, irq), \ ESP_PRIO_TO_FLAGS(DT_INST_IRQ_BY_IDX(idx, 0, priority)) | \ ESP_INT_FLAGS_CHECK(DT_INST_IRQ_BY_IDX(idx, 0, flags)) | \ ESP_INTR_FLAG_IRAM, \ (intr_handler_t)mcpwm_esp32_isr, (void *)dev, NULL); \ return ret; \ } #define CAPTURE_INIT(idx) .irq_config_func = mcpwm_esp32_irq_config_func_##idx #else #define IRQ_CONFIG_FUNC(idx) #define CAPTURE_INIT(idx) #endif /* CONFIG_PWM_CAPTURE */ #define ESP32_MCPWM_INIT(idx) \ PINCTRL_DT_INST_DEFINE(idx); \ IRQ_CONFIG_FUNC(idx); \ static struct mcpwm_esp32_data mcpwm_esp32_data_##idx = { \ .hal = \ { \ .dev = (mcpwm_dev_t *)DT_INST_REG_ADDR(idx), \ }, \ .init_config = \ { \ .group_id = idx, \ }, \ .cmd_sem = Z_SEM_INITIALIZER(mcpwm_esp32_data_##idx.cmd_sem, 1, 1), \ }; \ \ static struct mcpwm_esp32_config mcpwm_esp32_config_##idx = { \ .index = idx, \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \ .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(idx, offset), \ .prescale = DT_INST_PROP(idx, prescale), \ .prescale_timer0 = DT_INST_PROP_OR(idx, prescale_timer0, 0), \ .prescale_timer1 = DT_INST_PROP_OR(idx, prescale_timer1, 0), \ .prescale_timer2 = DT_INST_PROP_OR(idx, prescale_timer2, 0), \ CAPTURE_INIT(idx)}; \ \ DEVICE_DT_INST_DEFINE(idx, &mcpwm_esp32_init, NULL, &mcpwm_esp32_data_##idx, \ &mcpwm_esp32_config_##idx, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \ &mcpwm_esp32_api); DT_INST_FOREACH_STATUS_OKAY(ESP32_MCPWM_INIT)