linux/drivers/clk/meson/clk-cpu.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2015 Endless Mobile, Inc.
   3 * Author: Carlo Caione <carlo@endlessm.com>
   4 *
   5 * This program is free software; you can redistribute it and/or modify it
   6 * under the terms and conditions of the GNU General Public License,
   7 * version 2, as published by the Free Software Foundation.
   8 *
   9 * This program is distributed in the hope it will be useful, but WITHOUT
  10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  12 * more details.
  13 *
  14 * You should have received a copy of the GNU General Public License along with
  15 * this program.  If not, see <http://www.gnu.org/licenses/>.
  16 */
  17
  18/*
  19 * CPU clock path:
  20 *
  21 *                           +-[/N]-----|3|
  22 *             MUX2  +--[/3]-+----------|2| MUX1
  23 * [sys_pll]---|1|   |--[/2]------------|1|-|1|
  24 *             | |---+------------------|0| | |----- [a5_clk]
  25 *          +--|0|                          | |
  26 * [xtal]---+-------------------------------|0|
  27 *
  28 *
  29 *
  30 */
  31
  32#include <linux/delay.h>
  33#include <linux/err.h>
  34#include <linux/io.h>
  35#include <linux/module.h>
  36#include <linux/of_address.h>
  37#include <linux/slab.h>
  38#include <linux/clk.h>
  39#include <linux/clk-provider.h>
  40
  41#define MESON_CPU_CLK_CNTL1             0x00
  42#define MESON_CPU_CLK_CNTL              0x40
  43
  44#define MESON_CPU_CLK_MUX1              BIT(7)
  45#define MESON_CPU_CLK_MUX2              BIT(0)
  46
  47#define MESON_N_WIDTH                   9
  48#define MESON_N_SHIFT                   20
  49#define MESON_SEL_WIDTH                 2
  50#define MESON_SEL_SHIFT                 2
  51
  52#include "clkc.h"
  53
  54struct meson_clk_cpu {
  55        struct notifier_block           clk_nb;
  56        const struct clk_div_table      *div_table;
  57        struct clk_hw                   hw;
  58        void __iomem                    *base;
  59        u16                             reg_off;
  60};
  61#define to_meson_clk_cpu_hw(_hw) container_of(_hw, struct meson_clk_cpu, hw)
  62#define to_meson_clk_cpu_nb(_nb) container_of(_nb, struct meson_clk_cpu, clk_nb)
  63
  64static long meson_clk_cpu_round_rate(struct clk_hw *hw, unsigned long rate,
  65                                     unsigned long *prate)
  66{
  67        struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
  68
  69        return divider_round_rate(hw, rate, prate, clk_cpu->div_table,
  70                                  MESON_N_WIDTH, CLK_DIVIDER_ROUND_CLOSEST);
  71}
  72
  73static int meson_clk_cpu_set_rate(struct clk_hw *hw, unsigned long rate,
  74                                  unsigned long parent_rate)
  75{
  76        struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
  77        unsigned int div, sel, N = 0;
  78        u32 reg;
  79
  80        div = DIV_ROUND_UP(parent_rate, rate);
  81
  82        if (div <= 3) {
  83                sel = div - 1;
  84        } else {
  85                sel = 3;
  86                N = div / 2;
  87        }
  88
  89        reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
  90        reg = PARM_SET(MESON_N_WIDTH, MESON_N_SHIFT, reg, N);
  91        writel(reg, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
  92
  93        reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
  94        reg = PARM_SET(MESON_SEL_WIDTH, MESON_SEL_SHIFT, reg, sel);
  95        writel(reg, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
  96
  97        return 0;
  98}
  99
 100static unsigned long meson_clk_cpu_recalc_rate(struct clk_hw *hw,
 101                                               unsigned long parent_rate)
 102{
 103        struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
 104        unsigned int N, sel;
 105        unsigned int div = 1;
 106        u32 reg;
 107
 108        reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
 109        N = PARM_GET(MESON_N_WIDTH, MESON_N_SHIFT, reg);
 110
 111        reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
 112        sel = PARM_GET(MESON_SEL_WIDTH, MESON_SEL_SHIFT, reg);
 113
 114        if (sel < 3)
 115                div = sel + 1;
 116        else
 117                div = 2 * N;
 118
 119        return parent_rate / div;
 120}
 121
 122static int meson_clk_cpu_pre_rate_change(struct meson_clk_cpu *clk_cpu,
 123                                         struct clk_notifier_data *ndata)
 124{
 125        u32 cpu_clk_cntl;
 126
 127        /* switch MUX1 to xtal */
 128        cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off
 129                                + MESON_CPU_CLK_CNTL);
 130        cpu_clk_cntl &= ~MESON_CPU_CLK_MUX1;
 131        writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
 132                                + MESON_CPU_CLK_CNTL);
 133        udelay(100);
 134
 135        /* switch MUX2 to sys-pll */
 136        cpu_clk_cntl |= MESON_CPU_CLK_MUX2;
 137        writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
 138                                + MESON_CPU_CLK_CNTL);
 139
 140        return 0;
 141}
 142
 143static int meson_clk_cpu_post_rate_change(struct meson_clk_cpu *clk_cpu,
 144                                          struct clk_notifier_data *ndata)
 145{
 146        u32 cpu_clk_cntl;
 147
 148        /* switch MUX1 to divisors' output */
 149        cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off
 150                                + MESON_CPU_CLK_CNTL);
 151        cpu_clk_cntl |= MESON_CPU_CLK_MUX1;
 152        writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
 153                                + MESON_CPU_CLK_CNTL);
 154        udelay(100);
 155
 156        return 0;
 157}
 158
 159/*
 160 * This clock notifier is called when the frequency of the of the parent
 161 * PLL clock is to be changed. We use the xtal input as temporary parent
 162 * while the PLL frequency is stabilized.
 163 */
 164static int meson_clk_cpu_notifier_cb(struct notifier_block *nb,
 165                                     unsigned long event, void *data)
 166{
 167        struct clk_notifier_data *ndata = data;
 168        struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_nb(nb);
 169        int ret = 0;
 170
 171        if (event == PRE_RATE_CHANGE)
 172                ret = meson_clk_cpu_pre_rate_change(clk_cpu, ndata);
 173        else if (event == POST_RATE_CHANGE)
 174                ret = meson_clk_cpu_post_rate_change(clk_cpu, ndata);
 175
 176        return notifier_from_errno(ret);
 177}
 178
 179static const struct clk_ops meson_clk_cpu_ops = {
 180        .recalc_rate    = meson_clk_cpu_recalc_rate,
 181        .round_rate     = meson_clk_cpu_round_rate,
 182        .set_rate       = meson_clk_cpu_set_rate,
 183};
 184
 185struct clk *meson_clk_register_cpu(const struct clk_conf *clk_conf,
 186                                   void __iomem *reg_base,
 187                                   spinlock_t *lock)
 188{
 189        struct clk *clk;
 190        struct clk *pclk;
 191        struct meson_clk_cpu *clk_cpu;
 192        struct clk_init_data init;
 193        int ret;
 194
 195        clk_cpu = kzalloc(sizeof(*clk_cpu), GFP_KERNEL);
 196        if (!clk_cpu)
 197                return ERR_PTR(-ENOMEM);
 198
 199        clk_cpu->base = reg_base;
 200        clk_cpu->reg_off = clk_conf->reg_off;
 201        clk_cpu->div_table = clk_conf->conf.div_table;
 202        clk_cpu->clk_nb.notifier_call = meson_clk_cpu_notifier_cb;
 203
 204        init.name = clk_conf->clk_name;
 205        init.ops = &meson_clk_cpu_ops;
 206        init.flags = clk_conf->flags | CLK_GET_RATE_NOCACHE;
 207        init.flags |= CLK_SET_RATE_PARENT;
 208        init.parent_names = clk_conf->clks_parent;
 209        init.num_parents = 1;
 210
 211        clk_cpu->hw.init = &init;
 212
 213        pclk = __clk_lookup(clk_conf->clks_parent[0]);
 214        if (!pclk) {
 215                pr_err("%s: could not lookup parent clock %s\n",
 216                                __func__, clk_conf->clks_parent[0]);
 217                ret = -EINVAL;
 218                goto free_clk;
 219        }
 220
 221        ret = clk_notifier_register(pclk, &clk_cpu->clk_nb);
 222        if (ret) {
 223                pr_err("%s: failed to register clock notifier for %s\n",
 224                                __func__, clk_conf->clk_name);
 225                goto free_clk;
 226        }
 227
 228        clk = clk_register(NULL, &clk_cpu->hw);
 229        if (IS_ERR(clk)) {
 230                ret = PTR_ERR(clk);
 231                goto unregister_clk_nb;
 232        }
 233
 234        return clk;
 235
 236unregister_clk_nb:
 237        clk_notifier_unregister(pclk, &clk_cpu->clk_nb);
 238free_clk:
 239        kfree(clk_cpu);
 240
 241        return ERR_PTR(ret);
 242}
 243
 244