mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
d8e5b2ac41
MCPWM group clock pre scale was originally fixed to 2, which is inconvenient. Set group clock prescale dynamically. Now the maximum resolution of the MCPWM timer is up to 160MHz(when the prescale set to 1). And add a resulotion config for MCPWM capture.
227 lines
9.1 KiB
C
227 lines
9.1 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <sys/lock.h>
|
|
#include "sdkconfig.h"
|
|
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
|
// The local log level must be defined before including esp_log.h
|
|
// Set the maximum log level for this source file
|
|
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
#endif
|
|
#include "esp_log.h"
|
|
#include "esp_check.h"
|
|
#include "esp_clk_tree.h"
|
|
#include "esp_private/periph_ctrl.h"
|
|
#include "soc/mcpwm_periph.h"
|
|
#include "hal/mcpwm_ll.h"
|
|
#include "mcpwm_private.h"
|
|
|
|
static const char *TAG = "mcpwm";
|
|
|
|
typedef struct {
|
|
_lock_t mutex; // platform level mutex lock
|
|
mcpwm_group_t *groups[SOC_MCPWM_GROUPS]; // array of MCPWM group instances
|
|
int group_ref_counts[SOC_MCPWM_GROUPS]; // reference count used to protect group install/uninstall
|
|
} mcpwm_platform_t;
|
|
|
|
static mcpwm_platform_t s_platform; // singleton platform
|
|
|
|
mcpwm_group_t *mcpwm_acquire_group_handle(int group_id)
|
|
{
|
|
bool new_group = false;
|
|
mcpwm_group_t *group = NULL;
|
|
|
|
// prevent install mcpwm group concurrently
|
|
_lock_acquire(&s_platform.mutex);
|
|
if (!s_platform.groups[group_id]) {
|
|
group = heap_caps_calloc(1, sizeof(mcpwm_group_t), MCPWM_MEM_ALLOC_CAPS);
|
|
if (group) {
|
|
new_group = true;
|
|
s_platform.groups[group_id] = group;
|
|
group->group_id = group_id;
|
|
group->intr_priority = -1;
|
|
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
|
// enable APB to access MCPWM registers
|
|
periph_module_enable(mcpwm_periph_signals.groups[group_id].module);
|
|
periph_module_reset(mcpwm_periph_signals.groups[group_id].module);
|
|
// initialize HAL context
|
|
mcpwm_hal_init_config_t hal_config = {
|
|
.group_id = group_id
|
|
};
|
|
mcpwm_hal_context_t *hal = &group->hal;
|
|
mcpwm_hal_init(hal, &hal_config);
|
|
// disable all interrupts and clear pending status
|
|
mcpwm_ll_intr_enable(hal->dev, UINT32_MAX, false);
|
|
mcpwm_ll_intr_clear_status(hal->dev, UINT32_MAX);
|
|
mcpwm_ll_group_enable_clock(hal->dev, true);
|
|
}
|
|
} else { // group already install
|
|
group = s_platform.groups[group_id];
|
|
}
|
|
if (group) {
|
|
// someone acquired the group handle means we have a new object that refer to this group
|
|
s_platform.group_ref_counts[group_id]++;
|
|
}
|
|
_lock_release(&s_platform.mutex);
|
|
|
|
if (new_group) {
|
|
ESP_LOGD(TAG, "new group(%d) at %p", group_id, group);
|
|
}
|
|
return group;
|
|
}
|
|
|
|
void mcpwm_release_group_handle(mcpwm_group_t *group)
|
|
{
|
|
int group_id = group->group_id;
|
|
bool do_deinitialize = false;
|
|
|
|
_lock_acquire(&s_platform.mutex);
|
|
s_platform.group_ref_counts[group_id]--;
|
|
if (s_platform.group_ref_counts[group_id] == 0) {
|
|
do_deinitialize = true;
|
|
s_platform.groups[group_id] = NULL; // deregister from platfrom
|
|
mcpwm_ll_group_enable_clock(group->hal.dev, false);
|
|
// hal layer deinitialize
|
|
mcpwm_hal_deinit(&group->hal);
|
|
periph_module_disable(mcpwm_periph_signals.groups[group_id].module);
|
|
free(group);
|
|
}
|
|
_lock_release(&s_platform.mutex);
|
|
|
|
if (do_deinitialize) {
|
|
ESP_LOGD(TAG, "del group(%d)", group_id);
|
|
}
|
|
}
|
|
|
|
esp_err_t mcpwm_check_intr_priority(mcpwm_group_t *group, int intr_priority)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
bool intr_priority_conflict = false;
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
if (group->intr_priority == -1) {
|
|
group->intr_priority = intr_priority;
|
|
} else if (intr_priority != 0) {
|
|
intr_priority_conflict = (group->intr_priority != intr_priority);
|
|
}
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
ESP_RETURN_ON_FALSE(!intr_priority_conflict, ESP_ERR_INVALID_STATE, TAG, "intr_priority conflict, already is %d but attempt to %d", group->intr_priority, intr_priority);
|
|
return ret;
|
|
}
|
|
|
|
int mcpwm_get_intr_priority_flag(mcpwm_group_t *group)
|
|
{
|
|
int isr_flags = 0;
|
|
if (group->intr_priority) {
|
|
isr_flags |= 1 << (group->intr_priority);
|
|
} else {
|
|
isr_flags |= MCPWM_ALLOW_INTR_PRIORITY_MASK;
|
|
}
|
|
return isr_flags;
|
|
}
|
|
|
|
esp_err_t mcpwm_select_periph_clock(mcpwm_group_t *group, soc_module_clk_t clk_src)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
bool clock_selection_conflict = false;
|
|
bool do_clock_init = false;
|
|
// check if we need to update the group clock source, group clock source is shared by all mcpwm modules
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
if (group->clk_src == 0) {
|
|
group->clk_src = clk_src;
|
|
do_clock_init = true;
|
|
} else {
|
|
clock_selection_conflict = (group->clk_src != clk_src);
|
|
}
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG,
|
|
"group clock conflict, already is %d but attempt to %d", group->clk_src, clk_src);
|
|
|
|
if (do_clock_init) {
|
|
|
|
#if CONFIG_PM_ENABLE
|
|
sprintf(group->pm_lock_name, "mcpwm_%d", group->group_id); // e.g. mcpwm_0
|
|
ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, group->pm_lock_name, &group->pm_lock);
|
|
ESP_RETURN_ON_ERROR(ret, TAG, "create pm lock failed");
|
|
ESP_LOGD(TAG, "install NO_LIGHT_SLEEP lock for MCPWM group(%d)", group->group_id);
|
|
#endif // CONFIG_PM_ENABLE
|
|
|
|
mcpwm_ll_group_set_clock_source(group->hal.dev, clk_src);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t mcpwm_set_prescale(mcpwm_group_t *group, uint32_t expect_module_resolution_hz, uint32_t module_prescale_max, uint32_t* ret_module_prescale)
|
|
{
|
|
ESP_RETURN_ON_FALSE(group && expect_module_resolution_hz && module_prescale_max, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
uint32_t periph_src_clk_hz = 0;
|
|
int group_id = group->group_id;
|
|
uint32_t group_resolution_hz = 0;
|
|
uint32_t group_prescale = group->prescale > 0 ? group->prescale : MCPWM_GROUP_CLOCK_DEFAULT_PRESCALE; // range: 1~256, 0 means not calculated
|
|
uint32_t module_prescale = 0; // range: 1~256 (for timer) or 1~16 (for carrier) or 1 (for capture)
|
|
|
|
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(group->clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz), TAG, "get clock source freq failed");
|
|
|
|
// calc the group prescale
|
|
|
|
group_resolution_hz = periph_src_clk_hz / group_prescale;
|
|
module_prescale = group_resolution_hz / expect_module_resolution_hz;
|
|
|
|
// default prescale cannot match
|
|
// try to ensure accurate division. If none of the division factors can be guaranteed to be integers, then allocate the clock frequency to the highest divisor
|
|
uint32_t fit_module_prescale = 0;
|
|
uint32_t fit_group_prescale = 0;
|
|
if (!(module_prescale >= 1 && module_prescale <= module_prescale_max)) {
|
|
group_prescale = 0;
|
|
while (++group_prescale <= MCPWM_LL_MAX_GROUP_PRESCALE) {
|
|
group_resolution_hz = periph_src_clk_hz / group_prescale;
|
|
module_prescale = group_resolution_hz / expect_module_resolution_hz;
|
|
if (module_prescale >= 1 && module_prescale <= module_prescale_max) {
|
|
// maintain the first value found during the search that satisfies the division requirement (highest frequency), applicable for cases where integer division is not possible."
|
|
fit_module_prescale = fit_module_prescale ? fit_module_prescale : module_prescale;
|
|
fit_group_prescale = fit_group_prescale ? fit_group_prescale : group_prescale;
|
|
// find accurate division
|
|
if (group_resolution_hz == expect_module_resolution_hz * module_prescale) {
|
|
fit_module_prescale = module_prescale;
|
|
fit_group_prescale = group_prescale;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
module_prescale = fit_module_prescale;
|
|
group_prescale = fit_group_prescale;
|
|
group_resolution_hz = periph_src_clk_hz / group_prescale;
|
|
}
|
|
|
|
ESP_LOGD(TAG, "group (%d) calc prescale:%"PRIu32", module calc prescale:%"PRIu32"", group_id, group_prescale, module_prescale);
|
|
ESP_RETURN_ON_FALSE(group_prescale > 0 && group_prescale <= MCPWM_LL_MAX_GROUP_PRESCALE, ESP_ERR_INVALID_STATE, TAG,
|
|
"set group prescale failed, group clock cannot match the resolution");
|
|
|
|
// check if we need to update the group prescale, group prescale is shared by all mcpwm modules
|
|
bool prescale_conflict = false;
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
if (group->prescale == 0) {
|
|
group->prescale = group_prescale;
|
|
group->resolution_hz = group_resolution_hz;
|
|
mcpwm_ll_group_set_clock_prescale(group->hal.dev, group_prescale);
|
|
} else {
|
|
prescale_conflict = (group->prescale != group_prescale);
|
|
}
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
|
|
ESP_RETURN_ON_FALSE(!prescale_conflict, ESP_ERR_INVALID_STATE, TAG,
|
|
"group prescale conflict, already is %"PRIu32" but attempt to %"PRIu32"", group->prescale, group_prescale);
|
|
|
|
ESP_LOGD(TAG, "group (%d) clock resolution:%"PRIu32"Hz", group_id, group->resolution_hz);
|
|
|
|
// set module resolution
|
|
if (ret_module_prescale) {
|
|
*ret_module_prescale = module_prescale;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|