diff options
Diffstat (limited to 'drivers/clk/sunxi/clk-sun6i-gpu.c')
-rw-r--r-- | drivers/clk/sunxi/clk-sun6i-gpu.c | 126 |
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); |