diff --git a/bsp/nxp/mcxn/Libraries/drivers/drv_pwm.c b/bsp/nxp/mcxn/Libraries/drivers/drv_pwm.c new file mode 100644 index 0000000000..48aed6962e --- /dev/null +++ b/bsp/nxp/mcxn/Libraries/drivers/drv_pwm.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2024-02-26 Yilin Sun Initial version. + */ + +#include +#include + +#include "fsl_ctimer.h" + +#define MCX_PWM_MATCH_COUNT 4 + +#ifdef RT_USING_PWM + +typedef struct +{ + struct rt_device_pwm pwm_device; + CTIMER_Type *ct_instance; + uint32_t counter_period_ps; +} mcx_pwm_obj_t; + +static CTIMER_Type *mcx_pwm_instances[] = CTIMER_BASE_PTRS; + +static mcx_pwm_obj_t mcx_pwm_list[ARRAY_SIZE(mcx_pwm_instances)]; + +static int mcx_pwm_current_period_channel(mcx_pwm_obj_t *pwm) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + uint32_t mcr = ct->MCR; + + for (uint8_t i = 0; i < 4; i++) + { + if (mcr & (1U << (3 * i + CTIMER_MCR_MR0R_SHIFT))) + { + return i; + } + } + + return -1; +} + +static int mcx_pwm_first_free_channel(mcx_pwm_obj_t *pwm) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + int pc = mcx_pwm_current_period_channel(pwm); + + uint32_t pwmc = ct->PWMC; + + for (uint8_t i = 0; i < 4; i++) + { + if (pwmc & (1U << i)) + { + /* Skip this channel if there's an active PWM output */ + continue; + } + + if (i == pc) + { + /* Skip this channel if this channel is the current period channel */ + continue; + } + + return i; + } + + /* There are no free channels left. */ + return -1; +} + +static int mcx_pwm_period_set(mcx_pwm_obj_t *pwm, uint32_t period_ns) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + int p_channel = mcx_pwm_current_period_channel(pwm); + if (p_channel < 0) + { + return -EINVAL; + } + + /* Store new values in shadow registers */ + ct->MSR[p_channel] = period_ns * 1000 / pwm->counter_period_ps; + + /* Enable period channel interrupt to check a reload event occurs. + * Since interrupts are not configured from NVIC, so no ISR will occur. + * Check IR[MRnINT] for reload point. + */ + uint32_t mcr_mask = (CTIMER_MCR_MR0RL_MASK << p_channel) | (CTIMER_MCR_MR0I_MASK << (3 * p_channel)); + + for (uint8_t i = 0; i < 4; i++) + { + if (ct->PWMC & (1U << i)) + { + /* Channel PWM output is enabled, calculate new values and store into shadow registers */ + uint32_t new_mr = ct->MR[i] * ct->MSR[p_channel] / ct->MR[p_channel]; + ct->MSR[i] = new_mr; + + /* Update MRnRL map */ + mcr_mask |= CTIMER_MCR_MR0RL_MASK << i; + } + } + + /* Reload MRs on next counter reset, enable reload MR interrupt */ + ct->MCR |= mcr_mask; + + while ((ct->IR & (CTIMER_IR_MR0INT_MASK << p_channel)) == 0U) + { + /* -- */ + } + + /* Disable reload channel interrupt and MSR synchronization */ + ct->MCR &= ~mcr_mask; + + /* Clear interrupt flags. */ + ct->IR |= (CTIMER_IR_MR0INT_MASK << p_channel); + + return 0; +} + +static int mcx_pwm_period_get(mcx_pwm_obj_t *pwm, uint32_t *period_ns) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + int p_channel = mcx_pwm_current_period_channel(pwm); + if (p_channel < 0) + { + return -1; + } + + *period_ns = ct->MR[p_channel] * pwm->counter_period_ps / 1000; + + return 0; +} + +static int mcx_pwm_pulse_set(mcx_pwm_obj_t *pwm, uint8_t channel, uint32_t pulse_ns) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + int p_channel = mcx_pwm_current_period_channel(pwm); + if (p_channel < 0) + { + return -1; + } + + /* Up-counting counters, the polarity is inversed */ + ct->MSR[channel] = ct->MR[p_channel] - pulse_ns * 1000 / pwm->counter_period_ps; + + /* Reload MRn on the next cycle */ + ct->MCR |= (CTIMER_MCR_MR0RL_MASK << channel); + + /* Wait for new duty cycle loaded into the MRn */ + while (ct->MR[channel] != ct->MSR[channel]) + { + /* -- */ + } + + /* Disable shadow register updates */ + ct->MCR &= ~(CTIMER_MCR_MR0RL_MASK << channel); + + return 0; +} + +static int mcx_pwm_pulse_get(mcx_pwm_obj_t *pwm, uint8_t channel, uint32_t *pulse_ns) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + int p_channel = mcx_pwm_current_period_channel(pwm); + if (p_channel < 0) + { + return -1; + } + + /* Up-counting counters, the polarity is inversed */ + *pulse_ns = (ct->MR[p_channel] - ct->MR[channel]) * pwm->counter_period_ps / 1000; + + return 0; +} + +static rt_err_t mcx_drv_pwm_get(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration) +{ + if (mcx_pwm_period_get(pwm, &configuration->period) < 0) + { + return -RT_EFAULT; + } + + if (mcx_pwm_pulse_get(pwm, configuration->channel, &configuration->pulse) < 0) + { + return -RT_EFAULT; + } + + return RT_EOK; +} + +static rt_err_t mcx_drv_pwm_set(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration) +{ + CTIMER_Type *ct = pwm->ct_instance; + + uint32_t period = configuration->period * 1000 / pwm->counter_period_ps; + uint8_t channel = configuration->channel; + + if ((ct->TCR & CTIMER_TCR_CEN_MASK) == 0U) + { + /* There's two conditions for a all-zero TCR: either a reset condition or timer is stopped. */ + /* In either case, we need to initialize the timer instance (AHB RST CTRL). */ + + /* TODO: Do not use SDK functions */ + + ctimer_config_t ct_cfg = + { + .mode = kCTIMER_TimerMode, + .prescale = 1U, + }; + + /* Frequency: 150MHz max., we got 32bit counters, we can take that. */ + /* Approx. maximum period: 28.6 seconds. */ + + CTIMER_Init(ct, &ct_cfg); + + /* Current timer is not running, we are the first channel being configured. */ + + ct->TC = 0U; /* Reset counter */ + ct->PC = 0U; /* Reset prescaler counter */ + ct->PR = 0U; /* Prescaler, divide by 1 to get best resolution */ + ct->MCR = 0U; /* Reset interrupt and reset condition */ + ct->EMR = 0U; /* Do nothing on match event and output 0 as default state */ + ct->PWMC = 0U; /* Disable all PWM channels, outputs will be controlled by EMn */ + + /* Here, we have a favoritism of using channel 3 as period channel, unless channel 3 is used for output */ + if (channel != 3) + { + ct->MR[3] = period; + ct->MCR |= CTIMER_MCR_MR3R_MASK; + } + else + { + /* Use channel 2 as period channel. */ + ct->MR[2] = period; + ct->MCR |= CTIMER_MCR_MR2R_MASK; + } + + /* Start counter */ + ct->TCR |= CTIMER_TCR_CEN_MASK; + } + else + { + /* + * Due to the nature of the CTimer, one of the 4 match channels is needed for period control (frequency) + * To find out which one is the current period channel, check the MRxR bit for each match output. + * If we are configuring the same match being used as periodic channel, configure the next free match as period + * then current channel can be re-used. If all 4 channels are in use then the function will fail with an errno. + */ + + /* The timer is running, check whether we need to re-locate the period channel */ + int p_channel = mcx_pwm_current_period_channel(pwm); + if (p_channel < 0) + { + return -RT_EINVAL; + } + + if (p_channel == channel) + { + /* We need to re-locate the period channel */ + + int f_channel = mcx_pwm_first_free_channel(pwm); + if (f_channel < 0) + { + /* There's no free channel, bail out. */ + return -RT_EBUSY; + } + + /* Transfer the period channel to first free channel */ + + /* Step 1: Copy current period to first free channel */ + ct->MR[f_channel] = ct->MR[p_channel]; + + /* Step 2: Enable reset for new period channel */ + /* Note: it's safe doing it here since both old and new channel MRs contains same value */ + ct->MCR |= (CTIMER_MCR_MR0R_MASK << (3 * f_channel)); + + /* Step 3: Disable reset for old period channel */ + ct->MCR &= ~(CTIMER_MCR_MR0R_MASK << (3 * p_channel)); + + /* The old period channel is now available for PWM output */ + p_channel = f_channel; + } + + if (mcx_pwm_period_set(pwm, configuration->period) < 0) + { + return -RT_EINVAL; + } + } + + if (mcx_pwm_pulse_set(pwm, channel, configuration->pulse) < 0) + { + return -RT_EINVAL; + } + + return 0; +} + +static rt_err_t mcx_drv_pwm_enable(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + ct->PWMC |= (1U << configuration->channel); + + return 0; +} + +static rt_err_t mcx_drv_pwm_disable(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration) +{ + CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance; + + ct->PWMC &= ~(1U << configuration->channel); + + return 0; +} + +static rt_err_t mcx_drv_pwm_control(struct rt_device_pwm *device, int cmd, void *args) +{ + mcx_pwm_obj_t *pwm = device->parent.user_data; + struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)args; + + switch (cmd) + { + case PWM_CMD_ENABLE: + return mcx_drv_pwm_enable(pwm, configuration); + + case PWM_CMD_DISABLE: + return mcx_drv_pwm_disable(pwm, configuration); + + case PWM_CMD_SET: + return mcx_drv_pwm_set(pwm, configuration); + + case PWM_CMD_GET: + return mcx_drv_pwm_get(pwm, configuration); + + default: + return -RT_EINVAL; + } + + return RT_EOK; +} + +static struct rt_pwm_ops mcx_pwm_ops = +{ + .control = mcx_drv_pwm_control, +}; + +int mcx_pwm_init(void) +{ + rt_err_t ret; + char name_buf[8]; + + for (uint8_t i = 0; i < ARRAY_SIZE(mcx_pwm_instances); i++) + { + mcx_pwm_list[i].ct_instance = mcx_pwm_instances[i]; + mcx_pwm_list[i].counter_period_ps = 1000000000000ULL / CLOCK_GetCTimerClkFreq(i); + + rt_snprintf(name_buf, sizeof(name_buf), "pwm%d", i); + + ret = rt_device_pwm_register(&mcx_pwm_list[i].pwm_device, name_buf, &mcx_pwm_ops, &mcx_pwm_list[i]); + if (ret != RT_EOK) + { + return ret; + } + } + + return RT_EOK; +} + +INIT_DEVICE_EXPORT(mcx_pwm_init); + +#endif /* RT_USING_PWM */ \ No newline at end of file diff --git a/bsp/nxp/mcxn/Libraries/drivers/drv_pwm.h b/bsp/nxp/mcxn/Libraries/drivers/drv_pwm.h new file mode 100644 index 0000000000..e6dad6d193 --- /dev/null +++ b/bsp/nxp/mcxn/Libraries/drivers/drv_pwm.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2024-02-26 Yilin Sun Initial version. + */ + +#ifndef __DRV_PWM_H__ +#define __DRV_PWM_H__ + +#include +#include + +int mcx_pwm_init(void); + +#endif diff --git a/bsp/nxp/mcxn/frdm-mcxn947/board/Kconfig b/bsp/nxp/mcxn/frdm-mcxn947/board/Kconfig index 21835dfdcf..e246de745e 100644 --- a/bsp/nxp/mcxn/frdm-mcxn947/board/Kconfig +++ b/bsp/nxp/mcxn/frdm-mcxn947/board/Kconfig @@ -125,25 +125,9 @@ menu "On-chip Peripheral Drivers" default y if BSP_USING_PWM - config BSP_USING_CTIMER1_MAT0 - bool "Enable CIMER1 Match0 as PWM output" + config BSP_USING_LEDG_PWM + bool "Enable on-board green LED as PWM output (pwm0, channel 3)" default y - - config BSP_USING_CTIMER2_MAT0 - bool "Enable CIMER2 Match0 as PWM output" - default n - - config BSP_USING_CTIMER2_MAT1 - bool "Enable CIMER2 Match1 as PWM output" - default n - - config BSP_USING_CTIMER2_MAT2 - bool "Enable CIMER2 Match2 as PWM output" - default n - - config BSP_USING_CTIMER3_MAT2 - bool "Enable CIMER3 Match2 as PWM output" - default n endif endmenu diff --git a/bsp/nxp/mcxn/frdm-mcxn947/board/board.c b/bsp/nxp/mcxn/frdm-mcxn947/board/board.c index 0e5d21f771..c7fd47bac4 100644 --- a/bsp/nxp/mcxn/frdm-mcxn947/board/board.c +++ b/bsp/nxp/mcxn/frdm-mcxn947/board/board.c @@ -14,7 +14,9 @@ #include "board.h" #include "clock_config.h" +#include "pin_mux.h" #include "drv_uart.h" +#include "fsl_port.h" #include "fsl_cache_lpcac.h" /** @@ -79,6 +81,17 @@ void rt_hw_board_init() CLOCK_SetupClk16KClocking(kCLOCK_Clk16KToAll); + CLOCK_AttachClk(kPLL0_to_CTIMER0); + CLOCK_AttachClk(kPLL0_to_CTIMER1); + CLOCK_AttachClk(kPLL0_to_CTIMER2); + CLOCK_AttachClk(kPLL0_to_CTIMER3); + CLOCK_AttachClk(kPLL0_to_CTIMER4); + CLOCK_SetClkDiv(kCLOCK_DivCtimer0Clk, 1u); + CLOCK_SetClkDiv(kCLOCK_DivCtimer1Clk, 1u); + CLOCK_SetClkDiv(kCLOCK_DivCtimer2Clk, 1u); + CLOCK_SetClkDiv(kCLOCK_DivCtimer3Clk, 1u); + CLOCK_SetClkDiv(kCLOCK_DivCtimer4Clk, 1u); + SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); /* set pend exception priority */ NVIC_SetPriority(PendSV_IRQn, (1 << __NVIC_PRIO_BITS) - 1); @@ -86,6 +99,10 @@ void rt_hw_board_init() /*init uart device*/ rt_hw_uart_init(); +#if defined(BSP_USING_LEDG_PWM) && defined(BSP_USING_PWM) + PORT_SetPinMux(PORT0, 27, kPORT_MuxAlt4); +#endif + #if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE) rt_console_set_device(RT_CONSOLE_DEVICE_NAME); #endif