hc
2023-03-21 4b55d97acc464242bcd6a8ae77b8ff37c22dec58
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/*
 * Copyright (c) 2017 Rockchip Electronics Co. Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
 
#include "clk-regmap.h"
 
#define PLLCON_OFFSET(x)    (x * 4)
 
#define PLL_BYPASS(x)            HIWORD_UPDATE(x, 15, 15)
#define PLL_BYPASS_MASK            BIT(15)
#define PLL_BYPASS_SHIFT        15
#define PLL_POSTDIV1(x)            HIWORD_UPDATE(x, 14, 12)
#define PLL_POSTDIV1_MASK        GENMASK(14, 12)
#define PLL_POSTDIV1_SHIFT        12
#define PLL_FBDIV(x)            HIWORD_UPDATE(x, 11, 0)
#define PLL_FBDIV_MASK            GENMASK(11, 0)
#define PLL_FBDIV_SHIFT            0
 
#define PLL_POSTDIV2(x)            HIWORD_UPDATE(x, 8, 6)
#define PLL_POSTDIV2_MASK        GENMASK(8, 6)
#define PLL_POSTDIV2_SHIFT        6
#define PLL_REFDIV(x)            HIWORD_UPDATE(x, 5, 0)
#define PLL_REFDIV_MASK            GENMASK(5, 0)
#define PLL_REFDIV_SHIFT        0
 
#define PLL_FOUT_4PHASE_CLK_POWER_DOWN    BIT(27)
#define PLL_FOUT_VCO_CLK_POWER_DOWN    BIT(26)
#define PLL_FOUT_POST_DIV_POWER_DOWN    BIT(25)
#define PLL_DAC_POWER_DOWN        BIT(24)
#define PLL_FRAC(x)            UPDATE(x, 23, 0)
#define PLL_FRAC_MASK            GENMASK(23, 0)
#define PLL_FRAC_SHIFT            0
 
#define MIN_FREF_RATE        10000000UL
#define MAX_FREF_RATE        800000000UL
#define MIN_FREFDIV_RATE    1000000UL
#define MAX_FREFDIV_RATE    40000000UL
#define MIN_FVCO_RATE        400000000UL
#define MAX_FVCO_RATE        1600000000UL
#define MIN_FOUTPOSTDIV_RATE    8000000UL
#define MAX_FOUTPOSTDIV_RATE    1600000000UL
 
struct clk_regmap_pll {
   struct clk_hw hw;
   struct device *dev;
   struct regmap *regmap;
   unsigned int reg;
   u8 pd_shift;
   u8 dsmpd_shift;
   u8 lock_shift;
};
 
#define to_clk_regmap_pll(_hw)    container_of(_hw, struct clk_regmap_pll, hw)
 
static unsigned long
clk_regmap_pll_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
   struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
   unsigned int postdiv1, fbdiv, dsmpd, postdiv2, refdiv, frac, bypass;
   unsigned int con0, con1, con2;
   u64 foutvco, foutpostdiv;
 
   regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(0), &con0);
   regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1);
   regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(2), &con2);
 
   bypass = (con0 & PLL_BYPASS_MASK) >> PLL_BYPASS_SHIFT;
   postdiv1 = (con0 & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
   fbdiv = (con0 & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
   dsmpd = (con1 & BIT(pll->dsmpd_shift)) >> pll->dsmpd_shift;
   postdiv2 = (con1 & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
   refdiv = (con1 & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
   frac = (con2 & PLL_FRAC_MASK) >> PLL_FRAC_SHIFT;
 
   if (bypass)
       return prate;
 
   foutvco = prate * fbdiv;
   do_div(foutvco, refdiv);
 
   if (!dsmpd) {
       u64 frac_rate = (u64)prate * frac;
 
       do_div(frac_rate, refdiv);
       foutvco += frac_rate >> 24;
   }
 
   foutpostdiv = foutvco;
   do_div(foutpostdiv, postdiv1);
   do_div(foutpostdiv, postdiv2);
 
   return foutpostdiv;
}
 
static long clk_pll_round_rate(unsigned long fin, unsigned long fout,
                  u8 *refdiv, u16 *fbdiv,
                  u8 *postdiv1, u8 *postdiv2,
                  u32 *frac, u8 *dsmpd, u8 *bypass)
{
   u8 min_refdiv, max_refdiv, postdiv;
   u8 _dsmpd = 1, _postdiv1 = 0, _postdiv2 = 0, _refdiv = 0;
   u16 _fbdiv = 0;
   u32 _frac = 0;
   u64 foutvco, foutpostdiv;
 
   /*
    * FREF : 10MHz ~ 800MHz
    * FREFDIV : 1MHz ~ 40MHz
    * FOUTVCO : 400MHz ~ 1.6GHz
    * FOUTPOSTDIV : 8MHz ~ 1.6GHz
    */
   if (fin < MIN_FREF_RATE || fin > MAX_FREF_RATE)
       return -EINVAL;
 
   if (fout < MIN_FOUTPOSTDIV_RATE || fout > MAX_FOUTPOSTDIV_RATE)
       return -EINVAL;
 
   if (fin == fout) {
       if (bypass)
           *bypass = true;
       return fin;
   }
 
   min_refdiv = DIV_ROUND_UP(fin, MAX_FREFDIV_RATE);
   max_refdiv = fin / MIN_FREFDIV_RATE;
   if (max_refdiv > 64)
       max_refdiv = 64;
 
   if (fout < MIN_FVCO_RATE) {
       postdiv = DIV_ROUND_UP_ULL(MIN_FVCO_RATE, fout);
 
       for (_postdiv2 = 1; _postdiv2 < 8; _postdiv2++) {
           if (postdiv % _postdiv2)
               continue;
 
           _postdiv1 = postdiv / _postdiv2;
 
           if (_postdiv1 > 0 && _postdiv1 < 8)
               break;
       }
 
       if (_postdiv2 > 7)
           return -EINVAL;
 
       fout *= _postdiv1 * _postdiv2;
   } else {
       _postdiv1 = 1;
       _postdiv2 = 1;
   }
 
   for (_refdiv = min_refdiv; _refdiv <= max_refdiv; _refdiv++) {
       u64 tmp, frac_rate;
 
       if (fin % _refdiv)
           continue;
 
       tmp = (u64)fout * _refdiv;
       do_div(tmp, fin);
       _fbdiv = tmp;
       if (_fbdiv < 10 || _fbdiv > 1600)
           continue;
 
       tmp = (u64)_fbdiv * fin;
       do_div(tmp, _refdiv);
       if (fout < MIN_FVCO_RATE || fout > MAX_FVCO_RATE)
           continue;
 
       frac_rate = fout - tmp;
 
       if (frac_rate) {
           tmp = (u64)frac_rate * _refdiv;
           tmp <<= 24;
           do_div(tmp, fin);
           _frac = tmp;
           _dsmpd = 0;
       }
 
       break;
   }
 
   /*
    * If DSMPD = 1 (DSM is disabled, "integer mode")
    * FOUTVCO = FREF / REFDIV * FBDIV
    * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
    *
    * If DSMPD = 0 (DSM is enabled, "fractional mode")
    * FOUTVCO = FREF / REFDIV * (FBDIV + FRAC / 2^24)
    * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
    */
   foutvco = fin * _fbdiv;
   do_div(foutvco, _refdiv);
 
   if (!_dsmpd) {
       u64 frac_rate = (u64)fin * _frac;
 
       do_div(frac_rate, _refdiv);
       foutvco += frac_rate >> 24;
   }
 
   foutpostdiv = foutvco;
   do_div(foutpostdiv, _postdiv1);
   do_div(foutpostdiv, _postdiv2);
 
   if (refdiv)
       *refdiv = _refdiv;
   if (fbdiv)
       *fbdiv = _fbdiv;
   if (postdiv1)
       *postdiv1 = _postdiv1;
   if (postdiv2)
       *postdiv2 = _postdiv2;
   if (frac)
       *frac = _frac;
   if (dsmpd)
       *dsmpd = _dsmpd;
   if (bypass)
       *bypass = false;
 
   return (unsigned long)foutpostdiv;
}
 
static long
clk_regmap_pll_round_rate(struct clk_hw *hw, unsigned long drate,
             unsigned long *prate)
{
   struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
   long rate;
 
   rate = clk_pll_round_rate(*prate, drate, NULL, NULL, NULL, NULL, NULL,
                 NULL, NULL);
 
   dev_dbg(pll->dev, "%s: prate=%ld, drate=%ld, rate=%ld\n",
       clk_hw_get_name(hw), *prate, drate, rate);
 
   return rate;
}
 
static int
clk_regmap_pll_set_rate(struct clk_hw *hw, unsigned long drate,
           unsigned long prate)
{
   struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
   u8 refdiv, postdiv1, postdiv2, dsmpd, bypass;
   u16 fbdiv;
   u32 frac;
   long rate;
 
   rate = clk_pll_round_rate(prate, drate, &refdiv, &fbdiv, &postdiv1,
                 &postdiv2, &frac, &dsmpd, &bypass);
   if (rate < 0)
       return rate;
 
   dev_dbg(pll->dev, "%s: rate=%ld, bypass=%d\n",
       clk_hw_get_name(hw), drate, bypass);
 
   if (bypass) {
       regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0),
                PLL_BYPASS(1));
   } else {
       regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0),
                PLL_BYPASS(0) | PLL_POSTDIV1(postdiv1) |
                PLL_FBDIV(fbdiv));
       regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
                HIWORD_UPDATE(dsmpd, pll->dsmpd_shift, pll->dsmpd_shift) |
                PLL_POSTDIV2(postdiv2) | PLL_REFDIV(refdiv));
       regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(2),
                PLL_FRAC(frac));
 
       dev_dbg(pll->dev, "refdiv=%d, fbdiv=%d, frac=%d\n",
           refdiv, fbdiv, frac);
       dev_dbg(pll->dev, "postdiv1=%d, postdiv2=%d\n",
           postdiv1, postdiv2);
   }
 
   return 0;
}
 
static int clk_regmap_pll_prepare(struct clk_hw *hw)
{
   struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
   u32 v;
   int ret;
 
   regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
            HIWORD_UPDATE(0, pll->pd_shift, pll->pd_shift));
 
   ret = regmap_read_poll_timeout(pll->regmap,
                      pll->reg + PLLCON_OFFSET(1),
                      v, v & BIT(pll->lock_shift), 50, 50000);
   if (ret)
       dev_err(pll->dev, "%s is not lock\n", clk_hw_get_name(hw));
 
   return 0;
}
 
static void clk_regmap_pll_unprepare(struct clk_hw *hw)
{
   struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
 
   regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
            HIWORD_UPDATE(1, pll->pd_shift, pll->pd_shift));
}
 
static int clk_regmap_pll_is_prepared(struct clk_hw *hw)
{
   struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
   unsigned int con1;
 
   regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1);
 
   return !(con1 & BIT(pll->pd_shift));
}
 
static const struct clk_ops clk_regmap_pll_ops = {
   .recalc_rate = clk_regmap_pll_recalc_rate,
   .round_rate = clk_regmap_pll_round_rate,
   .set_rate = clk_regmap_pll_set_rate,
   .prepare = clk_regmap_pll_prepare,
   .unprepare = clk_regmap_pll_unprepare,
   .is_prepared = clk_regmap_pll_is_prepared,
};
 
struct clk *
devm_clk_regmap_register_pll(struct device *dev, const char *name,
                const char *parent_name,
                struct regmap *regmap, u32 reg, u8 pd_shift,
                u8 dsmpd_shift, u8 lock_shift,
                unsigned long flags)
{
   struct clk_regmap_pll *pll;
   struct clk_init_data init = {};
 
   pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
   if (!pll)
       return ERR_PTR(-ENOMEM);
 
   init.name = name;
   init.ops = &clk_regmap_pll_ops;
   init.flags = flags;
   init.parent_names = (parent_name ? &parent_name : NULL);
   init.num_parents = (parent_name ? 1 : 0);
 
   pll->dev = dev;
   pll->regmap = regmap;
   pll->reg = reg;
   pll->pd_shift = pd_shift;
   pll->dsmpd_shift = dsmpd_shift;
   pll->lock_shift = lock_shift;
   pll->hw.init = &init;
 
   return devm_clk_register(dev, &pll->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_pll);