drivers: pwm: add driver for NXP Kinetis TPM module

The TPM (Timer/PWM Module) is a 2- to 8-channel timer which supports
input capture, output compare, and the generation of PWM signals to
control electric motor and power management applications.

This patch adds the driver and the binding necessary for instantiating
the driver. The work is based on the RV32M1 driver for TPM done by
Henrik Brix Andersen. A later patch will enable this driver to be used
for the KW41Z SoC, if PWM support is requested.

Signed-off-by: Alex Porosanu <alexandru.porosanu@nxp.com>
This commit is contained in:
Alex Porosanu 2020-04-08 09:02:11 +03:00 committed by Maureen Helm
commit f4c36b7beb
5 changed files with 241 additions and 0 deletions

View file

@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_MCUX pwm_mcux.c)
zephyr_library_sources_ifdef(CONFIG_PWM_XEC pwm_mchp_xec.c)
zephyr_library_sources_ifdef(CONFIG_PWM_LITEX pwm_litex.c)
zephyr_library_sources_ifdef(CONFIG_PWM_RV32M1_TPM pwm_rv32m1_tpm.c)
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_TPM pwm_mcux_tpm.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c)

View file

@ -48,4 +48,6 @@ source "drivers/pwm/Kconfig.litex"
source "drivers/pwm/Kconfig.rv32m1_tpm"
source "drivers/pwm/Kconfig.mcux_tpm"
endif # PWM

View file

@ -0,0 +1,10 @@
# Copyright 2020 NXP
# SPDX-License-Identifier: Apache-2.0
# MCUX TPM PWM
config PWM_MCUX_TPM
bool "MCUX TPM PWM driver"
depends on HAS_MCUX_TPM && CLOCK_CONTROL
help
Enable the MCUX TPM PWM driver.

204
drivers/pwm/pwm_mcux_tpm.c Normal file
View file

@ -0,0 +1,204 @@
/*
* Copyright 2019 Henrik Brix Andersen <henrik@brixandersen.dk>
* Copyright 2020 NXP
*
* Heavily based on pwm_mcux_ftm.c, which is:
* Copyright (c) 2017, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_kinetis_tpm
#include <drivers/clock_control.h>
#include <errno.h>
#include <drivers/pwm.h>
#include <soc.h>
#include <fsl_tpm.h>
#include <fsl_clock.h>
#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(pwm_mcux_tpm);
#define MAX_CHANNELS ARRAY_SIZE(TPM0->CONTROLS)
struct mcux_tpm_config {
TPM_Type *base;
char *clock_name;
clock_control_subsys_t clock_subsys;
tpm_clock_source_t tpm_clock_source;
tpm_clock_prescale_t prescale;
u8_t channel_count;
tpm_pwm_mode_t mode;
};
struct mcux_tpm_data {
u32_t clock_freq;
u32_t period_cycles;
tpm_chnl_pwm_signal_param_t channel[MAX_CHANNELS];
};
static int mcux_tpm_pin_set(struct device *dev, u32_t pwm,
u32_t period_cycles, u32_t pulse_cycles,
pwm_flags_t flags)
{
const struct mcux_tpm_config *config = dev->config->config_info;
struct mcux_tpm_data *data = dev->driver_data;
u8_t duty_cycle;
if ((period_cycles == 0U) || (pulse_cycles > period_cycles)) {
LOG_ERR("Invalid combination: period_cycles=%d, "
"pulse_cycles=%d", period_cycles, pulse_cycles);
return -EINVAL;
}
if (pwm >= config->channel_count) {
LOG_ERR("Invalid channel");
return -ENOTSUP;
}
duty_cycle = pulse_cycles * 100U / period_cycles;
data->channel[pwm].dutyCyclePercent = duty_cycle;
if ((flags & PWM_POLARITY_INVERTED) == 0) {
data->channel[pwm].level = kTPM_HighTrue;
} else {
data->channel[pwm].level = kTPM_LowTrue;
}
LOG_DBG("pulse_cycles=%d, period_cycles=%d, duty_cycle=%d, flags=%d",
pulse_cycles, period_cycles, duty_cycle, flags);
if (period_cycles != data->period_cycles) {
u32_t pwm_freq;
status_t status;
if (data->period_cycles != 0) {
/* Only warn when not changing from zero */
LOG_WRN("Changing period cycles from %d to %d"
" affects all %d channels in %s",
data->period_cycles, period_cycles,
config->channel_count, dev->config->name);
}
data->period_cycles = period_cycles;
pwm_freq = (data->clock_freq >> config->prescale) /
period_cycles;
LOG_DBG("pwm_freq=%d, clock_freq=%d", pwm_freq,
data->clock_freq);
if (pwm_freq == 0U) {
LOG_ERR("Could not set up pwm_freq=%d", pwm_freq);
return -EINVAL;
}
TPM_StopTimer(config->base);
status = TPM_SetupPwm(config->base, data->channel,
config->channel_count, config->mode,
pwm_freq, data->clock_freq);
if (status != kStatus_Success) {
LOG_ERR("Could not set up pwm");
return -ENOTSUP;
}
TPM_StartTimer(config->base, config->tpm_clock_source);
} else {
TPM_UpdateChnlEdgeLevelSelect(config->base, pwm,
data->channel[pwm].level);
TPM_UpdatePwmDutycycle(config->base, pwm, config->mode,
duty_cycle);
}
return 0;
}
static int mcux_tpm_get_cycles_per_sec(struct device *dev, u32_t pwm,
u64_t *cycles)
{
const struct mcux_tpm_config *config = dev->config->config_info;
struct mcux_tpm_data *data = dev->driver_data;
*cycles = data->clock_freq >> config->prescale;
return 0;
}
static int mcux_tpm_init(struct device *dev)
{
const struct mcux_tpm_config *config = dev->config->config_info;
struct mcux_tpm_data *data = dev->driver_data;
tpm_chnl_pwm_signal_param_t *channel = data->channel;
struct device *clock_dev;
tpm_config_t tpm_config;
int i;
if (config->channel_count > ARRAY_SIZE(data->channel)) {
LOG_ERR("Invalid channel count");
return -EINVAL;
}
clock_dev = device_get_binding(config->clock_name);
if (clock_dev == NULL) {
LOG_ERR("Could not get clock device");
return -EINVAL;
}
if (clock_control_on(clock_dev, config->clock_subsys)) {
LOG_ERR("Could not turn on clock");
return -EINVAL;
}
if (clock_control_get_rate(clock_dev, config->clock_subsys,
&data->clock_freq)) {
LOG_ERR("Could not get clock frequency");
return -EINVAL;
}
for (i = 0; i < config->channel_count; i++) {
channel->chnlNumber = i;
channel->level = kTPM_NoPwmSignal;
channel->dutyCyclePercent = 0;
channel->firstEdgeDelayPercent = 0;
channel++;
}
TPM_GetDefaultConfig(&tpm_config);
tpm_config.prescale = config->prescale;
TPM_Init(config->base, &tpm_config);
return 0;
}
static const struct pwm_driver_api mcux_tpm_driver_api = {
.pin_set = mcux_tpm_pin_set,
.get_cycles_per_sec = mcux_tpm_get_cycles_per_sec,
};
#define TPM_DEVICE(n) \
static const struct mcux_tpm_config mcux_tpm_config_##n = { \
.base = (TPM_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), \
.tpm_clock_source = kTPM_SystemClock, \
.prescale = kTPM_Prescale_Divide_16, \
.channel_count = FSL_FEATURE_TPM_CHANNEL_COUNTn((TPM_Type *) \
DT_INST_REG_ADDR(n)), \
.mode = kTPM_EdgeAlignedPwm, \
}; \
static struct mcux_tpm_data mcux_tpm_data_##n; \
DEVICE_AND_API_INIT(mcux_tpm_##n, \
DT_INST_LABEL(n), \
&mcux_tpm_init, &mcux_tpm_data_##n, \
&mcux_tpm_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&mcux_tpm_driver_api)
DT_INST_FOREACH(TPM_DEVICE)

View file

@ -0,0 +1,24 @@
# Copyright (c) 2019 Henrik Brix Andersen <henrik@brixandersen.dk>
# SPDX-License-Identifier: Apache-2.0
description: MCUX Timer/PWM Module (TPM)
compatible: "nxp,kinetis-tpm"
include: [pwm-controller.yaml, base.yaml]
properties:
reg:
required: true
interrupts:
required: true
"#pwm-cells":
const: 3
pwm-cells:
- channel
# period in terms of nanoseconds
- period
- flags