summaryrefslogtreecommitdiff
path: root/drivers/video/imx/ipu_clk_legacy.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/imx/ipu_clk_legacy.c')
-rw-r--r--drivers/video/imx/ipu_clk_legacy.c310
1 files changed, 310 insertions, 0 deletions
diff --git a/drivers/video/imx/ipu_clk_legacy.c b/drivers/video/imx/ipu_clk_legacy.c
new file mode 100644
index 00000000000..8aaafa2a080
--- /dev/null
+++ b/drivers/video/imx/ipu_clk_legacy.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Legacy IPU clock management for i.MX5/6 without Common Clock Framework
+ *
+ * (C) Copyright 2026
+ * Brian Ruley, GE HealthCare, brian.ruley@gehealthcare.com
+ */
+
+#include "ipu.h"
+#include "ipu_regs.h"
+#include <asm/arch/crm_regs.h>
+#include <asm/arch/sys_proto.h>
+#include <asm/io.h>
+#include <div64.h>
+#include <dm/devres.h>
+#include <linux/err.h>
+#include <log.h>
+
+extern struct mxc_ccm_reg *mxc_ccm;
+
+void clk_enable(struct clk *clk)
+{
+ if (clk) {
+ if (clk->usecount++ == 0)
+ clk->enable(clk);
+ }
+}
+
+void clk_disable(struct clk *clk)
+{
+ if (clk) {
+ if (!(--clk->usecount)) {
+ if (clk->disable)
+ clk->disable(clk);
+ }
+ }
+}
+
+int clk_get_usecount(struct clk *clk)
+{
+ if (clk == NULL)
+ return 0;
+
+ return clk->usecount;
+}
+
+u32 clk_get_rate(struct clk *clk)
+{
+ if (!clk)
+ return 0;
+
+ return clk->rate;
+}
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+ if (!clk)
+ return 0;
+
+ return clk->parent;
+}
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ if (!clk)
+ return 0;
+
+ if (clk->set_rate)
+ clk->set_rate(clk, rate);
+
+ return clk->rate;
+}
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ if (clk == NULL || !clk->round_rate)
+ return 0;
+
+ return clk->round_rate(clk, rate);
+}
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ clk->parent = parent;
+ if (clk->set_parent)
+ return clk->set_parent(clk, parent);
+ return 0;
+}
+
+static int clk_ipu_enable(struct clk *clk)
+{
+ u32 reg;
+
+ reg = __raw_readl(clk->enable_reg);
+ reg |= MXC_CCM_CCGR_CG_MASK << clk->enable_shift;
+ __raw_writel(reg, clk->enable_reg);
+
+#if CONFIG_IS_ENABLED(MX51) || CONFIG_IS_ENABLED(MX53)
+ reg = __raw_readl(&mxc_ccm->ccdr);
+ reg &= ~MXC_CCM_CCDR_IPU_HS_MASK;
+ __raw_writel(reg, &mxc_ccm->ccdr);
+
+ reg = __raw_readl(&mxc_ccm->clpcr);
+ reg &= ~MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS;
+ __raw_writel(reg, &mxc_ccm->clpcr);
+#endif
+ return 0;
+}
+
+static void clk_ipu_disable(struct clk *clk)
+{
+ u32 reg;
+
+ reg = __raw_readl(clk->enable_reg);
+ reg &= ~(MXC_CCM_CCGR_CG_MASK << clk->enable_shift);
+ __raw_writel(reg, clk->enable_reg);
+
+#if CONFIG_IS_ENABLED(MX51) || CONFIG_IS_ENABLED(MX53)
+ reg = __raw_readl(&mxc_ccm->ccdr);
+ reg |= MXC_CCM_CCDR_IPU_HS_MASK;
+ __raw_writel(reg, &mxc_ccm->ccdr);
+
+ reg = __raw_readl(&mxc_ccm->clpcr);
+ reg |= MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS;
+ __raw_writel(reg, &mxc_ccm->clpcr);
+#endif
+}
+
+static void ipu_pixel_clk_recalc(struct clk *clk)
+{
+ u32 div;
+ u64 final_rate = (unsigned long long)clk->parent->rate * 16;
+
+ div = __raw_readl(DI_BS_CLKGEN0(clk->id));
+ debug("read BS_CLKGEN0 div:%d, final_rate:%lld, prate:%ld\n", div,
+ final_rate, clk->parent->rate);
+
+ clk->rate = 0;
+ if (div != 0) {
+ do_div(final_rate, div);
+ clk->rate = final_rate;
+ }
+}
+
+static unsigned long ipu_pixel_clk_round_rate(struct clk *clk,
+ unsigned long rate)
+{
+ u64 div, final_rate;
+ u32 remainder;
+ u64 parent_rate = (unsigned long long)clk->parent->rate * 16;
+
+ div = parent_rate;
+ remainder = do_div(div, rate);
+ if (remainder > (rate / 2))
+ div++;
+ if (div < 0x10)
+ div = 0x10;
+ if (div & ~0xFEF)
+ div &= 0xFF8;
+ else {
+ if ((div & 0xC) == 0xC) {
+ div += 0x10;
+ div &= ~0xF;
+ }
+ }
+ final_rate = parent_rate;
+ do_div(final_rate, div);
+
+ return final_rate;
+}
+
+static int ipu_pixel_clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ u64 div, parent_rate;
+ u32 remainder;
+
+ parent_rate = (unsigned long long)clk->parent->rate * 16;
+ div = parent_rate;
+ remainder = do_div(div, rate);
+ if (remainder > (rate / 2))
+ div++;
+
+ if ((div & 0xC) == 0xC) {
+ div += 0x10;
+ div &= ~0xF;
+ }
+ if (div > 0x1000)
+ debug("Overflow, DI_BS_CLKGEN0 div:0x%x\n", (u32)div);
+
+ __raw_writel(div, DI_BS_CLKGEN0(clk->id));
+ __raw_writel((div / 16) << 16, DI_BS_CLKGEN1(clk->id));
+
+ do_div(parent_rate, div);
+ clk->rate = parent_rate;
+
+ return 0;
+}
+
+static int ipu_pixel_clk_enable(struct clk *clk)
+{
+ u32 disp_gen = __raw_readl(IPU_DISP_GEN);
+ disp_gen |= clk->id ? DI1_COUNTER_RELEASE : DI0_COUNTER_RELEASE;
+ __raw_writel(disp_gen, IPU_DISP_GEN);
+
+ return 0;
+}
+
+static void ipu_pixel_clk_disable(struct clk *clk)
+{
+ u32 disp_gen = __raw_readl(IPU_DISP_GEN);
+ disp_gen &= clk->id ? ~DI1_COUNTER_RELEASE : ~DI0_COUNTER_RELEASE;
+ __raw_writel(disp_gen, IPU_DISP_GEN);
+}
+
+static int ipu_pixel_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ u32 di_gen = __raw_readl(DI_GENERAL(clk->id));
+ struct ipu_ctx *ctx = clk->ctx;
+
+ if (parent == ctx->ipu_clk)
+ di_gen &= ~DI_GEN_DI_CLK_EXT;
+ else if (!IS_ERR(ctx->di_clk[clk->id]) && parent == ctx->ldb_clk)
+ di_gen |= DI_GEN_DI_CLK_EXT;
+ else
+ return -EINVAL;
+
+ __raw_writel(di_gen, DI_GENERAL(clk->id));
+ ipu_pixel_clk_recalc(clk);
+ return 0;
+}
+
+int ipu_pixel_clk_init_legacy(struct ipu_ctx *ctx, int id)
+{
+ struct clk *pixel_clk;
+
+ pixel_clk = devm_kzalloc(ctx->dev, sizeof(*pixel_clk), GFP_KERNEL);
+ if (!pixel_clk)
+ return -ENOMEM;
+
+ pixel_clk->name = "pixel_clk";
+ pixel_clk->id = id;
+ pixel_clk->ctx = ctx;
+ pixel_clk->recalc = ipu_pixel_clk_recalc;
+ pixel_clk->set_rate = ipu_pixel_clk_set_rate;
+ pixel_clk->round_rate = ipu_pixel_clk_round_rate;
+ pixel_clk->set_parent = ipu_pixel_clk_set_parent;
+ pixel_clk->enable = ipu_pixel_clk_enable;
+ pixel_clk->disable = ipu_pixel_clk_disable;
+ pixel_clk->usecount = 0;
+
+ ctx->pixel_clk[id] = pixel_clk;
+ return 0;
+}
+
+int ipu_clk_init_legacy(struct ipu_ctx *ctx)
+{
+ struct clk *ipu_clk;
+
+ ipu_clk = devm_kzalloc(ctx->dev, sizeof(*ipu_clk), GFP_KERNEL);
+ if (!ipu_clk)
+ return -ENOMEM;
+
+ ipu_clk->name = "ipu_clk";
+ ipu_clk->ctx = ctx;
+#if CONFIG_IS_ENABLED(MX51) || CONFIG_IS_ENABLED(MX53)
+ ipu_clk->enable_reg =
+ (u32 *)(CCM_BASE_ADDR + offsetof(struct mxc_ccm_reg, CCGR5));
+ ipu_clk->enable_shift = MXC_CCM_CCGR5_IPU_OFFSET;
+#else
+ ipu_clk->enable_reg =
+ (u32 *)(CCM_BASE_ADDR + offsetof(struct mxc_ccm_reg, CCGR3));
+ ipu_clk->enable_shift = MXC_CCM_CCGR3_IPU1_IPU_DI0_OFFSET;
+#endif
+
+ ipu_clk->enable = clk_ipu_enable;
+ ipu_clk->disable = clk_ipu_disable;
+ ipu_clk->usecount = 0;
+
+#if CONFIG_IS_ENABLED(MX51)
+ ipu_clk->rate = IPUV3_CLK_MX51;
+#elif CONFIG_IS_ENABLED(MX53)
+ ipu_clk->rate = IPUV3_CLK_MX53;
+#else
+ ipu_clk->rate = is_mx6sdl() ? IPUV3_CLK_MX6DL : IPUV3_CLK_MX6Q;
+#endif
+
+ ctx->ipu_clk = ipu_clk;
+ return 0;
+}
+
+#if !defined CFG_SYS_LDB_CLOCK
+#define CFG_SYS_LDB_CLOCK 65000000
+#endif
+
+int ipu_ldb_clk_init_legacy(struct ipu_ctx *ctx)
+{
+ struct clk *ldb_clk;
+
+ ldb_clk = devm_kzalloc(ctx->dev, sizeof(*ldb_clk), GFP_KERNEL);
+ if (!ldb_clk)
+ return -ENOMEM;
+
+ ldb_clk->name = "ldb_clk";
+ ldb_clk->ctx = ctx;
+ ldb_clk->rate = CFG_SYS_LDB_CLOCK;
+ ldb_clk->usecount = 0;
+
+ ctx->ldb_clk = ldb_clk;
+ return 0;
+}