228 lines
5.3 KiB
C
228 lines
5.3 KiB
C
/*
|
|
* Copyright (c) 2020 IoT.bzh
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT renesas_rcar_cpg_mssr
|
|
#include <errno.h>
|
|
#include <soc.h>
|
|
#include <drivers/clock_control.h>
|
|
#include <drivers/clock_control/rcar_clock_control.h>
|
|
#include <dt-bindings/clock/renesas_rcar_cpg.h>
|
|
|
|
struct rcar_mssr_config {
|
|
uint32_t base_address;
|
|
};
|
|
|
|
#define DEV_CFG(dev) ((struct rcar_mssr_config *)(dev->config))
|
|
|
|
static const uint16_t rmstpsr[] = {
|
|
0x110, 0x114, 0x118, 0x11c,
|
|
0x120, 0x124, 0x128, 0x12c,
|
|
0x980, 0x984, 0x988, 0x98c,
|
|
};
|
|
|
|
#define RMSTPSR(i) rmstpsr[i]
|
|
|
|
/* Software Reset Register offsets */
|
|
static const uint16_t srcr[] = {
|
|
0x0A0, 0x0A8, 0x0B0, 0x0B8,
|
|
0x0BC, 0x0C4, 0x1C8, 0x1CC,
|
|
0x920, 0x924, 0x928, 0x92C,
|
|
};
|
|
|
|
#define SRCR(i) srcr[i]
|
|
#define SRSTCLR(i) (0x940 + (i) * 4)
|
|
|
|
/* CPG write protect */
|
|
#define CPGWPR 0x0900
|
|
/* CAN-FD Clock Frequency Control Register */
|
|
#define CANFDCKCR 0x244
|
|
|
|
/* Clock stop bit */
|
|
#define CANFDCKCR_CKSTP BIT(8)
|
|
|
|
/* On H3,M3,E3 parent clock of CANFD has 800MHz rate */
|
|
#define CANFDCKCR_PARENT_CLK_RATE 800000000
|
|
#define CANFDCKCR_DIVIDER_MASK 0x1FF
|
|
|
|
#define S3D4_CLK_RATE 66600000
|
|
|
|
static void cpg_write(const struct rcar_mssr_config *config,
|
|
uint32_t reg, uint32_t val)
|
|
{
|
|
sys_write32(~val, config->base_address + CPGWPR);
|
|
sys_write32(val, config->base_address + reg);
|
|
/* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */
|
|
k_sleep(K_USEC(35));
|
|
}
|
|
|
|
static void cpg_reset(const struct rcar_mssr_config *config,
|
|
uint32_t reg, uint32_t bit)
|
|
{
|
|
cpg_write(config, SRCR(reg), BIT(bit));
|
|
cpg_write(config, SRSTCLR(reg), BIT(bit));
|
|
}
|
|
|
|
static int cpg_core_clock_endisable(const struct device *dev,
|
|
uint32_t module, uint32_t rate, bool enable)
|
|
{
|
|
const struct rcar_mssr_config *config = DEV_CFG(dev);
|
|
uint32_t divider;
|
|
unsigned int key;
|
|
int ret;
|
|
|
|
/* Only support CANFD core clock at the moment */
|
|
if (module != CPG_CORE_CLK_CANFD) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
key = irq_lock();
|
|
|
|
if (enable) {
|
|
if ((CANFDCKCR_PARENT_CLK_RATE % rate) != 0) {
|
|
__ASSERT(true, "Can not generate "
|
|
"%u from CANFD parent clock", rate);
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
divider = (CANFDCKCR_PARENT_CLK_RATE / rate) - 1;
|
|
if (divider > CANFDCKCR_DIVIDER_MASK) {
|
|
__ASSERT(true, "Can not generate %u "
|
|
"from CANFD parent clock", rate);
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
cpg_write(config, CANFDCKCR, divider);
|
|
} else {
|
|
cpg_write(config, CANFDCKCR, CANFDCKCR_CKSTP);
|
|
}
|
|
|
|
unlock:
|
|
irq_unlock(key);
|
|
return 0;
|
|
}
|
|
|
|
static int cpg_rmstp_clock_endisable(const struct device *dev,
|
|
uint32_t module, bool enable)
|
|
{
|
|
const struct rcar_mssr_config *config = DEV_CFG(dev);
|
|
uint32_t reg = module / 100;
|
|
uint32_t bit = module % 100;
|
|
uint32_t bitmask = BIT(bit);
|
|
uint32_t reg_val;
|
|
|
|
__ASSERT((bit < 32) && reg < ARRAY_SIZE(rmstpsr),
|
|
"Invalid module number for cpg clock: %d", module);
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
reg_val = sys_read32(config->base_address + RMSTPSR(reg));
|
|
if (enable) {
|
|
reg_val &= ~bitmask;
|
|
} else {
|
|
reg_val |= bitmask;
|
|
}
|
|
|
|
sys_write32(reg_val, config->base_address + RMSTPSR(reg));
|
|
if (!enable) {
|
|
cpg_reset(config, reg, bit);
|
|
}
|
|
|
|
irq_unlock(key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cpg_mssr_blocking_start(const struct device *dev,
|
|
clock_control_subsys_t sys)
|
|
{
|
|
struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
|
|
int ret = -EINVAL;
|
|
|
|
if (clk->domain == CPG_MOD) {
|
|
ret = cpg_rmstp_clock_endisable(dev, clk->module, true);
|
|
} else if (clk->domain == CPG_CORE) {
|
|
ret = cpg_core_clock_endisable(dev, clk->module, clk->rate,
|
|
true);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cpg_mssr_stop(const struct device *dev,
|
|
clock_control_subsys_t sys)
|
|
{
|
|
struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
|
|
int ret = -EINVAL;
|
|
|
|
if (clk->domain == CPG_MOD) {
|
|
ret = cpg_rmstp_clock_endisable(dev, clk->module, false);
|
|
} else if (clk->domain == CPG_CORE) {
|
|
ret = cpg_core_clock_endisable(dev, clk->module, 0, false);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cpg_get_rate(const struct device *dev,
|
|
clock_control_subsys_t sys,
|
|
uint32_t *rate)
|
|
{
|
|
const struct rcar_mssr_config *config = DEV_CFG(dev);
|
|
struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
|
|
uint32_t val;
|
|
int ret = 0;
|
|
|
|
if (clk->domain != CPG_CORE) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (clk->module) {
|
|
case CPG_CORE_CLK_CANFD:
|
|
val = sys_read32(config->base_address + CANFDCKCR);
|
|
if (val & CANFDCKCR_CKSTP) {
|
|
*rate = 0;
|
|
} else {
|
|
val &= CANFDCKCR_DIVIDER_MASK;
|
|
*rate = CANFDCKCR_PARENT_CLK_RATE / (val + 1);
|
|
}
|
|
break;
|
|
case CPG_CORE_CLK_S3D4:
|
|
*rate = S3D4_CLK_RATE;
|
|
break;
|
|
default:
|
|
ret = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rcar_cpg_mssr_init(const struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct clock_control_driver_api rcar_cpg_mssr_api = {
|
|
.on = cpg_mssr_blocking_start,
|
|
.off = cpg_mssr_stop,
|
|
.get_rate = cpg_get_rate,
|
|
};
|
|
|
|
#define RCAR_MSSR_INIT(inst) \
|
|
static struct rcar_mssr_config rcar_mssr##inst##_config = { \
|
|
.base_address = DT_INST_REG_ADDR(inst) \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(inst, \
|
|
&rcar_cpg_mssr_init, \
|
|
device_pm_control_nop, \
|
|
NULL, &rcar_mssr##inst##_config, \
|
|
PRE_KERNEL_1, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_OBJECTS, \
|
|
&rcar_cpg_mssr_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RCAR_MSSR_INIT)
|