xref: /linux/drivers/clk/spacemit/ccu_ddn.c (revision 9f32a03e3e0d372c520d829dd4da6022fe88832a)
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  * DDN stands for "Divider Denominator Numerator", it's M/N clock with a
7  * constant x2 factor. This clock hardware follows the equation below,
8  *
9  *	      numerator       Fin
10  *	2 * ------------- = -------
11  *	     denominator      Fout
12  *
13  * Thus, Fout could be calculated with,
14  *
15  *		Fin	denominator
16  *	Fout = ----- * -------------
17  *		 2	 numerator
18  */
19 
20 #include <linux/clk-provider.h>
21 #include <linux/rational.h>
22 
23 #include "ccu_ddn.h"
24 
ccu_ddn_calc_rate(unsigned long prate,unsigned long num,unsigned long den)25 static unsigned long ccu_ddn_calc_rate(unsigned long prate,
26 				       unsigned long num, unsigned long den)
27 {
28 	return prate * den / 2 / num;
29 }
30 
ccu_ddn_calc_best_rate(struct ccu_ddn * ddn,unsigned long rate,unsigned long prate,unsigned long * num,unsigned long * den)31 static unsigned long ccu_ddn_calc_best_rate(struct ccu_ddn *ddn,
32 					    unsigned long rate, unsigned long prate,
33 					    unsigned long *num, unsigned long *den)
34 {
35 	rational_best_approximation(rate, prate / 2,
36 				    ddn->den_mask >> ddn->den_shift,
37 				    ddn->num_mask >> ddn->num_shift,
38 				    den, num);
39 	return ccu_ddn_calc_rate(prate, *num, *den);
40 }
41 
ccu_ddn_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)42 static long ccu_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
43 			       unsigned long *prate)
44 {
45 	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
46 	unsigned long num, den;
47 
48 	return ccu_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
49 }
50 
ccu_ddn_recalc_rate(struct clk_hw * hw,unsigned long prate)51 static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
52 {
53 	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
54 	unsigned int val, num, den;
55 
56 	val = ccu_read(&ddn->common, ctrl);
57 
58 	num = (val & ddn->num_mask) >> ddn->num_shift;
59 	den = (val & ddn->den_mask) >> ddn->den_shift;
60 
61 	return ccu_ddn_calc_rate(prate, num, den);
62 }
63 
ccu_ddn_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long prate)64 static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
65 			    unsigned long prate)
66 {
67 	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
68 	unsigned long num, den;
69 
70 	ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
71 
72 	ccu_update(&ddn->common, ctrl,
73 		   ddn->num_mask | ddn->den_mask,
74 		   (num << ddn->num_shift) | (den << ddn->den_shift));
75 
76 	return 0;
77 }
78 
79 const struct clk_ops spacemit_ccu_ddn_ops = {
80 	.recalc_rate	= ccu_ddn_recalc_rate,
81 	.round_rate	= ccu_ddn_round_rate,
82 	.set_rate	= ccu_ddn_set_rate,
83 };
84