diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index dcdd0c7b6ca..cc0cb51413c 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 3bfed08ad69..acdc3508fdc 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -48,4 +48,6 @@ source "drivers/pwm/Kconfig.litex" source "drivers/pwm/Kconfig.rv32m1_tpm" +source "drivers/pwm/Kconfig.mcux_tpm" + endif # PWM diff --git a/drivers/pwm/Kconfig.mcux_tpm b/drivers/pwm/Kconfig.mcux_tpm new file mode 100644 index 00000000000..36571e6d7bc --- /dev/null +++ b/drivers/pwm/Kconfig.mcux_tpm @@ -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. diff --git a/drivers/pwm/pwm_mcux_tpm.c b/drivers/pwm/pwm_mcux_tpm.c new file mode 100644 index 00000000000..5f7469aee69 --- /dev/null +++ b/drivers/pwm/pwm_mcux_tpm.c @@ -0,0 +1,204 @@ +/* + * Copyright 2019 Henrik Brix Andersen + * 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 +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL +#include +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) diff --git a/dts/bindings/pwm/nxp,kinetis-tpm.yaml b/dts/bindings/pwm/nxp,kinetis-tpm.yaml new file mode 100644 index 00000000000..7bb838916a6 --- /dev/null +++ b/dts/bindings/pwm/nxp,kinetis-tpm.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2019 Henrik Brix Andersen +# 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