aboutsummaryrefslogtreecommitdiff
path: root/drivers/clk
diff options
context:
space:
mode:
authorZhangfei Gao <zhangfei.gao@linaro.org>2013-08-21 10:34:54 +0800
committerAndrey Konovalov <andrey.konovalov@linaro.org>2014-04-16 23:52:26 +0400
commitef597ea8326a8d442b31c1b64c667df6e8c71248 (patch)
tree9075f340b5bc2853e9aeaa01288852b190451870 /drivers/clk
parent43fb1d6a029169847e412c9f20464124c8e98d4a (diff)
ARM: hs: add clk-hi3716
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org> Signed-off-by: Zhang Mingjun <zhang.mingjun@linaro.org>
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/hisilicon/Makefile2
-rw-r--r--drivers/clk/hisilicon/clk-hi3716.c268
2 files changed, 269 insertions, 1 deletions
diff --git a/drivers/clk/hisilicon/Makefile b/drivers/clk/hisilicon/Makefile
index a049108341f..f3f11c4b748 100644
--- a/drivers/clk/hisilicon/Makefile
+++ b/drivers/clk/hisilicon/Makefile
@@ -2,4 +2,4 @@
# Hisilicon Clock specific Makefile
#
-obj-y += clk.o clkgate-separated.o clk-hi3620.o
+obj-y += clk.o clkgate-separated.o clk-hi3620.o clk-hi3716.o
diff --git a/drivers/clk/hisilicon/clk-hi3716.c b/drivers/clk/hisilicon/clk-hi3716.c
new file mode 100644
index 00000000000..887ffe90fba
--- /dev/null
+++ b/drivers/clk/hisilicon/clk-hi3716.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (c) 2013 Hisilicon Limited.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+static DEFINE_SPINLOCK(_lock);
+
+static void __iomem *clk_base;
+
+struct hi3716_clk {
+ struct clk_gate gate;
+ void __iomem *reg;
+ u8 reset_bit;
+};
+
+#define MAX_NUMS 10
+
+static struct hi3716_clk *to_clk_hi3716(struct clk_hw *hw)
+{
+ return container_of(hw, struct hi3716_clk, gate.hw);
+}
+
+static void __init hi3716_map_io(void)
+{
+ struct device_node *node;
+
+ if (clk_base)
+ return;
+
+ node = of_find_compatible_node(NULL, NULL, "hisilicon,clkbase");
+ if (node)
+ clk_base = of_iomap(node, 0);
+ WARN_ON(!clk_base);
+}
+
+static int hi3716_clkgate_prepare(struct clk_hw *hw)
+{
+ struct hi3716_clk *clk = to_clk_hi3716(hw);
+ unsigned long flags = 0;
+ u32 reg;
+
+ spin_lock_irqsave(&_lock, flags);
+
+ reg = readl_relaxed(clk->reg);
+ reg &= ~BIT(clk->reset_bit);
+ writel_relaxed(reg, clk->reg);
+
+ spin_unlock_irqrestore(&_lock, flags);
+
+ return 0;
+}
+
+static void hi3716_clkgate_unprepare(struct clk_hw *hw)
+{
+ struct hi3716_clk *clk = to_clk_hi3716(hw);
+ unsigned long flags = 0;
+ u32 reg;
+
+ spin_lock_irqsave(&_lock, flags);
+
+ reg = readl_relaxed(clk->reg);
+ reg |= BIT(clk->reset_bit);
+ writel_relaxed(reg, clk->reg);
+
+ spin_unlock_irqrestore(&_lock, flags);
+}
+
+static struct clk_ops hi3716_clkgate_ops = {
+ .prepare = hi3716_clkgate_prepare,
+ .unprepare = hi3716_clkgate_unprepare,
+};
+
+void __init hi3716_clkgate_setup(struct device_node *node)
+{
+ struct clk *clk;
+ struct hi3716_clk *p_clk;
+ struct clk_init_data init;
+ const char *parent_name;
+ u32 array[3]; /* reg, enable_bit, reset_bit */
+ int err;
+
+ hi3716_map_io();
+ err = of_property_read_u32_array(node, "gate-reg", &array[0], 3);
+ if (WARN_ON(err))
+ return;
+
+ err = of_property_read_string(node, "clock-output-names", &init.name);
+ if (WARN_ON(err))
+ return;
+
+ p_clk = kzalloc(sizeof(*p_clk), GFP_KERNEL);
+ if (WARN_ON(!p_clk))
+ return;
+
+ hi3716_clkgate_ops.enable = clk_gate_ops.enable;
+ hi3716_clkgate_ops.disable = clk_gate_ops.disable;
+ hi3716_clkgate_ops.is_enabled = clk_gate_ops.is_enabled;
+
+ init.ops = &hi3716_clkgate_ops;
+ init.flags = CLK_SET_RATE_PARENT;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ p_clk->reg = p_clk->gate.reg = clk_base + array[0];
+ p_clk->gate.bit_idx = array[1];
+ p_clk->gate.flags = 0;
+ p_clk->gate.lock = &_lock;
+ p_clk->gate.hw.init = &init;
+ p_clk->reset_bit = array[2];
+
+ clk = clk_register(NULL, &p_clk->gate.hw);
+ if (WARN_ON(IS_ERR(clk))) {
+ kfree(p_clk);
+ return;
+ }
+
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+}
+
+static void __init hi3716_clkmux_setup(struct device_node *node)
+{
+ int num = 0, err;
+ void __iomem *reg;
+ unsigned int shift, width;
+ u32 array[3]; /* reg, mux_shift, mux_width */
+ u32 *table = NULL;
+ const char *clk_name = node->name;
+ const char *parents[MAX_NUMS];
+ struct clk *clk;
+
+ hi3716_map_io();
+ err = of_property_read_string(node, "clock-output-names", &clk_name);
+ if (WARN_ON(err))
+ return;
+
+ err = of_property_read_u32_array(node, "mux-reg", &array[0], 3);
+ if (WARN_ON(err))
+ return;
+
+ reg = clk_base + array[0];
+ shift = array[1];
+ width = array[2];
+
+ while ((num < MAX_NUMS) &&
+ ((parents[num] = of_clk_get_parent_name(node, num)) != NULL))
+ num++;
+ if (!num)
+ return;
+
+ table = kzalloc(sizeof(u32 *) * num, GFP_KERNEL);
+ if (WARN_ON(!table))
+ return;
+
+ err = of_property_read_u32_array(node, "mux-table", table, num);
+ if (WARN_ON(err))
+ goto err;
+
+ clk = clk_register_mux_table(NULL, clk_name, parents, num,
+ CLK_SET_RATE_PARENT, reg, shift, BIT(width) - 1,
+ 0, table, &_lock);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err;
+
+ clk_register_clkdev(clk, clk_name, NULL);
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ return;
+
+err:
+ kfree(table);
+ return;
+}
+
+static void __init hi3716_fixed_pll_setup(struct device_node *node)
+{
+ const char *clk_name, *parent_name;
+ struct clk *clks[MAX_NUMS];
+ u32 rate[MAX_NUMS];
+ struct clk_onecell_data *clk_data;
+ int i, err, nums = 0;
+
+ nums = of_property_count_strings(node, "clock-output-names");
+ if (WARN_ON((nums < 0) || (nums > MAX_NUMS)))
+ return;
+
+ err = of_property_read_u32_array(node, "clock-frequency",
+ &rate[0], nums);
+ WARN_ON(err);
+
+ parent_name = of_clk_get_parent_name(node, 0);
+
+ for (i = 0; i < nums; i++) {
+ err = of_property_read_string_index(node, "clock-output-names",
+ i, &clk_name);
+ WARN_ON(err);
+
+ clks[i] = clk_register_fixed_rate(NULL, clk_name,
+ parent_name, 0, rate[i]);
+ WARN_ON(IS_ERR(clks[i]));
+ }
+
+ clk_data = kzalloc(sizeof(*clk_data) + nums * sizeof(struct clk *),
+ GFP_KERNEL);
+ if (WARN_ON(!clk_data))
+ return;
+
+ memcpy(&clk_data[1], clks, nums * sizeof(struct clk *));
+ clk_data->clks = (struct clk **)&clk_data[1];
+ clk_data->clk_num = nums;
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+}
+
+void __init hi3716_fixed_divider_setup(struct device_node *node)
+{
+ const char *clk_parent;
+ const char *clk_name;
+ u32 div[MAX_NUMS];
+ struct clk *clks[MAX_NUMS];
+ struct clk_onecell_data *clk_data;
+ int err, i, nums = 0;
+
+ clk_parent = of_clk_get_parent_name(node, 0);
+
+ nums = of_property_count_strings(node, "clock-output-names");
+ if (WARN_ON((nums < 0) || (nums > MAX_NUMS)))
+ return;
+
+ err = of_property_read_u32_array(node, "div-table", &div[0], nums);
+ WARN_ON(err);
+
+ for (i = 0; i < nums; i++) {
+ err = of_property_read_string_index(node,
+ "clock-output-names", i, &clk_name);
+ WARN_ON(err);
+
+ clks[i] = clk_register_fixed_factor(NULL, clk_name,
+ clk_parent, CLK_SET_RATE_PARENT, 1, div[i]);
+ WARN_ON(IS_ERR(clks[i]));
+ }
+
+ clk_data = kzalloc(sizeof(*clk_data) + nums * sizeof(struct clk *),
+ GFP_KERNEL);
+ if (WARN_ON(!clk_data))
+ return;
+
+ memcpy(&clk_data[1], clks, nums * sizeof(struct clk *));
+ clk_data->clks = (struct clk **)&clk_data[1];
+ clk_data->clk_num = nums;
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+}
+
+CLK_OF_DECLARE(hi3716_fixed_rate, "fixed-clock", of_fixed_clk_setup)
+CLK_OF_DECLARE(hi3716_fixed_pll, "hisilicon,hi3716-fixed-pll", hi3716_fixed_pll_setup)
+CLK_OF_DECLARE(hi3716_divider, "hisilicon,hi3716-fixed-divider", hi3716_fixed_divider_setup)
+CLK_OF_DECLARE(hi3716_mux, "hisilicon,hi3716-clk-mux", hi3716_clkmux_setup)
+CLK_OF_DECLARE(hi3716_gate, "hisilicon,hi3716-clk-gate", hi3716_clkgate_setup)