1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright 2018 NXP.
4 *   Dong Aisheng <aisheng.dong@nxp.com>
5 */
6
7#include <linux/clk-provider.h>
8#include <linux/err.h>
9#include <linux/io.h>
10#include <linux/slab.h>
11
12#include "clk.h"
13
14struct clk_divider_gate {
15	struct clk_divider divider;
16	u32 cached_val;
17};
18
19static inline struct clk_divider_gate *to_clk_divider_gate(struct clk_hw *hw)
20{
21	struct clk_divider *div = to_clk_divider(hw);
22
23	return container_of(div, struct clk_divider_gate, divider);
24}
25
26static unsigned long clk_divider_gate_recalc_rate_ro(struct clk_hw *hw,
27						     unsigned long parent_rate)
28{
29	struct clk_divider *div = to_clk_divider(hw);
30	unsigned int val;
31
32	val = readl(div->reg) >> div->shift;
33	val &= clk_div_mask(div->width);
34	if (!val)
35		return 0;
36
37	return divider_recalc_rate(hw, parent_rate, val, div->table,
38				   div->flags, div->width);
39}
40
41static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,
42						  unsigned long parent_rate)
43{
44	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);
45	struct clk_divider *div = to_clk_divider(hw);
46	unsigned long flags;
47	unsigned int val;
48
49	spin_lock_irqsave(div->lock, flags);
50
51	if (!clk_hw_is_enabled(hw)) {
52		val = div_gate->cached_val;
53	} else {
54		val = readl(div->reg) >> div->shift;
55		val &= clk_div_mask(div->width);
56	}
57
58	spin_unlock_irqrestore(div->lock, flags);
59
60	if (!val)
61		return 0;
62
63	return divider_recalc_rate(hw, parent_rate, val, div->table,
64				   div->flags, div->width);
65}
66
67static int clk_divider_determine_rate(struct clk_hw *hw,
68				      struct clk_rate_request *req)
69{
70	return clk_divider_ops.determine_rate(hw, req);
71}
72
73static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,
74				unsigned long parent_rate)
75{
76	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);
77	struct clk_divider *div = to_clk_divider(hw);
78	unsigned long flags;
79	int value;
80	u32 val;
81
82	value = divider_get_val(rate, parent_rate, div->table,
83				div->width, div->flags);
84	if (value < 0)
85		return value;
86
87	spin_lock_irqsave(div->lock, flags);
88
89	if (clk_hw_is_enabled(hw)) {
90		val = readl(div->reg);
91		val &= ~(clk_div_mask(div->width) << div->shift);
92		val |= (u32)value << div->shift;
93		writel(val, div->reg);
94	} else {
95		div_gate->cached_val = value;
96	}
97
98	spin_unlock_irqrestore(div->lock, flags);
99
100	return 0;
101}
102
103static int clk_divider_enable(struct clk_hw *hw)
104{
105	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);
106	struct clk_divider *div = to_clk_divider(hw);
107	unsigned long flags;
108	u32 val;
109
110	if (!div_gate->cached_val) {
111		pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
112		return -EINVAL;
113	}
114
115	spin_lock_irqsave(div->lock, flags);
116	/* restore div val */
117	val = readl(div->reg);
118	val |= div_gate->cached_val << div->shift;
119	writel(val, div->reg);
120
121	spin_unlock_irqrestore(div->lock, flags);
122
123	return 0;
124}
125
126static void clk_divider_disable(struct clk_hw *hw)
127{
128	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);
129	struct clk_divider *div = to_clk_divider(hw);
130	unsigned long flags;
131	u32 val;
132
133	spin_lock_irqsave(div->lock, flags);
134
135	/* store the current div val */
136	val = readl(div->reg) >> div->shift;
137	val &= clk_div_mask(div->width);
138	div_gate->cached_val = val;
139	writel(0, div->reg);
140
141	spin_unlock_irqrestore(div->lock, flags);
142}
143
144static int clk_divider_is_enabled(struct clk_hw *hw)
145{
146	struct clk_divider *div = to_clk_divider(hw);
147	u32 val;
148
149	val = readl(div->reg) >> div->shift;
150	val &= clk_div_mask(div->width);
151
152	return val ? 1 : 0;
153}
154
155static const struct clk_ops clk_divider_gate_ro_ops = {
156	.recalc_rate = clk_divider_gate_recalc_rate_ro,
157	.determine_rate = clk_divider_determine_rate,
158};
159
160static const struct clk_ops clk_divider_gate_ops = {
161	.recalc_rate = clk_divider_gate_recalc_rate,
162	.determine_rate = clk_divider_determine_rate,
163	.set_rate = clk_divider_gate_set_rate,
164	.enable = clk_divider_enable,
165	.disable = clk_divider_disable,
166	.is_enabled = clk_divider_is_enabled,
167};
168
169/*
170 * NOTE: In order to reuse the most code from the common divider,
171 * we also design our divider following the way that provids an extra
172 * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by
173 * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY
174 * flag which can be specified by user flexibly.
175 */
176struct clk_hw *imx_clk_hw_divider_gate(const char *name, const char *parent_name,
177				    unsigned long flags, void __iomem *reg,
178				    u8 shift, u8 width, u8 clk_divider_flags,
179				    const struct clk_div_table *table,
180				    spinlock_t *lock)
181{
182	struct clk_init_data init;
183	struct clk_divider_gate *div_gate;
184	struct clk_hw *hw;
185	u32 val;
186	int ret;
187
188	div_gate  = kzalloc(sizeof(*div_gate), GFP_KERNEL);
189	if (!div_gate)
190		return ERR_PTR(-ENOMEM);
191
192	init.name = name;
193	if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
194		init.ops = &clk_divider_gate_ro_ops;
195	else
196		init.ops = &clk_divider_gate_ops;
197	init.flags = flags;
198	init.parent_names = parent_name ? &parent_name : NULL;
199	init.num_parents = parent_name ? 1 : 0;
200
201	div_gate->divider.reg = reg;
202	div_gate->divider.shift = shift;
203	div_gate->divider.width = width;
204	div_gate->divider.lock = lock;
205	div_gate->divider.table = table;
206	div_gate->divider.hw.init = &init;
207	div_gate->divider.flags = CLK_DIVIDER_ONE_BASED | clk_divider_flags;
208	/* cache gate status */
209	val = readl(reg) >> shift;
210	val &= clk_div_mask(width);
211	div_gate->cached_val = val;
212
213	hw = &div_gate->divider.hw;
214	ret = clk_hw_register(NULL, hw);
215	if (ret) {
216		kfree(div_gate);
217		hw = ERR_PTR(ret);
218	}
219
220	return hw;
221}
222