Commit 5ee063fa authored by Peng Fan's avatar Peng Fan Committed by Abel Vesa
Browse files

clk: imx: lpcg-scu: SW workaround for errata (e10858)

Back-to-back LPCG writes can be ignored by the LPCG register due to
a HW bug. The writes need to be separated by at least 4 cycles of
the gated clock. See https://www.nxp.com.cn/docs/en/errata/IMX8_1N94W.pdf



The workaround is implemented as follows:
1. For clocks running greater than or equal to 24MHz, a read
followed by the write will provide sufficient delay.
2. For clocks running below 24MHz, add a delay of 4 clock cylces
after the write to the LPCG register.

Fixes: 2f77296d ("clk: imx: add lpcg clock support")
Signed-off-by: default avatarPeng Fan <peng.fan@nxp.com>
Reviewed-by: default avatarAbel Vesa <abel.vesa@linaro.org>
Link: https://lore.kernel.org/r/20241027-imx-clk-v1-v3-1-89152574d1d7@nxp.com


Signed-off-by: default avatarAbel Vesa <abel.vesa@linaro.org>
parent a27bfff8
Loading
Loading
Loading
Loading
+29 −8
Original line number Diff line number Diff line
@@ -6,10 +6,12 @@

#include <linux/bits.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/units.h>

#include "clk-scu.h"

@@ -41,6 +43,29 @@ struct clk_lpcg_scu {

#define to_clk_lpcg_scu(_hw) container_of(_hw, struct clk_lpcg_scu, hw)

/* e10858 -LPCG clock gating register synchronization errata */
static void lpcg_e10858_writel(unsigned long rate, void __iomem *reg, u32 val)
{
	writel(val, reg);

	if (rate >= 24 * HZ_PER_MHZ || rate == 0) {
		/*
		 * The time taken to access the LPCG registers from the AP core
		 * through the interconnect is longer than the minimum delay
		 * of 4 clock cycles required by the errata.
		 * Adding a readl will provide sufficient delay to prevent
		 * back-to-back writes.
		 */
		readl(reg);
	} else {
		/*
		 * For clocks running below 24MHz, wait a minimum of
		 * 4 clock cycles.
		 */
		ndelay(4 * (DIV_ROUND_UP(1000 * HZ_PER_MHZ, rate)));
	}
}

static int clk_lpcg_scu_enable(struct clk_hw *hw)
{
	struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw);
@@ -57,7 +82,8 @@ static int clk_lpcg_scu_enable(struct clk_hw *hw)
		val |= CLK_GATE_SCU_LPCG_HW_SEL;

	reg |= val << clk->bit_idx;
	writel(reg, clk->reg);

	lpcg_e10858_writel(clk_hw_get_rate(hw), clk->reg, reg);

	spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags);

@@ -74,7 +100,7 @@ static void clk_lpcg_scu_disable(struct clk_hw *hw)

	reg = readl_relaxed(clk->reg);
	reg &= ~(CLK_GATE_SCU_LPCG_MASK << clk->bit_idx);
	writel(reg, clk->reg);
	lpcg_e10858_writel(clk_hw_get_rate(hw), clk->reg, reg);

	spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags);
}
@@ -145,13 +171,8 @@ static int __maybe_unused imx_clk_lpcg_scu_resume(struct device *dev)
{
	struct clk_lpcg_scu *clk = dev_get_drvdata(dev);

	/*
	 * FIXME: Sometimes writes don't work unless the CPU issues
	 * them twice
	 */

	writel(clk->state, clk->reg);
	writel(clk->state, clk->reg);
	lpcg_e10858_writel(0, clk->reg, clk->state);
	dev_dbg(dev, "restore lpcg state 0x%x\n", clk->state);

	return 0;