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 <pierre.marzin@iot.bzh>
This commit is contained in:
Pierre Marzin 2022-01-27 18:14:13 +01:00 committed by Carles Cufí
parent 1367767624
commit 1d2752f4ee
7 changed files with 290 additions and 2 deletions

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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)

View file

@ -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"

12
drivers/pwm/Kconfig.rcar Normal file
View file

@ -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.

268
drivers/pwm/pwm_rcar.c Normal file
View file

@ -0,0 +1,268 @@
/*
* Copyright (c) 2022 IoT.bzh
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT renesas_pwm_rcar
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/renesas_cpg_mssr.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pwm.h>
#include <soc.h>
#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL
#include <zephyr/logging/log.h>
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)