@@ -7,6 +7,7 @@ mxc-clk-objs += clk-composite-8m.o
mxc-clk-objs += clk-composite-93.o
mxc-clk-objs += clk-fracn-gppll.o
mxc-clk-objs += clk-cpu.o
+mxc-clk-objs += clk-divider.o
mxc-clk-objs += clk-divider-gate.o
mxc-clk-objs += clk-fixup-div.o
mxc-clk-objs += clk-fixup-mux.o
new file mode 100644
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 Amarula Solutions
+ *
+ * Dario Binacchi <dario.binacchi@amarulasolutions.com>
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+
+#include "clk.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#define to_clk_imx_divider(_hw) container_of(_hw, struct clk_imx_divider, hw)
+
+struct clk_imx_divider {
+ struct clk_hw hw;
+ struct imx_clk_reg reg;
+ u8 shift;
+ u8 width;
+};
+
+static int imx_clk_divider_write(const struct imx_clk_reg *reg, u32 val)
+{
+ int ret = 0;
+
+ if (reg->base) {
+ writel(val, reg->base + reg->offset);
+ } else if (reg->regmap) {
+ ret = regmap_write(reg->regmap, reg->offset, val);
+ } else {
+ pr_err("memory address not set\n");
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int imx_clk_divider_read(const struct imx_clk_reg *reg, u32 *val)
+{
+ int ret = 0;
+
+ if (reg->base) {
+ *val = readl(reg->base + reg->offset);
+ } else if (reg->regmap) {
+ ret = regmap_read(reg->regmap, reg->offset, val);
+ } else {
+ pr_err("memory address not set\n");
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static unsigned long imx_clk_divider_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_imx_divider *divider = to_clk_imx_divider(hw);
+ unsigned int val;
+ int ret;
+
+ ret = imx_clk_divider_read(÷r->reg, &val);
+ if (ret)
+ return 0;
+
+ val >>= divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_recalc_rate(hw, parent_rate, val, NULL, 0,
+ divider->width);
+}
+
+static long imx_clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct clk_imx_divider *divider = to_clk_imx_divider(hw);
+
+ return divider_round_rate(hw, rate, prate, NULL, divider->width, 0);
+}
+
+static int imx_clk_divider_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_imx_divider *divider = to_clk_imx_divider(hw);
+
+ return divider_determine_rate(hw, req, NULL, divider->width, 0);
+}
+
+static int imx_clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_imx_divider *divider = to_clk_imx_divider(hw);
+ unsigned int val;
+ int div, ret;
+
+ div = divider_get_val(rate, parent_rate, NULL, divider->width, 0);
+ if (div < 0)
+ return div;
+
+ ret = imx_clk_divider_read(÷r->reg, &val);
+ if (ret)
+ return ret;
+
+ val &= ~(clk_div_mask(divider->width) << divider->shift);
+ val |= div << divider->shift;
+ return imx_clk_divider_write(÷r->reg, val);
+}
+
+const struct clk_ops imx_clk_divider_ops = {
+ .recalc_rate = imx_clk_divider_recalc_rate,
+ .round_rate = imx_clk_divider_round_rate,
+ .determine_rate = imx_clk_divider_determine_rate,
+ .set_rate = imx_clk_divider_set_rate,
+};
+
+static void imx_clk_hw_unregister_divider(struct clk_hw *hw)
+{
+ struct clk_imx_divider *divider = to_clk_imx_divider(hw);
+
+ clk_hw_unregister(hw);
+ kfree(divider);
+}
+
+static struct clk_hw *imx_clk_hw_register_divider(struct device_node *node,
+ const char *name,
+ unsigned long flags,
+ struct imx_clk_reg *reg,
+ u8 shift, u8 width)
+{
+ struct clk_parent_data pdata = { .index = 0 };
+ struct clk_init_data init = { NULL };
+ struct clk_imx_divider *divider;
+ struct clk_hw *hw;
+ int ret;
+
+ divider = kzalloc(sizeof(*divider), GFP_KERNEL);
+ if (!divider)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.flags = flags;
+ init.ops = &imx_clk_divider_ops;
+ init.parent_data = &pdata;
+ init.num_parents = 1;
+
+ memcpy(÷r->reg, reg, sizeof(*reg));
+ divider->shift = shift;
+ divider->width = width;
+ divider->hw.init = &init;
+
+ hw = ÷r->hw;
+ ret = of_clk_hw_register(node, hw);
+ if (ret) {
+ kfree(divider);
+ return ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+/**
+ * of_imx_divider_clk_setup() - Setup function for imx gate clock
+ * @node: device node for the clock
+ */
+static void __init of_imx_divider_clk_setup(struct device_node *node)
+{
+ struct clk_hw *hw;
+ struct imx_clk_reg reg = {};
+ const char *name = node->name;
+ u8 shift = 0;
+ u8 width;
+ u32 flags = 0;
+ u32 val;
+
+ reg.regmap = syscon_regmap_lookup_by_phandle(node, "fsl,anatop");
+ if (!IS_ERR(reg.regmap)) {
+ if (of_property_read_u32_index(node, "fsl,anatop", 1, &val)) {
+ pr_err("missing register offset for %pOFn\n", node);
+ return;
+ }
+
+ reg.offset = val;
+ } else {
+ reg.base = of_iomap(node, 0);
+ if (IS_ERR(reg.base)) {
+ pr_err("failed to get register address for %pOFn\n",
+ node);
+ return;
+ }
+ }
+
+ if (!of_property_read_u32(node, "fsl,bit-shift", &val))
+ shift = val;
+
+ if (of_property_read_u32(node, "fsl,width", &val)) {
+ pr_err("missing width for %pOFn\n", node);
+ return;
+
+ }
+
+ width = val;
+
+ if (of_property_read_bool(node, "fsl,ops-parent-enable"))
+ flags |= CLK_OPS_PARENT_ENABLE;
+
+ if (of_property_read_bool(node, "fsl,set-rate-parent"))
+ flags |= CLK_SET_RATE_PARENT;
+
+ if (of_clk_get_parent_count(node) != 1) {
+ pr_err("%pOFn must have 1 parent clock\n", node);
+ return;
+ }
+
+ of_property_read_string(node, "clock-output-names", &name);
+
+ hw = imx_clk_hw_register_divider(node, name, flags, ®, shift, width);
+ if (IS_ERR(hw))
+ return;
+
+ if (of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw)) {
+ imx_clk_hw_unregister_divider(hw);
+ return;
+ }
+
+ pr_debug("name: %s, offset: 0x%x, shift: %d, width: %d\n", name,
+ reg.offset, shift, width);
+}
+CLK_OF_DECLARE(fsl_imx8mn_divider_clk, "fsl,imx8mn-divider-clock",
+ of_imx_divider_clk_setup);