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,
};
|