427 lines
11 KiB
C
427 lines
11 KiB
C
/*
|
|
* Copyright (c) 2020-2022 IoT.bzh
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/renesas_cpg_mssr.h>
|
|
#include <zephyr/dt-bindings/clock/renesas_cpg_mssr.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/kernel.h>
|
|
#include "clock_control_renesas_cpg_mssr.h"
|
|
#include <stdlib.h>
|
|
|
|
#define LOG_LEVEL CONFIG_CLOCK_CONTROL_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(clock_control_rcar);
|
|
|
|
static void rcar_cpg_reset(uint32_t base_address, uint32_t reg, uint32_t bit)
|
|
{
|
|
rcar_cpg_write(base_address, srcr[reg], BIT(bit));
|
|
rcar_cpg_write(base_address, SRSTCLR(reg), BIT(bit));
|
|
}
|
|
|
|
void rcar_cpg_write(uint32_t base_address, uint32_t reg, uint32_t val)
|
|
{
|
|
sys_write32(~val, base_address + CPGWPR);
|
|
sys_write32(val, base_address + reg);
|
|
/* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */
|
|
k_sleep(K_USEC(35));
|
|
}
|
|
|
|
int rcar_cpg_mstp_clock_endisable(uint32_t base_address, uint32_t module, bool enable)
|
|
{
|
|
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(mstpcr), "Invalid module number for cpg clock: %d",
|
|
module);
|
|
|
|
reg_val = sys_read32(base_address + mstpcr[reg]);
|
|
if (enable) {
|
|
reg_val &= ~bitmask;
|
|
} else {
|
|
reg_val |= bitmask;
|
|
}
|
|
|
|
sys_write32(reg_val, base_address + mstpcr[reg]);
|
|
if (!enable) {
|
|
rcar_cpg_reset(base_address, reg, bit);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_cpg_clk_info_table_items(const void *key, const void *element)
|
|
{
|
|
const struct cpg_clk_info_table *e = element;
|
|
uint32_t module = (uintptr_t)key;
|
|
|
|
if (e->module == module) {
|
|
return 0;
|
|
} else if (e->module < module) {
|
|
return 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
struct cpg_clk_info_table *
|
|
rcar_cpg_find_clk_info_by_module_id(const struct device *dev, uint32_t domain, uint32_t id)
|
|
{
|
|
struct rcar_cpg_mssr_data *data = dev->data;
|
|
struct cpg_clk_info_table *item;
|
|
struct cpg_clk_info_table *table = data->clk_info_table[domain];
|
|
uint32_t table_size = data->clk_info_table_size[domain];
|
|
uintptr_t uintptr_id = id;
|
|
|
|
item = bsearch((void *)uintptr_id, table, table_size, sizeof(*item),
|
|
cmp_cpg_clk_info_table_items);
|
|
if (!item) {
|
|
LOG_ERR("%s: can't find clk info (domain %u module %u)", dev->name, domain, id);
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
static uint32_t rcar_cpg_get_divider(const struct device *dev, struct cpg_clk_info_table *clk_info)
|
|
{
|
|
mem_addr_t reg_addr;
|
|
mm_reg_t reg_val;
|
|
uint32_t divider = RCAR_CPG_NONE;
|
|
struct rcar_cpg_mssr_data *data = dev->data;
|
|
|
|
if (clk_info->domain == CPG_MOD) {
|
|
return 1;
|
|
}
|
|
|
|
reg_addr = clk_info->offset;
|
|
if (reg_addr == RCAR_CPG_NONE) {
|
|
/* if we don't have valid offset, in is equal to out */
|
|
return 1;
|
|
}
|
|
|
|
reg_addr += DEVICE_MMIO_GET(dev);
|
|
reg_val = sys_read32(reg_addr);
|
|
|
|
if (data->get_div_helper) {
|
|
divider = data->get_div_helper(reg_val, clk_info->module);
|
|
}
|
|
|
|
if (!divider) {
|
|
return RCAR_CPG_NONE;
|
|
}
|
|
|
|
return divider;
|
|
}
|
|
|
|
static int rcar_cpg_update_out_freq(const struct device *dev, struct cpg_clk_info_table *clk_info)
|
|
{
|
|
uint32_t divider = rcar_cpg_get_divider(dev, clk_info);
|
|
|
|
if (divider == RCAR_CPG_NONE) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
clk_info->out_freq = clk_info->in_freq / divider;
|
|
return 0;
|
|
}
|
|
|
|
static int64_t rcar_cpg_get_in_update_out_freq(const struct device *dev,
|
|
struct cpg_clk_info_table *clk_info)
|
|
{
|
|
int64_t freq = -ENOTSUP;
|
|
struct cpg_clk_info_table *parent_clk;
|
|
|
|
if (!clk_info) {
|
|
return freq;
|
|
}
|
|
|
|
if (clk_info->in_freq != RCAR_CPG_NONE) {
|
|
if (clk_info->out_freq == RCAR_CPG_NONE) {
|
|
if (rcar_cpg_update_out_freq(dev, clk_info) < 0) {
|
|
return freq;
|
|
}
|
|
}
|
|
return clk_info->in_freq;
|
|
}
|
|
|
|
parent_clk = clk_info->parent;
|
|
|
|
freq = rcar_cpg_get_in_update_out_freq(dev, parent_clk);
|
|
if (freq < 0) {
|
|
return freq;
|
|
}
|
|
|
|
clk_info->in_freq = parent_clk->out_freq;
|
|
|
|
freq = rcar_cpg_update_out_freq(dev, clk_info);
|
|
if (freq < 0) {
|
|
return freq;
|
|
}
|
|
|
|
return clk_info->in_freq;
|
|
}
|
|
|
|
static int64_t rcar_cpg_get_out_freq(const struct device *dev, struct cpg_clk_info_table *clk_info)
|
|
{
|
|
int64_t freq;
|
|
|
|
if (clk_info->out_freq != RCAR_CPG_NONE) {
|
|
return clk_info->out_freq;
|
|
}
|
|
|
|
freq = rcar_cpg_get_in_update_out_freq(dev, clk_info);
|
|
if (freq < 0) {
|
|
return freq;
|
|
}
|
|
|
|
return clk_info->out_freq;
|
|
}
|
|
|
|
static void rcar_cpg_change_children_in_out_freq(const struct device *dev,
|
|
struct cpg_clk_info_table *parent)
|
|
{
|
|
struct cpg_clk_info_table *children_list = parent->children_list;
|
|
|
|
while (children_list) {
|
|
children_list->in_freq = parent->out_freq;
|
|
|
|
if (rcar_cpg_update_out_freq(dev, children_list) < 0) {
|
|
/*
|
|
* Why it can happen:
|
|
* - divider is zero (with current implementation of board specific
|
|
* divider helper function it is impossible);
|
|
* - we don't have board specific implementation of get divider helper
|
|
* function;
|
|
* - we don't have this module in a table (for some of call chains of
|
|
* this function it is impossible);
|
|
* - impossible value is set in clock register divider bits.
|
|
*/
|
|
LOG_ERR("%s: error during getting divider from clock register, domain %u "
|
|
"module %u! Please, revise logic related to obtaining divider or "
|
|
"check presentence of clock inside appropriate clk_info_table",
|
|
dev->name, children_list->domain, children_list->module);
|
|
k_panic();
|
|
return;
|
|
}
|
|
|
|
/* child can have childrens */
|
|
rcar_cpg_change_children_in_out_freq(dev, children_list);
|
|
children_list = children_list->next_sibling;
|
|
}
|
|
}
|
|
|
|
int rcar_cpg_get_rate(const struct device *dev, clock_control_subsys_t sys, uint32_t *rate)
|
|
{
|
|
int64_t ret;
|
|
struct rcar_cpg_mssr_data *data;
|
|
struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
|
|
k_spinlock_key_t key;
|
|
|
|
struct cpg_clk_info_table *clk_info;
|
|
|
|
if (!dev || !sys || !rate) {
|
|
LOG_ERR("%s: received null ptr input arg(s) dev %p sys %p rate %p",
|
|
__func__, dev, sys, rate);
|
|
return -EINVAL;
|
|
}
|
|
|
|
clk_info = rcar_cpg_find_clk_info_by_module_id(dev, clk->domain, clk->module);
|
|
if (clk_info == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = dev->data;
|
|
|
|
key = k_spin_lock(&data->lock);
|
|
ret = rcar_cpg_get_out_freq(dev, clk_info);
|
|
k_spin_unlock(&data->lock, key);
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("%s: clk (domain %u module %u) error (%lld) during getting out frequency",
|
|
dev->name, clk->domain, clk->module, ret);
|
|
return -EINVAL;
|
|
} else if (ret > UINT_MAX) {
|
|
LOG_ERR("%s: clk (domain %u module %u) frequency bigger then max uint value",
|
|
dev->name, clk->domain, clk->module);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*rate = ret;
|
|
return 0;
|
|
}
|
|
|
|
int rcar_cpg_set_rate(const struct device *dev, clock_control_subsys_t sys,
|
|
clock_control_subsys_rate_t rate)
|
|
{
|
|
int ret = -ENOTSUP;
|
|
k_spinlock_key_t key;
|
|
struct cpg_clk_info_table *clk_info;
|
|
struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
|
|
struct rcar_cpg_mssr_data *data;
|
|
int64_t in_freq;
|
|
uint32_t divider;
|
|
uint32_t div_mask;
|
|
uint32_t module;
|
|
uintptr_t u_rate = (uintptr_t)rate;
|
|
|
|
if (!dev || !sys || !rate) {
|
|
LOG_ERR("%s: received null ptr input arg(s) dev %p sys %p rate %p",
|
|
__func__, dev, sys, rate);
|
|
return -EINVAL;
|
|
}
|
|
|
|
clk_info = rcar_cpg_find_clk_info_by_module_id(dev, clk->domain, clk->module);
|
|
if (clk_info == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (clk_info->domain == CPG_MOD) {
|
|
if (!clk_info->parent) {
|
|
LOG_ERR("%s: parent isn't present for module clock, module id %u",
|
|
dev->name, clk_info->module);
|
|
k_panic();
|
|
}
|
|
clk_info = clk_info->parent;
|
|
}
|
|
|
|
module = clk_info->module;
|
|
data = dev->data;
|
|
|
|
key = k_spin_lock(&data->lock);
|
|
in_freq = rcar_cpg_get_in_update_out_freq(dev, clk_info);
|
|
if (in_freq < 0) {
|
|
ret = in_freq;
|
|
goto unlock;
|
|
}
|
|
|
|
divider = in_freq / u_rate;
|
|
if (divider * u_rate != in_freq) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
if (!data->set_rate_helper) {
|
|
ret = -ENOTSUP;
|
|
goto unlock;
|
|
}
|
|
|
|
ret = data->set_rate_helper(module, ÷r, &div_mask);
|
|
if (!ret) {
|
|
int64_t out_rate;
|
|
uint32_t reg = sys_read32(clk_info->offset + DEVICE_MMIO_GET(dev));
|
|
|
|
reg &= ~div_mask;
|
|
rcar_cpg_write(DEVICE_MMIO_GET(dev), clk_info->offset, reg | divider);
|
|
|
|
clk_info->out_freq = RCAR_CPG_NONE;
|
|
|
|
out_rate = rcar_cpg_get_out_freq(dev, clk_info);
|
|
if (out_rate < 0 || out_rate != u_rate) {
|
|
ret = -EINVAL;
|
|
LOG_ERR("%s: clock (domain %u module %u) register cfg freq (%lld) "
|
|
"isn't equal to requested %lu",
|
|
dev->name, clk->domain, clk->module, out_rate, u_rate);
|
|
goto unlock;
|
|
}
|
|
|
|
rcar_cpg_change_children_in_out_freq(dev, clk_info);
|
|
}
|
|
|
|
unlock:
|
|
k_spin_unlock(&data->lock, key);
|
|
return ret;
|
|
}
|
|
|
|
void rcar_cpg_build_clock_relationship(const struct device *dev)
|
|
{
|
|
uint32_t domain;
|
|
k_spinlock_key_t key;
|
|
struct rcar_cpg_mssr_data *data = dev->data;
|
|
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
key = k_spin_lock(&data->lock);
|
|
for (domain = 0; domain < CPG_NUM_DOMAINS; domain++) {
|
|
uint32_t idx;
|
|
uint32_t prev_mod_id = 0;
|
|
struct cpg_clk_info_table *item = data->clk_info_table[domain];
|
|
|
|
for (idx = 0; idx < data->clk_info_table_size[domain]; idx++, item++) {
|
|
struct cpg_clk_info_table *parent;
|
|
|
|
/* check if an array is sorted by module id or not */
|
|
if (prev_mod_id >= item->module) {
|
|
LOG_ERR("%s: clocks have to be sorted inside clock table in "
|
|
"ascending order by module id field, domain %u "
|
|
"module id %u",
|
|
dev->name, item->domain, item->module);
|
|
k_panic();
|
|
}
|
|
|
|
prev_mod_id = item->module;
|
|
|
|
if (item->parent_id == RCAR_CPG_NONE) {
|
|
continue;
|
|
}
|
|
|
|
parent = rcar_cpg_find_clk_info_by_module_id(dev, CPG_CORE,
|
|
item->parent_id);
|
|
if (!parent) {
|
|
LOG_ERR("%s: can't find parent for clock with valid parent id, "
|
|
"domain %u module id %u",
|
|
dev->name, item->domain, item->module);
|
|
k_panic();
|
|
}
|
|
|
|
if (item->parent != NULL) {
|
|
LOG_ERR("%s: trying to set another parent for a clock, domain %u "
|
|
"module id %u, parent for the clock has been already set",
|
|
dev->name, item->domain, item->module);
|
|
k_panic();
|
|
}
|
|
|
|
item->parent = parent;
|
|
|
|
/* insert in the head of the children list of the parent */
|
|
item->next_sibling = parent->children_list;
|
|
parent->children_list = item;
|
|
}
|
|
}
|
|
k_spin_unlock(&data->lock, key);
|
|
}
|
|
|
|
void rcar_cpg_update_all_in_out_freq(const struct device *dev)
|
|
{
|
|
uint32_t domain;
|
|
k_spinlock_key_t key;
|
|
struct rcar_cpg_mssr_data *data = dev->data;
|
|
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
key = k_spin_lock(&data->lock);
|
|
for (domain = 0; domain < CPG_NUM_DOMAINS; domain++) {
|
|
uint32_t idx;
|
|
struct cpg_clk_info_table *item = data->clk_info_table[domain];
|
|
|
|
for (idx = 0; idx < data->clk_info_table_size[domain]; idx++, item++) {
|
|
if (rcar_cpg_get_in_update_out_freq(dev, item) < 0) {
|
|
LOG_ERR("%s: can't update in/out freq for clock during init, "
|
|
"domain %u module %u! Please, review correctness of data "
|
|
"inside clk_info_table",
|
|
dev->name, item->domain, item->module);
|
|
k_panic();
|
|
}
|
|
}
|
|
}
|
|
k_spin_unlock(&data->lock, key);
|
|
}
|