mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-04-29 12:28:27 +02:00
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: 2f77296d3d ("clk: imx: add lpcg clock support")
Signed-off-by: Peng Fan <peng.fan@nxp.com>
Reviewed-by: Abel Vesa <abel.vesa@linaro.org>
Link: https://lore.kernel.org/r/20241027-imx-clk-v1-v3-1-89152574d1d7@nxp.com
Signed-off-by: Abel Vesa <abel.vesa@linaro.org>
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user