1 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
3 * Copyright (c) 2016 AmLogic, Inc.
4 * Author: Michael Turquette <mturquette@baylibre.com>
8 * MultiPhase Locked Loops are outputs from a PLL with additional frequency
9 * scaling capabilities. MPLL rates are calculated as:
11 * f(N2_integer, SDM_IN ) = 2.0G/(N2_integer + SDM_IN/16384)
14 #include <linux/clk-provider.h>
15 #include <linux/module.h>
16 #include <linux/spinlock.h>
18 #include "clk-regmap.h"
25 static inline struct meson_clk_mpll_data
*
26 meson_clk_mpll_data(struct clk_regmap
*clk
)
28 return (struct meson_clk_mpll_data
*)clk
->data
;
31 static long rate_from_params(unsigned long parent_rate
,
35 unsigned long divisor
= (SDM_DEN
* n2
) + sdm
;
40 return DIV_ROUND_UP_ULL((u64
)parent_rate
* SDM_DEN
, divisor
);
43 static void params_from_rate(unsigned long requested_rate
,
44 unsigned long parent_rate
,
49 uint64_t div
= parent_rate
;
50 uint64_t frac
= do_div(div
, requested_rate
);
54 if (flags
& CLK_MESON_MPLL_ROUND_CLOSEST
)
55 *sdm
= DIV_ROUND_CLOSEST_ULL(frac
, requested_rate
);
57 *sdm
= DIV_ROUND_UP_ULL(frac
, requested_rate
);
59 if (*sdm
== SDM_DEN
) {
67 } else if (div
> N2_MAX
) {
75 static unsigned long mpll_recalc_rate(struct clk_hw
*hw
,
76 unsigned long parent_rate
)
78 struct clk_regmap
*clk
= to_clk_regmap(hw
);
79 struct meson_clk_mpll_data
*mpll
= meson_clk_mpll_data(clk
);
83 sdm
= meson_parm_read(clk
->map
, &mpll
->sdm
);
84 n2
= meson_parm_read(clk
->map
, &mpll
->n2
);
86 rate
= rate_from_params(parent_rate
, sdm
, n2
);
87 return rate
< 0 ? 0 : rate
;
90 static long mpll_round_rate(struct clk_hw
*hw
,
92 unsigned long *parent_rate
)
94 struct clk_regmap
*clk
= to_clk_regmap(hw
);
95 struct meson_clk_mpll_data
*mpll
= meson_clk_mpll_data(clk
);
98 params_from_rate(rate
, *parent_rate
, &sdm
, &n2
, mpll
->flags
);
99 return rate_from_params(*parent_rate
, sdm
, n2
);
102 static int mpll_set_rate(struct clk_hw
*hw
,
104 unsigned long parent_rate
)
106 struct clk_regmap
*clk
= to_clk_regmap(hw
);
107 struct meson_clk_mpll_data
*mpll
= meson_clk_mpll_data(clk
);
108 unsigned int sdm
, n2
;
109 unsigned long flags
= 0;
111 params_from_rate(rate
, parent_rate
, &sdm
, &n2
, mpll
->flags
);
114 spin_lock_irqsave(mpll
->lock
, flags
);
116 __acquire(mpll
->lock
);
118 /* Set the fractional part */
119 meson_parm_write(clk
->map
, &mpll
->sdm
, sdm
);
121 /* Set the integer divider part */
122 meson_parm_write(clk
->map
, &mpll
->n2
, n2
);
125 spin_unlock_irqrestore(mpll
->lock
, flags
);
127 __release(mpll
->lock
);
132 static int mpll_init(struct clk_hw
*hw
)
134 struct clk_regmap
*clk
= to_clk_regmap(hw
);
135 struct meson_clk_mpll_data
*mpll
= meson_clk_mpll_data(clk
);
137 if (mpll
->init_count
)
138 regmap_multi_reg_write(clk
->map
, mpll
->init_regs
,
141 /* Enable the fractional part */
142 meson_parm_write(clk
->map
, &mpll
->sdm_en
, 1);
144 /* Set spread spectrum if possible */
145 if (MESON_PARM_APPLICABLE(&mpll
->ssen
)) {
147 mpll
->flags
& CLK_MESON_MPLL_SPREAD_SPECTRUM
? 1 : 0;
148 meson_parm_write(clk
->map
, &mpll
->ssen
, ss
);
151 /* Set the magic misc bit if required */
152 if (MESON_PARM_APPLICABLE(&mpll
->misc
))
153 meson_parm_write(clk
->map
, &mpll
->misc
, 1);
158 const struct clk_ops meson_clk_mpll_ro_ops
= {
159 .recalc_rate
= mpll_recalc_rate
,
160 .round_rate
= mpll_round_rate
,
162 EXPORT_SYMBOL_GPL(meson_clk_mpll_ro_ops
);
164 const struct clk_ops meson_clk_mpll_ops
= {
165 .recalc_rate
= mpll_recalc_rate
,
166 .round_rate
= mpll_round_rate
,
167 .set_rate
= mpll_set_rate
,
170 EXPORT_SYMBOL_GPL(meson_clk_mpll_ops
);
172 MODULE_DESCRIPTION("Amlogic MPLL driver");
173 MODULE_AUTHOR("Michael Turquette <mturquette@baylibre.com>");
174 MODULE_LICENSE("GPL v2");