18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/kernel.h>
38c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
48c2ecf20Sopenharmony_ci#include <linux/of_address.h>
58c2ecf20Sopenharmony_ci#include <linux/init.h>
68c2ecf20Sopenharmony_ci#include <linux/io.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#define CLK_COUNT 4 /* cpu_clk, sys_clk, usb_clk, sdio_clk */
98c2ecf20Sopenharmony_cistatic struct clk *clks[CLK_COUNT];
108c2ecf20Sopenharmony_cistatic struct clk_onecell_data clk_data = { clks, CLK_COUNT };
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#define SYSCLK_DIV	0x20
138c2ecf20Sopenharmony_ci#define CPUCLK_DIV	0x24
148c2ecf20Sopenharmony_ci#define DIV_BYPASS	BIT(23)
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci/*** CLKGEN_PLL ***/
178c2ecf20Sopenharmony_ci#define extract_pll_n(val)	((val >>  0) & ((1u << 7) - 1))
188c2ecf20Sopenharmony_ci#define extract_pll_k(val)	((val >> 13) & ((1u << 3) - 1))
198c2ecf20Sopenharmony_ci#define extract_pll_m(val)	((val >> 16) & ((1u << 3) - 1))
208c2ecf20Sopenharmony_ci#define extract_pll_isel(val)	((val >> 24) & ((1u << 3) - 1))
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic void __init make_pll(int idx, const char *parent, void __iomem *base)
238c2ecf20Sopenharmony_ci{
248c2ecf20Sopenharmony_ci	char name[8];
258c2ecf20Sopenharmony_ci	u32 val, mul, div;
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci	sprintf(name, "pll%d", idx);
288c2ecf20Sopenharmony_ci	val = readl(base + idx * 8);
298c2ecf20Sopenharmony_ci	mul =  extract_pll_n(val) + 1;
308c2ecf20Sopenharmony_ci	div = (extract_pll_m(val) + 1) << extract_pll_k(val);
318c2ecf20Sopenharmony_ci	clk_register_fixed_factor(NULL, name, parent, 0, mul, div);
328c2ecf20Sopenharmony_ci	if (extract_pll_isel(val) != 1)
338c2ecf20Sopenharmony_ci		panic("%s: input not set to XTAL_IN\n", name);
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic void __init make_cd(int idx, void __iomem *base)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	char name[8];
398c2ecf20Sopenharmony_ci	u32 val, mul, div;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	sprintf(name, "cd%d", idx);
428c2ecf20Sopenharmony_ci	val = readl(base + idx * 8);
438c2ecf20Sopenharmony_ci	mul =  1 << 27;
448c2ecf20Sopenharmony_ci	div = (2 << 27) + val;
458c2ecf20Sopenharmony_ci	clk_register_fixed_factor(NULL, name, "pll2", 0, mul, div);
468c2ecf20Sopenharmony_ci	if (val > 0xf0000000)
478c2ecf20Sopenharmony_ci		panic("%s: unsupported divider %x\n", name, val);
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic void __init tango4_clkgen_setup(struct device_node *np)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	struct clk **pp = clk_data.clks;
538c2ecf20Sopenharmony_ci	void __iomem *base = of_iomap(np, 0);
548c2ecf20Sopenharmony_ci	const char *parent = of_clk_get_parent_name(np, 0);
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (!base)
578c2ecf20Sopenharmony_ci		panic("%pOFn: invalid address\n", np);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	if (readl(base + CPUCLK_DIV) & DIV_BYPASS)
608c2ecf20Sopenharmony_ci		panic("%pOFn: unsupported cpuclk setup\n", np);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	if (readl(base + SYSCLK_DIV) & DIV_BYPASS)
638c2ecf20Sopenharmony_ci		panic("%pOFn: unsupported sysclk setup\n", np);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	writel(0x100, base + CPUCLK_DIV); /* disable frequency ramping */
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	make_pll(0, parent, base);
688c2ecf20Sopenharmony_ci	make_pll(1, parent, base);
698c2ecf20Sopenharmony_ci	make_pll(2, parent, base);
708c2ecf20Sopenharmony_ci	make_cd(2, base + 0x80);
718c2ecf20Sopenharmony_ci	make_cd(6, base + 0x80);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	pp[0] = clk_register_divider(NULL, "cpu_clk", "pll0", 0,
748c2ecf20Sopenharmony_ci			base + CPUCLK_DIV, 8, 8, CLK_DIVIDER_ONE_BASED, NULL);
758c2ecf20Sopenharmony_ci	pp[1] = clk_register_fixed_factor(NULL, "sys_clk", "pll1", 0, 1, 4);
768c2ecf20Sopenharmony_ci	pp[2] = clk_register_fixed_factor(NULL,  "usb_clk", "cd2", 0, 1, 2);
778c2ecf20Sopenharmony_ci	pp[3] = clk_register_fixed_factor(NULL, "sdio_clk", "cd6", 0, 1, 2);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	if (IS_ERR(pp[0]) || IS_ERR(pp[1]) || IS_ERR(pp[2]) || IS_ERR(pp[3]))
808c2ecf20Sopenharmony_ci		panic("%pOFn: clk registration failed\n", np);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	if (of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data))
838c2ecf20Sopenharmony_ci		panic("%pOFn: clk provider registration failed\n", np);
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ciCLK_OF_DECLARE(tango4_clkgen, "sigma,tango4-clkgen", tango4_clkgen_setup);
86