You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by GitBox <gi...@apache.org> on 2022/01/06 19:08:10 UTC

[GitHub] [incubator-nuttx] acassis opened a new pull request #5187: esp32: Add PWM support using the LEDC peripheral @acassis

acassis opened a new pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187


   ## Summary
   This patch add support to PWM for ESP32
   ## Impact
   User will be able to use PWM
   ## Testing
   ESP32-Devkitc
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] acassis commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
acassis commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r779845390



##########
File path: arch/xtensa/src/esp32/Kconfig
##########
@@ -324,9 +324,10 @@ config ESP32_I2S1
 		No yet implemented
 
 config ESP32_LEDC
-	bool "LED PWM (LEDC)"
+	bool "LEDC (PWM)"
 	default n
-	depends on EXPERIMENTAL
+	select PWM
+	select ARCH_HAVE_PWM_MULTICHAN
 	---help---
 		No yet implemented

Review comment:
       Hahaha, true! I forgot to remove the comment




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#issuecomment-1007995945


   LGTM!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780245216



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       @acassis do you plan to integrate this change?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780245434



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       @acassis do you plan to integrate this change?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#issuecomment-1007996610


   > @pkarashchenko LGTM ?
   
   I think it is fine to merge, but please address comments related to unsigned print specifier in the next commit


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r779828273



##########
File path: arch/xtensa/src/esp32/Kconfig
##########
@@ -324,9 +324,10 @@ config ESP32_I2S1
 		No yet implemented
 
 config ESP32_LEDC
-	bool "LED PWM (LEDC)"
+	bool "LEDC (PWM)"
 	default n
-	depends on EXPERIMENTAL
+	select PWM
+	select ARCH_HAVE_PWM_MULTICHAN
 	---help---
 		No yet implemented

Review comment:
       Probably now it is implemented :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] acassis commented on pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
acassis commented on pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#issuecomment-1007987892


   @pkarashchenko LGTM ?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] acassis commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
acassis commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780233682



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,812 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1

Review comment:
       Unfortunately we cannot remove this defined(CONFIG_PWM_NCHANNELS), otherwise it will generate a warning when PWM_NCHANNELS is not used:
   ```
   chip/esp32_ledc.c:52:5: warning: "CONFIG_PWM_NCHANNELS" is not defined, evaluates to 0 [-Wundef]
    #if CONFIG_PWM_NCHANNELS > 1
        ^~~~~~~~~~~~~~~~~~~~
   ```
   When "#if defined(CONFIG_PWM_NCHANNELS) && ..." is present the send part is not tested and the warning doesn't show up!

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,812 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1

Review comment:
       Unfortunately we cannot remove this defined(CONFIG_PWM_NCHANNELS), otherwise it will generate a warning when PWM_NCHANNELS is not used:
   ```
   chip/esp32_ledc.c:52:5: warning: "CONFIG_PWM_NCHANNELS" is not defined, evaluates to 0 [-Wundef]
    #if CONFIG_PWM_NCHANNELS > 1
        ^~~~~~~~~~~~~~~~~~~~
   ```
   When "#if defined(CONFIG_PWM_NCHANNELS) && ..." is present the second part is not tested and the warning doesn't show up!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r779832401



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)

Review comment:
       ```suggestion
     if (g_clk_ref == 0)
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)

Review comment:
       ```suggestion
     if (g_clk_ref == 0)
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+      case 2:
+        {

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+      case 2:
+        {
+          lower = &g_pwm2dev;
+          break;
+        }

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       ```suggestion
     pwminfo("PWM timer%u\n", priv->num);
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       ```suggestion
     pwminfo("PWM timer%u\n", priv->num);
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \

Review comment:
       ```suggestion
     pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+      case 2:
+        {
+          lower = &g_pwm2dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+      case 3:
+        {

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \

Review comment:
       ```suggestion
     pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%"
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+      case 2:
+        {
+          lower = &g_pwm2dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+      case 3:
+        {
+          lower = &g_pwm3dev;
+          break;
+        }

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+      case 2:
+        {
+          lower = &g_pwm2dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+      case 3:
+        {
+          lower = &g_pwm3dev;
+          break;
+        }
+#endif
+
+      default:
+        {

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+      case 1:
+        {
+          lower = &g_pwm1dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+      case 2:
+        {
+          lower = &g_pwm2dev;
+          break;
+        }
+#endif
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+      case 3:
+        {
+          lower = &g_pwm3dev;
+          break;
+        }
+#endif
+
+      default:
+        {
+          pwmerr("ERROR: No such timer configured %d\n", timer);
+          lower = NULL;
+          break;
+        }

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);
+
+  switch (timer)
+    {
+#ifdef CONFIG_ESP32_LEDC_TIM0
+      case 0:
+        {
+          lower = &g_pwm0dev;
+          break;
+        }

Review comment:
       ```suggestion
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       ```suggestion
     pwminfo("PWM timer%u\n", priv->num);
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  flags = enter_critical_section();
+
+  /* Stop timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PAUSE);
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  leave_critical_section(flags);
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+#endif
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32_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 LEDC lower half PWM driver is
+ *   returned. NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *esp32_ledc_init(int timer)
+{
+  struct esp32_ledc_s *lower = NULL;
+
+  pwminfo("TIM%u\n", timer);

Review comment:
       ```suggestion
     pwminfo("TIM%d\n", timer);
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne

Review comment:
       ```suggestion
    *   None
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne

Review comment:
       ```suggestion
    *   None
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       ```suggestion
     pwminfo("PWM timer%u\n", priv->num);
   ```
   

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,

Review comment:
       ```suggestion
         pwminfo("channel%u --> pin%u\n", priv->chans[i].num,
   ```
   




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] acassis commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
acassis commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780233682



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,812 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1

Review comment:
       Unfortunately we cannot remove this defined(CONFIG_PWM_NCHANNELS), otherwise it will generate a warning when PWM_NCHANNELS is not used:
   ```
   chip/esp32_ledc.c:52:5: warning: "CONFIG_PWM_NCHANNELS" is not defined, evaluates to 0 [-Wundef]
    #if CONFIG_PWM_NCHANNELS > 1
        ^~~~~~~~~~~~~~~~~~~~
   ```
   When "#if defined(CONFIG_PWM_NCHANNELS) && ..." is present the second part is not tested and the warning doesn't show up!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780111691



##########
File path: arch/xtensa/src/esp32/Kconfig
##########
@@ -1282,6 +1283,91 @@ config ESP32_FREERUN
 endmenu # Timer/counter Configuration
 endif # ESP32_TIMER
 
+menu "LEDC configuration"
+        depends on ESP32_LEDC

Review comment:
       ```suggestion
   	depends on ESP32_LEDC
   ```

##########
File path: arch/xtensa/src/esp32/Kconfig
##########
@@ -1282,6 +1283,91 @@ config ESP32_FREERUN
 endmenu # Timer/counter Configuration
 endif # ESP32_TIMER
 
+menu "LEDC configuration"
+        depends on ESP32_LEDC
+
+menuconfig ESP32_LEDC_TIM0
+        bool "Timer 0"
+        default n

Review comment:
       ```suggestion
   	bool "Timer 0"
   	default n
   ```

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,812 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1

Review comment:
       I think that next code should be enough
   ```suggestion
   #  if CONFIG_PWM_NCHANNELS > 1
   ```

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,

Review comment:
       @acassis do you plan to integrate this change?

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       @acassis do you plan to integrate this change?

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,
+              priv->chans[i].pin);
+
+      esp32_configgpio(priv->chans[i].pin, OUTPUT | PULLUP);
+      esp32_gpio_matrix_out(priv->chans[i].pin,
+                              LEDC_LS_SIG_OUT0_IDX + priv->chans[i].num,
+                              0, 0);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->reload    = 0;
+  for (int i = 0; i < channels; i++)
+    {
+      priv->chans[i].duty = 0;
+    }
+
+  ledc_disable_clk();
+
+  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)
+{
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+#ifdef CONFIG_PWM_NCHANNELS
+  int channels = MIN(priv->channels, CONFIG_PWM_NCHANNELS);
+#else
+  int channels = 1;
+#endif
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+      setup_timer(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+  for (int i = 0; i < channels; i++)
+    {
+#ifdef CONFIG_PWM_NCHANNELS
+      if (priv->chans[i].duty != info->channels[i].duty)
+#else
+      if (priv->chans[i].duty != info[i].duty)
+#endif
+        {
+#ifdef CONFIG_PWM_NCHANNELS
+          priv->chans[i].duty = info->channels[i].duty;
+#else
+          priv->chans[i].duty = info[i].duty;
+#endif
+          setup_channel(priv, i);
+        }
+    }
+
+  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)
+{
+  irqstate_t flags;
+  struct esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);

Review comment:
       @acassis do you plan to integrate this change?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] acassis commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
acassis commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780233682



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,812 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1

Review comment:
       Unfortunately we cannot remove this defined(CONFIG_PWM_NCHANNELS), otherwise it will generate a warning when PWM_NCHANNELS is not used:
   ```
   chip/esp32_ledc.c:52:5: warning: "CONFIG_PWM_NCHANNELS" is not defined, evaluates to 0 [-Wundef]
    #if CONFIG_PWM_NCHANNELS > 1
        ^~~~~~~~~~~~~~~~~~~~
   ```
   When "#if defined(CONFIG_PWM_NCHANNELS) && ..." is present the send part is not tested and the warning doesn't show up!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko merged pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko merged pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780111691



##########
File path: arch/xtensa/src/esp32/Kconfig
##########
@@ -1282,6 +1283,91 @@ config ESP32_FREERUN
 endmenu # Timer/counter Configuration
 endif # ESP32_TIMER
 
+menu "LEDC configuration"
+        depends on ESP32_LEDC

Review comment:
       ```suggestion
   	depends on ESP32_LEDC
   ```

##########
File path: arch/xtensa/src/esp32/Kconfig
##########
@@ -1282,6 +1283,91 @@ config ESP32_FREERUN
 endmenu # Timer/counter Configuration
 endif # ESP32_TIMER
 
+menu "LEDC configuration"
+        depends on ESP32_LEDC
+
+menuconfig ESP32_LEDC_TIM0
+        bool "Timer 0"
+        default n

Review comment:
       ```suggestion
   	bool "Timer 0"
   	default n
   ```

##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,812 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1

Review comment:
       I think that next code should be enough
   ```suggestion
   #  if CONFIG_PWM_NCHANNELS > 1
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [incubator-nuttx] pkarashchenko commented on a change in pull request #5187: esp32: Add PWM support using the LEDC peripheral

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on a change in pull request #5187:
URL: https://github.com/apache/incubator-nuttx/pull/5187#discussion_r780244979



##########
File path: arch/xtensa/src/esp32/esp32_ledc.c
##########
@@ -0,0 +1,822 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32/esp32_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 <debug.h>
+#include <errno.h>
+
+#include "esp32_clockconfig.h"
+#include "esp32_gpio.h"
+#include "esp32_ledc.h"
+
+#include "xtensa.h"
+#include "hardware/esp32_ledc.h"
+#include "hardware/esp32_dport.h"
+#include "hardware/esp32_gpio_sigmap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* LEDC total timers */
+
+#define LEDC_TIMERS               (4)
+
+/* LEDC total channels */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#  define LEDC_CHANNELS           (8)
+#else
+#  define LEDC_CHANNELS           (4)
+#endif
+
+/* LEDC timer0 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM0_CHANS       CONFIG_ESP32_LEDC_TIM0_CHANNELS
+#  else
+#    define LEDC_TIM0_CHANS       (1)
+#  endif
+#    define LEDC_TIM0_CHANS_OFF   (0)
+#endif
+
+/* LEDC timer1 channels and offset */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM1_CHANS       CONFIG_ESP32_LEDC_TIM1_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_ESP32_LEDC_TIM2
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM2_CHANS       CONFIG_ESP32_LEDC_TIM2_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_ESP32_LEDC_TIM3
+#  if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS > 1
+#    define LEDC_TIM3_CHANS       CONFIG_ESP32_LEDC_TIM3_CHANNELS
+#  else
+#    define LEDC_TIM3_CHANS       (1)
+#  endif
+
+#  define LEDC_TIM3_CHANS_OFF     (LEDC_TIM2_CHANS_OFF + LEDC_TIM2_CHANS)
+#endif
+
+/* LEDC clock resource */
+
+#define LEDC_CLK_RES              (1)         /* APB clock */
+
+/* LEDC timer max reload */
+
+#define LEDC_RELOAD_MAX           (1048576)   /* 2^20 */
+
+/* LEDC timer max clock divider parameter */
+
+#define LEDC_CLKDIV_MAX           (1024)      /* 2^10 */
+
+/* LEDC timer registers mapping */
+
+#define LEDC_TIMER_REG(r, n)      ((r) + (n) * (LEDC_LSTIMER1_CONF_REG - \
+                                                LEDC_LSTIMER0_CONF_REG))
+
+/* LEDC timer channel registers mapping */
+
+#define setbits(bs, a)            modifyreg32(a, 0, bs)
+#define resetbits(bs, a)          modifyreg32(a, bs, 0)
+
+#define LEDC_CHAN_REG(r, n)       ((r) + (n) * (LEDC_LSCH1_CONF0_REG - \
+                                                LEDC_LSCH0_CONF0_REG))
+
+#define SET_TIMER_BITS(t, r, b)   setbits(b, LEDC_TIMER_REG(r, (t)->num));
+#define SET_TIMER_REG(t, r, v)    putreg32(v, LEDC_TIMER_REG(r, (t)->num));
+
+#define SET_CHAN_BITS(c, r, b)    setbits(b, LEDC_CHAN_REG(r, (c)->num));
+#define SET_CHAN_REG(c, r, v)     putreg32(v, LEDC_CHAN_REG(r, (c)->num));
+
+#ifndef MIN
+#  define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* LEDC timer channel configuration */
+
+struct esp32_ledc_chan_s
+{
+  const uint8_t num;                    /* Timer channel ID */
+  const uint8_t pin;                    /* Timer channel GPIO pin number */
+
+  uint16_t duty;                        /* Timer channel current duty */
+};
+
+/* This structure represents the state of one LEDC timer */
+
+struct esp32_ledc_s
+{
+  const struct pwm_ops_s *ops;          /* PWM operations */
+
+  const uint8_t num;                    /* Timer ID */
+
+  const uint8_t channels;               /* Timer channels number */
+  struct esp32_ledc_chan_s *chans;      /* Timer channels pointer */
+
+  uint32_t frequency;                   /* Timer current frequency */
+  uint32_t reload;                      /* Timer current reload */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+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 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 esp32_ledc_chan_s g_ledc_chans[LEDC_CHANNELS] =
+{
+  {
+    .num       = 0,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL0_PIN
+  },
+
+  {
+    .num       = 1,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL1_PIN
+  },
+
+  {
+    .num       = 2,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL2_PIN
+  },
+
+  {
+    .num       = 3,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL3_PIN
+  },
+
+#if LEDC_CHANNELS > 4
+  {
+    .num       = 4,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL4_PIN
+  },
+
+  {
+    .num       = 5,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL5_PIN
+  },
+
+  {
+    .num       = 6,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL6_PIN
+  },
+
+  {
+    .num       = 7,
+    .pin       = CONFIG_ESP32_LEDC_CHANNEL7_PIN
+  }
+#endif
+};
+
+/* LEDC timer0 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM0
+static struct esp32_ledc_s g_pwm0dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 0,
+  .channels    = LEDC_TIM0_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM0_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM0 */
+
+/* LEDC timer1 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM1
+static struct esp32_ledc_s g_pwm1dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 1,
+  .channels    = LEDC_TIM1_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM1_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM1 */
+
+/* LEDC timer2 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM2
+static struct esp32_ledc_s g_pwm2dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 2,
+  .channels    = LEDC_TIM2_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM2_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM2 */
+
+/* LEDC timer3 private data */
+
+#ifdef CONFIG_ESP32_LEDC_TIM3
+static struct esp32_ledc_s g_pwm3dev =
+{
+  .ops         = &g_pwmops,
+  .num         = 3,
+  .channels    = LEDC_TIM3_CHANS,
+  .chans       = &g_ledc_chans[LEDC_TIM3_CHANS_OFF]
+};
+#endif /* CONFIG_ESP32_LEDC_TIM3 */
+
+/* Clock reference count */
+
+static uint32_t g_clk_ref;
+
+/****************************************************************************
+ * Private functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ledc_enable_clk
+ *
+ * Description:
+ *   Enable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_enable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  if (!g_clk_ref)
+    {
+      setbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+      resetbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+
+      putreg32(LEDC_CLK_RES, LEDC_CONF_REG);
+
+      pwminfo("Enable ledc clock\n");
+    }
+
+  g_clk_ref++;
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ledc_disable_clk
+ *
+ * Description:
+ *   Disable LEDC clock.
+ *
+ * Input Parameters:
+ *   NOne
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void ledc_disable_clk(void)
+{
+  irqstate_t flags;
+
+  flags = enter_critical_section();
+
+  g_clk_ref--;
+
+  if (!g_clk_ref)
+    {
+      pwminfo("Disable ledc clock\n");
+
+      setbits(DPORT_LEDC_RST, DPORT_PERIP_RST_EN_REG);
+      resetbits(DPORT_LEDC_CLK_EN, DPORT_PERIP_CLK_EN_REG);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_timer
+ *
+ * Description:
+ *   Setup LEDC timer frequency and reload.
+ *
+ * Input Parameters:
+ *   priv - A reference to the LEDC timer state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void setup_timer(struct esp32_ledc_s *priv)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  uint32_t reload;
+  uint32_t prescaler;
+  uint32_t shift = 1;
+  uint64_t pwmclk = esp_clk_apb_freq();
+
+  /* Reset timer */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_RST);
+
+  /* Calculate optimal values for the timer prescaler and for the timer
+   * modulo register.  If' frequency' is the desired frequency, then
+   *
+   *   tpmclk = pwmclk / presc
+   *   frequency = tpmclk / reload
+   *
+   * ==>
+   *
+   *   reload = pwmclk / presc / frequency
+   *
+   * In ESP32, there are 2 clock resources for PWM:
+   *
+   *   1. APB clock (80 MHz)
+   *   2. RTC clock (8 MHz)
+   *
+   * We mostly use APB clock generally.
+   *
+   * There are many solutions to this, but the best solution will be the one
+   * that has the largest reload value and the smallest prescaler value.
+   * That is the solution that should give us the most accuracy in the timer
+   * control.  Subject to:
+   *
+   *   2 <= presc  <= 2^20(1,048,576)
+   *   1 <= clkdiv <= 2^10
+   *
+   * clkdiv has 8-bit decimal precision, so
+   * clkdiv = pwmclk * 256 / 1048576 / frequency would be optimal.
+   *
+   * Example:
+   *
+   *  pwmclk    = 80 MHz
+   *  frequency = 100 Hz
+   *
+   *  presc     = 80,000,000 * 256 / 16,384 / 100
+   *            = 12,500
+   *  timclk    = 80,000,000 / (12,500 / 256)
+   *            = 1,638,400
+   *  counter   = 1,638,400 / 100
+   *            = 16,384
+   *            = 2^14
+   *  shift     = 14
+   */
+
+  reload = (pwmclk * 256 / priv->frequency + LEDC_CLKDIV_MAX) /
+           LEDC_CLKDIV_MAX;
+  if (reload == 0)
+    {
+      reload = 1;
+    }
+  else if (reload > LEDC_RELOAD_MAX)
+    {
+      reload = LEDC_RELOAD_MAX;
+    }
+
+  for (int c = 2; c <= LEDC_RELOAD_MAX; c *= 2)
+    {
+      if (c * 2 > reload)
+        {
+          reload = c;
+          break;
+        }
+
+      shift++;
+    }
+
+  prescaler = pwmclk * 256 / reload / priv->frequency;
+
+  pwminfo("PWM timer%" PRIu8 " frequency=%0.4f reload=%" PRIu32 " shift=%" \
+          PRIu32 " prescaler=%0.4f\n",
+          priv->num, (float)pwmclk / reload / ((float)prescaler / 256),
+          reload, shift, (float)prescaler / 256);
+
+  /* Store reload for channel duty */
+
+  priv->reload = reload;
+
+  flags = enter_critical_section();
+
+  /* Set timer clock divide and reload */
+
+  regval = (shift << LEDC_LSTIMER0_DUTY_RES_S) |
+           (prescaler << LEDC_DIV_NUM_LSTIMER0_S);
+  SET_TIMER_REG(priv, LEDC_LSTIMER0_CONF_REG, regval);
+
+  /* Setup to timer to use APB clock (80MHz) */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_TICK_SEL_LSTIMER0);
+
+  /* Update clock divide and reload to hardware */
+
+  SET_TIMER_BITS(priv, LEDC_LSTIMER0_CONF_REG, LEDC_LSTIMER0_PARA_UP);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * 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 esp32_ledc_s *priv, int cn)
+{
+  irqstate_t flags;
+  uint32_t regval;
+  struct esp32_ledc_chan_s *chan = &priv->chans[cn];
+
+  /* Duty cycle:
+   *
+   * duty cycle = duty / 65536 * reload (fractional value)
+   */
+
+  regval = b16toi(chan->duty * priv->reload + b16HALF);
+
+  pwminfo("channel=%" PRIu8 " duty=%" PRIu16 "(%0.4f) regval=%" PRIu32 \
+          " reload=%" PRIu32 "\n",
+          chan->num, chan->duty, (float)chan->duty / UINT16_MAX,
+          regval, priv->reload);
+
+  flags = enter_critical_section();
+
+  /* Reset config 0 & 1 registers */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF0_REG, 0);
+  SET_CHAN_REG(chan, LEDC_LSCH0_CONF1_REG, 0);
+
+  /* Set pulse phase 0 */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_HPOINT_REG, 0);
+
+  /* Duty register uses bits [18:4]  */
+
+  SET_CHAN_REG(chan, LEDC_LSCH0_DUTY_REG, regval << 4);
+
+  /* Start GPIO output  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_SIG_OUT_EN_LSCH0);
+
+  /* Start Duty counter  */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF1_REG, LEDC_DUTY_START_LSCH0);
+
+  /* Update duty and phase to hardware */
+
+  SET_CHAN_BITS(chan, LEDC_LSCH0_CONF0_REG, LEDC_PARA_UP_LSCH0);
+
+  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 esp32_ledc_s *priv = (struct esp32_ledc_s *)dev;
+
+  pwminfo("PWM timer%d\n", priv->num);
+
+  ledc_enable_clk();
+
+  /* Setup channel GPIO pins */
+
+  for (int i = 0; i < priv->channels; i++)
+    {
+      pwminfo("channel%d --> pin%d\n", priv->chans[i].num,

Review comment:
       @acassis do you plan to integrate this change?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org