File Index
Files and directories
Sign in
FILE

ccu_mix.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/spacemit/ccu_mix.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (c) 2024 SpacemiT Technology Co. Ltd
  4 * Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
  5 *
  6 * MIX clock type is the combination of mux, factor or divider, and gate
  7 */
  8
  9#include <linux/clk-provider.h>
 10
 11#include "ccu_mix.h"
 12
 13#define MIX_FC_TIMEOUT_US	10000
 14#define MIX_FC_DELAY_US		5
 15
 16static void ccu_gate_disable(struct clk_hw *hw)
 17{
 18	struct ccu_mix *mix = hw_to_ccu_mix(hw);
 19
 20	ccu_update(&mix->common, ctrl, mix->gate.mask, 0);
 21}
 22
 23static int ccu_gate_enable(struct clk_hw *hw)
 24{
 25	struct ccu_mix *mix = hw_to_ccu_mix(hw);
 26	struct ccu_gate_config *gate = &mix->gate;
 27
 28	ccu_update(&mix->common, ctrl, gate->mask, gate->mask);
 29
 30	return 0;
 31}
 32
 33static int ccu_gate_is_enabled(struct clk_hw *hw)
 34{
 35	struct ccu_mix *mix = hw_to_ccu_mix(hw);
 36	struct ccu_gate_config *gate = &mix->gate;
 37
 38	return (ccu_read(&mix->common, ctrl) & gate->mask) == gate->mask;
 39}
 40
 41static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
 42					    unsigned long parent_rate)
 43{
 44	struct ccu_mix *mix = hw_to_ccu_mix(hw);
 45
 46	return parent_rate * mix->factor.mul / mix->factor.div;
 47}
 48
 49static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
 50					 unsigned long parent_rate)
 51{
 52	struct ccu_mix *mix = hw_to_ccu_mix(hw);
 53	struct ccu_div_config *div = &mix->div;
 54	unsigned long val;
 55
 56	val = ccu_read(&mix->common, ctrl) >> div->shift;
 57	val &= (1 << div->width) - 1;
 58
 59	return divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
 60}
 61
 62/*
 63 * Some clocks require a "FC" (frequency change) bit to be set after changing
 64 * their rates or reparenting. This bit will be automatically cleared by
 65 * hardware in MIX_FC_TIMEOUT_US, which indicates the operation is completed.
 66 */
 67static int ccu_mix_trigger_fc(struct clk_hw *hw)
 68{
 69	struct ccu_common *common = hw_to_ccu_common(hw);
 70	unsigned int val;
 71
 72	if (common->reg_fc)
 73		return 0;
 74
 75	ccu_update(common, fc, common->mask_fc, common->mask_fc);
 76
 77	return regmap_read_poll_timeout_atomic(common->regmap, common->reg_fc,
 78					       val, !(val & common->mask_fc),
 79					       MIX_FC_DELAY_US,
 80					       MIX_FC_TIMEOUT_US);
 81}
 82
 83static int ccu_factor_determine_rate(struct clk_hw *hw,
 84				     struct clk_rate_request *req)
 85{
 86	req->rate = ccu_factor_recalc_rate(hw, req->best_parent_rate);
 87
 88	return 0;
 89}
 90
 91static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
 92			       unsigned long parent_rate)
 93{
 94	return 0;
 95}
 96
 97static unsigned long
 98ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
 99		       struct clk_hw **best_parent,
100		       unsigned long *best_parent_rate,
101		       u32 *div_val)
102{
103	struct ccu_mix *mix = hw_to_ccu_mix(hw);
104	unsigned int parent_num = clk_hw_get_num_parents(hw);
105	struct ccu_div_config *div = &mix->div;
106	u32 div_max = 1 << div->width;
107	unsigned long best_rate = 0;
108
109	for (int i = 0; i < parent_num; i++) {
110		struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
111		unsigned long parent_rate;
112
113		if (!parent)
114			continue;
115
116		parent_rate = clk_hw_get_rate(parent);
117
118		for (int j = 1; j <= div_max; j++) {
119			unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j);
120
121			if (abs(tmp - rate) < abs(best_rate - rate)) {
122				best_rate = tmp;
123
124				if (div_val)
125					*div_val = j - 1;
126
127				if (best_parent) {
128					*best_parent      = parent;
129					*best_parent_rate = parent_rate;
130				}
131			}
132		}
133	}
134
135	return best_rate;
136}
137
138static int ccu_mix_determine_rate(struct clk_hw *hw,
139				  struct clk_rate_request *req)
140{
141	req->rate = ccu_mix_calc_best_rate(hw, req->rate,
142					   &req->best_parent_hw,
143					   &req->best_parent_rate,
144					   NULL);
145	return 0;
146}
147
148static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
149			    unsigned long parent_rate)
150{
151	struct ccu_mix *mix = hw_to_ccu_mix(hw);
152	struct ccu_common *common = &mix->common;
153	struct ccu_div_config *div = &mix->div;
154	u32 current_div, target_div, mask;
155
156	ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
157
158	current_div = ccu_read(common, ctrl) >> div->shift;
159	current_div &= (1 << div->width) - 1;
160
161	if (current_div == target_div)
162		return 0;
163
164	mask = GENMASK(div->width + div->shift - 1, div->shift);
165
166	ccu_update(common, ctrl, mask, target_div << div->shift);
167
168	return ccu_mix_trigger_fc(hw);
169}
170
171static u8 ccu_mux_get_parent(struct clk_hw *hw)
172{
173	struct ccu_mix *mix = hw_to_ccu_mix(hw);
174	struct ccu_mux_config *mux = &mix->mux;
175	u8 parent;
176
177	parent = ccu_read(&mix->common, ctrl) >> mux->shift;
178	parent &= (1 << mux->width) - 1;
179
180	return parent;
181}
182
183static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
184{
185	struct ccu_mix *mix = hw_to_ccu_mix(hw);
186	struct ccu_mux_config *mux = &mix->mux;
187	u32 mask;
188
189	mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
190
191	ccu_update(&mix->common, ctrl, mask, index << mux->shift);
192
193	return ccu_mix_trigger_fc(hw);
194}
195
196const struct clk_ops spacemit_ccu_gate_ops = {
197	.disable	= ccu_gate_disable,
198	.enable		= ccu_gate_enable,
199	.is_enabled	= ccu_gate_is_enabled,
200};
201EXPORT_SYMBOL_NS_GPL(spacemit_ccu_gate_ops, "CLK_SPACEMIT");
202
203const struct clk_ops spacemit_ccu_factor_ops = {
204	.determine_rate = ccu_factor_determine_rate,
205	.recalc_rate	= ccu_factor_recalc_rate,
206	.set_rate	= ccu_factor_set_rate,
207};
208EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_ops, "CLK_SPACEMIT");
209
210const struct clk_ops spacemit_ccu_mux_ops = {
211	.determine_rate = ccu_mix_determine_rate,
212	.get_parent	= ccu_mux_get_parent,
213	.set_parent	= ccu_mux_set_parent,
214};
215EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_ops, "CLK_SPACEMIT");
216
217const struct clk_ops spacemit_ccu_div_ops = {
218	.determine_rate = ccu_mix_determine_rate,
219	.recalc_rate	= ccu_div_recalc_rate,
220	.set_rate	= ccu_mix_set_rate,
221};
222EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_ops, "CLK_SPACEMIT");
223
224const struct clk_ops spacemit_ccu_factor_gate_ops = {
225	.disable	= ccu_gate_disable,
226	.enable		= ccu_gate_enable,
227	.is_enabled	= ccu_gate_is_enabled,
228
229	.determine_rate = ccu_factor_determine_rate,
230	.recalc_rate	= ccu_factor_recalc_rate,
231	.set_rate	= ccu_factor_set_rate,
232};
233EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_gate_ops, "CLK_SPACEMIT");
234
235const struct clk_ops spacemit_ccu_mux_gate_ops = {
236	.disable	= ccu_gate_disable,
237	.enable		= ccu_gate_enable,
238	.is_enabled	= ccu_gate_is_enabled,
239
240	.determine_rate = ccu_mix_determine_rate,
241	.get_parent	= ccu_mux_get_parent,
242	.set_parent	= ccu_mux_set_parent,
243};
244EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_gate_ops, "CLK_SPACEMIT");
245
246const struct clk_ops spacemit_ccu_div_gate_ops = {
247	.disable	= ccu_gate_disable,
248	.enable		= ccu_gate_enable,
249	.is_enabled	= ccu_gate_is_enabled,
250
251	.determine_rate = ccu_mix_determine_rate,
252	.recalc_rate	= ccu_div_recalc_rate,
253	.set_rate	= ccu_mix_set_rate,
254};
255EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_gate_ops, "CLK_SPACEMIT");
256
257const struct clk_ops spacemit_ccu_mux_div_gate_ops = {
258	.disable	= ccu_gate_disable,
259	.enable		= ccu_gate_enable,
260	.is_enabled	= ccu_gate_is_enabled,
261
262	.get_parent	= ccu_mux_get_parent,
263	.set_parent	= ccu_mux_set_parent,
264
265	.determine_rate = ccu_mix_determine_rate,
266	.recalc_rate	= ccu_div_recalc_rate,
267	.set_rate	= ccu_mix_set_rate,
268};
269EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_gate_ops, "CLK_SPACEMIT");
270
271const struct clk_ops spacemit_ccu_mux_div_ops = {
272	.get_parent	= ccu_mux_get_parent,
273	.set_parent	= ccu_mux_set_parent,
274
275	.determine_rate = ccu_mix_determine_rate,
276	.recalc_rate	= ccu_div_recalc_rate,
277	.set_rate	= ccu_mix_set_rate,
278};
279EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_ops, "CLK_SPACEMIT");
~/ Read-only file preview.