269 lines
8.2 KiB
C
269 lines
8.2 KiB
C
/*
|
|
* 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)
|