Message ID | 20240228082649.1633083-1-peng.fan@oss.nxp.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | clk: imx: lpcg-scu: SW workaround for errata (e10858) | expand |
On Wed, Feb 28, 2024 at 04:26:49PM +0800, Peng Fan (OSS) wrote: > From: Peng Fan <peng.fan@nxp.com> > > 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. > > 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. > > Signed-off-by: Peng Fan <peng.fan@nxp.com> > --- Fixes tag? Frank > > This is to upstream a downstream patch that goes through many releases > in several years. It maybe hard to trigger, but to address potential > issues, let's add this workaround. > > drivers/clk/imx/clk-lpcg-scu.c | 33 ++++++++++++++++++++++++++++++--- > 1 file changed, 30 insertions(+), 3 deletions(-) > > diff --git a/drivers/clk/imx/clk-lpcg-scu.c b/drivers/clk/imx/clk-lpcg-scu.c > index dd5abd09f3e2..b30d0f8b5bca 100644 > --- a/drivers/clk/imx/clk-lpcg-scu.c > +++ b/drivers/clk/imx/clk-lpcg-scu.c > @@ -6,6 +6,7 @@ > > #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> > @@ -41,6 +42,31 @@ 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 do_lpcg_workaround(u32 rate, void __iomem *reg, u32 val) > +{ > + writel(val, reg); > + > + if (rate >= 24000000 || rate == 0) { > + u32 reg1; > + > + /* > + * 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. > + */ > + reg1 = readl(reg); > + } else { > + /* > + * For clocks running below 24MHz, wait a minimum of > + * 4 clock cycles. > + */ > + ndelay(4 * (DIV_ROUND_UP(1000000000, rate))); > + } > +} > + > static int clk_lpcg_scu_enable(struct clk_hw *hw) > { > struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); > @@ -57,7 +83,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); > + > + do_lpcg_workaround(clk_hw_get_rate(hw), clk->reg, reg); > > spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); > > @@ -74,7 +101,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); > + do_lpcg_workaround(clk_hw_get_rate(hw), clk->reg, reg); > > spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); > } > @@ -151,7 +178,7 @@ static int __maybe_unused imx_clk_lpcg_scu_resume(struct device *dev) > */ > > writel(clk->state, clk->reg); > - writel(clk->state, clk->reg); > + do_lpcg_workaround(0, clk->reg, clk->state); > dev_dbg(dev, "restore lpcg state 0x%x\n", clk->state); > > return 0; > -- > 2.37.1 >
Quoting Peng Fan (OSS) (2024-02-28 00:26:49) > diff --git a/drivers/clk/imx/clk-lpcg-scu.c b/drivers/clk/imx/clk-lpcg-scu.c > index dd5abd09f3e2..b30d0f8b5bca 100644 > --- a/drivers/clk/imx/clk-lpcg-scu.c > +++ b/drivers/clk/imx/clk-lpcg-scu.c > @@ -6,6 +6,7 @@ > > #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> > @@ -41,6 +42,31 @@ 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 do_lpcg_workaround(u32 rate, void __iomem *reg, u32 val) unsigned long rate > +{ > + writel(val, reg); > + > + if (rate >= 24000000 || rate == 0) { > + u32 reg1; Please declare this variable at the start of the function. > + > + /* > + * 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. > + */ > + reg1 = readl(reg); > + } else { > + /* > + * For clocks running below 24MHz, wait a minimum of > + * 4 clock cycles. > + */ > + ndelay(4 * (DIV_ROUND_UP(1000000000, rate))); > + } > +} > + > static int clk_lpcg_scu_enable(struct clk_hw *hw) > { > struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); > @@ -57,7 +83,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); > + > + do_lpcg_workaround(clk_hw_get_rate(hw), clk->reg, reg); I'd prefer the name had 'writel' in it somewhere. > > spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); >
diff --git a/drivers/clk/imx/clk-lpcg-scu.c b/drivers/clk/imx/clk-lpcg-scu.c index dd5abd09f3e2..b30d0f8b5bca 100644 --- a/drivers/clk/imx/clk-lpcg-scu.c +++ b/drivers/clk/imx/clk-lpcg-scu.c @@ -6,6 +6,7 @@ #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> @@ -41,6 +42,31 @@ 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 do_lpcg_workaround(u32 rate, void __iomem *reg, u32 val) +{ + writel(val, reg); + + if (rate >= 24000000 || rate == 0) { + u32 reg1; + + /* + * 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. + */ + reg1 = readl(reg); + } else { + /* + * For clocks running below 24MHz, wait a minimum of + * 4 clock cycles. + */ + ndelay(4 * (DIV_ROUND_UP(1000000000, rate))); + } +} + static int clk_lpcg_scu_enable(struct clk_hw *hw) { struct clk_lpcg_scu *clk = to_clk_lpcg_scu(hw); @@ -57,7 +83,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); + + do_lpcg_workaround(clk_hw_get_rate(hw), clk->reg, reg); spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); @@ -74,7 +101,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); + do_lpcg_workaround(clk_hw_get_rate(hw), clk->reg, reg); spin_unlock_irqrestore(&imx_lpcg_scu_lock, flags); } @@ -151,7 +178,7 @@ static int __maybe_unused imx_clk_lpcg_scu_resume(struct device *dev) */ writel(clk->state, clk->reg); - writel(clk->state, clk->reg); + do_lpcg_workaround(0, clk->reg, clk->state); dev_dbg(dev, "restore lpcg state 0x%x\n", clk->state); return 0;