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.
426 lines
11 KiB
426 lines
11 KiB
/* |
|
* 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); |
|
}
|
|
|