linux/drivers/clk/samsung/clk-cpu.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
   3 * Author: Thomas Abraham <thomas.ab@samsung.com>
   4 *
   5 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
   6 * Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 *
  12 * This file contains the utility function to register CPU clock for Samsung
  13 * Exynos platforms. A CPU clock is defined as a clock supplied to a CPU or a
  14 * group of CPUs. The CPU clock is typically derived from a hierarchy of clock
  15 * blocks which includes mux and divider blocks. There are a number of other
  16 * auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
  17 * clock for CPU domain. The rates of these auxiliary clocks are related to the
  18 * CPU clock rate and this relation is usually specified in the hardware manual
  19 * of the SoC or supplied after the SoC characterization.
  20 *
  21 * The below implementation of the CPU clock allows the rate changes of the CPU
  22 * clock and the corresponding rate changes of the auxillary clocks of the CPU
  23 * domain. The platform clock driver provides a clock register configuration
  24 * for each configurable rate which is then used to program the clock hardware
  25 * registers to acheive a fast co-oridinated rate change for all the CPU domain
  26 * clocks.
  27 *
  28 * On a rate change request for the CPU clock, the rate change is propagated
  29 * upto the PLL supplying the clock to the CPU domain clock blocks. While the
  30 * CPU domain PLL is reconfigured, the CPU domain clocks are driven using an
  31 * alternate clock source. If required, the alternate clock source is divided
  32 * down in order to keep the output clock rate within the previous OPP limits.
  33*/
  34
  35#include <linux/errno.h>
  36#include <linux/slab.h>
  37#include <linux/clk.h>
  38#include <linux/clk-provider.h>
  39#include "clk-cpu.h"
  40
  41#define E4210_SRC_CPU           0x0
  42#define E4210_STAT_CPU          0x200
  43#define E4210_DIV_CPU0          0x300
  44#define E4210_DIV_CPU1          0x304
  45#define E4210_DIV_STAT_CPU0     0x400
  46#define E4210_DIV_STAT_CPU1     0x404
  47
  48#define E4210_DIV0_RATIO0_MASK  0x7
  49#define E4210_DIV1_HPM_MASK     (0x7 << 4)
  50#define E4210_DIV1_COPY_MASK    (0x7 << 0)
  51#define E4210_MUX_HPM_MASK      (1 << 20)
  52#define E4210_DIV0_ATB_SHIFT    16
  53#define E4210_DIV0_ATB_MASK     (DIV_MASK << E4210_DIV0_ATB_SHIFT)
  54
  55#define MAX_DIV                 8
  56#define DIV_MASK                7
  57#define DIV_MASK_ALL            0xffffffff
  58#define MUX_MASK                7
  59
  60/*
  61 * Helper function to wait until divider(s) have stabilized after the divider
  62 * value has changed.
  63 */
  64static void wait_until_divider_stable(void __iomem *div_reg, unsigned long mask)
  65{
  66        unsigned long timeout = jiffies + msecs_to_jiffies(10);
  67
  68        do {
  69                if (!(readl(div_reg) & mask))
  70                        return;
  71        } while (time_before(jiffies, timeout));
  72
  73        if (!(readl(div_reg) & mask))
  74                return;
  75
  76        pr_err("%s: timeout in divider stablization\n", __func__);
  77}
  78
  79/*
  80 * Helper function to wait until mux has stabilized after the mux selection
  81 * value was changed.
  82 */
  83static void wait_until_mux_stable(void __iomem *mux_reg, u32 mux_pos,
  84                                        unsigned long mux_value)
  85{
  86        unsigned long timeout = jiffies + msecs_to_jiffies(10);
  87
  88        do {
  89                if (((readl(mux_reg) >> mux_pos) & MUX_MASK) == mux_value)
  90                        return;
  91        } while (time_before(jiffies, timeout));
  92
  93        if (((readl(mux_reg) >> mux_pos) & MUX_MASK) == mux_value)
  94                return;
  95
  96        pr_err("%s: re-parenting mux timed-out\n", __func__);
  97}
  98
  99/* common round rate callback useable for all types of CPU clocks */
 100static long exynos_cpuclk_round_rate(struct clk_hw *hw,
 101                        unsigned long drate, unsigned long *prate)
 102{
 103        struct clk_hw *parent = clk_hw_get_parent(hw);
 104        *prate = clk_hw_round_rate(parent, drate);
 105        return *prate;
 106}
 107
 108/* common recalc rate callback useable for all types of CPU clocks */
 109static unsigned long exynos_cpuclk_recalc_rate(struct clk_hw *hw,
 110                        unsigned long parent_rate)
 111{
 112        /*
 113         * The CPU clock output (armclk) rate is the same as its parent
 114         * rate. Although there exist certain dividers inside the CPU
 115         * clock block that could be used to divide the parent clock,
 116         * the driver does not make use of them currently, except during
 117         * frequency transitions.
 118         */
 119        return parent_rate;
 120}
 121
 122static const struct clk_ops exynos_cpuclk_clk_ops = {
 123        .recalc_rate = exynos_cpuclk_recalc_rate,
 124        .round_rate = exynos_cpuclk_round_rate,
 125};
 126
 127/*
 128 * Helper function to set the 'safe' dividers for the CPU clock. The parameters
 129 * div and mask contain the divider value and the register bit mask of the
 130 * dividers to be programmed.
 131 */
 132static void exynos_set_safe_div(void __iomem *base, unsigned long div,
 133                                        unsigned long mask)
 134{
 135        unsigned long div0;
 136
 137        div0 = readl(base + E4210_DIV_CPU0);
 138        div0 = (div0 & ~mask) | (div & mask);
 139        writel(div0, base + E4210_DIV_CPU0);
 140        wait_until_divider_stable(base + E4210_DIV_STAT_CPU0, mask);
 141}
 142
 143/* handler for pre-rate change notification from parent clock */
 144static int exynos_cpuclk_pre_rate_change(struct clk_notifier_data *ndata,
 145                        struct exynos_cpuclk *cpuclk, void __iomem *base)
 146{
 147        const struct exynos_cpuclk_cfg_data *cfg_data = cpuclk->cfg;
 148        unsigned long alt_prate = clk_get_rate(cpuclk->alt_parent);
 149        unsigned long alt_div = 0, alt_div_mask = DIV_MASK;
 150        unsigned long div0, div1 = 0, mux_reg;
 151        unsigned long flags;
 152
 153        /* find out the divider values to use for clock data */
 154        while ((cfg_data->prate * 1000) != ndata->new_rate) {
 155                if (cfg_data->prate == 0)
 156                        return -EINVAL;
 157                cfg_data++;
 158        }
 159
 160        spin_lock_irqsave(cpuclk->lock, flags);
 161
 162        /*
 163         * For the selected PLL clock frequency, get the pre-defined divider
 164         * values. If the clock for sclk_hpm is not sourced from apll, then
 165         * the values for DIV_COPY and DIV_HPM dividers need not be set.
 166         */
 167        div0 = cfg_data->div0;
 168        if (cpuclk->flags & CLK_CPU_HAS_DIV1) {
 169                div1 = cfg_data->div1;
 170                if (readl(base + E4210_SRC_CPU) & E4210_MUX_HPM_MASK)
 171                        div1 = readl(base + E4210_DIV_CPU1) &
 172                                (E4210_DIV1_HPM_MASK | E4210_DIV1_COPY_MASK);
 173        }
 174
 175        /*
 176         * If the old parent clock speed is less than the clock speed of
 177         * the alternate parent, then it should be ensured that at no point
 178         * the armclk speed is more than the old_prate until the dividers are
 179         * set.  Also workaround the issue of the dividers being set to lower
 180         * values before the parent clock speed is set to new lower speed
 181         * (this can result in too high speed of armclk output clocks).
 182         */
 183        if (alt_prate > ndata->old_rate || ndata->old_rate > ndata->new_rate) {
 184                unsigned long tmp_rate = min(ndata->old_rate, ndata->new_rate);
 185
 186                alt_div = DIV_ROUND_UP(alt_prate, tmp_rate) - 1;
 187                WARN_ON(alt_div >= MAX_DIV);
 188
 189                if (cpuclk->flags & CLK_CPU_NEEDS_DEBUG_ALT_DIV) {
 190                        /*
 191                         * In Exynos4210, ATB clock parent is also mout_core. So
 192                         * ATB clock also needs to be mantained at safe speed.
 193                         */
 194                        alt_div |= E4210_DIV0_ATB_MASK;
 195                        alt_div_mask |= E4210_DIV0_ATB_MASK;
 196                }
 197                exynos_set_safe_div(base, alt_div, alt_div_mask);
 198                div0 |= alt_div;
 199        }
 200
 201        /* select sclk_mpll as the alternate parent */
 202        mux_reg = readl(base + E4210_SRC_CPU);
 203        writel(mux_reg | (1 << 16), base + E4210_SRC_CPU);
 204        wait_until_mux_stable(base + E4210_STAT_CPU, 16, 2);
 205
 206        /* alternate parent is active now. set the dividers */
 207        writel(div0, base + E4210_DIV_CPU0);
 208        wait_until_divider_stable(base + E4210_DIV_STAT_CPU0, DIV_MASK_ALL);
 209
 210        if (cpuclk->flags & CLK_CPU_HAS_DIV1) {
 211                writel(div1, base + E4210_DIV_CPU1);
 212                wait_until_divider_stable(base + E4210_DIV_STAT_CPU1,
 213                                DIV_MASK_ALL);
 214        }
 215
 216        spin_unlock_irqrestore(cpuclk->lock, flags);
 217        return 0;
 218}
 219
 220/* handler for post-rate change notification from parent clock */
 221static int exynos_cpuclk_post_rate_change(struct clk_notifier_data *ndata,
 222                        struct exynos_cpuclk *cpuclk, void __iomem *base)
 223{
 224        const struct exynos_cpuclk_cfg_data *cfg_data = cpuclk->cfg;
 225        unsigned long div = 0, div_mask = DIV_MASK;
 226        unsigned long mux_reg;
 227        unsigned long flags;
 228
 229        /* find out the divider values to use for clock data */
 230        if (cpuclk->flags & CLK_CPU_NEEDS_DEBUG_ALT_DIV) {
 231                while ((cfg_data->prate * 1000) != ndata->new_rate) {
 232                        if (cfg_data->prate == 0)
 233                                return -EINVAL;
 234                        cfg_data++;
 235                }
 236        }
 237
 238        spin_lock_irqsave(cpuclk->lock, flags);
 239
 240        /* select mout_apll as the alternate parent */
 241        mux_reg = readl(base + E4210_SRC_CPU);
 242        writel(mux_reg & ~(1 << 16), base + E4210_SRC_CPU);
 243        wait_until_mux_stable(base + E4210_STAT_CPU, 16, 1);
 244
 245        if (cpuclk->flags & CLK_CPU_NEEDS_DEBUG_ALT_DIV) {
 246                div |= (cfg_data->div0 & E4210_DIV0_ATB_MASK);
 247                div_mask |= E4210_DIV0_ATB_MASK;
 248        }
 249
 250        exynos_set_safe_div(base, div, div_mask);
 251        spin_unlock_irqrestore(cpuclk->lock, flags);
 252        return 0;
 253}
 254
 255/*
 256 * This notifier function is called for the pre-rate and post-rate change
 257 * notifications of the parent clock of cpuclk.
 258 */
 259static int exynos_cpuclk_notifier_cb(struct notifier_block *nb,
 260                                unsigned long event, void *data)
 261{
 262        struct clk_notifier_data *ndata = data;
 263        struct exynos_cpuclk *cpuclk;
 264        void __iomem *base;
 265        int err = 0;
 266
 267        cpuclk = container_of(nb, struct exynos_cpuclk, clk_nb);
 268        base = cpuclk->ctrl_base;
 269
 270        if (event == PRE_RATE_CHANGE)
 271                err = exynos_cpuclk_pre_rate_change(ndata, cpuclk, base);
 272        else if (event == POST_RATE_CHANGE)
 273                err = exynos_cpuclk_post_rate_change(ndata, cpuclk, base);
 274
 275        return notifier_from_errno(err);
 276}
 277
 278/* helper function to register a CPU clock */
 279int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx,
 280                unsigned int lookup_id, const char *name, const char *parent,
 281                const char *alt_parent, unsigned long offset,
 282                const struct exynos_cpuclk_cfg_data *cfg,
 283                unsigned long num_cfgs, unsigned long flags)
 284{
 285        struct exynos_cpuclk *cpuclk;
 286        struct clk_init_data init;
 287        struct clk *clk;
 288        int ret = 0;
 289
 290        cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL);
 291        if (!cpuclk)
 292                return -ENOMEM;
 293
 294        init.name = name;
 295        init.flags = CLK_SET_RATE_PARENT;
 296        init.parent_names = &parent;
 297        init.num_parents = 1;
 298        init.ops = &exynos_cpuclk_clk_ops;
 299
 300        cpuclk->hw.init = &init;
 301        cpuclk->ctrl_base = ctx->reg_base + offset;
 302        cpuclk->lock = &ctx->lock;
 303        cpuclk->flags = flags;
 304        cpuclk->clk_nb.notifier_call = exynos_cpuclk_notifier_cb;
 305
 306        cpuclk->alt_parent = __clk_lookup(alt_parent);
 307        if (!cpuclk->alt_parent) {
 308                pr_err("%s: could not lookup alternate parent %s\n",
 309                                __func__, alt_parent);
 310                ret = -EINVAL;
 311                goto free_cpuclk;
 312        }
 313
 314        clk = __clk_lookup(parent);
 315        if (!clk) {
 316                pr_err("%s: could not lookup parent clock %s\n",
 317                                __func__, parent);
 318                ret = -EINVAL;
 319                goto free_cpuclk;
 320        }
 321
 322        ret = clk_notifier_register(clk, &cpuclk->clk_nb);
 323        if (ret) {
 324                pr_err("%s: failed to register clock notifier for %s\n",
 325                                __func__, name);
 326                goto free_cpuclk;
 327        }
 328
 329        cpuclk->cfg = kmemdup(cfg, sizeof(*cfg) * num_cfgs, GFP_KERNEL);
 330        if (!cpuclk->cfg) {
 331                pr_err("%s: could not allocate memory for cpuclk data\n",
 332                                __func__);
 333                ret = -ENOMEM;
 334                goto unregister_clk_nb;
 335        }
 336
 337        clk = clk_register(NULL, &cpuclk->hw);
 338        if (IS_ERR(clk)) {
 339                pr_err("%s: could not register cpuclk %s\n", __func__,  name);
 340                ret = PTR_ERR(clk);
 341                goto free_cpuclk_data;
 342        }
 343
 344        samsung_clk_add_lookup(ctx, clk, lookup_id);
 345        return 0;
 346
 347free_cpuclk_data:
 348        kfree(cpuclk->cfg);
 349unregister_clk_nb:
 350        clk_notifier_unregister(__clk_lookup(parent), &cpuclk->clk_nb);
 351free_cpuclk:
 352        kfree(cpuclk);
 353        return ret;
 354}
 355