From: Conor Dooley <conor.dooley@microchip.com>
Implement regmap-backed copies of gate and divider clocks by replacing
the iomem pointer to the clock registers with a regmap and offset
within.
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
---
drivers/clk/Kconfig | 8 +
drivers/clk/Makefile | 2 +
drivers/clk/clk-divider-regmap.c | 270 +++++++++++++++++++++++++++++++
drivers/clk/clk-gate-regmap.c | 253 +++++++++++++++++++++++++++++
drivers/clk/clk-gate.c | 5 +-
include/linux/clk-provider.h | 120 ++++++++++++++
6 files changed, 656 insertions(+), 2 deletions(-)
create mode 100644 drivers/clk/clk-divider-regmap.c
create mode 100644 drivers/clk/clk-gate-regmap.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 713573b6c86c..c700b6ef4f34 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -33,6 +33,14 @@ menuconfig COMMON_CLK
if COMMON_CLK
+config COMMON_CLK_DIVIDER_REGMAP
+ bool
+ select REGMAP
+
+config COMMON_CLK_GATE_REGMAP
+ bool
+ select REGMAP
+
config COMMON_CLK_WM831X
tristate "Clock driver for WM831x/2x PMICs"
depends on MFD_WM831X
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index bf4bd45adc3a..c57f72af5db9 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -20,11 +20,13 @@ clk-test-y := clk_test.o \
kunit_clk_assigned_rates_zero_consumer.dtbo.o \
kunit_clk_parent_data_test.dtbo.o
obj-$(CONFIG_COMMON_CLK) += clk-divider.o
+obj-$(CONFIG_COMMON_CLK_DIVIDER_REGMAP) += clk-divider-regmap.o
obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o
obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o
obj-$(CONFIG_CLK_FIXED_RATE_KUNIT_TEST) += clk-fixed-rate-test.o
clk-fixed-rate-test-y := clk-fixed-rate_test.o kunit_clk_fixed_rate_test.dtbo.o
obj-$(CONFIG_COMMON_CLK) += clk-gate.o
+obj-$(CONFIG_COMMON_CLK_GATE_REGMAP) += clk-gate-regmap.o
obj-$(CONFIG_CLK_GATE_KUNIT_TEST) += clk-gate_test.o
obj-$(CONFIG_COMMON_CLK) += clk-multiplier.o
obj-$(CONFIG_COMMON_CLK) += clk-mux.o
diff --git a/drivers/clk/clk-divider-regmap.c b/drivers/clk/clk-divider-regmap.c
new file mode 100644
index 000000000000..2906130a2b47
--- /dev/null
+++ b/drivers/clk/clk-divider-regmap.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+
+static inline u32 clk_div_regmap_readl(struct clk_divider_regmap *divider)
+{
+ u32 val;
+
+ regmap_read(divider->regmap, divider->map_offset, &val);
+
+ return val;
+}
+
+static inline void clk_div_regmap_writel(struct clk_divider_regmap *divider, u32 val)
+{
+ regmap_write(divider->regmap, divider->map_offset, val);
+
+}
+
+static unsigned long clk_divider_regmap_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
+ unsigned int val;
+
+ val = clk_div_regmap_readl(divider) >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_recalc_rate(hw, parent_rate, val, divider->table,
+ divider->flags, divider->width);
+}
+
+static long clk_divider_regmap_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
+
+ /* if read only, just return current value */
+ if (divider->flags & CLK_DIVIDER_READ_ONLY) {
+ u32 val;
+
+ val = clk_div_regmap_readl(divider) >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_ro_round_rate(hw, rate, prate, divider->table,
+ divider->width, divider->flags,
+ val);
+ }
+
+ return divider_round_rate(hw, rate, prate, divider->table,
+ divider->width, divider->flags);
+}
+
+static int clk_divider_regmap_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
+
+ /* if read only, just return current value */
+ if (divider->flags & CLK_DIVIDER_READ_ONLY) {
+ u32 val;
+
+ val = clk_div_regmap_readl(divider) >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_ro_determine_rate(hw, req, divider->table,
+ divider->width,
+ divider->flags, val);
+ }
+
+ return divider_determine_rate(hw, req, divider->table, divider->width,
+ divider->flags);
+}
+
+static int clk_divider_regmap_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
+ int value;
+ unsigned long flags = 0;
+ u32 val;
+
+ value = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, divider->flags);
+ if (value < 0)
+ return value;
+
+ if (divider->lock)
+ spin_lock_irqsave(divider->lock, flags);
+ else
+ __acquire(divider->lock);
+
+ if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
+ val = clk_div_mask(divider->width) << (divider->shift + 16);
+ } else {
+ val = clk_div_regmap_readl(divider);
+ val &= ~(clk_div_mask(divider->width) << divider->shift);
+ }
+ val |= (u32)value << divider->shift;
+ clk_div_regmap_writel(divider, val);
+
+ if (divider->lock)
+ spin_unlock_irqrestore(divider->lock, flags);
+ else
+ __release(divider->lock);
+
+ return 0;
+}
+
+const struct clk_ops clk_divider_regmap_ops = {
+ .recalc_rate = clk_divider_regmap_recalc_rate,
+ .round_rate = clk_divider_regmap_round_rate,
+ .determine_rate = clk_divider_regmap_determine_rate,
+ .set_rate = clk_divider_regmap_set_rate,
+};
+EXPORT_SYMBOL_GPL(clk_divider_regmap_ops);
+
+const struct clk_ops clk_divider_regmap_ro_ops = {
+ .recalc_rate = clk_divider_regmap_recalc_rate,
+ .round_rate = clk_divider_regmap_round_rate,
+ .determine_rate = clk_divider_regmap_determine_rate,
+};
+EXPORT_SYMBOL_GPL(clk_divider_regmap_ro_ops);
+
+struct clk_hw *__clk_hw_register_divider_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data, unsigned long flags,
+ struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ spinlock_t *lock)
+{
+ struct clk_divider_regmap *div;
+ struct clk_hw *hw;
+ struct clk_init_data init = {};
+ int ret;
+
+ if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
+ if (width + shift > 16) {
+ pr_warn("divider value exceeds LOWORD field\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ /* allocate the divider */
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
+ init.ops = &clk_divider_regmap_ro_ops;
+ else
+ init.ops = &clk_divider_regmap_ops;
+ init.flags = flags;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.parent_hws = parent_hw ? &parent_hw : NULL;
+ init.parent_data = parent_data;
+ if (parent_name || parent_hw || parent_data)
+ init.num_parents = 1;
+ else
+ init.num_parents = 0;
+
+ /* struct clk_divider assignments */
+ div->regmap = regmap;
+ div->map_offset = map_offset;
+ div->shift = shift;
+ div->width = width;
+ div->flags = clk_divider_flags;
+ div->lock = lock;
+ div->hw.init = &init;
+ div->table = table;
+
+ /* register the clock */
+ hw = &div->hw;
+ ret = clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(div);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+EXPORT_SYMBOL_GPL(__clk_hw_register_divider_regmap);
+
+struct clk *clk_register_divider_regmap_table(struct device *dev, const char *name,
+ const char *parent_name, unsigned long flags,
+ struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ spinlock_t *lock)
+{
+ struct clk_hw *hw;
+
+ hw = __clk_hw_register_divider_regmap(dev, NULL, name, parent_name, NULL,
+ NULL, flags, regmap, map_offset,
+ shift, width, clk_divider_flags,
+ table, lock);
+ if (IS_ERR(hw))
+ return ERR_CAST(hw);
+ return hw->clk;
+}
+EXPORT_SYMBOL_GPL(clk_register_divider_regmap_table);
+
+void clk_unregister_divider_regmap(struct clk *clk)
+{
+ struct clk_divider_regmap *div;
+ struct clk_hw *hw;
+
+ hw = __clk_get_hw(clk);
+ if (!hw)
+ return;
+
+ div = to_clk_divider_regmap(hw);
+
+ clk_unregister(clk);
+ kfree(div);
+}
+EXPORT_SYMBOL_GPL(clk_unregister_divider_regmap);
+
+/**
+ * clk_hw_unregister_divider_regmap - unregister a clk divider
+ * @hw: hardware-specific clock data to unregister
+ */
+void clk_hw_unregister_divider_regmap(struct clk_hw *hw)
+{
+ struct clk_divider_regmap *div;
+
+ div = to_clk_divider_regmap(hw);
+
+ clk_hw_unregister(hw);
+ kfree(div);
+}
+EXPORT_SYMBOL_GPL(clk_hw_unregister_divider_regmap);
+
+static void devm_clk_hw_release_divider_regmap(struct device *dev, void *res)
+{
+ clk_hw_unregister_divider_regmap(*(struct clk_hw **)res);
+}
+
+struct clk_hw *__devm_clk_hw_register_divider_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data, unsigned long flags,
+ struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ spinlock_t *lock)
+{
+ struct clk_hw **ptr, *hw;
+
+ ptr = devres_alloc(devm_clk_hw_release_divider_regmap, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ hw = __clk_hw_register_divider_regmap(dev, np, name, parent_name, parent_hw,
+ parent_data, flags, regmap, map_offset,
+ shift, width, clk_divider_flags, table,
+ lock);
+
+ if (!IS_ERR(hw)) {
+ *ptr = hw;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return hw;
+}
+EXPORT_SYMBOL_GPL(__devm_clk_hw_register_divider_regmap);
diff --git a/drivers/clk/clk-gate-regmap.c b/drivers/clk/clk-gate-regmap.c
new file mode 100644
index 000000000000..cf9e48407971
--- /dev/null
+++ b/drivers/clk/clk-gate-regmap.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/string.h>
+
+/**
+ * DOC: basic gatable clock which can gate and ungate its output
+ *
+ * Traits of this clock:
+ * prepare - clk_(un)prepare only ensures parent is (un)prepared
+ * enable - clk_enable and clk_disable are functional & control gating
+ * rate - inherits rate from parent. No clk_set_rate support
+ * parent - fixed parent. No clk_set_parent support
+ */
+
+static inline u32 clk_gate_regmap_readl(struct clk_gate_regmap *gate)
+{
+ u32 val;
+
+ regmap_read(gate->map, gate->map_offset, &val);
+
+ return val;
+}
+
+static inline void clk_gate_regmap_writel(struct clk_gate_regmap *gate, u32 val)
+{
+ regmap_write(gate->map, gate->map_offset, val);
+
+}
+
+/*
+ * It works on following logic:
+ *
+ * For enabling clock, enable = 1
+ * set2dis = 1 -> clear bit -> set = 0
+ * set2dis = 0 -> set bit -> set = 1
+ *
+ * For disabling clock, enable = 0
+ * set2dis = 1 -> set bit -> set = 1
+ * set2dis = 0 -> clear bit -> set = 0
+ *
+ * So, result is always: enable xor set2dis.
+ */
+static void clk_gate_regmap_endisable(struct clk_hw *hw, int enable)
+{
+ struct clk_gate_regmap *gate = to_clk_gate_regmap(hw);
+ int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
+ unsigned long flags;
+ u32 reg;
+
+ set ^= enable;
+
+ if (gate->lock)
+ spin_lock_irqsave(gate->lock, flags);
+ else
+ __acquire(gate->lock);
+
+ if (gate->flags & CLK_GATE_HIWORD_MASK) {
+ reg = BIT(gate->bit_idx + 16);
+ if (set)
+ reg |= BIT(gate->bit_idx);
+ } else {
+ reg = clk_gate_regmap_readl(gate);
+
+ if (set)
+ reg |= BIT(gate->bit_idx);
+ else
+ reg &= ~BIT(gate->bit_idx);
+ }
+
+ clk_gate_regmap_writel(gate, reg);
+
+ if (gate->lock)
+ spin_unlock_irqrestore(gate->lock, flags);
+ else
+ __release(gate->lock);
+}
+
+static int clk_gate_regmap_enable(struct clk_hw *hw)
+{
+ clk_gate_regmap_endisable(hw, 1);
+
+ return 0;
+}
+
+static void clk_gate_regmap_disable(struct clk_hw *hw)
+{
+ clk_gate_regmap_endisable(hw, 0);
+}
+
+int clk_gate_regmap_is_enabled(struct clk_hw *hw)
+{
+ u32 reg;
+ struct clk_gate_regmap *gate = to_clk_gate_regmap(hw);
+
+ reg = clk_gate_regmap_readl(gate);
+
+ /* if a set bit disables this clk, flip it before masking */
+ if (gate->flags & CLK_GATE_SET_TO_DISABLE)
+ reg ^= BIT(gate->bit_idx);
+
+ reg &= BIT(gate->bit_idx);
+
+ return reg ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(clk_gate_regmap_is_enabled);
+
+const struct clk_ops clk_gate_regmap_ops = {
+ .enable = clk_gate_regmap_enable,
+ .disable = clk_gate_regmap_disable,
+ .is_enabled = clk_gate_regmap_is_enabled,
+};
+EXPORT_SYMBOL_GPL(clk_gate_regmap_ops);
+
+struct clk_hw *__clk_hw_register_gate_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data,
+ unsigned long flags,
+ struct regmap *map, u8 map_offset, u8 bit_idx,
+ u8 clk_gate_flags, spinlock_t *lock)
+{
+ struct clk_gate_regmap *gate;
+ struct clk_hw *hw;
+ struct clk_init_data init = {};
+ int ret = -EINVAL;
+
+ if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
+ if (bit_idx > 15) {
+ pr_err("gate bit exceeds LOWORD field\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ /* allocate the gate */
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &clk_gate_regmap_ops;
+ init.flags = flags;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.parent_hws = parent_hw ? &parent_hw : NULL;
+ init.parent_data = parent_data;
+ if (parent_name || parent_hw || parent_data)
+ init.num_parents = 1;
+ else
+ init.num_parents = 0;
+
+ /* struct clk_gate_regmap assignments */
+ gate->map = map;
+ gate->map_offset = map_offset;
+ gate->bit_idx = bit_idx;
+ gate->flags = clk_gate_flags;
+ gate->lock = lock;
+ gate->hw.init = &init;
+
+ hw = &gate->hw;
+ if (dev || !np)
+ ret = clk_hw_register(dev, hw);
+ else if (np)
+ ret = of_clk_hw_register(np, hw);
+ if (ret) {
+ kfree(gate);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+
+}
+EXPORT_SYMBOL_GPL(__clk_hw_register_gate_regmap);
+
+struct clk *clk_register_gate_regmap(struct device *dev, const char *name,
+ const char *parent_name, unsigned long flags, struct regmap *map,
+ u8 map_offset, u8 bit_idx, u8 clk_gate_flags, spinlock_t *lock)
+{
+ struct clk_hw *hw;
+
+ hw = __clk_hw_register_gate_regmap(dev, NULL, name, parent_name, NULL,
+ NULL, flags, map, map_offset, bit_idx,
+ clk_gate_flags, lock);
+ if (IS_ERR(hw))
+ return ERR_CAST(hw);
+ return hw->clk;
+}
+EXPORT_SYMBOL_GPL(clk_register_gate_regmap);
+
+void clk_unregister_gate_regmap(struct clk *clk)
+{
+ struct clk_gate_regmap *gate;
+ struct clk_hw *hw;
+
+ hw = __clk_get_hw(clk);
+ if (!hw)
+ return;
+
+ gate = to_clk_gate_regmap(hw);
+
+ clk_unregister(clk);
+ kfree(gate);
+}
+EXPORT_SYMBOL_GPL(clk_unregister_gate_regmap);
+
+void clk_hw_unregister_gate_regmap(struct clk_hw *hw)
+{
+ struct clk_gate_regmap *gate;
+
+ gate = to_clk_gate_regmap(hw);
+
+ clk_hw_unregister(hw);
+ kfree(gate);
+}
+EXPORT_SYMBOL_GPL(clk_hw_unregister_gate_regmap);
+
+static void devm_clk_hw_release_gate_regmap(struct device *dev, void *res)
+{
+ clk_hw_unregister_gate_regmap(*(struct clk_hw **)res);
+}
+
+struct clk_hw *__devm_clk_hw_register_gate_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data,
+ unsigned long flags, struct regmap *map,
+ u8 map_offset, u8 bit_idx,
+ u8 clk_gate_flags, spinlock_t *lock)
+{
+ struct clk_hw **ptr, *hw;
+
+ ptr = devres_alloc(devm_clk_hw_release_gate_regmap, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ hw = __clk_hw_register_gate_regmap(dev, np, name, parent_name, parent_hw,
+ parent_data, flags, map, map_offset,
+ bit_idx, clk_gate_flags, lock);
+
+ if (!IS_ERR(hw)) {
+ *ptr = hw;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return hw;
+}
+EXPORT_SYMBOL_GPL(__devm_clk_hw_register_gate_regmap);
diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c
index 68e585a02fd9..1e3f21a249ca 100644
--- a/drivers/clk/clk-gate.c
+++ b/drivers/clk/clk-gate.c
@@ -34,10 +34,11 @@ static inline u32 clk_gate_readl(struct clk_gate *gate)
static inline void clk_gate_writel(struct clk_gate *gate, u32 val)
{
- if (gate->flags & CLK_GATE_BIG_ENDIAN)
+ if (gate->flags & CLK_GATE_BIG_ENDIAN) {
iowrite32be(val, gate->reg);
- else
+ } else {
writel(val, gate->reg);
+ }
}
/*
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 2e6e603b7493..6f5cf6670b48 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -8,6 +8,7 @@
#include <linux/of.h>
#include <linux/of_clk.h>
+#include <linux/regmap.h>
/*
* flags used across common struct clk. these flags should only affect the
@@ -526,6 +527,7 @@ void of_fixed_clk_setup(struct device_node *np);
struct clk_gate {
struct clk_hw hw;
void __iomem *reg;
+ u8 map_offset;
u8 bit_idx;
u8 flags;
spinlock_t *lock;
@@ -538,6 +540,37 @@ struct clk_gate {
#define CLK_GATE_BIG_ENDIAN BIT(2)
extern const struct clk_ops clk_gate_ops;
+
+#ifdef CONFIG_COMMON_CLK_GATE_REGMAP
+/**
+ * struct clk_gate_regmap - gating clock via regmap
+ *
+ * @hw: handle between common and hardware-specific interfaces
+ * @map: regmap controlling gate
+ * @map_offset: register offset within the regmap controlling gate
+ * @bit_idx: single bit controlling gate
+ * @flags: hardware-specific flags
+ * @lock: register lock
+ *
+ * Clock which can gate its output. Implements .enable & .disable
+ *
+ * Flags:
+ * See clk_gate
+ */
+struct clk_gate_regmap {
+ struct clk_hw hw;
+ struct regmap *map;
+ u8 map_offset;
+ u8 bit_idx;
+ u8 flags;
+ spinlock_t *lock;
+};
+
+#define to_clk_gate_regmap(_hw) container_of(_hw, struct clk_gate_regmap, hw)
+
+extern const struct clk_ops clk_gate_regmap_ops;
+#endif
+
struct clk_hw *__clk_hw_register_gate(struct device *dev,
struct device_node *np, const char *name,
const char *parent_name, const struct clk_hw *parent_hw,
@@ -663,6 +696,31 @@ void clk_unregister_gate(struct clk *clk);
void clk_hw_unregister_gate(struct clk_hw *hw);
int clk_gate_is_enabled(struct clk_hw *hw);
+#ifdef CONFIG_COMMON_CLK_GATE_REGMAP
+struct clk_hw *__clk_hw_register_gate_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data,
+ unsigned long flags,
+ struct regmap *map, u8 map_offset, u8 bit_idx,
+ u8 clk_gate_flags, spinlock_t *lock);
+struct clk_hw *__devm_clk_hw_register_gate_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data,
+ unsigned long flags,
+ struct regmap *map, u8 map_offset, u8 bit_idx,
+ u8 clk_gate_flags, spinlock_t *lock);
+struct clk *clk_register_gate_regmap(struct device *dev, const char *name,
+ const char *parent_name, unsigned long flags,
+ struct regmap *map, u8 map_offset, u8 bit_idx,
+ u8 clk_gate_flags, spinlock_t *lock);
+
+void clk_unregister_gate_regmap(struct clk *clk);
+void clk_hw_unregister_gate_regmap(struct clk_hw *hw);
+int clk_gate_regmap_is_enabled(struct clk_hw *hw);
+#endif
+
struct clk_div_table {
unsigned int val;
unsigned int div;
@@ -736,6 +794,41 @@ struct clk_divider {
extern const struct clk_ops clk_divider_ops;
extern const struct clk_ops clk_divider_ro_ops;
+#ifdef CONFIG_COMMON_CLK_DIVIDER_REGMAP
+/**
+ * struct clk_divider_regmap - adjustable divider clock via regmap
+ *
+ * @hw: handle between common and hardware-specific interfaces
+ * @map: regmap containing the divider
+ * @map_offset: register offset within the regmap containing the divider
+ * @shift: shift to the divider bit field
+ * @width: width of the divider bit field
+ * @table: array of value/divider pairs, last entry should have div = 0
+ * @lock: register lock
+ *
+ * Clock with an adjustable divider affecting its output frequency. Implements
+ * .recalc_rate, .set_rate and .round_rate
+ *
+ * @flags:
+ * See clk_divider
+ */
+struct clk_divider_regmap {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u8 map_offset;
+ u8 shift;
+ u8 width;
+ u8 flags;
+ const struct clk_div_table *table;
+ spinlock_t *lock;
+};
+
+#define to_clk_divider_regmap(_hw) container_of(_hw, struct clk_divider_regmap, hw)
+
+extern const struct clk_ops clk_divider_regmap_ops;
+extern const struct clk_ops clk_divider_regmap_ro_ops;
+#endif
+
unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
unsigned int val, const struct clk_div_table *table,
unsigned long flags, unsigned long width);
@@ -972,6 +1065,33 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name,
void clk_unregister_divider(struct clk *clk);
void clk_hw_unregister_divider(struct clk_hw *hw);
+#ifdef CONFIG_COMMON_CLK_DIVIDER_REGMAP
+struct clk_hw *__clk_hw_register_divider_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data, unsigned long flags,
+ struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ spinlock_t *lock);
+
+struct clk_hw *__devm_clk_hw_register_divider_regmap(struct device *dev,
+ struct device_node *np, const char *name,
+ const char *parent_name, const struct clk_hw *parent_hw,
+ const struct clk_parent_data *parent_data, unsigned long flags,
+ struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ spinlock_t *lock);
+
+struct clk *clk_register_divider_regmap_table(struct device *dev,
+ const char *name, const char *parent_name, unsigned long flags,
+ struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ spinlock_t *lock);
+
+void clk_unregister_divider_regmap(struct clk *clk);
+void clk_hw_unregister_divider_regmap(struct clk_hw *hw);
+#endif
+
/**
* struct clk_mux - multiplexer clock
*
--
2.45.2
Stephen,
On Fri, Mar 21, 2025 at 05:22:41PM +0000, Conor Dooley wrote:
> From: Conor Dooley <conor.dooley@microchip.com>
>
> Implement regmap-backed copies of gate and divider clocks by replacing
> the iomem pointer to the clock registers with a regmap and offset
> within.
Is this the sort of implementation you were looking for in your comments
on the previous version of this series?
(https://lore.kernel.org/linux-clk/20250220-iciness-mobilize-94a027ff52ce@spud/)
Cheers,
Conor.
>
> Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> ---
> drivers/clk/Kconfig | 8 +
> drivers/clk/Makefile | 2 +
> drivers/clk/clk-divider-regmap.c | 270 +++++++++++++++++++++++++++++++
> drivers/clk/clk-gate-regmap.c | 253 +++++++++++++++++++++++++++++
> drivers/clk/clk-gate.c | 5 +-
> include/linux/clk-provider.h | 120 ++++++++++++++
> 6 files changed, 656 insertions(+), 2 deletions(-)
> create mode 100644 drivers/clk/clk-divider-regmap.c
> create mode 100644 drivers/clk/clk-gate-regmap.c
>
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 713573b6c86c..c700b6ef4f34 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -33,6 +33,14 @@ menuconfig COMMON_CLK
>
> if COMMON_CLK
>
> +config COMMON_CLK_DIVIDER_REGMAP
> + bool
> + select REGMAP
> +
> +config COMMON_CLK_GATE_REGMAP
> + bool
> + select REGMAP
> +
> config COMMON_CLK_WM831X
> tristate "Clock driver for WM831x/2x PMICs"
> depends on MFD_WM831X
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index bf4bd45adc3a..c57f72af5db9 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -20,11 +20,13 @@ clk-test-y := clk_test.o \
> kunit_clk_assigned_rates_zero_consumer.dtbo.o \
> kunit_clk_parent_data_test.dtbo.o
> obj-$(CONFIG_COMMON_CLK) += clk-divider.o
> +obj-$(CONFIG_COMMON_CLK_DIVIDER_REGMAP) += clk-divider-regmap.o
> obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o
> obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o
> obj-$(CONFIG_CLK_FIXED_RATE_KUNIT_TEST) += clk-fixed-rate-test.o
> clk-fixed-rate-test-y := clk-fixed-rate_test.o kunit_clk_fixed_rate_test.dtbo.o
> obj-$(CONFIG_COMMON_CLK) += clk-gate.o
> +obj-$(CONFIG_COMMON_CLK_GATE_REGMAP) += clk-gate-regmap.o
> obj-$(CONFIG_CLK_GATE_KUNIT_TEST) += clk-gate_test.o
> obj-$(CONFIG_COMMON_CLK) += clk-multiplier.o
> obj-$(CONFIG_COMMON_CLK) += clk-mux.o
> diff --git a/drivers/clk/clk-divider-regmap.c b/drivers/clk/clk-divider-regmap.c
> new file mode 100644
> index 000000000000..2906130a2b47
> --- /dev/null
> +++ b/drivers/clk/clk-divider-regmap.c
> @@ -0,0 +1,270 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/slab.h>
> +
> +static inline u32 clk_div_regmap_readl(struct clk_divider_regmap *divider)
> +{
> + u32 val;
> +
> + regmap_read(divider->regmap, divider->map_offset, &val);
> +
> + return val;
> +}
> +
> +static inline void clk_div_regmap_writel(struct clk_divider_regmap *divider, u32 val)
> +{
> + regmap_write(divider->regmap, divider->map_offset, val);
> +
> +}
> +
> +static unsigned long clk_divider_regmap_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> + unsigned int val;
> +
> + val = clk_div_regmap_readl(divider) >> divider->shift;
> + val &= clk_div_mask(divider->width);
> +
> + return divider_recalc_rate(hw, parent_rate, val, divider->table,
> + divider->flags, divider->width);
> +}
> +
> +static long clk_divider_regmap_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *prate)
> +{
> + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> +
> + /* if read only, just return current value */
> + if (divider->flags & CLK_DIVIDER_READ_ONLY) {
> + u32 val;
> +
> + val = clk_div_regmap_readl(divider) >> divider->shift;
> + val &= clk_div_mask(divider->width);
> +
> + return divider_ro_round_rate(hw, rate, prate, divider->table,
> + divider->width, divider->flags,
> + val);
> + }
> +
> + return divider_round_rate(hw, rate, prate, divider->table,
> + divider->width, divider->flags);
> +}
> +
> +static int clk_divider_regmap_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> +
> + /* if read only, just return current value */
> + if (divider->flags & CLK_DIVIDER_READ_ONLY) {
> + u32 val;
> +
> + val = clk_div_regmap_readl(divider) >> divider->shift;
> + val &= clk_div_mask(divider->width);
> +
> + return divider_ro_determine_rate(hw, req, divider->table,
> + divider->width,
> + divider->flags, val);
> + }
> +
> + return divider_determine_rate(hw, req, divider->table, divider->width,
> + divider->flags);
> +}
> +
> +static int clk_divider_regmap_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> + int value;
> + unsigned long flags = 0;
> + u32 val;
> +
> + value = divider_get_val(rate, parent_rate, divider->table,
> + divider->width, divider->flags);
> + if (value < 0)
> + return value;
> +
> + if (divider->lock)
> + spin_lock_irqsave(divider->lock, flags);
> + else
> + __acquire(divider->lock);
> +
> + if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
> + val = clk_div_mask(divider->width) << (divider->shift + 16);
> + } else {
> + val = clk_div_regmap_readl(divider);
> + val &= ~(clk_div_mask(divider->width) << divider->shift);
> + }
> + val |= (u32)value << divider->shift;
> + clk_div_regmap_writel(divider, val);
> +
> + if (divider->lock)
> + spin_unlock_irqrestore(divider->lock, flags);
> + else
> + __release(divider->lock);
> +
> + return 0;
> +}
> +
> +const struct clk_ops clk_divider_regmap_ops = {
> + .recalc_rate = clk_divider_regmap_recalc_rate,
> + .round_rate = clk_divider_regmap_round_rate,
> + .determine_rate = clk_divider_regmap_determine_rate,
> + .set_rate = clk_divider_regmap_set_rate,
> +};
> +EXPORT_SYMBOL_GPL(clk_divider_regmap_ops);
> +
> +const struct clk_ops clk_divider_regmap_ro_ops = {
> + .recalc_rate = clk_divider_regmap_recalc_rate,
> + .round_rate = clk_divider_regmap_round_rate,
> + .determine_rate = clk_divider_regmap_determine_rate,
> +};
> +EXPORT_SYMBOL_GPL(clk_divider_regmap_ro_ops);
> +
> +struct clk_hw *__clk_hw_register_divider_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data, unsigned long flags,
> + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table,
> + spinlock_t *lock)
> +{
> + struct clk_divider_regmap *div;
> + struct clk_hw *hw;
> + struct clk_init_data init = {};
> + int ret;
> +
> + if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> + if (width + shift > 16) {
> + pr_warn("divider value exceeds LOWORD field\n");
> + return ERR_PTR(-EINVAL);
> + }
> + }
> +
> + /* allocate the divider */
> + div = kzalloc(sizeof(*div), GFP_KERNEL);
> + if (!div)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
> + init.ops = &clk_divider_regmap_ro_ops;
> + else
> + init.ops = &clk_divider_regmap_ops;
> + init.flags = flags;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.parent_hws = parent_hw ? &parent_hw : NULL;
> + init.parent_data = parent_data;
> + if (parent_name || parent_hw || parent_data)
> + init.num_parents = 1;
> + else
> + init.num_parents = 0;
> +
> + /* struct clk_divider assignments */
> + div->regmap = regmap;
> + div->map_offset = map_offset;
> + div->shift = shift;
> + div->width = width;
> + div->flags = clk_divider_flags;
> + div->lock = lock;
> + div->hw.init = &init;
> + div->table = table;
> +
> + /* register the clock */
> + hw = &div->hw;
> + ret = clk_hw_register(dev, hw);
> + if (ret) {
> + kfree(div);
> + hw = ERR_PTR(ret);
> + }
> +
> + return hw;
> +}
> +EXPORT_SYMBOL_GPL(__clk_hw_register_divider_regmap);
> +
> +struct clk *clk_register_divider_regmap_table(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table,
> + spinlock_t *lock)
> +{
> + struct clk_hw *hw;
> +
> + hw = __clk_hw_register_divider_regmap(dev, NULL, name, parent_name, NULL,
> + NULL, flags, regmap, map_offset,
> + shift, width, clk_divider_flags,
> + table, lock);
> + if (IS_ERR(hw))
> + return ERR_CAST(hw);
> + return hw->clk;
> +}
> +EXPORT_SYMBOL_GPL(clk_register_divider_regmap_table);
> +
> +void clk_unregister_divider_regmap(struct clk *clk)
> +{
> + struct clk_divider_regmap *div;
> + struct clk_hw *hw;
> +
> + hw = __clk_get_hw(clk);
> + if (!hw)
> + return;
> +
> + div = to_clk_divider_regmap(hw);
> +
> + clk_unregister(clk);
> + kfree(div);
> +}
> +EXPORT_SYMBOL_GPL(clk_unregister_divider_regmap);
> +
> +/**
> + * clk_hw_unregister_divider_regmap - unregister a clk divider
> + * @hw: hardware-specific clock data to unregister
> + */
> +void clk_hw_unregister_divider_regmap(struct clk_hw *hw)
> +{
> + struct clk_divider_regmap *div;
> +
> + div = to_clk_divider_regmap(hw);
> +
> + clk_hw_unregister(hw);
> + kfree(div);
> +}
> +EXPORT_SYMBOL_GPL(clk_hw_unregister_divider_regmap);
> +
> +static void devm_clk_hw_release_divider_regmap(struct device *dev, void *res)
> +{
> + clk_hw_unregister_divider_regmap(*(struct clk_hw **)res);
> +}
> +
> +struct clk_hw *__devm_clk_hw_register_divider_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data, unsigned long flags,
> + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table,
> + spinlock_t *lock)
> +{
> + struct clk_hw **ptr, *hw;
> +
> + ptr = devres_alloc(devm_clk_hw_release_divider_regmap, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return ERR_PTR(-ENOMEM);
> +
> + hw = __clk_hw_register_divider_regmap(dev, np, name, parent_name, parent_hw,
> + parent_data, flags, regmap, map_offset,
> + shift, width, clk_divider_flags, table,
> + lock);
> +
> + if (!IS_ERR(hw)) {
> + *ptr = hw;
> + devres_add(dev, ptr);
> + } else {
> + devres_free(ptr);
> + }
> +
> + return hw;
> +}
> +EXPORT_SYMBOL_GPL(__devm_clk_hw_register_divider_regmap);
> diff --git a/drivers/clk/clk-gate-regmap.c b/drivers/clk/clk-gate-regmap.c
> new file mode 100644
> index 000000000000..cf9e48407971
> --- /dev/null
> +++ b/drivers/clk/clk-gate-regmap.c
> @@ -0,0 +1,253 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/string.h>
> +
> +/**
> + * DOC: basic gatable clock which can gate and ungate its output
> + *
> + * Traits of this clock:
> + * prepare - clk_(un)prepare only ensures parent is (un)prepared
> + * enable - clk_enable and clk_disable are functional & control gating
> + * rate - inherits rate from parent. No clk_set_rate support
> + * parent - fixed parent. No clk_set_parent support
> + */
> +
> +static inline u32 clk_gate_regmap_readl(struct clk_gate_regmap *gate)
> +{
> + u32 val;
> +
> + regmap_read(gate->map, gate->map_offset, &val);
> +
> + return val;
> +}
> +
> +static inline void clk_gate_regmap_writel(struct clk_gate_regmap *gate, u32 val)
> +{
> + regmap_write(gate->map, gate->map_offset, val);
> +
> +}
> +
> +/*
> + * It works on following logic:
> + *
> + * For enabling clock, enable = 1
> + * set2dis = 1 -> clear bit -> set = 0
> + * set2dis = 0 -> set bit -> set = 1
> + *
> + * For disabling clock, enable = 0
> + * set2dis = 1 -> set bit -> set = 1
> + * set2dis = 0 -> clear bit -> set = 0
> + *
> + * So, result is always: enable xor set2dis.
> + */
> +static void clk_gate_regmap_endisable(struct clk_hw *hw, int enable)
> +{
> + struct clk_gate_regmap *gate = to_clk_gate_regmap(hw);
> + int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
> + unsigned long flags;
> + u32 reg;
> +
> + set ^= enable;
> +
> + if (gate->lock)
> + spin_lock_irqsave(gate->lock, flags);
> + else
> + __acquire(gate->lock);
> +
> + if (gate->flags & CLK_GATE_HIWORD_MASK) {
> + reg = BIT(gate->bit_idx + 16);
> + if (set)
> + reg |= BIT(gate->bit_idx);
> + } else {
> + reg = clk_gate_regmap_readl(gate);
> +
> + if (set)
> + reg |= BIT(gate->bit_idx);
> + else
> + reg &= ~BIT(gate->bit_idx);
> + }
> +
> + clk_gate_regmap_writel(gate, reg);
> +
> + if (gate->lock)
> + spin_unlock_irqrestore(gate->lock, flags);
> + else
> + __release(gate->lock);
> +}
> +
> +static int clk_gate_regmap_enable(struct clk_hw *hw)
> +{
> + clk_gate_regmap_endisable(hw, 1);
> +
> + return 0;
> +}
> +
> +static void clk_gate_regmap_disable(struct clk_hw *hw)
> +{
> + clk_gate_regmap_endisable(hw, 0);
> +}
> +
> +int clk_gate_regmap_is_enabled(struct clk_hw *hw)
> +{
> + u32 reg;
> + struct clk_gate_regmap *gate = to_clk_gate_regmap(hw);
> +
> + reg = clk_gate_regmap_readl(gate);
> +
> + /* if a set bit disables this clk, flip it before masking */
> + if (gate->flags & CLK_GATE_SET_TO_DISABLE)
> + reg ^= BIT(gate->bit_idx);
> +
> + reg &= BIT(gate->bit_idx);
> +
> + return reg ? 1 : 0;
> +}
> +EXPORT_SYMBOL_GPL(clk_gate_regmap_is_enabled);
> +
> +const struct clk_ops clk_gate_regmap_ops = {
> + .enable = clk_gate_regmap_enable,
> + .disable = clk_gate_regmap_disable,
> + .is_enabled = clk_gate_regmap_is_enabled,
> +};
> +EXPORT_SYMBOL_GPL(clk_gate_regmap_ops);
> +
> +struct clk_hw *__clk_hw_register_gate_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data,
> + unsigned long flags,
> + struct regmap *map, u8 map_offset, u8 bit_idx,
> + u8 clk_gate_flags, spinlock_t *lock)
> +{
> + struct clk_gate_regmap *gate;
> + struct clk_hw *hw;
> + struct clk_init_data init = {};
> + int ret = -EINVAL;
> +
> + if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
> + if (bit_idx > 15) {
> + pr_err("gate bit exceeds LOWORD field\n");
> + return ERR_PTR(-EINVAL);
> + }
> + }
> +
> + /* allocate the gate */
> + gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> + if (!gate)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &clk_gate_regmap_ops;
> + init.flags = flags;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.parent_hws = parent_hw ? &parent_hw : NULL;
> + init.parent_data = parent_data;
> + if (parent_name || parent_hw || parent_data)
> + init.num_parents = 1;
> + else
> + init.num_parents = 0;
> +
> + /* struct clk_gate_regmap assignments */
> + gate->map = map;
> + gate->map_offset = map_offset;
> + gate->bit_idx = bit_idx;
> + gate->flags = clk_gate_flags;
> + gate->lock = lock;
> + gate->hw.init = &init;
> +
> + hw = &gate->hw;
> + if (dev || !np)
> + ret = clk_hw_register(dev, hw);
> + else if (np)
> + ret = of_clk_hw_register(np, hw);
> + if (ret) {
> + kfree(gate);
> + hw = ERR_PTR(ret);
> + }
> +
> + return hw;
> +
> +}
> +EXPORT_SYMBOL_GPL(__clk_hw_register_gate_regmap);
> +
> +struct clk *clk_register_gate_regmap(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags, struct regmap *map,
> + u8 map_offset, u8 bit_idx, u8 clk_gate_flags, spinlock_t *lock)
> +{
> + struct clk_hw *hw;
> +
> + hw = __clk_hw_register_gate_regmap(dev, NULL, name, parent_name, NULL,
> + NULL, flags, map, map_offset, bit_idx,
> + clk_gate_flags, lock);
> + if (IS_ERR(hw))
> + return ERR_CAST(hw);
> + return hw->clk;
> +}
> +EXPORT_SYMBOL_GPL(clk_register_gate_regmap);
> +
> +void clk_unregister_gate_regmap(struct clk *clk)
> +{
> + struct clk_gate_regmap *gate;
> + struct clk_hw *hw;
> +
> + hw = __clk_get_hw(clk);
> + if (!hw)
> + return;
> +
> + gate = to_clk_gate_regmap(hw);
> +
> + clk_unregister(clk);
> + kfree(gate);
> +}
> +EXPORT_SYMBOL_GPL(clk_unregister_gate_regmap);
> +
> +void clk_hw_unregister_gate_regmap(struct clk_hw *hw)
> +{
> + struct clk_gate_regmap *gate;
> +
> + gate = to_clk_gate_regmap(hw);
> +
> + clk_hw_unregister(hw);
> + kfree(gate);
> +}
> +EXPORT_SYMBOL_GPL(clk_hw_unregister_gate_regmap);
> +
> +static void devm_clk_hw_release_gate_regmap(struct device *dev, void *res)
> +{
> + clk_hw_unregister_gate_regmap(*(struct clk_hw **)res);
> +}
> +
> +struct clk_hw *__devm_clk_hw_register_gate_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data,
> + unsigned long flags, struct regmap *map,
> + u8 map_offset, u8 bit_idx,
> + u8 clk_gate_flags, spinlock_t *lock)
> +{
> + struct clk_hw **ptr, *hw;
> +
> + ptr = devres_alloc(devm_clk_hw_release_gate_regmap, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return ERR_PTR(-ENOMEM);
> +
> + hw = __clk_hw_register_gate_regmap(dev, np, name, parent_name, parent_hw,
> + parent_data, flags, map, map_offset,
> + bit_idx, clk_gate_flags, lock);
> +
> + if (!IS_ERR(hw)) {
> + *ptr = hw;
> + devres_add(dev, ptr);
> + } else {
> + devres_free(ptr);
> + }
> +
> + return hw;
> +}
> +EXPORT_SYMBOL_GPL(__devm_clk_hw_register_gate_regmap);
> diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c
> index 68e585a02fd9..1e3f21a249ca 100644
> --- a/drivers/clk/clk-gate.c
> +++ b/drivers/clk/clk-gate.c
> @@ -34,10 +34,11 @@ static inline u32 clk_gate_readl(struct clk_gate *gate)
>
> static inline void clk_gate_writel(struct clk_gate *gate, u32 val)
> {
> - if (gate->flags & CLK_GATE_BIG_ENDIAN)
> + if (gate->flags & CLK_GATE_BIG_ENDIAN) {
> iowrite32be(val, gate->reg);
> - else
> + } else {
> writel(val, gate->reg);
> + }
> }
>
> /*
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> index 2e6e603b7493..6f5cf6670b48 100644
> --- a/include/linux/clk-provider.h
> +++ b/include/linux/clk-provider.h
> @@ -8,6 +8,7 @@
>
> #include <linux/of.h>
> #include <linux/of_clk.h>
> +#include <linux/regmap.h>
>
> /*
> * flags used across common struct clk. these flags should only affect the
> @@ -526,6 +527,7 @@ void of_fixed_clk_setup(struct device_node *np);
> struct clk_gate {
> struct clk_hw hw;
> void __iomem *reg;
> + u8 map_offset;
> u8 bit_idx;
> u8 flags;
> spinlock_t *lock;
> @@ -538,6 +540,37 @@ struct clk_gate {
> #define CLK_GATE_BIG_ENDIAN BIT(2)
>
> extern const struct clk_ops clk_gate_ops;
> +
> +#ifdef CONFIG_COMMON_CLK_GATE_REGMAP
> +/**
> + * struct clk_gate_regmap - gating clock via regmap
> + *
> + * @hw: handle between common and hardware-specific interfaces
> + * @map: regmap controlling gate
> + * @map_offset: register offset within the regmap controlling gate
> + * @bit_idx: single bit controlling gate
> + * @flags: hardware-specific flags
> + * @lock: register lock
> + *
> + * Clock which can gate its output. Implements .enable & .disable
> + *
> + * Flags:
> + * See clk_gate
> + */
> +struct clk_gate_regmap {
> + struct clk_hw hw;
> + struct regmap *map;
> + u8 map_offset;
> + u8 bit_idx;
> + u8 flags;
> + spinlock_t *lock;
> +};
> +
> +#define to_clk_gate_regmap(_hw) container_of(_hw, struct clk_gate_regmap, hw)
> +
> +extern const struct clk_ops clk_gate_regmap_ops;
> +#endif
> +
> struct clk_hw *__clk_hw_register_gate(struct device *dev,
> struct device_node *np, const char *name,
> const char *parent_name, const struct clk_hw *parent_hw,
> @@ -663,6 +696,31 @@ void clk_unregister_gate(struct clk *clk);
> void clk_hw_unregister_gate(struct clk_hw *hw);
> int clk_gate_is_enabled(struct clk_hw *hw);
>
> +#ifdef CONFIG_COMMON_CLK_GATE_REGMAP
> +struct clk_hw *__clk_hw_register_gate_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data,
> + unsigned long flags,
> + struct regmap *map, u8 map_offset, u8 bit_idx,
> + u8 clk_gate_flags, spinlock_t *lock);
> +struct clk_hw *__devm_clk_hw_register_gate_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data,
> + unsigned long flags,
> + struct regmap *map, u8 map_offset, u8 bit_idx,
> + u8 clk_gate_flags, spinlock_t *lock);
> +struct clk *clk_register_gate_regmap(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + struct regmap *map, u8 map_offset, u8 bit_idx,
> + u8 clk_gate_flags, spinlock_t *lock);
> +
> +void clk_unregister_gate_regmap(struct clk *clk);
> +void clk_hw_unregister_gate_regmap(struct clk_hw *hw);
> +int clk_gate_regmap_is_enabled(struct clk_hw *hw);
> +#endif
> +
> struct clk_div_table {
> unsigned int val;
> unsigned int div;
> @@ -736,6 +794,41 @@ struct clk_divider {
> extern const struct clk_ops clk_divider_ops;
> extern const struct clk_ops clk_divider_ro_ops;
>
> +#ifdef CONFIG_COMMON_CLK_DIVIDER_REGMAP
> +/**
> + * struct clk_divider_regmap - adjustable divider clock via regmap
> + *
> + * @hw: handle between common and hardware-specific interfaces
> + * @map: regmap containing the divider
> + * @map_offset: register offset within the regmap containing the divider
> + * @shift: shift to the divider bit field
> + * @width: width of the divider bit field
> + * @table: array of value/divider pairs, last entry should have div = 0
> + * @lock: register lock
> + *
> + * Clock with an adjustable divider affecting its output frequency. Implements
> + * .recalc_rate, .set_rate and .round_rate
> + *
> + * @flags:
> + * See clk_divider
> + */
> +struct clk_divider_regmap {
> + struct clk_hw hw;
> + struct regmap *regmap;
> + u8 map_offset;
> + u8 shift;
> + u8 width;
> + u8 flags;
> + const struct clk_div_table *table;
> + spinlock_t *lock;
> +};
> +
> +#define to_clk_divider_regmap(_hw) container_of(_hw, struct clk_divider_regmap, hw)
> +
> +extern const struct clk_ops clk_divider_regmap_ops;
> +extern const struct clk_ops clk_divider_regmap_ro_ops;
> +#endif
> +
> unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> unsigned int val, const struct clk_div_table *table,
> unsigned long flags, unsigned long width);
> @@ -972,6 +1065,33 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name,
> void clk_unregister_divider(struct clk *clk);
> void clk_hw_unregister_divider(struct clk_hw *hw);
>
> +#ifdef CONFIG_COMMON_CLK_DIVIDER_REGMAP
> +struct clk_hw *__clk_hw_register_divider_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data, unsigned long flags,
> + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table,
> + spinlock_t *lock);
> +
> +struct clk_hw *__devm_clk_hw_register_divider_regmap(struct device *dev,
> + struct device_node *np, const char *name,
> + const char *parent_name, const struct clk_hw *parent_hw,
> + const struct clk_parent_data *parent_data, unsigned long flags,
> + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table,
> + spinlock_t *lock);
> +
> +struct clk *clk_register_divider_regmap_table(struct device *dev,
> + const char *name, const char *parent_name, unsigned long flags,
> + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table,
> + spinlock_t *lock);
> +
> +void clk_unregister_divider_regmap(struct clk *clk);
> +void clk_hw_unregister_divider_regmap(struct clk_hw *hw);
> +#endif
> +
> /**
> * struct clk_mux - multiplexer clock
> *
> --
> 2.45.2
>
Stephen,
On Thu, May 01, 2025 at 09:51:48AM +0100, Conor Dooley wrote:
> On Fri, Mar 21, 2025 at 05:22:41PM +0000, Conor Dooley wrote:
> > From: Conor Dooley <conor.dooley@microchip.com>
> >
> > Implement regmap-backed copies of gate and divider clocks by replacing
> > the iomem pointer to the clock registers with a regmap and offset
> > within.
>
> Is this the sort of implementation you were looking for in your comments
> on the previous version of this series?
> (https://lore.kernel.org/linux-clk/20250220-iciness-mobilize-94a027ff52ce@spud/)
Any chance of taking a look at this approach and letting me know if it
is what you wanted me to do?
Thanks,
Conor.
> > Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> > ---
> > drivers/clk/Kconfig | 8 +
> > drivers/clk/Makefile | 2 +
> > drivers/clk/clk-divider-regmap.c | 270 +++++++++++++++++++++++++++++++
> > drivers/clk/clk-gate-regmap.c | 253 +++++++++++++++++++++++++++++
> > drivers/clk/clk-gate.c | 5 +-
> > include/linux/clk-provider.h | 120 ++++++++++++++
> > 6 files changed, 656 insertions(+), 2 deletions(-)
> > create mode 100644 drivers/clk/clk-divider-regmap.c
> > create mode 100644 drivers/clk/clk-gate-regmap.c
> >
> > diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> > index 713573b6c86c..c700b6ef4f34 100644
> > --- a/drivers/clk/Kconfig
> > +++ b/drivers/clk/Kconfig
> > @@ -33,6 +33,14 @@ menuconfig COMMON_CLK
> >
> > if COMMON_CLK
> >
> > +config COMMON_CLK_DIVIDER_REGMAP
> > + bool
> > + select REGMAP
> > +
> > +config COMMON_CLK_GATE_REGMAP
> > + bool
> > + select REGMAP
> > +
> > config COMMON_CLK_WM831X
> > tristate "Clock driver for WM831x/2x PMICs"
> > depends on MFD_WM831X
> > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> > index bf4bd45adc3a..c57f72af5db9 100644
> > --- a/drivers/clk/Makefile
> > +++ b/drivers/clk/Makefile
> > @@ -20,11 +20,13 @@ clk-test-y := clk_test.o \
> > kunit_clk_assigned_rates_zero_consumer.dtbo.o \
> > kunit_clk_parent_data_test.dtbo.o
> > obj-$(CONFIG_COMMON_CLK) += clk-divider.o
> > +obj-$(CONFIG_COMMON_CLK_DIVIDER_REGMAP) += clk-divider-regmap.o
> > obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o
> > obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o
> > obj-$(CONFIG_CLK_FIXED_RATE_KUNIT_TEST) += clk-fixed-rate-test.o
> > clk-fixed-rate-test-y := clk-fixed-rate_test.o kunit_clk_fixed_rate_test.dtbo.o
> > obj-$(CONFIG_COMMON_CLK) += clk-gate.o
> > +obj-$(CONFIG_COMMON_CLK_GATE_REGMAP) += clk-gate-regmap.o
> > obj-$(CONFIG_CLK_GATE_KUNIT_TEST) += clk-gate_test.o
> > obj-$(CONFIG_COMMON_CLK) += clk-multiplier.o
> > obj-$(CONFIG_COMMON_CLK) += clk-mux.o
> > diff --git a/drivers/clk/clk-divider-regmap.c b/drivers/clk/clk-divider-regmap.c
> > new file mode 100644
> > index 000000000000..2906130a2b47
> > --- /dev/null
> > +++ b/drivers/clk/clk-divider-regmap.c
> > @@ -0,0 +1,270 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/device.h>
> > +#include <linux/slab.h>
> > +
> > +static inline u32 clk_div_regmap_readl(struct clk_divider_regmap *divider)
> > +{
> > + u32 val;
> > +
> > + regmap_read(divider->regmap, divider->map_offset, &val);
> > +
> > + return val;
> > +}
> > +
> > +static inline void clk_div_regmap_writel(struct clk_divider_regmap *divider, u32 val)
> > +{
> > + regmap_write(divider->regmap, divider->map_offset, val);
> > +
> > +}
> > +
> > +static unsigned long clk_divider_regmap_recalc_rate(struct clk_hw *hw,
> > + unsigned long parent_rate)
> > +{
> > + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> > + unsigned int val;
> > +
> > + val = clk_div_regmap_readl(divider) >> divider->shift;
> > + val &= clk_div_mask(divider->width);
> > +
> > + return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > + divider->flags, divider->width);
> > +}
> > +
> > +static long clk_divider_regmap_round_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long *prate)
> > +{
> > + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> > +
> > + /* if read only, just return current value */
> > + if (divider->flags & CLK_DIVIDER_READ_ONLY) {
> > + u32 val;
> > +
> > + val = clk_div_regmap_readl(divider) >> divider->shift;
> > + val &= clk_div_mask(divider->width);
> > +
> > + return divider_ro_round_rate(hw, rate, prate, divider->table,
> > + divider->width, divider->flags,
> > + val);
> > + }
> > +
> > + return divider_round_rate(hw, rate, prate, divider->table,
> > + divider->width, divider->flags);
> > +}
> > +
> > +static int clk_divider_regmap_determine_rate(struct clk_hw *hw,
> > + struct clk_rate_request *req)
> > +{
> > + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> > +
> > + /* if read only, just return current value */
> > + if (divider->flags & CLK_DIVIDER_READ_ONLY) {
> > + u32 val;
> > +
> > + val = clk_div_regmap_readl(divider) >> divider->shift;
> > + val &= clk_div_mask(divider->width);
> > +
> > + return divider_ro_determine_rate(hw, req, divider->table,
> > + divider->width,
> > + divider->flags, val);
> > + }
> > +
> > + return divider_determine_rate(hw, req, divider->table, divider->width,
> > + divider->flags);
> > +}
> > +
> > +static int clk_divider_regmap_set_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long parent_rate)
> > +{
> > + struct clk_divider_regmap *divider = to_clk_divider_regmap(hw);
> > + int value;
> > + unsigned long flags = 0;
> > + u32 val;
> > +
> > + value = divider_get_val(rate, parent_rate, divider->table,
> > + divider->width, divider->flags);
> > + if (value < 0)
> > + return value;
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
> > + val = clk_div_mask(divider->width) << (divider->shift + 16);
> > + } else {
> > + val = clk_div_regmap_readl(divider);
> > + val &= ~(clk_div_mask(divider->width) << divider->shift);
> > + }
> > + val |= (u32)value << divider->shift;
> > + clk_div_regmap_writel(divider, val);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +
> > + return 0;
> > +}
> > +
> > +const struct clk_ops clk_divider_regmap_ops = {
> > + .recalc_rate = clk_divider_regmap_recalc_rate,
> > + .round_rate = clk_divider_regmap_round_rate,
> > + .determine_rate = clk_divider_regmap_determine_rate,
> > + .set_rate = clk_divider_regmap_set_rate,
> > +};
> > +EXPORT_SYMBOL_GPL(clk_divider_regmap_ops);
> > +
> > +const struct clk_ops clk_divider_regmap_ro_ops = {
> > + .recalc_rate = clk_divider_regmap_recalc_rate,
> > + .round_rate = clk_divider_regmap_round_rate,
> > + .determine_rate = clk_divider_regmap_determine_rate,
> > +};
> > +EXPORT_SYMBOL_GPL(clk_divider_regmap_ro_ops);
> > +
> > +struct clk_hw *__clk_hw_register_divider_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data, unsigned long flags,
> > + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> > + u8 clk_divider_flags, const struct clk_div_table *table,
> > + spinlock_t *lock)
> > +{
> > + struct clk_divider_regmap *div;
> > + struct clk_hw *hw;
> > + struct clk_init_data init = {};
> > + int ret;
> > +
> > + if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > + if (width + shift > 16) {
> > + pr_warn("divider value exceeds LOWORD field\n");
> > + return ERR_PTR(-EINVAL);
> > + }
> > + }
> > +
> > + /* allocate the divider */
> > + div = kzalloc(sizeof(*div), GFP_KERNEL);
> > + if (!div)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + init.name = name;
> > + if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
> > + init.ops = &clk_divider_regmap_ro_ops;
> > + else
> > + init.ops = &clk_divider_regmap_ops;
> > + init.flags = flags;
> > + init.parent_names = parent_name ? &parent_name : NULL;
> > + init.parent_hws = parent_hw ? &parent_hw : NULL;
> > + init.parent_data = parent_data;
> > + if (parent_name || parent_hw || parent_data)
> > + init.num_parents = 1;
> > + else
> > + init.num_parents = 0;
> > +
> > + /* struct clk_divider assignments */
> > + div->regmap = regmap;
> > + div->map_offset = map_offset;
> > + div->shift = shift;
> > + div->width = width;
> > + div->flags = clk_divider_flags;
> > + div->lock = lock;
> > + div->hw.init = &init;
> > + div->table = table;
> > +
> > + /* register the clock */
> > + hw = &div->hw;
> > + ret = clk_hw_register(dev, hw);
> > + if (ret) {
> > + kfree(div);
> > + hw = ERR_PTR(ret);
> > + }
> > +
> > + return hw;
> > +}
> > +EXPORT_SYMBOL_GPL(__clk_hw_register_divider_regmap);
> > +
> > +struct clk *clk_register_divider_regmap_table(struct device *dev, const char *name,
> > + const char *parent_name, unsigned long flags,
> > + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> > + u8 clk_divider_flags, const struct clk_div_table *table,
> > + spinlock_t *lock)
> > +{
> > + struct clk_hw *hw;
> > +
> > + hw = __clk_hw_register_divider_regmap(dev, NULL, name, parent_name, NULL,
> > + NULL, flags, regmap, map_offset,
> > + shift, width, clk_divider_flags,
> > + table, lock);
> > + if (IS_ERR(hw))
> > + return ERR_CAST(hw);
> > + return hw->clk;
> > +}
> > +EXPORT_SYMBOL_GPL(clk_register_divider_regmap_table);
> > +
> > +void clk_unregister_divider_regmap(struct clk *clk)
> > +{
> > + struct clk_divider_regmap *div;
> > + struct clk_hw *hw;
> > +
> > + hw = __clk_get_hw(clk);
> > + if (!hw)
> > + return;
> > +
> > + div = to_clk_divider_regmap(hw);
> > +
> > + clk_unregister(clk);
> > + kfree(div);
> > +}
> > +EXPORT_SYMBOL_GPL(clk_unregister_divider_regmap);
> > +
> > +/**
> > + * clk_hw_unregister_divider_regmap - unregister a clk divider
> > + * @hw: hardware-specific clock data to unregister
> > + */
> > +void clk_hw_unregister_divider_regmap(struct clk_hw *hw)
> > +{
> > + struct clk_divider_regmap *div;
> > +
> > + div = to_clk_divider_regmap(hw);
> > +
> > + clk_hw_unregister(hw);
> > + kfree(div);
> > +}
> > +EXPORT_SYMBOL_GPL(clk_hw_unregister_divider_regmap);
> > +
> > +static void devm_clk_hw_release_divider_regmap(struct device *dev, void *res)
> > +{
> > + clk_hw_unregister_divider_regmap(*(struct clk_hw **)res);
> > +}
> > +
> > +struct clk_hw *__devm_clk_hw_register_divider_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data, unsigned long flags,
> > + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> > + u8 clk_divider_flags, const struct clk_div_table *table,
> > + spinlock_t *lock)
> > +{
> > + struct clk_hw **ptr, *hw;
> > +
> > + ptr = devres_alloc(devm_clk_hw_release_divider_regmap, sizeof(*ptr), GFP_KERNEL);
> > + if (!ptr)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + hw = __clk_hw_register_divider_regmap(dev, np, name, parent_name, parent_hw,
> > + parent_data, flags, regmap, map_offset,
> > + shift, width, clk_divider_flags, table,
> > + lock);
> > +
> > + if (!IS_ERR(hw)) {
> > + *ptr = hw;
> > + devres_add(dev, ptr);
> > + } else {
> > + devres_free(ptr);
> > + }
> > +
> > + return hw;
> > +}
> > +EXPORT_SYMBOL_GPL(__devm_clk_hw_register_divider_regmap);
> > diff --git a/drivers/clk/clk-gate-regmap.c b/drivers/clk/clk-gate-regmap.c
> > new file mode 100644
> > index 000000000000..cf9e48407971
> > --- /dev/null
> > +++ b/drivers/clk/clk-gate-regmap.c
> > @@ -0,0 +1,253 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <linux/io.h>
> > +#include <linux/err.h>
> > +#include <linux/string.h>
> > +
> > +/**
> > + * DOC: basic gatable clock which can gate and ungate its output
> > + *
> > + * Traits of this clock:
> > + * prepare - clk_(un)prepare only ensures parent is (un)prepared
> > + * enable - clk_enable and clk_disable are functional & control gating
> > + * rate - inherits rate from parent. No clk_set_rate support
> > + * parent - fixed parent. No clk_set_parent support
> > + */
> > +
> > +static inline u32 clk_gate_regmap_readl(struct clk_gate_regmap *gate)
> > +{
> > + u32 val;
> > +
> > + regmap_read(gate->map, gate->map_offset, &val);
> > +
> > + return val;
> > +}
> > +
> > +static inline void clk_gate_regmap_writel(struct clk_gate_regmap *gate, u32 val)
> > +{
> > + regmap_write(gate->map, gate->map_offset, val);
> > +
> > +}
> > +
> > +/*
> > + * It works on following logic:
> > + *
> > + * For enabling clock, enable = 1
> > + * set2dis = 1 -> clear bit -> set = 0
> > + * set2dis = 0 -> set bit -> set = 1
> > + *
> > + * For disabling clock, enable = 0
> > + * set2dis = 1 -> set bit -> set = 1
> > + * set2dis = 0 -> clear bit -> set = 0
> > + *
> > + * So, result is always: enable xor set2dis.
> > + */
> > +static void clk_gate_regmap_endisable(struct clk_hw *hw, int enable)
> > +{
> > + struct clk_gate_regmap *gate = to_clk_gate_regmap(hw);
> > + int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
> > + unsigned long flags;
> > + u32 reg;
> > +
> > + set ^= enable;
> > +
> > + if (gate->lock)
> > + spin_lock_irqsave(gate->lock, flags);
> > + else
> > + __acquire(gate->lock);
> > +
> > + if (gate->flags & CLK_GATE_HIWORD_MASK) {
> > + reg = BIT(gate->bit_idx + 16);
> > + if (set)
> > + reg |= BIT(gate->bit_idx);
> > + } else {
> > + reg = clk_gate_regmap_readl(gate);
> > +
> > + if (set)
> > + reg |= BIT(gate->bit_idx);
> > + else
> > + reg &= ~BIT(gate->bit_idx);
> > + }
> > +
> > + clk_gate_regmap_writel(gate, reg);
> > +
> > + if (gate->lock)
> > + spin_unlock_irqrestore(gate->lock, flags);
> > + else
> > + __release(gate->lock);
> > +}
> > +
> > +static int clk_gate_regmap_enable(struct clk_hw *hw)
> > +{
> > + clk_gate_regmap_endisable(hw, 1);
> > +
> > + return 0;
> > +}
> > +
> > +static void clk_gate_regmap_disable(struct clk_hw *hw)
> > +{
> > + clk_gate_regmap_endisable(hw, 0);
> > +}
> > +
> > +int clk_gate_regmap_is_enabled(struct clk_hw *hw)
> > +{
> > + u32 reg;
> > + struct clk_gate_regmap *gate = to_clk_gate_regmap(hw);
> > +
> > + reg = clk_gate_regmap_readl(gate);
> > +
> > + /* if a set bit disables this clk, flip it before masking */
> > + if (gate->flags & CLK_GATE_SET_TO_DISABLE)
> > + reg ^= BIT(gate->bit_idx);
> > +
> > + reg &= BIT(gate->bit_idx);
> > +
> > + return reg ? 1 : 0;
> > +}
> > +EXPORT_SYMBOL_GPL(clk_gate_regmap_is_enabled);
> > +
> > +const struct clk_ops clk_gate_regmap_ops = {
> > + .enable = clk_gate_regmap_enable,
> > + .disable = clk_gate_regmap_disable,
> > + .is_enabled = clk_gate_regmap_is_enabled,
> > +};
> > +EXPORT_SYMBOL_GPL(clk_gate_regmap_ops);
> > +
> > +struct clk_hw *__clk_hw_register_gate_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data,
> > + unsigned long flags,
> > + struct regmap *map, u8 map_offset, u8 bit_idx,
> > + u8 clk_gate_flags, spinlock_t *lock)
> > +{
> > + struct clk_gate_regmap *gate;
> > + struct clk_hw *hw;
> > + struct clk_init_data init = {};
> > + int ret = -EINVAL;
> > +
> > + if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
> > + if (bit_idx > 15) {
> > + pr_err("gate bit exceeds LOWORD field\n");
> > + return ERR_PTR(-EINVAL);
> > + }
> > + }
> > +
> > + /* allocate the gate */
> > + gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> > + if (!gate)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + init.name = name;
> > + init.ops = &clk_gate_regmap_ops;
> > + init.flags = flags;
> > + init.parent_names = parent_name ? &parent_name : NULL;
> > + init.parent_hws = parent_hw ? &parent_hw : NULL;
> > + init.parent_data = parent_data;
> > + if (parent_name || parent_hw || parent_data)
> > + init.num_parents = 1;
> > + else
> > + init.num_parents = 0;
> > +
> > + /* struct clk_gate_regmap assignments */
> > + gate->map = map;
> > + gate->map_offset = map_offset;
> > + gate->bit_idx = bit_idx;
> > + gate->flags = clk_gate_flags;
> > + gate->lock = lock;
> > + gate->hw.init = &init;
> > +
> > + hw = &gate->hw;
> > + if (dev || !np)
> > + ret = clk_hw_register(dev, hw);
> > + else if (np)
> > + ret = of_clk_hw_register(np, hw);
> > + if (ret) {
> > + kfree(gate);
> > + hw = ERR_PTR(ret);
> > + }
> > +
> > + return hw;
> > +
> > +}
> > +EXPORT_SYMBOL_GPL(__clk_hw_register_gate_regmap);
> > +
> > +struct clk *clk_register_gate_regmap(struct device *dev, const char *name,
> > + const char *parent_name, unsigned long flags, struct regmap *map,
> > + u8 map_offset, u8 bit_idx, u8 clk_gate_flags, spinlock_t *lock)
> > +{
> > + struct clk_hw *hw;
> > +
> > + hw = __clk_hw_register_gate_regmap(dev, NULL, name, parent_name, NULL,
> > + NULL, flags, map, map_offset, bit_idx,
> > + clk_gate_flags, lock);
> > + if (IS_ERR(hw))
> > + return ERR_CAST(hw);
> > + return hw->clk;
> > +}
> > +EXPORT_SYMBOL_GPL(clk_register_gate_regmap);
> > +
> > +void clk_unregister_gate_regmap(struct clk *clk)
> > +{
> > + struct clk_gate_regmap *gate;
> > + struct clk_hw *hw;
> > +
> > + hw = __clk_get_hw(clk);
> > + if (!hw)
> > + return;
> > +
> > + gate = to_clk_gate_regmap(hw);
> > +
> > + clk_unregister(clk);
> > + kfree(gate);
> > +}
> > +EXPORT_SYMBOL_GPL(clk_unregister_gate_regmap);
> > +
> > +void clk_hw_unregister_gate_regmap(struct clk_hw *hw)
> > +{
> > + struct clk_gate_regmap *gate;
> > +
> > + gate = to_clk_gate_regmap(hw);
> > +
> > + clk_hw_unregister(hw);
> > + kfree(gate);
> > +}
> > +EXPORT_SYMBOL_GPL(clk_hw_unregister_gate_regmap);
> > +
> > +static void devm_clk_hw_release_gate_regmap(struct device *dev, void *res)
> > +{
> > + clk_hw_unregister_gate_regmap(*(struct clk_hw **)res);
> > +}
> > +
> > +struct clk_hw *__devm_clk_hw_register_gate_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data,
> > + unsigned long flags, struct regmap *map,
> > + u8 map_offset, u8 bit_idx,
> > + u8 clk_gate_flags, spinlock_t *lock)
> > +{
> > + struct clk_hw **ptr, *hw;
> > +
> > + ptr = devres_alloc(devm_clk_hw_release_gate_regmap, sizeof(*ptr), GFP_KERNEL);
> > + if (!ptr)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + hw = __clk_hw_register_gate_regmap(dev, np, name, parent_name, parent_hw,
> > + parent_data, flags, map, map_offset,
> > + bit_idx, clk_gate_flags, lock);
> > +
> > + if (!IS_ERR(hw)) {
> > + *ptr = hw;
> > + devres_add(dev, ptr);
> > + } else {
> > + devres_free(ptr);
> > + }
> > +
> > + return hw;
> > +}
> > +EXPORT_SYMBOL_GPL(__devm_clk_hw_register_gate_regmap);
> > diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c
> > index 68e585a02fd9..1e3f21a249ca 100644
> > --- a/drivers/clk/clk-gate.c
> > +++ b/drivers/clk/clk-gate.c
> > @@ -34,10 +34,11 @@ static inline u32 clk_gate_readl(struct clk_gate *gate)
> >
> > static inline void clk_gate_writel(struct clk_gate *gate, u32 val)
> > {
> > - if (gate->flags & CLK_GATE_BIG_ENDIAN)
> > + if (gate->flags & CLK_GATE_BIG_ENDIAN) {
> > iowrite32be(val, gate->reg);
> > - else
> > + } else {
> > writel(val, gate->reg);
> > + }
> > }
> >
> > /*
> > diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> > index 2e6e603b7493..6f5cf6670b48 100644
> > --- a/include/linux/clk-provider.h
> > +++ b/include/linux/clk-provider.h
> > @@ -8,6 +8,7 @@
> >
> > #include <linux/of.h>
> > #include <linux/of_clk.h>
> > +#include <linux/regmap.h>
> >
> > /*
> > * flags used across common struct clk. these flags should only affect the
> > @@ -526,6 +527,7 @@ void of_fixed_clk_setup(struct device_node *np);
> > struct clk_gate {
> > struct clk_hw hw;
> > void __iomem *reg;
> > + u8 map_offset;
> > u8 bit_idx;
> > u8 flags;
> > spinlock_t *lock;
> > @@ -538,6 +540,37 @@ struct clk_gate {
> > #define CLK_GATE_BIG_ENDIAN BIT(2)
> >
> > extern const struct clk_ops clk_gate_ops;
> > +
> > +#ifdef CONFIG_COMMON_CLK_GATE_REGMAP
> > +/**
> > + * struct clk_gate_regmap - gating clock via regmap
> > + *
> > + * @hw: handle between common and hardware-specific interfaces
> > + * @map: regmap controlling gate
> > + * @map_offset: register offset within the regmap controlling gate
> > + * @bit_idx: single bit controlling gate
> > + * @flags: hardware-specific flags
> > + * @lock: register lock
> > + *
> > + * Clock which can gate its output. Implements .enable & .disable
> > + *
> > + * Flags:
> > + * See clk_gate
> > + */
> > +struct clk_gate_regmap {
> > + struct clk_hw hw;
> > + struct regmap *map;
> > + u8 map_offset;
> > + u8 bit_idx;
> > + u8 flags;
> > + spinlock_t *lock;
> > +};
> > +
> > +#define to_clk_gate_regmap(_hw) container_of(_hw, struct clk_gate_regmap, hw)
> > +
> > +extern const struct clk_ops clk_gate_regmap_ops;
> > +#endif
> > +
> > struct clk_hw *__clk_hw_register_gate(struct device *dev,
> > struct device_node *np, const char *name,
> > const char *parent_name, const struct clk_hw *parent_hw,
> > @@ -663,6 +696,31 @@ void clk_unregister_gate(struct clk *clk);
> > void clk_hw_unregister_gate(struct clk_hw *hw);
> > int clk_gate_is_enabled(struct clk_hw *hw);
> >
> > +#ifdef CONFIG_COMMON_CLK_GATE_REGMAP
> > +struct clk_hw *__clk_hw_register_gate_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data,
> > + unsigned long flags,
> > + struct regmap *map, u8 map_offset, u8 bit_idx,
> > + u8 clk_gate_flags, spinlock_t *lock);
> > +struct clk_hw *__devm_clk_hw_register_gate_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data,
> > + unsigned long flags,
> > + struct regmap *map, u8 map_offset, u8 bit_idx,
> > + u8 clk_gate_flags, spinlock_t *lock);
> > +struct clk *clk_register_gate_regmap(struct device *dev, const char *name,
> > + const char *parent_name, unsigned long flags,
> > + struct regmap *map, u8 map_offset, u8 bit_idx,
> > + u8 clk_gate_flags, spinlock_t *lock);
> > +
> > +void clk_unregister_gate_regmap(struct clk *clk);
> > +void clk_hw_unregister_gate_regmap(struct clk_hw *hw);
> > +int clk_gate_regmap_is_enabled(struct clk_hw *hw);
> > +#endif
> > +
> > struct clk_div_table {
> > unsigned int val;
> > unsigned int div;
> > @@ -736,6 +794,41 @@ struct clk_divider {
> > extern const struct clk_ops clk_divider_ops;
> > extern const struct clk_ops clk_divider_ro_ops;
> >
> > +#ifdef CONFIG_COMMON_CLK_DIVIDER_REGMAP
> > +/**
> > + * struct clk_divider_regmap - adjustable divider clock via regmap
> > + *
> > + * @hw: handle between common and hardware-specific interfaces
> > + * @map: regmap containing the divider
> > + * @map_offset: register offset within the regmap containing the divider
> > + * @shift: shift to the divider bit field
> > + * @width: width of the divider bit field
> > + * @table: array of value/divider pairs, last entry should have div = 0
> > + * @lock: register lock
> > + *
> > + * Clock with an adjustable divider affecting its output frequency. Implements
> > + * .recalc_rate, .set_rate and .round_rate
> > + *
> > + * @flags:
> > + * See clk_divider
> > + */
> > +struct clk_divider_regmap {
> > + struct clk_hw hw;
> > + struct regmap *regmap;
> > + u8 map_offset;
> > + u8 shift;
> > + u8 width;
> > + u8 flags;
> > + const struct clk_div_table *table;
> > + spinlock_t *lock;
> > +};
> > +
> > +#define to_clk_divider_regmap(_hw) container_of(_hw, struct clk_divider_regmap, hw)
> > +
> > +extern const struct clk_ops clk_divider_regmap_ops;
> > +extern const struct clk_ops clk_divider_regmap_ro_ops;
> > +#endif
> > +
> > unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> > unsigned int val, const struct clk_div_table *table,
> > unsigned long flags, unsigned long width);
> > @@ -972,6 +1065,33 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name,
> > void clk_unregister_divider(struct clk *clk);
> > void clk_hw_unregister_divider(struct clk_hw *hw);
> >
> > +#ifdef CONFIG_COMMON_CLK_DIVIDER_REGMAP
> > +struct clk_hw *__clk_hw_register_divider_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data, unsigned long flags,
> > + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> > + u8 clk_divider_flags, const struct clk_div_table *table,
> > + spinlock_t *lock);
> > +
> > +struct clk_hw *__devm_clk_hw_register_divider_regmap(struct device *dev,
> > + struct device_node *np, const char *name,
> > + const char *parent_name, const struct clk_hw *parent_hw,
> > + const struct clk_parent_data *parent_data, unsigned long flags,
> > + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> > + u8 clk_divider_flags, const struct clk_div_table *table,
> > + spinlock_t *lock);
> > +
> > +struct clk *clk_register_divider_regmap_table(struct device *dev,
> > + const char *name, const char *parent_name, unsigned long flags,
> > + struct regmap *regmap, u8 map_offset, u8 shift, u8 width,
> > + u8 clk_divider_flags, const struct clk_div_table *table,
> > + spinlock_t *lock);
> > +
> > +void clk_unregister_divider_regmap(struct clk *clk);
> > +void clk_hw_unregister_divider_regmap(struct clk_hw *hw);
> > +#endif
> > +
> > /**
> > * struct clk_mux - multiplexer clock
> > *
> > --
> > 2.45.2
> >
© 2016 - 2025 Red Hat, Inc.