From 1d2752f4ee506efdb9b67a166a9037347be4fc37 Mon Sep 17 00:00:00 2001 From: Pierre Marzin Date: Thu, 27 Jan 2022 18:14:13 +0100 Subject: [PATCH] drivers: pwm: add Renesas R-Car driver Add R-Car Gen3 PWM driver. Clock diviser is automatically adjusted according to requested period and duty-cycle in order to obtain as much accuracy as possible. Indeed, in order to improve PWM accurancy, the PWM clock has to fit the requested period. So use the given period_cycle to define if the clock as to be adapted. In such case, increase/decrease the clock diviser to adapt the period_cycle and be sure that it fits into the 10 bits counter of the PWM controller. Tested on H3ULCB on pwm0 and pwm4. Signed-off-by: Pierre Marzin --- CODEOWNERS | 1 + .../clock_control_r8a7795_cpg_mssr.c | 3 + .../clock_control_renesas_cpg_mssr.h | 5 +- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.rcar | 12 + drivers/pwm/pwm_rcar.c | 268 ++++++++++++++++++ 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 drivers/pwm/Kconfig.rcar create mode 100644 drivers/pwm/pwm_rcar.c 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)