| /* |
| * 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); |
| } |