1 | // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) |
2 | /* |
3 | * Copyright (c) 2016 AmLogic, Inc. |
4 | * Author: Michael Turquette <mturquette@baylibre.com> |
5 | */ |
6 | |
7 | /* |
8 | * MultiPhase Locked Loops are outputs from a PLL with additional frequency |
9 | * scaling capabilities. MPLL rates are calculated as: |
10 | * |
11 | * f(N2_integer, SDM_IN ) = 2.0G/(N2_integer + SDM_IN/16384) |
12 | */ |
13 | |
14 | #include <linux/clk-provider.h> |
15 | #include <linux/module.h> |
16 | #include <linux/spinlock.h> |
17 | |
18 | #include "clk-regmap.h" |
19 | #include "clk-mpll.h" |
20 | |
21 | #define SDM_DEN 16384 |
22 | #define N2_MIN 4 |
23 | #define N2_MAX 511 |
24 | |
25 | static inline struct meson_clk_mpll_data * |
26 | meson_clk_mpll_data(struct clk_regmap *clk) |
27 | { |
28 | return (struct meson_clk_mpll_data *)clk->data; |
29 | } |
30 | |
31 | static long rate_from_params(unsigned long parent_rate, |
32 | unsigned int sdm, |
33 | unsigned int n2) |
34 | { |
35 | unsigned long divisor = (SDM_DEN * n2) + sdm; |
36 | |
37 | if (n2 < N2_MIN) |
38 | return -EINVAL; |
39 | |
40 | return DIV_ROUND_UP_ULL((u64)parent_rate * SDM_DEN, divisor); |
41 | } |
42 | |
43 | static void params_from_rate(unsigned long requested_rate, |
44 | unsigned long parent_rate, |
45 | unsigned int *sdm, |
46 | unsigned int *n2, |
47 | u8 flags) |
48 | { |
49 | uint64_t div = parent_rate; |
50 | uint64_t frac = do_div(div, requested_rate); |
51 | |
52 | frac *= SDM_DEN; |
53 | |
54 | if (flags & CLK_MESON_MPLL_ROUND_CLOSEST) |
55 | *sdm = DIV_ROUND_CLOSEST_ULL(frac, requested_rate); |
56 | else |
57 | *sdm = DIV_ROUND_UP_ULL(frac, requested_rate); |
58 | |
59 | if (*sdm == SDM_DEN) { |
60 | *sdm = 0; |
61 | div += 1; |
62 | } |
63 | |
64 | if (div < N2_MIN) { |
65 | *n2 = N2_MIN; |
66 | *sdm = 0; |
67 | } else if (div > N2_MAX) { |
68 | *n2 = N2_MAX; |
69 | *sdm = SDM_DEN - 1; |
70 | } else { |
71 | *n2 = div; |
72 | } |
73 | } |
74 | |
75 | static unsigned long mpll_recalc_rate(struct clk_hw *hw, |
76 | unsigned long parent_rate) |
77 | { |
78 | struct clk_regmap *clk = to_clk_regmap(hw); |
79 | struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); |
80 | unsigned int sdm, n2; |
81 | long rate; |
82 | |
83 | sdm = meson_parm_read(map: clk->map, p: &mpll->sdm); |
84 | n2 = meson_parm_read(map: clk->map, p: &mpll->n2); |
85 | |
86 | rate = rate_from_params(parent_rate, sdm, n2); |
87 | return rate < 0 ? 0 : rate; |
88 | } |
89 | |
90 | static int mpll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) |
91 | { |
92 | struct clk_regmap *clk = to_clk_regmap(hw); |
93 | struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); |
94 | unsigned int sdm, n2; |
95 | long rate; |
96 | |
97 | params_from_rate(requested_rate: req->rate, parent_rate: req->best_parent_rate, sdm: &sdm, n2: &n2, |
98 | flags: mpll->flags); |
99 | |
100 | rate = rate_from_params(parent_rate: req->best_parent_rate, sdm, n2); |
101 | if (rate < 0) |
102 | return rate; |
103 | |
104 | req->rate = rate; |
105 | return 0; |
106 | } |
107 | |
108 | static int mpll_set_rate(struct clk_hw *hw, |
109 | unsigned long rate, |
110 | unsigned long parent_rate) |
111 | { |
112 | struct clk_regmap *clk = to_clk_regmap(hw); |
113 | struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); |
114 | unsigned int sdm, n2; |
115 | unsigned long flags = 0; |
116 | |
117 | params_from_rate(requested_rate: rate, parent_rate, sdm: &sdm, n2: &n2, flags: mpll->flags); |
118 | |
119 | if (mpll->lock) |
120 | spin_lock_irqsave(mpll->lock, flags); |
121 | else |
122 | __acquire(mpll->lock); |
123 | |
124 | /* Set the fractional part */ |
125 | meson_parm_write(map: clk->map, p: &mpll->sdm, val: sdm); |
126 | |
127 | /* Set the integer divider part */ |
128 | meson_parm_write(map: clk->map, p: &mpll->n2, val: n2); |
129 | |
130 | if (mpll->lock) |
131 | spin_unlock_irqrestore(lock: mpll->lock, flags); |
132 | else |
133 | __release(mpll->lock); |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int mpll_init(struct clk_hw *hw) |
139 | { |
140 | struct clk_regmap *clk = to_clk_regmap(hw); |
141 | struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); |
142 | |
143 | if (mpll->init_count) |
144 | regmap_multi_reg_write(map: clk->map, regs: mpll->init_regs, |
145 | num_regs: mpll->init_count); |
146 | |
147 | /* Enable the fractional part */ |
148 | meson_parm_write(map: clk->map, p: &mpll->sdm_en, val: 1); |
149 | |
150 | /* Set spread spectrum if possible */ |
151 | if (MESON_PARM_APPLICABLE(&mpll->ssen)) { |
152 | unsigned int ss = |
153 | mpll->flags & CLK_MESON_MPLL_SPREAD_SPECTRUM ? 1 : 0; |
154 | meson_parm_write(map: clk->map, p: &mpll->ssen, val: ss); |
155 | } |
156 | |
157 | /* Set the magic misc bit if required */ |
158 | if (MESON_PARM_APPLICABLE(&mpll->misc)) |
159 | meson_parm_write(map: clk->map, p: &mpll->misc, val: 1); |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | const struct clk_ops meson_clk_mpll_ro_ops = { |
165 | .recalc_rate = mpll_recalc_rate, |
166 | .determine_rate = mpll_determine_rate, |
167 | }; |
168 | EXPORT_SYMBOL_GPL(meson_clk_mpll_ro_ops); |
169 | |
170 | const struct clk_ops meson_clk_mpll_ops = { |
171 | .recalc_rate = mpll_recalc_rate, |
172 | .determine_rate = mpll_determine_rate, |
173 | .set_rate = mpll_set_rate, |
174 | .init = mpll_init, |
175 | }; |
176 | EXPORT_SYMBOL_GPL(meson_clk_mpll_ops); |
177 | |
178 | MODULE_DESCRIPTION("Amlogic MPLL driver" ); |
179 | MODULE_AUTHOR("Michael Turquette <mturquette@baylibre.com>" ); |
180 | MODULE_LICENSE("GPL v2" ); |
181 | |