root/drivers/clk/rockchip/clk-cpu.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. rockchip_get_cpuclk_settings
  2. rockchip_cpuclk_recalc_rate
  3. rockchip_cpuclk_set_dividers
  4. rockchip_cpuclk_pre_rate_change
  5. rockchip_cpuclk_post_rate_change
  6. rockchip_cpuclk_notifier_cb
  7. rockchip_clk_register_cpuclk

   1 // SPDX-License-Identifier: GPL-2.0-only
   2 /*
   3  * Copyright (c) 2014 MundoReader S.L.
   4  * Author: Heiko Stuebner <heiko@sntech.de>
   5  *
   6  * based on clk/samsung/clk-cpu.c
   7  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
   8  * Author: Thomas Abraham <thomas.ab@samsung.com>
   9  *
  10  * A CPU clock is defined as a clock supplied to a CPU or a group of CPUs.
  11  * The CPU clock is typically derived from a hierarchy of clock
  12  * blocks which includes mux and divider blocks. There are a number of other
  13  * auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
  14  * clock for CPU domain. The rates of these auxiliary clocks are related to the
  15  * CPU clock rate and this relation is usually specified in the hardware manual
  16  * of the SoC or supplied after the SoC characterization.
  17  *
  18  * The below implementation of the CPU clock allows the rate changes of the CPU
  19  * clock and the corresponding rate changes of the auxillary clocks of the CPU
  20  * domain. The platform clock driver provides a clock register configuration
  21  * for each configurable rate which is then used to program the clock hardware
  22  * registers to acheive a fast co-oridinated rate change for all the CPU domain
  23  * clocks.
  24  *
  25  * On a rate change request for the CPU clock, the rate change is propagated
  26  * upto the PLL supplying the clock to the CPU domain clock blocks. While the
  27  * CPU domain PLL is reconfigured, the CPU domain clocks are driven using an
  28  * alternate clock source. If required, the alternate clock source is divided
  29  * down in order to keep the output clock rate within the previous OPP limits.
  30  */
  31 
  32 #include <linux/of.h>
  33 #include <linux/slab.h>
  34 #include <linux/io.h>
  35 #include <linux/clk.h>
  36 #include <linux/clk-provider.h>
  37 #include "clk.h"
  38 
  39 /**
  40  * struct rockchip_cpuclk: information about clock supplied to a CPU core.
  41  * @hw:         handle between ccf and cpu clock.
  42  * @alt_parent: alternate parent clock to use when switching the speed
  43  *              of the primary parent clock.
  44  * @reg_base:   base register for cpu-clock values.
  45  * @clk_nb:     clock notifier registered for changes in clock speed of the
  46  *              primary parent clock.
  47  * @rate_count: number of rates in the rate_table
  48  * @rate_table: pll-rates and their associated dividers
  49  * @reg_data:   cpu-specific register settings
  50  * @lock:       clock lock
  51  */
  52 struct rockchip_cpuclk {
  53         struct clk_hw                           hw;
  54 
  55         struct clk_mux                          cpu_mux;
  56         const struct clk_ops                    *cpu_mux_ops;
  57 
  58         struct clk                              *alt_parent;
  59         void __iomem                            *reg_base;
  60         struct notifier_block                   clk_nb;
  61         unsigned int                            rate_count;
  62         struct rockchip_cpuclk_rate_table       *rate_table;
  63         const struct rockchip_cpuclk_reg_data   *reg_data;
  64         spinlock_t                              *lock;
  65 };
  66 
  67 #define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw)
  68 #define to_rockchip_cpuclk_nb(nb) \
  69                         container_of(nb, struct rockchip_cpuclk, clk_nb)
  70 
  71 static const struct rockchip_cpuclk_rate_table *rockchip_get_cpuclk_settings(
  72                             struct rockchip_cpuclk *cpuclk, unsigned long rate)
  73 {
  74         const struct rockchip_cpuclk_rate_table *rate_table =
  75                                                         cpuclk->rate_table;
  76         int i;
  77 
  78         for (i = 0; i < cpuclk->rate_count; i++) {
  79                 if (rate == rate_table[i].prate)
  80                         return &rate_table[i];
  81         }
  82 
  83         return NULL;
  84 }
  85 
  86 static unsigned long rockchip_cpuclk_recalc_rate(struct clk_hw *hw,
  87                                         unsigned long parent_rate)
  88 {
  89         struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw);
  90         const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
  91         u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg);
  92 
  93         clksel0 >>= reg_data->div_core_shift;
  94         clksel0 &= reg_data->div_core_mask;
  95         return parent_rate / (clksel0 + 1);
  96 }
  97 
  98 static const struct clk_ops rockchip_cpuclk_ops = {
  99         .recalc_rate = rockchip_cpuclk_recalc_rate,
 100 };
 101 
 102 static void rockchip_cpuclk_set_dividers(struct rockchip_cpuclk *cpuclk,
 103                                 const struct rockchip_cpuclk_rate_table *rate)
 104 {
 105         int i;
 106 
 107         /* alternate parent is active now. set the dividers */
 108         for (i = 0; i < ARRAY_SIZE(rate->divs); i++) {
 109                 const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i];
 110 
 111                 if (!clksel->reg)
 112                         continue;
 113 
 114                 pr_debug("%s: setting reg 0x%x to 0x%x\n",
 115                          __func__, clksel->reg, clksel->val);
 116                 writel(clksel->val, cpuclk->reg_base + clksel->reg);
 117         }
 118 }
 119 
 120 static int rockchip_cpuclk_pre_rate_change(struct rockchip_cpuclk *cpuclk,
 121                                            struct clk_notifier_data *ndata)
 122 {
 123         const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
 124         const struct rockchip_cpuclk_rate_table *rate;
 125         unsigned long alt_prate, alt_div;
 126         unsigned long flags;
 127 
 128         /* check validity of the new rate */
 129         rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate);
 130         if (!rate) {
 131                 pr_err("%s: Invalid rate : %lu for cpuclk\n",
 132                        __func__, ndata->new_rate);
 133                 return -EINVAL;
 134         }
 135 
 136         alt_prate = clk_get_rate(cpuclk->alt_parent);
 137 
 138         spin_lock_irqsave(cpuclk->lock, flags);
 139 
 140         /*
 141          * If the old parent clock speed is less than the clock speed
 142          * of the alternate parent, then it should be ensured that at no point
 143          * the armclk speed is more than the old_rate until the dividers are
 144          * set.
 145          */
 146         if (alt_prate > ndata->old_rate) {
 147                 /* calculate dividers */
 148                 alt_div =  DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1;
 149                 if (alt_div > reg_data->div_core_mask) {
 150                         pr_warn("%s: limiting alt-divider %lu to %d\n",
 151                                 __func__, alt_div, reg_data->div_core_mask);
 152                         alt_div = reg_data->div_core_mask;
 153                 }
 154 
 155                 /*
 156                  * Change parents and add dividers in a single transaction.
 157                  *
 158                  * NOTE: we do this in a single transaction so we're never
 159                  * dividing the primary parent by the extra dividers that were
 160                  * needed for the alt.
 161                  */
 162                 pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n",
 163                          __func__, alt_div, alt_prate, ndata->old_rate);
 164 
 165                 writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask,
 166                                               reg_data->div_core_shift) |
 167                        HIWORD_UPDATE(reg_data->mux_core_alt,
 168                                      reg_data->mux_core_mask,
 169                                      reg_data->mux_core_shift),
 170                        cpuclk->reg_base + reg_data->core_reg);
 171         } else {
 172                 /* select alternate parent */
 173                 writel(HIWORD_UPDATE(reg_data->mux_core_alt,
 174                                      reg_data->mux_core_mask,
 175                                      reg_data->mux_core_shift),
 176                        cpuclk->reg_base + reg_data->core_reg);
 177         }
 178 
 179         spin_unlock_irqrestore(cpuclk->lock, flags);
 180         return 0;
 181 }
 182 
 183 static int rockchip_cpuclk_post_rate_change(struct rockchip_cpuclk *cpuclk,
 184                                             struct clk_notifier_data *ndata)
 185 {
 186         const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
 187         const struct rockchip_cpuclk_rate_table *rate;
 188         unsigned long flags;
 189 
 190         rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate);
 191         if (!rate) {
 192                 pr_err("%s: Invalid rate : %lu for cpuclk\n",
 193                        __func__, ndata->new_rate);
 194                 return -EINVAL;
 195         }
 196 
 197         spin_lock_irqsave(cpuclk->lock, flags);
 198 
 199         if (ndata->old_rate < ndata->new_rate)
 200                 rockchip_cpuclk_set_dividers(cpuclk, rate);
 201 
 202         /*
 203          * post-rate change event, re-mux to primary parent and remove dividers.
 204          *
 205          * NOTE: we do this in a single transaction so we're never dividing the
 206          * primary parent by the extra dividers that were needed for the alt.
 207          */
 208 
 209         writel(HIWORD_UPDATE(0, reg_data->div_core_mask,
 210                                 reg_data->div_core_shift) |
 211                HIWORD_UPDATE(reg_data->mux_core_main,
 212                                 reg_data->mux_core_mask,
 213                                 reg_data->mux_core_shift),
 214                cpuclk->reg_base + reg_data->core_reg);
 215 
 216         if (ndata->old_rate > ndata->new_rate)
 217                 rockchip_cpuclk_set_dividers(cpuclk, rate);
 218 
 219         spin_unlock_irqrestore(cpuclk->lock, flags);
 220         return 0;
 221 }
 222 
 223 /*
 224  * This clock notifier is called when the frequency of the parent clock
 225  * of cpuclk is to be changed. This notifier handles the setting up all
 226  * the divider clocks, remux to temporary parent and handling the safe
 227  * frequency levels when using temporary parent.
 228  */
 229 static int rockchip_cpuclk_notifier_cb(struct notifier_block *nb,
 230                                         unsigned long event, void *data)
 231 {
 232         struct clk_notifier_data *ndata = data;
 233         struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb);
 234         int ret = 0;
 235 
 236         pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n",
 237                  __func__, event, ndata->old_rate, ndata->new_rate);
 238         if (event == PRE_RATE_CHANGE)
 239                 ret = rockchip_cpuclk_pre_rate_change(cpuclk, ndata);
 240         else if (event == POST_RATE_CHANGE)
 241                 ret = rockchip_cpuclk_post_rate_change(cpuclk, ndata);
 242 
 243         return notifier_from_errno(ret);
 244 }
 245 
 246 struct clk *rockchip_clk_register_cpuclk(const char *name,
 247                         const char *const *parent_names, u8 num_parents,
 248                         const struct rockchip_cpuclk_reg_data *reg_data,
 249                         const struct rockchip_cpuclk_rate_table *rates,
 250                         int nrates, void __iomem *reg_base, spinlock_t *lock)
 251 {
 252         struct rockchip_cpuclk *cpuclk;
 253         struct clk_init_data init;
 254         struct clk *clk, *cclk;
 255         int ret;
 256 
 257         if (num_parents < 2) {
 258                 pr_err("%s: needs at least two parent clocks\n", __func__);
 259                 return ERR_PTR(-EINVAL);
 260         }
 261 
 262         cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL);
 263         if (!cpuclk)
 264                 return ERR_PTR(-ENOMEM);
 265 
 266         init.name = name;
 267         init.parent_names = &parent_names[reg_data->mux_core_main];
 268         init.num_parents = 1;
 269         init.ops = &rockchip_cpuclk_ops;
 270 
 271         /* only allow rate changes when we have a rate table */
 272         init.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0;
 273 
 274         /* disallow automatic parent changes by ccf */
 275         init.flags |= CLK_SET_RATE_NO_REPARENT;
 276 
 277         init.flags |= CLK_GET_RATE_NOCACHE;
 278 
 279         cpuclk->reg_base = reg_base;
 280         cpuclk->lock = lock;
 281         cpuclk->reg_data = reg_data;
 282         cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb;
 283         cpuclk->hw.init = &init;
 284 
 285         cpuclk->alt_parent = __clk_lookup(parent_names[reg_data->mux_core_alt]);
 286         if (!cpuclk->alt_parent) {
 287                 pr_err("%s: could not lookup alternate parent: (%d)\n",
 288                        __func__, reg_data->mux_core_alt);
 289                 ret = -EINVAL;
 290                 goto free_cpuclk;
 291         }
 292 
 293         ret = clk_prepare_enable(cpuclk->alt_parent);
 294         if (ret) {
 295                 pr_err("%s: could not enable alternate parent\n",
 296                        __func__);
 297                 goto free_cpuclk;
 298         }
 299 
 300         clk = __clk_lookup(parent_names[reg_data->mux_core_main]);
 301         if (!clk) {
 302                 pr_err("%s: could not lookup parent clock: (%d) %s\n",
 303                        __func__, reg_data->mux_core_main,
 304                        parent_names[reg_data->mux_core_main]);
 305                 ret = -EINVAL;
 306                 goto free_alt_parent;
 307         }
 308 
 309         ret = clk_notifier_register(clk, &cpuclk->clk_nb);
 310         if (ret) {
 311                 pr_err("%s: failed to register clock notifier for %s\n",
 312                                 __func__, name);
 313                 goto free_alt_parent;
 314         }
 315 
 316         if (nrates > 0) {
 317                 cpuclk->rate_count = nrates;
 318                 cpuclk->rate_table = kmemdup(rates,
 319                                              sizeof(*rates) * nrates,
 320                                              GFP_KERNEL);
 321                 if (!cpuclk->rate_table) {
 322                         ret = -ENOMEM;
 323                         goto unregister_notifier;
 324                 }
 325         }
 326 
 327         cclk = clk_register(NULL, &cpuclk->hw);
 328         if (IS_ERR(cclk)) {
 329                 pr_err("%s: could not register cpuclk %s\n", __func__,  name);
 330                 ret = PTR_ERR(cclk);
 331                 goto free_rate_table;
 332         }
 333 
 334         return cclk;
 335 
 336 free_rate_table:
 337         kfree(cpuclk->rate_table);
 338 unregister_notifier:
 339         clk_notifier_unregister(clk, &cpuclk->clk_nb);
 340 free_alt_parent:
 341         clk_disable_unprepare(cpuclk->alt_parent);
 342 free_cpuclk:
 343         kfree(cpuclk);
 344         return ERR_PTR(ret);
 345 }

/* [<][>][^][v][top][bottom][index][help] */