On Thu 03 Aug 2023 at 14:03, Neil Armstrong <neil.armstrong@xxxxxxxxxx> wrote:
The VCLK and VCLK_DIV clocks have supplementary bits.
The VCLK has a "SOFT RESET" bit to toggle after the whole
VCLK sub-tree rate has been set, this is implemented in
the gate enable callback.
The VCLK_DIV clocks as enable and reset bits used to disable
and reset the divider, associated with CLK_SET_RATE_GATE it ensures
the rate is set while the divider is disabled and in reset mode.
The VCLK_DIV enable bit isn't implemented as a gate since it's part
of the divider logic and vendor does this exact sequence to ensure
the divider is correctly set.
Unless there is reason, I'd prefer if this driver was using 'struct
parm', like the rest of amlogic custom clock drivers, for consistency.
Signed-off-by: Neil Armstrong <neil.armstrong@xxxxxxxxxx>
---
drivers/clk/meson/Kconfig | 5 ++
drivers/clk/meson/Makefile | 1 +
drivers/clk/meson/vclk.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/meson/vclk.h | 68 +++++++++++++++++++++
4 files changed, 220 insertions(+)
diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig
index 135da8f2d0b1..83f629515e96 100644
--- a/drivers/clk/meson/Kconfig
+++ b/drivers/clk/meson/Kconfig
@@ -30,6 +30,10 @@ config COMMON_CLK_MESON_VID_PLL_DIV
tristate
select COMMON_CLK_MESON_REGMAP
+config COMMON_CLK_MESON_VCLK
+ tristate
+ select COMMON_CLK_MESON_REGMAP
+
config COMMON_CLK_MESON_CLKC_UTILS
tristate
@@ -140,6 +144,7 @@ config COMMON_CLK_G12A
select COMMON_CLK_MESON_EE_CLKC
select COMMON_CLK_MESON_CPU_DYNDIV
select COMMON_CLK_MESON_VID_PLL_DIV
+ select COMMON_CLK_MESON_VCLK
select MFD_SYSCON
help
Support for the clock controller on Amlogic S905D2, S905X2 and S905Y2
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
index cd961cc4f4db..6efeb8c7bd2a 100644
--- a/drivers/clk/meson/Makefile
+++ b/drivers/clk/meson/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_COMMON_CLK_MESON_PLL) += clk-pll.o
obj-$(CONFIG_COMMON_CLK_MESON_REGMAP) += clk-regmap.o
obj-$(CONFIG_COMMON_CLK_MESON_SCLK_DIV) += sclk-div.o
obj-$(CONFIG_COMMON_CLK_MESON_VID_PLL_DIV) += vid-pll-div.o
+obj-$(CONFIG_COMMON_CLK_MESON_VCLK) += vclk.o
# Amlogic Clock controllers
diff --git a/drivers/clk/meson/vclk.c b/drivers/clk/meson/vclk.c
new file mode 100644
index 000000000000..0df84403b17f
--- /dev/null
+++ b/drivers/clk/meson/vclk.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Neil Armstrong <neil.armstrong@xxxxxxxxxx>
+ */
+
+#include <linux/module.h>
+#include "vclk.h"
+
+/* The VCLK gate has a supplementary reset bit to pulse after ungating */
+
+static int clk_regmap_vclk_enable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_data *vclk = clk_get_regmap_vclk_data(clk);
+
+ regmap_set_bits(clk->map, vclk->offset, BIT(vclk->enable_bit_idx));
+
+ /* Do a reset pulse */
+ regmap_set_bits(clk->map, vclk->offset, BIT(vclk->reset_bit_idx));
+ regmap_clear_bits(clk->map, vclk->offset, BIT(vclk->reset_bit_idx));
+
+ return 0;
+}
+
+static void clk_regmap_vclk_disable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_data *vclk = clk_get_regmap_vclk_data(clk);
+
+ regmap_clear_bits(clk->map, vclk->offset, BIT(vclk->enable_bit_idx));
+}
+
+static int clk_regmap_vclk_is_enabled(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_data *vclk = clk_get_regmap_vclk_data(clk);
+ unsigned int val;
+
+ regmap_read(clk->map, vclk->offset, &val);
+
+ return val & BIT(vclk->enable_bit_idx) ? 1 : 0;
+}
+
+const struct clk_ops clk_regmap_vclk_ops = {
+ .enable = clk_regmap_vclk_enable,
+ .disable = clk_regmap_vclk_disable,
+ .is_enabled = clk_regmap_vclk_is_enabled,
+};
+EXPORT_SYMBOL_GPL(clk_regmap_vclk_ops);
+
+/* The VCLK Divider has supplementary reset & enable bits */
+
+static unsigned long clk_regmap_vclk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_div_data *vclk = clk_get_regmap_vclk_div_data(clk);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(clk->map, vclk->offset, &val);
+ if (ret)
+ /* Gives a hint that something is wrong */
+ return 0;
+
+ val >>= vclk->shift;
+ val &= clk_div_mask(vclk->width);
+
+ return divider_recalc_rate(hw, prate, val, vclk->table, vclk->flags,
+ vclk->width);
+}
+
+static int clk_regmap_vclk_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_div_data *vclk = clk_get_regmap_vclk_div_data(clk);
+
+ return divider_determine_rate(hw, req, vclk->table, vclk->width,
+ vclk->flags);
+}
+
+static int clk_regmap_vclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_div_data *vclk = clk_get_regmap_vclk_div_data(clk);
+ unsigned int val;
+ int ret;
+
+ ret = divider_get_val(rate, parent_rate, vclk->table, vclk->width,
+ vclk->flags);
+ if (ret < 0)
+ return ret;
+
+ val = (unsigned int)ret << vclk->shift;
+ return regmap_update_bits(clk->map, vclk->offset,
+ clk_div_mask(vclk->width) << vclk->shift, val);
+};
+
+static int clk_regmap_vclk_div_enable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_div_data *vclk = clk_get_regmap_vclk_div_data(clk);
+
+ /* Unreset the divider when ungating */
+ regmap_clear_bits(clk->map, vclk->offset, BIT(vclk->reset_bit_idx));
+
+ return regmap_set_bits(clk->map, vclk->offset, BIT(vclk->enable_bit_idx));
+}
+
+static void clk_regmap_vclk_div_disable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_div_data *vclk = clk_get_regmap_vclk_div_data(clk);
+
+ /* Reset the divider when gating */
+ regmap_clear_bits(clk->map, vclk->offset, BIT(vclk->enable_bit_idx));
+
+ regmap_set_bits(clk->map, vclk->offset, BIT(vclk->reset_bit_idx));
+}
+
+static int clk_regmap_vclk_div_is_enabled(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct clk_regmap_vclk_div_data *vclk = clk_get_regmap_vclk_div_data(clk);
+ unsigned int val;
+
+ regmap_read(clk->map, vclk->offset, &val);
+
+ return val & BIT(vclk->enable_bit_idx) ? 1 : 0;
+}
+
+const struct clk_ops clk_regmap_vclk_div_ops = {
+ .recalc_rate = clk_regmap_vclk_div_recalc_rate,
+ .determine_rate = clk_regmap_vclk_div_determine_rate,
+ .set_rate = clk_regmap_vclk_div_set_rate,
+ .enable = clk_regmap_vclk_div_enable,
+ .disable = clk_regmap_vclk_div_disable,
+ .is_enabled = clk_regmap_vclk_div_is_enabled,
+};
+EXPORT_SYMBOL_GPL(clk_regmap_vclk_div_ops);
+
+MODULE_DESCRIPTION("Amlogic vclk clock driver");
+MODULE_AUTHOR("Neil Armstrong <neil.armstrong@xxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/meson/vclk.h b/drivers/clk/meson/vclk.h
new file mode 100644
index 000000000000..90786552a7f3
--- /dev/null
+++ b/drivers/clk/meson/vclk.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023 Neil Armstrong <neil.armstrong@xxxxxxxxxx>
+ */
+
+#ifndef __VCLK_H
+#define __VCLK_H
+
+#include "clk-regmap.h"
+
+/**
+ * struct clk_regmap_vclk_data - vclk regmap backed specific data
+ *
+ * @offset: offset of the register controlling gate
+ * @enable_bit_idx: single bit controlling vclk enable
+ * @reset_bit_idx: single bit controlling vclk reset
+ * @flags: hardware-specific flags
+ *
+ * Flags:
+ * Same as clk_gate except CLK_GATE_HIWORD_MASK which is ignored
+ */
+struct clk_regmap_vclk_data {
+ unsigned int offset;
+ u8 enable_bit_idx;
+ u8 reset_bit_idx;
+ u8 flags;
+};
+
+static inline struct clk_regmap_vclk_data *
+clk_get_regmap_vclk_data(struct clk_regmap *clk)
+{
+ return (struct clk_regmap_vclk_data *)clk->data;
+}
+
+extern const struct clk_ops clk_regmap_vclk_ops;
+
+/**
+ * struct clk_regmap_vclk_div_data - vclk_div regmap back specific data
+ *
+ * @offset: offset of the register controlling the divider
+ * @shift: shift to the divider bit field
+ * @width: width of the divider bit field
+ * @enable_bit_idx: single bit controlling vclk divider enable
+ * @reset_bit_idx: single bit controlling vclk divider reset
+ * @table: array of value/divider pairs, last entry should have div = 0
+ *
+ * Flags:
+ * Same as clk_divider except CLK_DIVIDER_HIWORD_MASK which is ignored
+ */
+struct clk_regmap_vclk_div_data {
+ unsigned int offset;
+ u8 shift;
+ u8 width;
+ u8 enable_bit_idx;
+ u8 reset_bit_idx;
+ const struct clk_div_table *table;
+ u8 flags;
+};
+
+static inline struct clk_regmap_vclk_div_data *
+clk_get_regmap_vclk_div_data(struct clk_regmap *clk)
+{
+ return (struct clk_regmap_vclk_div_data *)clk->data;
+}
+
+extern const struct clk_ops clk_regmap_vclk_div_ops;
+
+#endif /* __VCLK_H */