Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
[linux-2.6-block.git] / drivers / clk / meson / vclk.c
diff --git a/drivers/clk/meson/vclk.c b/drivers/clk/meson/vclk.c
new file mode 100644 (file)
index 0000000..e886df5
--- /dev/null
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 Neil Armstrong <neil.armstrong@linaro.org>
+ */
+
+#include <linux/module.h>
+#include "vclk.h"
+
+/* The VCLK gate has a supplementary reset bit to pulse after ungating */
+
+static inline struct meson_vclk_gate_data *
+clk_get_meson_vclk_gate_data(struct clk_regmap *clk)
+{
+       return (struct meson_vclk_gate_data *)clk->data;
+}
+
+static int meson_vclk_gate_enable(struct clk_hw *hw)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk);
+
+       meson_parm_write(clk->map, &vclk->enable, 1);
+
+       /* Do a reset pulse */
+       meson_parm_write(clk->map, &vclk->reset, 1);
+       meson_parm_write(clk->map, &vclk->reset, 0);
+
+       return 0;
+}
+
+static void meson_vclk_gate_disable(struct clk_hw *hw)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk);
+
+       meson_parm_write(clk->map, &vclk->enable, 0);
+}
+
+static int meson_vclk_gate_is_enabled(struct clk_hw *hw)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk);
+
+       return meson_parm_read(clk->map, &vclk->enable);
+}
+
+const struct clk_ops meson_vclk_gate_ops = {
+       .enable = meson_vclk_gate_enable,
+       .disable = meson_vclk_gate_disable,
+       .is_enabled = meson_vclk_gate_is_enabled,
+};
+EXPORT_SYMBOL_GPL(meson_vclk_gate_ops);
+
+/* The VCLK Divider has supplementary reset & enable bits */
+
+static inline struct meson_vclk_div_data *
+clk_get_meson_vclk_div_data(struct clk_regmap *clk)
+{
+       return (struct meson_vclk_div_data *)clk->data;
+}
+
+static unsigned long meson_vclk_div_recalc_rate(struct clk_hw *hw,
+                                               unsigned long prate)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+       return divider_recalc_rate(hw, prate, meson_parm_read(clk->map, &vclk->div),
+                                  vclk->table, vclk->flags, vclk->div.width);
+}
+
+static int meson_vclk_div_determine_rate(struct clk_hw *hw,
+                                        struct clk_rate_request *req)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+       return divider_determine_rate(hw, req, vclk->table, vclk->div.width,
+                                     vclk->flags);
+}
+
+static int meson_vclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+                                  unsigned long parent_rate)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+       int ret;
+
+       ret = divider_get_val(rate, parent_rate, vclk->table, vclk->div.width,
+                             vclk->flags);
+       if (ret < 0)
+               return ret;
+
+       meson_parm_write(clk->map, &vclk->div, ret);
+
+       return 0;
+};
+
+static int meson_vclk_div_enable(struct clk_hw *hw)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+       /* Unreset the divider when ungating */
+       meson_parm_write(clk->map, &vclk->reset, 0);
+       meson_parm_write(clk->map, &vclk->enable, 1);
+
+       return 0;
+}
+
+static void meson_vclk_div_disable(struct clk_hw *hw)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+       /* Reset the divider when gating */
+       meson_parm_write(clk->map, &vclk->enable, 0);
+       meson_parm_write(clk->map, &vclk->reset, 1);
+}
+
+static int meson_vclk_div_is_enabled(struct clk_hw *hw)
+{
+       struct clk_regmap *clk = to_clk_regmap(hw);
+       struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+       return meson_parm_read(clk->map, &vclk->enable);
+}
+
+const struct clk_ops meson_vclk_div_ops = {
+       .recalc_rate = meson_vclk_div_recalc_rate,
+       .determine_rate = meson_vclk_div_determine_rate,
+       .set_rate = meson_vclk_div_set_rate,
+       .enable = meson_vclk_div_enable,
+       .disable = meson_vclk_div_disable,
+       .is_enabled = meson_vclk_div_is_enabled,
+};
+EXPORT_SYMBOL_GPL(meson_vclk_div_ops);
+
+MODULE_DESCRIPTION("Amlogic vclk clock driver");
+MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
+MODULE_LICENSE("GPL");