1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * TI clock autoidle support |
4 | * |
5 | * Copyright (C) 2013 Texas Instruments, Inc. |
6 | * |
7 | * Tero Kristo <t-kristo@ti.com> |
8 | */ |
9 | |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/io.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/clk/ti.h> |
16 | |
17 | #include "clock.h" |
18 | |
19 | struct clk_ti_autoidle { |
20 | struct clk_omap_reg reg; |
21 | u8 shift; |
22 | u8 flags; |
23 | const char *name; |
24 | struct list_head node; |
25 | }; |
26 | |
27 | #define AUTOIDLE_LOW 0x1 |
28 | |
29 | static LIST_HEAD(autoidle_clks); |
30 | |
31 | /* |
32 | * we have some non-atomic read/write |
33 | * operations behind it, so lets |
34 | * take one lock for handling autoidle |
35 | * of all clocks |
36 | */ |
37 | static DEFINE_SPINLOCK(autoidle_spinlock); |
38 | |
39 | static int _omap2_clk_deny_idle(struct clk_hw_omap *clk) |
40 | { |
41 | if (clk->ops && clk->ops->deny_idle) { |
42 | unsigned long irqflags; |
43 | |
44 | spin_lock_irqsave(&autoidle_spinlock, irqflags); |
45 | clk->autoidle_count++; |
46 | if (clk->autoidle_count == 1) |
47 | clk->ops->deny_idle(clk); |
48 | |
49 | spin_unlock_irqrestore(lock: &autoidle_spinlock, flags: irqflags); |
50 | } |
51 | return 0; |
52 | } |
53 | |
54 | static int _omap2_clk_allow_idle(struct clk_hw_omap *clk) |
55 | { |
56 | if (clk->ops && clk->ops->allow_idle) { |
57 | unsigned long irqflags; |
58 | |
59 | spin_lock_irqsave(&autoidle_spinlock, irqflags); |
60 | clk->autoidle_count--; |
61 | if (clk->autoidle_count == 0) |
62 | clk->ops->allow_idle(clk); |
63 | |
64 | spin_unlock_irqrestore(lock: &autoidle_spinlock, flags: irqflags); |
65 | } |
66 | return 0; |
67 | } |
68 | |
69 | /** |
70 | * omap2_clk_deny_idle - disable autoidle on an OMAP clock |
71 | * @clk: struct clk * to disable autoidle for |
72 | * |
73 | * Disable autoidle on an OMAP clock. |
74 | */ |
75 | int omap2_clk_deny_idle(struct clk *clk) |
76 | { |
77 | struct clk_hw *hw; |
78 | |
79 | if (!clk) |
80 | return -EINVAL; |
81 | |
82 | hw = __clk_get_hw(clk); |
83 | |
84 | if (omap2_clk_is_hw_omap(hw)) { |
85 | struct clk_hw_omap *c = to_clk_hw_omap(hw); |
86 | |
87 | return _omap2_clk_deny_idle(clk: c); |
88 | } |
89 | |
90 | return -EINVAL; |
91 | } |
92 | |
93 | /** |
94 | * omap2_clk_allow_idle - enable autoidle on an OMAP clock |
95 | * @clk: struct clk * to enable autoidle for |
96 | * |
97 | * Enable autoidle on an OMAP clock. |
98 | */ |
99 | int omap2_clk_allow_idle(struct clk *clk) |
100 | { |
101 | struct clk_hw *hw; |
102 | |
103 | if (!clk) |
104 | return -EINVAL; |
105 | |
106 | hw = __clk_get_hw(clk); |
107 | |
108 | if (omap2_clk_is_hw_omap(hw)) { |
109 | struct clk_hw_omap *c = to_clk_hw_omap(hw); |
110 | |
111 | return _omap2_clk_allow_idle(clk: c); |
112 | } |
113 | |
114 | return -EINVAL; |
115 | } |
116 | |
117 | static void _allow_autoidle(struct clk_ti_autoidle *clk) |
118 | { |
119 | u32 val; |
120 | |
121 | val = ti_clk_ll_ops->clk_readl(&clk->reg); |
122 | |
123 | if (clk->flags & AUTOIDLE_LOW) |
124 | val &= ~(1 << clk->shift); |
125 | else |
126 | val |= (1 << clk->shift); |
127 | |
128 | ti_clk_ll_ops->clk_writel(val, &clk->reg); |
129 | } |
130 | |
131 | static void _deny_autoidle(struct clk_ti_autoidle *clk) |
132 | { |
133 | u32 val; |
134 | |
135 | val = ti_clk_ll_ops->clk_readl(&clk->reg); |
136 | |
137 | if (clk->flags & AUTOIDLE_LOW) |
138 | val |= (1 << clk->shift); |
139 | else |
140 | val &= ~(1 << clk->shift); |
141 | |
142 | ti_clk_ll_ops->clk_writel(val, &clk->reg); |
143 | } |
144 | |
145 | /** |
146 | * _clk_generic_allow_autoidle_all - enable autoidle for all clocks |
147 | * |
148 | * Enables hardware autoidle for all registered DT clocks, which have |
149 | * the feature. |
150 | */ |
151 | static void _clk_generic_allow_autoidle_all(void) |
152 | { |
153 | struct clk_ti_autoidle *c; |
154 | |
155 | list_for_each_entry(c, &autoidle_clks, node) |
156 | _allow_autoidle(clk: c); |
157 | } |
158 | |
159 | /** |
160 | * _clk_generic_deny_autoidle_all - disable autoidle for all clocks |
161 | * |
162 | * Disables hardware autoidle for all registered DT clocks, which have |
163 | * the feature. |
164 | */ |
165 | static void _clk_generic_deny_autoidle_all(void) |
166 | { |
167 | struct clk_ti_autoidle *c; |
168 | |
169 | list_for_each_entry(c, &autoidle_clks, node) |
170 | _deny_autoidle(clk: c); |
171 | } |
172 | |
173 | /** |
174 | * of_ti_clk_autoidle_setup - sets up hardware autoidle for a clock |
175 | * @node: pointer to the clock device node |
176 | * |
177 | * Checks if a clock has hardware autoidle support or not (check |
178 | * for presence of 'ti,autoidle-shift' property in the device tree |
179 | * node) and sets up the hardware autoidle feature for the clock |
180 | * if available. If autoidle is available, the clock is also added |
181 | * to the autoidle list for later processing. Returns 0 on success, |
182 | * negative error value on failure. |
183 | */ |
184 | int __init of_ti_clk_autoidle_setup(struct device_node *node) |
185 | { |
186 | u32 shift; |
187 | struct clk_ti_autoidle *clk; |
188 | int ret; |
189 | |
190 | /* Check if this clock has autoidle support or not */ |
191 | if (of_property_read_u32(np: node, propname: "ti,autoidle-shift" , out_value: &shift)) |
192 | return 0; |
193 | |
194 | clk = kzalloc(size: sizeof(*clk), GFP_KERNEL); |
195 | |
196 | if (!clk) |
197 | return -ENOMEM; |
198 | |
199 | clk->shift = shift; |
200 | clk->name = ti_dt_clk_name(np: node); |
201 | ret = ti_clk_get_reg_addr(node, index: 0, reg: &clk->reg); |
202 | if (ret) { |
203 | kfree(objp: clk); |
204 | return ret; |
205 | } |
206 | |
207 | if (of_property_read_bool(np: node, propname: "ti,invert-autoidle-bit" )) |
208 | clk->flags |= AUTOIDLE_LOW; |
209 | |
210 | list_add(new: &clk->node, head: &autoidle_clks); |
211 | |
212 | return 0; |
213 | } |
214 | |
215 | /** |
216 | * omap2_clk_enable_autoidle_all - enable autoidle on all OMAP clocks that |
217 | * support it |
218 | * |
219 | * Enable clock autoidle on all OMAP clocks that have allow_idle |
220 | * function pointers associated with them. This function is intended |
221 | * to be temporary until support for this is added to the common clock |
222 | * code. Returns 0. |
223 | */ |
224 | int omap2_clk_enable_autoidle_all(void) |
225 | { |
226 | int ret; |
227 | |
228 | ret = omap2_clk_for_each(fn: _omap2_clk_allow_idle); |
229 | if (ret) |
230 | return ret; |
231 | |
232 | _clk_generic_allow_autoidle_all(); |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | /** |
238 | * omap2_clk_disable_autoidle_all - disable autoidle on all OMAP clocks that |
239 | * support it |
240 | * |
241 | * Disable clock autoidle on all OMAP clocks that have allow_idle |
242 | * function pointers associated with them. This function is intended |
243 | * to be temporary until support for this is added to the common clock |
244 | * code. Returns 0. |
245 | */ |
246 | int omap2_clk_disable_autoidle_all(void) |
247 | { |
248 | int ret; |
249 | |
250 | ret = omap2_clk_for_each(fn: _omap2_clk_deny_idle); |
251 | if (ret) |
252 | return ret; |
253 | |
254 | _clk_generic_deny_autoidle_all(); |
255 | |
256 | return 0; |
257 | } |
258 | |