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 00000000000..a5553871d58 --- /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 00000000000..a28dc8ad2e5 --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#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 00000000000..18a5a88a5d0 --- /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 +#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 00000000000..7b86ea3ded8 --- /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 + +/**************************************************************************** + * 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 00000000000..902c75418ca --- /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 + +#include +#include +#include + +#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 */ +