18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2013 ARM Limited
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/amba/sp810.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci#include <linux/clk.h>
108c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
118c2ecf20Sopenharmony_ci#include <linux/err.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/of.h>
148c2ecf20Sopenharmony_ci#include <linux/of_address.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define to_clk_sp810_timerclken(_hw) \
178c2ecf20Sopenharmony_ci		container_of(_hw, struct clk_sp810_timerclken, hw)
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistruct clk_sp810;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct clk_sp810_timerclken {
228c2ecf20Sopenharmony_ci	struct clk_hw hw;
238c2ecf20Sopenharmony_ci	struct clk *clk;
248c2ecf20Sopenharmony_ci	struct clk_sp810 *sp810;
258c2ecf20Sopenharmony_ci	int channel;
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistruct clk_sp810 {
298c2ecf20Sopenharmony_ci	struct device_node *node;
308c2ecf20Sopenharmony_ci	void __iomem *base;
318c2ecf20Sopenharmony_ci	spinlock_t lock;
328c2ecf20Sopenharmony_ci	struct clk_sp810_timerclken timerclken[4];
338c2ecf20Sopenharmony_ci};
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw)
368c2ecf20Sopenharmony_ci{
378c2ecf20Sopenharmony_ci	struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
388c2ecf20Sopenharmony_ci	u32 val = readl(timerclken->sp810->base + SCCTRL);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel)));
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
468c2ecf20Sopenharmony_ci	struct clk_sp810 *sp810 = timerclken->sp810;
478c2ecf20Sopenharmony_ci	u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel);
488c2ecf20Sopenharmony_ci	unsigned long flags = 0;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (WARN_ON(index > 1))
518c2ecf20Sopenharmony_ci		return -EINVAL;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sp810->lock, flags);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	val = readl(sp810->base + SCCTRL);
568c2ecf20Sopenharmony_ci	val &= ~(1 << shift);
578c2ecf20Sopenharmony_ci	val |= index << shift;
588c2ecf20Sopenharmony_ci	writel(val, sp810->base + SCCTRL);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sp810->lock, flags);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return 0;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic const struct clk_ops clk_sp810_timerclken_ops = {
668c2ecf20Sopenharmony_ci	.get_parent = clk_sp810_timerclken_get_parent,
678c2ecf20Sopenharmony_ci	.set_parent = clk_sp810_timerclken_set_parent,
688c2ecf20Sopenharmony_ci};
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec,
718c2ecf20Sopenharmony_ci		void *data)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	struct clk_sp810 *sp810 = data;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	if (WARN_ON(clkspec->args_count != 1 ||
768c2ecf20Sopenharmony_ci		    clkspec->args[0] >=	ARRAY_SIZE(sp810->timerclken)))
778c2ecf20Sopenharmony_ci		return NULL;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	return sp810->timerclken[clkspec->args[0]].clk;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic void __init clk_sp810_of_setup(struct device_node *node)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL);
858c2ecf20Sopenharmony_ci	const char *parent_names[2];
868c2ecf20Sopenharmony_ci	int num = ARRAY_SIZE(parent_names);
878c2ecf20Sopenharmony_ci	char name[12];
888c2ecf20Sopenharmony_ci	struct clk_init_data init;
898c2ecf20Sopenharmony_ci	static int instance;
908c2ecf20Sopenharmony_ci	int i;
918c2ecf20Sopenharmony_ci	bool deprecated;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	if (!sp810)
948c2ecf20Sopenharmony_ci		return;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (of_clk_parent_fill(node, parent_names, num) != num) {
978c2ecf20Sopenharmony_ci		pr_warn("Failed to obtain parent clocks for SP810!\n");
988c2ecf20Sopenharmony_ci		kfree(sp810);
998c2ecf20Sopenharmony_ci		return;
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	sp810->node = node;
1038c2ecf20Sopenharmony_ci	sp810->base = of_iomap(node, 0);
1048c2ecf20Sopenharmony_ci	spin_lock_init(&sp810->lock);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	init.name = name;
1078c2ecf20Sopenharmony_ci	init.ops = &clk_sp810_timerclken_ops;
1088c2ecf20Sopenharmony_ci	init.flags = 0;
1098c2ecf20Sopenharmony_ci	init.parent_names = parent_names;
1108c2ecf20Sopenharmony_ci	init.num_parents = num;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	deprecated = !of_find_property(node, "assigned-clock-parents", NULL);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) {
1158c2ecf20Sopenharmony_ci		snprintf(name, sizeof(name), "sp810_%d_%d", instance, i);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci		sp810->timerclken[i].sp810 = sp810;
1188c2ecf20Sopenharmony_ci		sp810->timerclken[i].channel = i;
1198c2ecf20Sopenharmony_ci		sp810->timerclken[i].hw.init = &init;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci		/*
1228c2ecf20Sopenharmony_ci		 * If DT isn't setting the parent, force it to be
1238c2ecf20Sopenharmony_ci		 * the 1 MHz clock without going through the framework.
1248c2ecf20Sopenharmony_ci		 * We do this before clk_register() so that it can determine
1258c2ecf20Sopenharmony_ci		 * the parent and setup the tree properly.
1268c2ecf20Sopenharmony_ci		 */
1278c2ecf20Sopenharmony_ci		if (deprecated)
1288c2ecf20Sopenharmony_ci			init.ops->set_parent(&sp810->timerclken[i].hw, 1);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci		sp810->timerclken[i].clk = clk_register(NULL,
1318c2ecf20Sopenharmony_ci				&sp810->timerclken[i].hw);
1328c2ecf20Sopenharmony_ci		WARN_ON(IS_ERR(sp810->timerclken[i].clk));
1338c2ecf20Sopenharmony_ci	}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810);
1368c2ecf20Sopenharmony_ci	instance++;
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ciCLK_OF_DECLARE(sp810, "arm,sp810", clk_sp810_of_setup);
139