clk: meson: Add support for Meson clock controller
authorCarlo Caione <carlo@endlessm.com>
Mon, 1 Jun 2015 11:13:53 +0000 (13:13 +0200)
committerStephen Boyd <sboyd@codeaurora.org>
Sat, 6 Jun 2015 00:22:08 +0000 (17:22 -0700)
This patchset adds the infrastructure for registering and managing the
core clocks found on Amlogic MesonX SoCs. In particular:

- PLLs
- CPU clock
- Fixed rate clocks, fixed factor clocks, ...

Signed-off-by: Carlo Caione <carlo@endlessm.com>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
drivers/clk/Makefile
drivers/clk/meson/Makefile [new file with mode: 0644]
drivers/clk/meson/clk-cpu.c [new file with mode: 0644]
drivers/clk/meson/clk-pll.c [new file with mode: 0644]
drivers/clk/meson/clkc.c [new file with mode: 0644]
drivers/clk/meson/clkc.h [new file with mode: 0644]
include/dt-bindings/clock/meson8b-clkc.h [new file with mode: 0644]

index 3d00c25382c53b02e9d461437aa38c5eea77b090..9c4ddb1b0845f1347d56ce91cfa94d9b21ac4040 100644 (file)
@@ -55,6 +55,7 @@ ifeq ($(CONFIG_COMMON_CLK), y)
 obj-$(CONFIG_ARCH_MMP)                 += mmp/
 endif
 obj-$(CONFIG_PLAT_ORION)               += mvebu/
+obj-$(CONFIG_ARCH_MESON)               += meson/
 obj-$(CONFIG_ARCH_MXS)                 += mxs/
 obj-$(CONFIG_MACH_PISTACHIO)           += pistachio/
 obj-$(CONFIG_COMMON_CLK_PXA)           += pxa/
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
new file mode 100644 (file)
index 0000000..66c6d8d
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Makefile for Meson specific clk
+#
+
+obj-y += clkc.o clk-pll.o clk-cpu.o
diff --git a/drivers/clk/meson/clk-cpu.c b/drivers/clk/meson/clk-cpu.c
new file mode 100644 (file)
index 0000000..148e99f
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2015 Endless Mobile, Inc.
+ * Author: Carlo Caione <carlo@endlessm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * CPU clock path:
+ *
+ *                           +-[/N]-----|3|
+ *             MUX2  +--[/3]-+----------|2| MUX1
+ * [sys_pll]---|1|   |--[/2]------------|1|-|1|
+ *             | |---+------------------|0| | |----- [a5_clk]
+ *          +--|0|                          | |
+ * [xtal]---+-------------------------------|0|
+ *
+ *
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+
+#define MESON_CPU_CLK_CNTL1            0x00
+#define MESON_CPU_CLK_CNTL             0x40
+
+#define MESON_CPU_CLK_MUX1             BIT(7)
+#define MESON_CPU_CLK_MUX2             BIT(0)
+
+#define MESON_N_WIDTH                  9
+#define MESON_N_SHIFT                  20
+#define MESON_SEL_WIDTH                        2
+#define MESON_SEL_SHIFT                        2
+
+#include "clkc.h"
+
+struct meson_clk_cpu {
+       struct notifier_block           clk_nb;
+       const struct clk_div_table      *div_table;
+       struct clk_hw                   hw;
+       void __iomem                    *base;
+       u16                             reg_off;
+};
+#define to_meson_clk_cpu_hw(_hw) container_of(_hw, struct meson_clk_cpu, hw)
+#define to_meson_clk_cpu_nb(_nb) container_of(_nb, struct meson_clk_cpu, clk_nb)
+
+static long meson_clk_cpu_round_rate(struct clk_hw *hw, unsigned long rate,
+                                    unsigned long *prate)
+{
+       struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
+
+       return divider_round_rate(hw, rate, prate, clk_cpu->div_table,
+                                 MESON_N_WIDTH, CLK_DIVIDER_ROUND_CLOSEST);
+}
+
+static int meson_clk_cpu_set_rate(struct clk_hw *hw, unsigned long rate,
+                                 unsigned long parent_rate)
+{
+       struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
+       unsigned int div, sel, N = 0;
+       u32 reg;
+
+       div = DIV_ROUND_UP(parent_rate, rate);
+
+       if (div <= 3) {
+               sel = div - 1;
+       } else {
+               sel = 3;
+               N = div / 2;
+       }
+
+       reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
+       reg = PARM_SET(MESON_N_WIDTH, MESON_N_SHIFT, reg, N);
+       writel(reg, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
+
+       reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
+       reg = PARM_SET(MESON_SEL_WIDTH, MESON_SEL_SHIFT, reg, sel);
+       writel(reg, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
+
+       return 0;
+}
+
+static unsigned long meson_clk_cpu_recalc_rate(struct clk_hw *hw,
+                                              unsigned long parent_rate)
+{
+       struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
+       unsigned int N, sel;
+       unsigned int div = 1;
+       u32 reg;
+
+       reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
+       N = PARM_GET(MESON_N_WIDTH, MESON_N_SHIFT, reg);
+
+       reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
+       sel = PARM_GET(MESON_SEL_WIDTH, MESON_SEL_SHIFT, reg);
+
+       if (sel < 3)
+               div = sel + 1;
+       else
+               div = 2 * N;
+
+       return parent_rate / div;
+}
+
+static int meson_clk_cpu_pre_rate_change(struct meson_clk_cpu *clk_cpu,
+                                        struct clk_notifier_data *ndata)
+{
+       u32 cpu_clk_cntl;
+
+       /* switch MUX1 to xtal */
+       cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off
+                               + MESON_CPU_CLK_CNTL);
+       cpu_clk_cntl &= ~MESON_CPU_CLK_MUX1;
+       writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
+                               + MESON_CPU_CLK_CNTL);
+       udelay(100);
+
+       /* switch MUX2 to sys-pll */
+       cpu_clk_cntl |= MESON_CPU_CLK_MUX2;
+       writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
+                               + MESON_CPU_CLK_CNTL);
+
+       return 0;
+}
+
+static int meson_clk_cpu_post_rate_change(struct meson_clk_cpu *clk_cpu,
+                                         struct clk_notifier_data *ndata)
+{
+       u32 cpu_clk_cntl;
+
+       /* switch MUX1 to divisors' output */
+       cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off
+                               + MESON_CPU_CLK_CNTL);
+       cpu_clk_cntl |= MESON_CPU_CLK_MUX1;
+       writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
+                               + MESON_CPU_CLK_CNTL);
+       udelay(100);
+
+       return 0;
+}
+
+/*
+ * This clock notifier is called when the frequency of the of the parent
+ * PLL clock is to be changed. We use the xtal input as temporary parent
+ * while the PLL frequency is stabilized.
+ */
+static int meson_clk_cpu_notifier_cb(struct notifier_block *nb,
+                                    unsigned long event, void *data)
+{
+       struct clk_notifier_data *ndata = data;
+       struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_nb(nb);
+       int ret = 0;
+
+       if (event == PRE_RATE_CHANGE)
+               ret = meson_clk_cpu_pre_rate_change(clk_cpu, ndata);
+       else if (event == POST_RATE_CHANGE)
+               ret = meson_clk_cpu_post_rate_change(clk_cpu, ndata);
+
+       return notifier_from_errno(ret);
+}
+
+static const struct clk_ops meson_clk_cpu_ops = {
+       .recalc_rate    = meson_clk_cpu_recalc_rate,
+       .round_rate     = meson_clk_cpu_round_rate,
+       .set_rate       = meson_clk_cpu_set_rate,
+};
+
+struct clk *meson_clk_register_cpu(const struct clk_conf *clk_conf,
+                                  void __iomem *reg_base,
+                                  spinlock_t *lock)
+{
+       struct clk *clk;
+       struct clk *pclk;
+       struct meson_clk_cpu *clk_cpu;
+       struct clk_init_data init;
+       int ret;
+
+       clk_cpu = kzalloc(sizeof(*clk_cpu), GFP_KERNEL);
+       if (!clk_cpu)
+               return ERR_PTR(-ENOMEM);
+
+       clk_cpu->base = reg_base;
+       clk_cpu->reg_off = clk_conf->reg_off;
+       clk_cpu->div_table = clk_conf->conf.div_table;
+       clk_cpu->clk_nb.notifier_call = meson_clk_cpu_notifier_cb;
+
+       init.name = clk_conf->clk_name;
+       init.ops = &meson_clk_cpu_ops;
+       init.flags = clk_conf->flags | CLK_GET_RATE_NOCACHE;
+       init.flags |= CLK_SET_RATE_PARENT;
+       init.parent_names = clk_conf->clks_parent;
+       init.num_parents = 1;
+
+       clk_cpu->hw.init = &init;
+
+       pclk = __clk_lookup(clk_conf->clks_parent[0]);
+       if (!pclk) {
+               pr_err("%s: could not lookup parent clock %s\n",
+                               __func__, clk_conf->clks_parent[0]);
+               return ERR_PTR(-EINVAL);
+       }
+
+       ret = clk_notifier_register(pclk, &clk_cpu->clk_nb);
+       if (ret) {
+               pr_err("%s: failed to register clock notifier for %s\n",
+                               __func__, clk_conf->clk_name);
+               return ERR_PTR(-EINVAL);
+       }
+
+       clk = clk_register(NULL, &clk_cpu->hw);
+       if (IS_ERR(clk)) {
+               clk_notifier_unregister(pclk, &clk_cpu->clk_nb);
+               kfree(clk_cpu);
+       }
+
+       return clk;
+}
+
diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c
new file mode 100644 (file)
index 0000000..664edf0
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2015 Endless Mobile, Inc.
+ * Author: Carlo Caione <carlo@endlessm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * In the most basic form, a Meson PLL is composed as follows:
+ *
+ *                     PLL
+ *      +------------------------------+
+ *      |                              |
+ * in -----[ /N ]---[ *M ]---[ >>OD ]----->> out
+ *      |         ^        ^           |
+ *      +------------------------------+
+ *                |        |
+ *               FREF     VCO
+ *
+ * out = (in * M / N) >> OD
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "clkc.h"
+
+#define MESON_PLL_RESET                                BIT(29)
+#define MESON_PLL_LOCK                         BIT(31)
+
+struct meson_clk_pll {
+       struct clk_hw   hw;
+       void __iomem    *base;
+       struct pll_conf *conf;
+       unsigned int    rate_count;
+       spinlock_t      *lock;
+};
+#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
+
+static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
+                                               unsigned long parent_rate)
+{
+       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
+       struct parm *p;
+       unsigned long parent_rate_mhz = parent_rate / 1000000;
+       unsigned long rate_mhz;
+       u16 n, m, od;
+       u32 reg;
+
+       p = &pll->conf->n;
+       reg = readl(pll->base + p->reg_off);
+       n = PARM_GET(p->width, p->shift, reg);
+
+       p = &pll->conf->m;
+       reg = readl(pll->base + p->reg_off);
+       m = PARM_GET(p->width, p->shift, reg);
+
+       p = &pll->conf->od;
+       reg = readl(pll->base + p->reg_off);
+       od = PARM_GET(p->width, p->shift, reg);
+
+       rate_mhz = (parent_rate_mhz * m / n) >> od;
+
+       return rate_mhz * 1000000;
+}
+
+static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+                                    unsigned long *parent_rate)
+{
+       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
+       const struct pll_rate_table *rate_table = pll->conf->rate_table;
+       int i;
+
+       for (i = 0; i < pll->rate_count; i++) {
+               if (rate <= rate_table[i].rate)
+                       return rate_table[i].rate;
+       }
+
+       /* else return the smallest value */
+       return rate_table[0].rate;
+}
+
+static const struct pll_rate_table *meson_clk_get_pll_settings(struct meson_clk_pll *pll,
+                                                              unsigned long rate)
+{
+       const struct pll_rate_table *rate_table = pll->conf->rate_table;
+       int i;
+
+       for (i = 0; i < pll->rate_count; i++) {
+               if (rate == rate_table[i].rate)
+                       return &rate_table[i];
+       }
+       return NULL;
+}
+
+static int meson_clk_pll_wait_lock(struct meson_clk_pll *pll,
+                                  struct parm *p_n)
+{
+       int delay = 24000000;
+       u32 reg;
+
+       while (delay > 0) {
+               reg = readl(pll->base + p_n->reg_off);
+
+               if (reg & MESON_PLL_LOCK)
+                       return 0;
+               delay--;
+       }
+       return -ETIMEDOUT;
+}
+
+static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+                                 unsigned long parent_rate)
+{
+       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
+       struct parm *p;
+       const struct pll_rate_table *rate_set;
+       unsigned long old_rate;
+       int ret = 0;
+       u32 reg;
+
+       if (parent_rate == 0 || rate == 0)
+               return -EINVAL;
+
+       old_rate = rate;
+
+       rate_set = meson_clk_get_pll_settings(pll, rate);
+       if (!rate_set)
+               return -EINVAL;
+
+       /* PLL reset */
+       p = &pll->conf->n;
+       reg = readl(pll->base + p->reg_off);
+       writel(reg | MESON_PLL_RESET, pll->base + p->reg_off);
+
+       reg = PARM_SET(p->width, p->shift, reg, rate_set->n);
+       writel(reg, pll->base + p->reg_off);
+
+       p = &pll->conf->m;
+       reg = readl(pll->base + p->reg_off);
+       reg = PARM_SET(p->width, p->shift, reg, rate_set->m);
+       writel(reg, pll->base + p->reg_off);
+
+       p = &pll->conf->od;
+       reg = readl(pll->base + p->reg_off);
+       reg = PARM_SET(p->width, p->shift, reg, rate_set->od);
+       writel(reg, pll->base + p->reg_off);
+
+       p = &pll->conf->n;
+       ret = meson_clk_pll_wait_lock(pll, p);
+       if (ret) {
+               pr_warn("%s: pll did not lock, trying to restore old rate %lu\n",
+                       __func__, old_rate);
+               meson_clk_pll_set_rate(hw, old_rate, parent_rate);
+       }
+
+       return ret;
+}
+
+static const struct clk_ops meson_clk_pll_ops = {
+       .recalc_rate    = meson_clk_pll_recalc_rate,
+       .round_rate     = meson_clk_pll_round_rate,
+       .set_rate       = meson_clk_pll_set_rate,
+};
+
+static const struct clk_ops meson_clk_pll_ro_ops = {
+       .recalc_rate    = meson_clk_pll_recalc_rate,
+};
+
+struct clk *meson_clk_register_pll(const struct clk_conf *clk_conf,
+                                  void __iomem *reg_base,
+                                  spinlock_t *lock)
+{
+       struct clk *clk;
+       struct meson_clk_pll *clk_pll;
+       struct clk_init_data init;
+
+       clk_pll = kzalloc(sizeof(*clk_pll), GFP_KERNEL);
+       if (!clk_pll)
+               return ERR_PTR(-ENOMEM);
+
+       clk_pll->base = reg_base + clk_conf->reg_off;
+       clk_pll->lock = lock;
+       clk_pll->conf = clk_conf->conf.pll;
+
+       init.name = clk_conf->clk_name;
+       init.flags = clk_conf->flags | CLK_GET_RATE_NOCACHE;
+
+       init.parent_names = &clk_conf->clks_parent[0];
+       init.num_parents = 1;
+       init.ops = &meson_clk_pll_ro_ops;
+
+       /* If no rate_table is specified we assume the PLL is read-only */
+       if (clk_pll->conf->rate_table) {
+               int len;
+
+               for (len = 0; clk_pll->conf->rate_table[len].rate != 0; )
+                       len++;
+
+                clk_pll->rate_count = len;
+                init.ops = &meson_clk_pll_ops;
+       }
+
+       clk_pll->hw.init = &init;
+
+       clk = clk_register(NULL, &clk_pll->hw);
+       if (IS_ERR(clk))
+               kfree(clk_pll);
+
+       return clk;
+}
diff --git a/drivers/clk/meson/clkc.c b/drivers/clk/meson/clkc.c
new file mode 100644 (file)
index 0000000..b8c511c
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2015 Endless Mobile, Inc.
+ * Author: Carlo Caione <carlo@endlessm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include "clkc.h"
+
+static DEFINE_SPINLOCK(clk_lock);
+
+static struct clk **clks;
+static struct clk_onecell_data clk_data;
+
+struct clk ** __init meson_clk_init(struct device_node *np,
+                                  unsigned long nr_clks)
+{
+       clks = kcalloc(nr_clks, sizeof(*clks), GFP_KERNEL);
+       if (!clks)
+               return ERR_PTR(-ENOMEM);
+
+       clk_data.clks = clks;
+       clk_data.clk_num = nr_clks;
+       of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+
+       return clks;
+}
+
+static void meson_clk_add_lookup(struct clk *clk, unsigned int id)
+{
+       if (clks && id)
+               clks[id] = clk;
+}
+
+static struct clk * __init
+meson_clk_register_composite(const struct clk_conf *clk_conf,
+                            void __iomem *clk_base)
+{
+       struct clk *clk;
+       struct clk_mux *mux = NULL;
+       struct clk_divider *div = NULL;
+       struct clk_gate *gate = NULL;
+       const struct clk_ops *mux_ops = NULL;
+       const struct composite_conf *composite_conf;
+
+       composite_conf = clk_conf->conf.composite;
+
+       if (clk_conf->num_parents > 1) {
+               mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+               if (!mux)
+                       return ERR_PTR(-ENOMEM);
+
+               mux->reg = clk_base + clk_conf->reg_off
+                               + composite_conf->mux_parm.reg_off;
+               mux->shift = composite_conf->mux_parm.shift;
+               mux->mask = BIT(composite_conf->mux_parm.width) - 1;
+               mux->flags = composite_conf->mux_flags;
+               mux->lock = &clk_lock;
+               mux->table = composite_conf->mux_table;
+               mux_ops = (composite_conf->mux_flags & CLK_MUX_READ_ONLY) ?
+                         &clk_mux_ro_ops : &clk_mux_ops;
+       }
+
+       if (MESON_PARM_APPLICABLE(&composite_conf->div_parm)) {
+               div = kzalloc(sizeof(*div), GFP_KERNEL);
+               if (!div) {
+                       clk = ERR_PTR(-ENOMEM);
+                       goto error;
+               }
+
+               div->reg = clk_base + clk_conf->reg_off
+                               + composite_conf->div_parm.reg_off;
+               div->shift = composite_conf->div_parm.shift;
+               div->width = composite_conf->div_parm.width;
+               div->lock = &clk_lock;
+               div->flags = composite_conf->div_flags;
+               div->table = composite_conf->div_table;
+       }
+
+       if (MESON_PARM_APPLICABLE(&composite_conf->gate_parm)) {
+               gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+               if (!gate) {
+                       clk = ERR_PTR(-ENOMEM);
+                       goto error;
+               }
+
+               gate->reg = clk_base + clk_conf->reg_off
+                               + composite_conf->div_parm.reg_off;
+               gate->bit_idx = composite_conf->gate_parm.shift;
+               gate->flags = composite_conf->gate_flags;
+               gate->lock = &clk_lock;
+       }
+
+       clk = clk_register_composite(NULL, clk_conf->clk_name,
+                                   clk_conf->clks_parent,
+                                   clk_conf->num_parents,
+                                   mux ? &mux->hw : NULL, mux_ops,
+                                   div ? &div->hw : NULL, &clk_divider_ops,
+                                   gate ? &gate->hw : NULL, &clk_gate_ops,
+                                   clk_conf->flags);
+       if (IS_ERR(clk))
+               goto error;
+
+       return clk;
+
+error:
+       kfree(gate);
+       kfree(div);
+       kfree(mux);
+
+       return clk;
+}
+
+static struct clk * __init
+meson_clk_register_fixed_factor(const struct clk_conf *clk_conf,
+                               void __iomem *clk_base)
+{
+       struct clk *clk;
+       const struct fixed_fact_conf *fixed_fact_conf;
+       const struct parm *p;
+       unsigned int mult, div;
+       u32 reg;
+
+       fixed_fact_conf = &clk_conf->conf.fixed_fact;
+
+       mult = clk_conf->conf.fixed_fact.mult;
+       div = clk_conf->conf.fixed_fact.div;
+
+       if (!mult) {
+               mult = 1;
+               p = &fixed_fact_conf->mult_parm;
+               if (MESON_PARM_APPLICABLE(p)) {
+                       reg = readl(clk_base + clk_conf->reg_off + p->reg_off);
+                       mult = PARM_GET(p->width, p->shift, reg);
+               }
+       }
+
+       if (!div) {
+               div = 1;
+               p = &fixed_fact_conf->div_parm;
+               if (MESON_PARM_APPLICABLE(p)) {
+                       reg = readl(clk_base + clk_conf->reg_off + p->reg_off);
+                       mult = PARM_GET(p->width, p->shift, reg);
+               }
+       }
+
+       clk = clk_register_fixed_factor(NULL,
+                       clk_conf->clk_name,
+                       clk_conf->clks_parent[0],
+                       clk_conf->flags,
+                       mult, div);
+
+       return clk;
+}
+
+static struct clk * __init
+meson_clk_register_fixed_rate(const struct clk_conf *clk_conf,
+                             void __iomem *clk_base)
+{
+       struct clk *clk;
+       const struct fixed_rate_conf *fixed_rate_conf;
+       const struct parm *r;
+       unsigned long rate;
+       u32 reg;
+
+       fixed_rate_conf = &clk_conf->conf.fixed_rate;
+       rate = fixed_rate_conf->rate;
+
+       if (!rate) {
+               r = &fixed_rate_conf->rate_parm;
+               reg = readl(clk_base + clk_conf->reg_off + r->reg_off);
+               rate = PARM_GET(r->width, r->shift, reg);
+       }
+
+       rate *= 1000000;
+
+       clk = clk_register_fixed_rate(NULL,
+                       clk_conf->clk_name,
+                       clk_conf->num_parents
+                               ? clk_conf->clks_parent[0] : NULL,
+                       clk_conf->flags, rate);
+
+       return clk;
+}
+
+void __init meson_clk_register_clks(const struct clk_conf *clk_confs,
+                                   size_t nr_confs,
+                                   void __iomem *clk_base)
+{
+       unsigned int i;
+       struct clk *clk = NULL;
+
+       for (i = 0; i < nr_confs; i++) {
+               const struct clk_conf *clk_conf = &clk_confs[i];
+
+               switch (clk_conf->clk_type) {
+               case CLK_FIXED_RATE:
+                       clk = meson_clk_register_fixed_rate(clk_conf,
+                                                           clk_base);
+                       break;
+               case CLK_FIXED_FACTOR:
+                       clk = meson_clk_register_fixed_factor(clk_conf,
+                                                             clk_base);
+                       break;
+               case CLK_COMPOSITE:
+                       clk = meson_clk_register_composite(clk_conf,
+                                                          clk_base);
+                       break;
+               case CLK_CPU:
+                       clk = meson_clk_register_cpu(clk_conf, clk_base,
+                                                    &clk_lock);
+                       break;
+               case CLK_PLL:
+                       clk = meson_clk_register_pll(clk_conf, clk_base,
+                                                    &clk_lock);
+                       break;
+               default:
+                       clk = NULL;
+               }
+
+               if (!clk) {
+                       pr_err("%s: unknown clock type %d\n", __func__,
+                              clk_conf->clk_type);
+                       continue;
+               }
+
+               if (IS_ERR(clk)) {
+                       pr_warn("%s: Unable to create %s clock\n", __func__,
+                               clk_conf->clk_name);
+                       continue;
+               }
+
+               meson_clk_add_lookup(clk, clk_conf->clk_id);
+       }
+}
diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h
new file mode 100644 (file)
index 0000000..609ae92
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2015 Endless Mobile, Inc.
+ * Author: Carlo Caione <carlo@endlessm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __CLKC_H
+#define __CLKC_H
+
+#define PMASK(width)                   GENMASK(width - 1, 0)
+#define SETPMASK(width, shift)         GENMASK(shift + width - 1, shift)
+#define CLRPMASK(width, shift)         (~SETPMASK(width, shift))
+
+#define PARM_GET(width, shift, reg)                                    \
+       (((reg) & SETPMASK(width, shift)) >> (shift))
+#define PARM_SET(width, shift, reg, val)                               \
+       (((reg) & CLRPMASK(width, shift)) | (val << (shift)))
+
+#define MESON_PARM_APPLICABLE(p)               (!!((p)->width))
+
+struct parm {
+       u16     reg_off;
+       u8      shift;
+       u8      width;
+};
+#define PARM(_r, _s, _w)                                               \
+       {                                                               \
+               .reg_off        = (_r),                                 \
+               .shift          = (_s),                                 \
+               .width          = (_w),                                 \
+       }                                                               \
+
+struct pll_rate_table {
+       unsigned long   rate;
+       u16             m;
+       u16             n;
+       u16             od;
+};
+#define PLL_RATE(_r, _m, _n, _od)                                      \
+       {                                                               \
+               .rate           = (_r),                                 \
+               .m              = (_m),                                 \
+               .n              = (_n),                                 \
+               .od             = (_od),                                \
+       }                                                               \
+
+struct pll_conf {
+       const struct pll_rate_table     *rate_table;
+       struct parm                     m;
+       struct parm                     n;
+       struct parm                     od;
+};
+
+struct fixed_fact_conf {
+       unsigned int    div;
+       unsigned int    mult;
+       struct parm     div_parm;
+       struct parm     mult_parm;
+};
+
+struct fixed_rate_conf {
+       unsigned long   rate;
+       struct parm     rate_parm;
+};
+
+struct composite_conf {
+       struct parm             mux_parm;
+       struct parm             div_parm;
+       struct parm             gate_parm;
+       struct clk_div_table    *div_table;
+       u32                     *mux_table;
+       u8                      mux_flags;
+       u8                      div_flags;
+       u8                      gate_flags;
+};
+
+#define PNAME(x) static const char *x[]
+
+enum clk_type {
+       CLK_FIXED_FACTOR,
+       CLK_FIXED_RATE,
+       CLK_COMPOSITE,
+       CLK_CPU,
+       CLK_PLL,
+};
+
+struct clk_conf {
+       u16                             reg_off;
+       enum clk_type                   clk_type;
+       unsigned int                    clk_id;
+       const char                      *clk_name;
+       const char                      **clks_parent;
+       int                             num_parents;
+       unsigned long                   flags;
+       union {
+               struct fixed_fact_conf          fixed_fact;
+               struct fixed_rate_conf          fixed_rate;
+               const struct composite_conf             *composite;
+               struct pll_conf                 *pll;
+               const struct clk_div_table      *div_table;
+       } conf;
+};
+
+#define FIXED_RATE_P(_ro, _ci, _cn, _f, _c)                            \
+       {                                                               \
+               .reg_off                        = (_ro),                \
+               .clk_type                       = CLK_FIXED_RATE,       \
+               .clk_id                         = (_ci),                \
+               .clk_name                       = (_cn),                \
+               .flags                          = (_f),                 \
+               .conf.fixed_rate.rate_parm      = _c,                   \
+       }                                                               \
+
+#define FIXED_RATE(_ci, _cn, _f, _r)                                   \
+       {                                                               \
+               .clk_type                       = CLK_FIXED_RATE,       \
+               .clk_id                         = (_ci),                \
+               .clk_name                       = (_cn),                \
+               .flags                          = (_f),                 \
+               .conf.fixed_rate.rate           = (_r),                 \
+       }                                                               \
+
+#define PLL(_ro, _ci, _cn, _cp, _f, _c)                                        \
+       {                                                               \
+               .reg_off                        = (_ro),                \
+               .clk_type                       = CLK_PLL,              \
+               .clk_id                         = (_ci),                \
+               .clk_name                       = (_cn),                \
+               .clks_parent                    = (_cp),                \
+               .num_parents                    = ARRAY_SIZE(_cp),      \
+               .flags                          = (_f),                 \
+               .conf.pll                       = (_c),                 \
+       }                                                               \
+
+#define FIXED_FACTOR_DIV(_ci, _cn, _cp, _f, _d)                                \
+       {                                                               \
+               .clk_type                       = CLK_FIXED_FACTOR,     \
+               .clk_id                         = (_ci),                \
+               .clk_name                       = (_cn),                \
+               .clks_parent                    = (_cp),                \
+               .num_parents                    = ARRAY_SIZE(_cp),      \
+               .conf.fixed_fact.div            = (_d),                 \
+       }                                                               \
+
+#define CPU(_ro, _ci, _cn, _cp, _dt)                                   \
+       {                                                               \
+               .reg_off                        = (_ro),                \
+               .clk_type                       = CLK_CPU,              \
+               .clk_id                         = (_ci),                \
+               .clk_name                       = (_cn),                \
+               .clks_parent                    = (_cp),                \
+               .num_parents                    = ARRAY_SIZE(_cp),      \
+               .conf.div_table                 = (_dt),                \
+       }                                                               \
+
+#define COMPOSITE(_ro, _ci, _cn, _cp, _f, _c)                          \
+       {                                                               \
+               .reg_off                        = (_ro),                \
+               .clk_type                       = CLK_COMPOSITE,        \
+               .clk_id                         = (_ci),                \
+               .clk_name                       = (_cn),                \
+               .clks_parent                    = (_cp),                \
+               .num_parents                    = ARRAY_SIZE(_cp),      \
+               .flags                          = (_f),                 \
+               .conf.composite                 = (_c),                 \
+       }                                                               \
+
+struct clk **meson_clk_init(struct device_node *np, unsigned long nr_clks);
+void meson_clk_register_clks(const struct clk_conf *clk_confs,
+                            unsigned int nr_confs, void __iomem *clk_base);
+struct clk *meson_clk_register_cpu(const struct clk_conf *clk_conf,
+                                  void __iomem *reg_base, spinlock_t *lock);
+struct clk *meson_clk_register_pll(const struct clk_conf *clk_conf,
+                                  void __iomem *reg_base, spinlock_t *lock);
+
+#endif /* __CLKC_H */
diff --git a/include/dt-bindings/clock/meson8b-clkc.h b/include/dt-bindings/clock/meson8b-clkc.h
new file mode 100644 (file)
index 0000000..bd2720d
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Meson8b clock tree IDs
+ */
+
+#ifndef __MESON8B_CLKC_H
+#define __MESON8B_CLKC_H
+
+#define CLKID_UNUSED           0
+#define CLKID_XTAL             1
+#define CLKID_PLL_FIXED                2
+#define CLKID_PLL_VID          3
+#define CLKID_PLL_SYS          4
+#define CLKID_FCLK_DIV2                5
+#define CLKID_FCLK_DIV3                6
+#define CLKID_FCLK_DIV4                7
+#define CLKID_FCLK_DIV5                8
+#define CLKID_FCLK_DIV7                9
+#define CLKID_CLK81            10
+#define CLKID_MALI             11
+#define CLKID_CPUCLK           12
+#define CLKID_ZERO             13
+
+#define CLK_NR_CLKS            (CLKID_ZERO + 1)
+
+#endif /* __MESON8B_CLKC_H */