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