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
19struct 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
29static 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 */
37static DEFINE_SPINLOCK(autoidle_spinlock);
38
39static 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(&autoidle_spinlock, irqflags);
50	}
51	return 0;
52}
53
54static 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(&autoidle_spinlock, 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 */
75int 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(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 */
99int 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(c);
112	}
113
114	return -EINVAL;
115}
116
117static 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
131static 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 */
151static 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(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 */
165static 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(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 */
184int __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(node, "ti,autoidle-shift", &shift))
192		return 0;
193
194	clk = kzalloc(sizeof(*clk), GFP_KERNEL);
195
196	if (!clk)
197		return -ENOMEM;
198
199	clk->shift = shift;
200	clk->name = ti_dt_clk_name(node);
201	ret = ti_clk_get_reg_addr(node, 0, &clk->reg);
202	if (ret) {
203		kfree(clk);
204		return ret;
205	}
206
207	if (of_property_read_bool(node, "ti,invert-autoidle-bit"))
208		clk->flags |= AUTOIDLE_LOW;
209
210	list_add(&clk->node, &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 */
224int omap2_clk_enable_autoidle_all(void)
225{
226	int ret;
227
228	ret = omap2_clk_for_each(_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 */
246int omap2_clk_disable_autoidle_all(void)
247{
248	int ret;
249
250	ret = omap2_clk_for_each(_omap2_clk_deny_idle);
251	if (ret)
252		return ret;
253
254	_clk_generic_deny_autoidle_all();
255
256	return 0;
257}
258