From 383a0a512a66e15feb83a21103272b06f123b40f Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 11:10:34 +0100 Subject: [PATCH 1/7] dts: bindings: pwm: add binding for the NXP Kinetis Pulse Width Timer Add devicetree binding for the NXP Kinetis Pulse Width Timer (PWT). Signed-off-by: Henrik Brix Andersen --- dts/bindings/pwm/nxp,kinetis-pwt.yaml | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 dts/bindings/pwm/nxp,kinetis-pwt.yaml diff --git a/dts/bindings/pwm/nxp,kinetis-pwt.yaml b/dts/bindings/pwm/nxp,kinetis-pwt.yaml new file mode 100644 index 0000000000000..02e928298880c --- /dev/null +++ b/dts/bindings/pwm/nxp,kinetis-pwt.yaml @@ -0,0 +1,38 @@ +# Copyright (c) 2021 Vestas Wind Systems A/S +# SPDX-License-Identifier: Apache-2.0 + +description: Kinetis PWT PWM Capture + +compatible: "nxp,kinetis-pwt" + +include: [pwm-controller.yaml, base.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + prescaler: + type: int + required: true + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + - 128 + description: Input clock prescaler + + "#pwm-cells": + const: 3 + +pwm-cells: + - channel +# period in terms of nanoseconds + - period + - flags From 29b8048bb6d4b9280c182be92fdb0c218b5c5d7a Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 11:12:31 +0100 Subject: [PATCH 2/7] modules: mcux: add support for indicating the presence of PWT Add Kconfig option for indicating that a given SoC contains the NXP Kinetis Pulse Width Timer (PWT). Signed-off-by: Henrik Brix Andersen --- modules/Kconfig.mcux | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/Kconfig.mcux b/modules/Kconfig.mcux index 4a8e4058da39f..40c181c21acce 100644 --- a/modules/Kconfig.mcux +++ b/modules/Kconfig.mcux @@ -240,4 +240,9 @@ config HAS_MCUX_ACMP help Set if the ACMP module is present on the SoC. +config HAS_MCUX_PWT + bool + help + Set if the PWT module is present on the SoC. + endif # HAS_MCUX From 58de7606d20c94fdb0ebb8c828a5fe75bf07d59f Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 14:32:34 +0100 Subject: [PATCH 3/7] manifest: update hal_nxp to support the Pulse Width Timer Update the NXP HAL module revision to support for the NXP Pulse Width Timer (PWT). Signed-off-by: Henrik Brix Andersen --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index b5f61b9b447f1..f377d73b4a3d4 100644 --- a/west.yml +++ b/west.yml @@ -102,7 +102,7 @@ manifest: revision: 41132e9220f8bc1223084975350c5e5f3b492afe path: tools/net-tools - name: hal_nxp - revision: 61a7788ac9379bfa7711bf9d15de735230528ea5 + revision: 74ec105b931773854384ecabe95d7552c82e59e4 path: modules/hal/nxp - name: open-amp revision: de1b85a13032a2de1d8b6695ae5f800b613e739d From e8c0b86df4c57e689164fa73af74e6199804984e Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 11:17:21 +0100 Subject: [PATCH 4/7] drivers: pwm: add driver for the NXP Kinetis Pulse Width Timer (PWT) Add PWM capture driver for the NXP Kinetis Pulse Width Timer (PWT). Signed-off-by: Henrik Brix Andersen --- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.mcux_pwt | 10 + drivers/pwm/pwm_mcux_pwt.c | 347 ++++++++++++++++++ .../ke1xf/Kconfig.defconfig.series | 4 + soc/arm/nxp_kinetis/ke1xf/Kconfig.series | 1 + 6 files changed, 365 insertions(+) create mode 100644 drivers/pwm/Kconfig.mcux_pwt create mode 100644 drivers/pwm/pwm_mcux_pwt.c diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index bb2c37d13d811..2ed06a603ace3 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a1554b12fc5cc..6c5664c1447d9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -63,4 +63,6 @@ source "drivers/pwm/Kconfig.npcx" source "drivers/pwm/Kconfig.xlnx" +source "drivers/pwm/Kconfig.mcux_pwt" + endif # PWM diff --git a/drivers/pwm/Kconfig.mcux_pwt b/drivers/pwm/Kconfig.mcux_pwt new file mode 100644 index 0000000000000..6ad2980363337 --- /dev/null +++ b/drivers/pwm/Kconfig.mcux_pwt @@ -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. diff --git a/drivers/pwm/pwm_mcux_pwt.c b/drivers/pwm/pwm_mcux_pwt.c new file mode 100644 index 0000000000000..5a2351b28dfd7 --- /dev/null +++ b/drivers/pwm/pwm_mcux_pwt.c @@ -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 +#include +#include +#include +#include +#include + +#include +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) diff --git a/soc/arm/nxp_kinetis/ke1xf/Kconfig.defconfig.series b/soc/arm/nxp_kinetis/ke1xf/Kconfig.defconfig.series index c7aff28544abc..b4c694bd98ec1 100644 --- a/soc/arm/nxp_kinetis/ke1xf/Kconfig.defconfig.series +++ b/soc/arm/nxp_kinetis/ke1xf/Kconfig.defconfig.series @@ -50,6 +50,10 @@ config PWM_MCUX_FTM default y depends on PWM +config PWM_MCUX_PWT + default y + depends on PWM_CAPTURE + config PINMUX_MCUX default y depends on PINMUX diff --git a/soc/arm/nxp_kinetis/ke1xf/Kconfig.series b/soc/arm/nxp_kinetis/ke1xf/Kconfig.series index 00484225a63d1..79a8c486c1ef4 100644 --- a/soc/arm/nxp_kinetis/ke1xf/Kconfig.series +++ b/soc/arm/nxp_kinetis/ke1xf/Kconfig.series @@ -29,5 +29,6 @@ config SOC_SERIES_KINETIS_KE1XF select HAS_MCUX_DAC32 select HAS_MCUX_EDMA select HAS_MCUX_ACMP + select HAS_MCUX_PWT help Enable support for Kinetis KE1xF MCU series From b2b5f2c034e931d654ca7de17893f00355fe0d98 Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 11:15:31 +0100 Subject: [PATCH 5/7] dts: arm: nxp: ke1xf: add PWT devicetree node Add devicetree node for the NXP Kinetis Pulse Width Timer (PWT) to the NXP Kinetis KE1xF Soc. Signed-off-by: Henrik Brix Andersen --- dts/arm/nxp/nxp_ke1xf.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dts/arm/nxp/nxp_ke1xf.dtsi b/dts/arm/nxp/nxp_ke1xf.dtsi index 8b583f67453e5..345b44f49bed1 100644 --- a/dts/arm/nxp/nxp_ke1xf.dtsi +++ b/dts/arm/nxp/nxp_ke1xf.dtsi @@ -163,6 +163,18 @@ label = "WDT_0"; }; + pwt: pwt@40056000 { + compatible = "nxp,kinetis-pwt"; + reg = <0x40056000 0x1000>; + interrupts = <29 0>; + clocks = <&scg KINETIS_SCG_BUS_CLK>; + prescaler = <1>; + label = "PWT_0"; + status = "disabled"; + + #pwm-cells = <3>; + }; + ftfe: flash-controller@40020000 { compatible = "nxp,kinetis-ftfe"; label = "FLASH_CTRL"; From e59e7be8b2da92801aa20327f5a2f16df6421051 Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 11:19:00 +0100 Subject: [PATCH 6/7] boards: arm: twr_ke18f: add pinmux configuration for PWT testing Add pinmux configuration for testing the NXP Kinetis Pulse Width Timer (PWT) in loopback mode. Signed-off-by: Henrik Brix Andersen --- boards/arm/twr_ke18f/pinmux.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/boards/arm/twr_ke18f/pinmux.c b/boards/arm/twr_ke18f/pinmux.c index c6b1a9065c078..d38609881bdd4 100644 --- a/boards/arm/twr_ke18f/pinmux.c +++ b/boards/arm/twr_ke18f/pinmux.c @@ -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)); @@ -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)); From a922c74f38c2fc731e00197f7a4ca213fe3d9326 Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Thu, 7 Jan 2021 14:23:11 +0100 Subject: [PATCH 7/7] tests: drivers: pwm: loopback: add support for twr_ke18f board Add support for testing the NXP Kinetis Pulse Width Timer (PWT) on the NXP TWR-KE18F development board using PWM loopback. Signed-off-by: Henrik Brix Andersen --- .../pwm/pwm_loopback/boards/twr_ke18f.overlay | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/drivers/pwm/pwm_loopback/boards/twr_ke18f.overlay diff --git a/tests/drivers/pwm/pwm_loopback/boards/twr_ke18f.overlay b/tests/drivers/pwm/pwm_loopback/boards/twr_ke18f.overlay new file mode 100644 index 0000000000000..086a32f5b5a4c --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/boards/twr_ke18f.overlay @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020-2021 Vestas Wind Systems A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + pwm_loopback_0 { + compatible = "test,pwm_loopback"; + pwms = <&ftm2 6 0 PWM_POLARITY_NORMAL>, /* PTE15, J20 pin 5 */ + <&pwt 1 0 PWM_POLARITY_NORMAL>; /* PTE11, J20 pin 8 */ + }; +}; + +&pwt { + status = "okay"; + prescaler = <32>; +}; + +&ftm2 { + status = "okay"; + compatible = "nxp,kinetis-ftm-pwm"; + prescaler = <128>; + #pwm-cells = <3>; +};