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,unsigned int pre_div)25 static unsigned long ccu_ddn_calc_rate(unsigned long prate, unsigned long num,
26 unsigned long den, unsigned int pre_div)
27 {
28 return prate * den / pre_div / 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 / ddn->pre_div,
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, ddn->pre_div);
40 }
41
ccu_ddn_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)42 static int ccu_ddn_determine_rate(struct clk_hw *hw,
43 struct clk_rate_request *req)
44 {
45 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
46 unsigned long num, den;
47
48 req->rate = ccu_ddn_calc_best_rate(ddn, req->rate,
49 req->best_parent_rate, &num, &den);
50
51 return 0;
52 }
53
ccu_ddn_recalc_rate(struct clk_hw * hw,unsigned long prate)54 static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
55 {
56 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
57 unsigned int val, num, den;
58
59 val = ccu_read(&ddn->common, ctrl);
60
61 num = (val & ddn->num_mask) >> ddn->num_shift;
62 den = (val & ddn->den_mask) >> ddn->den_shift;
63
64 return ccu_ddn_calc_rate(prate, num, den, ddn->pre_div);
65 }
66
ccu_ddn_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long prate)67 static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
68 unsigned long prate)
69 {
70 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
71 unsigned long num, den;
72
73 ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
74
75 ccu_update(&ddn->common, ctrl,
76 ddn->num_mask | ddn->den_mask,
77 (num << ddn->num_shift) | (den << ddn->den_shift));
78
79 return 0;
80 }
81
82 const struct clk_ops spacemit_ccu_ddn_ops = {
83 .recalc_rate = ccu_ddn_recalc_rate,
84 .determine_rate = ccu_ddn_determine_rate,
85 .set_rate = ccu_ddn_set_rate,
86 };
87