This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new bb025be12db arch/nrf91: add PWM support
bb025be12db is described below
commit bb025be12db86bad3e1a7b931926bf646e2ed5e5
Author: raiden00pl <[email protected]>
AuthorDate: Thu Jun 11 09:19:39 2026 +0200
arch/nrf91: add PWM support
add PWM support for nrf91
Signed-off-by: raiden00pl <[email protected]>
---
arch/arm/src/nrf91/Kconfig | 39 ++
arch/arm/src/nrf91/hardware/nrf91_pwm.h | 180 ++++++++
arch/arm/src/nrf91/nrf91_pwm.c | 706 ++++++++++++++++++++++++++++++++
arch/arm/src/nrf91/nrf91_pwm.h | 133 ++++++
4 files changed, 1058 insertions(+)
diff --git a/arch/arm/src/nrf91/Kconfig b/arch/arm/src/nrf91/Kconfig
index 57048b5fb8c..120688899fd 100644
--- a/arch/arm/src/nrf91/Kconfig
+++ b/arch/arm/src/nrf91/Kconfig
@@ -203,6 +203,11 @@ config NRF91_PWM2
select NRF91_PWM
default n
+config NRF91_PWM3
+ bool "PWM3"
+ select NRF91_PWM
+ default n
+
config NRF91_SAADC
bool "SAADC"
default n
@@ -568,6 +573,34 @@ config NRF91_PWM2_CH3
endif # NRF91_PWM2
+if NRF91_PWM3
+
+config NRF91_PWM3_CH0
+ bool "PWM3 Channel 0 Output"
+ default n
+ ---help---
+ Enables channel 0 output.
+
+config NRF91_PWM3_CH1
+ bool "PWM3 Channel 1 Output"
+ default n
+ ---help---
+ Enables channel 1 output.
+
+config NRF91_PWM3_CH2
+ bool "PWM3 Channel 2 Output"
+ default n
+ ---help---
+ Enables channel 2 output.
+
+config NRF91_PWM3_CH3
+ bool "PWM3 Channel 3 Output"
+ default n
+ ---help---
+ Enables channel 3 output.
+
+endif # NRF91_PWM3
+
endif # !NRF91_PWM_MULTICHAN
if !NRF91_PWM_MULTICHAN
@@ -590,6 +623,12 @@ config NRF91_PWM2_CHANNEL
default 0
range 0 3
+config NRF91_PWM3_CHANNEL
+ int "PWM3 Output Channel"
+ depends on NRF91_PWM3
+ default 0
+ range 0 3
+
endif # !NRF91_PWM_MULTICHAN
endif # NRF91_PWM
diff --git a/arch/arm/src/nrf91/hardware/nrf91_pwm.h
b/arch/arm/src/nrf91/hardware/nrf91_pwm.h
new file mode 100644
index 00000000000..95249fc1a1c
--- /dev/null
+++ b/arch/arm/src/nrf91/hardware/nrf91_pwm.h
@@ -0,0 +1,180 @@
+/****************************************************************************
+ * arch/arm/src/nrf91/hardware/nrf91_pwm.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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_NRF91_HARDWARE_NRF91_PWM_H
+#define __ARCH_ARM_SRC_NRF91_HARDWARE_NRF91_PWM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include "hardware/nrf91_memorymap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+/* Register offsets *********************************************************/
+
+#define NRF91_PWM_TASKS_STOP_OFFSET 0x0004 /* Stop PWM */
+#define NRF91_PWM_TASKS_SEQSTART0_OFFSET 0x0008 /* Sequence 0 start */
+#define NRF91_PWM_TASKS_SEQSTART1_OFFSET 0x000c /* Sequence 1 start */
+#define NRF91_PWM_TASKS_NEXTSTEP_OFFSET 0x0010 /* Steps by one value in
the current sequence */
+ /* TODO: 0x084-0x090 */
+#define NRF91_PWM_EVENTS_STOPPED_OFFSET 0x0104 /* STOP event */
+#define NRF91_PWM_EVENTS_SEQSTARTED0_OFFSET 0x0108 /* Sequence 0 started
event */
+#define NRF91_PWM_EVENTS_SEQSTARTED1_OFFSET 0x010c /* Sequence 1 started
event */
+#define NRF91_PWM_EVENTS_SEQEND0_OFFSET 0x0110 /* Sequence 0 end event */
+#define NRF91_PWM_EVENTS_SEQEND1_OFFSET 0x0114 /* Sequence 1 end event */
+#define NRF91_PWM_EVENTS_PWMPERIODEN_OFFSET 0x0118 /* PWM period end event */
+#define NRF91_PWM_EVENTS_LOOPSDONE_OFFSET 0x011c /* Loop done event */
+ /* TODO: 0x184-0x19c */
+#define NRF91_PWM_SHORTS_OFFSET 0x0200 /* Shourtcut register */
+#define NRF91_PWM_INTEN_OFFSET 0x0300 /* Enable or disable
interrupt */
+#define NRF91_PWM_INTENSET_OFFSET 0x0304 /* Enable interrupt */
+#define NRF91_PWM_INTENCLR_OFFSET 0x0308 /* Disable interrupt */
+#define NRF91_PWM_ENABLE_OFFSET 0x0500 /* PWM enable */
+#define NRF91_PWM_MODE_OFFSET 0x0504 /* Wave counter mode */
+#define NRF91_PWM_COUNTERTOP_OFFSET 0x0508 /* Counter max value */
+#define NRF91_PWM_PRESCALER_OFFSET 0x050c /* Prescaler configuration
*/
+#define NRF91_PWM_DECODER_OFFSET 0x0510 /* Configuration of the
decoder */
+#define NRF91_PWM_LOOP_OFFSET 0x0514 /* Amount of playback of a
loop */
+#define NRF91_PWM_SEQ0PTR_OFFSET 0x0520 /* Sequence 0 beginning
address */
+#define NRF91_PWM_SEQ0CNT_OFFSET 0x0524 /* Sequence 0 length */
+#define NRF91_PWM_SEQ0REFRESH_OFFSET 0x0528 /* Sequence 0 additional
periods */
+#define NRF91_PWM_SEQ0ENDDELAY_OFFSET 0x052c /* Time added after
sequence 0 */
+#define NRF91_PWM_SEQ1PTR_OFFSET 0x0540 /* Sequence 1 beginning
address */
+#define NRF91_PWM_SEQ1CNT_OFFSET 0x0544 /* Sequence 1 length */
+#define NRF91_PWM_SEQ1REFRESH_OFFSET 0x0548 /* Sequence 0 additional
periods */
+#define NRF91_PWM_SEQ1ENDDELAY_OFFSET 0x054c /* Time added after
sequence 1 */
+#define NRF91_PWM_PSEL0_OFFSET 0x0560 /* Output pin select for
PWM chan 0 */
+#define NRF91_PWM_PSEL1_OFFSET 0x0564 /* Output pin select for
PWM chan 1 */
+#define NRF91_PWM_PSEL2_OFFSET 0x0568 /* Output pin select for
PWM chan 2 */
+#define NRF91_PWM_PSEL3_OFFSET 0x056c /* Output pin select for
PWM chan 3 */
+
+/* Register Bitfield Definitions ********************************************/
+
+/* TASKS_STOP Register */
+
+#define PWM_TASKS_STOP (1 << 0) /* Bit 0: Stop PWM */
+
+/* TASKS_SEQSTART[n] Register */
+
+#define PWM_TASKS_SEQSTART (1 << 0) /* Bit 0: Start sequence */
+
+/* TASKS_NEXTSTEP Register */
+
+#define PWM_TASKS_NEXTSTEP (1 << 0) /* Bit 0: Next step */
+
+/* SHORTS Register */
+
+#define PWM_SHORTS_SEQEND0_STOP (1 << 0) /* Bit 0: Shortcut between
event SEQEND[0] and task STOP */
+#define PWM_SHORTS_SEQEND1_STOP (1 << 1) /* Bit 1: Shortcut between
event SEQEND[1] and task STOP */
+#define PWM_SHORTS_LOOPSDONE_SEQSTART0 (1 << 2) /* Bit 2: Shortcut between
event LOOPSDONE and task SEQSTART[0] */
+#define PWM_SHORTS_LOOPSDONE_SEQSTART1 (1 << 3) /* Bit 3: Shortcut between
event LOOPSDONE and task SEQSTART[1] */
+#define PWM_SHORTS_LOOPSDONE_STOP (1 << 4) /* Bit 4: Shortcut between
event LOOPSDONE and task STOP */
+
+/* INTEN/INTENSET/INTENCLR Register */
+
+#define PWM_INT_STOPPED (1 << 0) /* Bit 0: Interrupt for event
STOPPED */
+#define PWM_INT_SEQSTARTED0 (1 << 1) /* Bit 1: Interrupt for event
SEQSTARTED0 */
+#define PWM_INT_SEQSTARTED1 (1 << 2) /* Bit 2: Interrupt for event
SEQSTARTED2 */
+#define PWM_INT_SEQEND0 (1 << 3) /* Bit 3: Interrupt for event
SEQEND0 */
+#define PWM_INT_SEQEND1 (1 << 4) /* Bit 4: Interrupt for event
SEQEND1 */
+#define PWM_INT_PWMPERIODEND (1 << 5) /* Bit 5: Interrupt for event
PWMPERIODEND */
+#define PWM_INT_LOOPSDONE (1 << 6) /* Bit 6: Interrupt for event
LOOPSDONE */
+
+/* ENABLE Register */
+
+#define PWM_ENABLE_ENABLE (1 << 0) /* Bit 0: Enable PWM module */
+#define PWM_ENABLE_DISABLE (0 << 0) /* Bit 0: Disable PWM module */
+
+/* MODE Register */
+
+#define PWM_MODE_UP (0 << 0) /* Bit 0: Up counter,
edge-aligned PWM */
+#define PWM_MODE_UPDOWN (1 << 0) /* Bit 0: Up and down counter,
center-aligned PWM */
+
+/* COUNTERTOP Register */
+
+#define PWM_COUNTERTOP_MASK (0x7fff)
+
+/* PRESCALER Register */
+
+#define PWM_PRESCALER_SHIFT (0)
+#define PWM_PRESCALER_MASK (7 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_16MHZ (0 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_8MHZ (1 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_4MHZ (2 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_2MHZ (3 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_1MHZ (4 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_500KHZ (5 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_250KHZ (6 << PWM_PRESCALER_SHIFT)
+# define PWM_PRESCALER_125KHZ (7 << PWM_PRESCALER_SHIFT)
+
+/* DECODER Register */
+
+#define PWM_DECODER_LOAD_SHIFT (0) /* Bits 0-1: How a sequence is read
from RAM */
+#define PWM_DECODER_LOAD_MASK (3 << PWM_DECODER_LOAD_SHIFT)
+# define PWM_DECODER_LOAD_COMMON (0 << PWM_DECODER_LOAD_SHIFT)
+# define PWM_DECODER_LOAD_GROUPED (1 << PWM_DECODER_LOAD_SHIFT)
+# define PWM_DECODER_LOAD_INDIVIDUAL (2 << PWM_DECODER_LOAD_SHIFT)
+# define PWM_DECODER_LOAD_WAVEFORM (3 << PWM_DECODER_LOAD_SHIFT)
+
+#define PWM_DECODER_MODE_REFRESH (8 << 0) /* Bit 8: */
+#define PWM_DECODER_MODE_NEXTSTEP (8 << 1) /* Bit 8: */
+
+/* LOOP Register */
+
+#define PWM_LOOP_MASK (0xffff)
+
+/* SEQ[n]CNT Register */
+
+#define PWM_SEQCNT_MASK (0x7fff)
+
+/* SEQ[n]REFRESH Register */
+
+#define PWM_SEQREFRESH_MASK (0xffffff)
+
+/* SEQ[n]ENDDELAY Register */
+
+#define PWM_SEQENDDELAY_MASK (0xffffff)
+
+/* PSEL[x] Register */
+
+#define PWM_PSEL_PIN_SHIFT (0) /* Bits 0-4: OUT pin number
*/
+#define PWM_PSEL_PIN_MASK (0x1f << PWM_PSELSDA_PIN_SHIFT)
+#define PWM_PSEL_PORT_SHIFT (5) /* Bit 5: PUT port number */
+#define PWM_PSEL_PORT_MASK (0x1 << PWM_PSELSDA_PORT_SHIFT)
+#define PWM_PSEL_CONNECTED (1 << 31) /* Bit 31: Connection */
+#define PWM_PSEL_RESET (0xffffffff)
+
+/* Decoder data */
+
+#define PWM_DECODER_COMPARE_SHIFT (0)
+#define PWM_DECODER_COMPARE_MASK (0x7fff)
+#define PWM_DECODER_POL_RISING (0 << 15)
+#define PWM_DECODER_POL_FALLING (1 << 15)
+
+#endif /* __ARCH_ARM_SRC_NRF91_HARDWARE_NRF91_PWM_H */
diff --git a/arch/arm/src/nrf91/nrf91_pwm.c b/arch/arm/src/nrf91/nrf91_pwm.c
new file mode 100644
index 00000000000..964ac96ef51
--- /dev/null
+++ b/arch/arm/src/nrf91/nrf91_pwm.c
@@ -0,0 +1,706 @@
+/****************************************************************************
+ * arch/arm/src/nrf91/nrf91_pwm.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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 <assert.h>
+#include <nuttx/debug.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <arch/board/board.h>
+
+#include "arm_internal.h"
+#include "nrf91_gpio.h"
+#include "nrf91_pwm.h"
+
+#include "hardware/nrf91_pwm.h"
+#include "hardware/nrf91_utils.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Default PWM polarity */
+
+#define PWM_POLARITY_DEFAULT (PWM_DECODER_POL_FALLING)
+
+/* Sequence 0 length */
+
+#define PWM_SEQ0_LEN (4)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct nrf91_pwm_s
+{
+ const struct pwm_ops_s *ops; /* PWM operations */
+ uint32_t base; /* Base address of PWM register */
+ uint32_t frequency; /* Current frequency setting */
+ uint32_t cntrtop; /* Counter top */
+ uint32_t ch0_pin; /* Channel 1 pin */
+ uint32_t ch1_pin; /* Channel 2 pin */
+ uint32_t ch2_pin; /* Channel 3 pin */
+ uint32_t ch3_pin; /* Channel 4 pin */
+
+ /* Sequence 0 */
+
+ uint16_t seq0[PWM_SEQ0_LEN];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* PWM Register access */
+
+static inline void nrf91_pwm_putreg(struct nrf91_pwm_s *priv,
+ uint32_t offset,
+ uint32_t value);
+static inline uint32_t nrf91_pwm_getreg(struct nrf91_pwm_s *priv,
+ uint32_t offset);
+
+/* PWM helpers */
+
+static int nrf91_pwm_configure(struct nrf91_pwm_s *priv);
+static int nrf91_pwm_duty(struct nrf91_pwm_s *priv, uint8_t chan,
+ ub16_t duty);
+static int nrf91_pwm_freq(struct nrf91_pwm_s *priv, uint32_t freq);
+
+/* PWM driver methods */
+
+static int nrf91_pwm_setup(struct pwm_lowerhalf_s *dev);
+static int nrf91_pwm_shutdown(struct pwm_lowerhalf_s *dev);
+static int nrf91_pwm_start(struct pwm_lowerhalf_s *dev,
+ const struct pwm_info_s *info);
+static int nrf91_pwm_stop(struct pwm_lowerhalf_s *dev);
+static int nrf91_pwm_ioctl(struct pwm_lowerhalf_s *dev,
+ int cmd, unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* This is the list of lower half PWM driver methods used by the upper half
+ * driver.
+ */
+
+static const struct pwm_ops_s g_nrf91_pwmops =
+{
+ .setup = nrf91_pwm_setup,
+ .shutdown = nrf91_pwm_shutdown,
+ .start = nrf91_pwm_start,
+ .stop = nrf91_pwm_stop,
+ .ioctl = nrf91_pwm_ioctl,
+};
+
+#ifdef CONFIG_NRF91_PWM0
+/* PWM 0 */
+
+struct nrf91_pwm_s g_nrf91_pwm0 =
+{
+ .ops = &g_nrf91_pwmops,
+ .base = NRF91_PWM0_BASE,
+#ifdef CONFIG_NRF91_PWM0_CH0
+ .ch0_pin = NRF91_PWM0_CH0_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM0_CH1
+ .ch1_pin = NRF91_PWM0_CH1_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM0_CH2
+ .ch2_pin = NRF91_PWM0_CH2_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM0_CH3
+ .ch3_pin = NRF91_PWM0_CH3_PIN,
+#endif
+};
+#endif
+
+#ifdef CONFIG_NRF91_PWM1
+/* PWM 1 */
+
+struct nrf91_pwm_s g_nrf91_pwm1 =
+{
+ .ops = &g_nrf91_pwmops,
+ .base = NRF91_PWM1_BASE,
+#ifdef CONFIG_NRF91_PWM1_CH0
+ .ch0_pin = NRF91_PWM1_CH0_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM1_CH1
+ .ch1_pin = NRF91_PWM1_CH1_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM1_CH2
+ .ch2_pin = NRF91_PWM1_CH2_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM1_CH3
+ .ch3_pin = NRF91_PWM1_CH3_PIN,
+#endif
+};
+#endif
+
+#ifdef CONFIG_NRF91_PWM2
+/* PWM 2 */
+
+struct nrf91_pwm_s g_nrf91_pwm2 =
+{
+ .ops = &g_nrf91_pwmops,
+ .base = NRF91_PWM2_BASE,
+#ifdef CONFIG_NRF91_PWM2_CH0
+ .ch0_pin = NRF91_PWM2_CH0_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM2_CH1
+ .ch1_pin = NRF91_PWM2_CH1_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM2_CH2
+ .ch2_pin = NRF91_PWM2_CH2_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM2_CH3
+ .ch3_pin = NRF91_PWM2_CH3_PIN,
+#endif
+};
+#endif
+
+#ifdef CONFIG_NRF91_PWM3
+/* PWM 2 */
+
+struct nrf91_pwm_s g_nrf91_pwm3 =
+{
+ .ops = &g_nrf91_pwmops,
+ .base = NRF91_PWM3_BASE,
+#ifdef CONFIG_NRF91_PWM3_CH0
+ .ch0_pin = NRF91_PWM3_CH0_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM3_CH1
+ .ch1_pin = NRF91_PWM3_CH1_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM3_CH2
+ .ch2_pin = NRF91_PWM3_CH2_PIN,
+#endif
+#ifdef CONFIG_NRF91_PWM3_CH3
+ .ch3_pin = NRF91_PWM3_CH3_PIN,
+#endif
+};
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nrf91_pwm_putreg
+ *
+ * Description:
+ * Put a 32-bit register value by offset
+ *
+ ****************************************************************************/
+
+static inline void nrf91_pwm_putreg(struct nrf91_pwm_s *priv,
+ uint32_t offset,
+ uint32_t value)
+{
+ putreg32(value, priv->base + offset);
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_getreg
+ *
+ * Description:
+ * Get a 32-bit register value by offset
+ *
+ ****************************************************************************/
+
+static inline uint32_t nrf91_pwm_getreg(struct nrf91_pwm_s *priv,
+ uint32_t offset)
+{
+ return getreg32(priv->base + offset);
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_configure
+ *
+ * Description:
+ * Configure PWM
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_configure(struct nrf91_pwm_s *priv)
+{
+ uint32_t regval = 0;
+ int ret = OK;
+
+ DEBUGASSERT(priv);
+
+ /* Configure PWM mode */
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_MODE_OFFSET, PWM_MODE_UP);
+
+ /* Configure decoder */
+
+ regval = PWM_DECODER_LOAD_INDIVIDUAL | PWM_DECODER_MODE_REFRESH;
+ nrf91_pwm_putreg(priv, NRF91_PWM_DECODER_OFFSET, regval);
+
+ /* Configure sequence 0 */
+
+ regval = (uint32_t)priv->seq0;
+ DEBUGASSERT(nrf91_easydma_valid(regval));
+ nrf91_pwm_putreg(priv, NRF91_PWM_SEQ0PTR_OFFSET, regval);
+
+ regval = PWM_SEQ0_LEN;
+ nrf91_pwm_putreg(priv, NRF91_PWM_SEQ0CNT_OFFSET, regval);
+
+ regval = 0;
+ nrf91_pwm_putreg(priv, NRF91_PWM_SEQ0REFRESH_OFFSET, regval);
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_duty
+ *
+ * Description:
+ * Configure PWM duty
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_duty(struct nrf91_pwm_s *priv, uint8_t chan,
+ ub16_t duty)
+{
+ uint16_t compare = 0;
+
+ DEBUGASSERT(priv);
+
+ pwminfo("PWM channel: %d duty: %" PRId32 "\n", chan, duty);
+
+ /* Get compare
+ *
+ * duty cycle = compare / reload (fractional value)
+ */
+
+ compare = b16toi(duty * priv->cntrtop + b16HALF);
+
+ /* Configure channel sequence */
+
+ priv->seq0[chan] = PWM_POLARITY_DEFAULT | compare;
+
+ pwminfo("seq0[%d]: %d %d\n", chan, compare, priv->seq0[chan]);
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_freq
+ *
+ * Description:
+ * Configure PWM frequency
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_freq(struct nrf91_pwm_s *priv, uint32_t freq)
+{
+ uint32_t regval = 0;
+ uint32_t pwm_clk = 0;
+ uint32_t top = 0;
+ uint32_t prescaler = 0;
+ uint64_t tmp = 0;
+ int ret = OK;
+
+ DEBUGASSERT(priv);
+
+ /* Get best prescaler */
+
+ tmp = PWM_COUNTERTOP_MASK * freq;
+
+ if (tmp >= 16000000)
+ {
+ pwm_clk = 16000000;
+ prescaler = PWM_PRESCALER_16MHZ;
+ }
+ else if (tmp >= 8000000)
+ {
+ pwm_clk = 8000000;
+ prescaler = PWM_PRESCALER_8MHZ;
+ }
+ else if (tmp >= 4000000)
+ {
+ pwm_clk = 4000000;
+ prescaler = PWM_PRESCALER_4MHZ;
+ }
+ else if (tmp >= 2000000)
+ {
+ pwm_clk = 2000000;
+ prescaler = PWM_PRESCALER_2MHZ;
+ }
+ else if (tmp >= 1000000)
+ {
+ pwm_clk = 1000000;
+ prescaler = PWM_PRESCALER_1MHZ;
+ }
+ else if (tmp >= 500000)
+ {
+ pwm_clk = 500000;
+ prescaler = PWM_PRESCALER_500KHZ;
+ }
+ else if (tmp >= 250000)
+ {
+ pwm_clk = 250000;
+ prescaler = PWM_PRESCALER_250KHZ;
+ }
+ else
+ {
+ pwm_clk = 125000;
+ prescaler = PWM_PRESCALER_125KHZ;
+ }
+
+ /* Configure prescaler */
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_PRESCALER_OFFSET, prescaler);
+
+ /* Get counter max */
+
+ top = pwm_clk / freq;
+ if (top < 2)
+ {
+ top = 1;
+ }
+ else if (top > PWM_COUNTERTOP_MASK)
+ {
+ top = PWM_COUNTERTOP_MASK;
+ }
+ else
+ {
+ top = top - 1;
+ }
+
+ /* Configure counter max */
+
+ regval = top;
+ nrf91_pwm_putreg(priv, NRF91_PWM_COUNTERTOP_OFFSET, regval);
+
+ priv->cntrtop = top;
+
+ pwminfo("PWM frequency: %" PRId32 " pwm_clk: %" PRId32
+ " pwm_prescaler: %" PRId32 " top: %" PRId32 "\n",
+ freq, pwm_clk, prescaler, top);
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: nrf91_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.
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_setup(struct pwm_lowerhalf_s *dev)
+{
+ struct nrf91_pwm_s *priv = (struct nrf91_pwm_s *)dev;
+ int ret = OK;
+ uint32_t regval = 0;
+ uint32_t pin = 0;
+ uint32_t port = 0;
+
+ DEBUGASSERT(dev);
+
+ /* Configure channels */
+
+ if (priv->ch0_pin != 0)
+ {
+ nrf91_gpio_config(priv->ch0_pin);
+
+ pin = GPIO_PIN_DECODE(priv->ch0_pin);
+ port = GPIO_PORT_DECODE(priv->ch0_pin);
+
+ regval = (port << PWM_PSEL_PORT_SHIFT);
+ regval |= (pin << PWM_PSEL_PIN_SHIFT);
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_PSEL0_OFFSET, regval);
+ }
+
+ if (priv->ch1_pin != 0)
+ {
+ nrf91_gpio_config(priv->ch1_pin);
+
+ pin = GPIO_PIN_DECODE(priv->ch1_pin);
+ port = GPIO_PORT_DECODE(priv->ch1_pin);
+
+ regval = (port << PWM_PSEL_PORT_SHIFT);
+ regval |= (pin << PWM_PSEL_PIN_SHIFT);
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_PSEL1_OFFSET, regval);
+ }
+
+ if (priv->ch2_pin != 0)
+ {
+ nrf91_gpio_config(priv->ch2_pin);
+
+ pin = GPIO_PIN_DECODE(priv->ch2_pin);
+ port = GPIO_PORT_DECODE(priv->ch2_pin);
+
+ regval = (port << PWM_PSEL_PORT_SHIFT);
+ regval |= (pin << PWM_PSEL_PIN_SHIFT);
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_PSEL2_OFFSET, regval);
+ }
+
+ if (priv->ch3_pin != 0)
+ {
+ nrf91_gpio_config(priv->ch3_pin);
+
+ pin = GPIO_PIN_DECODE(priv->ch3_pin);
+ port = GPIO_PORT_DECODE(priv->ch3_pin);
+
+ regval = (port << PWM_PSEL_PORT_SHIFT);
+ regval |= (pin << PWM_PSEL_PIN_SHIFT);
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_PSEL3_OFFSET, regval);
+ }
+
+ /* Configure PWM */
+
+ ret = nrf91_pwm_configure(priv);
+ if (ret < 0)
+ {
+ pwmerr("ERROR: nrf91_pwm_configure failed %d\n", ret);
+ goto errout;
+ }
+
+ /* Enable PWM */
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_ENABLE_OFFSET, 1);
+
+errout:
+ return ret;
+}
+
+/****************************************************************************
+ * Name: nrf91_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
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_shutdown(struct pwm_lowerhalf_s *dev)
+{
+ struct nrf91_pwm_s *priv = (struct nrf91_pwm_s *)dev;
+ int ret = OK;
+
+ DEBUGASSERT(dev);
+
+ /* Disable PWM */
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_ENABLE_OFFSET, 0);
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_start
+ *
+ * Description:
+ * (Re-)initialize the PWM and start the pulsed output
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_start(struct pwm_lowerhalf_s *dev,
+ const struct pwm_info_s *info)
+{
+ struct nrf91_pwm_s *priv = (struct nrf91_pwm_s *)dev;
+ int ret = OK;
+ int i = 0;
+
+ DEBUGASSERT(dev);
+
+ /* If frequency has not changed we just update duty */
+
+ if (info->frequency != priv->frequency)
+ {
+ /* Update frequency */
+
+ ret = nrf91_pwm_freq(priv, info->frequency);
+
+ if (ret == OK)
+ {
+ priv->frequency = info->frequency;
+ }
+ }
+
+ for (i = 0; ret == OK && i < CONFIG_PWM_NCHANNELS; i++)
+ {
+ /* Break the loop if all following channels are not configured */
+
+ if (info->channels[i].channel == -1)
+ {
+ break;
+ }
+
+ /* Set output if channel configured */
+
+ if (info->channels[i].channel != 0)
+ {
+ ret = nrf91_pwm_duty(priv,
+ (info->channels[i].channel - 1),
+ info->channels[i].duty);
+ }
+ }
+
+ /* Start sequence 0 */
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_TASKS_SEQSTART0_OFFSET, 1);
+
+ /* Wait for sequence started */
+
+ while (nrf91_pwm_getreg(priv, NRF91_PWM_EVENTS_SEQSTARTED0_OFFSET) != 1);
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_stop
+ *
+ * Description:
+ * Stop the PWM
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_stop(struct pwm_lowerhalf_s *dev)
+{
+ struct nrf91_pwm_s *priv = (struct nrf91_pwm_s *)dev;
+
+ DEBUGASSERT(dev);
+
+ /* Stop PWM */
+
+ nrf91_pwm_putreg(priv, NRF91_PWM_TASKS_STOP_OFFSET, 1);
+
+ /* Wait for PWM stopped */
+
+ while (nrf91_pwm_getreg(priv, NRF91_PWM_EVENTS_STOPPED_OFFSET) != 1);
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: nrf91_pwm_ioctl
+ *
+ * Description:
+ * Lower-half logic may support platform-specific ioctl commands
+ *
+ ****************************************************************************/
+
+static int nrf91_pwm_ioctl(struct pwm_lowerhalf_s *dev,
+ int cmd, unsigned long arg)
+{
+ struct nrf91_pwm_s *priv = (struct nrf91_pwm_s *)dev;
+
+ DEBUGASSERT(dev);
+
+ /* There are no platform-specific ioctl commands */
+
+ UNUSED(priv);
+
+ return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Function
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nrf91_pwminitialize
+ *
+ * Description:
+ * Initialize one timer for use with the upper_level PWM driver.
+ *
+ * Input Parameters:
+ * pwm - A number identifying the pwm instance.
+ *
+ * Returned Value:
+ * On success, a pointer to the NRF91 lower half PWM driver is returned.
+ * NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *nrf91_pwminitialize(int pwm)
+{
+ struct nrf91_pwm_s *lower = NULL;
+
+ pwminfo("Initialize PWM%u\n", pwm);
+
+ switch (pwm)
+ {
+#ifdef CONFIG_NRF91_PWM0
+ case 0:
+ {
+ lower = &g_nrf91_pwm0;
+ break;
+ }
+#endif
+
+#ifdef CONFIG_NRF91_PWM1
+ case 1:
+ {
+ lower = &g_nrf91_pwm1;
+ break;
+ }
+#endif
+
+#ifdef CONFIG_NRF91_PWM2
+ case 2:
+ {
+ lower = &g_nrf91_pwm2;
+ break;
+ }
+#endif
+
+#ifdef CONFIG_NRF91_PWM3
+ case 3:
+ {
+ lower = &g_nrf91_pwm3;
+ break;
+ }
+#endif
+
+ default:
+ {
+ pwmerr("ERROR: No such PWM device %d\n", pwm);
+ lower = NULL;
+ goto errout;
+ }
+ }
+
+errout:
+ return (struct pwm_lowerhalf_s *)lower;
+}
diff --git a/arch/arm/src/nrf91/nrf91_pwm.h b/arch/arm/src/nrf91/nrf91_pwm.h
new file mode 100644
index 00000000000..19e72781876
--- /dev/null
+++ b/arch/arm/src/nrf91/nrf91_pwm.h
@@ -0,0 +1,133 @@
+/****************************************************************************
+ * arch/arm/src/nrf91/nrf91_pwm.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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_NRF91_NRF91_PWM_H
+#define __ARCH_ARM_SRC_NRF91_NRF91_PWM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/timers/pwm.h>
+
+#include "chip.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+/* Enable the specified PWM channel if multichannel PWM is disabled */
+
+#ifndef CONFIG_NRF91_PWM_MULTICHAN
+
+# ifdef CONFIG_NRF91_PWM0
+# if !defined(CONFIG_NRF91_PWM0_CHANNEL)
+# error "CONFIG_NRF91_PWM0_CHANNEL must be provided"
+# elif CONFIG_NRF91_PWM0_CHANNEL == 0
+# define CONFIG_NRF91_PWM0_CH0 1
+# elif CONFIG_NRF91_PWM0_CHANNEL == 1
+# define CONFIG_NRF91_PWM0_CH1 1
+# elif CONFIG_NRF91_PWM0_CHANNEL == 2
+# define CONFIG_NRF91_PWM0_CH2 1
+# elif CONFIG_NRF91_PWM0_CHANNEL == 3
+# define CONFIG_NRF91_PWM0_CH3 1
+# else
+# error "Unsupported value of CONFIG_NRF91_PWM0_CHANNEL"
+# endif
+# endif
+
+# ifdef CONFIG_NRF91_PWM1
+# if !defined(CONFIG_NRF91_PWM1_CHANNEL)
+# error "CONFIG_NRF91_PWM1_CHANNEL must be provided"
+# elif CONFIG_NRF91_PWM1_CHANNEL == 0
+# define CONFIG_NRF91_PWM1_CH0 1
+# elif CONFIG_NRF91_PWM1_CHANNEL == 1
+# define CONFIG_NRF91_PWM1_CH1 1
+# elif CONFIG_NRF91_PWM1_CHANNEL == 2
+# define CONFIG_NRF91_PWM1_CH2 1
+# elif CONFIG_NRF91_PWM1_CHANNEL == 3
+# define CONFIG_NRF91_PWM1_CH3 1
+# else
+# error "Unsupported value of CONFIG_NRF91_PWM1_CHANNEL"
+# endif
+# endif
+
+# ifdef CONFIG_NRF91_PWM2
+# if !defined(CONFIG_NRF91_PWM2_CHANNEL)
+# error "CONFIG_NRF91_PWM2_CHANNEL must be provided"
+# elif CONFIG_NRF91_PWM2_CHANNEL == 0
+# define CONFIG_NRF91_PWM2_CH0 1
+# elif CONFIG_NRF91_PWM2_CHANNEL == 1
+# define CONFIG_NRF91_PWM2_CH1 1
+# elif CONFIG_NRF91_PWM2_CHANNEL == 2
+# define CONFIG_NRF91_PWM2_CH2 1
+# elif CONFIG_NRF91_PWM2_CHANNEL == 3
+# define CONFIG_NRF91_PWM2_CH3 1
+# else
+# error "Unsupported value of CONFIG_NRF91_PWM2_CHANNEL"
+# endif
+# endif
+
+# ifdef CONFIG_NRF91_PWM3
+# if !defined(CONFIG_NRF91_PWM3_CHANNEL)
+# error "CONFIG_NRF91_PWM3_CHANNEL must be provided"
+# elif CONFIG_NRF91_PWM3_CHANNEL == 0
+# define CONFIG_NRF91_PWM3_CH0 1
+# elif CONFIG_NRF91_PWM3_CHANNEL == 1
+# define CONFIG_NRF91_PWM3_CH1 1
+# elif CONFIG_NRF91_PWM3_CHANNEL == 2
+# define CONFIG_NRF91_PWM3_CH2 1
+# elif CONFIG_NRF91_PWM3_CHANNEL == 3
+# define CONFIG_NRF91_PWM3_CH3 1
+# else
+# error "Unsupported value of CONFIG_NRF91_PWM3_CHANNEL"
+# endif
+# endif
+
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nrf91_pwminitialize
+ *
+ * Description:
+ * Initialize one timer for use with the upper_level PWM driver.
+ *
+ * Input Parameters:
+ * pwm - A number identifying the pwm instance.
+ *
+ * Returned Value:
+ * On success, a pointer to the NRF91 lower half PWM driver is returned.
+ * NULL is returned on any failure.
+ *
+ ****************************************************************************/
+
+struct pwm_lowerhalf_s *nrf91_pwminitialize(int pwm);
+
+#endif /* __ARCH_ARM_SRC_NRF91_NRF91_PWM_H */