diff options
-rw-r--r-- | arch/arm/boot/dts/sun6i-a31-pangolin.dts | 4 | ||||
-rw-r--r-- | arch/arm/boot/dts/sun6i-a31.dtsi | 49 | ||||
-rw-r--r-- | drivers/clk/sunxi/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sun6i-gpu.c | 126 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sunxi.c | 63 |
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,}, {} }; |