summaryrefslogtreecommitdiff
path: root/drivers/clk/spacemit/clk_ddn.c
blob: 7b93f30d5c373dfee27f6856f161c06c9ad3e505 (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
84
85
86
87
88
89
90
91
92
93
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2024 SpacemiT Technology Co. Ltd
 * Copyright (c) 2024-2025 Haylen Chu <[email protected]>
 * Copyright (c) 2025 Junhui Liu <[email protected]>
 *  Authors: Haylen Chu <[email protected]>
 *
 * 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 <dm/device.h>
#include <regmap.h>
#include <linux/clk-provider.h>
#include <linux/rational.h>

#include "clk_ddn.h"

#define UBOOT_DM_SPACEMIT_CLK_DDN "spacemit_clk_ddn"

static unsigned long ccu_ddn_calc_rate(unsigned long prate, unsigned long num,
				       unsigned long den, unsigned int pre_div)
{
	return prate * den / pre_div / 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 / ddn->pre_div,
				    ddn->den_mask >> ddn->den_shift,
				    ddn->num_mask >> ddn->num_shift,
				    den, num);
	return ccu_ddn_calc_rate(prate, *num, *den, ddn->pre_div);
}

static unsigned long ccu_ddn_recalc_rate(struct clk *clk)
{
	struct ccu_ddn *ddn = clk_to_ccu_ddn(clk);
	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(clk_get_parent_rate(clk), num, den, ddn->pre_div);
}

static unsigned long ccu_ddn_set_rate(struct clk *clk, unsigned long rate)
{
	struct ccu_ddn *ddn = clk_to_ccu_ddn(clk);
	unsigned long num, den;

	ccu_ddn_calc_best_rate(ddn, rate, clk_get_parent_rate(clk), &num, &den);

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

	return 0;
}

static const struct clk_ops spacemit_clk_ddn_ops = {
	.get_rate = ccu_ddn_recalc_rate,
	.set_rate = ccu_ddn_set_rate,
};

int spacemit_ddn_init(struct ccu_common *common)
{
	struct clk *clk = &common->clk;

	return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_DDN,
			    common->name, common->parents[0]);
}

U_BOOT_DRIVER(spacemit_clk_ddn) = {
	.name	= UBOOT_DM_SPACEMIT_CLK_DDN,
	.id	= UCLASS_CLK,
	.ops	= &spacemit_clk_ddn_ops,
	.flags	= DM_FLAG_PRE_RELOC,
};