You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
5.3 KiB
227 lines
5.3 KiB
/* |
|
* 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, \ |
|
NULL, \ |
|
NULL, &rcar_mssr##inst##_config, \ |
|
PRE_KERNEL_1, \ |
|
CONFIG_CLOCK_CONTROL_INIT_PRIORITY, \ |
|
&rcar_cpg_mssr_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RCAR_MSSR_INIT)
|
|
|