zephyr/drivers/clock_control/clock_control_renesas_cpg_m...

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, &divider, &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);
}