297 lines
8.5 KiB
C
297 lines
8.5 KiB
C
/*
|
|
* Copyright (c) 2022 IoT.bzh
|
|
*
|
|
* r8a7795 Clock Pulse Generator / Module Standby and Software Reset
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT renesas_r8a7795_cpg_mssr
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/arch/cpu.h>
|
|
#include <zephyr/drivers/clock_control/renesas_cpg_mssr.h>
|
|
#include <zephyr/dt-bindings/clock/renesas_cpg_mssr.h>
|
|
#include <zephyr/dt-bindings/clock/r8a7795_cpg_mssr.h>
|
|
#include <zephyr/irq.h>
|
|
#include "clock_control_renesas_cpg_mssr.h"
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_DECLARE(clock_control_rcar);
|
|
|
|
#define R8A7795_CLK_SD_STOP_BIT 8
|
|
#define R8A7795_CLK_SD_DIV_MASK 0x3
|
|
#define R8A7795_CLK_SD_DIV_SHIFT 0
|
|
|
|
#define R8A7795_CLK_SDH_STOP_BIT 9
|
|
#define R8A7795_CLK_SDH_DIV_MASK 0x7
|
|
#define R8A7795_CLK_SDH_DIV_SHIFT 2
|
|
|
|
#define R8A7795_CLK_CANFD_STOP_BIT 8
|
|
#define R8A7795_CLK_CANFD_DIV_MASK 0x3f
|
|
|
|
struct r8a7795_cpg_mssr_config {
|
|
DEVICE_MMIO_ROM; /* Must be first */
|
|
};
|
|
|
|
struct r8a7795_cpg_mssr_data {
|
|
struct rcar_cpg_mssr_data cmn; /* Must be first */
|
|
};
|
|
|
|
/* NOTE: the array MUST be sorted by module field */
|
|
static struct cpg_clk_info_table core_props[] = {
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_S3D4, RCAR_CPG_NONE, RCAR_CPG_NONE,
|
|
RCAR_CPG_KHZ(66600)),
|
|
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD0H, 0x0074, RCAR_CPG_NONE, RCAR_CPG_MHZ(800)),
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD0, 0x0074, R8A7795_CLK_SD0H, RCAR_CPG_MHZ(800)),
|
|
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD1H, 0x0078, RCAR_CPG_NONE, RCAR_CPG_MHZ(800)),
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD1, 0x0078, R8A7795_CLK_SD1H, RCAR_CPG_MHZ(800)),
|
|
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD2H, 0x0268, RCAR_CPG_NONE, RCAR_CPG_MHZ(800)),
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD2, 0x0268, R8A7795_CLK_SD2H, RCAR_CPG_MHZ(800)),
|
|
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD3H, 0x026C, RCAR_CPG_NONE, RCAR_CPG_MHZ(800)),
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_SD3, 0x026C, R8A7795_CLK_SD3H, RCAR_CPG_MHZ(800)),
|
|
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_CANFD, 0x0244, RCAR_CPG_NONE, RCAR_CPG_MHZ(800)),
|
|
|
|
RCAR_CORE_CLK_INFO_ITEM(R8A7795_CLK_S0D12, RCAR_CPG_NONE, RCAR_CPG_NONE,
|
|
RCAR_CPG_KHZ(66600)),
|
|
};
|
|
|
|
/* NOTE: the array MUST be sorted by module field */
|
|
static struct cpg_clk_info_table mod_props[] = {
|
|
RCAR_MOD_CLK_INFO_ITEM(310, R8A7795_CLK_S3D4),
|
|
|
|
RCAR_MOD_CLK_INFO_ITEM(311, R8A7795_CLK_SD3),
|
|
RCAR_MOD_CLK_INFO_ITEM(312, R8A7795_CLK_SD2),
|
|
RCAR_MOD_CLK_INFO_ITEM(313, R8A7795_CLK_SD1),
|
|
RCAR_MOD_CLK_INFO_ITEM(314, R8A7795_CLK_SD0),
|
|
};
|
|
|
|
static int r8a7795_cpg_enable_disable_core(const struct device *dev,
|
|
struct cpg_clk_info_table *clk_info, uint32_t enable)
|
|
{
|
|
int ret = 0;
|
|
uint32_t reg;
|
|
|
|
enable = !!enable;
|
|
|
|
switch (clk_info->module) {
|
|
case R8A7795_CLK_SD0:
|
|
case R8A7795_CLK_SD1:
|
|
case R8A7795_CLK_SD2:
|
|
case R8A7795_CLK_SD3:
|
|
reg = sys_read32(DEVICE_MMIO_GET(dev) + clk_info->offset);
|
|
reg &= ~(1 << R8A7795_CLK_SD_STOP_BIT);
|
|
reg |= (!enable << R8A7795_CLK_SD_STOP_BIT);
|
|
break;
|
|
case R8A7795_CLK_SD0H:
|
|
case R8A7795_CLK_SD1H:
|
|
case R8A7795_CLK_SD2H:
|
|
case R8A7795_CLK_SD3H:
|
|
reg = sys_read32(DEVICE_MMIO_GET(dev) + clk_info->offset);
|
|
reg &= ~(1 << R8A7795_CLK_SDH_STOP_BIT);
|
|
reg |= (!enable << R8A7795_CLK_SDH_STOP_BIT);
|
|
break;
|
|
case R8A7795_CLK_CANFD:
|
|
reg = sys_read32(DEVICE_MMIO_GET(dev) + clk_info->offset);
|
|
reg &= ~(1 << R8A7795_CLK_CANFD_STOP_BIT);
|
|
reg |= (!enable << R8A7795_CLK_CANFD_STOP_BIT);
|
|
break;
|
|
default:
|
|
ret = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
if (!ret) {
|
|
rcar_cpg_write(DEVICE_MMIO_GET(dev), clk_info->offset, reg);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int r8a7795_cpg_core_clock_endisable(const struct device *dev, struct rcar_cpg_clk *clk,
|
|
bool enable)
|
|
{
|
|
struct cpg_clk_info_table *clk_info;
|
|
struct r8a7795_cpg_mssr_data *data = dev->data;
|
|
k_spinlock_key_t key;
|
|
|
|
clk_info = rcar_cpg_find_clk_info_by_module_id(dev, clk->domain, clk->module);
|
|
if (!clk_info) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (enable) {
|
|
if (clk->rate > 0) {
|
|
int ret;
|
|
uintptr_t rate = clk->rate;
|
|
|
|
ret = rcar_cpg_set_rate(dev, (clock_control_subsys_t)clk,
|
|
(clock_control_subsys_rate_t)rate);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
key = k_spin_lock(&data->cmn.lock);
|
|
r8a7795_cpg_enable_disable_core(dev, clk_info, enable);
|
|
k_spin_unlock(&data->cmn.lock, key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int r8a7795_cpg_mssr_start_stop(const struct device *dev, clock_control_subsys_t sys,
|
|
bool enable)
|
|
{
|
|
struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
|
|
int ret;
|
|
|
|
if (!dev || !sys) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (clk->domain == CPG_MOD) {
|
|
struct r8a7795_cpg_mssr_data *data = dev->data;
|
|
k_spinlock_key_t key;
|
|
|
|
key = k_spin_lock(&data->cmn.lock);
|
|
ret = rcar_cpg_mstp_clock_endisable(DEVICE_MMIO_GET(dev), clk->module, enable);
|
|
k_spin_unlock(&data->cmn.lock, key);
|
|
} else if (clk->domain == CPG_CORE) {
|
|
ret = r8a7795_cpg_core_clock_endisable(dev, clk, enable);
|
|
} else {
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t r8a7795_get_div_helper(uint32_t reg_val, uint32_t module)
|
|
{
|
|
switch (module) {
|
|
case R8A7795_CLK_SD0H:
|
|
case R8A7795_CLK_SD1H:
|
|
case R8A7795_CLK_SD2H:
|
|
case R8A7795_CLK_SD3H:
|
|
reg_val >>= R8A7795_CLK_SDH_DIV_SHIFT;
|
|
/* setting of value bigger than 4 is prohibited */
|
|
if ((reg_val & R8A7795_CLK_SDH_DIV_MASK) < 5) {
|
|
return 1 << (reg_val & R8A7795_CLK_SDH_DIV_MASK);
|
|
} else {
|
|
return RCAR_CPG_NONE;
|
|
}
|
|
case R8A7795_CLK_SD0:
|
|
case R8A7795_CLK_SD1:
|
|
case R8A7795_CLK_SD2:
|
|
case R8A7795_CLK_SD3:
|
|
/* convert only two possible values 0,1 to 2,4 */
|
|
return 1 << ((reg_val & R8A7795_CLK_SD_DIV_MASK) + 1);
|
|
case R8A7795_CLK_CANFD:
|
|
/* according to documentation, divider value stored in reg is equal to: val + 1 */
|
|
return (reg_val & R8A7795_CLK_CANFD_DIV_MASK) + 1;
|
|
case R8A7795_CLK_S3D4:
|
|
case R8A7795_CLK_S0D12:
|
|
return 1;
|
|
default:
|
|
return RCAR_CPG_NONE;
|
|
}
|
|
}
|
|
|
|
static int r8a7795_set_rate_helper(uint32_t module, uint32_t *divider, uint32_t *div_mask)
|
|
{
|
|
switch (module) {
|
|
case R8A7795_CLK_SD0:
|
|
case R8A7795_CLK_SD1:
|
|
case R8A7795_CLK_SD2:
|
|
case R8A7795_CLK_SD3:
|
|
/* possible to have only 2 or 4 */
|
|
if (*divider == 2 || *divider == 4) {
|
|
/* convert 2/4 to 0/1 */
|
|
*divider >>= 2;
|
|
*div_mask = R8A7795_CLK_SD_DIV_MASK << R8A7795_CLK_SD_DIV_SHIFT;
|
|
return 0;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
case R8A7795_CLK_SD0H:
|
|
case R8A7795_CLK_SD1H:
|
|
case R8A7795_CLK_SD2H:
|
|
case R8A7795_CLK_SD3H:
|
|
/* divider should be power of two and max possible value 16 */
|
|
if (!is_power_of_two(*divider) || *divider > 16) {
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
/* 1,2,4,8,16 have to be converted to 0,1,2,3,4 and then shifted */
|
|
*divider = (find_lsb_set(*divider) - 1) << R8A7795_CLK_SDH_DIV_SHIFT;
|
|
*div_mask = R8A7795_CLK_SDH_DIV_MASK << R8A7795_CLK_SDH_DIV_SHIFT;
|
|
return 0;
|
|
case R8A7795_CLK_CANFD:
|
|
/* according to documentation, divider value stored in reg is equal to: val + 1 */
|
|
*divider -= 1;
|
|
if (*divider <= R8A7795_CLK_CANFD_DIV_MASK) {
|
|
*div_mask = R8A7795_CLK_CANFD_DIV_MASK;
|
|
return 0;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static int r8a7795_cpg_mssr_start(const struct device *dev, clock_control_subsys_t sys)
|
|
{
|
|
return r8a7795_cpg_mssr_start_stop(dev, sys, true);
|
|
}
|
|
|
|
static int r8a7795_cpg_mssr_stop(const struct device *dev, clock_control_subsys_t sys)
|
|
{
|
|
return r8a7795_cpg_mssr_start_stop(dev, sys, false);
|
|
}
|
|
|
|
static int r8a7795_cpg_mssr_init(const struct device *dev)
|
|
{
|
|
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
|
|
|
|
rcar_cpg_build_clock_relationship(dev);
|
|
rcar_cpg_update_all_in_out_freq(dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct clock_control_driver_api r8a7795_cpg_mssr_api = {
|
|
.on = r8a7795_cpg_mssr_start,
|
|
.off = r8a7795_cpg_mssr_stop,
|
|
.get_rate = rcar_cpg_get_rate,
|
|
.set_rate = rcar_cpg_set_rate,
|
|
};
|
|
|
|
#define R8A7795_MSSR_INIT(inst) \
|
|
static struct r8a7795_cpg_mssr_config r8a7795_cpg_mssr##inst##_config = { \
|
|
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(inst)), \
|
|
}; \
|
|
\
|
|
static struct r8a7795_cpg_mssr_data r8a7795_cpg_mssr##inst##_data = { \
|
|
.cmn.clk_info_table[CPG_CORE] = core_props, \
|
|
.cmn.clk_info_table_size[CPG_CORE] = ARRAY_SIZE(core_props), \
|
|
.cmn.clk_info_table[CPG_MOD] = mod_props, \
|
|
.cmn.clk_info_table_size[CPG_MOD] = ARRAY_SIZE(mod_props), \
|
|
.cmn.get_div_helper = r8a7795_get_div_helper, \
|
|
.cmn.set_rate_helper = r8a7795_set_rate_helper \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(inst, \
|
|
r8a7795_cpg_mssr_init, \
|
|
NULL, \
|
|
&r8a7795_cpg_mssr##inst##_data, \
|
|
&r8a7795_cpg_mssr##inst##_config, \
|
|
PRE_KERNEL_1, \
|
|
CONFIG_CLOCK_CONTROL_INIT_PRIORITY, \
|
|
&r8a7795_cpg_mssr_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(R8A7795_MSSR_INIT)
|