Skip to content

drivers: pwm: extend API to support interrupts #93275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions drivers/pwm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ config PWM_CAPTURE
This option extends the Zephyr PWM API with the ability to capture PWM
period/pulse widths.

config PWM_INTERRUPT
bool "Provide API for PWM interrupt"
help
This option extends the Zephyr PWM API with the abilitiy to configure a
PWM interrupt.

source "drivers/pwm/Kconfig.b91"

source "drivers/pwm/Kconfig.cc13xx_cc26xx_timer"
Expand Down
134 changes: 126 additions & 8 deletions drivers/pwm/pwm_sam.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control/atmel_sam_pmc.h>

#ifdef CONFIG_PWM_INTERRUPT
#include <zephyr/irq.h>
#endif

#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(pwm_sam, CONFIG_PWM_LOG_LEVEL);
Expand All @@ -35,10 +39,19 @@ struct sam_pwm_config {
const struct pinctrl_dev_config *pcfg;
uint8_t prescaler;
uint8_t divider;
#ifdef CONFIG_PWM_INTERRUPT
void (*irq_config)(void);
#endif /* CONFIG_PWM_INTERRUPT */
};

struct sam_pwm_data {
#ifdef CONFIG_PWM_INTERRUPT
pwm_interrupt_callback_handler_t callback;
void *user_data;
#endif /* CONFIG_PWM_INTERRUPT */
};

static int sam_pwm_get_cycles_per_sec(const struct device *dev,
uint32_t channel, uint64_t *cycles)
static int sam_pwm_get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles)
{
const struct sam_pwm_config *config = dev->config;
uint8_t prescaler = config->prescaler;
Expand Down Expand Up @@ -99,6 +112,88 @@ static int sam_pwm_set_cycles(const struct device *dev, uint32_t channel,
return 0;
}

#ifdef CONFIG_PWM_INTERRUPT
static void sam_pwm_isr(const struct device *dev)
{
const struct sam_pwm_config *config = dev->config;
struct sam_pwm_data *data = dev->data;
Pwm *const pwm = config->regs;
pwm_flags_t flags;

uint32_t status = pwm->PWM_ISR1;

if (data->callback == NULL) {
return;
}

for (uint32_t i = 0; i < PWMCHNUM_NUMBER; i++) {
flags = 0;
if ((status & (PWM_ISR1_CHID0 << i)) != 0) {
flags |= PWM_INTERRUPT_TYPE_PERIOD;
}
if ((status & (PWM_ISR1_FCHID0 << i)) != 0) {
flags |= PWM_INTERRUPT_TYPE_FAULT;
}

if (flags > 0) {
data->callback(dev, i, flags, data->user_data);
}
}
}

static int sam_pwm_configure_interrupt(const struct device *dev,
pwm_interrupt_callback_handler_t cb, void *user_data)
{
struct sam_pwm_data *data = dev->data;

data->callback = cb;
data->user_data = user_data;

return 0;
}

static int sam_pwm_enable_interrupt(const struct device *dev, uint32_t channel, pwm_flags_t flags)
{
const struct sam_pwm_config *config = dev->config;
uint32_t pwm_ier1 = 0;

Pwm *const pwm = config->regs;

/* Dummy read to clear status register*/
(void)pwm->PWM_ISR1;

if ((flags & PWM_INTERRUPT_TYPE_PERIOD) != 0) {
pwm_ier1 |= (PWM_ENA_CHID0 << channel);
}
if ((flags & PWM_INTERRUPT_TYPE_FAULT) != 0) {
pwm_ier1 |= (PWM_IER1_FCHID0 << channel);
}

pwm->PWM_IER1 = pwm_ier1;

return 0;
}

static int sam_pwm_disable_interrupt(const struct device *dev, uint32_t channel, pwm_flags_t flags)
{
const struct sam_pwm_config *config = dev->config;
uint32_t pwm_idr1 = 0;

Pwm *const pwm = config->regs;

if ((flags & PWM_INTERRUPT_TYPE_PERIOD) != 0) {
pwm_idr1 |= (PWM_IDR1_CHID0 << channel);
}
if ((flags & PWM_INTERRUPT_TYPE_FAULT) != 0) {
pwm_idr1 |= (PWM_IDR1_FCHID0 << channel);
}

pwm->PWM_IDR1 = pwm_idr1;

return 0;
}
#endif /* CONFIG_PWM_INTERRUPT */

static int sam_pwm_init(const struct device *dev)
{
const struct sam_pwm_config *config = dev->config;
Expand All @@ -110,6 +205,10 @@ static int sam_pwm_init(const struct device *dev)

/* FIXME: way to validate prescaler & divider */

#ifdef CONFIG_PWM_INTERRUPT
config->irq_config();
#endif

/* Enable PWM clock in PMC */
(void)clock_control_on(SAM_DT_PMC_CONTROLLER,
(clock_control_subsys_t)&config->clock_cfg);
Expand All @@ -128,23 +227,42 @@ static int sam_pwm_init(const struct device *dev)
static DEVICE_API(pwm, sam_pwm_driver_api) = {
.set_cycles = sam_pwm_set_cycles,
.get_cycles_per_sec = sam_pwm_get_cycles_per_sec,
#ifdef CONFIG_PWM_INTERRUPT
.configure_interrupt = sam_pwm_configure_interrupt,
.enable_interrupt = sam_pwm_enable_interrupt,
.disable_interrupt = sam_pwm_disable_interrupt,
#endif /* CONFIG_PWM_INTERRUPT */
};

#define SAM_PWM_INTERRUPT_INIT(inst) \
static void sam_pwm_irq_config_##inst(void) \
{ \
IRQ_CONNECT(DT_INST_IRQN(inst), 0, sam_pwm_isr, \
DEVICE_DT_INST_GET(inst), 0); \
irq_enable(DT_INST_IRQN(inst)); \
}

#define SAM_INST_INIT(inst) \
PINCTRL_DT_INST_DEFINE(inst); \
\
IF_ENABLED(CONFIG_PWM_INTERRUPT, (SAM_PWM_INTERRUPT_INIT(inst)))\
\
static const struct sam_pwm_config sam_pwm_config_##inst = { \
.regs = (Pwm *)DT_INST_REG_ADDR(inst), \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(inst), \
.prescaler = DT_INST_PROP(inst, prescaler), \
.divider = DT_INST_PROP(inst, divider), \
IF_ENABLED(CONFIG_PWM_INTERRUPT, \
(.irq_config = sam_pwm_irq_config_##inst,)) \
}; \
\
DEVICE_DT_INST_DEFINE(inst, \
&sam_pwm_init, NULL, \
NULL, &sam_pwm_config_##inst, \
POST_KERNEL, \
CONFIG_PWM_INIT_PRIORITY, \
&sam_pwm_driver_api);
static struct sam_pwm_data sam_pwm_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, &sam_pwm_init, NULL, \
&sam_pwm_data_##inst, \
&sam_pwm_config_##inst, POST_KERNEL, \
CONFIG_PWM_INIT_PRIORITY, \
&sam_pwm_driver_api);

DT_INST_FOREACH_STATUS_OKAY(SAM_INST_INIT)
153 changes: 153 additions & 0 deletions include/zephyr/drivers/pwm.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,30 @@ extern "C" {

/** @} */

/**
* @name PWM interrupt configuration flags
* @anchor PWM_INTERRUPT_FLAGS
* @{
*/

#define PWM_INTERRUPT_TYPE_SHIFT 0U

/** Configure the interrupt to trigger at the end of each PWM period */
#define PWM_INTERRUPT_TYPE_PERIOD (1U << PWM_INTERRUPT_TYPE_SHIFT)

/** Configure the interrupt to trigger at a fault */
#define PWM_INTERRUPT_TYPE_FAULT (2U << PWM_INTERRUPT_TYPE_SHIFT)

/** @} */

/**
* @brief Provides a type to hold PWM configuration flags.
*
* The lower 8 bits are used for standard flags.
* The upper 8 bits are reserved for SoC specific flags.
*
* @see @ref PWM_CAPTURE_FLAGS.
* @see @ref PWM_INTERRUPT_FLAGS.
*/

typedef uint16_t pwm_flags_t;
Expand Down Expand Up @@ -394,6 +411,23 @@ typedef void (*pwm_capture_callback_handler_t)(const struct device *dev,
uint32_t pulse_cycles,
int status, void *user_data);

/**
* @brief PWM interrupt callback handler function signature
*
* @note The callback handler will be called in interrupt context.
*
* @note @kconfig{CONFIG_PWM_INTERRUPT} must be selected to enable PWM interrupt
* support.
*
* @param[in] dev PWM device instance.
* @param channel PWM channel.
* @param flags Interrupt type. See @ref PWM_INTERRUPT_FLAGS
* @param user_data User data passed to pwm_configure_interrupt()
*
*/
typedef void (*pwm_interrupt_callback_handler_t)(const struct device *dev, uint32_t channel,
pwm_flags_t flags, void *user_data);

/** @cond INTERNAL_HIDDEN */
/**
* @brief PWM driver API call to configure PWM pin period and pulse width.
Expand Down Expand Up @@ -434,6 +468,29 @@ typedef int (*pwm_disable_capture_t)(const struct device *dev,
uint32_t channel);
#endif /* CONFIG_PWM_CAPTURE */

#ifdef CONFIG_PWM_INTERRUPT
/**
* @brief PWM driver API to configure PWM interrupts
* @see pwm_configure_interrupt() for argument description
*/
typedef int (*pwm_configure_interrupt_t)(const struct device *dev,
pwm_interrupt_callback_handler_t cb, void *user_data);

/**
* @brief PWM driver API to enable PWM interrupts
* @see pwm_enable_interrupt() for arguments description
*/
typedef int (*pwm_enable_interrupt_t)(const struct device *dev, uint32_t channel,
pwm_flags_t flags);

/**
* @brief PWM driver API to disable PWM interrupts
* @see pwm_disable_interrupt() for arguments description
*/
typedef int (*pwm_disable_interrupt_t)(const struct device *dev, uint32_t channel,
pwm_flags_t flags);
#endif /* CONFIG_PWM_INTERRUPT */

/** @brief PWM driver API definition. */
__subsystem struct pwm_driver_api {
pwm_set_cycles_t set_cycles;
Expand All @@ -443,6 +500,11 @@ __subsystem struct pwm_driver_api {
pwm_enable_capture_t enable_capture;
pwm_disable_capture_t disable_capture;
#endif /* CONFIG_PWM_CAPTURE */
#ifdef CONFIG_PWM_INTERRUPT
pwm_configure_interrupt_t configure_interrupt;
pwm_enable_interrupt_t enable_interrupt;
pwm_disable_interrupt_t disable_interrupt;
#endif /* CONFIG_PWM_INTERRUPT */
};
/** @endcond */

Expand Down Expand Up @@ -927,6 +989,97 @@ static inline int pwm_capture_nsec(const struct device *dev, uint32_t channel,
return 0;
}

#if defined(CONFIG_PWM_INTERRUPT) || defined(__DOXYGEN__)
/**
* @brief Configure pwm interrupt callback
*
* After configuring the callback, the interrupts can be enabled or disabled
* using pwm_enable_interrupt() and pwm_disable_interrupt
*
* @note @kconfig{CONFIG_PWM_INTERRUPT} must be selected for this function to be
* available.
*
* @param[in] dev PWM device instance.
* @param[in] cb Callback handler function to be called upon interrupt
* @param[in] user_data User data to pass to the application callback handler
* function
*
* @retval 0 If successful
*/
static inline int pwm_configure_interrupt(const struct device *dev,
pwm_interrupt_callback_handler_t cb, void *user_data)
{
const struct pwm_driver_api *api = (const struct pwm_driver_api *)dev->api;

if (api->configure_interrupt == NULL) {
return -ENOSYS;
}

return api->configure_interrupt(dev, cb, user_data);
}
#endif /* CONFIG_PWM_INTERRUPT */

/**
* @brief Enable the PWM interrupts
*
* A callback must be configured using pwm_configure_interrupt() prior to
* calling this function
*
* @note @kconfig{CONFIG_PWM_INTERRUPT} must be selected for this function to be
* available.
*
* @param[in] dev PWM device instance.
* @param channel PWM channel.
* @param flags Interrupt(s) to be enabled. See @ref PWM_INTERRUPT_FLAGS
*
* @retval 0 If successful
*/
__syscall int pwm_enable_interrupt(const struct device *dev, uint32_t channel, pwm_flags_t flags);

#ifdef CONFIG_PWM_INTERRUPT
static inline int z_impl_pwm_enable_interrupt(const struct device *dev, uint32_t channel,
pwm_flags_t flags)
{
const struct pwm_driver_api *api =
(const struct pwm_driver_api *)dev->api;

if (api->enable_interrupt == NULL) {
return -ENOSYS;
}

return api->enable_interrupt(dev, channel, flags);
}
#endif /* CONFIG_PWM_INTERRUPT */

/**
* @brief Disable the PWM interrupts
*
* @note @kconfig{CONFIG_PWM_INTERRUPT} must be selected for this function to be
* available.
*
* @param[in] dev PWM device instance.
* @param channel PWM channel.
* @param flags Interrupt(s) to be disabled. See @ref PWM_INTERRUPT_FLAGS
*
* @retval 0 If successful
*/
__syscall int pwm_disable_interrupt(const struct device *dev, uint32_t channel, pwm_flags_t flags);

#ifdef CONFIG_PWM_INTERRUPT
static inline int z_impl_pwm_disable_interrupt(const struct device *dev, uint32_t channel,
pwm_flags_t flags)
{
const struct pwm_driver_api *api =
(const struct pwm_driver_api *)dev->api;

if (api->disable_interrupt == NULL) {
return -ENOSYS;
}

return api->disable_interrupt(dev, channel, flags);
}
#endif /* CONFIG_PWM_INTERRUPT */

/**
* @brief Validate that the PWM device is ready.
*
Expand Down
2 changes: 2 additions & 0 deletions tests/drivers/build_all/pwm/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ tests:
platform_allow:
- sam_e70_xplained/same70q21
- sam_v71_xult/samv71q21b
extra_configs:
- CONFIG_PWM_INTERRUPT=y
drivers.pwm.stm32.build:
platform_allow: disco_l475_iot1
drivers.pwm.xec.build:
Expand Down
Loading