summaryrefslogtreecommitdiff
path: root/drivers/clk/sunxi/clk-sun6i-gpu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/sunxi/clk-sun6i-gpu.c')
-rw-r--r--drivers/clk/sunxi/clk-sun6i-gpu.c126
1 files changed, 126 insertions, 0 deletions
diff --git a/drivers/clk/sunxi/clk-sun6i-gpu.c b/drivers/clk/sunxi/clk-sun6i-gpu.c
new file mode 100644
index 000000000000..86a97d43cc44
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-gpu.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2015 Theobroma Systems
+ *
+ * Christoph Muellner <christoph.muellner@theobroma-systems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+
+#include "clk-factors.h"
+
+#define CLK_SEL_POS 24
+#define CLK_SEL_MASK (0x7<<CLK_SEL_POS)
+
+#define CLK_SEL_PLL8 0x0
+#define CLK_SEL_PLL9 0x4
+
+/**
+ * sun6i_get_gpu_factors() - calculates factors for GPU clocks
+ * GPU rate is calculated as follows
+ * rate = (parent_rate) / (m + 1);
+ */
+
+static void sun6i_a31_get_gpu_factors(u32 *freq, u32 parent_rate,
+ u8 *n, u8 *k, u8 *m, u8 *p)
+{
+ u8 div, calcm;
+
+ /* These clocks can only divide, so we will never be able to achieve
+ * frequencies higher than the parent frequency */
+ if (*freq > parent_rate)
+ *freq = parent_rate;
+
+ div = DIV_ROUND_UP(parent_rate, *freq);
+
+ calcm = DIV_ROUND_UP(div, 1);
+
+ *freq = (parent_rate) / calcm;
+
+ /* we were called to round the frequency, we can now return */
+ if (n == NULL)
+ return;
+
+ *m = calcm - 1;
+}
+
+/* user manual says "n" but it's really "m" */
+static struct clk_factors_config sun6i_a31_gpu_config = {
+ .mshift = 0,
+ .mwidth = 2,
+};
+
+static const struct factors_data sun6i_a31_gpu_data = {
+ .enable = 31,
+ .mux = 24,
+ .muxmask = BIT(2) | BIT(1) | BIT(0),
+ .table = &sun6i_a31_gpu_config,
+ .getter = sun6i_a31_get_gpu_factors,
+};
+
+static DEFINE_SPINLOCK(sun6i_a31_gpu_lock);
+
+static void sun6i_a31_set_gpu_clk_src(struct device_node *node, void __iomem *reg)
+{
+ u32 val, clk_sel;
+ const char *name;
+ const char *parent_name;
+
+ name = node->name;
+ of_property_read_string_index(node, "clock-output-names", 0, &name);
+ /* get first parent clock */
+ parent_name = of_clk_get_parent_name(node, 0);
+
+ /* setup CLK_SRC_SEL bits accordingly */
+ val = readl(reg);
+ clk_sel = 0;
+
+ /* get clk sel */
+ if (strcmp(parent_name, "pll8") == 0) {
+ clk_sel = CLK_SEL_PLL8;
+ } else if (strcmp(parent_name, "pll9") == 0) {
+ clk_sel = CLK_SEL_PLL9;
+ } else {
+ clk_sel = CLK_SEL_PLL8;
+ parent_name = "pll8 (as DTS provided parent is unknown)";
+ }
+
+ pr_info("Setting clk src of %s to %s\n", name, parent_name);
+
+ /* apply clk selection */
+ val &= ~(CLK_SEL_MASK);
+ val = val | (clk_sel<<CLK_SEL_POS);
+
+ writel(val, reg);
+}
+
+static void __init sun6i_a31_gpu_setup(struct device_node *node)
+{
+ void __iomem *reg = NULL;
+
+ reg = of_iomap(node, 0);
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for a31-gpu-clk\n");
+ return;
+ }
+
+ /* Setup routing corretly */
+ sun6i_a31_set_gpu_clk_src(node, reg);
+
+ sunxi_factors_register(node, &sun6i_a31_gpu_data,
+ &sun6i_a31_gpu_lock, reg);
+}
+
+CLK_OF_DECLARE(sun6i_a31_gpu, "allwinner,sun6i-a31-gpu-clk", sun6i_a31_gpu_setup);