clk: mvebu: add armada-370-xp CPU specific clocks
authorGregory CLEMENT <gregory.clement@free-electrons.com>
Sat, 17 Nov 2012 14:22:23 +0000 (15:22 +0100)
committerThomas Petazzoni <thomas.petazzoni@free-electrons.com>
Tue, 20 Nov 2012 13:35:42 +0000 (14:35 +0100)
Add Armada 370/XP specific CPU clocks

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Tested-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt [new file with mode: 0644]
drivers/clk/mvebu/Kconfig
drivers/clk/mvebu/Makefile
drivers/clk/mvebu/clk-cpu.c [new file with mode: 0644]
drivers/clk/mvebu/clk-cpu.h [new file with mode: 0644]
drivers/clk/mvebu/clk.c

diff --git a/Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt b/Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt
new file mode 100644 (file)
index 0000000..feb8301
--- /dev/null
@@ -0,0 +1,21 @@
+Device Tree Clock bindings for cpu clock of Marvell EBU platforms
+
+Required properties:
+- compatible : shall be one of the following:
+       "marvell,armada-xp-cpu-clock" - cpu clocks for Armada XP
+- reg : Address and length of the clock complex register set
+- #clock-cells : should be set to 1.
+- clocks : shall be the input parent clock phandle for the clock.
+
+cpuclk: clock-complex@d0018700 {
+       #clock-cells = <1>;
+       compatible = "marvell,armada-xp-cpu-clock";
+       reg = <0xd0018700 0xA0>;
+       clocks = <&coreclk 1>;
+}
+
+cpu@0 {
+       compatible = "marvell,sheeva-v7";
+       reg = <0>;
+       clocks = <&cpuclk 0>;
+};
index fd7bf97ff74d7c9763c7372e3e4553caada2de98..1dd93ada9cb32b445f2017d40f34be53de8ec4e1 100644 (file)
@@ -1,3 +1,6 @@
 config MVEBU_CLK_CORE
        bool
 
+config MVEBU_CLK_CPU
+       bool
+
index de1d9617f75a22ea51d19d2aee9679028471f1d3..93da083b77e398480f15c10d40bffb22e678c7e1 100644 (file)
@@ -1 +1,2 @@
 obj-$(CONFIG_MVEBU_CLK_CORE)   += clk.o clk-core.o
+obj-$(CONFIG_MVEBU_CLK_CPU)    += clk-cpu.o
diff --git a/drivers/clk/mvebu/clk-cpu.c b/drivers/clk/mvebu/clk-cpu.c
new file mode 100644 (file)
index 0000000..ff00457
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Marvell MVEBU CPU clock handling.
+ *
+ * Copyright (C) 2012 Marvell
+ *
+ * Gregory CLEMENT <gregory.clement@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+#include <linux/kernel.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/of_address.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include "clk-cpu.h"
+
+#define SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET    0x0
+#define SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET   0xC
+#define SYS_CTRL_CLK_DIVIDER_MASK          0x3F
+
+#define MAX_CPU            4
+struct cpu_clk {
+       struct clk_hw hw;
+       int cpu;
+       const char *clk_name;
+       const char *parent_name;
+       void __iomem *reg_base;
+};
+
+static struct clk **clks;
+
+static struct clk_onecell_data clk_data;
+
+#define to_cpu_clk(p) container_of(p, struct cpu_clk, hw)
+
+static unsigned long clk_cpu_recalc_rate(struct clk_hw *hwclk,
+                                        unsigned long parent_rate)
+{
+       struct cpu_clk *cpuclk = to_cpu_clk(hwclk);
+       u32 reg, div;
+
+       reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET);
+       div = (reg >> (cpuclk->cpu * 8)) & SYS_CTRL_CLK_DIVIDER_MASK;
+       return parent_rate / div;
+}
+
+static long clk_cpu_round_rate(struct clk_hw *hwclk, unsigned long rate,
+                              unsigned long *parent_rate)
+{
+       /* Valid ratio are 1:1, 1:2 and 1:3 */
+       u32 div;
+
+       div = *parent_rate / rate;
+       if (div == 0)
+               div = 1;
+       else if (div > 3)
+               div = 3;
+
+       return *parent_rate / div;
+}
+
+static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate,
+                           unsigned long parent_rate)
+{
+       struct cpu_clk *cpuclk = to_cpu_clk(hwclk);
+       u32 reg, div;
+       u32 reload_mask;
+
+       div = parent_rate / rate;
+       reg = (readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET)
+               & (~(SYS_CTRL_CLK_DIVIDER_MASK << (cpuclk->cpu * 8))))
+               | (div << (cpuclk->cpu * 8));
+       writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET);
+       /* Set clock divider reload smooth bit mask */
+       reload_mask = 1 << (20 + cpuclk->cpu);
+
+       reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET)
+           | reload_mask;
+       writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET);
+
+       /* Now trigger the clock update */
+       reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET)
+           | 1 << 24;
+       writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET);
+
+       /* Wait for clocks to settle down then clear reload request */
+       udelay(1000);
+       reg &= ~(reload_mask | 1 << 24);
+       writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET);
+       udelay(1000);
+
+       return 0;
+}
+
+static const struct clk_ops cpu_ops = {
+       .recalc_rate = clk_cpu_recalc_rate,
+       .round_rate = clk_cpu_round_rate,
+       .set_rate = clk_cpu_set_rate,
+};
+
+void __init of_cpu_clk_setup(struct device_node *node)
+{
+       struct cpu_clk *cpuclk;
+       void __iomem *clock_complex_base = of_iomap(node, 0);
+       int ncpus = 0;
+       struct device_node *dn;
+
+       if (clock_complex_base == NULL) {
+               pr_err("%s: clock-complex base register not set\n",
+                       __func__);
+               return;
+       }
+
+       for_each_node_by_type(dn, "cpu")
+               ncpus++;
+
+       cpuclk = kzalloc(ncpus * sizeof(*cpuclk), GFP_KERNEL);
+       if (WARN_ON(!cpuclk))
+               return;
+
+       clks = kzalloc(ncpus * sizeof(*clks), GFP_KERNEL);
+       if (WARN_ON(!clks))
+               return;
+
+       for_each_node_by_type(dn, "cpu") {
+               struct clk_init_data init;
+               struct clk *clk;
+               struct clk *parent_clk;
+               char *clk_name = kzalloc(5, GFP_KERNEL);
+               int cpu, err;
+
+               if (WARN_ON(!clk_name))
+                       return;
+
+               err = of_property_read_u32(dn, "reg", &cpu);
+               if (WARN_ON(err))
+                       return;
+
+               sprintf(clk_name, "cpu%d", cpu);
+               parent_clk = of_clk_get(node, 0);
+
+               cpuclk[cpu].parent_name = __clk_get_name(parent_clk);
+               cpuclk[cpu].clk_name = clk_name;
+               cpuclk[cpu].cpu = cpu;
+               cpuclk[cpu].reg_base = clock_complex_base;
+               cpuclk[cpu].hw.init = &init;
+
+               init.name = cpuclk[cpu].clk_name;
+               init.ops = &cpu_ops;
+               init.flags = 0;
+               init.parent_names = &cpuclk[cpu].parent_name;
+               init.num_parents = 1;
+
+               clk = clk_register(NULL, &cpuclk[cpu].hw);
+               if (WARN_ON(IS_ERR(clk)))
+                       goto bail_out;
+               clks[cpu] = clk;
+       }
+       clk_data.clk_num = MAX_CPU;
+       clk_data.clks = clks;
+       of_clk_add_provider(node, of_clk_src_onecell_get, &clk_data);
+
+       return;
+bail_out:
+       kfree(clks);
+       kfree(cpuclk);
+}
+
+static const __initconst struct of_device_id clk_cpu_match[] = {
+       {
+               .compatible = "marvell,armada-xp-cpu-clock",
+               .data = of_cpu_clk_setup,
+       },
+       {
+               /* sentinel */
+       },
+};
+
+void __init mvebu_cpu_clk_init(void)
+{
+       of_clk_init(clk_cpu_match);
+}
diff --git a/drivers/clk/mvebu/clk-cpu.h b/drivers/clk/mvebu/clk-cpu.h
new file mode 100644 (file)
index 0000000..08e2aff
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Marvell MVEBU CPU clock handling.
+ *
+ * Copyright (C) 2012 Marvell
+ *
+ * Gregory CLEMENT <gregory.clement@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __MVEBU_CLK_CPU_H
+#define __MVEBU_CLK_CPU_H
+
+#ifdef CONFIG_MVEBU_CLK_CPU
+void __init mvebu_cpu_clk_init(void);
+#else
+static inline void mvebu_cpu_clk_init(void) {}
+#endif
+
+#endif
index e6742acf9880d676d1d83e501e47a3f2c9d72030..e3d4d7a9de6a8dac0121618639a72169b770ce1d 100644 (file)
 #include <linux/clk/mvebu.h>
 #include <linux/of.h>
 #include "clk-core.h"
+#include "clk-cpu.h"
 
 void __init mvebu_clocks_init(void)
 {
        mvebu_core_clk_init();
+       mvebu_cpu_clk_init();
 }