18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2014 MundoReader S.L. 48c2ecf20Sopenharmony_ci * Author: Heiko Stuebner <heiko@sntech.de> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * based on clk/samsung/clk-cpu.c 78c2ecf20Sopenharmony_ci * Copyright (c) 2014 Samsung Electronics Co., Ltd. 88c2ecf20Sopenharmony_ci * Author: Thomas Abraham <thomas.ab@samsung.com> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * A CPU clock is defined as a clock supplied to a CPU or a group of CPUs. 118c2ecf20Sopenharmony_ci * The CPU clock is typically derived from a hierarchy of clock 128c2ecf20Sopenharmony_ci * blocks which includes mux and divider blocks. There are a number of other 138c2ecf20Sopenharmony_ci * auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI 148c2ecf20Sopenharmony_ci * clock for CPU domain. The rates of these auxiliary clocks are related to the 158c2ecf20Sopenharmony_ci * CPU clock rate and this relation is usually specified in the hardware manual 168c2ecf20Sopenharmony_ci * of the SoC or supplied after the SoC characterization. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * The below implementation of the CPU clock allows the rate changes of the CPU 198c2ecf20Sopenharmony_ci * clock and the corresponding rate changes of the auxillary clocks of the CPU 208c2ecf20Sopenharmony_ci * domain. The platform clock driver provides a clock register configuration 218c2ecf20Sopenharmony_ci * for each configurable rate which is then used to program the clock hardware 228c2ecf20Sopenharmony_ci * registers to acheive a fast co-oridinated rate change for all the CPU domain 238c2ecf20Sopenharmony_ci * clocks. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * On a rate change request for the CPU clock, the rate change is propagated 268c2ecf20Sopenharmony_ci * upto the PLL supplying the clock to the CPU domain clock blocks. While the 278c2ecf20Sopenharmony_ci * CPU domain PLL is reconfigured, the CPU domain clocks are driven using an 288c2ecf20Sopenharmony_ci * alternate clock source. If required, the alternate clock source is divided 298c2ecf20Sopenharmony_ci * down in order to keep the output clock rate within the previous OPP limits. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include <linux/of.h> 338c2ecf20Sopenharmony_ci#include <linux/slab.h> 348c2ecf20Sopenharmony_ci#include <linux/io.h> 358c2ecf20Sopenharmony_ci#include <linux/clk.h> 368c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 378c2ecf20Sopenharmony_ci#include "clk.h" 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci/** 408c2ecf20Sopenharmony_ci * struct rockchip_cpuclk: information about clock supplied to a CPU core. 418c2ecf20Sopenharmony_ci * @hw: handle between ccf and cpu clock. 428c2ecf20Sopenharmony_ci * @alt_parent: alternate parent clock to use when switching the speed 438c2ecf20Sopenharmony_ci * of the primary parent clock. 448c2ecf20Sopenharmony_ci * @reg_base: base register for cpu-clock values. 458c2ecf20Sopenharmony_ci * @clk_nb: clock notifier registered for changes in clock speed of the 468c2ecf20Sopenharmony_ci * primary parent clock. 478c2ecf20Sopenharmony_ci * @rate_count: number of rates in the rate_table 488c2ecf20Sopenharmony_ci * @rate_table: pll-rates and their associated dividers 498c2ecf20Sopenharmony_ci * @reg_data: cpu-specific register settings 508c2ecf20Sopenharmony_ci * @lock: clock lock 518c2ecf20Sopenharmony_ci */ 528c2ecf20Sopenharmony_cistruct rockchip_cpuclk { 538c2ecf20Sopenharmony_ci struct clk_hw hw; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci struct clk_mux cpu_mux; 568c2ecf20Sopenharmony_ci const struct clk_ops *cpu_mux_ops; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci struct clk *alt_parent; 598c2ecf20Sopenharmony_ci void __iomem *reg_base; 608c2ecf20Sopenharmony_ci struct notifier_block clk_nb; 618c2ecf20Sopenharmony_ci unsigned int rate_count; 628c2ecf20Sopenharmony_ci struct rockchip_cpuclk_rate_table *rate_table; 638c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_reg_data *reg_data; 648c2ecf20Sopenharmony_ci spinlock_t *lock; 658c2ecf20Sopenharmony_ci}; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw) 688c2ecf20Sopenharmony_ci#define to_rockchip_cpuclk_nb(nb) \ 698c2ecf20Sopenharmony_ci container_of(nb, struct rockchip_cpuclk, clk_nb) 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic const struct rockchip_cpuclk_rate_table *rockchip_get_cpuclk_settings( 728c2ecf20Sopenharmony_ci struct rockchip_cpuclk *cpuclk, unsigned long rate) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_rate_table *rate_table = 758c2ecf20Sopenharmony_ci cpuclk->rate_table; 768c2ecf20Sopenharmony_ci int i; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci for (i = 0; i < cpuclk->rate_count; i++) { 798c2ecf20Sopenharmony_ci if (rate == rate_table[i].prate) 808c2ecf20Sopenharmony_ci return &rate_table[i]; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return NULL; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic unsigned long rockchip_cpuclk_recalc_rate(struct clk_hw *hw, 878c2ecf20Sopenharmony_ci unsigned long parent_rate) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw); 908c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; 918c2ecf20Sopenharmony_ci u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci clksel0 >>= reg_data->div_core_shift; 948c2ecf20Sopenharmony_ci clksel0 &= reg_data->div_core_mask; 958c2ecf20Sopenharmony_ci return parent_rate / (clksel0 + 1); 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic const struct clk_ops rockchip_cpuclk_ops = { 998c2ecf20Sopenharmony_ci .recalc_rate = rockchip_cpuclk_recalc_rate, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void rockchip_cpuclk_set_dividers(struct rockchip_cpuclk *cpuclk, 1038c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_rate_table *rate) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci int i; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci /* alternate parent is active now. set the dividers */ 1088c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(rate->divs); i++) { 1098c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i]; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (!clksel->reg) 1128c2ecf20Sopenharmony_ci continue; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci pr_debug("%s: setting reg 0x%x to 0x%x\n", 1158c2ecf20Sopenharmony_ci __func__, clksel->reg, clksel->val); 1168c2ecf20Sopenharmony_ci writel(clksel->val, cpuclk->reg_base + clksel->reg); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int rockchip_cpuclk_pre_rate_change(struct rockchip_cpuclk *cpuclk, 1218c2ecf20Sopenharmony_ci struct clk_notifier_data *ndata) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; 1248c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_rate_table *rate; 1258c2ecf20Sopenharmony_ci unsigned long alt_prate, alt_div; 1268c2ecf20Sopenharmony_ci unsigned long flags; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci /* check validity of the new rate */ 1298c2ecf20Sopenharmony_ci rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate); 1308c2ecf20Sopenharmony_ci if (!rate) { 1318c2ecf20Sopenharmony_ci pr_err("%s: Invalid rate : %lu for cpuclk\n", 1328c2ecf20Sopenharmony_ci __func__, ndata->new_rate); 1338c2ecf20Sopenharmony_ci return -EINVAL; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci alt_prate = clk_get_rate(cpuclk->alt_parent); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci spin_lock_irqsave(cpuclk->lock, flags); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci /* 1418c2ecf20Sopenharmony_ci * If the old parent clock speed is less than the clock speed 1428c2ecf20Sopenharmony_ci * of the alternate parent, then it should be ensured that at no point 1438c2ecf20Sopenharmony_ci * the armclk speed is more than the old_rate until the dividers are 1448c2ecf20Sopenharmony_ci * set. 1458c2ecf20Sopenharmony_ci */ 1468c2ecf20Sopenharmony_ci if (alt_prate > ndata->old_rate) { 1478c2ecf20Sopenharmony_ci /* calculate dividers */ 1488c2ecf20Sopenharmony_ci alt_div = DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1; 1498c2ecf20Sopenharmony_ci if (alt_div > reg_data->div_core_mask) { 1508c2ecf20Sopenharmony_ci pr_warn("%s: limiting alt-divider %lu to %d\n", 1518c2ecf20Sopenharmony_ci __func__, alt_div, reg_data->div_core_mask); 1528c2ecf20Sopenharmony_ci alt_div = reg_data->div_core_mask; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci /* 1568c2ecf20Sopenharmony_ci * Change parents and add dividers in a single transaction. 1578c2ecf20Sopenharmony_ci * 1588c2ecf20Sopenharmony_ci * NOTE: we do this in a single transaction so we're never 1598c2ecf20Sopenharmony_ci * dividing the primary parent by the extra dividers that were 1608c2ecf20Sopenharmony_ci * needed for the alt. 1618c2ecf20Sopenharmony_ci */ 1628c2ecf20Sopenharmony_ci pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n", 1638c2ecf20Sopenharmony_ci __func__, alt_div, alt_prate, ndata->old_rate); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask, 1668c2ecf20Sopenharmony_ci reg_data->div_core_shift) | 1678c2ecf20Sopenharmony_ci HIWORD_UPDATE(reg_data->mux_core_alt, 1688c2ecf20Sopenharmony_ci reg_data->mux_core_mask, 1698c2ecf20Sopenharmony_ci reg_data->mux_core_shift), 1708c2ecf20Sopenharmony_ci cpuclk->reg_base + reg_data->core_reg); 1718c2ecf20Sopenharmony_ci } else { 1728c2ecf20Sopenharmony_ci /* select alternate parent */ 1738c2ecf20Sopenharmony_ci writel(HIWORD_UPDATE(reg_data->mux_core_alt, 1748c2ecf20Sopenharmony_ci reg_data->mux_core_mask, 1758c2ecf20Sopenharmony_ci reg_data->mux_core_shift), 1768c2ecf20Sopenharmony_ci cpuclk->reg_base + reg_data->core_reg); 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci spin_unlock_irqrestore(cpuclk->lock, flags); 1808c2ecf20Sopenharmony_ci return 0; 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic int rockchip_cpuclk_post_rate_change(struct rockchip_cpuclk *cpuclk, 1848c2ecf20Sopenharmony_ci struct clk_notifier_data *ndata) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; 1878c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_rate_table *rate; 1888c2ecf20Sopenharmony_ci unsigned long flags; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate); 1918c2ecf20Sopenharmony_ci if (!rate) { 1928c2ecf20Sopenharmony_ci pr_err("%s: Invalid rate : %lu for cpuclk\n", 1938c2ecf20Sopenharmony_ci __func__, ndata->new_rate); 1948c2ecf20Sopenharmony_ci return -EINVAL; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci spin_lock_irqsave(cpuclk->lock, flags); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci if (ndata->old_rate < ndata->new_rate) 2008c2ecf20Sopenharmony_ci rockchip_cpuclk_set_dividers(cpuclk, rate); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* 2038c2ecf20Sopenharmony_ci * post-rate change event, re-mux to primary parent and remove dividers. 2048c2ecf20Sopenharmony_ci * 2058c2ecf20Sopenharmony_ci * NOTE: we do this in a single transaction so we're never dividing the 2068c2ecf20Sopenharmony_ci * primary parent by the extra dividers that were needed for the alt. 2078c2ecf20Sopenharmony_ci */ 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci writel(HIWORD_UPDATE(0, reg_data->div_core_mask, 2108c2ecf20Sopenharmony_ci reg_data->div_core_shift) | 2118c2ecf20Sopenharmony_ci HIWORD_UPDATE(reg_data->mux_core_main, 2128c2ecf20Sopenharmony_ci reg_data->mux_core_mask, 2138c2ecf20Sopenharmony_ci reg_data->mux_core_shift), 2148c2ecf20Sopenharmony_ci cpuclk->reg_base + reg_data->core_reg); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci if (ndata->old_rate > ndata->new_rate) 2178c2ecf20Sopenharmony_ci rockchip_cpuclk_set_dividers(cpuclk, rate); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci spin_unlock_irqrestore(cpuclk->lock, flags); 2208c2ecf20Sopenharmony_ci return 0; 2218c2ecf20Sopenharmony_ci} 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci/* 2248c2ecf20Sopenharmony_ci * This clock notifier is called when the frequency of the parent clock 2258c2ecf20Sopenharmony_ci * of cpuclk is to be changed. This notifier handles the setting up all 2268c2ecf20Sopenharmony_ci * the divider clocks, remux to temporary parent and handling the safe 2278c2ecf20Sopenharmony_ci * frequency levels when using temporary parent. 2288c2ecf20Sopenharmony_ci */ 2298c2ecf20Sopenharmony_cistatic int rockchip_cpuclk_notifier_cb(struct notifier_block *nb, 2308c2ecf20Sopenharmony_ci unsigned long event, void *data) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct clk_notifier_data *ndata = data; 2338c2ecf20Sopenharmony_ci struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb); 2348c2ecf20Sopenharmony_ci int ret = 0; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n", 2378c2ecf20Sopenharmony_ci __func__, event, ndata->old_rate, ndata->new_rate); 2388c2ecf20Sopenharmony_ci if (event == PRE_RATE_CHANGE) 2398c2ecf20Sopenharmony_ci ret = rockchip_cpuclk_pre_rate_change(cpuclk, ndata); 2408c2ecf20Sopenharmony_ci else if (event == POST_RATE_CHANGE) 2418c2ecf20Sopenharmony_ci ret = rockchip_cpuclk_post_rate_change(cpuclk, ndata); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return notifier_from_errno(ret); 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistruct clk *rockchip_clk_register_cpuclk(const char *name, 2478c2ecf20Sopenharmony_ci const char *const *parent_names, u8 num_parents, 2488c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_reg_data *reg_data, 2498c2ecf20Sopenharmony_ci const struct rockchip_cpuclk_rate_table *rates, 2508c2ecf20Sopenharmony_ci int nrates, void __iomem *reg_base, spinlock_t *lock) 2518c2ecf20Sopenharmony_ci{ 2528c2ecf20Sopenharmony_ci struct rockchip_cpuclk *cpuclk; 2538c2ecf20Sopenharmony_ci struct clk_init_data init; 2548c2ecf20Sopenharmony_ci struct clk *clk, *cclk; 2558c2ecf20Sopenharmony_ci int ret; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci if (num_parents < 2) { 2588c2ecf20Sopenharmony_ci pr_err("%s: needs at least two parent clocks\n", __func__); 2598c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2608c2ecf20Sopenharmony_ci } 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL); 2638c2ecf20Sopenharmony_ci if (!cpuclk) 2648c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci init.name = name; 2678c2ecf20Sopenharmony_ci init.parent_names = &parent_names[reg_data->mux_core_main]; 2688c2ecf20Sopenharmony_ci init.num_parents = 1; 2698c2ecf20Sopenharmony_ci init.ops = &rockchip_cpuclk_ops; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* only allow rate changes when we have a rate table */ 2728c2ecf20Sopenharmony_ci init.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci /* disallow automatic parent changes by ccf */ 2758c2ecf20Sopenharmony_ci init.flags |= CLK_SET_RATE_NO_REPARENT; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci init.flags |= CLK_GET_RATE_NOCACHE; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci cpuclk->reg_base = reg_base; 2808c2ecf20Sopenharmony_ci cpuclk->lock = lock; 2818c2ecf20Sopenharmony_ci cpuclk->reg_data = reg_data; 2828c2ecf20Sopenharmony_ci cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb; 2838c2ecf20Sopenharmony_ci cpuclk->hw.init = &init; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci cpuclk->alt_parent = __clk_lookup(parent_names[reg_data->mux_core_alt]); 2868c2ecf20Sopenharmony_ci if (!cpuclk->alt_parent) { 2878c2ecf20Sopenharmony_ci pr_err("%s: could not lookup alternate parent: (%d)\n", 2888c2ecf20Sopenharmony_ci __func__, reg_data->mux_core_alt); 2898c2ecf20Sopenharmony_ci ret = -EINVAL; 2908c2ecf20Sopenharmony_ci goto free_cpuclk; 2918c2ecf20Sopenharmony_ci } 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci ret = clk_prepare_enable(cpuclk->alt_parent); 2948c2ecf20Sopenharmony_ci if (ret) { 2958c2ecf20Sopenharmony_ci pr_err("%s: could not enable alternate parent\n", 2968c2ecf20Sopenharmony_ci __func__); 2978c2ecf20Sopenharmony_ci goto free_cpuclk; 2988c2ecf20Sopenharmony_ci } 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci clk = __clk_lookup(parent_names[reg_data->mux_core_main]); 3018c2ecf20Sopenharmony_ci if (!clk) { 3028c2ecf20Sopenharmony_ci pr_err("%s: could not lookup parent clock: (%d) %s\n", 3038c2ecf20Sopenharmony_ci __func__, reg_data->mux_core_main, 3048c2ecf20Sopenharmony_ci parent_names[reg_data->mux_core_main]); 3058c2ecf20Sopenharmony_ci ret = -EINVAL; 3068c2ecf20Sopenharmony_ci goto free_alt_parent; 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci ret = clk_notifier_register(clk, &cpuclk->clk_nb); 3108c2ecf20Sopenharmony_ci if (ret) { 3118c2ecf20Sopenharmony_ci pr_err("%s: failed to register clock notifier for %s\n", 3128c2ecf20Sopenharmony_ci __func__, name); 3138c2ecf20Sopenharmony_ci goto free_alt_parent; 3148c2ecf20Sopenharmony_ci } 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci if (nrates > 0) { 3178c2ecf20Sopenharmony_ci cpuclk->rate_count = nrates; 3188c2ecf20Sopenharmony_ci cpuclk->rate_table = kmemdup(rates, 3198c2ecf20Sopenharmony_ci sizeof(*rates) * nrates, 3208c2ecf20Sopenharmony_ci GFP_KERNEL); 3218c2ecf20Sopenharmony_ci if (!cpuclk->rate_table) { 3228c2ecf20Sopenharmony_ci ret = -ENOMEM; 3238c2ecf20Sopenharmony_ci goto unregister_notifier; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci cclk = clk_register(NULL, &cpuclk->hw); 3288c2ecf20Sopenharmony_ci if (IS_ERR(cclk)) { 3298c2ecf20Sopenharmony_ci pr_err("%s: could not register cpuclk %s\n", __func__, name); 3308c2ecf20Sopenharmony_ci ret = PTR_ERR(cclk); 3318c2ecf20Sopenharmony_ci goto free_rate_table; 3328c2ecf20Sopenharmony_ci } 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci return cclk; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_cifree_rate_table: 3378c2ecf20Sopenharmony_ci kfree(cpuclk->rate_table); 3388c2ecf20Sopenharmony_ciunregister_notifier: 3398c2ecf20Sopenharmony_ci clk_notifier_unregister(clk, &cpuclk->clk_nb); 3408c2ecf20Sopenharmony_cifree_alt_parent: 3418c2ecf20Sopenharmony_ci clk_disable_unprepare(cpuclk->alt_parent); 3428c2ecf20Sopenharmony_cifree_cpuclk: 3438c2ecf20Sopenharmony_ci kfree(cpuclk); 3448c2ecf20Sopenharmony_ci return ERR_PTR(ret); 3458c2ecf20Sopenharmony_ci} 346