/* * Copyright (c) 2020-2022 IoT.bzh * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "clock_control_renesas_cpg_mssr.h" #include #define LOG_LEVEL CONFIG_CLOCK_CONTROL_LOG_LEVEL #include 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); }