From c31681df543d30390d0e0f0cdbaa9462acceb72e Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <lucas.vaz@espressif.com> Date: Fri, 21 Jul 2023 10:05:38 -0300 Subject: [PATCH] arch/risc-v/espressif: Add PWM (LEDC) support Add support for the PWM peripheral to ESP32-C3/C6/H2 by using the Espressif HAL --- arch/risc-v/src/espressif/Kconfig | 140 ++ arch/risc-v/src/espressif/Make.defs | 4 + arch/risc-v/src/espressif/esp_ledc.c | 1678 ++++++++++++++++++++++ arch/risc-v/src/espressif/esp_ledc.h | 52 + arch/risc-v/src/espressif/hal_esp32c3.mk | 5 + arch/risc-v/src/espressif/hal_esp32c6.mk | 5 + arch/risc-v/src/espressif/hal_esp32h2.mk | 5 + 7 files changed, 1889 insertions(+) create mode 100644 arch/risc-v/src/espressif/esp_ledc.c create mode 100644 arch/risc-v/src/espressif/esp_ledc.h diff --git a/arch/risc-v/src/espressif/Kconfig b/arch/risc-v/src/espressif/Kconfig index c4c1a78726..c71f553fa8 100644 --- a/arch/risc-v/src/espressif/Kconfig +++ b/arch/risc-v/src/espressif/Kconfig @@ -236,6 +236,12 @@ config ESPRESSIF_GPIO_IRQ ---help--- Enable support for interrupting GPIO pins +config ESPRESSIF_LEDC + bool "LEDC (PWM)" + default n + select PWM + select ARCH_HAVE_PWM_MULTICHAN + config ESPRESSIF_HR_TIMER bool default RTC_DRIVER @@ -384,6 +390,140 @@ endchoice # ESPRESSIF_FLASH_FREQ endmenu # SPI Flash Configuration +menu "LEDC configuration" + depends on ESPRESSIF_LEDC + +config ESPRESSIF_LEDC_HPOINT + hex "LEDC hpoint value" + default 0x0000 + range 0x0 0xfffff + +menuconfig ESPRESSIF_LEDC_TIMER0 + bool "Timer 0" + default n + +if ESPRESSIF_LEDC_TIMER0 + +config ESPRESSIF_LEDC_TIMER0_CHANNELS + int "Number of Timer 0 channels" + default 2 if PWM_MULTICHAN && PWM_NCHANNELS > 1 + default 1 if !PWM_MULTICHAN || PWM_NCHANNELS = 1 + range 0 6 + +config ESPRESSIF_LEDC_TIMER0_RESOLUTION + int "Timer 0 resolution" + default 13 + range 1 14 if ESPRESSIF_ESP32C3 && !(ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + range 1 20 if !ESPRESSIF_ESP32C3 && (ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + ---help--- + Timer resolution in bits. The resolution is the number of bits used to by the timer + counter to generate the PWM signal. The duty cycle provided by the upper layers + will be scaled to fit the resolution. + +endif # ESPRESSIF_LEDC_TIMER0 + +menuconfig ESPRESSIF_LEDC_TIMER1 + bool "Timer 1" + default n + +if ESPRESSIF_LEDC_TIMER1 + +config ESPRESSIF_LEDC_TIMER1_CHANNELS + int "Number of Timer 1 channels" + default 2 if PWM_MULTICHAN && PWM_NCHANNELS > 1 + default 1 if !PWM_MULTICHAN || PWM_NCHANNELS = 1 + range 0 6 + +config ESPRESSIF_LEDC_TIMER1_RESOLUTION + int "Timer 1 resolution" + default 13 + range 1 14 if ESPRESSIF_ESP32C3 && !(ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + range 1 20 if !ESPRESSIF_ESP32C3 && (ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + ---help--- + Timer resolution in bits. The resolution is the number of bits used to by the timer + counter to generate the PWM signal. The duty cycle provided by the upper layers + will be scaled to fit the resolution. + +endif # ESPRESSIF_LEDC_TIMER1 + +menuconfig ESPRESSIF_LEDC_TIMER2 + bool "Timer 2" + default n + +if ESPRESSIF_LEDC_TIMER2 + +config ESPRESSIF_LEDC_TIMER2_CHANNELS + int "Number of Timer 2 channels" + default 2 if PWM_MULTICHAN && PWM_NCHANNELS > 1 + default 1 if !PWM_MULTICHAN || PWM_NCHANNELS = 1 + range 0 6 + +config ESPRESSIF_LEDC_TIMER2_RESOLUTION + int "Timer 2 resolution" + default 13 + range 1 14 if ESPRESSIF_ESP32C3 && !(ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + range 1 20 if !ESPRESSIF_ESP32C3 && (ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + ---help--- + Timer resolution in bits. The resolution is the number of bits used to by the timer + counter to generate the PWM signal. The duty cycle provided by the upper layers + will be scaled to fit the resolution. + +endif # ESPRESSIF_LEDC_TIMER2 + +menuconfig ESPRESSIF_LEDC_TIMER3 + bool "Timer 3" + default n + +if ESPRESSIF_LEDC_TIMER3 + +config ESPRESSIF_LEDC_TIMER3_CHANNELS + int "Number of Timer 3 channels" + default 2 if PWM_MULTICHAN && PWM_NCHANNELS > 1 + default 1 if !PWM_MULTICHAN || PWM_NCHANNELS = 1 + range 0 6 + +config ESPRESSIF_LEDC_TIMER3_RESOLUTION + int "Timer 3 resolution" + default 13 + range 1 14 if ESPRESSIF_ESP32C3 && !(ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + range 1 20 if !ESPRESSIF_ESP32C3 && (ESPRESSIF_ESP32C6 || ESPRESSIF_ESP32H2) + ---help--- + Timer resolution in bits. The resolution is the number of bits used to by the timer + counter to generate the PWM signal. The duty cycle provided by the upper layers + will be scaled to fit the resolution. + +endif # ESPRESSIF_LEDC_TIMER3 + +config ESPRESSIF_LEDC_CHANNEL0_PIN + int "Channel 0 pin" + default 2 + +config ESPRESSIF_LEDC_CHANNEL1_PIN + int "Channel 1 pin" + default 3 + +config ESPRESSIF_LEDC_CHANNEL2_PIN + int "Channel 2 pin" + default 4 + +config ESPRESSIF_LEDC_CHANNEL3_PIN + int "Channel 3 pin" + default 5 + +if PWM_MULTICHAN && PWM_NCHANNELS > 1 + +config ESPRESSIF_LEDC_CHANNEL4_PIN + int "Channel 4 pin" + default 6 + +config ESPRESSIF_LEDC_CHANNEL5_PIN + int "Channel 5 pin" + default 7 + +endif # PWM_MULTICHAN && PWM_NCHANNELS > 1 + +endmenu # LEDC configuration + menu "High Resolution Timer" depends on ESPRESSIF_HR_TIMER diff --git a/arch/risc-v/src/espressif/Make.defs b/arch/risc-v/src/espressif/Make.defs index 520fda109c..6f43bdae93 100644 --- a/arch/risc-v/src/espressif/Make.defs +++ b/arch/risc-v/src/espressif/Make.defs @@ -68,6 +68,10 @@ ifeq ($(CONFIG_ESPRESSIF_HR_TIMER),y) CHIP_CSRCS += esp_hr_timer.c endif +ifeq ($(CONFIG_ESPRESSIF_LEDC),y) + CHIP_CSRCS += esp_ledc.c +endif + ifeq ($(CONFIG_ESPRESSIF_USBSERIAL),y) CHIP_CSRCS += esp_usbserial.c endif diff --git a/arch/risc-v/src/espressif/esp_ledc.c b/arch/risc-v/src/espressif/esp_ledc.c new file mode 100644 index 0000000000..30f868ee74 --- /dev/null +++ b/arch/risc-v/src/espressif/esp_ledc.c @@ -0,0 +1,1678 @@ +/**************************************************************************** + * arch/risc-v/src/espressif/esp_ledc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <inttypes.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/param.h> +#include <debug.h> +#include <errno.h> + +#include <nuttx/kmalloc.h> + +#include "esp_gpio.h" +#include "esp_ledc.h" +#include "riscv_internal.h" + +#include <arch/chip/gpio_sig_map.h> +#include "esp_private/periph_ctrl.h" +#include "hal/ledc_hal.h" +#include "hal/ledc_types.h" +#include "soc/soc_caps.h" +#include "clk_ctrl_os.h" +#include "esp_clk_tree.h" +#include "esp_attr.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define LEDC_TIMER_DIV_NUM_MAX (0x3FFFF) + +#define LEDC_IS_DIV_INVALID(div) ((div) <= LEDC_LL_FRACTIONAL_MAX || \ + (div) > LEDC_TIMER_DIV_NUM_MAX) + +/* Precision degree only affects RC_FAST, other clock sources' frequences are + * fixed values. For targets that do not support RC_FAST calibration, can + * only use its approximate value. + */ + +#if SOC_CLK_RC_FAST_SUPPORT_CALIBRATION +# define LEDC_CLK_SRC_FREQ_PRECISION ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED +#else +# define LEDC_CLK_SRC_FREQ_PRECISION ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX +#endif + +/* All chips have 4 internal timers */ + +#define LEDC_TIMERS (4) + +/* If PWM multi-channel is disabled, then only one channel is supported per + * timer. Thus, the number of channels is the same as the number of timers. + * Note that we only support the maximum of 6 PWM channels. + */ + +#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1 +# define LEDC_CHANNELS SOC_LEDC_CHANNEL_NUM +#else +# define LEDC_CHANNELS LEDC_TIMERS +#endif + +/* LEDC timer0 channels and offset */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER0 +# if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1 +# define LEDC_TIM0_CHANS CONFIG_ESPRESSIF_LEDC_TIMER0_CHANNELS +# else +# define LEDC_TIM0_CHANS (1) +# endif +# define LEDC_TIM0_CHANS_OFF (0) +#endif + +/* LEDC timer1 channels and offset */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER1 +# if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1 +# define LEDC_TIM1_CHANS CONFIG_ESPRESSIF_LEDC_TIMER1_CHANNELS +# else +# define LEDC_TIM1_CHANS (1) +# endif +# define LEDC_TIM1_CHANS_OFF (LEDC_TIM0_CHANS_OFF + LEDC_TIM0_CHANS) +#endif + +/* LEDC timer2 channels and offset */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER2 +# if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1 +# define LEDC_TIM2_CHANS CONFIG_ESPRESSIF_LEDC_TIMER2_CHANNELS +# else +# define LEDC_TIM2_CHANS (1) +# endif +# define LEDC_TIM2_CHANS_OFF (LEDC_TIM1_CHANS_OFF + LEDC_TIM1_CHANS) +#endif + +/* LEDC timer3 channels and offset */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER3 +# if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1 +# define LEDC_TIM3_CHANS CONFIG_ESPRESSIF_LEDC_TIMER3_CHANNELS +# else +# define LEDC_TIM3_CHANS (1) +# endif +# define LEDC_TIM3_CHANS_OFF (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS) +#endif + +/* Unititialized LEDC timer clock */ + +#define LEDC_SLOW_CLK_UNINIT (-1) + +/* Clock not found */ + +#define LEDC_CLK_NOT_FOUND (0) + +/* LEDC keep config */ + +#define LEDC_VAL_NO_CHANGE (-1) + +/* LEDC Timer default frequency */ + +#define LEDC_DEFAULT_FREQ (1000) + +/* Check max LEDC channels number */ + +#if CONFIG_ESPRESSIF_LEDC_TIMER0_CHANNELS + \ + CONFIG_ESPRESSIF_LEDC_TIMER1_CHANNELS + \ + CONFIG_ESPRESSIF_LEDC_TIMER2_CHANNELS + \ + CONFIG_ESPRESSIF_LEDC_TIMER3_CHANNELS > LEDC_CHANNELS +# error "Too many LEDC channels. The maximum number of channels used " \ + "by all timers is 6 when multi-channel PWM is enabled and " \ + "4 when multi-channel PWM is disabled." +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* LEDC timer channel configuration */ + +struct esp_ledc_chan_s +{ + const uint8_t num; /* PWM channel ID */ + const uint8_t pin; /* PWM channel GPIO pin number */ + + uint32_t duty; /* PWM channel current duty */ +}; + +/* This structure represents the state of one LEDC timer */ + +struct esp_ledc_s +{ + const struct pwm_ops_s *ops; /* PWM operations */ + + const uint8_t timer; /* Timer ID */ + + const uint8_t channels; /* Timer channels number */ + struct esp_ledc_chan_s *chans; /* Timer channels pointer */ + + ledc_timer_bit_t duty_resolution; /* Timer duty resolution */ + ledc_clk_cfg_t clk_cfg; /* Timer clock configuration */ + uint32_t frequency; /* Timer current frequency */ + uint32_t reload; /* Timer current reload */ +}; + +struct ledc_obj_s +{ + ledc_hal_context_t ledc_hal; /* LEDC hal context */ + ledc_slow_clk_sel_t glb_clk; /* LEDC global clock selection */ + bool timer_is_stopped[LEDC_TIMER_MAX]; /* Indicates whether each timer has been stopped */ + bool glb_clk_is_acquired[LEDC_TIMER_MAX]; /* Tracks whether the global clock is being acquired by each timer */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#if 0 /* Should be added when sleep is supported */ +extern void esp_sleep_periph_use_8m(bool use_or_not); +#endif + +static bool ledc_ctx_create(void); +static bool ledc_slow_clk_calibrate(void); +static uint32_t ledc_calculate_divisor(uint32_t src_clk_freq, + int freq_hz, + uint32_t precision); + +static uint32_t ledc_auto_global_clk_div(int freq_hz, + uint32_t precision, + ledc_slow_clk_sel_t *clk_target); + +static uint32_t ledc_auto_clk_divisor(int freq_hz, + uint32_t precision, + ledc_clk_src_t *clk_source, + ledc_slow_clk_sel_t *clk_target); + +static int ledc_timer_set(ledc_timer_t timer_sel, + uint32_t clock_divider, + uint32_t duty_resolution, + ledc_clk_src_t clk_src); + +static int ledc_set_timer_div(ledc_timer_t timer_num, + ledc_clk_cfg_t clk_cfg, + int freq_hz, + int duty_resolution); + +static int ledc_timer_resume(ledc_timer_t timer_sel); +static int ledc_timer_rst(ledc_timer_t timer_sel); +static int ledc_timer_pause(ledc_timer_t timer_sel); +static int setup_timer(struct esp_ledc_s *priv); +static int ledc_duty_config(ledc_channel_t channel, + int hpoint_val, + int duty_val, + ledc_duty_direction_t duty_direction, + uint32_t duty_num, + uint32_t duty_cycle, + uint32_t duty_scale); + +static int ledc_set_duty_with_hpoint(ledc_channel_t channel, + uint32_t duty, + uint32_t hpoint); + +static int ledc_channel_output_enable(ledc_channel_t channel); +static int ledc_channel_output_disable(ledc_channel_t channel); +static int ledc_update_duty(ledc_channel_t channel); +static uint32_t ledc_duty_bin_conversion(uint16_t nuttx_duty, + uint32_t duty_resolution); + +static int ledc_bind_channel_timer(ledc_channel_t channel, + ledc_timer_t timer_sel); + +static void setup_channel(struct esp_ledc_s *priv, int cn); +static int pwm_setup(struct pwm_lowerhalf_s *dev); +static int pwm_shutdown(struct pwm_lowerhalf_s *dev); +static int pwm_start(struct pwm_lowerhalf_s *dev, + const struct pwm_info_s *info); + +static int pwm_stop(struct pwm_lowerhalf_s *dev); +static int pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* LEDC context */ + +static struct ledc_obj_s *p_ledc_obj = NULL; + +/* LEDC available timer global clocks */ + +static const ledc_slow_clk_sel_t s_glb_clks[] = LEDC_LL_GLOBAL_CLOCKS; + +/* Current RC_FAST frequency */ + +static uint32_t s_ledc_slow_clk_rc_fast_freq = 0; + +/* LEDC PWM operations */ + +static const struct pwm_ops_s g_pwmops = +{ + .setup = pwm_setup, + .shutdown = pwm_shutdown, + .start = pwm_start, + .stop = pwm_stop, + .ioctl = pwm_ioctl +}; + +/* LEDC channels table */ + +static struct esp_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] = +{ + { + .num = 0, + .pin = CONFIG_ESPRESSIF_LEDC_CHANNEL0_PIN + }, + { + .num = 1, + .pin = CONFIG_ESPRESSIF_LEDC_CHANNEL1_PIN + }, + { + .num = 2, + .pin = CONFIG_ESPRESSIF_LEDC_CHANNEL2_PIN + }, + { + .num = 3, + .pin = CONFIG_ESPRESSIF_LEDC_CHANNEL3_PIN + }, +#if LEDC_CHANNELS > 4 + { + .num = 4, + .pin = CONFIG_ESPRESSIF_LEDC_CHANNEL4_PIN + }, + { + .num = 5, + .pin = CONFIG_ESPRESSIF_LEDC_CHANNEL5_PIN + } +#endif +}; + +/* LEDC timer0 private data */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER0 + +# if CONFIG_ESPRESSIF_LEDC_TIMER0_RESOLUTION > SOC_LEDC_TIMER_BIT_WIDTH +# error "Invalid PWM Timer 0 resolution." +# endif + +static struct esp_ledc_s g_pwm0dev = +{ + .ops = &g_pwmops, + .timer = 0, + .channels = LEDC_TIM0_CHANS, + .chans = &g_ledc_chans[LEDC_TIM0_CHANS_OFF], + .duty_resolution = CONFIG_ESPRESSIF_LEDC_TIMER0_RESOLUTION, + .clk_cfg = LEDC_AUTO_CLK, + .frequency = LEDC_DEFAULT_FREQ +}; +#endif /* CONFIG_ESPRESSIF_LEDC_TIMER0 */ + +/* LEDC timer1 private data */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER1 + +# if CONFIG_ESPRESSIF_LEDC_TIMER1_RESOLUTION > SOC_LEDC_TIMER_BIT_WIDTH +# error "Invalid PWM Timer 1 resolution." +# endif + +static struct esp_ledc_s g_pwm1dev = +{ + .ops = &g_pwmops, + .timer = 1, + .channels = LEDC_TIM1_CHANS, + .chans = &g_ledc_chans[LEDC_TIM1_CHANS_OFF], + .duty_resolution = CONFIG_ESPRESSIF_LEDC_TIMER1_RESOLUTION, + .clk_cfg = LEDC_AUTO_CLK, + .frequency = LEDC_DEFAULT_FREQ +}; +#endif /* CONFIG_ESPRESSIF_LEDC_TIMER1 */ + +/* LEDC timer2 private data */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER2 + +# if CONFIG_ESPRESSIF_LEDC_TIMER2_RESOLUTION > SOC_LEDC_TIMER_BIT_WIDTH +# error "Invalid PWM Timer 2 resolution." +# endif + +static struct esp_ledc_s g_pwm2dev = +{ + .ops = &g_pwmops, + .timer = 2, + .channels = LEDC_TIM2_CHANS, + .chans = &g_ledc_chans[LEDC_TIM2_CHANS_OFF], + .duty_resolution = CONFIG_ESPRESSIF_LEDC_TIMER2_RESOLUTION, + .clk_cfg = LEDC_AUTO_CLK, + .frequency = LEDC_DEFAULT_FREQ +}; +#endif /* CONFIG_ESPRESSIF_LEDC_TIMER2 */ + +/* LEDC timer3 private data */ + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER3 + +# if CONFIG_ESPRESSIF_LEDC_TIMER3_RESOLUTION > SOC_LEDC_TIMER_BIT_WIDTH +# error "Invalid PWM Timer 3 resolution." +# endif + +static struct esp_ledc_s g_pwm3dev = +{ + .ops = &g_pwmops, + .timer = 3, + .channels = LEDC_TIM3_CHANS, + .chans = &g_ledc_chans[LEDC_TIM3_CHANS_OFF], + .duty_resolution = CONFIG_ESPRESSIF_LEDC_TIMER3_RESOLUTION, + .clk_cfg = LEDC_AUTO_CLK, + .frequency = LEDC_DEFAULT_FREQ +}; +#endif /* CONFIG_ESPRESSIF_LEDC_TIMER3 */ + +/**************************************************************************** + * Private functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ledc_ctx_create + * + * Description: + * Create LEDC context. + * + * Input Parameters: + * None. + * + * Returned Value: + * True if context was created, false otherwise. + * + ****************************************************************************/ + +static bool ledc_ctx_create(void) +{ + bool new_ctx = false; + irqstate_t flags; + + flags = enter_critical_section(); + + if (!p_ledc_obj) + { + struct ledc_obj_s *ledc_new_mode_obj; + ledc_new_mode_obj = (struct ledc_obj_s *) + kmm_calloc(1, sizeof(struct ledc_obj_s)); + if (ledc_new_mode_obj) + { + new_ctx = true; + + /* Only ESP32 supports High Speed mode */ + + ledc_hal_init(&(ledc_new_mode_obj->ledc_hal), LEDC_LOW_SPEED_MODE); + ledc_new_mode_obj->glb_clk = LEDC_SLOW_CLK_UNINIT; + p_ledc_obj = ledc_new_mode_obj; + periph_module_enable(PERIPH_LEDC_MODULE); + } + } + + leave_critical_section(flags); + + return new_ctx; +} + +/**************************************************************************** + * Name: ledc_slow_clk_calibrate + * + * Description: + * Calibrate RC_FAST clock. + * + * Input Parameters: + * None. + * + * Returned Value: + * True if calibration was successful, false otherwise. + * + ****************************************************************************/ + +static bool ledc_slow_clk_calibrate(void) +{ + if (periph_rtc_dig_clk8m_enable()) + { + s_ledc_slow_clk_rc_fast_freq = periph_rtc_dig_clk8m_get_freq(); +#if !SOC_CLK_RC_FAST_SUPPORT_CALIBRATION + pwminfo("Calibration cannot be performed." + "Approximate RC_FAST_CLK : %"PRIu32" Hz\n", + s_ledc_slow_clk_rc_fast_freq); +#else + pwminfo("Calibrate RC_FAST_CLK : %"PRIu32" Hz", + s_ledc_slow_clk_rc_fast_freq); +#endif + return true; + } + + pwmerr("Calibrate RC_FAST_CLK failed\n"); + return false; +} + +/**************************************************************************** + * Name: ledc_calculate_divisor + * + * Description: + * Calculate the divisor to achieve the desired frequency. + * + * Input Parameters: + * src_clk_freq - The source clock frequency. + * freq_hz - The desired frequency. + * precision - The granularity of the clock. + * + * Returned Value: + * The divisor parameter to use to achieve the frequency requested. + * + ****************************************************************************/ + +static inline uint32_t ledc_calculate_divisor(uint32_t src_clk_freq, + int freq_hz, + uint32_t precision) +{ + /* In order to find the right divisor, we need to divide the source clock + * frequency by the desired frequency. However, two things to note here: + * - The lowest LEDC_LL_FRACTIONAL_BITS bits of the result are the + * FRACTIONAL part. The higher bits represent the integer part, this is + * why we need to right shift the source frequency. + * - The `precision` parameter represents the granularity of the clock. It + * **must** be a power of 2. It means that the resulted divisor is + * a multiplier of `precision`. + * + * Let's take a concrete example, we need to generate a 5KHz clock out of + * a 80MHz clock (APB). + * If the precision is 1024 (10 bits), the resulted multiplier is: + * (80000000 << 8) / (5000 * 1024) = 4000 (0xfa0) + * Let's ignore the fractional part to simplify the explanation, so we get + * a result of 15 (0xf). + * This can be interpreted as: every 15 "precision" ticks, the resulted + * clock will go high, where one precision tick is made out of 1024 source + * clock ticks. + * Thus, every `15 * 1024` source clock ticks, the resulted clock will go + * high. + * + * NOTE: We are also going to round up the value when necessary, thanks to: + * (freq_hz * precision) / 2 + */ + + return (((uint64_t)src_clk_freq << LEDC_LL_FRACTIONAL_BITS) + \ + ((freq_hz * precision) / 2)) / (freq_hz * precision); +} + +/**************************************************************************** + * Name: ledc_auto_global_clk_div + * + * Description: + * Try to find the clock with its divisor giving the frequency requested + * by the caller. + * + * Input Parameters: + * freq_hz - Frequency to achieve; + * precision - Precision of the frequency to achieve; + * clk_target - Clock target to use. + * + * Returned Value: + * The divisor parameter to use to achieve the frequency requested. + * + ****************************************************************************/ + +static uint32_t ledc_auto_global_clk_div(int freq_hz, + uint32_t precision, + ledc_slow_clk_sel_t *clk_target) +{ + uint32_t ret = LEDC_CLK_NOT_FOUND; + uint32_t clk_freq = 0; + + /* This function will go through all the following clock sources to look + * for a valid divisor which generates the requested frequency. + */ + + for (int i = 0; i < nitems(s_glb_clks); i++) + { + /* Before calculating the divisor, we need to have the RC_FAST + * frequency. If it hasn't been measured yet, try calibrating + * it now. + */ + + if (s_glb_clks[i] == LEDC_SLOW_CLK_RC_FAST && + s_ledc_slow_clk_rc_fast_freq == 0 && + !ledc_slow_clk_calibrate()) + { + pwminfo("Unable to retrieve RC_FAST clock frequency, skipping it"); + continue; + } + + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)s_glb_clks[i], + LEDC_CLK_SRC_FREQ_PRECISION, + &clk_freq); + + uint32_t div_param = ledc_calculate_divisor(clk_freq, + freq_hz, + precision); + + /* If the divisor is valid, we can return this value. */ + + if (!LEDC_IS_DIV_INVALID(div_param)) + { + *clk_target = s_glb_clks[i]; + ret = div_param; + break; + } + } + + return ret; +} + +/**************************************************************************** + * Name: ledc_auto_clk_divisor + * + * Description: + * Try to find the clock with its divisor giving the frequency requested + * by the caller. + * + * Input Parameters: + * freq_hz - Frequency to achieve; + * precision - Precision of the frequency to achieve; + * clk_source - Clock source to use; + * clk_target - Clock target to use. + * + * Returned Value: + * The divisor parameter to use to achieve the frequency requested. + * + ****************************************************************************/ + +static uint32_t ledc_auto_clk_divisor(int freq_hz, + uint32_t precision, + ledc_clk_src_t *clk_source, + ledc_slow_clk_sel_t *clk_target) +{ + uint32_t ret = LEDC_CLK_NOT_FOUND; + + uint32_t div_param_global = ledc_auto_global_clk_div(freq_hz, + precision, + clk_target); + + if (div_param_global != LEDC_CLK_NOT_FOUND) + { + pwminfo("Found a global clock source for frequency %d Hz " + "and precision 0x%"PRIx32"\n", freq_hz, precision); + *clk_source = LEDC_SCLK; + ret = div_param_global; + } + else + { + pwminfo("Could not find a global clock source for frequency %d Hz " + "and precision 0x%"PRIx32"\n", freq_hz, precision); + } + + return ret; +} + +/**************************************************************************** + * Name: ledc_timer_set + * + * Description: + * Set LEDC timer. + * + * Input Parameters: + * timer_sel - Select the timer to be set; + * clock_divider - The divider of the clock source; + * duty_resolution- The duty resolution of the timer; + * clk_src - The clock source of the timer; + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int ledc_timer_set(ledc_timer_t timer_sel, + uint32_t clock_divider, + uint32_t duty_resolution, + ledc_clk_src_t clk_src) +{ + DEBUGASSERT(timer_sel < LEDC_TIMER_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + ledc_hal_set_clock_divider(&(p_ledc_obj->ledc_hal), + timer_sel, + clock_divider); + + ledc_hal_set_duty_resolution(&(p_ledc_obj->ledc_hal), + timer_sel, + duty_resolution); + + ledc_hal_ls_timer_update(&(p_ledc_obj->ledc_hal), timer_sel); + + leave_critical_section(flags); + + return OK; +} + +/**************************************************************************** + * Name: ledc_set_timer_div + * + * Description: + * Set LEDC timer divider. + * + * Input Parameters: + * timer_num - LEDC timer number; + * clk_cfg - LEDC timer clock source; + * freq_hz - LEDC timer frequency; + * duty_resolution - LEDC timer duty resolution. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int ledc_set_timer_div(ledc_timer_t timer_num, + ledc_clk_cfg_t clk_cfg, + int freq_hz, + int duty_resolution) +{ + irqstate_t flags; + uint32_t div_param = 0; + int i; + const uint32_t precision = (0x1 << duty_resolution); + + /* The clock sources are not initialized on purpose. To produce compiler + * warning if used but the selector functions don't set them properly. + */ + + /* Timer-specific mux. Set to timer-specific clock or LEDC_SCLK if a global + * clock is used. + */ + + ledc_clk_src_t timer_clk_src; + + /* Global clock mux. Should be set when LEDC_SCLK is used in + * LOW_SPEED_MODE. Otherwise left uninitialized. + */ + + ledc_slow_clk_sel_t glb_clk = LEDC_SLOW_CLK_UNINIT; + + if (clk_cfg == LEDC_AUTO_CLK) + { + /* User hasn't specified the speed, we should try to guess it. */ + + pwminfo("Using auto clock source selection\n"); + + div_param = ledc_auto_clk_divisor(freq_hz, + precision, + &timer_clk_src, + &glb_clk); + } + else if (clk_cfg == LEDC_USE_RC_FAST_CLK) + { + pwminfo("Using RC_FAST clock source\n"); + + /* Before calculating the divisor, we need to have the RC_FAST + * frequency. If it hasn't been measured yet, try calibrating + * it now. + */ + + if (s_ledc_slow_clk_rc_fast_freq == 0 && + ledc_slow_clk_calibrate() == false) + { + goto error; + } + + /* Set the global clock source */ + + timer_clk_src = LEDC_SCLK; + glb_clk = LEDC_SLOW_CLK_RC_FAST; + + /* We have the RC_FAST clock frequency now. */ + + div_param = ledc_calculate_divisor(s_ledc_slow_clk_rc_fast_freq, + freq_hz, + precision); + + if (LEDC_IS_DIV_INVALID(div_param)) + { + div_param = LEDC_CLK_NOT_FOUND; + } + } + else + { + timer_clk_src = LEDC_SCLK; + glb_clk = (ledc_slow_clk_sel_t)clk_cfg; + + uint32_t src_clk_freq = 0; + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_cfg, + LEDC_CLK_SRC_FREQ_PRECISION, + &src_clk_freq); + + div_param = ledc_calculate_divisor(src_clk_freq, freq_hz, precision); + + if (LEDC_IS_DIV_INVALID(div_param)) + { + div_param = LEDC_CLK_NOT_FOUND; + } + } + + if (div_param == LEDC_CLK_NOT_FOUND) + { + goto error; + } + + pwminfo("Using clock source %d (in slow mode). Divisor: 0x%"PRIx32, + timer_clk_src, + div_param); + + /* The following block configures the global clock. + * Arriving here, variable glb_clk must have been assigned to one of the + * ledc_slow_clk_sel_t enum values + */ + + ASSERT(timer_clk_src == LEDC_SCLK); + ASSERT(glb_clk != LEDC_SLOW_CLK_UNINIT); + + flags = enter_critical_section(); + + if (p_ledc_obj->glb_clk != LEDC_SLOW_CLK_UNINIT && + p_ledc_obj->glb_clk != glb_clk) + { + for (i = 0; i < LEDC_TIMER_MAX; i++) + { + if (i != timer_num && p_ledc_obj->glb_clk_is_acquired[i]) + { + leave_critical_section(flags); + pwmerr("Timer clock conflict. Already is %d but attempt to %d", + p_ledc_obj->glb_clk, + glb_clk); + return -EINVAL; + } + } + } + + p_ledc_obj->glb_clk_is_acquired[timer_num] = true; + if (p_ledc_obj->glb_clk != glb_clk) + { + p_ledc_obj->glb_clk = glb_clk; + ledc_hal_set_slow_clk_sel(&(p_ledc_obj->ledc_hal), glb_clk); + } + + leave_critical_section(flags); + + pwminfo("In slow speed mode. Global clock: %d", glb_clk); + + /* Keep ESP_PD_DOMAIN_RC_FAST on during light sleep */ + +#if 0 /* Should be added when sleep is supported */ +#ifndef CONFIG_ESPRESSIF_ESP32H2 /* TODO: Remove when H2 light sleep is supported */ + esp_sleep_periph_use_8m(glb_clk == LEDC_SLOW_CLK_RC_FAST); +#endif +#endif + + /* The divisor is correct, we can write in the hardware. */ + + ledc_timer_set(timer_num, div_param, duty_resolution, timer_clk_src); + return OK; + +error: + pwmerr("Requested frequency and duty resolution can not be achieved. " + "Try reducing the frequency or timer resolution.\n"); + + return -EINVAL; +} + +/**************************************************************************** + * Name: ledc_timer_resume + * + * Description: + * Resume a LEDC timer. + * + * Input Parameters: + * timer_sel - LEDC timer id. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int ledc_timer_resume(ledc_timer_t timer_sel) +{ + DEBUGASSERT(timer_sel < LEDC_TIMER_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + p_ledc_obj->timer_is_stopped[timer_sel] = false; + ledc_hal_timer_resume(&(p_ledc_obj->ledc_hal), timer_sel); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: ledc_timer_rst + * + * Description: + * Reset a LEDC timer. + * + * Input Parameters: + * timer_sel - LEDC timer id. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int ledc_timer_rst(ledc_timer_t timer_sel) +{ + DEBUGASSERT(timer_sel < LEDC_TIMER_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + ledc_hal_timer_rst(&(p_ledc_obj->ledc_hal), timer_sel); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: ledc_timer_pause + * + * Description: + * Pause a LEDC timer. + * + * Input Parameters: + * timer_sel - LEDC timer id. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int ledc_timer_pause(ledc_timer_t timer_sel) +{ + DEBUGASSERT(timer_sel < LEDC_TIMER_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + p_ledc_obj->timer_is_stopped[timer_sel] = true; + ledc_hal_timer_pause(&(p_ledc_obj->ledc_hal), timer_sel); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: setup_timer + * + * Description: + * Setup LEDC timer frequency and reload. + * + * Input Parameters: + * priv - A reference to the LEDC timer state structure + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int setup_timer(struct esp_ledc_s *priv) +{ + DEBUGASSERT(priv != NULL); + DEBUGASSERT(priv->timer < LEDC_TIMER_MAX); + DEBUGASSERT(priv->frequency > 0); + DEBUGASSERT(priv->duty_resolution < LEDC_TIMER_BIT_MAX && + priv->duty_resolution > 0); + + int ret; + + if (!ledc_ctx_create() && !p_ledc_obj) + { + pwmerr("ERROR: No memory for LEDC context\n"); + PANIC(); + } + + ret = ledc_set_timer_div(priv->timer, + priv->clk_cfg, + priv->frequency, + priv->duty_resolution); + + if (ret == OK) + { + ledc_timer_pause(priv->timer); + ledc_timer_rst(priv->timer); + } + + return ret; +} + +/**************************************************************************** + * Name: ledc_duty_config + * + * Description: + * Set the duty cycle of a PWM channel. + * + * Input Parameters: + * channel - LEDC channel index. + * hpoint_val - LEDC channel hpoint value. + * duty_val - LEDC channel duty value. + * duty_direction - LEDC channel duty direction. + * duty_num - LEDC channel duty number. + * duty_cycle - LEDC channel duty cycle. + * + * Returned Value: + * OK. + * + ****************************************************************************/ + +static IRAM_ATTR int ledc_duty_config(ledc_channel_t channel, + int hpoint_val, + int duty_val, + ledc_duty_direction_t duty_direction, + uint32_t duty_num, + uint32_t duty_cycle, + uint32_t duty_scale) +{ + if (hpoint_val >= 0) + { + ledc_hal_set_hpoint(&(p_ledc_obj->ledc_hal), channel, hpoint_val); + } + + if (duty_val >= 0) + { + ledc_hal_set_duty_int_part(&(p_ledc_obj->ledc_hal), channel, duty_val); + } + + ledc_hal_set_duty_direction(&(p_ledc_obj->ledc_hal), + channel, + duty_direction); + + ledc_hal_set_duty_num(&(p_ledc_obj->ledc_hal), channel, duty_num); + ledc_hal_set_duty_cycle(&(p_ledc_obj->ledc_hal), channel, duty_cycle); + ledc_hal_set_duty_scale(&(p_ledc_obj->ledc_hal), channel, duty_scale); + +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + ledc_hal_set_duty_range_wr_addr(&(p_ledc_obj->ledc_hal), channel, 0); + ledc_hal_set_range_number(&(p_ledc_obj->ledc_hal), channel, 1); +#endif + return OK; +} + +/**************************************************************************** + * Name: ledc_set_duty_with_hpoint + * + * Description: + * Set the duty cycle of a PWM channel with a given hpoint value. + * + * Input Parameters: + * channel - LEDC channel index. + * duty - Duty cycle value. + * hpoint - Hpoint value. + * + * Returned Value: + * Ok on success; A negated errno value is returned on failure. + * + ****************************************************************************/ + +static int ledc_set_duty_with_hpoint(ledc_channel_t channel, + uint32_t duty, + uint32_t hpoint) +{ + DEBUGASSERT(channel < LEDC_CHANNEL_MAX); + DEBUGASSERT(hpoint <= LEDC_LL_HPOINT_VAL_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + ledc_duty_config(channel, hpoint, duty, LEDC_DUTY_DIR_INCREASE, 1, 1, 0); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: ledc_channel_output_enable + * + * Description: + * Enable LEDC channel output. + * + * Input Parameters: + * channel - LEDC channel index. + * + * Returned Value: + * Ok on success; A negated errno value is returned on failure. + * + ****************************************************************************/ + +static int ledc_channel_output_enable(ledc_channel_t channel) +{ + DEBUGASSERT(channel < LEDC_CHANNEL_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + ledc_hal_set_sig_out_en(&(p_ledc_obj->ledc_hal), channel, true); + ledc_hal_set_duty_start(&(p_ledc_obj->ledc_hal), channel, true); + + return OK; +} + +/**************************************************************************** + * Name: ledc_channel_output_disable + * + * Description: + * Disable LEDC channel output. + * + * Input Parameters: + * channel - LEDC channel index. + * + * Returned Value: + * Ok on success; A negated errno value is returned on failure. + * + ****************************************************************************/ + +static int ledc_channel_output_disable(ledc_channel_t channel) +{ + DEBUGASSERT(channel < LEDC_CHANNEL_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + ledc_hal_set_idle_level(&(p_ledc_obj->ledc_hal), channel, 0); + ledc_hal_set_sig_out_en(&(p_ledc_obj->ledc_hal), channel, false); + ledc_hal_set_duty_start(&(p_ledc_obj->ledc_hal), channel, false); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: ledc_update_duty + * + * Description: + * Update the duty cycle of a PWM channel. + * + * Input Parameters: + * channel - LEDC channel index. + * + * Returned Value: + * Ok on success; A negated errno value is returned on failure. + * + ****************************************************************************/ + +static int ledc_update_duty(ledc_channel_t channel) +{ + DEBUGASSERT(channel < LEDC_CHANNEL_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + ledc_channel_output_enable(channel); + + flags = enter_critical_section(); + ledc_hal_ls_channel_update(&(p_ledc_obj->ledc_hal), channel); + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: ledc_duty_bin_conversion + * + * Description: + * Truncate or expand the duty cycle value to fit the duty resolution. + * + * Input Parameters: + * nuttx_duty - Duty cycle value from nuttx. + * duty_resolution - Duty resolution of the channel. + * + * Returned Value: + * Truncated or expanded duty cycle value. + * + ****************************************************************************/ + +static uint32_t ledc_duty_bin_conversion(uint16_t nuttx_duty, + uint32_t duty_resolution) +{ + /* Calculate the maximum value based on the duty resolution */ + + uint32_t max_value = (1 << duty_resolution) - 1; + + /* Map the value to the specified range */ + + uint32_t mapped_value = (nuttx_duty * max_value) / UINT16_MAX; + + return mapped_value; +} + +/**************************************************************************** + * Name: ledc_bind_channel_timer + * + * Description: + * Bind a LEDC channel to a LEDC timer. + * + * Input Parameters: + * channel - LEDC channel index. + * timer_sel - LEDC timer index. + * + * Returned Value: + * Ok on success; A negated errno value is returned on failure. + * + ****************************************************************************/ + +static int ledc_bind_channel_timer(ledc_channel_t channel, + ledc_timer_t timer_sel) +{ + DEBUGASSERT(timer_sel < LEDC_TIMER_MAX); + + if (p_ledc_obj == NULL) + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + irqstate_t flags; + + flags = enter_critical_section(); + + ledc_hal_bind_channel_timer(&(p_ledc_obj->ledc_hal), channel, timer_sel); + ledc_hal_ls_channel_update(&(p_ledc_obj->ledc_hal), channel); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: setup_channel + * + * Description: + * Setup LEDC timer channel duty. + * + * Input Parameters: + * priv - A reference to the LEDC timer state structure + * cn - Timer channel number + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void setup_channel(struct esp_ledc_s *priv, int cn) +{ + irqstate_t flags; + bool new_ctx_created; + struct esp_ledc_chan_s *chan = &priv->chans[cn]; + + new_ctx_created = ledc_ctx_create(); + if (!new_ctx_created && !p_ledc_obj) + { + pwmerr("ERROR: No memory for LEDC context\n"); + PANIC(); + } +#ifndef CONFIG_ESPRESSIF_ESP32H2 + /* On such targets, the default ledc core(global) clock does not connect to + * any clock source. Setting channel configurations and updating bits + * before core clock is enabled could lead to an error. + * Therefore, we should connect the core clock to a real clock source to + * enable it before any ledc register operation happens. + * It can be switched to the other desired clock sources to meet the output + * PWM frequency requirements later at timer configuration. + * So we consider the glb_clk still as LEDC_SLOW_CLK_UNINIT. + */ + + else if (new_ctx_created) + { + flags = enter_critical_section(); + + if (p_ledc_obj->glb_clk == LEDC_SLOW_CLK_UNINIT) + { + ledc_hal_set_slow_clk_sel(&(p_ledc_obj->ledc_hal), + LEDC_LL_GLOBAL_CLK_DEFAULT); + } + + leave_critical_section(flags); + } +#endif + + ledc_set_duty_with_hpoint(chan->num, + chan->duty, + CONFIG_ESPRESSIF_LEDC_HPOINT); + + ledc_bind_channel_timer(chan->num, priv->timer); + + flags = enter_critical_section(); + ledc_hal_set_fade_end_intr(&(p_ledc_obj->ledc_hal), chan->num, false); + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: pwm_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_setup(struct pwm_lowerhalf_s *dev) +{ + struct esp_ledc_s *priv = (struct esp_ledc_s *)dev; + int ret; + + pwminfo("Initializing PWM Timer %d with default frequency of %d Hz\n", + priv->timer, + LEDC_DEFAULT_FREQ); + + ret = setup_timer(priv); + + if (ret != OK) + { + pwmerr("ERROR: Failed to setup timer\n"); + return ret; + } + + /* Setup channel GPIO pins */ + + for (int i = 0; i < priv->channels; i++) + { + setup_channel(priv, i); + + pwminfo("Channel %d mapped to pin %d\n", priv->chans[i].num, + priv->chans[i].pin); + + esp_configgpio(priv->chans[i].pin, OUTPUT | PULLUP); + esp_gpio_matrix_out(priv->chans[i].pin, + LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num, + 0, 0); + } + + return OK; +} + +/**************************************************************************** + * Name: pwm_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_shutdown(struct pwm_lowerhalf_s *dev) +{ + struct esp_ledc_s *priv = (struct esp_ledc_s *)dev; +#ifdef CONFIG_PWM_NCHANNELS + int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS); +#else + int channels = 1; +#endif + + pwminfo("Shutting down PWM Timer %d\n", priv->timer); + + /* Stop timer */ + + pwm_stop(dev); + + /* Clear timer and channel configuration */ + + priv->frequency = LEDC_DEFAULT_FREQ; + + for (int i = 0; i < channels; i++) + { + priv->chans[i].duty = 0; + } + + if (p_ledc_obj != NULL) + { + periph_module_disable(PERIPH_LEDC_MODULE); + kmm_free(p_ledc_obj); + p_ledc_obj = NULL; + s_ledc_slow_clk_rc_fast_freq = 0; + } + else + { + pwmerr("ERROR: LEDC not initialized\n"); + return -ENODEV; + } + + return 0; +} + +/**************************************************************************** + * Name: pwm_start + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_start(struct pwm_lowerhalf_s *dev, + const struct pwm_info_s *info) +{ + uint32_t duty; + int ret = OK; + irqstate_t flags; + struct esp_ledc_s *priv = (struct esp_ledc_s *)dev; +#ifdef CONFIG_PWM_NCHANNELS + int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS); +#else + int channels = 1; +#endif + + /* Update timer with given PWM timer frequency */ + + if (priv->frequency != info->frequency) + { + pwminfo("Setting Timer %" PRIu8 + " to frequency %" PRIu32 + " Hz using %" PRIu8 " bits of resolution\n", + priv->timer, + info->frequency, + priv->duty_resolution); + + ret |= ledc_set_timer_div(priv->timer, + priv->clk_cfg, + info->frequency, + priv->duty_resolution); + + ret |= ledc_timer_rst(priv->timer); + + if (ret != OK) + { + pwmerr("ERROR: Failed to set timer configuration\n"); + return ret; + } + + pwminfo("Timer %d successfully configured\n", priv->timer); + priv->frequency = info->frequency; + } + + /* Update timer with given PWM channel duty */ + + for (int i = 0; i < channels; i++) + { +#ifdef CONFIG_PWM_NCHANNELS + duty = ledc_duty_bin_conversion(info->channels[i].duty, + priv->duty_resolution); +#else + duty = ledc_duty_bin_conversion(info[i].duty, + priv->duty_resolution); +#endif + if (priv->chans[i].duty != duty) + { + uint32_t max_value = (1 << priv->duty_resolution) - 1; + pwminfo("Setting PWM channel %" PRIu8 + " to duty cycle %" PRIu32 "(%0.4f)\n", + priv->chans[i].num, + duty, + (float)duty / max_value); + + flags = enter_critical_section(); + + ret |= ledc_duty_config(priv->chans[i].num, + LEDC_VAL_NO_CHANGE, + duty, + LEDC_DUTY_DIR_INCREASE, + 1, + 1, + 0); + + leave_critical_section(flags); + ret |= ledc_update_duty(priv->chans[i].num); + + if (ret != OK) + { + pwmerr("ERROR: Failed to set channel configuration\n"); + return ret; + } + + pwminfo("Channel %d successfully configured\n", + priv->chans[i].num); + + priv->chans[i].duty = duty; + } + + ledc_channel_output_enable(priv->chans[i].num); + } + + ledc_timer_resume(priv->timer); + + return 0; +} + +/**************************************************************************** + * Name: pwm_stop + * + * Description: + * Stop the pulsed output and reset the timer resources. + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_stop(struct pwm_lowerhalf_s *dev) +{ + int i; + int channel; + struct esp_ledc_s *priv = (struct esp_ledc_s *)dev; + + pwminfo("Stopping PWM Timer %d\n", priv->timer); + + for (i = 0; i < priv->channels; i++) + { + ledc_channel_output_disable(priv->chans[i].num); + } + + ledc_timer_pause(priv->timer); + ledc_timer_rst(priv->timer); + + return 0; +} + +/**************************************************************************** + * Name: pwm_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_PWM_INFO + struct esp_ledc_s *priv = (struct esp_ledc_s *)dev; + + pwminfo("PWM Timer %d\n", priv->timer); +#endif + + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_ledc_init + * + * Description: + * Initialize one LEDC timer for use with the upper_level PWM driver. + * + * Input Parameters: + * timer - A number identifying the timer use. + * + * Returned Value: + * On success, a pointer to the ESP32-C3 LEDC lower half PWM driver is + * returned. NULL is returned on any failure. + * + ****************************************************************************/ + +struct pwm_lowerhalf_s *esp_ledc_init(int timer) +{ + struct esp_ledc_s *lower = NULL; + + pwminfo("PWM Timer %u initialized\n", timer); + + switch (timer) + { +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER0 + case 0: + { + lower = &g_pwm0dev; + break; + } +#endif + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER1 + case 1: + { + lower = &g_pwm1dev; + break; + } +#endif + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER2 + case 2: + { + lower = &g_pwm2dev; + break; + } +#endif + +#ifdef CONFIG_ESPRESSIF_LEDC_TIMER3 + case 3: + { + lower = &g_pwm3dev; + break; + } +#endif + + default: + { + pwmerr("ERROR: No such timer configured %d\n", timer); + lower = NULL; + break; + } + } + + return (struct pwm_lowerhalf_s *)lower; +} diff --git a/arch/risc-v/src/espressif/esp_ledc.h b/arch/risc-v/src/espressif/esp_ledc.h new file mode 100644 index 0000000000..c5153c8c99 --- /dev/null +++ b/arch/risc-v/src/espressif/esp_ledc.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * arch/risc-v/src/espressif/esp_ledc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ARCH_RISCV_SRC_ESPRESSIF_ESP_LEDC_H +#define __ARCH_RISCV_SRC_ESPRESSIF_ESP_LEDC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <nuttx/timers/pwm.h> + +/**************************************************************************** + * Public functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_ledc_init + * + * Description: + * Initialize one LEDC timer for use with the upper_level PWM driver. + * + * Input Parameters: + * timer - A number identifying the timer use. + * + * Returned Value: + * On success, a pointer to the ESP LEDC lower half PWM driver is + * returned. NULL is returned on any failure. + * + ****************************************************************************/ + +struct pwm_lowerhalf_s *esp_ledc_init(int timer); + +#endif /* __ARCH_RISCV_SRC_ESPRESSIF_ESP_LEDC_H */ diff --git a/arch/risc-v/src/espressif/hal_esp32c3.mk b/arch/risc-v/src/espressif/hal_esp32c3.mk index 2fe17c718a..e0ac44ba84 100644 --- a/arch/risc-v/src/espressif/hal_esp32c3.mk +++ b/arch/risc-v/src/espressif/hal_esp32c3.mk @@ -40,6 +40,7 @@ INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/compone INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/include/private INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/public_compat +INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_timer/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/$(CHIP_SERIES)/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/platform_port/include @@ -65,6 +66,7 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/src/esp_efuse_utili CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_fields.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_table.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_utility.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/clk_ctrl_os.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/cpu.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/esp_clk.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/hw_random.c @@ -85,6 +87,8 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/soc/$(CHI CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/brownout_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/efuse_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/gpio_hal.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/ledc_hal.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/ledc_hal_iram.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/systimer_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/timer_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/timer_hal_iram.c @@ -97,3 +101,4 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/log/log.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/log/log_noos.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/riscv/interrupt.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/soc/$(CHIP_SERIES)/gpio_periph.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/soc/$(CHIP_SERIES)/ledc_periph.c diff --git a/arch/risc-v/src/espressif/hal_esp32c6.mk b/arch/risc-v/src/espressif/hal_esp32c6.mk index a50dee95f4..406aad1096 100644 --- a/arch/risc-v/src/espressif/hal_esp32c6.mk +++ b/arch/risc-v/src/espressif/hal_esp32c6.mk @@ -40,6 +40,7 @@ INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/compone INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/include/private INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/public_compat +INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_timer/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/$(CHIP_SERIES)/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/platform_port/include @@ -66,6 +67,7 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/src/esp_efuse_utili CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_fields.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_table.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_utility.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/clk_ctrl_os.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/cpu.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/esp_clk.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/hw_random.c @@ -90,6 +92,8 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/soc/$(CHI CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/brownout_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/efuse_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/gpio_hal.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/ledc_hal.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/ledc_hal_iram.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/timer_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/timer_hal_iram.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/uart_hal.c @@ -102,3 +106,4 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/log/log.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/log/log_noos.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/riscv/interrupt.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/soc/$(CHIP_SERIES)/gpio_periph.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/soc/$(CHIP_SERIES)/ledc_periph.c diff --git a/arch/risc-v/src/espressif/hal_esp32h2.mk b/arch/risc-v/src/espressif/hal_esp32h2.mk index 5c59436dfc..098ea47759 100644 --- a/arch/risc-v/src/espressif/hal_esp32h2.mk +++ b/arch/risc-v/src/espressif/hal_esp32h2.mk @@ -40,6 +40,7 @@ INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/compone INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/include/private INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_system/port/public_compat +INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_timer/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/$(CHIP_SERIES)/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/include INCLUDES += $(INCDIR_PREFIX)$(ARCH_SRCDIR)/chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/platform_port/include @@ -66,6 +67,7 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/src/esp_efuse_utili CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_fields.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_table.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/efuse/$(CHIP_SERIES)/esp_efuse_utility.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/clk_ctrl_os.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/cpu.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/esp_clk.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/esp_hw_support/hw_random.c @@ -90,6 +92,8 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/brownout_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/cache_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/efuse_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/gpio_hal.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/ledc_hal.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/ledc_hal_iram.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/timer_hal.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/timer_hal_iram.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/hal/uart_hal.c @@ -100,3 +104,4 @@ CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/log/log.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/log/log_noos.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/riscv/interrupt.c CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/soc/$(CHIP_SERIES)/gpio_periph.c +CHIP_CSRCS += chip/$(ESP_HAL_3RDPARTY_REPO)/components/soc/$(CHIP_SERIES)/ledc_periph.c