From d8ab33ae968ff47bdf419f04913ec64fdfee401a Mon Sep 17 00:00:00 2001 From: Phi Tran Date: Tue, 10 Dec 2024 18:39:34 +0700 Subject: [PATCH] drivers: pwm: Add support for PWM driver on RSK_RX130_512KB This is initial commit to support PWM driver on Renesas RX130 with MTU modules. Signed-off-by: Phi Tran --- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.renesas_rx_mtu | 9 + drivers/pwm/pwm_renesas_rx_mtu.c | 662 ++++++++++++++++++++ dts/bindings/misc/renesas,rx-mtu.yaml | 44 ++ dts/bindings/pwm/renesas,rx-mtu-pwm.yaml | 57 ++ include/zephyr/dt-bindings/pwm/rx_mtu_pwm.h | 22 + 7 files changed, 797 insertions(+) create mode 100644 drivers/pwm/Kconfig.renesas_rx_mtu create mode 100644 drivers/pwm/pwm_renesas_rx_mtu.c create mode 100644 dts/bindings/misc/renesas,rx-mtu.yaml create mode 100644 dts/bindings/pwm/renesas,rx-mtu-pwm.yaml create mode 100644 include/zephyr/dt-bindings/pwm/rx_mtu_pwm.h diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index a6f406b8ab0..fc12517c461 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -49,6 +49,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_NXP_FLEXIO pwm_nxp_flexio.c) zephyr_library_sources_ifdef(CONFIG_PWM_NXP_S32_EMIOS pwm_nxp_s32_emios.c) zephyr_library_sources_ifdef(CONFIG_PWM_ENE_KB1200 pwm_ene_kb1200.c) zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RA pwm_renesas_ra.c) +zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RX_MTU pwm_renesas_rx_mtu.c) zephyr_library_sources_ifdef(CONFIG_PWM_INFINEON_CAT1 pwm_ifx_cat1.c) zephyr_library_sources_ifdef(CONFIG_PWM_FAKE pwm_fake.c) zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RZ_GPT pwm_renesas_rz_gpt.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 1b9e8713b5b..2000ac4fd50 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -118,6 +118,8 @@ source "drivers/pwm/Kconfig.ene" source "drivers/pwm/Kconfig.renesas_ra" +source "drivers/pwm/Kconfig.renesas_rx_mtu" + source "drivers/pwm/Kconfig.ifx_cat1" source "drivers/pwm/Kconfig.fake" diff --git a/drivers/pwm/Kconfig.renesas_rx_mtu b/drivers/pwm/Kconfig.renesas_rx_mtu new file mode 100644 index 00000000000..18294cf5136 --- /dev/null +++ b/drivers/pwm/Kconfig.renesas_rx_mtu @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +config PWM_RENESAS_RX_MTU + bool "Renesas RX MTU PWM driver" + default y + depends on DT_HAS_RENESAS_RX_MTU_PWM_ENABLED + help + Enable the Renesas RX PWM driver. diff --git a/drivers/pwm/pwm_renesas_rx_mtu.c b/drivers/pwm/pwm_renesas_rx_mtu.c new file mode 100644 index 00000000000..9b3917e9108 --- /dev/null +++ b/drivers/pwm/pwm_renesas_rx_mtu.c @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "r_gpio_rx_if.h" + +#define DT_DRV_COMPAT renesas_rx_mtu_pwm + +LOG_MODULE_REGISTER(pwm_renesas_rx_mtu, CONFIG_PWM_LOG_LEVEL); + +#define MAX_CHANNEL (4) + +#define TMDR_MD_PWM_NORMAL_MODE (0) +#define TMDR_MD_PWM_MODE_1 (2) +#define TMDR_MD_PWM_MODE_2 (3) + +#define TCIEV_BIT (4) +#define TCFD_BIT (7) + +#define INPUT_CAPTURE_AT_RISING_EDGE 0x8 +#define INPUT_CAPTURE_AT_FALLING_EDGE 0x9 +#define INPUT_CAPTURE_AT_BOTH_EDGE 0xA + +#define INPUT_LOW (0) +#define INPUT_HIGH (1) + +#define CAPTURE_STOP (0) +#define CAPTURE_START (1) + +/* output always low (0% duty cycle). */ +#define PWM_STATE_0 0x11 +/* output switches (1% - 99% duty cycle).*/ +#define PWM_STATE_SWITCHING 0x65 +/* output always high (100% duty cycle). */ +#define PWM_STATE_100 0x66 + +#ifdef CONFIG_PWM_CAPTURE +struct pwm_renesas_rx_capture_data { + pwm_capture_callback_handler_t callback; + void *user_data; + uint32_t period; + uint32_t pulse; + uint32_t capture; + uint8_t mode; + uint32_t overflows; + bool is_busy; + bool is_pulse_capture; + bool continuous; + uint8_t channel; +}; +#endif /* CONFIG_PWM_CAPTURE */ + +struct tcr_reg { + /* time prescaler select */ + uint8_t tpsc: 3; + /* input clock edge select */ + uint8_t ckeg: 2; + /* counter clear source select */ + uint8_t cclr: 3; +}; + +struct pwm_renesas_rx_data { + uint32_t clk_rate; + uint8_t capture_a_irqn; + uint8_t cycle_end_irqn; + gpio_port_pin_t port_pin; +#ifdef CONFIG_PWM_CAPTURE + struct pwm_renesas_rx_capture_data capture; + bool start_flag; + uint8_t skip_irq; + uint8_t start_source; + uint8_t capture_source; +#endif /* CONFIG_PWM_CAPTURE */ +}; + +struct pwm_renesas_rx_config { + /* channel MTU */ + uint8_t channel; + uint8_t bit_idx; + /* supported number of channels (not necessarily number of used channels) */ + uint8_t max_num_channels; + /* operate the device in synchronous mode ? */ + bool synchronous; + /* prescaler setting for TCR */ + uint8_t prescaler; + const struct device *clock; + struct clock_control_rx_subsys_cfg clock_subsys; + const struct pinctrl_dev_config *pcfg; + struct { + /* timer control register */ + volatile struct tcr_reg *tcr; + /* timer mode register */ + volatile uint8_t *tmdr; + /* timer I/O control register (16 bit or 8 bit depending on number of channels) */ + volatile uint8_t *tior; + /* Timer Interrupt Enable Register */ + volatile uint8_t *tier; + /* Timer Status Register */ + volatile uint8_t *tsr; + /* timer general registers */ + volatile uint16_t *tgr; + /* timer counter register */ + volatile uint16_t *tcnt; + /* timer start register */ + volatile uint8_t *tstr; + /* timer synchronous register */ + volatile uint8_t *tsyr; + /* timer noise filter */ + volatile uint8_t *nfcr; + } reg; +#ifdef CONFIG_PWM_CAPTURE + uint8_t tgi_irq[MAX_CHANNEL + 1]; +#endif +}; + +static inline void mtu_output_enable(const struct device *dev, int channel, uint8_t state) +{ + const struct pwm_renesas_rx_config *config = dev->config; + + switch (config->channel) { + case 3: + if (channel == RX_MTIOCxB) { + MTU.TOER.BIT.OE3B = state; + } else if (channel == RX_MTIOCxD) { + MTU.TOER.BIT.OE3D = state; + } else { + /* Do nothing */ + } + break; + + case 4: + if (channel == RX_MTIOCxA) { + MTU.TOER.BIT.OE4A = state; + } else if (channel == RX_MTIOCxB) { + MTU.TOER.BIT.OE4B = state; + } else if (channel == RX_MTIOCxC) { + MTU.TOER.BIT.OE4C = state; + } else if (channel == RX_MTIOCxD) { + MTU.TOER.BIT.OE4D = state; + } else { + /* Do nothing */ + } + break; + + default: + break; + } +} + +static inline int pwm_rx_set_counter_clear(const struct device *dev, int counter_clear_channel) +{ + const struct pwm_renesas_rx_config *config = dev->config; + + switch (counter_clear_channel) { + case RX_MTIOCxA: + config->reg.tcr->cclr = 1; + break; + case RX_MTIOCxB: + config->reg.tcr->cclr = 2; + break; + case RX_MTIOCxC: + if (config->max_num_channels > 2) { + config->reg.tcr->cclr = 5; + } + break; + case RX_MTIOCxD: + if (config->max_num_channels > 2) { + config->reg.tcr->cclr = 6; + } + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static inline int pwm_rx_set_period(const struct device *dev, int channel, uint16_t period_cycles) +{ + const struct pwm_renesas_rx_config *config = dev->config; + int counter_clear_channel, ret; + + if (channel % 2 == 0) { + counter_clear_channel = channel + 1; + } else { + counter_clear_channel = channel - 1; + } + + ret = pwm_rx_set_counter_clear(dev, counter_clear_channel); + if (ret) { + return ret; + } + config->reg.tgr[counter_clear_channel] = period_cycles; + + /* For synchronous PWM, the device with the counter clear register has to be started + * so that all other synchronous PWMs can work. For non-synchronous PWMs, the clock + * has to be started anyway. + */ + + return 0; +} + +static inline void mtu_start_counter(const struct device *dev) +{ + const struct pwm_renesas_rx_config *config = dev->config; + + WRITE_BIT(*config->reg.tstr, config->bit_idx, 1); +} + +static inline void mtu_stop_counter(const struct device *dev) +{ + const struct pwm_renesas_rx_config *config = dev->config; + + WRITE_BIT(*config->reg.tstr, config->bit_idx, 0); +} + +static int pwm_renesas_rx_set_cycles(const struct device *dev, uint32_t channel, + uint32_t period_cycles, uint32_t pulse_cycles, + pwm_flags_t flags) +{ + const struct pwm_renesas_rx_config *config = dev->config; + uint8_t pwm_state = PWM_STATE_SWITCHING; + uint32_t pulse; + + if ((period_cycles > UINT16_MAX) || (pulse_cycles > UINT16_MAX)) { + LOG_INF("Fail to set period: %d", period_cycles); + return -EINVAL; + } + + if (channel >= config->max_num_channels) { + LOG_INF("Fail to set channel: %d", channel); + return -EINVAL; + } + + mtu_stop_counter(dev); + + if (pulse_cycles == period_cycles) { + /* 100% duty cycle */ + if (flags & PWM_POLARITY_INVERTED) { + pwm_state = PWM_STATE_0; + } else { + pwm_state = PWM_STATE_100; + } + + /* The PWM device apparently does not change state if pulse_cycles == period_cycles, + * so we have to reduce pulse_cycles by one. Due to the value of pwm_state, the + * signal will remain constant at compare match + */ + pulse_cycles--; + } + + if (pulse_cycles == 0) { + /* 0% duty cycle */ + if (flags & PWM_POLARITY_INVERTED) { + pwm_state = PWM_STATE_100; + } else { + pwm_state = PWM_STATE_0; + } + } + + /* Enable TOER output when outputting a waveform from the MTIOC pin of MTU3 and MTU4. */ + mtu_output_enable(dev, channel, 1); + + /* Set the timer I/O control register (TIOR) for a PWM, in this version using PWM mode 1*/ + config->reg.tior[channel] = pwm_state; + pulse = (flags & PWM_POLARITY_INVERTED) ? period_cycles - pulse_cycles : pulse_cycles; + config->reg.tgr[channel] = (uint16_t)pulse; + pwm_rx_set_period(dev, channel, (uint16_t)period_cycles); + *config->reg.tcnt = 0; + + mtu_start_counter(dev); + + return 0; +} + +static int pwm_renesas_rx_get_cycles_per_sec(const struct device *dev, uint32_t channel, + uint64_t *cycles) +{ + + const struct pwm_renesas_rx_config *config = dev->config; + + uint32_t freq_hz; + int ret = 0; + + ret = clock_control_get_rate(config->clock, (clock_control_subsys_t)&config->clock_subsys, + &freq_hz); + + switch (config->prescaler) { + case RX_MTU_PWM_SOURCE_DIV_1: + __fallthrough; + case RX_MTU_PWM_SOURCE_DIV_4: + __fallthrough; + case RX_MTU_PWM_SOURCE_DIV_16: + __fallthrough; + case RX_MTU_PWM_SOURCE_DIV_64: + *cycles = freq_hz >> (config->prescaler * 2); + break; + default: + break; + } + + return 0; +} + +#ifdef CONFIG_PWM_CAPTURE +static int pwm_renesas_rx_configure_capture(const struct device *dev, uint32_t channel, + pwm_flags_t flags, pwm_capture_callback_handler_t cb, + void *user_data) +{ + const struct pwm_renesas_rx_config *config = dev->config; + struct pwm_renesas_rx_data *data = dev->data; + uint8_t state; + int ret; + + if (!(flags & PWM_CAPTURE_TYPE_MASK)) { + LOG_ERR("No PWM capture type specified"); + return -EINVAL; + } + if ((flags & PWM_CAPTURE_TYPE_MASK) == PWM_CAPTURE_TYPE_BOTH) { + LOG_ERR("Cannot capture both period and pulse width"); + return -ENOTSUP; + } + if (data->capture.is_busy) { + LOG_ERR("Capture already active on this pin"); + return -EBUSY; + } + + /* Set normal mode */ + *config->reg.tmdr = TMDR_MD_PWM_NORMAL_MODE; + + /* Set clear source */ + ret = pwm_rx_set_counter_clear(dev, channel); + if (ret) { + return ret; + } + + pinctrl_soc_pin_t pin = config->pcfg->states->pins[0]; + + data->port_pin = (pin.port_num << PORT_POS) | pin.pin_num; + + if (flags & PWM_CAPTURE_TYPE_PERIOD) { + data->capture.is_pulse_capture = false; + if (flags & PWM_POLARITY_INVERTED) { + state = INPUT_CAPTURE_AT_RISING_EDGE; + } else { + state = INPUT_CAPTURE_AT_FALLING_EDGE; + } + + } else { + state = INPUT_CAPTURE_AT_BOTH_EDGE; + data->capture.is_pulse_capture = true; + if (flags & PWM_POLARITY_INVERTED) { + data->start_source = INPUT_LOW; + data->capture_source = INPUT_HIGH; + } else { + data->start_source = INPUT_HIGH; + data->capture_source = INPUT_LOW; + } + } + + /* Set noise filter */ + WRITE_BIT(*config->reg.nfcr, config->bit_idx, 1); + + /* Set Timer I/O Control */ + if (channel % 2 == 0) { + /* I/O settings for even numbered channels are encoded in the lower 4 bytes + * of the timer I/O control register + */ + config->reg.tior[channel / 2] = (config->reg.tior[channel / 2] & 0xf0) + state; + } else { + /* I/O settings for even numbered channels are encoded in the higher 4 bytes + * of the timer I/O control register + */ + config->reg.tior[channel / 2] = + (config->reg.tior[channel / 2] & 0x0f) + (state << 4); + } + + data->capture.channel = channel; + data->capture.callback = cb; + data->capture.user_data = user_data; + data->capture.continuous = (flags & PWM_CAPTURE_MODE_CONTINUOUS) ? true : false; + + return 0; +} + +static int pwm_renesas_rx_enable_capture(const struct device *dev, uint32_t channel) +{ + const struct pwm_renesas_rx_config *config = dev->config; + struct pwm_renesas_rx_data *data = dev->data; + + if (channel >= config->max_num_channels) { + return -EINVAL; + } + + if (data->capture.is_busy) { + LOG_ERR("Capture already active on this pin"); + return -EBUSY; + } + + if (!data->capture.callback) { + LOG_ERR("PWM capture not configured"); + return -EINVAL; + } + + data->capture.is_busy = true; + + data->capture_a_irqn = config->tgi_irq[channel]; + data->cycle_end_irqn = config->tgi_irq[MAX_CHANNEL]; + + /* start counter */ + mtu_start_counter(dev); + + WRITE_BIT(*(config->reg.tier), channel, 1); + WRITE_BIT(*(config->reg.tier), TCIEV_BIT, 1); + + irq_enable(data->capture_a_irqn); + irq_enable(data->cycle_end_irqn); + + return 0; +} + +static int pwm_renesas_rx_disable_capture(const struct device *dev, uint32_t channel) +{ + const struct pwm_renesas_rx_config *config = dev->config; + struct pwm_renesas_rx_data *data = dev->data; + + if (channel >= config->max_num_channels) { + return -EINVAL; + } + + data->capture.is_busy = false; + + /* Disable interruption */ + irq_disable(data->capture_a_irqn); + irq_disable(data->cycle_end_irqn); + + /* Disable capture source */ + WRITE_BIT(*(config->reg.tier), channel, 0); + WRITE_BIT(*(config->reg.tier), TCIEV_BIT, 0); + + /* Stop timer */ + mtu_stop_counter(dev); + + /* Clear timer */ + config->reg.tgr[channel] = 0; + *config->reg.tcnt = 0; + + return 0; +} + +static void mtu_rx_tgi_isr(const struct device *dev) +{ + struct pwm_renesas_rx_data *data = dev->data; + const struct pwm_renesas_rx_config *config = dev->config; + uint16_t counter = config->reg.tgr[data->capture.channel]; + uint32_t period = UINT16_MAX + 1U; + uint8_t source = R_GPIO_PinRead(data->port_pin); + + if (data->capture.is_pulse_capture) { + if (source == data->start_source) { + data->capture.overflows = 0U; /* Clear the overflow counter */ + data->start_flag = CAPTURE_START; /* Start pulse width measurement */ + } else if (source == data->capture_source) { + data->capture.pulse = (data->capture.overflows * period) + counter; + data->start_flag = CAPTURE_STOP; /* Measurement Invalid */ + if (data->capture.callback != NULL) { + data->capture.callback(dev, data->capture.channel, 0, + data->capture.pulse, 0, + data->capture.user_data); + } + + /* Disable capture in single mode */ + if (data->capture.continuous == false) { + pwm_renesas_rx_disable_capture(dev, data->capture.channel); + } + } else { + /* Do nothing */ + } + } else { + if (data->start_flag == CAPTURE_STOP) { + data->start_flag = CAPTURE_START; /* Start measurement */ + data->capture.overflows = 0U; /* Clear the overflow counter */ + } else { + data->capture.period = (data->capture.overflows * period) + counter; + data->start_flag = CAPTURE_STOP; + if (data->capture.callback != NULL) { + data->capture.callback(dev, data->capture.channel, + data->capture.period, 0, 0, + data->capture.user_data); + } + + /* Disable capture in single mode */ + if (data->capture.continuous == false) { + pwm_renesas_rx_disable_capture(dev, data->capture.channel); + } + } + } +} + +static void mtu_rx_tgiv_isr(const struct device *dev) +{ + struct pwm_renesas_rx_data *data = dev->data; + + /* During the pulse width measurement */ + if (data->start_flag != CAPTURE_STOP) { + data->capture.overflows++; + } +} +#endif /* CONFIG_PWM_CAPTURE */ + +static DEVICE_API(pwm, pwm_renesas_rx_driver_api) = { + .set_cycles = pwm_renesas_rx_set_cycles, + .get_cycles_per_sec = pwm_renesas_rx_get_cycles_per_sec, +#ifdef CONFIG_PWM_CAPTURE + .configure_capture = pwm_renesas_rx_configure_capture, + .enable_capture = pwm_renesas_rx_enable_capture, + .disable_capture = pwm_renesas_rx_disable_capture, +#endif /* CONFIG_PWM_CAPTURE */ +}; + +static int pwm_renesas_rx_init(const struct device *dev) +{ + const struct pwm_renesas_rx_config *config = dev->config; + struct pwm_renesas_rx_data *data = dev->data; + int ret; + + /* Configure dt provided device signals when available */ + ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + return ret; + } + + ret = clock_control_on(config->clock, (clock_control_subsys_t)&config->clock_subsys); + if (ret < 0) { + return ret; + } + + ret = clock_control_get_rate(config->clock, (clock_control_subsys_t)&config->clock_subsys, + &data->clk_rate); + + if (ret < 0) { + return ret; + } + + /* for the functionality provided by the zephyr PWM API, PWM mode 2 is sufficient, + * but some MTUs only support PWM mode 1. So that, we using PWM mode 1 in this version. + */ + *config->reg.tmdr = TMDR_MD_PWM_MODE_1; + + config->reg.tcr->tpsc = config->prescaler; + + /* internal input clock default setting (falling edge) */ + config->reg.tcr->ckeg = 0; + + /* setting count-up */ + WRITE_BIT(*config->reg.tsr, TCFD_BIT, true); + + /* synchronize not set*/ + WRITE_BIT(*config->reg.tsyr, config->bit_idx, false); + + return 0; +} + +#ifdef CONFIG_PWM_CAPTURE +#define IS_HAVE_4_CHANNEL(index) ((DT_REG_SIZE_BY_NAME(DT_INST_PARENT(index), TIOR) * 2) > 2) +#define IRQ_PWM_INIT(index) \ + .tgi_irq[RX_MTIOCxA] = DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgia, irq), \ + .tgi_irq[RX_MTIOCxB] = DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgib, irq), \ + .tgi_irq[MAX_CHANNEL] = DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgiv, irq), \ + COND_CODE_1(DT_IRQ_HAS_NAME(DT_INST_PARENT(index), tgic), \ + (.tgi_irq[RX_MTIOCxC] = \ + DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgic, irq),), ()) \ + COND_CODE_1(DT_IRQ_HAS_NAME(DT_INST_PARENT(index), tgid), \ + (.tgi_irq[RX_MTIOCxD] = DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgid, irq),), ()) +#define IRQ_PWM_CONFIG_INIT(index) \ + do { \ + IRQ_CONNECT(DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgia, irq), \ + DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgia, priority), mtu_rx_tgi_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + IRQ_CONNECT(DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgib, irq), \ + DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgib, priority), mtu_rx_tgi_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + IRQ_CONNECT(DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgiv, irq), \ + DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgiv, priority), \ + mtu_rx_tgiv_isr, DEVICE_DT_INST_GET(index), 0); \ + COND_CODE_1(DT_IRQ_HAS_NAME(DT_INST_PARENT(index), tgic), \ + (IRQ_CONNECT(DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgic, irq), \ + DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgic, priority), \ + mtu_rx_tgi_isr, DEVICE_DT_INST_GET(index), 0);), \ + ()) \ + COND_CODE_1(DT_IRQ_HAS_NAME(DT_INST_PARENT(index), tgid), \ + (IRQ_CONNECT(DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgid, irq), \ + DT_IRQ_BY_NAME(DT_INST_PARENT(index), tgid, priority), \ + mtu_rx_tgi_isr, DEVICE_DT_INST_GET(index), 0);), \ + ()) \ + } while (0) +#else +#define IRQ_PWM_INIT(index) +#define IRQ_PWM_CONFIG_INIT(index) +#endif + +#define PWM_DEVICE_INIT(index) \ + PINCTRL_DT_DEFINE(DT_INST_PARENT(index)); \ + static const struct pwm_renesas_rx_config pwm_rx_cfg##index = { \ + .pcfg = PINCTRL_DT_DEV_CONFIG_GET(DT_INST_PARENT(index)), \ + .channel = DT_PROP(DT_INST_PARENT(index), channel), \ + .prescaler = DT_INST_PROP(index, prescaler), \ + .reg = \ + { \ + .tcr = (struct tcr_reg *)DT_REG_ADDR_BY_NAME( \ + DT_INST_PARENT(index), TCR), \ + .tmdr = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), \ + TMDR), \ + .tior = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), \ + TIOR), \ + .tier = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), \ + TIER), \ + .tsr = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), TSR), \ + .tgr = (uint16_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), \ + TGR), \ + .tcnt = (uint16_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), \ + TCNT), \ + .nfcr = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_PARENT(index), \ + NFCR), \ + .tstr = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_GPARENT(index), \ + TSTR), \ + .tsyr = (uint8_t *)DT_REG_ADDR_BY_NAME(DT_INST_GPARENT(index), \ + TSYR), \ + }, \ + .bit_idx = DT_PROP(DT_INST_PARENT(index), bit_idx), \ + .max_num_channels = DT_REG_SIZE_BY_NAME(DT_INST_PARENT(index), TIOR) * 2, \ + .clock = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(index))), \ + .clock_subsys = \ + { \ + .mstp = DT_CLOCKS_CELL(DT_INST_PARENT(index), mstp), \ + .stop_bit = DT_CLOCKS_CELL(DT_INST_PARENT(index), stop_bit), \ + }, \ + IRQ_PWM_INIT(index)}; \ + static struct pwm_renesas_rx_data pwm_renesas_rx_data##index; \ + static int pwm_renesas_rx_init_##index(const struct device *dev) \ + { \ + IRQ_PWM_CONFIG_INIT(index); \ + int err = pwm_renesas_rx_init(dev); \ + if (err != 0) { \ + return err; \ + } \ + return 0; \ + } \ + DEVICE_DT_INST_DEFINE(index, pwm_renesas_rx_init_##index, NULL, \ + &pwm_renesas_rx_data##index, &pwm_rx_cfg##index, POST_KERNEL, \ + CONFIG_PWM_INIT_PRIORITY, &pwm_renesas_rx_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PWM_DEVICE_INIT) diff --git a/dts/bindings/misc/renesas,rx-mtu.yaml b/dts/bindings/misc/renesas,rx-mtu.yaml new file mode 100644 index 00000000000..fbaa298aa32 --- /dev/null +++ b/dts/bindings/misc/renesas,rx-mtu.yaml @@ -0,0 +1,44 @@ +# Copyright (c) 2025 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Renesas RX MTU controller + +compatible: "renesas,rx-mtu" + +include: [base.yaml, pinctrl-device.yaml] + +properties: + channel: + type: int + description: channel MTU + + reg: + required: true + + reg-names: + required: true + + clocks: + required: true + + counter-clear-channel: + type: int + description: + Choose one channel to act as counter clear channel. Not required in synchronous + mode if another synchronous MTU has a counter clear channel defined. If neither a channel + nor synchronous mode is chosen, it is not possible to set a period, but the MTU will use a + constant period of 0xffff cycles. + + synchronous: + type: boolean + description: + if set, the device will operate in synchronous mode. All MTU devices in + synchronous mode have the same period (in cycles, but subject to different prescaler + settings). Only one of the synchronous devices has to specify a counter clear channel. + + bit-idx: + type: int + required: true + description: + which bit of the common timer start register (TSTR) and timer synchronous + register (TSYR) to set for this device. diff --git a/dts/bindings/pwm/renesas,rx-mtu-pwm.yaml b/dts/bindings/pwm/renesas,rx-mtu-pwm.yaml new file mode 100644 index 00000000000..77bb314feb5 --- /dev/null +++ b/dts/bindings/pwm/renesas,rx-mtu-pwm.yaml @@ -0,0 +1,57 @@ +# Copyright (c) 2025 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: + Renesas PWM RX Controller. There are two PWM modes in RX, PWM mode 1 and PWM mode 2. + In this version, we only support PWM mode 1. + The PWM waveform is output from the MTIOCnA and MTIOCnC pins by coupling the TGRA + register to the TGRB register and the TGRC register to the TGRD register. + // +----------------+----------------+----------------+--------------------+ + // | Channel | Register | PWM Mode 1 | PWM Mode 2 | + // +----------------+----------------+----------------+--------------------+ + // | MTU0 | MTU0.TGRA | MTIOCOA | MTIOCOA | + // | | MTU0.TGRB | | MTIOCOB | + // | | MTU0.TGRC | MTIOCOC | MTIOCOC | + // | | MTU0.TGRD | | MTIOCOD | + // +----------------+----------------+----------------+--------------------+ + // | MTU1 | MTU1.TGRA | MTIOC1A | MTIOC1A | + // | | MTU1.TGRB | | MTIOC1B | + // +----------------+----------------+----------------+--------------------+ + // | MTU2 | MTU2.TGRA | MTIOC2A | MTIOC2A | + // | | MTU2.TGRB | | MTIOC2B | + // +----------------+----------------+----------------+--------------------+ + // | MTU3 | MTU3.TGRA | MTIOC3A | Setting prohibited| + // | | MTU3.TGRB | | | + // | | MTU3.TGRC | MTIOC3C | | + // | | MTU3.TGRD | | | + // +----------------+----------------+----------------+ + + // | MTU4 | MTU4.TGRA | MTIOC4A | | + // | | MTU4.TGRB | | | + // | | MTU4.TGRC | MTIOC4C | | + // | | MTU4.TGRD | | | + // +----------------+----------------+----------------+--------------------+ + +compatible: "renesas,rx-mtu-pwm" + +include: [pwm-controller.yaml, pinctrl-device.yaml, base.yaml] + +properties: + prescaler: + type: int + default: 0 + description: Valid values are in the range 0-7 but + specifically depend on the device (see RX user's manual). Common settings are 0-3 for + dividers PCKL/1, PCLK/4, PCLK/16 and PCLK/64. + + "#pwm-cells": + const: 3 + description: | + Number of items to expect in a PWM + - channel of the timer used for PWM + - period to set in ns + - flags : combination of standard flags like PWM_POLARITY_NORMAL + +pwm-cells: + - channel + - period + - flags diff --git a/include/zephyr/dt-bindings/pwm/rx_mtu_pwm.h b/include/zephyr/dt-bindings/pwm/rx_mtu_pwm.h new file mode 100644 index 00000000000..43b94e49750 --- /dev/null +++ b/include/zephyr/dt-bindings/pwm/rx_mtu_pwm.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_PWM_RX_MTU_PWM_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_PWM_RX_MTU_PWM_H_ + +/* PWM SOURCE DIVIDER */ +#define RX_MTU_PWM_SOURCE_DIV_1 0 +#define RX_MTU_PWM_SOURCE_DIV_4 1 +#define RX_MTU_PWM_SOURCE_DIV_16 2 +#define RX_MTU_PWM_SOURCE_DIV_64 3 + +/* PWM IO */ +#define RX_MTIOCxA 0 +#define RX_MTIOCxB 1 +#define RX_MTIOCxC 2 +#define RX_MTIOCxD 3 + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_PWM_RX_MTU_PWM_H_ */