|
@@ -0,0 +1,243 @@
|
|
|
+// 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);
|