You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ac...@apache.org on 2022/06/23 13:17:46 UTC

[incubator-nuttx] 02/02: Added PWM support to rp2040

This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git

commit 412539e66c8b798a86847419f4776dd9b8d302ff
Author: curuvar <58...@users.noreply.github.com>
AuthorDate: Wed Jun 22 20:36:10 2022 -0400

    Added PWM support to rp2040
---
 arch/arm/src/rp2040/hardware/rp2040_pwm.h        | 143 ++++++
 arch/arm/src/rp2040/rp2040_pwm.c                 | 596 +++++++++++++++++++++++
 arch/arm/src/rp2040/rp2040_pwm.h                 | 120 +++++
 boards/arm/rp2040/common/include/rp2040_pwmdev.h |  78 +++
 boards/arm/rp2040/common/src/rp2040_pwmdev.c     |  95 ++++
 5 files changed, 1032 insertions(+)

diff --git a/arch/arm/src/rp2040/hardware/rp2040_pwm.h b/arch/arm/src/rp2040/hardware/rp2040_pwm.h
new file mode 100644
index 0000000000..a5553871d5
--- /dev/null
+++ b/arch/arm/src/rp2040/hardware/rp2040_pwm.h
@@ -0,0 +1,143 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/hardware/rp2040_pwm.h
+ *
+ * Generated from rp2040.svd originally provided by
+ *   Raspberry Pi (Trading) Ltd.
+ *
+ * Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_PWM_H
+#define __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_PWM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include "hardware/rp2040_memorymap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Register offsets *********************************************************/
+
+#define RP2040_PWM_CSR_OFFSET(n)  (0x000000 + (n) * 20) /* PWM control and status register */
+#define RP2040_PWM_DIV_OFFSET(n)  (0x000004 + (n) * 20) /* PWM clock divisor register */
+#define RP2040_PWM_CTR_OFFSET(n)  (0x000008 + (n) * 20) /* PWM counter register */
+#define RP2040_PWM_CC_OFFSET(n)   (0x00000C + (n) * 20) /* PWM compare register */
+#define RP2040_PWM_TOP_OFFSET(n)  (0x000010 + (n) * 20) /* PWM wrap value register */
+#define RP2040_PWM_ENA_OFFSET     0x0000A0              /* PWM enable register */
+#define RP2040_PWM_INTR_OFFSET    0x0000A4              /* PWM raw interrupt register */
+#define RP2040_PWM_INTE_OFFSET    0x0000A8              /* PWM interrupt enable register */
+#define RP2040_PWM_INTF_OFFSET    0x0000AC              /* PWM interrupt force register */
+#define RP2040_PWM_INTS_OFFSET    0x0000BO              /* PWM interrupt status register */
+
+/* Register definitions *****************************************************/
+
+#define RP2040_PWM_CSR(n)  (RP2040_PWM_BASE + RP2040_PWM_CSR_OFFSET(n))
+#define RP2040_PWM_DIV(n)  (RP2040_PWM_BASE + RP2040_PWM_DIV_OFFSET(n))
+#define RP2040_PWM_CTR(n)  (RP2040_PWM_BASE + RP2040_PWM_CTR_OFFSET(n))
+#define RP2040_PWM_CC(n)   (RP2040_PWM_BASE + RP2040_PWM_CC_OFFSET(n))
+#define RP2040_PWM_TOP(n)  (RP2040_PWM_BASE + RP2040_PWM_TOP_OFFSET(n))
+#define RP2040_PWM_ENA     (RP2040_PWM_BASE + RP2040_PWM_ENA_OFFSET)
+#define RP2040_PWM_INTR    (RP2040_PWM_BASE + RP2040_PWM_INTR_OFFSET)
+#define RP2040_PWM_INTE    (RP2040_PWM_BASE + RP2040_PWM_INTE_OFFSET)
+#define RP2040_PWM_INTF    (RP2040_PWM_BASE + RP2040_PWM_INTF_OFFSET)
+#define RP2040_PWM_INTS    (RP2040_PWM_BASE + RP2040_PWM_INTS_OFFSET)
+
+/* Register bit definitions *************************************************/
+
+#define RP2040_PWM_CSR_PH_ADV        (1 << 7) /* advance phase of counter by one */
+#define RP2040_PWM_CSR_PH_RET        (1 << 5) /* retard phase of counter by one */
+#define RP2040_PWM_CSR_DIVMODE_SHIFT (4)      /* divisor mode */
+#define RP2040_PWM_CSR_DIVMODE_MASK  (0x03 << RP2040_PWM_CSR_DIVMODE_SHIFT)
+#define RP2040_PWM_CSR_B_INV         (1 << 3) /* invert output B */
+#define RP2040_PWM_CSR_A_INV         (1 << 2) /* invert output A */
+#define RP2040_PWM_CSR_PH_CORRECT    (1 << 1) /* enable phase correct modulation */
+#define RP2040_PWM_CSR_EN            (1 << 0) /* enable the PWM channel */
+
+#define RP2040_PWN_CSR_DIVMODE_DIV    0x00
+#define RP2040_PWN_CSR_DIVMODE_LEVEL  0x01
+#define RP2040_PWN_CSR_DIVMODE_RISE   0x02
+#define RP2040_PWN_CSR_DIVMODE_FALL   0x03
+#define RP2040_PWM_DIV_INT_SHIFT     (4)      /* divisor integer part */
+#define RP2040_PWM_DIV_INT_MASK      (0xff << RP2040_PWM_DIV_INT_SHIFT)
+#define RP2040_PWM_DIV_FRAC_SHIFT    (0)      /* divisor fraction part */
+#define RP2040_PWM_DIV_FRAC_MASK     (0x0f << RP2040_PWM_DIV_FRAC_SHIFT)
+
+#define RP2040_PWM_CC_B_SHIFT        (16)      /* channel B compare register */
+#define RP2040_PWM_CC_B_MASK         (0xffff << RP2040_PWM_CC_B_SHIFT)
+#define RP2040_PWM_CC_A_SHIFT        (0)       /* channel A compare register */
+#define RP2040_PWM_CC_A_MASK         (0xffff << RP2040_PWM_CC_A_SHIFT)
+
+#define RP2040_PWM_TOP_SHIFT         (0)       /* channel A compare register */
+#define RP2040_PWM_TOP_MASK          (0xffff << RP2040_PWM_TOP_SHIFT)
+
+/*  Bit mask for ENA, INTR, INTE, INTF, and INTS registers */
+
+#define RP2040_PWM_CH7              (1 << 7) /* PWM channel 7 */
+#define RP2040_PWM_CH6              (1 << 6) /* PWM channel 6 */
+#define RP2040_PWM_CH5              (1 << 5) /* PWM channel 5 */
+#define RP2040_PWM_CH4              (1 << 4) /* PWM channel 4 */
+#define RP2040_PWM_CH3              (1 << 3) /* PWM channel 3 */
+#define RP2040_PWM_CH2              (1 << 2) /* PWM channel 2 */
+#define RP2040_PWM_CH1              (1 << 1) /* PWM channel 1 */
+#define RP2040_PWM_CH0              (1 << 0) /* PWM channel 0 */
+
+/****************************************************************************
+ * The following IOCTL values set additional flags in the RP2040 PWM
+ * device.
+ ****************************************************************************/
+
+/****************************************************************************
+ * PWMIOC_RP2040_SETINVERTPULSE sets the pulse invert flag.
+ *
+ * The argument is an integer where:
+ *   bit zero is set to invert channel A
+ *   bit one  is set to invert channel B
+ ****************************************************************************/
+
+#define PWMIOC_RP2040_SETINVERTPULSE  _PWMIOC(0x80)
+
+#define PWMIOC_RP2040_GETINVERTPULSE  _PWMIOC(0x81)
+
+/****************************************************************************
+ * PWMIOC_RP2040_SETPHASECORRECT sets phase correct flags.
+ *
+ * The argument is an integer which if non-zero sets the phase correct flag.
+ ****************************************************************************/
+
+#define PWMIOC_RP2040_SETPHASECORRECT _PWMIOC(0x82)
+
+#define PWMIOC_RP2040_GETPHASECORRECT _PWMIOC(0x83)
+
+#endif
\ No newline at end of file
diff --git a/arch/arm/src/rp2040/rp2040_pwm.c b/arch/arm/src/rp2040/rp2040_pwm.c
new file mode 100644
index 0000000000..a28dc8ad2e
--- /dev/null
+++ b/arch/arm/src/rp2040/rp2040_pwm.c
@@ -0,0 +1,596 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/rp2040_pwm.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 <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/irq.h>
+#include <nuttx/timers/pwm.h>
+#include <arch/board/board.h>
+#include "rp2040_gpio.h"
+#include "rp2040_pwm.h"
+
+/****************************************************************************
+ * Local 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);
+
+static void setup_period (struct rp2040_pwm_lowerhalf_s  * priv);
+
+static void setup_pulse  (struct rp2040_pwm_lowerhalf_s  * priv);
+
+static void set_enabled  (struct rp2040_pwm_lowerhalf_s  * priv);
+
+static void clear_enabled(struct rp2040_pwm_lowerhalf_s  * priv);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* 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
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_pwm_initialize
+ *
+ * Description:
+ *   Initialize the selected PWM port. And return a unique instance of struct
+ *   struct rp2040_pwm_lowerhalf_s.  This function may be called to obtain
+ *   multiple instances of the interface, each of which may be set up with a
+ *   different frequency and address.
+ *
+ * Input Parameters:
+ *   Port number (for hardware that has multiple PWM interfaces)
+ *   GPIO pin number for pin A
+ *   GPIO pin number for pin B (CONFIG_PWM_NCHANNELS == 2)
+ *
+ * Returned Value:
+ *   Valid PWM device structure reference on success; a NULL on failure
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+struct rp2040_pwm_lowerhalf_s *rp2040_pwm_initialize(int      port,
+                                                      int      pin_a,
+                                                      int      pin_b,
+                                                      uint32_t flags)
+#else
+struct rp2040_pwm_lowerhalf_s *rp2040_pwm_initialize(int      port,
+                                                      int      pin,
+                                                      uint32_t flags)
+#endif
+{
+  struct rp2040_pwm_lowerhalf_s *data;
+
+  data = calloc(1, sizeof (struct rp2040_pwm_lowerhalf_s));
+
+  if (data != NULL)
+    {
+      data->ops   = &g_pwmops;
+      data->num   = port;
+      data->flags = flags;
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+      if (pin_a == 2*port  ||  pin_a == 2*port + 16)
+        {
+          data->pin[0] = pin_a;
+        }
+        else
+        {
+          data->pin[0] = -1;
+        }
+
+      if (pin_b == 2*port + 1  ||  pin_b == 2*port + 17)
+        {
+          data->pin[1] = pin_b;
+        }
+        else
+        {
+          data->pin[1] = -1;
+        }
+#else
+      if (pin == 2*port  ||  pin == 2*port + 16)
+        {
+          data->pin = pin;
+        }
+        else
+        {
+          data->pin = -1;
+        }
+
+#endif
+    }
+
+  return data;
+}
+
+/****************************************************************************
+ * Name: rp2040_pwm_uninitialize
+ *
+ * Description:
+ *   De-initialize the selected pwm port, and power down the device.
+ *
+ * Input Parameter:
+ *   Device structure as returned by the rp2040_pwmdev_initialize()
+ *
+ * Returned Value:
+ *   OK on success, ERROR when internal reference count mismatch or dev
+ *   points to invalid hardware device.
+ *
+ ****************************************************************************/
+
+int rp2040_pwm_uninitialize(struct pwm_lowerhalf_s *dev)
+{
+  free(dev);
+  return (OK);
+}
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * 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
+ *
+ ****************************************************************************/
+
+int pwm_setup(struct pwm_lowerhalf_s  * dev)
+{
+  struct rp2040_pwm_lowerhalf_s *priv = (struct rp2040_pwm_lowerhalf_s *)dev;
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  pwminfo("PWM%d pin_a %d pin_b %d\n",
+          priv->num,
+          priv->pin[0],
+          priv->pin[1]);
+
+  if (priv->pin[0] >= 0)
+    {
+      rp2040_gpio_set_function(priv->pin[0], RP2040_GPIO_FUNC_PWM);
+    }
+
+  if (priv->pin[1] >= 0)
+    {
+      rp2040_gpio_set_function(priv->pin[1], RP2040_GPIO_FUNC_PWM);
+    }
+#else
+  if (priv->pin >= 0)
+    {
+      rp2040_gpio_set_function(priv->pin, RP2040_GPIO_FUNC_PWM);
+    }
+#endif
+
+  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
+ *
+ ****************************************************************************/
+
+int pwm_shutdown (struct pwm_lowerhalf_s  * dev)
+{
+  struct rp2040_pwm_lowerhalf_s *priv = (struct rp2040_pwm_lowerhalf_s *)dev;
+
+  pwminfo("PWM%d\n", priv->num);
+
+  /* Stop timer */
+
+  pwm_stop(dev);
+
+  /* Force the GPIO pins to the appropriate idle state */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  pwminfo("PWM%d pin_a %d pin_b %d\n",
+          priv->num,
+          priv->pin[0],
+          priv->pin[1]);
+
+  if (priv->pin[0] >= 0)
+    {
+      pwminfo("PWM%d setting pin_a %d\n",
+              priv->num,
+              (priv->flags & RP2040_PWM_CSR_A_INV) ? 1 : 0);
+
+      rp2040_gpio_setdir(priv->pin[0], true);
+      rp2040_gpio_put(priv->pin[0],
+                      ((priv->flags & RP2040_PWM_CSR_A_INV) != 0));
+      rp2040_gpio_set_function(priv->pin[0], RP2040_GPIO_FUNC_SIO);
+    }
+
+  if (priv->pin[1] >= 0)
+    {
+      pwminfo("PWM%d setting pin_b %d\n",
+              priv->num,
+              (priv->flags & RP2040_PWM_CSR_B_INV) ? 1 : 0);
+
+      rp2040_gpio_setdir(priv->pin[1], true);
+      rp2040_gpio_put(priv->pin[1],
+                      ((priv->flags & RP2040_PWM_CSR_B_INV) != 0));
+      rp2040_gpio_set_function(priv->pin[1], RP2040_GPIO_FUNC_SIO);
+    }
+#else
+  pwminfo("PWM%d pin %d\n", priv->num, priv->pin);
+
+  if (priv->pin >= 0)
+    {
+      rp2040_gpio_setdir(priv->pin[0], true);
+      rp2040_gpio_put(priv->pin[0],
+                      ((priv->flags & RP2040_PWM_CSR_A_INV) != 0));
+      rp2040_gpio_set_function(priv->pin, RP2040_GPIO_FUNC_SIO);
+    }
+#endif
+
+  /* Clear timer and channel configuration */
+
+  priv->frequency = 0;
+  priv->divisor   = 0x00000010;  /* hex 1.0 */
+  priv->top       = 0xffff;
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  for (int i = 0; i < CONFIG_PWM_NCHANNELS; ++i)
+    {
+      priv->duty[i] = 0;
+    }
+#else
+  priv->duty = 0;
+#endif
+
+  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
+ *
+ ****************************************************************************/
+
+int pwm_start(struct pwm_lowerhalf_s  * dev,
+               const struct pwm_info_s * info)
+{
+  struct rp2040_pwm_lowerhalf_s *priv = (struct rp2040_pwm_lowerhalf_s *)dev;
+
+  pwminfo("PWM%d\n", priv->num);
+
+  /* Update timer with given PWM timer frequency */
+
+  if (priv->frequency != info->frequency)
+    {
+      priv->frequency = info->frequency;
+
+      /* We want to compute the top and divisor to give the finest control */
+
+      setup_period(priv);
+    }
+
+  /* Update timer with given PWM channel duty */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  for (int i = 0; i < CONFIG_PWM_NCHANNELS; i++)
+    {
+      if (priv->duty[i] != info->channels[i].duty)
+        {
+          priv->duty[i] = info->channels[i].duty;
+        }
+    }
+#else
+  if (priv->duty != info[0].duty)
+    {
+      priv->duty = info[0].duty;
+    }
+#endif
+
+  setup_pulse(priv);
+
+  set_enabled(priv);
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: pwm_stop
+ *
+ * Description:
+ *   Stop the pulsed output.
+ *
+ * Input Parameters:
+ *   dev - A reference to the lower half PWM driver state structure
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure
+ *
+ ****************************************************************************/
+
+int pwm_stop(struct pwm_lowerhalf_s  * dev)
+{
+  struct rp2040_pwm_lowerhalf_s *priv = (struct rp2040_pwm_lowerhalf_s *)dev;
+
+  pwminfo("PWM%d\n", priv->num);
+
+  clear_enabled(priv);
+
+  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
+ *
+ ****************************************************************************/
+
+int pwm_ioctl(struct pwm_lowerhalf_s  * dev,
+               int                       cmd,
+               unsigned long             arg)
+{
+  struct rp2040_pwm_lowerhalf_s *priv = (struct rp2040_pwm_lowerhalf_s *)dev;
+
+#ifdef CONFIG_DEBUG_PWM_INFO
+  pwminfo("PWM%d\n", priv->num);
+#endif
+
+  switch (cmd)
+    {
+    case PWMIOC_RP2040_SETINVERTPULSE:
+      priv->flags &= ~(RP2040_PWM_CSR_B_INV | RP2040_PWM_CSR_A_INV);
+      priv->flags |= (arg & 0x03) << 2;
+
+      setup_period(priv);
+      setup_pulse(priv);
+
+      return 0;
+
+    case PWMIOC_RP2040_GETINVERTPULSE:
+      return (priv->flags &  (RP2040_PWM_CSR_B_INV
+                            | RP2040_PWM_CSR_A_INV)) >> 2;
+
+    case PWMIOC_RP2040_SETPHASECORRECT:
+      priv->flags &= ~(RP2040_PWM_CSR_PH_CORRECT);
+      priv->flags |= (arg != 0) ? RP2040_PWM_CSR_PH_CORRECT : 0x00;
+
+      setup_period(priv);
+      setup_pulse(priv);
+
+      return 0;
+
+    case PWMIOC_RP2040_GETPHASECORRECT:
+      return (priv->flags & RP2040_PWM_CSR_PH_CORRECT) ? 1 : 0;
+  }
+
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Name: setup_period
+ *
+ * Description:
+ *   compute and set the clock divisor and top value based on frequency.
+ *
+ * Input Parameters:
+ *   priv - A reference to the lower half PWM driver state structure
+ *
+ ****************************************************************************/
+
+void setup_period(struct rp2040_pwm_lowerhalf_s  * priv)
+{
+  irqstate_t flags;
+  uint32_t max_freq = BOARD_SYS_FREQ / 0x10000; /* initially, with full range count */
+  uint32_t frequency = priv->frequency;
+
+  /* If we are running phase correct we double the frequency value
+   * since the PWM will generate a pulse chain at half what it
+   * would be in normal (non-phase correct) mode
+   */
+
+  if (priv->flags & RP2040_PWM_CSR_PH_CORRECT)
+    {
+      frequency *= 2;
+    }
+
+  pwminfo("PWM%d freq %ld max %ld\n", priv->num, priv->frequency, max_freq);
+
+  if (frequency <= max_freq)
+    {
+      /* We can keep full range count and slow clock down with divisor */
+
+      priv->top = 0xffff;
+    }
+    else
+    {
+      /* we need to speed things up by reducing top */
+
+      priv->top = 0xffff / (frequency / max_freq);
+
+      /* compute new maximum frequency */
+
+      max_freq  = BOARD_SYS_FREQ / (priv->top + 1);
+    }
+
+  priv->divisor = 16 * max_freq / frequency;
+
+  pwminfo("PWM%d top 0x%08X div 0x%08lX\n",
+          priv->num,
+          priv->top,
+          priv->divisor);
+
+  flags = enter_critical_section();
+
+  putreg32(priv->top,     RP2040_PWM_TOP(priv->num));
+  putreg32(priv->divisor, RP2040_PWM_DIV(priv->num));
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: setup_pulse
+ *
+ * Description:
+ *   compute and set the compare values and set CSR flags.
+ *
+ * Input Parameters:
+ *   priv    - A reference to the lower half PWM driver state structure
+ *
+ ****************************************************************************/
+
+void setup_pulse(struct rp2040_pwm_lowerhalf_s  * priv)
+{
+  irqstate_t flags;
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  uint32_t compare =
+             (0xffff * (uint32_t)priv->duty[0] / priv->top)
+          + ((0xffff * (uint32_t)priv->duty[1] / priv->top) << 16);
+#else
+  uint32_t compare = 0xffff * (uint32_t)priv->duty / priv->top;
+#endif
+
+  pwminfo("PWM%d compare 0x%08lX  flags 0x%08lX\n",
+          priv->num,
+          compare,
+          priv->flags);
+
+  flags = enter_critical_section();
+
+  putreg32(compare, RP2040_PWM_CC(priv->num));
+
+  modreg32(priv->flags,
+            RP2040_PWM_CSR_DIVMODE_MASK
+          | RP2040_PWM_CSR_B_INV
+          | RP2040_PWM_CSR_A_INV
+          | RP2040_PWM_CSR_PH_CORRECT,
+          RP2040_PWM_CSR(priv->num));
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: set_enabled
+ *
+ * Description:
+ *   set the enable bit for a given slice.
+ *
+ * Input Parameters:
+ *   priv    - A reference to the lower half PWM driver state structure
+ *
+ ****************************************************************************/
+
+static inline void set_enabled(struct rp2040_pwm_lowerhalf_s  * priv)
+{
+  irqstate_t flags = enter_critical_section();
+
+  modreg32(1 << priv->num, 1 << priv->num,  RP2040_PWM_ENA);
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: clear_enabled
+ *
+ * Description:
+ *   clear the enable bit for a given slice.
+ *
+ * Input Parameters:
+ *   priv    - A reference to the lower half PWM driver state structure
+ *
+ ****************************************************************************/
+
+static inline void clear_enabled(struct rp2040_pwm_lowerhalf_s  * priv)
+{
+  irqstate_t flags = enter_critical_section();
+
+  modreg32(0, 1 << priv->num, RP2040_PWM_ENA);
+
+  leave_critical_section(flags);
+}
diff --git a/arch/arm/src/rp2040/rp2040_pwm.h b/arch/arm/src/rp2040/rp2040_pwm.h
new file mode 100644
index 0000000000..18a5a88a5d
--- /dev/null
+++ b/arch/arm/src/rp2040/rp2040_pwm.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/rp2040_pwm.h
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_RP2040_RP2040_PWM_H
+#define __ARCH_ARM_SRC_RP2040_RP2040_PWM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include "hardware/rp2040_pwm.h"
+#include "nuttx/timers/pwm.h"
+
+#ifndef __ASSEMBLY__
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/* This structure represents the state of one PWM timer */
+
+struct rp2040_pwm_lowerhalf_s
+{
+  const struct pwm_ops_s   * ops;        /* PWM operations */
+
+  uint32_t                   frequency;  /* PWM current frequency */
+  uint32_t                   divisor;    /* PWM current clock divisor */
+  uint32_t                   flags;      /* PWM mode flags */
+  uint16_t                   top;        /* PWM current top value */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  uint16_t                   duty[2];
+  int8_t                     pin[2];
+#else
+  uint16_t                   duty;       /* Time duty value */
+  int8_t                     pin;
+#endif
+
+  uint8_t                    num;        /* Timer ID */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_pwm_initialize
+ *
+ * Description:
+ *   Initialize the selected PWM port. And return a unique instance of struct
+ *   struct rp2040_pwm_lowerhalf_s.  This function may be called to obtain
+ *   multiple instances of the interface, each of which may be set up with a
+ *   different frequency and address.
+ *
+ * Input Parameters:
+ *   Port number (for hardware that has multiple PWM interfaces)
+ *   GPIO pin number for pin A
+ *   GPIO pin number for pin B (CONFIG_PWM_NCHANNELS == 2)
+ *
+ * Returned Value:
+ *   Valid PWM device structure reference on success; a NULL on failure
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+struct rp2040_pwm_lowerhalf_s *rp2040_pwm_initialize(int      port,
+                                                     int      pin_a,
+                                                     int      pin_b,
+                                                     uint32_t flags);
+#else
+struct rp2040_pwm_lowerhalf_s *rp2040_pwm_initialize(int      port,
+                                                     int      pin,
+                                                     uint32_t flags);
+#endif
+
+/****************************************************************************
+ * Name: rp2040_pwmdev_uninitialize
+ *
+ * Description:
+ *   De-initialize the selected pwm port, and power down the device.
+ *
+ * Input Parameter:
+ *   Device structure as returned by the rp2040_pwmdev_initialize()
+ *
+ * Returned Value:
+ *   OK on success, ERROR when internal reference count mismatch or dev
+ *   points to invalid hardware device.
+ *
+ ****************************************************************************/
+
+int rp2040_pwm_uninitialize(struct pwm_lowerhalf_s *dev);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ARCH_ARM_SRC_RP2040_RP2040_I2C_H */
diff --git a/boards/arm/rp2040/common/include/rp2040_pwmdev.h b/boards/arm/rp2040/common/include/rp2040_pwmdev.h
new file mode 100644
index 0000000000..7b86ea3ded
--- /dev/null
+++ b/boards/arm/rp2040/common/include/rp2040_pwmdev.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+ * boards/arm/rp2040/common/include/rp2040_pwmdev.h
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __BOARDS_ARM_RP2040_RASPBERRYPI_PICO_INCLUDE_RP2040_PWMDEV_H
+#define __BOARDS_ARM_RP2040_RASPBERRYPI_PICO_INCLUDE_RP2040_PWMDEV_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_pwmdev_initialize
+ *
+ * Description:
+ *   Initialize pwm driver and register the /dev/pwm device.
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+int rp2040_pwmdev_initialize(int      slice,
+                             int      pin_a,
+                             int      pin_b,
+                             uint32_t flags);
+#else
+int rp2040_pwmdev_initialize(int      slice,
+                             int      pin,
+                             uint32_t flags);
+#endif
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __BOARDS_ARM_RP2040_RASPBERRYPI_PICO_INCLUDE_RP2040_PWMDEV_H */
diff --git a/boards/arm/rp2040/common/src/rp2040_pwmdev.c b/boards/arm/rp2040/common/src/rp2040_pwmdev.c
new file mode 100644
index 0000000000..902c75418c
--- /dev/null
+++ b/boards/arm/rp2040/common/src/rp2040_pwmdev.c
@@ -0,0 +1,95 @@
+/****************************************************************************
+ * boards/arm/rp2040/common/src/rp2040_pwmdev.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 <nuttx/config.h>
+
+#include <stdio.h>
+#include <debug.h>
+#include <errno.h>
+
+#include "rp2040_pwm.h"
+
+#ifdef CONFIG_RP2040_PWM
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: board_pwmdev_initialize
+ *
+ * Description:
+ *   Initialize and register spi driver for the specified pwm port
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+int rp2040_pwmdev_initialize(int      slice,
+                             int      pin_a,
+                             int      pin_b,
+                             uint32_t flags)
+#else
+int rp2040_pwmdev_initialize(int      slice,
+                             int      pin,
+                             uint32_t flags)
+#endif
+{
+  int ret;
+  struct rp2040_pwm_lowerhalf_s *pwm_lowerhalf;
+
+  pwminfo("Initializing /dev/pwm%d a %d b %d f 0x%08lX..\n",
+           slice,
+           pin_a,
+           pin_b,
+           flags);
+
+  /* Initialize spi device */
+
+#if defined(CONFIG_PWM_NCHANNELS) && CONFIG_PWM_NCHANNELS == 2
+  pwm_lowerhalf = rp2040_pwm_initialize(slice, pin_a, pin_b, flags);
+#else
+  pwm_lowerhalf = rp2040_pwm_initialize(slice, pin, flags);
+#endif
+
+  if (!pwm_lowerhalf)
+    {
+      pwmerr("ERROR: Failed to initialize pwm%d.\n", slice);
+      return -ENODEV;
+    }
+
+  char path[10] = "/dev/pwmN";
+  path[8] = '0' + slice; /* replace "N" with slice number. */
+
+  ret = pwm_register(path, (struct pwm_lowerhalf_s *) pwm_lowerhalf);
+  if (ret < 0)
+    {
+      pwmerr("ERROR: Failed to register pwm%d: %d\n", slice, ret);
+      return -ENODEV;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_RP2040_PWM */
+