summaryrefslogtreecommitdiff
path: root/drivers/clk/spacemit/ccu_ddn.c
blob: be311b045698e95a688a35858a8ac1bcfbffd2c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2024 SpacemiT Technology Co. Ltd
 * Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
 *
 * DDN stands for "Divider Denominator Numerator", it's M/N clock with a
 * constant x2 factor. This clock hardware follows the equation below,
 *
 *	      numerator       Fin
 *	2 * ------------- = -------
 *	     denominator      Fout
 *
 * Thus, Fout could be calculated with,
 *
 *		Fin	denominator
 *	Fout = ----- * -------------
 *		 2	 numerator
 */

#include <linux/clk-provider.h>
#include <linux/rational.h>

#include "ccu_ddn.h"

static unsigned long ccu_ddn_calc_rate(unsigned long prate,
				       unsigned long num, unsigned long den)
{
	return prate * den / 2 / num;
}

static unsigned long ccu_ddn_calc_best_rate(struct ccu_ddn *ddn,
					    unsigned long rate, unsigned long prate,
					    unsigned long *num, unsigned long *den)
{
	rational_best_approximation(rate, prate / 2,
				    ddn->den_mask >> ddn->den_shift,
				    ddn->num_mask >> ddn->num_shift,
				    den, num);
	return ccu_ddn_calc_rate(prate, *num, *den);
}

static long ccu_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
			       unsigned long *prate)
{
	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
	unsigned long num, den;

	return ccu_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
}

static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
	unsigned int val, num, den;

	val = ccu_read(&ddn->common, ctrl);

	num = (val & ddn->num_mask) >> ddn->num_shift;
	den = (val & ddn->den_mask) >> ddn->den_shift;

	return ccu_ddn_calc_rate(prate, num, den);
}

static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
			    unsigned long prate)
{
	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
	unsigned long num, den;

	ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den);

	ccu_update(&ddn->common, ctrl,
		   ddn->num_mask | ddn->den_mask,
		   (num << ddn->num_shift) | (den << ddn->den_shift));

	return 0;
}

const struct clk_ops spacemit_ccu_ddn_ops = {
	.recalc_rate	= ccu_ddn_recalc_rate,
	.round_rate	= ccu_ddn_round_rate,
	.set_rate	= ccu_ddn_set_rate,
};