123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- // SPDX-License-Identifier: (GPL-2.0 OR MIT)
- /*
- * Copyright (c) 2018 BayLibre, SAS.
- * Author: Jerome Brunet <jbrunet@baylibre.com>
- *
- * Sample clock generator divider:
- * This HW divider gates with value 0 but is otherwise a zero based divider:
- *
- * val >= 1
- * divider = val + 1
- *
- * The duty cycle may also be set for the LR clock variant. The duty cycle
- * ratio is:
- *
- * hi = [0 - val]
- * duty_cycle = (1 + hi) / (1 + val)
- */
- #include "clkc-audio.h"
- static inline struct meson_sclk_div_data *
- meson_sclk_div_data(struct clk_regmap *clk)
- {
- return (struct meson_sclk_div_data *)clk->data;
- }
- static int sclk_div_maxval(struct meson_sclk_div_data *sclk)
- {
- return (1 << sclk->div.width) - 1;
- }
- static int sclk_div_maxdiv(struct meson_sclk_div_data *sclk)
- {
- return sclk_div_maxval(sclk) + 1;
- }
- static int sclk_div_getdiv(struct clk_hw *hw, unsigned long rate,
- unsigned long prate, int maxdiv)
- {
- int div = DIV_ROUND_CLOSEST_ULL((u64)prate, rate);
- return clamp(div, 2, maxdiv);
- }
- static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate,
- unsigned long *prate,
- struct meson_sclk_div_data *sclk)
- {
- struct clk_hw *parent = clk_hw_get_parent(hw);
- int bestdiv = 0, i;
- unsigned long maxdiv, now, parent_now;
- unsigned long best = 0, best_parent = 0;
- if (!rate)
- rate = 1;
- maxdiv = sclk_div_maxdiv(sclk);
- if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT))
- return sclk_div_getdiv(hw, rate, *prate, maxdiv);
- /*
- * The maximum divider we can use without overflowing
- * unsigned long in rate * i below
- */
- maxdiv = min(ULONG_MAX / rate, maxdiv);
- for (i = 2; i <= maxdiv; i++) {
- /*
- * It's the most ideal case if the requested rate can be
- * divided from parent clock without needing to change
- * parent rate, so return the divider immediately.
- */
- if (rate * i == *prate)
- return i;
- parent_now = clk_hw_round_rate(parent, rate * i);
- now = DIV_ROUND_UP_ULL((u64)parent_now, i);
- if (abs(rate - now) < abs(rate - best)) {
- bestdiv = i;
- best = now;
- best_parent = parent_now;
- }
- }
- if (!bestdiv)
- bestdiv = sclk_div_maxdiv(sclk);
- else
- *prate = best_parent;
- return bestdiv;
- }
- static long sclk_div_round_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *prate)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- int div;
- div = sclk_div_bestdiv(hw, rate, prate, sclk);
- return DIV_ROUND_UP_ULL((u64)*prate, div);
- }
- static void sclk_apply_ratio(struct clk_regmap *clk,
- struct meson_sclk_div_data *sclk)
- {
- unsigned int hi = DIV_ROUND_CLOSEST(sclk->cached_div *
- sclk->cached_duty.num,
- sclk->cached_duty.den);
- if (hi)
- hi -= 1;
- meson_parm_write(clk->map, &sclk->hi, hi);
- }
- static int sclk_div_set_duty_cycle(struct clk_hw *hw,
- struct clk_duty *duty)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- if (MESON_PARM_APPLICABLE(&sclk->hi)) {
- memcpy(&sclk->cached_duty, duty, sizeof(*duty));
- sclk_apply_ratio(clk, sclk);
- }
- return 0;
- }
- static int sclk_div_get_duty_cycle(struct clk_hw *hw,
- struct clk_duty *duty)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- int hi;
- if (!MESON_PARM_APPLICABLE(&sclk->hi)) {
- duty->num = 1;
- duty->den = 2;
- return 0;
- }
- hi = meson_parm_read(clk->map, &sclk->hi);
- duty->num = hi + 1;
- duty->den = sclk->cached_div;
- return 0;
- }
- static void sclk_apply_divider(struct clk_regmap *clk,
- struct meson_sclk_div_data *sclk)
- {
- if (MESON_PARM_APPLICABLE(&sclk->hi))
- sclk_apply_ratio(clk, sclk);
- meson_parm_write(clk->map, &sclk->div, sclk->cached_div - 1);
- }
- static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long prate)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- unsigned long maxdiv = sclk_div_maxdiv(sclk);
- sclk->cached_div = sclk_div_getdiv(hw, rate, prate, maxdiv);
- if (clk_hw_is_enabled(hw))
- sclk_apply_divider(clk, sclk);
- return 0;
- }
- static unsigned long sclk_div_recalc_rate(struct clk_hw *hw,
- unsigned long prate)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- return DIV_ROUND_UP_ULL((u64)prate, sclk->cached_div);
- }
- static int sclk_div_enable(struct clk_hw *hw)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- sclk_apply_divider(clk, sclk);
- return 0;
- }
- static void sclk_div_disable(struct clk_hw *hw)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- meson_parm_write(clk->map, &sclk->div, 0);
- }
- static int sclk_div_is_enabled(struct clk_hw *hw)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- if (meson_parm_read(clk->map, &sclk->div))
- return 1;
- return 0;
- }
- static void sclk_div_init(struct clk_hw *hw)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- unsigned int val;
- val = meson_parm_read(clk->map, &sclk->div);
- /* if the divider is initially disabled, assume max */
- if (!val)
- sclk->cached_div = sclk_div_maxdiv(sclk);
- else
- sclk->cached_div = val + 1;
- sclk_div_get_duty_cycle(hw, &sclk->cached_duty);
- }
- const struct clk_ops meson_sclk_div_ops = {
- .recalc_rate = sclk_div_recalc_rate,
- .round_rate = sclk_div_round_rate,
- .set_rate = sclk_div_set_rate,
- .enable = sclk_div_enable,
- .disable = sclk_div_disable,
- .is_enabled = sclk_div_is_enabled,
- .get_duty_cycle = sclk_div_get_duty_cycle,
- .set_duty_cycle = sclk_div_set_duty_cycle,
- .init = sclk_div_init,
- };
- EXPORT_SYMBOL_GPL(meson_sclk_div_ops);
|