1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * Copyright (C) 2013 ARM Limited |
5 | */ |
6 | |
7 | #include <linux/amba/sp810.h> |
8 | #include <linux/slab.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> |
15 | |
16 | #define to_clk_sp810_timerclken(_hw) \ |
17 | container_of(_hw, struct clk_sp810_timerclken, hw) |
18 | |
19 | struct clk_sp810; |
20 | |
21 | struct clk_sp810_timerclken { |
22 | struct clk_hw hw; |
23 | struct clk *clk; |
24 | struct clk_sp810 *sp810; |
25 | int channel; |
26 | }; |
27 | |
28 | struct clk_sp810 { |
29 | struct device_node *node; |
30 | void __iomem *base; |
31 | spinlock_t lock; |
32 | struct clk_sp810_timerclken timerclken[4]; |
33 | }; |
34 | |
35 | static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw) |
36 | { |
37 | struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); |
38 | u32 val = readl(addr: timerclken->sp810->base + SCCTRL); |
39 | |
40 | return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel))); |
41 | } |
42 | |
43 | static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index) |
44 | { |
45 | struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); |
46 | struct clk_sp810 *sp810 = timerclken->sp810; |
47 | u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel); |
48 | unsigned long flags = 0; |
49 | |
50 | if (WARN_ON(index > 1)) |
51 | return -EINVAL; |
52 | |
53 | spin_lock_irqsave(&sp810->lock, flags); |
54 | |
55 | val = readl(addr: sp810->base + SCCTRL); |
56 | val &= ~(1 << shift); |
57 | val |= index << shift; |
58 | writel(val, addr: sp810->base + SCCTRL); |
59 | |
60 | spin_unlock_irqrestore(lock: &sp810->lock, flags); |
61 | |
62 | return 0; |
63 | } |
64 | |
65 | static const struct clk_ops clk_sp810_timerclken_ops = { |
66 | .determine_rate = clk_hw_determine_rate_no_reparent, |
67 | .get_parent = clk_sp810_timerclken_get_parent, |
68 | .set_parent = clk_sp810_timerclken_set_parent, |
69 | }; |
70 | |
71 | static struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec, |
72 | void *data) |
73 | { |
74 | struct clk_sp810 *sp810 = data; |
75 | |
76 | if (WARN_ON(clkspec->args_count != 1 || |
77 | clkspec->args[0] >= ARRAY_SIZE(sp810->timerclken))) |
78 | return NULL; |
79 | |
80 | return sp810->timerclken[clkspec->args[0]].clk; |
81 | } |
82 | |
83 | static void __init clk_sp810_of_setup(struct device_node *node) |
84 | { |
85 | struct clk_sp810 *sp810 = kzalloc(size: sizeof(*sp810), GFP_KERNEL); |
86 | const char *parent_names[2]; |
87 | int num = ARRAY_SIZE(parent_names); |
88 | char name[12]; |
89 | struct clk_init_data init; |
90 | static int instance; |
91 | int i; |
92 | bool deprecated; |
93 | |
94 | if (!sp810) |
95 | return; |
96 | |
97 | if (of_clk_parent_fill(np: node, parents: parent_names, size: num) != num) { |
98 | pr_warn("Failed to obtain parent clocks for SP810!\n" ); |
99 | kfree(objp: sp810); |
100 | return; |
101 | } |
102 | |
103 | sp810->node = node; |
104 | sp810->base = of_iomap(node, index: 0); |
105 | spin_lock_init(&sp810->lock); |
106 | |
107 | init.name = name; |
108 | init.ops = &clk_sp810_timerclken_ops; |
109 | init.flags = 0; |
110 | init.parent_names = parent_names; |
111 | init.num_parents = num; |
112 | |
113 | deprecated = !of_find_property(np: node, name: "assigned-clock-parents" , NULL); |
114 | |
115 | for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) { |
116 | snprintf(buf: name, size: sizeof(name), fmt: "sp810_%d_%d" , instance, i); |
117 | |
118 | sp810->timerclken[i].sp810 = sp810; |
119 | sp810->timerclken[i].channel = i; |
120 | sp810->timerclken[i].hw.init = &init; |
121 | |
122 | /* |
123 | * If DT isn't setting the parent, force it to be |
124 | * the 1 MHz clock without going through the framework. |
125 | * We do this before clk_register() so that it can determine |
126 | * the parent and setup the tree properly. |
127 | */ |
128 | if (deprecated) |
129 | init.ops->set_parent(&sp810->timerclken[i].hw, 1); |
130 | |
131 | sp810->timerclken[i].clk = clk_register(NULL, |
132 | hw: &sp810->timerclken[i].hw); |
133 | WARN_ON(IS_ERR(sp810->timerclken[i].clk)); |
134 | } |
135 | |
136 | of_clk_add_provider(np: node, clk_src_get: clk_sp810_timerclken_of_get, data: sp810); |
137 | instance++; |
138 | } |
139 | CLK_OF_DECLARE(sp810, "arm,sp810" , clk_sp810_of_setup); |
140 | |