Skip to content
Merged
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
10 changes: 10 additions & 0 deletions boards/arm/twr_ke18f/pinmux.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ static int twr_ke18f_pinmux_init(const struct device *dev)
pinmux_pin_set(portd, 16, PORT_PCR_MUX(kPORT_MuxAsGpio));
#endif

#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(ftm2), nxp_kinetis_ftm_pwm, okay) && CONFIG_PWM
/* PWM output on J20 pin 5 */
pinmux_pin_set(porte, 15, PORT_PCR_MUX(kPORT_MuxAlt4));
#endif

#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(ftm3), nxp_kinetis_ftm_pwm, okay) && CONFIG_PWM
/* User LEDs as PWM */
pinmux_pin_set(portc, 10, PORT_PCR_MUX(kPORT_MuxAlt2));
Expand All @@ -59,6 +64,11 @@ static int twr_ke18f_pinmux_init(const struct device *dev)
pinmux_pin_set(portc, 13, PORT_PCR_MUX(kPORT_MuxAsGpio));
#endif

#if DT_NODE_HAS_STATUS(DT_NODELABEL(pwt), okay) && CONFIG_PWM_CAPTURE
/* PWM capture input on J20 pin 8 */
pinmux_pin_set(porte, 11, PORT_PCR_MUX(kPORT_MuxAlt2));
#endif

/* Buttons */
pinmux_pin_set(portd, 3, PORT_PCR_MUX(kPORT_MuxAsGpio));
pinmux_pin_set(portd, 6, PORT_PCR_MUX(kPORT_MuxAsGpio));
Expand Down
1 change: 1 addition & 0 deletions drivers/pwm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_TPM pwm_mcux_tpm.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SAM0_TCC pwm_sam0_tcc.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NPCX pwm_npcx.c)
zephyr_library_sources_ifdef(CONFIG_PWM_XLNX_AXI_TIMER pwm_xlnx_axi_timer.c)
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_PWT pwm_mcux_pwt.c)

zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)
Expand Down
2 changes: 2 additions & 0 deletions drivers/pwm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ source "drivers/pwm/Kconfig.npcx"

source "drivers/pwm/Kconfig.xlnx"

source "drivers/pwm/Kconfig.mcux_pwt"

endif # PWM
10 changes: 10 additions & 0 deletions drivers/pwm/Kconfig.mcux_pwt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright 2021 Vestas Wind Systems A/S
# SPDX-License-Identifier: Apache-2.0

# MCUX PWT PWM

config PWM_MCUX_PWT
bool "MCUX PWT PWM capture driver"
depends on HAS_MCUX_PWT && CLOCK_CONTROL && PWM_CAPTURE
help
Enable the MCUX Pulse Width Timer (PWT) PWM capture driver.
347 changes: 347 additions & 0 deletions drivers/pwm/pwm_mcux_pwt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
/*
* Copyright (c) 2021 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT nxp_kinetis_pwt

#include <drivers/clock_control.h>
#include <errno.h>
#include <drivers/pwm.h>
#include <soc.h>
#include <fsl_pwt.h>
#include <fsl_clock.h>

#include <logging/log.h>
LOG_MODULE_REGISTER(pwm_mcux_pwt, CONFIG_PWM_LOG_LEVEL);

/* Number of PWT input ports */
#define PWT_INPUTS 4U

struct mcux_pwt_config {
PWT_Type *base;
char *clock_name;
clock_control_subsys_t clock_subsys;
pwt_clock_source_t pwt_clock_source;
pwt_clock_prescale_t prescale;
void (*irq_config_func)(const struct device *dev);
};

struct mcux_pwt_data {
uint32_t clock_freq;
uint32_t period_cycles;
uint32_t high_overflows;
uint32_t low_overflows;
pwm_capture_callback_handler_t callback;
void *user_data;
pwt_config_t pwt_config;
bool continuous : 1;
bool inverted : 1;
bool overflowed : 1;
};

static inline bool mcux_pwt_is_active(const struct device *dev)
{
const struct mcux_pwt_config *config = dev->config;

return !!(config->base->CS & PWT_CS_PWTEN_MASK);
}

static int mcux_pwt_pin_set(const struct device *dev, uint32_t pwm,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
ARG_UNUSED(dev);
ARG_UNUSED(pwm);
ARG_UNUSED(period_cycles);
ARG_UNUSED(pulse_cycles);
ARG_UNUSED(flags);

LOG_ERR("pwt only supports pwm capture");

return -ENOTSUP;
}

static int mcux_pwt_pin_configure_capture(const struct device *dev,
uint32_t pwm,
pwm_flags_t flags,
pwm_capture_callback_handler_t cb,
void *user_data)
{
const struct mcux_pwt_config *config = dev->config;
struct mcux_pwt_data *data = dev->data;

if (pwm >= PWT_INPUTS) {
LOG_ERR("invalid channel %d", pwm);
return -EINVAL;
}

if (mcux_pwt_is_active(dev)) {
LOG_ERR("pwm capture in progress");
return -EBUSY;
}

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

data->pwt_config.inputSelect = pwm;

data->continuous =
(flags & PWM_CAPTURE_MODE_MASK) == PWM_CAPTURE_MODE_CONTINUOUS;
data->inverted =
(flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED;

PWT_Init(config->base, &data->pwt_config);
PWT_EnableInterrupts(config->base,
kPWT_PulseWidthReadyInterruptEnable |
kPWT_CounterOverflowInterruptEnable);

return 0;
}

static int mcux_pwt_pin_enable_capture(const struct device *dev, uint32_t pwm)
{
const struct mcux_pwt_config *config = dev->config;
struct mcux_pwt_data *data = dev->data;

if (pwm >= PWT_INPUTS) {
LOG_ERR("invalid channel %d", pwm);
return -EINVAL;
}

if (!data->callback) {
LOG_ERR("PWM capture not configured");
return -EINVAL;
}

if (mcux_pwt_is_active(dev)) {
LOG_ERR("PWM capture already enabled");
return -EBUSY;
}

data->overflowed = false;
data->high_overflows = 0;
data->low_overflows = 0;
PWT_StartTimer(config->base);

return 0;
}

static int mcux_pwt_pin_disable_capture(const struct device *dev, uint32_t pwm)
{
const struct mcux_pwt_config *config = dev->config;

if (pwm >= PWT_INPUTS) {
LOG_ERR("invalid channel %d", pwm);
return -EINVAL;
}

PWT_StopTimer(config->base);

return 0;
}

static int mcux_pwt_calc_period(uint16_t ppw, uint16_t npw,
uint32_t high_overflows,
uint32_t low_overflows,
uint32_t *result)
{
uint32_t period;

/* Calculate sum of overflow counters */
if (u32_add_overflow(high_overflows, low_overflows, &period)) {
return -ERANGE;
}

/* Calculate cycles from sum of overflow counters */
if (u32_mul_overflow(period, 0xFFFFU, &period)) {
return -ERANGE;
}

/* Add positive pulse width */
if (u32_add_overflow(period, ppw, &period)) {
return -ERANGE;
}

/* Add negative pulse width */
if (u32_add_overflow(period, npw, &period)) {
return -ERANGE;
}

*result = period;

return 0;
}

static int mcux_pwt_calc_pulse(uint16_t pw, uint32_t overflows,
uint32_t *result)
{
uint32_t pulse;

/* Calculate cycles from overflow counter */
if (u32_mul_overflow(overflows, 0xFFFFU, &pulse)) {
return -ERANGE;
}

/* Add pulse width */
if (u32_add_overflow(pulse, pw, &pulse)) {
return -ERANGE;
}

*result = pulse;

return 0;
}

static void mcux_pwt_isr(const struct device *dev)
{
const struct mcux_pwt_config *config = dev->config;
struct mcux_pwt_data *data = dev->data;
uint32_t period = 0;
uint32_t pulse = 0;
uint32_t flags;
uint16_t ppw;
uint16_t npw;
int err;

flags = PWT_GetStatusFlags(config->base);

if (flags & kPWT_CounterOverflowFlag) {
if (config->base->CR & PWT_CR_LVL_MASK) {
data->overflowed |= u32_add_overflow(1,
data->high_overflows, &data->high_overflows);
} else {
data->overflowed |= u32_add_overflow(1,
data->low_overflows, &data->low_overflows);
}

PWT_ClearStatusFlags(config->base, kPWT_CounterOverflowFlag);
}

if (flags & kPWT_PulseWidthValidFlag) {
ppw = PWT_ReadPositivePulseWidth(config->base);
npw = PWT_ReadNegativePulseWidth(config->base);

if (!data->continuous) {
PWT_StopTimer(config->base);
}

if (data->inverted) {
err = mcux_pwt_calc_pulse(npw, data->low_overflows,
&pulse);
} else {
err = mcux_pwt_calc_pulse(ppw, data->high_overflows,
&pulse);
}

if (err == 0) {
err = mcux_pwt_calc_period(ppw, npw,
data->high_overflows,
data->low_overflows,
&period);
}

if (data->overflowed) {
err = -ERANGE;
}

LOG_DBG("period = %d, pulse = %d, err = %d", period, pulse,
err);

if (data->callback) {
data->callback(dev, data->pwt_config.inputSelect,
period, pulse, err, data->user_data);
}

data->overflowed = false;
data->high_overflows = 0;
data->low_overflows = 0;
PWT_ClearStatusFlags(config->base, kPWT_PulseWidthValidFlag);
}
}

static int mcux_pwt_get_cycles_per_sec(const struct device *dev, uint32_t pwm,
uint64_t *cycles)
{
const struct mcux_pwt_config *config = dev->config;
struct mcux_pwt_data *data = dev->data;

ARG_UNUSED(pwm);

*cycles = data->clock_freq >> config->prescale;

return 0;
}

static int mcux_pwt_init(const struct device *dev)
{
const struct mcux_pwt_config *config = dev->config;
struct mcux_pwt_data *data = dev->data;
pwt_config_t *pwt_config = &data->pwt_config;
const struct device *clock_dev;

clock_dev = device_get_binding(config->clock_name);
if (clock_dev == NULL) {
LOG_ERR("could not get clock device");
return -EINVAL;
}

if (clock_control_get_rate(clock_dev, config->clock_subsys,
&data->clock_freq)) {
LOG_ERR("could not get clock frequency");
return -EINVAL;
}

PWT_GetDefaultConfig(pwt_config);
pwt_config->clockSource = config->pwt_clock_source;
pwt_config->prescale = config->prescale;
pwt_config->enableFirstCounterLoad = true;
PWT_Init(config->base, pwt_config);

config->irq_config_func(dev);

return 0;
}

static const struct pwm_driver_api mcux_pwt_driver_api = {
.pin_set = mcux_pwt_pin_set,
.get_cycles_per_sec = mcux_pwt_get_cycles_per_sec,
.pin_configure_capture = mcux_pwt_pin_configure_capture,
.pin_enable_capture = mcux_pwt_pin_enable_capture,
.pin_disable_capture = mcux_pwt_pin_disable_capture,
};

#define TO_PWT_PRESCALE_DIVIDE(val) _DO_CONCAT(kPWT_Prescale_Divide_, val)

#define PWT_DEVICE(n) \
static void mcux_pwt_config_func_##n(const struct device *dev); \
\
static const struct mcux_pwt_config mcux_pwt_config_##n = { \
.base = (PWT_Type *)DT_INST_REG_ADDR(n), \
.clock_name = DT_INST_CLOCKS_LABEL(n), \
.clock_subsys = \
(clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \
.pwt_clock_source = kPWT_BusClock, \
.prescale = \
TO_PWT_PRESCALE_DIVIDE(DT_INST_PROP(n, prescaler)), \
.irq_config_func = mcux_pwt_config_func_##n, \
}; \
\
static struct mcux_pwt_data mcux_pwt_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, &mcux_pwt_init, \
device_pm_control_nop, &mcux_pwt_data_##n, \
&mcux_pwt_config_##n, \
POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&mcux_pwt_driver_api); \
\
static void mcux_pwt_config_func_##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
mcux_pwt_isr, DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_INST_IRQN(n)); \
}

DT_INST_FOREACH_STATUS_OKAY(PWT_DEVICE)
Loading