summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Muellner <christoph.muellner@theobroma-systems.com>2015-04-24 23:35:29 +0200
committerKlaus Goger <klaus.goger@theobroma-systems.com>2015-07-30 18:52:57 +0200
commit7ce3d59bcfbf650de4116a2bdf83c69de2267e0a (patch)
tree6c1bc65909a5d12a1404d9d59e1f234c70815952
parent8086fb77715f68cc6cfa9f2bd48e3f6e09154f84 (diff)
Clk: sunxi: Adding support for GPU clocks and PLL8/9.
This patch adds support for the GPU clocks on sun6i a31. This is a typical clock path, which can be realized with this patch: * PLL8 -> GPU_HYD -> GPU_MEM * PLL9 -> GPU_CORE * AHB1 -> AHB1_GPU Signed-off-by: Christoph Muellner <christoph.muellner@theobroma-systems.com>
-rw-r--r--arch/arm/boot/dts/sun6i-a31-pangolin.dts4
-rw-r--r--arch/arm/boot/dts/sun6i-a31.dtsi49
-rw-r--r--drivers/clk/sunxi/Makefile1
-rw-r--r--drivers/clk/sunxi/clk-sun6i-gpu.c126
-rw-r--r--drivers/clk/sunxi/clk-sunxi.c63
5 files changed, 243 insertions, 0 deletions
diff --git a/arch/arm/boot/dts/sun6i-a31-pangolin.dts b/arch/arm/boot/dts/sun6i-a31-pangolin.dts
index 7de0e70e16c3..9d7dbb40b022 100644
--- a/arch/arm/boot/dts/sun6i-a31-pangolin.dts
+++ b/arch/arm/boot/dts/sun6i-a31-pangolin.dts
@@ -137,6 +137,10 @@
status = "okay";
};
+&gpu {
+ status = "okay";
+};
+
&mmc0 {
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin_pangolin>;
diff --git a/arch/arm/boot/dts/sun6i-a31.dtsi b/arch/arm/boot/dts/sun6i-a31.dtsi
index 9ea8c31b1352..cf7f5a31a41b 100644
--- a/arch/arm/boot/dts/sun6i-a31.dtsi
+++ b/arch/arm/boot/dts/sun6i-a31.dtsi
@@ -187,6 +187,7 @@
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <24000000>;
+ clock-output-names = "osc24M";
};
osc32k: clk@0 {
@@ -212,6 +213,22 @@
clock-output-names = "pll6", "pll6x2";
};
+ pll8: clk@01c20038 {
+ #clock-cells = <0>;
+ compatible = "allwinner,sun6i-a31-pll8-clk";
+ reg = <0x01c20038 0x4>;
+ clocks = <&osc24M>;
+ clock-output-names = "pll8";
+ };
+
+ pll9: clk@01c20044 {
+ #clock-cells = <0>;
+ compatible = "allwinner,sun6i-a31-pll9-clk";
+ reg = <0x01c20044 0x4>;
+ clocks = <&osc24M>;
+ clock-output-names = "pll9";
+ };
+
cpu: cpu@01c20050 {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-cpu-clk";
@@ -392,6 +409,30 @@
"usb_ohci2";
};
+ gpucore_clk: clk@01c201a0 {
+ #clock-cells = <0>;
+ compatible = "allwinner,sun6i-a31-gpu-clk";
+ reg = <0x01c201a0 0x4>;
+ clocks = <&pll9>;
+ clock-output-names = "gpucore";
+ };
+
+ gpumem_clk: clk@01c201a4 {
+ #clock-cells = <0>;
+ compatible = "allwinner,sun6i-a31-gpu-clk";
+ reg = <0x01c201a4 0x4>;
+ clocks = <&pll8>;
+ clock-output-names = "gpumem";
+ };
+
+ gpuhyd_clk: clk@01c201a8 {
+ #clock-cells = <0>;
+ compatible = "allwinner,sun6i-a31-gpu-clk";
+ reg = <0x01c201a8 0x4>;
+ clocks = <&pll8>;
+ clock-output-names = "gpuhyd";
+ };
+
/*
* The following two are dummy clocks, placeholders used in the gmac_tx
* clock. The gmac driver will choose one parent depending on the PHY
@@ -928,6 +969,14 @@
status = "disabled";
};
+ gpu: gpu@01c40000 {
+ clocks = <&ahb1_gates 52>, <&gpucore_clk>, <&gpumem_clk>, <&gpuhyd_clk>;
+ clock-names = "ahb", "gpucore", "gpumem", "gpuhyd";
+ resets = <&ahb1_rst 52>;
+ reset-names = "ahb";
+ status = "disabled";
+ };
+
gic: interrupt-controller@01c81000 {
compatible = "arm,cortex-a7-gic", "arm,cortex-a15-gic";
reg = <0x01c81000 0x1000>,
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index 3a5292e3fcf8..89a45bdb9882 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -6,6 +6,7 @@ obj-y += clk-sunxi.o clk-factors.o
obj-y += clk-a10-hosc.o
obj-y += clk-a20-gmac.o
obj-y += clk-mod0.o
+obj-y += clk-sun6i-gpu.o
obj-y += clk-sun8i-mbus.o
obj-y += clk-sun9i-core.o
obj-y += clk-sun9i-mmc.o
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);
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
index b0bae0f2ad23..783acb5bb78a 100644
--- a/drivers/clk/sunxi/clk-sunxi.c
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -520,6 +520,45 @@ static void sun5i_a13_get_ahb_factors(u32 *freq, u32 parent_rate,
*p = div;
}
+/*
+ * sun6i_a31_get_pll8_9_factors() - calculates n, k factors for A31 PLL8 and PLL9
+ * PLL8 (or PLL9) rate is calculated as follows
+ * rate = parent_rate * (n + 1) / (m + 1)
+ * parent_rate is always 24Mhz
+ * rate must be in the range of 30MHz..600Mhz
+ * n must be in the range of 0..127
+ * m must be in the range of 0..15
+ */
+
+static void sun6i_a31_get_pll8_9_factors(u32 *freq, u32 parent_rate,
+ u8 *n, u8 *k, u8 *m, u8 *p)
+{
+ const u32 pll8_9_min = 30*1000*1000;
+ const u32 pll8_9_max = 600*1000*1000;
+ u8 n_tmp, m_tmp;
+
+ /* Limit the rate to the specified range */
+ if (*freq < pll8_9_min)
+ *freq = pll8_9_min;
+ if (*freq > pll8_9_max)
+ *freq = pll8_9_max;
+
+ /* Set m to a high value and derive n
+ * We choose m to 7, which means we will not exceed a value
+ * of 384 MHz for *freq.
+ */
+ m_tmp = 7;
+ n_tmp = ((*freq * (m_tmp+1)) / parent_rate) - 1;
+ *freq = parent_rate * (n_tmp+1) / (m_tmp+1);
+
+ /* we were called to round the frequency, we can now return */
+ if (n == NULL)
+ return;
+
+ *n = n_tmp;
+ *m = m_tmp;
+}
+
/**
* sun4i_get_apb1_factors() - calculates m, p factors for APB1
* APB1 rate is calculated as follows
@@ -660,6 +699,14 @@ static struct clk_factors_config sun5i_a13_ahb_config = {
.pwidth = 2,
};
+static struct clk_factors_config sun6i_a31_pll8_9_config = {
+ .nshift = 8,
+ .nwidth = 7,
+ .kshift = 0,
+ .kwidth = 4,
+ .n_start = 1,
+};
+
static struct clk_factors_config sun4i_apb1_config = {
.mshift = 0,
.mwidth = 5,
@@ -727,6 +774,20 @@ static const struct factors_data sun5i_a13_ahb_data __initconst = {
.getter = sun5i_a13_get_ahb_factors,
};
+static const struct factors_data sun6i_a31_pll8_data __initconst = {
+ .enable = 31,
+ .table = &sun6i_a31_pll8_9_config,
+ .getter = sun6i_a31_get_pll8_9_factors,
+ .name = "pll8",
+};
+
+static const struct factors_data sun6i_a31_pll9_data __initconst = {
+ .enable = 31,
+ .table = &sun6i_a31_pll8_9_config,
+ .getter = sun6i_a31_get_pll8_9_factors,
+ .name = "pll9",
+};
+
static const struct factors_data sun4i_apb1_data __initconst = {
.mux = 24,
.muxmask = BIT(1) | BIT(0),
@@ -1341,6 +1402,8 @@ static const struct of_device_id clk_factors_match[] __initconst = {
{.compatible = "allwinner,sun5i-a13-ahb-clk", .data = &sun5i_a13_ahb_data,},
{.compatible = "allwinner,sun4i-a10-apb1-clk", .data = &sun4i_apb1_data,},
{.compatible = "allwinner,sun7i-a20-out-clk", .data = &sun7i_a20_out_data,},
+ {.compatible = "allwinner,sun6i-a31-pll8-clk", .data = &sun6i_a31_pll8_data,},
+ {.compatible = "allwinner,sun6i-a31-pll9-clk", .data = &sun6i_a31_pll9_data,},
{}
};