diff --git a/CODEOWNERS b/CODEOWNERS index fd28d3d54c..ffe913d97e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -361,6 +361,7 @@ /drivers/pwm/*gecko* @sun681 /drivers/pwm/*it8xxx2* @RuibinChang /drivers/pwm/*esp32* @LucasTambor +/drivers/pwm/*rcar* @pmarzin /drivers/regulator/*pmic* @danieldegrasse /drivers/reset/ @andrei-edward-popa /drivers/sensor/ @MaureenHelm diff --git a/drivers/clock_control/clock_control_r8a7795_cpg_mssr.c b/drivers/clock_control/clock_control_r8a7795_cpg_mssr.c index 31f8b0be88..dcd78f53ac 100644 --- a/drivers/clock_control/clock_control_r8a7795_cpg_mssr.c +++ b/drivers/clock_control/clock_control_r8a7795_cpg_mssr.c @@ -121,6 +121,9 @@ static int r8a7795_cpg_get_rate(const struct device *dev, case R8A7795_CLK_S3D4: *rate = S3D4_CLK_RATE; break; + case R8A7795_CLK_S0D12: + *rate = S0D12_CLK_RATE; + break; default: ret = -ENOTSUP; break; diff --git a/drivers/clock_control/clock_control_renesas_cpg_mssr.h b/drivers/clock_control/clock_control_renesas_cpg_mssr.h index e2f7f45b32..bd5a237ee0 100644 --- a/drivers/clock_control/clock_control_renesas_cpg_mssr.h +++ b/drivers/clock_control/clock_control_renesas_cpg_mssr.h @@ -38,8 +38,9 @@ static const uint16_t srcr[] = { #define CANFDCKCR_PARENT_CLK_RATE 800000000 #define CANFDCKCR_DIVIDER_MASK 0x1FF -/* SCIF clock */ -#define S3D4_CLK_RATE 66600000 +/* Peripherals Clocks */ +#define S3D4_CLK_RATE 66600000 /* SCIF */ +#define S0D12_CLK_RATE 66600000 /* PWM */ #endif /* CONFIG_SOC_SERIES_RCAR_GEN3 */ void rcar_cpg_write(uint32_t base_address, uint32_t reg, uint32_t val); diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 4fd9aa2592..2722ee2ed8 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -25,6 +25,7 @@ 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_PWM_GECKO pwm_gecko.c) zephyr_library_sources_ifdef(CONFIG_PWM_GD32 pwm_gd32.c) +zephyr_library_sources_ifdef(CONFIG_PWM_RCAR pwm_rcar.c) zephyr_library_sources_ifdef(CONFIG_PWM_TEST pwm_test.c) zephyr_library_sources_ifdef(CONFIG_PWM_RPI_PICO pwm_rpi_pico.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 3782c9282a..38b99fe565 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -71,6 +71,8 @@ source "drivers/pwm/Kconfig.gecko" source "drivers/pwm/Kconfig.gd32" +source "drivers/pwm/Kconfig.rcar" + source "drivers/pwm/Kconfig.test" source "drivers/pwm/Kconfig.rpi_pico" diff --git a/drivers/pwm/Kconfig.rcar b/drivers/pwm/Kconfig.rcar new file mode 100644 index 0000000000..39d5eeb1c4 --- /dev/null +++ b/drivers/pwm/Kconfig.rcar @@ -0,0 +1,12 @@ +# Reneas R-Car PWM configuration options + +# Copyright (c) 2022 IoT.bzh +# SPDX-License-Identifier: Apache-2.0 + +config PWM_RCAR + bool "Renesas R-Car PWM Driver" + default y + depends on SOC_FAMILY_RCAR + depends on DT_HAS_RENESAS_PWM_RCAR_ENABLED + help + Enable Renesas R-Car PWM Driver. diff --git a/drivers/pwm/pwm_rcar.c b/drivers/pwm/pwm_rcar.c new file mode 100644 index 0000000000..5f3e65baf1 --- /dev/null +++ b/drivers/pwm/pwm_rcar.c @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2022 IoT.bzh + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT renesas_pwm_rcar + +#include + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL +#include +LOG_MODULE_REGISTER(pwm_rcar); + +/* PWM Controller capabilities */ +#define RCAR_PWM_MAX_CYCLE 1023U +#define RCAR_PWM_MAX_DIV 24U +#define RCAR_PWM_MAX_CHANNEL 6 + +/* Registers */ +#define RCAR_PWM_REG_SHIFT 0x1000 +#define RCAR_PWM_CR(channel) \ + ((uint32_t)((channel * RCAR_PWM_REG_SHIFT)) + 0x00) /* PWM Control Register */ +#define RCAR_PWM_CNT(channel) \ + ((uint32_t)((channel * RCAR_PWM_REG_SHIFT)) + 0x04) /* PWM Count Register */ + +/* PWMCR (PWM Control Register) */ +#define RCAR_PWM_CR_CC_MASK 0x000f0000 /* Clock Control */ +#define RCAR_PWM_CR_CC_SHIFT 16 +#define RCAR_PWM_CR_CCMD BIT(15) /* Frequency Division Mode */ +#define RCAR_PWM_CR_SYNC BIT(11) +#define RCAR_PWM_CR_SS BIT(4) /* Single Pulse Output */ +#define RCAR_PWM_CR_EN BIT(0) /* Channel Enable */ + +/* PWM Diviser is on 5 bits (CC combined with CCMD) */ +#define RCAR_PWM_DIVISER_MASK (RCAR_PWM_CR_CC_MASK | RCAR_PWM_CR_CCMD) +#define RCAR_PWM_DIVISER_SHIFT 15 + +/* PWMCNT (PWM Count Register) */ +#define RCAR_PWM_CNT_CYC_MASK 0x03ff0000 /* PWM Cycle */ +#define RCAR_PWM_CNT_CYC_SHIFT 16 +#define RCAR_PWM_CNT_PH_MASK 0x000003ff /* PWM High-Level Period */ +#define RCAR_PWM_CNT_PH_SHIFT 0 + +struct pwm_rcar_cfg { + uint32_t reg_addr; + const struct device *clock_dev; + struct rcar_cpg_clk core_clk; + struct rcar_cpg_clk mod_clk; + const struct pinctrl_dev_config *pcfg; +}; + +struct pwm_rcar_data { + uint32_t clk_rate; +}; + +static uint32_t pwm_rcar_read(const struct pwm_rcar_cfg *config, uint32_t offs) +{ + return sys_read32(config->reg_addr + offs); +} + +static void pwm_rcar_write(const struct pwm_rcar_cfg *config, uint32_t offs, uint32_t value) +{ + sys_write32(value, config->reg_addr + offs); +} + +static void pwm_rcar_write_bit(const struct pwm_rcar_cfg *config, uint32_t offs, uint32_t bits, + bool value) +{ + uint32_t reg_val = pwm_rcar_read(config, offs); + + if (value) { + reg_val |= bits; + } else { + reg_val &= ~(bits); + } + + pwm_rcar_write(config, offs, reg_val); +} + +static int pwm_rcar_update_clk(const struct pwm_rcar_cfg *config, uint32_t channel, + uint32_t *period_cycles, uint32_t *pulse_cycles) +{ + uint32_t reg_val, power, diviser; + + power = pwm_rcar_read(config, RCAR_PWM_CR(channel)) & RCAR_PWM_DIVISER_MASK; + power = power >> RCAR_PWM_DIVISER_SHIFT; + diviser = 1 << power; + + LOG_DBG("Found old diviser : 2^%d=%d", power, diviser); + + /* Looking for the best possible clock diviser */ + if (*period_cycles > RCAR_PWM_MAX_CYCLE) { + /* Reducing clock speed */ + while (*period_cycles > RCAR_PWM_MAX_CYCLE) { + diviser *= 2; + *period_cycles /= 2; + *pulse_cycles /= 2; + power++; + if (power > RCAR_PWM_MAX_DIV) { + return -ENOTSUP; + } + } + } else { + /* Increasing clock speed */ + while (*period_cycles < (RCAR_PWM_MAX_CYCLE / 2)) { + if (power == 0) { + return -ENOTSUP; + } + diviser /= 2; + *period_cycles *= 2; + *pulse_cycles *= 2; + power--; + } + } + LOG_DBG("Found new diviser : 2^%d=%d\n", power, diviser); + + /* Set new clock Diviser */ + reg_val = pwm_rcar_read(config, RCAR_PWM_CR(channel)); + reg_val &= ~RCAR_PWM_DIVISER_MASK; + reg_val |= (power << RCAR_PWM_DIVISER_SHIFT); + pwm_rcar_write(config, RCAR_PWM_CR(channel), reg_val); + + return 0; +} + +static int pwm_rcar_set_cycles(const struct device *dev, uint32_t channel, uint32_t period_cycles, + uint32_t pulse_cycles, pwm_flags_t flags) +{ + const struct pwm_rcar_cfg *config = dev->config; + uint32_t reg_val; + int ret = 0; + + if (channel > RCAR_PWM_MAX_CHANNEL) { + return -ENOTSUP; + } + + if (flags != PWM_POLARITY_NORMAL) { + return -ENOTSUP; + } + + /* Prohibited values */ + if (period_cycles == 0U || pulse_cycles == 0U || pulse_cycles > period_cycles) { + return -EINVAL; + } + + LOG_DBG("base_reg=0x%x, pulse_cycles=%d, period_cycles=%d," + " duty_cycle=%d", + config->reg_addr, pulse_cycles, period_cycles, + (pulse_cycles * 100U / period_cycles)); + + /* Disable PWM */ + pwm_rcar_write_bit(config, RCAR_PWM_CR(channel), RCAR_PWM_CR_EN, false); + + /* Set continuous mode */ + pwm_rcar_write_bit(config, RCAR_PWM_CR(channel), RCAR_PWM_CR_SS, false); + + /* Enable SYNC mode */ + pwm_rcar_write_bit(config, RCAR_PWM_CR(channel), RCAR_PWM_CR_SYNC, true); + + /* + * Set clock counter according to the requested period_cycles + * if period_cycles is less than half of the counter, then the + * clock diviser could be updated as the diviser is a modulo 2. + */ + if (period_cycles > RCAR_PWM_MAX_CYCLE || period_cycles < (RCAR_PWM_MAX_CYCLE / 2)) { + LOG_DBG("Adapting frequency diviser..."); + ret = pwm_rcar_update_clk(config, channel, &period_cycles, &pulse_cycles); + if (ret != 0) { + return ret; + } + } + + /* Set total period cycle */ + reg_val = pwm_rcar_read(config, RCAR_PWM_CNT(channel)); + reg_val &= ~(RCAR_PWM_CNT_CYC_MASK); + reg_val |= (period_cycles << RCAR_PWM_CNT_CYC_SHIFT); + pwm_rcar_write(config, RCAR_PWM_CNT(channel), reg_val); + + /* Set high level period cycle */ + reg_val = pwm_rcar_read(config, RCAR_PWM_CNT(channel)); + reg_val &= ~(RCAR_PWM_CNT_PH_MASK); + reg_val |= (pulse_cycles << RCAR_PWM_CNT_PH_SHIFT); + pwm_rcar_write(config, RCAR_PWM_CNT(channel), reg_val); + + /* Enable PWM */ + pwm_rcar_write_bit(config, RCAR_PWM_CR(channel), RCAR_PWM_CR_EN, true); + + return ret; +} + +static int pwm_rcar_get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles) +{ + const struct pwm_rcar_cfg *config = dev->config; + struct pwm_rcar_data *data = dev->data; + uint32_t diviser; + + if (channel > RCAR_PWM_MAX_CHANNEL) { + return -ENOTSUP; + } + + diviser = pwm_rcar_read(config, RCAR_PWM_CR(channel)) & RCAR_PWM_DIVISER_MASK; + diviser = diviser >> RCAR_PWM_DIVISER_SHIFT; + *cycles = data->clk_rate >> diviser; + + LOG_DBG("Actual division: %d and Frequency: %d Hz", diviser, (uint32_t)*cycles); + + return 0; +} + +static int pwm_rcar_init(const struct device *dev) +{ + const struct pwm_rcar_cfg *config = dev->config; + struct pwm_rcar_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_dev, (clock_control_subsys_t *)&config->mod_clk); + if (ret < 0) { + return ret; + } + + ret = clock_control_get_rate(config->clock_dev, (clock_control_subsys_t *)&config->core_clk, + &data->clk_rate); + + if (ret < 0) { + return ret; + } + + return 0; +} + +static const struct pwm_driver_api pwm_rcar_driver_api = { + .set_cycles = pwm_rcar_set_cycles, + .get_cycles_per_sec = pwm_rcar_get_cycles_per_sec, +}; + +/* Device Instantiation */ +#define PWM_DEVICE_RCAR_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static const struct pwm_rcar_cfg pwm_rcar_cfg_##n = { \ + .reg_addr = DT_INST_REG_ADDR(n), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .mod_clk.module = DT_INST_CLOCKS_CELL_BY_IDX(n, 0, module), \ + .mod_clk.domain = DT_INST_CLOCKS_CELL_BY_IDX(n, 0, domain), \ + .core_clk.module = DT_INST_CLOCKS_CELL_BY_IDX(n, 1, module), \ + .core_clk.domain = DT_INST_CLOCKS_CELL_BY_IDX(n, 1, domain), \ + }; \ + static struct pwm_rcar_data pwm_rcar_data_##n; \ + DEVICE_DT_INST_DEFINE(n, pwm_rcar_init, NULL, &pwm_rcar_data_##n, &pwm_rcar_cfg_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &pwm_rcar_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PWM_DEVICE_RCAR_INIT)