This patch series intends to add imx7ulp clk support.
i.MX7ULP Clock functions are under joint control of the System
Clock Generation (SCG) modules, Peripheral Clock Control (PCC)
modules, and Core Mode Controller (CMC)1 blocks
The clocking scheme provides clear separation between M4 domain
and A7 domain. Except for a few clock sources shared between two
domains, such as the System Oscillator clock, the Slow IRC (SIRC),
and and the Fast IRC clock (FIRCLK), clock sources and clock
management are separated and contained within each domain.
M4 clock management consists of SCG0, PCC0, PCC1, and CMC0 modules.
A7 clock management consists of SCG1, PCC2, PCC3, and CMC1 modules.
Note: this series only adds A7 clock domain support as M4 clock
domain will be handled by M4 seperately.
Change Log:
v1->v2:
* add enable/disable for the type of CLK_DIVIDER_ZERO_GATE dividers
* use clk_hw apis to register clocks
* use of_clk_add_hw_provider
* split the clocks register process into two parts: early part for possible
timers clocks registered by CLK_OF_DECLARE_DRIVER and the later part for
the left normal peripheral clocks registered by a platform driver.
Dong Aisheng (10):
clk: clk-divider: add CLK_DIVIDER_ZERO_GATE clk support
clk: reparent orphans after critical clocks enabled
clk: fractional-divider: add CLK_FRAC_DIVIDER_ZERO_BASED flag support
clk: imx: add pllv4 support
clk: imx: add pfdv2 support
clk: imx: add composite clk support
dt-bindings: clock: add imx7ulp clock binding doc
clk: imx: make mux parent strings const
clk: imx: implement new clk_hw based APIs
clk: imx: add imx7ulp clk driver
.../devicetree/bindings/clock/imx7ulp-clock.txt | 62 ++++++
drivers/clk/clk-divider.c | 100 ++++++++-
drivers/clk/clk-fractional-divider.c | 10 +
drivers/clk/clk.c | 39 ++--
drivers/clk/imx/Makefile | 6 +-
drivers/clk/imx/clk-busy.c | 2 +-
drivers/clk/imx/clk-composite.c | 90 ++++++++
drivers/clk/imx/clk-fixup-mux.c | 2 +-
drivers/clk/imx/clk-imx7ulp.c | 245 +++++++++++++++++++++
drivers/clk/imx/clk-pfdv2.c | 207 +++++++++++++++++
drivers/clk/imx/clk-pllv4.c | 188 ++++++++++++++++
drivers/clk/imx/clk.c | 22 ++
drivers/clk/imx/clk.h | 92 +++++++-
include/dt-bindings/clock/imx7ulp-clock.h | 108 +++++++++
include/linux/clk-provider.h | 17 ++
15 files changed, 1159 insertions(+), 31 deletions(-)
create mode 100644 Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
create mode 100644 drivers/clk/imx/clk-composite.c
create mode 100644 drivers/clk/imx/clk-imx7ulp.c
create mode 100644 drivers/clk/imx/clk-pfdv2.c
create mode 100644 drivers/clk/imx/clk-pllv4.c
create mode 100644 include/dt-bindings/clock/imx7ulp-clock.h
--
2.7.4
For dividers with zero indicating clock is disabled, instead of giving a
warning each time like "clkx: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not
set" in exist code, we'd like to introduce enable/disable function for it.
e.g.
000b - Clock disabled
001b - Divide by 1
010b - Divide by 2
...
Set rate when the clk is disabled will cache the rate request and only
when the clk is enabled will the driver actually program the hardware to
have the requested divider value. Similarly, when the clk is disabled we'll
write a 0 there, but when the clk is enabled we'll restore whatever rate
(divider) was chosen last.
It does mean that recalc rate will be sort of odd, because when the clk is
off it will return 0, and when the clk is on it will return the right rate.
So to make things work, we'll need to return the cached rate in recalc rate
when the clk is off and read the hardware when the clk is on.
NOTE for the default off divider, the recalc rate will still return 0 as
there's still no proper preset rate. Enable such divider will give user
a reminder error message.
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* add enable/disable for the type of CLK_DIVIDER_ZERO_GATE dividers
---
drivers/clk/clk-divider.c | 100 ++++++++++++++++++++++++++++++++++++++++++-
include/linux/clk-provider.h | 9 ++++
2 files changed, 107 insertions(+), 2 deletions(-)
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 9bb472c..55f8c41 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -123,6 +123,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
struct clk_divider *divider = to_clk_divider(hw);
unsigned int div;
+ if (flags & CLK_DIVIDER_ZERO_GATE && !val)
+ return 0;
+
div = _get_div(table, val, flags, divider->width);
if (!div) {
WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
@@ -141,8 +144,13 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
struct clk_divider *divider = to_clk_divider(hw);
unsigned int val;
- val = clk_readl(divider->reg) >> divider->shift;
- val &= div_mask(divider->width);
+ if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
+ !clk_hw_is_enabled(hw)) {
+ val = divider->cached_val;
+ } else {
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+ }
return divider_recalc_rate(hw, parent_rate, val, divider->table,
divider->flags);
@@ -392,6 +400,12 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
value = divider_get_val(rate, parent_rate, divider->table,
divider->width, divider->flags);
+ if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
+ !clk_hw_is_enabled(hw)) {
+ divider->cached_val = value;
+ return 0;
+ }
+
if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
else
@@ -414,10 +428,85 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
return 0;
}
+static int clk_divider_enable(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned long flags = 0;
+ u32 val;
+
+ if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
+ return 0;
+
+ if (!divider->cached_val) {
+ pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
+ return -EINVAL;
+ }
+
+ if (divider->lock)
+ spin_lock_irqsave(divider->lock, flags);
+ else
+ __acquire(divider->lock);
+
+ /* restore div val */
+ val = clk_readl(divider->reg);
+ val |= divider->cached_val << divider->shift;
+ clk_writel(val, divider->reg);
+
+ if (divider->lock)
+ spin_unlock_irqrestore(divider->lock, flags);
+ else
+ __release(divider->lock);
+
+ return 0;
+}
+
+static void clk_divider_disable(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned long flags = 0;
+ u32 val;
+
+ if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
+ return;
+
+ if (divider->lock)
+ spin_lock_irqsave(divider->lock, flags);
+ else
+ __acquire(divider->lock);
+
+ /* store the current div val */
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+ divider->cached_val = val;
+ clk_writel(0, divider->reg);
+
+ if (divider->lock)
+ spin_unlock_irqrestore(divider->lock, flags);
+ else
+ __release(divider->lock);
+}
+
+static int clk_divider_is_enabled(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ u32 val;
+
+ if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
+ return __clk_get_enable_count(hw->clk);
+
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+
+ return val ? 1 : 0;
+}
+
const struct clk_ops clk_divider_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
.set_rate = clk_divider_set_rate,
+ .enable = clk_divider_enable,
+ .disable = clk_divider_disable,
+ .is_enabled = clk_divider_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_divider_ops);
@@ -436,6 +525,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
struct clk_divider *div;
struct clk_hw *hw;
struct clk_init_data init;
+ u32 val;
int ret;
if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
@@ -468,6 +558,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
div->hw.init = &init;
div->table = table;
+ if (div->flags & CLK_DIVIDER_ZERO_GATE) {
+ val = clk_readl(reg) >> shift;
+ val &= div_mask(width);
+ div->cached_val = val;
+ }
+
/* register the clock */
hw = &div->hw;
ret = clk_hw_register(dev, hw);
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index c59c625..efaf5cf 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -357,6 +357,7 @@ struct clk_div_table {
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @table: array of value/divider pairs, last entry should have div = 0
+ * @cached_val: cached div hw value used for CLK_DIVIDER_ZERO_GATE
* @lock: register lock
*
* Clock with an adjustable divider affecting its output frequency. Implements
@@ -385,6 +386,12 @@ struct clk_div_table {
* CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
* except when the value read from the register is zero, the divisor is
* 2^width of the field.
+ * CLK_DIVIDER_ZERO_GATE - For dividers when the value read from the register
+ * is zero, it means the divisor is gated. For this case, the cached_val
+ * will be used to store the intermediate div for the normal rate
+ * operation, like set_rate/get_rate/recalc_rate. When the divider is
+ * ungated, the driver will actually program the hardware to have the
+ * requested divider value.
*/
struct clk_divider {
struct clk_hw hw;
@@ -393,6 +400,7 @@ struct clk_divider {
u8 width;
u8 flags;
const struct clk_div_table *table;
+ u32 cached_val;
spinlock_t *lock;
};
@@ -405,6 +413,7 @@ struct clk_divider {
#define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
#define CLK_DIVIDER_READ_ONLY BIT(5)
#define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
+#define CLK_DIVIDER_ZERO_GATE BIT(7)
extern const struct clk_ops clk_divider_ops;
extern const struct clk_ops clk_divider_ro_ops;
--
2.7.4
Adding CLK_FRAC_DIVIDER_ZERO_BASED flag to indicate the numerator and
denominator value in register are start from 0.
This can be used to support frac dividers like below:
Divider output clock = Divider input clock x [(frac +1) / (div +1)]
where frac/div in register is:
000b - Divide by 1.
001b - Divide by 2.
010b - Divide by 3.
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* improve comments suggested by Stephen
---
drivers/clk/clk-fractional-divider.c | 10 ++++++++++
include/linux/clk-provider.h | 8 ++++++++
2 files changed, 18 insertions(+)
diff --git a/drivers/clk/clk-fractional-divider.c b/drivers/clk/clk-fractional-divider.c
index aab9046..455bec5 100644
--- a/drivers/clk/clk-fractional-divider.c
+++ b/drivers/clk/clk-fractional-divider.c
@@ -40,6 +40,11 @@ static unsigned long clk_fd_recalc_rate(struct clk_hw *hw,
m = (val & fd->mmask) >> fd->mshift;
n = (val & fd->nmask) >> fd->nshift;
+ if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
+ m++;
+ n++;
+ }
+
if (!n || !m)
return parent_rate;
@@ -91,6 +96,11 @@ static int clk_fd_set_rate(struct clk_hw *hw, unsigned long rate,
GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
&m, &n);
+ if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
+ m--;
+ n--;
+ }
+
if (fd->lock)
spin_lock_irqsave(fd->lock, flags);
else
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index efaf5cf..4568a12 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -563,6 +563,12 @@ void clk_hw_unregister_fixed_factor(struct clk_hw *hw);
* @lock: register lock
*
* Clock with adjustable fractional divider affecting its output frequency.
+ *
+ * Flags:
+ * CLK_FRAC_DIVIDER_ZERO_BASED - by default the numerator and denominator
+ * is the value read from the register. If CLK_FRAC_DIVIDER_ZERO_BASED
+ * is set then the numerator and denominator are both the value read
+ * plus one.
*/
struct clk_fractional_divider {
struct clk_hw hw;
@@ -579,6 +585,8 @@ struct clk_fractional_divider {
#define to_clk_fd(_hw) container_of(_hw, struct clk_fractional_divider, hw)
+#define CLK_FRAC_DIVIDER_ZERO_BASED BIT(0)
+
extern const struct clk_ops clk_fractional_divider_ops;
struct clk *clk_register_fractional_divider(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
--
2.7.4
pllv4 is designed for System Clock Generation (SCG) module observed
in IMX ULP SoC series. e.g. i.MX7ULP.
The SCG modules generates clock used to derive processor, system,
peripheral bus and external memory interface clocks while this patch
intends to support the PLL part.
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Cc: Anson Huang <[email protected]>
Cc: Bai Ping <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* remove clk_pllv4_is_enabled() check in set_rate, instead it will
be handled by core later.
* use readl_poll_timeout
* use clk_hw_register instead of clk_register
* other minor changes
---
drivers/clk/imx/Makefile | 1 +
drivers/clk/imx/clk-pllv4.c | 188 ++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/imx/clk.h | 3 +
3 files changed, 192 insertions(+)
create mode 100644 drivers/clk/imx/clk-pllv4.c
diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile
index 1ada68a..1e308e2 100644
--- a/drivers/clk/imx/Makefile
+++ b/drivers/clk/imx/Makefile
@@ -10,6 +10,7 @@ obj-y += \
clk-pllv1.o \
clk-pllv2.o \
clk-pllv3.o \
+ clk-pllv4.o \
clk-pfd.o
obj-$(CONFIG_SOC_IMX1) += clk-imx1.o
diff --git a/drivers/clk/imx/clk-pllv4.c b/drivers/clk/imx/clk-pllv4.c
new file mode 100644
index 0000000..47e4d1c
--- /dev/null
+++ b/drivers/clk/imx/clk-pllv4.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ *
+ * Author: Dong Aisheng <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/slab.h>
+
+/* PLL Control Status Register (xPLLCSR) */
+#define PLL_CSR_OFFSET 0x0
+#define PLL_VLD BIT(24)
+#define PLL_EN BIT(0)
+
+/* PLL Configuration Register (xPLLCFG) */
+#define PLL_CFG_OFFSET 0x08
+#define BP_PLL_MULT 16
+#define BM_PLL_MULT (0x7f << 16)
+
+/* PLL Numerator Register (xPLLNUM) */
+#define PLL_NUM_OFFSET 0x10
+
+/* PLL Denominator Register (xPLLDENOM) */
+#define PLL_DENOM_OFFSET 0x14
+
+struct clk_pllv4 {
+ struct clk_hw hw;
+ void __iomem *base;
+};
+
+/* Valid PLL MULT Table */
+static const int pllv4_mult_table[] = {33, 27, 22, 20, 17, 16};
+
+#define to_clk_pllv4(__hw) container_of(__hw, struct clk_pllv4, hw)
+
+#define LOCK_TIMEOUT_US USEC_PER_MSEC
+
+static inline int clk_pllv4_wait_lock(struct clk_pllv4 *pll)
+{
+ u32 csr;
+
+ return readl_poll_timeout(pll->base + PLL_CSR_OFFSET,
+ csr, csr & PLL_VLD, 0, LOCK_TIMEOUT_US);
+}
+
+static int clk_pllv4_is_enabled(struct clk_hw *hw)
+{
+ struct clk_pllv4 *pll = to_clk_pllv4(hw);
+
+ if (readl_relaxed(pll->base) & PLL_EN)
+ return 1;
+
+ return 0;
+}
+
+static unsigned long clk_pllv4_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_pllv4 *pll = to_clk_pllv4(hw);
+ u32 div;
+
+ div = readl_relaxed(pll->base + PLL_CFG_OFFSET);
+ div &= BM_PLL_MULT;
+ div >>= BP_PLL_MULT;
+
+ return parent_rate * div;
+}
+
+static long clk_pllv4_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ unsigned long parent_rate = *prate;
+ unsigned long round_rate, i;
+
+ for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) {
+ round_rate = parent_rate * pllv4_mult_table[i];
+ if (rate >= round_rate)
+ return round_rate;
+ }
+
+ return round_rate;
+}
+
+static bool clk_pllv4_is_valid_mult(unsigned int mult)
+{
+ int i;
+
+ /* check if mult is in valid MULT table */
+ for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) {
+ if (pllv4_mult_table[i] == mult)
+ return true;
+ }
+
+ return false;
+}
+
+static int clk_pllv4_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_pllv4 *pll = to_clk_pllv4(hw);
+ u32 val, mult;
+
+ mult = rate / parent_rate;
+
+ if (clk_pllv4_is_valid_mult(mult))
+ return -EINVAL;
+
+ val = readl_relaxed(pll->base + PLL_CFG_OFFSET);
+ val &= ~BM_PLL_MULT;
+ val |= mult << BP_PLL_MULT;
+ writel_relaxed(val, pll->base + PLL_CFG_OFFSET);
+
+ return 0;
+}
+
+static int clk_pllv4_enable(struct clk_hw *hw)
+{
+ u32 val;
+ struct clk_pllv4 *pll = to_clk_pllv4(hw);
+
+ val = readl_relaxed(pll->base);
+ val |= PLL_EN;
+ writel_relaxed(val, pll->base);
+
+ return clk_pllv4_wait_lock(pll);
+}
+
+static void clk_pllv4_disable(struct clk_hw *hw)
+{
+ u32 val;
+ struct clk_pllv4 *pll = to_clk_pllv4(hw);
+
+ val = readl_relaxed(pll->base);
+ val &= ~PLL_EN;
+ writel_relaxed(val, pll->base);
+}
+
+static const struct clk_ops clk_pllv4_ops = {
+ .recalc_rate = clk_pllv4_recalc_rate,
+ .round_rate = clk_pllv4_round_rate,
+ .set_rate = clk_pllv4_set_rate,
+ .enable = clk_pllv4_enable,
+ .disable = clk_pllv4_disable,
+ .is_enabled = clk_pllv4_is_enabled,
+};
+
+struct clk_hw *imx_clk_pllv4(const char *name, const char *parent_name,
+ void __iomem *base)
+{
+ struct clk_pllv4 *pll;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->base = base;
+
+ init.name = name;
+ init.ops = &clk_pllv4_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ pll->hw.init = &init;
+
+ hw = &pll->hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(pll);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index d54f072..132c89f 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -41,6 +41,9 @@ enum imx_pllv3_type {
struct clk *imx_clk_pllv3(enum imx_pllv3_type type, const char *name,
const char *parent_name, void __iomem *base, u32 div_mask);
+struct clk_hw *imx_clk_pllv4(const char *name, const char *parent_name,
+ void __iomem *base);
+
struct clk *clk_register_gate2(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx, u8 cgr_val,
--
2.7.4
i.MX7ULP Clock functions are under joint control of the System
Clock Generation (SCG) modules, Peripheral Clock Control (PCC)
modules, and Core Mode Controller (CMC)1 blocks
The clocking scheme provides clear separation between M4 domain
and A7 domain. Except for a few clock sources shared between two
domains, such as the System Oscillator clock, the Slow IRC (SIRC),
and and the Fast IRC clock (FIRCLK), clock sources and clock
management are separated and contained within each domain.
M4 clock management consists of SCG0, PCC0, PCC1, and CMC0 modules.
A7 clock management consists of SCG1, PCC2, PCC3, and CMC1 modules.
This driver only adds clock support in A7 domain.
Note that most clocks required to be operated when gated, e.g. pll,
pfd, pcc. And more special cases that scs/ddr/nic mux selecting
different clock source requires that clock to be enabled first,
then we need set CLK_OPS_PARENT_ENABLE flag for them properly.
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Cc: Anson Huang <[email protected]>
Cc: Bai Ping <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* use of_clk_add_hw_provider instead
* split the clocks register process into two parts: early part for possible
timers clocks registered by CLK_OF_DECLARE_DRIVER and the later part for
the left normal peripheral clocks registered by a platform driver.
* correct some clocks with clk_div_table
---
drivers/clk/imx/Makefile | 1 +
drivers/clk/imx/clk-imx7ulp.c | 245 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 246 insertions(+)
create mode 100644 drivers/clk/imx/clk-imx7ulp.c
diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile
index bf001ce..6f013e0 100644
--- a/drivers/clk/imx/Makefile
+++ b/drivers/clk/imx/Makefile
@@ -27,4 +27,5 @@ obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o
obj-$(CONFIG_SOC_IMX6SX) += clk-imx6sx.o
obj-$(CONFIG_SOC_IMX6UL) += clk-imx6ul.o
obj-$(CONFIG_SOC_IMX7D) += clk-imx7d.o
+obj-$(CONFIG_SOC_IMX7ULP) += clk-imx7ulp.o
obj-$(CONFIG_SOC_VF610) += clk-vf610.o
diff --git a/drivers/clk/imx/clk-imx7ulp.c b/drivers/clk/imx/clk-imx7ulp.c
new file mode 100644
index 0000000..432b9c0
--- /dev/null
+++ b/drivers/clk/imx/clk-imx7ulp.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ *
+ * Author: Dong Aisheng <[email protected]>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <dt-bindings/clock/imx7ulp-clock.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "clk.h"
+
+static const char * const pll_pre_sels[] = { "sosc", "firc", };
+static const char * const spll_pfd_sels[] = { "spll_pfd0", "spll_pfd1", "spll_pfd2", "spll_pfd3", };
+static const char * const spll_sels[] = { "spll", "spll_pfd_sel", };
+static const char * const apll_pfd_sels[] = { "apll_pfd0", "apll_pfd1", "apll_pfd2", "apll_pfd3", };
+static const char * const apll_sels[] = { "apll", "apll_pfd_sel", };
+static const char * const scs_sels[] = { "dummy", "sosc", "sirc", "firc", "dummy", "apll_sel", "spll_sel", "upll", };
+static const char * const ddr_sels[] = { "apll_pfd_sel", "upll", };
+static const char * const nic_sels[] = { "firc", "ddr_clk", };
+static const char * const periph_plat_sels[] = { "dummy", "nic1_bus_clk", "nic1_clk", "ddr_clk", "apll_pfd2", "apll_pfd1", "apll_pfd0", "upll", };
+static const char * const periph_bus_sels[] = { "dummy", "sosc_bus_clk", "mpll", "firc_bus_clk", "rosc", "nic1_bus_clk", "nic1_clk", "spll_bus_clk", };
+
+static struct clk_hw_onecell_data *clk_data;
+
+/* used by sosc/sirc/firc/ddr/spll/apll dividers */
+static const struct clk_div_table ulp_div_table[] = {
+ { .val = 1, .div = 1, },
+ { .val = 2, .div = 2, },
+ { .val = 3, .div = 4, },
+ { .val = 4, .div = 8, },
+ { .val = 5, .div = 16, },
+ { .val = 6, .div = 32, },
+ { .val = 7, .div = 64, },
+ { }
+};
+
+static void __init imx7ulp_clocks_early_init(struct device_node *scg_node)
+{
+ struct device_node *np = scg_node;
+ void __iomem *base;
+ struct clk_hw **clks;
+
+ clk_data = kzalloc(sizeof(*clk_data) +
+ sizeof(*clk_data->hws) * IMX7ULP_CLK_END,
+ GFP_KERNEL);
+ if (!clk_data)
+ return;
+
+ clk_data->num = IMX7ULP_CLK_END;
+ clks = clk_data->hws;
+
+ clks[IMX7ULP_CLK_DUMMY] = imx_clk_hw_fixed("dummy", 0);
+
+ clks[IMX7ULP_CLK_ROSC] = imx_obtain_fixed_clk_hw(np, "rosc");
+ clks[IMX7ULP_CLK_SOSC] = imx_obtain_fixed_clk_hw(np, "sosc");
+ clks[IMX7ULP_CLK_SIRC] = imx_obtain_fixed_clk_hw(np, "sirc");
+ clks[IMX7ULP_CLK_FIRC] = imx_obtain_fixed_clk_hw(np, "firc");
+ clks[IMX7ULP_CLK_MIPI_PLL] = imx_obtain_fixed_clk_hw(np, "mpll");
+ clks[IMX7ULP_CLK_UPLL] = imx_obtain_fixed_clk_hw(np, "upll");
+
+ /* SCG1 */
+ base = of_iomap(np, 0);
+ WARN_ON(!base);
+
+ /* NOTE: xPLL config can't be changed when xPLL is enabled */
+ clks[IMX7ULP_CLK_APLL_PRE_SEL] = imx_clk_hw_mux_flags("apll_pre_sel", base + 0x508, 0, 1, pll_pre_sels, ARRAY_SIZE(pll_pre_sels), CLK_SET_PARENT_GATE);
+ clks[IMX7ULP_CLK_SPLL_PRE_SEL] = imx_clk_hw_mux_flags("spll_pre_sel", base + 0x608, 0, 1, pll_pre_sels, ARRAY_SIZE(pll_pre_sels), CLK_SET_PARENT_GATE);
+
+ /* name parent_name reg shift width flags */
+ clks[IMX7ULP_CLK_APLL_PRE_DIV] = imx_clk_hw_divider_flags("apll_pre_div", "apll_pre_sel", base + 0x508, 8, 3, CLK_SET_RATE_GATE);
+ clks[IMX7ULP_CLK_SPLL_PRE_DIV] = imx_clk_hw_divider_flags("spll_pre_div", "spll_pre_sel", base + 0x608, 8, 3, CLK_SET_RATE_GATE);
+
+ /* name parent_name base */
+ clks[IMX7ULP_CLK_APLL] = imx_clk_pllv4("apll", "apll_pre_div", base + 0x500);
+ clks[IMX7ULP_CLK_SPLL] = imx_clk_pllv4("spll", "spll_pre_div", base + 0x600);
+
+ /* APLL PFDs */
+ clks[IMX7ULP_CLK_APLL_PFD0] = imx_clk_pfdv2("apll_pfd0", "apll", base + 0x50c, 0);
+ clks[IMX7ULP_CLK_APLL_PFD1] = imx_clk_pfdv2("apll_pfd1", "apll", base + 0x50c, 1);
+ clks[IMX7ULP_CLK_APLL_PFD2] = imx_clk_pfdv2("apll_pfd2", "apll", base + 0x50c, 2);
+ clks[IMX7ULP_CLK_APLL_PFD3] = imx_clk_pfdv2("apll_pfd3", "apll", base + 0x50c, 3);
+
+ /* SPLL PFDs */
+ clks[IMX7ULP_CLK_SPLL_PFD0] = imx_clk_pfdv2("spll_pfd0", "spll", base + 0x60C, 0);
+ clks[IMX7ULP_CLK_SPLL_PFD1] = imx_clk_pfdv2("spll_pfd1", "spll", base + 0x60C, 1);
+ clks[IMX7ULP_CLK_SPLL_PFD2] = imx_clk_pfdv2("spll_pfd2", "spll", base + 0x60C, 2);
+ clks[IMX7ULP_CLK_SPLL_PFD3] = imx_clk_pfdv2("spll_pfd3", "spll", base + 0x60C, 3);
+
+ /* PLL Mux */
+ clks[IMX7ULP_CLK_APLL_PFD_SEL] = imx_clk_hw_mux_flags("apll_pfd_sel", base + 0x508, 14, 2, apll_pfd_sels, ARRAY_SIZE(apll_pfd_sels), CLK_SET_RATE_PARENT | CLK_SET_PARENT_GATE);
+ clks[IMX7ULP_CLK_SPLL_PFD_SEL] = imx_clk_hw_mux_flags("spll_pfd_sel", base + 0x608, 14, 2, spll_pfd_sels, ARRAY_SIZE(spll_pfd_sels), CLK_SET_RATE_PARENT | CLK_SET_PARENT_GATE);
+ clks[IMX7ULP_CLK_APLL_SEL] = imx_clk_hw_mux_flags("apll_sel", base + 0x508, 1, 1, apll_sels, ARRAY_SIZE(apll_sels), CLK_SET_RATE_PARENT | CLK_SET_PARENT_GATE);
+ clks[IMX7ULP_CLK_SPLL_SEL] = imx_clk_hw_mux_flags("spll_sel", base + 0x608, 1, 1, spll_sels, ARRAY_SIZE(spll_sels), CLK_SET_RATE_PARENT | CLK_SET_PARENT_GATE);
+
+ clks[IMX7ULP_CLK_SPLL_BUS_CLK] = clk_hw_register_divider_table(NULL, "spll_bus_clk", "spll_sel", 0, base + 0x604, 8, 3,
+ CLK_DIVIDER_ZERO_GATE, ulp_div_table, &imx_ccm_lock);
+
+ /* scs/ddr/nic select different clock source requires that clock to be enabled first */
+ clks[IMX7ULP_CLK_SYS_SEL] = imx_clk_hw_mux2("scs_sel", base + 0x14, 24, 4, scs_sels, ARRAY_SIZE(scs_sels));
+ clks[IMX7ULP_CLK_NIC_SEL] = imx_clk_hw_mux2("nic_sel", base + 0x40, 28, 1, nic_sels, ARRAY_SIZE(nic_sels));
+ clks[IMX7ULP_CLK_DDR_SEL] = imx_clk_hw_mux_flags("ddr_sel", base + 0x30, 24, 1, ddr_sels, ARRAY_SIZE(ddr_sels), CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE);
+
+ clks[IMX7ULP_CLK_CORE_DIV] = imx_clk_hw_divider_flags("divcore", "scs_sel", base + 0x14, 16, 4, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+
+ clks[IMX7ULP_CLK_DDR_DIV] = clk_hw_register_divider_table(NULL, "ddr_clk", "ddr_sel", CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, base + 0x30, 0, 3,
+ CLK_DIVIDER_ZERO_GATE, ulp_div_table, &imx_ccm_lock);
+
+ clks[IMX7ULP_CLK_NIC0_DIV] = imx_clk_hw_divider_flags("nic0_clk", "nic_sel", base + 0x40, 24, 4, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+ clks[IMX7ULP_CLK_NIC1_DIV] = imx_clk_hw_divider_flags("nic1_clk", "nic0_clk", base + 0x40, 16, 4, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+ clks[IMX7ULP_CLK_NIC1_BUS_DIV] = imx_clk_hw_divider_flags("nic1_bus_clk", "nic1_clk", base + 0x40, 4, 4, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+
+ clks[IMX7ULP_CLK_SOSC_BUS_CLK] = clk_hw_register_divider_table(NULL, "sosc_bus_clk", "sosc", 0, base + 0x104, 8, 3,
+ CLK_DIVIDER_READ_ONLY | CLK_DIVIDER_ZERO_GATE, ulp_div_table, &imx_ccm_lock);
+ clks[IMX7ULP_CLK_FIRC_BUS_CLK] = clk_hw_register_divider_table(NULL, "firc_bus_clk", "firc", 0, base + 0x304, 8, 3,
+ CLK_DIVIDER_READ_ONLY | CLK_DIVIDER_ZERO_GATE, ulp_div_table, &imx_ccm_lock);
+
+ /* PCC2 */
+ base = of_iomap(np, 1);
+ WARN_ON(!base);
+
+ clks[IMX7ULP_CLK_LPTPM4] = imx_clk_composite("lptpm4", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x94);
+ clks[IMX7ULP_CLK_LPTPM5] = imx_clk_composite("lptpm5", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x98);
+ clks[IMX7ULP_CLK_LPIT1] = imx_clk_composite("lpit1", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x9c);
+
+ /* PCC3 */
+ base = of_iomap(np, 2);
+ WARN_ON(!base);
+
+ clks[IMX7ULP_CLK_LPTPM6] = imx_clk_composite("lptpm6", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x84);
+ clks[IMX7ULP_CLK_LPTPM7] = imx_clk_composite("lptpm7", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x88);
+
+ clks[IMX7ULP_CLK_MMDC] = clk_hw_register_gate(NULL, "mmdc", "nic1_clk", CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ base + 0xac, 30, 0, &imx_ccm_lock);
+
+ imx_check_clk_hws(clks, clk_data->num);
+
+ of_clk_add_hw_provider(scg_node, of_clk_hw_onecell_get, clk_data);
+}
+CLK_OF_DECLARE_DRIVER(imx7ulp, "fsl,imx7ulp-clock", imx7ulp_clocks_early_init);
+
+static const struct of_device_id imx7ulp_clk_dt_ids[] = {
+ { .compatible = "fsl,imx7ulp-clock" },
+ { /* sentinel */ }
+};
+
+static int imx7ulp_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct clk_hw **clks = clk_data->hws;
+ struct resource *res;
+ void __iomem *base;
+
+ /* PCC2 */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "scg1");
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ clks[IMX7ULP_CLK_GPU_DIV] = imx_clk_hw_divider("gpu_clk", "nic0_clk", base + 0x40, 20, 4);
+
+ /* PCC2 */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcc2");
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ clks[IMX7ULP_CLK_DMA1] = imx_clk_hw_gate("dma1", "nic1_clk", base + 0x20, 30);
+ clks[IMX7ULP_CLK_RGPIO2P1] = imx_clk_hw_gate("rgpio2p1", "nic1_bus_clk", base + 0x3c, 30);
+ clks[IMX7ULP_CLK_DMA_MUX1] = imx_clk_hw_gate("dma_mux1", "nic1_bus_clk", base + 0x84, 30);
+ clks[IMX7ULP_CLK_SNVS] = imx_clk_hw_gate("snvs", "nic1_bus_clk", base + 0x8c, 30);
+ clks[IMX7ULP_CLK_CAAM] = imx_clk_hw_gate("caam", "nic1_clk", base + 0x90, 30);
+ clks[IMX7ULP_CLK_LPSPI2] = imx_clk_composite("lpspi2", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xa4);
+ clks[IMX7ULP_CLK_LPSPI3] = imx_clk_composite("lpspi3", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xa8);
+ clks[IMX7ULP_CLK_LPI2C4] = imx_clk_composite("lpi2c4", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xac);
+ clks[IMX7ULP_CLK_LPI2C5] = imx_clk_composite("lpi2c5", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xb0);
+ clks[IMX7ULP_CLK_LPUART4] = imx_clk_composite("lpuart4", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xb4);
+ clks[IMX7ULP_CLK_LPUART5] = imx_clk_composite("lpuart5", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xb8);
+ clks[IMX7ULP_CLK_FLEXIO1] = imx_clk_composite("flexio1", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0xc4);
+ clks[IMX7ULP_CLK_USB0] = imx_clk_composite("usb0", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, true, true, base + 0xcc);
+ clks[IMX7ULP_CLK_USB1] = imx_clk_composite("usb1", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, true, true, base + 0xd0);
+ clks[IMX7ULP_CLK_USB_PHY] = imx_clk_hw_gate("usb_phy", "nic1_bus_clk", base + 0xD4, 30);
+ clks[IMX7ULP_CLK_USDHC0] = imx_clk_composite("usdhc0", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, true, true, base + 0xdc);
+ clks[IMX7ULP_CLK_USDHC1] = imx_clk_composite("usdhc1", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, true, true, base + 0xe0);
+ clks[IMX7ULP_CLK_WDG1] = imx_clk_composite("wdg1", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, true, true, base + 0xf4);
+ clks[IMX7ULP_CLK_WDG2] = imx_clk_composite("sdg2", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, true, true, base + 0x10c);
+
+ /* PCC3 */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcc3");
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ clks[IMX7ULP_CLK_LPI2C6] = imx_clk_composite("lpi2c6", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x90);
+ clks[IMX7ULP_CLK_LPI2C7] = imx_clk_composite("lpi2c7", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x94);
+ clks[IMX7ULP_CLK_LPUART6] = imx_clk_composite("lpuart6", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x98);
+ clks[IMX7ULP_CLK_LPUART7] = imx_clk_composite("lpuart7", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, false, true, base + 0x9c);
+ clks[IMX7ULP_CLK_DSI] = imx_clk_composite("dsi", periph_bus_sels, ARRAY_SIZE(periph_bus_sels), true, true, true, base + 0xa4);
+ clks[IMX7ULP_CLK_LCDIF] = imx_clk_composite("lcdif", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, true, true, base + 0xa8);
+
+ clks[IMX7ULP_CLK_VIU] = imx_clk_hw_gate("viu", "nic1_clk", base + 0xa0, 30);
+ clks[IMX7ULP_CLK_PCTLC] = imx_clk_hw_gate("pctlc", "nic1_bus_clk", base + 0xb8, 30);
+ clks[IMX7ULP_CLK_PCTLD] = imx_clk_hw_gate("pctld", "nic1_bus_clk", base + 0xbc, 30);
+ clks[IMX7ULP_CLK_PCTLE] = imx_clk_hw_gate("pctle", "nic1_bus_clk", base + 0xc0, 30);
+ clks[IMX7ULP_CLK_PCTLF] = imx_clk_hw_gate("pctlf", "nic1_bus_clk", base + 0xc4, 30);
+
+ clks[IMX7ULP_CLK_GPU3D] = imx_clk_composite("gpu3d", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, false, true, base + 0x140);
+ clks[IMX7ULP_CLK_GPU2D] = imx_clk_composite("gpu2d", periph_plat_sels, ARRAY_SIZE(periph_plat_sels), true, false, true, base + 0x144);
+
+ imx_check_clk_hws(clks, clk_data->num);
+
+ of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
+
+ pr_debug("i.MX7ULP clock tree init done.\n");
+
+ return 0;
+}
+
+static struct platform_driver imx7ulp_clk_driver = {
+ .driver = {
+ .name = "imx7ulp-clock",
+ .of_match_table = imx7ulp_clk_dt_ids,
+ },
+ .probe = imx7ulp_clk_probe,
+};
+
+static int __init imx7ulp_clk_init(void)
+{
+ return platform_driver_register(&imx7ulp_clk_driver);
+}
+core_initcall(imx7ulp_clk_init);
--
2.7.4
Clock providers are recommended to use the new struct clk_hw based API,
so implement IMX clk_hw based provider helpers functions to the new
approach.
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2: new patches
---
drivers/clk/imx/clk.c | 22 ++++++++++++++++++
drivers/clk/imx/clk.h | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 84 insertions(+)
diff --git a/drivers/clk/imx/clk.c b/drivers/clk/imx/clk.c
index a634b11..0474306 100644
--- a/drivers/clk/imx/clk.c
+++ b/drivers/clk/imx/clk.c
@@ -17,6 +17,16 @@ void __init imx_check_clocks(struct clk *clks[], unsigned int count)
i, PTR_ERR(clks[i]));
}
+void imx_check_clk_hws(struct clk_hw *clks[], unsigned int count)
+{
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ if (IS_ERR(clks[i]))
+ pr_err("i.MX clk %u: register failed with %ld\n",
+ i, PTR_ERR(clks[i]));
+}
+
static struct clk * __init imx_obtain_fixed_clock_from_dt(const char *name)
{
struct of_phandle_args phandle;
@@ -48,6 +58,18 @@ struct clk * __init imx_obtain_fixed_clock(
return clk;
}
+struct clk_hw * __init imx_obtain_fixed_clk_hw(struct device_node *np,
+ const char *name)
+{
+ struct clk *clk;
+
+ clk = of_clk_get_by_name(np, name);
+ if (IS_ERR(clk))
+ return ERR_PTR(-ENOENT);
+
+ return __clk_get_hw(clk);
+}
+
/*
* This fixups the register CCM_CSCMR1 write value.
* The write/read/divider values of the aclk_podf field
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index 2646cc3..b5ad15f 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -7,6 +7,7 @@
extern spinlock_t imx_ccm_lock;
void imx_check_clocks(struct clk *clks[], unsigned int count);
+void imx_check_clk_hws(struct clk_hw *clks[], unsigned int count);
void imx_register_uart_clocks(struct clk ** const clks[]);
extern void imx_cscmr1_fixup(u32 *val);
@@ -53,6 +54,9 @@ struct clk *clk_register_gate2(struct device *dev, const char *name,
struct clk * imx_obtain_fixed_clock(
const char *name, unsigned long rate);
+struct clk_hw *imx_obtain_fixed_clk_hw(struct device_node *np,
+ const char *name);
+
struct clk *imx_clk_gate_exclusive(const char *name, const char *parent,
void __iomem *reg, u8 shift, u32 exclusive_mask);
@@ -89,6 +93,16 @@ static inline struct clk *imx_clk_fixed(const char *name, int rate)
return clk_register_fixed_rate(NULL, name, NULL, 0, rate);
}
+static inline struct clk_hw *imx_clk_hw_fixed(const char *name, int rate)
+{
+ return clk_hw_register_fixed_rate(NULL, name, NULL, 0, rate);
+}
+
+static inline struct clk_hw *imx_get_clk_hw_fixed(const char *name, int rate)
+{
+ return clk_hw_register_fixed_rate(NULL, name, NULL, 0, rate);
+}
+
static inline struct clk *imx_clk_mux_ldb(const char *name, void __iomem *reg,
u8 shift, u8 width, const char * const *parents,
int num_parents)
@@ -112,6 +126,15 @@ static inline struct clk *imx_clk_divider(const char *name, const char *parent,
reg, shift, width, 0, &imx_ccm_lock);
}
+static inline struct clk_hw *imx_clk_hw_divider(const char *name,
+ const char *parent,
+ void __iomem *reg, u8 shift,
+ u8 width)
+{
+ return clk_hw_register_divider(NULL, name, parent, CLK_SET_RATE_PARENT,
+ reg, shift, width, 0, &imx_ccm_lock);
+}
+
static inline struct clk *imx_clk_divider_flags(const char *name,
const char *parent, void __iomem *reg, u8 shift, u8 width,
unsigned long flags)
@@ -120,6 +143,15 @@ static inline struct clk *imx_clk_divider_flags(const char *name,
reg, shift, width, 0, &imx_ccm_lock);
}
+static inline struct clk_hw *imx_clk_hw_divider_flags(const char *name,
+ const char *parent,
+ void __iomem *reg, u8 shift,
+ u8 width, unsigned long flags)
+{
+ return clk_hw_register_divider(NULL, name, parent, flags,
+ reg, shift, width, 0, &imx_ccm_lock);
+}
+
static inline struct clk *imx_clk_divider2(const char *name, const char *parent,
void __iomem *reg, u8 shift, u8 width)
{
@@ -135,6 +167,13 @@ static inline struct clk *imx_clk_gate(const char *name, const char *parent,
shift, 0, &imx_ccm_lock);
}
+static inline struct clk_hw *imx_clk_hw_gate(const char *name, const char *parent,
+ void __iomem *reg, u8 shift)
+{
+ return clk_hw_register_gate(NULL, name, parent, CLK_SET_RATE_PARENT, reg,
+ shift, 0, &imx_ccm_lock);
+}
+
static inline struct clk *imx_clk_gate_dis(const char *name, const char *parent,
void __iomem *reg, u8 shift)
{
@@ -207,6 +246,17 @@ static inline struct clk *imx_clk_mux2(const char *name, void __iomem *reg,
reg, shift, width, 0, &imx_ccm_lock);
}
+static inline struct clk_hw *imx_clk_hw_mux2(const char *name, void __iomem *reg,
+ u8 shift, u8 width,
+ const char * const *parents,
+ int num_parents)
+{
+ return clk_hw_register_mux(NULL, name, parents, num_parents,
+ CLK_SET_RATE_NO_REPARENT |
+ CLK_OPS_PARENT_ENABLE,
+ reg, shift, width, 0, &imx_ccm_lock);
+}
+
static inline struct clk *imx_clk_mux_flags(const char *name,
void __iomem *reg, u8 shift, u8 width,
const char * const *parents, int num_parents,
@@ -217,6 +267,18 @@ static inline struct clk *imx_clk_mux_flags(const char *name,
&imx_ccm_lock);
}
+static inline struct clk_hw *imx_clk_hw_mux_flags(const char *name,
+ void __iomem *reg, u8 shift,
+ u8 width,
+ const char * const *parents,
+ int num_parents,
+ unsigned long flags)
+{
+ return clk_hw_register_mux(NULL, name, parents, num_parents,
+ flags | CLK_SET_RATE_NO_REPARENT,
+ reg, shift, width, 0, &imx_ccm_lock);
+}
+
struct clk *imx_clk_cpu(const char *name, const char *parent_name,
struct clk *div, struct clk *mux, struct clk *pll,
struct clk *step);
--
2.7.4
As the commit 2893c379461a ("clk: make strings in parent name arrays
const"), let's make the parent strings const, otherwise we may meet
the following warning when compiling:
drivers/clk/imx/clk-imx7ulp.c: In function 'imx7ulp_clocks_init':
drivers/clk/imx/clk-imx7ulp.c:73:35: warning: passing argument 5 of
'imx_clk_mux_flags' discards 'const' qualifier from pointer target type
clks[IMX7ULP_CLK_APLL_PRE_SEL] = imx_clk_mux_flags("apll_pre_sel", base + 0x508, 0,
1, pll_pre_sels, ARRAY_SIZE(pll_pre_sels), CLK_SET_PARENT_GATE);
^
In file included from drivers/clk/imx/clk-imx7ulp.c:23:0:
drivers/clk/imx/clk.h:200:27: note: expected 'const char **' but argument is
of type 'const char * const*'
...
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2: no changes
---
drivers/clk/imx/clk-busy.c | 2 +-
drivers/clk/imx/clk-fixup-mux.c | 2 +-
drivers/clk/imx/clk.h | 18 +++++++++++-------
3 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/drivers/clk/imx/clk-busy.c b/drivers/clk/imx/clk-busy.c
index 5cc9959..097625c 100644
--- a/drivers/clk/imx/clk-busy.c
+++ b/drivers/clk/imx/clk-busy.c
@@ -154,7 +154,7 @@ static struct clk_ops clk_busy_mux_ops = {
struct clk *imx_clk_busy_mux(const char *name, void __iomem *reg, u8 shift,
u8 width, void __iomem *busy_reg, u8 busy_shift,
- const char **parent_names, int num_parents)
+ const char * const *parent_names, int num_parents)
{
struct clk_busy_mux *busy;
struct clk *clk;
diff --git a/drivers/clk/imx/clk-fixup-mux.c b/drivers/clk/imx/clk-fixup-mux.c
index c9b327e..44817c1 100644
--- a/drivers/clk/imx/clk-fixup-mux.c
+++ b/drivers/clk/imx/clk-fixup-mux.c
@@ -70,7 +70,7 @@ static const struct clk_ops clk_fixup_mux_ops = {
};
struct clk *imx_clk_fixup_mux(const char *name, void __iomem *reg,
- u8 shift, u8 width, const char **parents,
+ u8 shift, u8 width, const char * const *parents,
int num_parents, void (*fixup)(u32 *val))
{
struct clk_fixup_mux *fixup_mux;
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index 702faba..2646cc3 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -68,7 +68,7 @@ struct clk *imx_clk_busy_divider(const char *name, const char *parent_name,
struct clk *imx_clk_busy_mux(const char *name, void __iomem *reg, u8 shift,
u8 width, void __iomem *busy_reg, u8 busy_shift,
- const char **parent_names, int num_parents);
+ const char * const *parent_names, int num_parents);
struct clk_hw *imx_clk_composite(const char *name,
const char * const *parent_names,
@@ -81,7 +81,7 @@ struct clk *imx_clk_fixup_divider(const char *name, const char *parent,
void (*fixup)(u32 *val));
struct clk *imx_clk_fixup_mux(const char *name, void __iomem *reg,
- u8 shift, u8 width, const char **parents,
+ u8 shift, u8 width, const char * const *parents,
int num_parents, void (*fixup)(u32 *val));
static inline struct clk *imx_clk_fixed(const char *name, int rate)
@@ -90,7 +90,8 @@ static inline struct clk *imx_clk_fixed(const char *name, int rate)
}
static inline struct clk *imx_clk_mux_ldb(const char *name, void __iomem *reg,
- u8 shift, u8 width, const char **parents, int num_parents)
+ u8 shift, u8 width, const char * const *parents,
+ int num_parents)
{
return clk_register_mux(NULL, name, parents, num_parents,
CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, reg,
@@ -189,7 +190,8 @@ static inline struct clk *imx_clk_gate4(const char *name, const char *parent,
}
static inline struct clk *imx_clk_mux(const char *name, void __iomem *reg,
- u8 shift, u8 width, const char **parents, int num_parents)
+ u8 shift, u8 width, const char * const *parents,
+ int num_parents)
{
return clk_register_mux(NULL, name, parents, num_parents,
CLK_SET_RATE_NO_REPARENT, reg, shift,
@@ -197,7 +199,8 @@ static inline struct clk *imx_clk_mux(const char *name, void __iomem *reg,
}
static inline struct clk *imx_clk_mux2(const char *name, void __iomem *reg,
- u8 shift, u8 width, const char **parents, int num_parents)
+ u8 shift, u8 width, const char * const *parents,
+ int num_parents)
{
return clk_register_mux(NULL, name, parents, num_parents,
CLK_SET_RATE_NO_REPARENT | CLK_OPS_PARENT_ENABLE,
@@ -205,8 +208,9 @@ static inline struct clk *imx_clk_mux2(const char *name, void __iomem *reg,
}
static inline struct clk *imx_clk_mux_flags(const char *name,
- void __iomem *reg, u8 shift, u8 width, const char **parents,
- int num_parents, unsigned long flags)
+ void __iomem *reg, u8 shift, u8 width,
+ const char * const *parents, int num_parents,
+ unsigned long flags)
{
return clk_register_mux(NULL, name, parents, num_parents,
flags | CLK_SET_RATE_NO_REPARENT, reg, shift, width, 0,
--
2.7.4
i.MX7ULP Clock functions are under joint control of the System
Clock Generation (SCG) modules, Peripheral Clock Control (PCC)
modules, and Core Mode Controller (CMC)1 blocks
Note IMX7ULP has two clock domains: M4 and A7. This binding doc
is only for A7 clock domain.
Cc: Rob Herring <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Cc: Anson Huang <[email protected]>
Cc: Bai Ping <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2: no changes
---
.../devicetree/bindings/clock/imx7ulp-clock.txt | 62 ++++++++++++
include/dt-bindings/clock/imx7ulp-clock.h | 108 +++++++++++++++++++++
2 files changed, 170 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
create mode 100644 include/dt-bindings/clock/imx7ulp-clock.h
diff --git a/Documentation/devicetree/bindings/clock/imx7ulp-clock.txt b/Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
new file mode 100644
index 0000000..76ea3c7
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
@@ -0,0 +1,62 @@
+* Clock bindings for Freescale i.MX7ULP
+
+i.MX7ULP Clock functions are under joint control of the System
+Clock Generation (SCG) modules, Peripheral Clock Control (PCC)
+modules, and Core Mode Controller (CMC)1 blocks
+
+The clocking scheme provides clear separation between M4 domain
+and A7 domain. Except for a few clock sources shared between two
+domains, such as the System Oscillator clock, the Slow IRC (SIRC),
+and and the Fast IRC clock (FIRCLK), clock sources and clock
+management are separated and contained within each domain.
+
+M4 clock management consists of SCG0, PCC0, PCC1, and CMC0 modules.
+A7 clock management consists of SCG1, PCC2, PCC3, and CMC1 modules.
+
+Note: this binding doc is only for A7 clock domain.
+
+Required properties:
+
+- compatible: Should be "fsl,imx7ulp-clock".
+- reg : Should contain registers location and length for scg1,
+ pcc2 and pcc3.
+- reg-names: Should contain the according reg names "scg1", "pcc2"
+ and "pcc3".
+- #clock-cells: Should be <1>.
+- clocks: Should contain the fixed input clocks.
+- clock-name: Should contain the following clock names:"rsoc", "sosc",
+ "sirc", "firc", "upll", "mpll".
+
+The clock consumer should specify the desired clock by having the clock
+ID in its "clocks" phandle cell.
+See include/dt-bindings/clock/imx7ulp-clock.h
+for the full list of i.MX7ULP clock IDs.
+
+Examples:
+
+#include <dt-bindings/clock/imx7ulp-clock.h>
+
+clks: scg1@403e0000 {
+ compatible = "fsl,imx7ulp-clock";
+ reg = <0x403e0000 0x10000>
+ <0x403f0000 0x10000>
+ <0x40b30000 0x10000>;
+ reg-names = "scg1", "pcc2", "pcc3";
+ clocks = <&rsoc>, <&sosc>, <&sirc>,
+ <&firc>, <&upll>, <&mpll>;
+ clock-names = "rsoc", "sosc", "sirc",
+ "firc", "upll", "mpll";
+ #clock-cells = <1>;
+};
+
+usdhc1: usdhc@40380000 {
+ compatible = "fsl,imx7ulp-usdhc";
+ reg = <0x40380000 0x10000>;
+ interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clks IMX7ULP_CLK_NIC1_BUS_DIV>,
+ <&clks IMX7ULP_CLK_NIC1_DIV>,
+ <&clks IMX7ULP_CLK_USDHC1>;
+ clock-names ="ipg", "ahb", "per";
+ bus-width = <4>;
+ status = "disabled";
+};
diff --git a/include/dt-bindings/clock/imx7ulp-clock.h b/include/dt-bindings/clock/imx7ulp-clock.h
new file mode 100644
index 0000000..2556106
--- /dev/null
+++ b/include/dt-bindings/clock/imx7ulp-clock.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_IMX7ULP_H
+#define __DT_BINDINGS_CLOCK_IMX7ULP_H
+
+#define IMX7ULP_CLK_DUMMY 0
+#define IMX7ULP_CLK_ROSC 1
+#define IMX7ULP_CLK_SOSC 2
+#define IMX7ULP_CLK_FIRC 3
+
+/* SCG1 */
+#define IMX7ULP_CLK_SPLL_PRE_SEL 4
+#define IMX7ULP_CLK_SPLL_PRE_DIV 5
+#define IMX7ULP_CLK_SPLL 6
+#define IMX7ULP_CLK_SPLL_POST_DIV1 7
+#define IMX7ULP_CLK_SPLL_POST_DIV2 8
+#define IMX7ULP_CLK_SPLL_PFD0 9
+#define IMX7ULP_CLK_SPLL_PFD1 10
+#define IMX7ULP_CLK_SPLL_PFD2 11
+#define IMX7ULP_CLK_SPLL_PFD3 12
+#define IMX7ULP_CLK_SPLL_PFD_SEL 13
+#define IMX7ULP_CLK_SPLL_SEL 14
+#define IMX7ULP_CLK_APLL_PRE_SEL 15
+#define IMX7ULP_CLK_APLL_PRE_DIV 16
+#define IMX7ULP_CLK_APLL 17
+#define IMX7ULP_CLK_APLL_POST_DIV1 18
+#define IMX7ULP_CLK_APLL_POST_DIV2 19
+#define IMX7ULP_CLK_APLL_PFD0 20
+#define IMX7ULP_CLK_APLL_PFD1 21
+#define IMX7ULP_CLK_APLL_PFD2 22
+#define IMX7ULP_CLK_APLL_PFD3 23
+#define IMX7ULP_CLK_APLL_PFD_SEL 24
+#define IMX7ULP_CLK_APLL_SEL 25
+#define IMX7ULP_CLK_UPLL 26
+#define IMX7ULP_CLK_SYS_SEL 27
+#define IMX7ULP_CLK_CORE_DIV 28
+#define IMX7ULP_CLK_BUS_DIV 29
+#define IMX7ULP_CLK_PLAT_DIV 30
+#define IMX7ULP_CLK_DDR_SEL 31
+#define IMX7ULP_CLK_DDR_DIV 32
+#define IMX7ULP_CLK_NIC_SEL 33
+#define IMX7ULP_CLK_NIC0_DIV 34
+#define IMX7ULP_CLK_GPU_DIV 35
+#define IMX7ULP_CLK_NIC1_DIV 36
+#define IMX7ULP_CLK_NIC1_BUS_DIV 37
+#define IMX7ULP_CLK_NIC1_EXT_DIV 38
+
+/* PCG2 */
+#define IMX7ULP_CLK_DMA1 39
+#define IMX7ULP_CLK_RGPIO2P1 40
+#define IMX7ULP_CLK_FLEXBUS 41
+#define IMX7ULP_CLK_SEMA42_1 42
+#define IMX7ULP_CLK_DMA_MUX1 43
+#define IMX7ULP_CLK_SNVS 44
+#define IMX7ULP_CLK_CAAM 45
+#define IMX7ULP_CLK_LPTPM4 46
+#define IMX7ULP_CLK_LPTPM5 47
+#define IMX7ULP_CLK_LPIT1 48
+#define IMX7ULP_CLK_LPSPI2 49
+#define IMX7ULP_CLK_LPSPI3 50
+#define IMX7ULP_CLK_LPI2C4 51
+#define IMX7ULP_CLK_LPI2C5 52
+#define IMX7ULP_CLK_LPUART4 53
+#define IMX7ULP_CLK_LPUART5 54
+#define IMX7ULP_CLK_FLEXIO1 55
+#define IMX7ULP_CLK_USB0 56
+#define IMX7ULP_CLK_USB1 57
+#define IMX7ULP_CLK_USB_PHY 58
+#define IMX7ULP_CLK_USB_PL301 59
+#define IMX7ULP_CLK_USDHC0 60
+#define IMX7ULP_CLK_USDHC1 61
+#define IMX7ULP_CLK_WDG1 62
+#define IMX7ULP_CLK_WDG2 63
+
+/* PCG3 */
+#define IMX7ULP_CLK_LPTPM6 64
+#define IMX7ULP_CLK_LPTPM7 65
+#define IMX7ULP_CLK_LPI2C6 66
+#define IMX7ULP_CLK_LPI2C7 67
+#define IMX7ULP_CLK_LPUART6 68
+#define IMX7ULP_CLK_LPUART7 69
+#define IMX7ULP_CLK_VIU 70
+#define IMX7ULP_CLK_DSI 71
+#define IMX7ULP_CLK_LCDIF 72
+#define IMX7ULP_CLK_MMDC 73
+#define IMX7ULP_CLK_PCTLC 74
+#define IMX7ULP_CLK_PCTLD 75
+#define IMX7ULP_CLK_PCTLE 76
+#define IMX7ULP_CLK_PCTLF 77
+#define IMX7ULP_CLK_GPU3D 78
+#define IMX7ULP_CLK_GPU2D 79
+#define IMX7ULP_CLK_MIPI_PLL 80
+#define IMX7ULP_CLK_SIRC 81
+#define IMX7ULP_CLK_SOSC_BUS_CLK 82
+#define IMX7ULP_CLK_FIRC_BUS_CLK 83
+#define IMX7ULP_CLK_SPLL_BUS_CLK 84
+
+#define IMX7ULP_CLK_END 85
+
+#endif /* __DT_BINDINGS_CLOCK_IMX7ULP_H */
--
2.7.4
The orphan clocks reparent operation should be moved after the critical
clocks enabled, otherwise it may get a chance to disable a newly
registered critical clock which triggers the following warning.
Assuming we have two clocks: A and B, B is the parent of A.
Clock A has flag: CLK_OPS_PARENT_ENABLE
Clock B has flag: CLK_IS_CRITICAL
Step 1:
Clock A is registered, then it becomes orphan.
Step 2:
Clock B is registered. Before clock B reach the critical clock enable
operation, orphan A will find the newly registered parent B and do
reparent operation, then parent B will be finally disabled in
__clk_set_parent_after() due to CLK_OPS_PARENT_ENABLE flag as there's
still no users of B which will then trigger the following warning.
[ 0.000000] WARNING: CPU: 0 PID: 0 at drivers/clk/clk.c:597 clk_core_disable+0xb4/0xe0
[ 0.000000] Modules linked in:
[ 0.000000] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.11.0-rc1-00056-gdff1f66-dirty #1373
[ 0.000000] Hardware name: Generic DT based system
[ 0.000000] Backtrace:
[ 0.000000] [<c010c4bc>] (dump_backtrace) from [<c010c764>] (show_stack+0x18/0x1c)
[ 0.000000] r6:600000d3 r5:00000000 r4:c0e26358 r3:00000000
[ 0.000000] [<c010c74c>] (show_stack) from [<c040599c>] (dump_stack+0xb4/0xe8)
[ 0.000000] [<c04058e8>] (dump_stack) from [<c0125c94>] (__warn+0xd8/0x104)
[ 0.000000] r10:c0c21cd0 r9:c048aa78 r8:00000255 r7:00000009 r6:c0c1cd90 r5:00000000
[ 0.000000] r4:00000000 r3:c0e01d34
[ 0.000000] [<c0125bbc>] (__warn) from [<c0125d74>] (warn_slowpath_null+0x28/0x30)
[ 0.000000] r9:00000000 r8:ef00bf80 r7:c165ac4c r6:ef00bf80 r5:ef00bf80 r4:ef00bf80
[ 0.000000] [<c0125d4c>] (warn_slowpath_null) from [<c048aa78>] (clk_core_disable+0xb4/0xe0)
[ 0.000000] [<c048a9c4>] (clk_core_disable) from [<c048be88>] (clk_core_disable_lock+0x20/0x2c)
[ 0.000000] r4:000000d3 r3:c0e0af00
[ 0.000000] [<c048be68>] (clk_core_disable_lock) from [<c048c224>] (clk_core_disable_unprepare+0x14/0x28)
[ 0.000000] r5:00000000 r4:ef00bf80
[ 0.000000] [<c048c210>] (clk_core_disable_unprepare) from [<c048c270>] (__clk_set_parent_after+0x38/0x54)
[ 0.000000] r4:ef00bd80 r3:000010a0
[ 0.000000] [<c048c238>] (__clk_set_parent_after) from [<c048daa8>] (clk_register+0x4d0/0x648)
[ 0.000000] r6:ef00d500 r5:ef00bf80 r4:ef00bd80 r3:ef00bfd4
[ 0.000000] [<c048d5d8>] (clk_register) from [<c048dc30>] (clk_hw_register+0x10/0x1c)
[ 0.000000] r9:00000000 r8:00000003 r7:00000000 r6:00000824 r5:00000001 r4:ef00d500
[ 0.000000] [<c048dc20>] (clk_hw_register) from [<c048e698>] (_register_divider+0xcc/0x120)
[ 0.000000] [<c048e5cc>] (_register_divider) from [<c048e730>] (clk_register_divider+0x44/0x54)
[ 0.000000] r10:00000004 r9:00000003 r8:00000001 r7:00000000 r6:00000003 r5:00000001
[ 0.000000] r4:f0810030
[ 0.000000] [<c048e6ec>] (clk_register_divider) from [<c0d3ff58>] (imx7ulp_clocks_init+0x558/0xe98)
[ 0.000000] r7:c0e296f8 r6:c165c808 r5:00000000 r4:c165c808
[ 0.000000] [<c0d3fa00>] (imx7ulp_clocks_init) from [<c0d24db0>] (of_clk_init+0x118/0x1e0)
[ 0.000000] r10:00000001 r9:c0e01f68 r8:00000000 r7:c0e01f60 r6:ef7f8974 r5:ef0035c0
[ 0.000000] r4:00000006
[ 0.000000] [<c0d24c98>] (of_clk_init) from [<c0d04a50>] (time_init+0x2c/0x38)
[ 0.000000] r10:efffed40 r9:c0d61a48 r8:c0e78000 r7:c0e07900 r6:ffffffff r5:c0e78000
[ 0.000000] r4:00000000
[ 0.000000] [<c0d04a24>] (time_init) from [<c0d00b8c>] (start_kernel+0x218/0x394)
[ 0.000000] [<c0d00974>] (start_kernel) from [<6000807c>] (0x6000807c)
[ 0.000000] r10:00000000 r9:410fc075 r8:6000406a r7:c0e0c930 r6:c0d61a44 r5:c0e07918
[ 0.000000] r4:c0e78294
[ 0.000000] ---[ end trace 0000000000000000 ]---
Fixes: fc8726a2c021 ("clk: core: support clocks which requires parents enable (part 2)")
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* add more detailed commit messages
---
drivers/clk/clk.c | 39 ++++++++++++++++++++-------------------
1 file changed, 20 insertions(+), 19 deletions(-)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index fc58c52..e2955b1 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -2471,25 +2471,6 @@ static int __clk_core_init(struct clk_core *core)
core->rate = core->req_rate = rate;
/*
- * walk the list of orphan clocks and reparent any that newly finds a
- * parent.
- */
- hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
- struct clk_core *parent = __clk_init_parent(orphan);
-
- /*
- * we could call __clk_set_parent, but that would result in a
- * redundant call to the .set_rate op, if it exists
- */
- if (parent) {
- __clk_set_parent_before(orphan, parent);
- __clk_set_parent_after(orphan, parent, NULL);
- __clk_recalc_accuracies(orphan);
- __clk_recalc_rates(orphan, 0);
- }
- }
-
- /*
* optional platform-specific magic
*
* The .init callback is not used by any of the basic clock types, but
@@ -2511,6 +2492,26 @@ static int __clk_core_init(struct clk_core *core)
}
kref_init(&core->ref);
+
+ /*
+ * walk the list of orphan clocks and reparent any that newly finds a
+ * parent.
+ */
+ hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
+ struct clk_core *parent = __clk_init_parent(orphan);
+
+ /*
+ * we could call __clk_set_parent, but that would result in a
+ * redundant call to the .set_rate op, if it exists
+ */
+ if (parent) {
+ __clk_set_parent_before(orphan, parent);
+ __clk_set_parent_after(orphan, parent, NULL);
+ __clk_recalc_accuracies(orphan);
+ __clk_recalc_rates(orphan, 0);
+ }
+ }
+
out:
clk_prepare_unlock();
--
2.7.4
The pfdv2 is designed for PLL Fractional Divide (PFD) observed in System
Clock Generation (SCG) module in IMX ULP SoC series. e.g. i.MX7ULP.
NOTE pfdv2 can only be operated when clk is gated.
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Cc: Anson Huang <[email protected]>
Cc: Bai Ping <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* change to readl_poll_timeout
* add pfd lock to protect share reg access between rate and enable/disable
operations and multiple pfd instances.
* use clk_hw_register
---
drivers/clk/imx/Makefile | 3 +-
drivers/clk/imx/clk-pfdv2.c | 207 ++++++++++++++++++++++++++++++++++++++++++++
drivers/clk/imx/clk.h | 3 +
3 files changed, 212 insertions(+), 1 deletion(-)
create mode 100644 drivers/clk/imx/clk-pfdv2.c
diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile
index 1e308e2..ea4ef27 100644
--- a/drivers/clk/imx/Makefile
+++ b/drivers/clk/imx/Makefile
@@ -11,7 +11,8 @@ obj-y += \
clk-pllv2.o \
clk-pllv3.o \
clk-pllv4.o \
- clk-pfd.o
+ clk-pfd.o \
+ clk-pfdv2.o
obj-$(CONFIG_SOC_IMX1) += clk-imx1.o
obj-$(CONFIG_SOC_IMX21) += clk-imx21.o
diff --git a/drivers/clk/imx/clk-pfdv2.c b/drivers/clk/imx/clk-pfdv2.c
new file mode 100644
index 0000000..5421bdf
--- /dev/null
+++ b/drivers/clk/imx/clk-pfdv2.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ *
+ * Author: Dong Aisheng <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/slab.h>
+
+/**
+ * struct clk_pfdv2 - IMX PFD clock
+ * @clk_hw: clock source
+ * @reg: PFD register address
+ * @gate_bit: Gate bit offset
+ * @vld_bit: Valid bit offset
+ * @frac_off: PLL Fractional Divider offset
+ */
+
+struct clk_pfdv2 {
+ struct clk_hw hw;
+ void __iomem *reg;
+ u8 gate_bit;
+ u8 vld_bit;
+ u8 frac_off;
+};
+
+#define to_clk_pfdv2(_hw) container_of(_hw, struct clk_pfdv2, hw)
+
+#define CLK_PFDV2_FRAC_MASK 0x3f
+
+#define LOCK_TIMEOUT_US USEC_PER_MSEC
+
+static DEFINE_SPINLOCK(pfd_lock);
+
+static int clk_pfdv2_wait(struct clk_pfdv2 *pfd)
+{
+ u32 val;
+
+ return readl_poll_timeout(pfd->reg, val, val & pfd->vld_bit,
+ 0, LOCK_TIMEOUT_US);
+}
+
+static int clk_pfdv2_enable(struct clk_hw *hw)
+{
+ struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&pfd_lock, flags);
+ val = readl_relaxed(pfd->reg);
+ val &= ~pfd->gate_bit;
+ writel_relaxed(val, pfd->reg);
+ spin_unlock_irqrestore(&pfd_lock, flags);
+
+ return clk_pfdv2_wait(pfd);
+}
+
+static void clk_pfdv2_disable(struct clk_hw *hw)
+{
+ struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&pfd_lock, flags);
+ val = readl_relaxed(pfd->reg);
+ val |= pfd->gate_bit;
+ writel_relaxed(val, pfd->reg);
+ spin_unlock_irqrestore(&pfd_lock, flags);
+}
+
+static unsigned long clk_pfdv2_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
+ u64 tmp = parent_rate;
+ u8 frac;
+
+ frac = (readl_relaxed(pfd->reg) >> pfd->frac_off)
+ & CLK_PFDV2_FRAC_MASK;
+
+ if (!frac) {
+ pr_debug("clk_pfdv2: %s invalid pfd frac value 0\n",
+ clk_hw_get_name(hw));
+ return 0;
+ }
+
+ tmp *= 18;
+ do_div(tmp, frac);
+
+ return tmp;
+}
+
+static long clk_pfdv2_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ u64 tmp = *prate;
+ u8 frac;
+
+ tmp = tmp * 18 + rate / 2;
+ do_div(tmp, rate);
+ frac = tmp;
+
+ if (frac < 12)
+ frac = 12;
+ else if (frac > 35)
+ frac = 35;
+
+ tmp = *prate;
+ tmp *= 18;
+ do_div(tmp, frac);
+
+ return tmp;
+}
+
+static int clk_pfdv2_is_enabled(struct clk_hw *hw)
+{
+ struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
+
+ if (readl_relaxed(pfd->reg) & pfd->gate_bit)
+ return 0;
+
+ return 1;
+}
+
+static int clk_pfdv2_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
+ unsigned long flags;
+ u64 tmp = parent_rate;
+ u32 val;
+ u8 frac;
+
+ tmp = tmp * 18 + rate / 2;
+ do_div(tmp, rate);
+ frac = tmp;
+ if (frac < 12)
+ frac = 12;
+ else if (frac > 35)
+ frac = 35;
+
+ spin_lock_irqsave(&pfd_lock, flags);
+ val = readl_relaxed(pfd->reg);
+ val &= ~(CLK_PFDV2_FRAC_MASK << pfd->frac_off);
+ val |= frac << pfd->frac_off;
+ writel_relaxed(val, pfd->reg);
+ spin_unlock_irqrestore(&pfd_lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops clk_pfdv2_ops = {
+ .enable = clk_pfdv2_enable,
+ .disable = clk_pfdv2_disable,
+ .recalc_rate = clk_pfdv2_recalc_rate,
+ .round_rate = clk_pfdv2_round_rate,
+ .set_rate = clk_pfdv2_set_rate,
+ .is_enabled = clk_pfdv2_is_enabled,
+};
+
+struct clk_hw *imx_clk_pfdv2(const char *name, const char *parent_name,
+ void __iomem *reg, u8 idx)
+{
+ struct clk_init_data init;
+ struct clk_pfdv2 *pfd;
+ struct clk_hw *hw;
+ int ret;
+
+ WARN_ON(idx > 3);
+
+ pfd = kzalloc(sizeof(*pfd), GFP_KERNEL);
+ if (!pfd)
+ return ERR_PTR(-ENOMEM);
+
+ pfd->reg = reg;
+ pfd->gate_bit = 1 << ((idx + 1) * 8 - 1);
+ pfd->vld_bit = pfd->gate_bit - 1;
+ pfd->frac_off = idx * 8;
+
+ init.name = name;
+ init.ops = &clk_pfdv2_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ pfd->hw.init = &init;
+
+ hw = &pfd->hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(pfd);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index 132c89f..cf3ea92 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -59,6 +59,9 @@ struct clk *imx_clk_gate_exclusive(const char *name, const char *parent,
struct clk *imx_clk_pfd(const char *name, const char *parent_name,
void __iomem *reg, u8 idx);
+struct clk_hw *imx_clk_pfdv2(const char *name, const char *parent_name,
+ void __iomem *reg, u8 idx);
+
struct clk *imx_clk_busy_divider(const char *name, const char *parent_name,
void __iomem *reg, u8 shift, u8 width,
void __iomem *busy_reg, u8 busy_shift);
--
2.7.4
The imx composite clk is designed for Peripheral Clock Control (PCC)
module observed in IMX ULP SoC series. e.g. i.MX7ULP.
NOTE pcc can only be operated when clk is gated.
Cc: Stephen Boyd <[email protected]>
Cc: Michael Turquette <[email protected]>
Cc: Shawn Guo <[email protected]>
Cc: Anson Huang <[email protected]>
Cc: Bai Ping <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
ChangeLog:
v1->v2:
* remove an unneeded blank line change
* use clk_hw_register
---
drivers/clk/imx/Makefile | 1 +
drivers/clk/imx/clk-composite.c | 90 +++++++++++++++++++++++++++++++++++++++++
drivers/clk/imx/clk.h | 6 +++
3 files changed, 97 insertions(+)
create mode 100644 drivers/clk/imx/clk-composite.c
diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile
index ea4ef27..bf001ce 100644
--- a/drivers/clk/imx/Makefile
+++ b/drivers/clk/imx/Makefile
@@ -3,6 +3,7 @@ obj-y += \
clk.o \
clk-busy.o \
clk-cpu.o \
+ clk-composite.o \
clk-fixup-div.o \
clk-fixup-mux.o \
clk-gate-exclusive.o \
diff --git a/drivers/clk/imx/clk-composite.c b/drivers/clk/imx/clk-composite.c
new file mode 100644
index 0000000..78b891e
--- /dev/null
+++ b/drivers/clk/imx/clk-composite.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#define PCG_PCS_SHIFT 24
+#define PCG_PCS_MASK 0x7
+#define PCG_CGC_SHIFT 30
+#define PCG_FRAC_SHIFT 3
+#define PCG_FRAC_WIDTH 1
+#define PCG_FRAC_MASK BIT(3)
+#define PCG_PCD_SHIFT 0
+#define PCG_PCD_WIDTH 3
+#define PCG_PCD_MASK 0x7
+
+struct clk_hw *imx_clk_composite(const char *name,
+ const char * const *parent_names,
+ int num_parents, bool mux_present,
+ bool rate_present, bool gate_present,
+ void __iomem *reg)
+{
+ struct clk_hw *mux_hw = NULL, *fd_hw = NULL, *gate_hw = NULL;
+ struct clk_fractional_divider *fd = NULL;
+ struct clk_gate *gate = NULL;
+ struct clk_mux *mux = NULL;
+ struct clk_hw *hw;
+
+ if (mux_present) {
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+ mux_hw = &mux->hw;
+ mux->reg = reg;
+ mux->shift = PCG_PCS_SHIFT;
+ mux->mask = PCG_PCS_MASK;
+ }
+
+ if (rate_present) {
+ fd = kzalloc(sizeof(*fd), GFP_KERNEL);
+ if (!fd) {
+ kfree(mux);
+ return ERR_PTR(-ENOMEM);
+ }
+ fd_hw = &fd->hw;
+ fd->reg = reg;
+ fd->mshift = PCG_FRAC_SHIFT;
+ fd->mwidth = PCG_FRAC_WIDTH;
+ fd->mmask = PCG_FRAC_MASK;
+ fd->nshift = PCG_PCD_SHIFT;
+ fd->nwidth = PCG_PCD_WIDTH;
+ fd->nmask = PCG_PCD_MASK;
+ fd->flags = CLK_FRAC_DIVIDER_ZERO_BASED;
+ }
+
+ if (gate_present) {
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate) {
+ kfree(mux);
+ kfree(fd);
+ return ERR_PTR(-ENOMEM);
+ }
+ gate_hw = &gate->hw;
+ gate->reg = reg;
+ gate->bit_idx = PCG_CGC_SHIFT;
+ }
+
+ hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
+ mux_hw, &clk_mux_ops, fd_hw,
+ &clk_fractional_divider_ops, gate_hw,
+ &clk_gate_ops, CLK_SET_RATE_GATE |
+ CLK_SET_PARENT_GATE);
+ if (IS_ERR(hw)) {
+ kfree(mux);
+ kfree(fd);
+ kfree(gate);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index cf3ea92..702faba 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -70,6 +70,12 @@ struct clk *imx_clk_busy_mux(const char *name, void __iomem *reg, u8 shift,
u8 width, void __iomem *busy_reg, u8 busy_shift,
const char **parent_names, int num_parents);
+struct clk_hw *imx_clk_composite(const char *name,
+ const char * const *parent_names,
+ int num_parents, bool mux_present,
+ bool rate_present, bool gate_present,
+ void __iomem *reg);
+
struct clk *imx_clk_fixup_divider(const char *name, const char *parent,
void __iomem *reg, u8 shift, u8 width,
void (*fixup)(u32 *val));
--
2.7.4
Ping...
> -----Original Message-----
> From: Dong Aisheng [mailto:[email protected]]
> Sent: Thursday, July 13, 2017 7:47 PM
> To: [email protected]
> Cc: [email protected]; [email protected];
> [email protected]; [email protected]; A.s. Dong;
> [email protected]; [email protected]; Anson Huang; Jacky Bai
> Subject: [PATCH V2 00/10] clk: add imx7ulp clk support
>
> This patch series intends to add imx7ulp clk support.
>
> i.MX7ULP Clock functions are under joint control of the System Clock
> Generation (SCG) modules, Peripheral Clock Control (PCC) modules, and Core
> Mode Controller (CMC)1 blocks
>
> The clocking scheme provides clear separation between M4 domain and A7
> domain. Except for a few clock sources shared between two domains, such as
> the System Oscillator clock, the Slow IRC (SIRC), and and the Fast IRC
> clock (FIRCLK), clock sources and clock management are separated and
> contained within each domain.
>
> M4 clock management consists of SCG0, PCC0, PCC1, and CMC0 modules.
> A7 clock management consists of SCG1, PCC2, PCC3, and CMC1 modules.
>
> Note: this series only adds A7 clock domain support as M4 clock domain
> will be handled by M4 seperately.
>
> Change Log:
> v1->v2:
> * add enable/disable for the type of CLK_DIVIDER_ZERO_GATE dividers
> * use clk_hw apis to register clocks
> * use of_clk_add_hw_provider
> * split the clocks register process into two parts: early part for
> possible
> timers clocks registered by CLK_OF_DECLARE_DRIVER and the later part
> for
> the left normal peripheral clocks registered by a platform driver.
>
> Dong Aisheng (10):
> clk: clk-divider: add CLK_DIVIDER_ZERO_GATE clk support
> clk: reparent orphans after critical clocks enabled
> clk: fractional-divider: add CLK_FRAC_DIVIDER_ZERO_BASED flag support
> clk: imx: add pllv4 support
> clk: imx: add pfdv2 support
> clk: imx: add composite clk support
> dt-bindings: clock: add imx7ulp clock binding doc
> clk: imx: make mux parent strings const
> clk: imx: implement new clk_hw based APIs
> clk: imx: add imx7ulp clk driver
>
> .../devicetree/bindings/clock/imx7ulp-clock.txt | 62 ++++++
> drivers/clk/clk-divider.c | 100 ++++++++-
> drivers/clk/clk-fractional-divider.c | 10 +
> drivers/clk/clk.c | 39 ++--
> drivers/clk/imx/Makefile | 6 +-
> drivers/clk/imx/clk-busy.c | 2 +-
> drivers/clk/imx/clk-composite.c | 90 ++++++++
> drivers/clk/imx/clk-fixup-mux.c | 2 +-
> drivers/clk/imx/clk-imx7ulp.c | 245
> +++++++++++++++++++++
> drivers/clk/imx/clk-pfdv2.c | 207
> +++++++++++++++++
> drivers/clk/imx/clk-pllv4.c | 188 ++++++++++++++++
> drivers/clk/imx/clk.c | 22 ++
> drivers/clk/imx/clk.h | 92 +++++++-
> include/dt-bindings/clock/imx7ulp-clock.h | 108 +++++++++
> include/linux/clk-provider.h | 17 ++
> 15 files changed, 1159 insertions(+), 31 deletions(-) create mode 100644
> Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
> create mode 100644 drivers/clk/imx/clk-composite.c create mode 100644
> drivers/clk/imx/clk-imx7ulp.c create mode 100644 drivers/clk/imx/clk-
> pfdv2.c create mode 100644 drivers/clk/imx/clk-pllv4.c create mode
> 100644 include/dt-bindings/clock/imx7ulp-clock.h
>
> --
> 2.7.4
> -----Original Message-----
> From: A.s. Dong
> Sent: Wednesday, July 26, 2017 10:57 AM
> To: A.s. Dong; [email protected]
> Cc: [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; Anson Huang; Jacky Bai
> Subject: RE: [PATCH V2 00/10] clk: add imx7ulp clk support
>
> Ping...
>
Gently ping again...
Hi Stephen,
This has been more there without comments for more than one month.
Would you help review when you're available later?
Regards
Dong Aisheng
> > -----Original Message-----
> > From: Dong Aisheng [mailto:[email protected]]
> > Sent: Thursday, July 13, 2017 7:47 PM
> > To: [email protected]
> > Cc: [email protected];
> > [email protected];
> > [email protected]; [email protected]; A.s. Dong;
> > [email protected]; [email protected]; Anson Huang; Jacky Bai
> > Subject: [PATCH V2 00/10] clk: add imx7ulp clk support
> >
> > This patch series intends to add imx7ulp clk support.
> >
> > i.MX7ULP Clock functions are under joint control of the System Clock
> > Generation (SCG) modules, Peripheral Clock Control (PCC) modules, and
> > Core Mode Controller (CMC)1 blocks
> >
> > The clocking scheme provides clear separation between M4 domain and A7
> > domain. Except for a few clock sources shared between two domains,
> > such as the System Oscillator clock, the Slow IRC (SIRC), and and the
> > Fast IRC clock (FIRCLK), clock sources and clock management are
> > separated and contained within each domain.
> >
> > M4 clock management consists of SCG0, PCC0, PCC1, and CMC0 modules.
> > A7 clock management consists of SCG1, PCC2, PCC3, and CMC1 modules.
> >
> > Note: this series only adds A7 clock domain support as M4 clock domain
> > will be handled by M4 seperately.
> >
> > Change Log:
> > v1->v2:
> > * add enable/disable for the type of CLK_DIVIDER_ZERO_GATE dividers
> > * use clk_hw apis to register clocks
> > * use of_clk_add_hw_provider
> > * split the clocks register process into two parts: early part for
> > possible
> > timers clocks registered by CLK_OF_DECLARE_DRIVER and the later
> > part for
> > the left normal peripheral clocks registered by a platform driver.
> >
> > Dong Aisheng (10):
> > clk: clk-divider: add CLK_DIVIDER_ZERO_GATE clk support
> > clk: reparent orphans after critical clocks enabled
> > clk: fractional-divider: add CLK_FRAC_DIVIDER_ZERO_BASED flag support
> > clk: imx: add pllv4 support
> > clk: imx: add pfdv2 support
> > clk: imx: add composite clk support
> > dt-bindings: clock: add imx7ulp clock binding doc
> > clk: imx: make mux parent strings const
> > clk: imx: implement new clk_hw based APIs
> > clk: imx: add imx7ulp clk driver
> >
> > .../devicetree/bindings/clock/imx7ulp-clock.txt | 62 ++++++
> > drivers/clk/clk-divider.c | 100 ++++++++-
> > drivers/clk/clk-fractional-divider.c | 10 +
> > drivers/clk/clk.c | 39 ++--
> > drivers/clk/imx/Makefile | 6 +-
> > drivers/clk/imx/clk-busy.c | 2 +-
> > drivers/clk/imx/clk-composite.c | 90 ++++++++
> > drivers/clk/imx/clk-fixup-mux.c | 2 +-
> > drivers/clk/imx/clk-imx7ulp.c | 245
> > +++++++++++++++++++++
> > drivers/clk/imx/clk-pfdv2.c | 207
> > +++++++++++++++++
> > drivers/clk/imx/clk-pllv4.c | 188
> ++++++++++++++++
> > drivers/clk/imx/clk.c | 22 ++
> > drivers/clk/imx/clk.h | 92 +++++++-
> > include/dt-bindings/clock/imx7ulp-clock.h | 108 +++++++++
> > include/linux/clk-provider.h | 17 ++
> > 15 files changed, 1159 insertions(+), 31 deletions(-) create mode
> > 100644 Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
> > create mode 100644 drivers/clk/imx/clk-composite.c create mode
> > 100644 drivers/clk/imx/clk-imx7ulp.c create mode 100644
> > drivers/clk/imx/clk- pfdv2.c create mode 100644
> > drivers/clk/imx/clk-pllv4.c create mode
> > 100644 include/dt-bindings/clock/imx7ulp-clock.h
> >
> > --
> > 2.7.4
Hi Stephen,
On Thu, Jul 13, 2017 at 07:47:05PM +0800, Dong Aisheng wrote:
> This patch series intends to add imx7ulp clk support.
>
> i.MX7ULP Clock functions are under joint control of the System
> Clock Generation (SCG) modules, Peripheral Clock Control (PCC)
> modules, and Core Mode Controller (CMC)1 blocks
>
> The clocking scheme provides clear separation between M4 domain
> and A7 domain. Except for a few clock sources shared between two
> domains, such as the System Oscillator clock, the Slow IRC (SIRC),
> and and the Fast IRC clock (FIRCLK), clock sources and clock
> management are separated and contained within each domain.
>
> M4 clock management consists of SCG0, PCC0, PCC1, and CMC0 modules.
> A7 clock management consists of SCG1, PCC2, PCC3, and CMC1 modules.
>
> Note: this series only adds A7 clock domain support as M4 clock
> domain will be handled by M4 seperately.
>
> Change Log:
> v1->v2:
> * add enable/disable for the type of CLK_DIVIDER_ZERO_GATE dividers
> * use clk_hw apis to register clocks
> * use of_clk_add_hw_provider
> * split the clocks register process into two parts: early part for possible
> timers clocks registered by CLK_OF_DECLARE_DRIVER and the later part for
> the left normal peripheral clocks registered by a platform driver.
Would you please help review this new series when you're free?
This has been pending for a long time.
Regards
Dong Aisheng
>
> Dong Aisheng (10):
> clk: clk-divider: add CLK_DIVIDER_ZERO_GATE clk support
> clk: reparent orphans after critical clocks enabled
> clk: fractional-divider: add CLK_FRAC_DIVIDER_ZERO_BASED flag support
> clk: imx: add pllv4 support
> clk: imx: add pfdv2 support
> clk: imx: add composite clk support
> dt-bindings: clock: add imx7ulp clock binding doc
> clk: imx: make mux parent strings const
> clk: imx: implement new clk_hw based APIs
> clk: imx: add imx7ulp clk driver
>
> .../devicetree/bindings/clock/imx7ulp-clock.txt | 62 ++++++
> drivers/clk/clk-divider.c | 100 ++++++++-
> drivers/clk/clk-fractional-divider.c | 10 +
> drivers/clk/clk.c | 39 ++--
> drivers/clk/imx/Makefile | 6 +-
> drivers/clk/imx/clk-busy.c | 2 +-
> drivers/clk/imx/clk-composite.c | 90 ++++++++
> drivers/clk/imx/clk-fixup-mux.c | 2 +-
> drivers/clk/imx/clk-imx7ulp.c | 245 +++++++++++++++++++++
> drivers/clk/imx/clk-pfdv2.c | 207 +++++++++++++++++
> drivers/clk/imx/clk-pllv4.c | 188 ++++++++++++++++
> drivers/clk/imx/clk.c | 22 ++
> drivers/clk/imx/clk.h | 92 +++++++-
> include/dt-bindings/clock/imx7ulp-clock.h | 108 +++++++++
> include/linux/clk-provider.h | 17 ++
> 15 files changed, 1159 insertions(+), 31 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/clock/imx7ulp-clock.txt
> create mode 100644 drivers/clk/imx/clk-composite.c
> create mode 100644 drivers/clk/imx/clk-imx7ulp.c
> create mode 100644 drivers/clk/imx/clk-pfdv2.c
> create mode 100644 drivers/clk/imx/clk-pllv4.c
> create mode 100644 include/dt-bindings/clock/imx7ulp-clock.h
>
> --
> 2.7.4
>
On Thu, Nov 02, 2017 at 12:50:39AM -0700, Stephen Boyd wrote:
> On 07/13, Dong Aisheng wrote:
> > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> > index 9bb472c..55f8c41 100644
> > --- a/drivers/clk/clk-divider.c
> > +++ b/drivers/clk/clk-divider.c
> > @@ -123,6 +123,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> > struct clk_divider *divider = to_clk_divider(hw);
> > unsigned int div;
> >
> > + if (flags & CLK_DIVIDER_ZERO_GATE && !val)
> > + return 0;
> > +
> > div = _get_div(table, val, flags, divider->width);
> > if (!div) {
> > WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> > @@ -141,8 +144,13 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> > struct clk_divider *divider = to_clk_divider(hw);
> > unsigned int val;
> >
> > - val = clk_readl(divider->reg) >> divider->shift;
> > - val &= div_mask(divider->width);
> > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > + !clk_hw_is_enabled(hw)) {
>
> This seems racy. Are we holding the register lock here?
>
Would you please clarify what type of racy you mean?
Currently it only protects register write between set_rate and enable/disable,
and other register read are not protected.
e.g. in recalc_rate and is_enabled.
And i did see similar users, e.g.
drivers/clk/sunxi-ng/ccu_mult.c
Should we still need protect them here?
> > + val = divider->cached_val;
> > + } else {
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > + }
> >
> > return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > divider->flags);
> > @@ -392,6 +400,12 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > value = divider_get_val(rate, parent_rate, divider->table,
> > divider->width, divider->flags);
> >
> > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > + !clk_hw_is_enabled(hw)) {
>
> Same racy comment here.
>
> > + divider->cached_val = value;
> > + return 0;
> > + }
> > +
> > if (divider->lock)
> > spin_lock_irqsave(divider->lock, flags);
> > else
> > @@ -414,10 +428,85 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > return 0;
> > }
> >
> > +static int clk_divider_enable(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned long flags = 0;
> > + u32 val;
> > +
> > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > + return 0;
>
> This is not good. We will always jump to these functions on
> enable/disable for a divider although 99.9% of all dividers that
> exist won't need to run this code at all.
>
I absolutely understand this concern.
> Can you please move this logic into your own divider
> implementation? The flag can be added to the generic layer if
> necessary but I'd prefer to see this logic kept in the driver
> that uses it. If we get more than one driver doing the cached
> divider thing then we can think about moving it to the more
> generic place like here, but for now we should be able to keep
> this contained away from the basic types and handled by the
> quirky driver that needs it.
>
If only for above issue, how about invent a clk_divider_gate_ops
to separate the users of normal divider and zero gate divider:
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 4ed516c..b51f3f9 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -125,6 +125,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
div = _get_div(table, val, flags, divider->width);
if (!div) {
+ if (flags & CLK_DIVIDER_ZERO_GATE)
+ return 0;
+
WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
"%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
clk_hw_get_name(hw));
@@ -148,6 +151,23 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
divider->flags);
}
+static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned int val;
+
+ if (!clk_hw_is_enabled(hw)) {
+ val = divider->cached_val;
+ } else {
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+ }
+
+ return divider_recalc_rate(hw, parent_rate, val, divider->table,
+ divider->flags);
+}
+
static bool _is_valid_table_div(const struct clk_div_table *table,
unsigned int div)
{
@@ -416,6 +436,89 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
return 0;
}
+static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ int value;
+
+ if (!clk_hw_is_enabled(hw)) {
+ value = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, divider->flags);
+ if (value < 0)
+ return value;
+
+ divider->cached_val = value;
+
+ return 0;
+ }
+
+ return clk_divider_set_rate(hw, rate, parent_rate);
+}
+
+static int clk_divider_enable(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned long uninitialized_var(flags);
+ u32 val;
+
+ if (!divider->cached_val) {
+ pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
+ return -EINVAL;
+ }
+
+ if (divider->lock)
+ spin_lock_irqsave(divider->lock, flags);
+ else
+ __acquire(divider->lock);
+
+ /* restore div val */
+ val = clk_readl(divider->reg);
+ val |= divider->cached_val << divider->shift;
+ clk_writel(val, divider->reg);
+
+ if (divider->lock)
+ spin_unlock_irqrestore(divider->lock, flags);
+ else
+ __release(divider->lock);
+
+ return 0;
+}
+
+static void clk_divider_disable(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned long uninitialized_var(flags);
+ u32 val;
+
+ if (divider->lock)
+ spin_lock_irqsave(divider->lock, flags);
+ else
+ __acquire(divider->lock);
+
+ /* store the current div val */
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+ divider->cached_val = val;
+ clk_writel(0, divider->reg);
+
+ if (divider->lock)
+ spin_unlock_irqrestore(divider->lock, flags);
+ else
+ __release(divider->lock);
+}
+
+static int clk_divider_is_enabled(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ u32 val;
+
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+
+ return val ? 1 : 0;
+}
+
const struct clk_ops clk_divider_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
@@ -423,6 +526,16 @@ const struct clk_ops clk_divider_ops = {
};
EXPORT_SYMBOL_GPL(clk_divider_ops);
+const struct clk_ops clk_divider_gate_ops = {
+ .recalc_rate = clk_divider_gate_recalc_rate,
+ .round_rate = clk_divider_round_rate,
+ .set_rate = clk_divider_gate_set_rate,
+ .enable = clk_divider_enable,
+ .disable = clk_divider_disable,
+ .is_enabled = clk_divider_is_enabled,
+};
+EXPORT_SYMBOL_GPL(clk_divider_gate_ops);
+
const struct clk_ops clk_divider_ro_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
@@ -438,6 +551,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
struct clk_divider *div;
struct clk_hw *hw;
struct clk_init_data init;
+ u32 val;
int ret;
if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
@@ -455,6 +569,8 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
init.name = name;
if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
init.ops = &clk_divider_ro_ops;
+ else if (clk_divider_flags & CLK_DIVIDER_ZERO_GATE)
+ init.ops = &clk_divider_gate_ops;
else
init.ops = &clk_divider_ops;
init.flags = flags | CLK_IS_BASIC;
@@ -470,6 +586,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
div->hw.init = &init;
div->table = table;
+ if (div->flags & CLK_DIVIDER_ZERO_GATE) {
+ val = clk_readl(reg) >> shift;
+ val &= div_mask(width);
+ div->cached_val = val;
+ }
+
/* register the clock */
hw = &div->hw;
ret = clk_hw_register(dev, hw);
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 7c925e6..5f33b73 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -358,6 +358,7 @@ struct clk_div_table {
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @table: array of value/divider pairs, last entry should have div = 0
+ * @cached_val: cached div hw value used for CLK_DIVIDER_ZERO_GATE
* @lock: register lock
*
* Clock with an adjustable divider affecting its output frequency. Implements
@@ -386,6 +387,12 @@ struct clk_div_table {
* CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
* except when the value read from the register is zero, the divisor is
* 2^width of the field.
+ * CLK_DIVIDER_ZERO_GATE - For dividers which are like CLK_DIVIDER_ONE_BASED
+ * when the value read from the register is zero, it means the divisor
+ * is gated. For this case, the cached_val will be used to store the
+ * intermediate div for the normal rate operation, like set_rate/get_rate/
+ * recalc_rate. When the divider is ungated, the driver will actually
+ * program the hardware to have the requested divider value.
*/
struct clk_divider {
struct clk_hw hw;
@@ -394,6 +401,7 @@ struct clk_divider {
u8 width;
u8 flags;
const struct clk_div_table *table;
+ u32 cached_val;
spinlock_t *lock;
};
@@ -406,6 +414,7 @@ struct clk_divider {
#define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
#define CLK_DIVIDER_READ_ONLY BIT(5)
#define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
+#define CLK_DIVIDER_ZERO_GATE BIT(7)
extern const struct clk_ops clk_divider_ops;
extern const struct clk_ops clk_divider_ro_ops;
Anyway, if you still think it's not proper, i can put it in platform
driver as you wish, just in the cost of a few duplicated codes.
> > +
> > + if (!divider->cached_val) {
> > + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> > + return -EINVAL;
> > + }
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + /* restore div val */
> > + val = clk_readl(divider->reg);
> > + val |= divider->cached_val << divider->shift;
> > + clk_writel(val, divider->reg);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static void clk_divider_disable(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned long flags = 0;
> > + u32 val;
> > +
> > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > + return;
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + /* store the current div val */
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > + divider->cached_val = val;
> > + clk_writel(0, divider->reg);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +}
> > +
> > +static int clk_divider_is_enabled(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + u32 val;
> > +
> > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > + return __clk_get_enable_count(hw->clk);
>
> The plan was to delete this API once OMAP stopped using it.
> clk_hw_is_enabled() doesn't work?
No, it did not work before because clk_hw_is_enabled will result
in the dead loop by calling .is_enabled() callback again.
That's why __clk_get_enable_count is used instead.
However, with above new patch method, this issue was gone.
>
> > +
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > +
> > + return val ? 1 : 0;
> > +}
> > +
> > const struct clk_ops clk_divider_ops = {
> > .recalc_rate = clk_divider_recalc_rate,
> > .round_rate = clk_divider_round_rate,
> > .set_rate = clk_divider_set_rate,
> > + .enable = clk_divider_enable,
> > + .disable = clk_divider_disable,
> > + .is_enabled = clk_divider_is_enabled,
> > };
> > EXPORT_SYMBOL_GPL(clk_divider_ops);
> >
> > @@ -436,6 +525,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > struct clk_divider *div;
> > struct clk_hw *hw;
> > struct clk_init_data init;
> > + u32 val;
> > int ret;
> >
> > if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > @@ -468,6 +558,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > div->hw.init = &init;
> > div->table = table;
> >
> > + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> > + val = clk_readl(reg) >> shift;
> > + val &= div_mask(width);
> > + div->cached_val = val;
> > + }
>
> What if it isn't on? Setting cached_val to 0 is ok?
>
If it isn't on, then the cache_val should be 0.
And recalc_rate will catch this case and return 0 as there's
no proper pre-set rate.
Regards
Dong Aisheng
> > +
> > /* register the clock */
> > hw = &div->hw;
> > ret = clk_hw_register(dev, hw);
>
> --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> a Linux Foundation Collaborative Project
On Thu, Nov 02, 2017 at 12:36:09AM -0700, Stephen Boyd wrote:
> On 07/13, Dong Aisheng wrote:
> > The orphan clocks reparent operation should be moved after the critical
> > clocks enabled, otherwise it may get a chance to disable a newly
> > registered critical clock which triggers the following warning.
> >
> > Assuming we have two clocks: A and B, B is the parent of A.
> > Clock A has flag: CLK_OPS_PARENT_ENABLE
> > Clock B has flag: CLK_IS_CRITICAL
> >
> > Step 1:
> > Clock A is registered, then it becomes orphan.
> >
> > Step 2:
> > Clock B is registered. Before clock B reach the critical clock enable
> > operation, orphan A will find the newly registered parent B and do
> > reparent operation, then parent B will be finally disabled in
> > __clk_set_parent_after() due to CLK_OPS_PARENT_ENABLE flag as there's
> > still no users of B which will then trigger the following warning.
> >
> > [ 0.000000] WARNING: CPU: 0 PID: 0 at drivers/clk/clk.c:597 clk_core_disable+0xb4/0xe0
> > [ 0.000000] Modules linked in:
> > [ 0.000000] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.11.0-rc1-00056-gdff1f66-dirty #1373
> > [ 0.000000] Hardware name: Generic DT based system
> > [ 0.000000] Backtrace:
> > [ 0.000000] [<c010c4bc>] (dump_backtrace) from [<c010c764>] (show_stack+0x18/0x1c)
> > [ 0.000000] r6:600000d3 r5:00000000 r4:c0e26358 r3:00000000
> > [ 0.000000] [<c010c74c>] (show_stack) from [<c040599c>] (dump_stack+0xb4/0xe8)
> > [ 0.000000] [<c04058e8>] (dump_stack) from [<c0125c94>] (__warn+0xd8/0x104)
> > [ 0.000000] r10:c0c21cd0 r9:c048aa78 r8:00000255 r7:00000009 r6:c0c1cd90 r5:00000000
> > [ 0.000000] r4:00000000 r3:c0e01d34
> > [ 0.000000] [<c0125bbc>] (__warn) from [<c0125d74>] (warn_slowpath_null+0x28/0x30)
> > [ 0.000000] r9:00000000 r8:ef00bf80 r7:c165ac4c r6:ef00bf80 r5:ef00bf80 r4:ef00bf80
> > [ 0.000000] [<c0125d4c>] (warn_slowpath_null) from [<c048aa78>] (clk_core_disable+0xb4/0xe0)
> > [ 0.000000] [<c048a9c4>] (clk_core_disable) from [<c048be88>] (clk_core_disable_lock+0x20/0x2c)
> > [ 0.000000] r4:000000d3 r3:c0e0af00
> > [ 0.000000] [<c048be68>] (clk_core_disable_lock) from [<c048c224>] (clk_core_disable_unprepare+0x14/0x28)
> > [ 0.000000] r5:00000000 r4:ef00bf80
> > [ 0.000000] [<c048c210>] (clk_core_disable_unprepare) from [<c048c270>] (__clk_set_parent_after+0x38/0x54)
> > [ 0.000000] r4:ef00bd80 r3:000010a0
> > [ 0.000000] [<c048c238>] (__clk_set_parent_after) from [<c048daa8>] (clk_register+0x4d0/0x648)
> > [ 0.000000] r6:ef00d500 r5:ef00bf80 r4:ef00bd80 r3:ef00bfd4
> > [ 0.000000] [<c048d5d8>] (clk_register) from [<c048dc30>] (clk_hw_register+0x10/0x1c)
> > [ 0.000000] r9:00000000 r8:00000003 r7:00000000 r6:00000824 r5:00000001 r4:ef00d500
> > [ 0.000000] [<c048dc20>] (clk_hw_register) from [<c048e698>] (_register_divider+0xcc/0x120)
> > [ 0.000000] [<c048e5cc>] (_register_divider) from [<c048e730>] (clk_register_divider+0x44/0x54)
> > [ 0.000000] r10:00000004 r9:00000003 r8:00000001 r7:00000000 r6:00000003 r5:00000001
> > [ 0.000000] r4:f0810030
> > [ 0.000000] [<c048e6ec>] (clk_register_divider) from [<c0d3ff58>] (imx7ulp_clocks_init+0x558/0xe98)
> > [ 0.000000] r7:c0e296f8 r6:c165c808 r5:00000000 r4:c165c808
> > [ 0.000000] [<c0d3fa00>] (imx7ulp_clocks_init) from [<c0d24db0>] (of_clk_init+0x118/0x1e0)
> > [ 0.000000] r10:00000001 r9:c0e01f68 r8:00000000 r7:c0e01f60 r6:ef7f8974 r5:ef0035c0
> > [ 0.000000] r4:00000006
> > [ 0.000000] [<c0d24c98>] (of_clk_init) from [<c0d04a50>] (time_init+0x2c/0x38)
> > [ 0.000000] r10:efffed40 r9:c0d61a48 r8:c0e78000 r7:c0e07900 r6:ffffffff r5:c0e78000
> > [ 0.000000] r4:00000000
> > [ 0.000000] [<c0d04a24>] (time_init) from [<c0d00b8c>] (start_kernel+0x218/0x394)
> > [ 0.000000] [<c0d00974>] (start_kernel) from [<6000807c>] (0x6000807c)
> > [ 0.000000] r10:00000000 r9:410fc075 r8:6000406a r7:c0e0c930 r6:c0d61a44 r5:c0e07918
> > [ 0.000000] r4:c0e78294
> > [ 0.000000] ---[ end trace 0000000000000000 ]---
>
> Please remove timestamps from logs unless they're important.
>
Got it.
> >
> > Fixes: fc8726a2c021 ("clk: core: support clocks which requires parents enable (part 2)")
> > Cc: Stephen Boyd <[email protected]>
> > Cc: Michael Turquette <[email protected]>
> > Cc: Shawn Guo <[email protected]>
> > Signed-off-by: Dong Aisheng <[email protected]>
> >
> > ---
> > ChangeLog:
> > v1->v2:
> > * add more detailed commit messages
>
> Thanks for that. We shouldn't be touching the hardware during clk
> registration though, so something is wrong there. It seems that
> adding the flag to enable clks when touching their registers has
> exposed that we should just be doing the toggle of the bookeeping
> stuff underneath the enable lock here.
>
> We know that the clk isn't enabled with any sort of prepare_count
> here so we don't need to enable anything to prevent a race. And
> we're holding the prepare mutex so set_rate/set_parent can't race
> here either.
>
Well, it looks like a good suggestion and it does make sense.
> Can you try this patch instead?
>
> ---8<----
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index c8d83acda006..416d44cc772c 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -2476,14 +2476,17 @@ static int __clk_core_init(struct clk_core *core)
> */
> hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
> struct clk_core *parent = __clk_init_parent(orphan);
> + unsigned long flags;
>
> /*
> * we could call __clk_set_parent, but that would result in a
> * redundant call to the .set_rate op, if it exists
> */
> if (parent) {
> - __clk_set_parent_before(orphan, parent);
> - __clk_set_parent_after(orphan, parent, NULL);
> + /* update the clk tree topology */
> + flags = clk_enable_lock();
> + clk_reparent(orphan, parent);
> + clk_enable_unlock(flags);
> __clk_recalc_accuracies(orphan);
> __clk_recalc_rates(orphan, 0);
> }
I tested this change worked well.
I could resent the patch with this new method later.
Regards
Dong Aisheng
On 12/20, Dong Aisheng wrote:
> On Thu, Nov 02, 2017 at 12:36:09AM -0700, Stephen Boyd wrote:
> > On 07/13, Dong Aisheng wrote:
> > > The orphan clocks reparent operation should be moved after the critical
> > > clocks enabled, otherwise it may get a chance to disable a newly
> > > registered critical clock which triggers the following warning.
> > >
> > > Assuming we have two clocks: A and B, B is the parent of A.
> > > Clock A has flag: CLK_OPS_PARENT_ENABLE
> > > Clock B has flag: CLK_IS_CRITICAL
> > >
> > > Step 1:
> > > Clock A is registered, then it becomes orphan.
> > >
> > > Step 2:
> > > Clock B is registered. Before clock B reach the critical clock enable
> > > operation, orphan A will find the newly registered parent B and do
> > > reparent operation, then parent B will be finally disabled in
> > > __clk_set_parent_after() due to CLK_OPS_PARENT_ENABLE flag as there's
> > > still no users of B which will then trigger the following warning.
> > >
> > > [ 0.000000] WARNING: CPU: 0 PID: 0 at drivers/clk/clk.c:597 clk_core_disable+0xb4/0xe0
> > > [ 0.000000] Modules linked in:
> > > [ 0.000000] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.11.0-rc1-00056-gdff1f66-dirty #1373
> > > [ 0.000000] Hardware name: Generic DT based system
> > > [ 0.000000] Backtrace:
> > > [ 0.000000] [<c010c4bc>] (dump_backtrace) from [<c010c764>] (show_stack+0x18/0x1c)
> > > [ 0.000000] r6:600000d3 r5:00000000 r4:c0e26358 r3:00000000
> > > [ 0.000000] [<c010c74c>] (show_stack) from [<c040599c>] (dump_stack+0xb4/0xe8)
> > > [ 0.000000] [<c04058e8>] (dump_stack) from [<c0125c94>] (__warn+0xd8/0x104)
> > > [ 0.000000] r10:c0c21cd0 r9:c048aa78 r8:00000255 r7:00000009 r6:c0c1cd90 r5:00000000
> > > [ 0.000000] r4:00000000 r3:c0e01d34
> > > [ 0.000000] [<c0125bbc>] (__warn) from [<c0125d74>] (warn_slowpath_null+0x28/0x30)
> > > [ 0.000000] r9:00000000 r8:ef00bf80 r7:c165ac4c r6:ef00bf80 r5:ef00bf80 r4:ef00bf80
> > > [ 0.000000] [<c0125d4c>] (warn_slowpath_null) from [<c048aa78>] (clk_core_disable+0xb4/0xe0)
> > > [ 0.000000] [<c048a9c4>] (clk_core_disable) from [<c048be88>] (clk_core_disable_lock+0x20/0x2c)
> > > [ 0.000000] r4:000000d3 r3:c0e0af00
> > > [ 0.000000] [<c048be68>] (clk_core_disable_lock) from [<c048c224>] (clk_core_disable_unprepare+0x14/0x28)
> > > [ 0.000000] r5:00000000 r4:ef00bf80
> > > [ 0.000000] [<c048c210>] (clk_core_disable_unprepare) from [<c048c270>] (__clk_set_parent_after+0x38/0x54)
> > > [ 0.000000] r4:ef00bd80 r3:000010a0
> > > [ 0.000000] [<c048c238>] (__clk_set_parent_after) from [<c048daa8>] (clk_register+0x4d0/0x648)
> > > [ 0.000000] r6:ef00d500 r5:ef00bf80 r4:ef00bd80 r3:ef00bfd4
> > > [ 0.000000] [<c048d5d8>] (clk_register) from [<c048dc30>] (clk_hw_register+0x10/0x1c)
> > > [ 0.000000] r9:00000000 r8:00000003 r7:00000000 r6:00000824 r5:00000001 r4:ef00d500
> > > [ 0.000000] [<c048dc20>] (clk_hw_register) from [<c048e698>] (_register_divider+0xcc/0x120)
> > > [ 0.000000] [<c048e5cc>] (_register_divider) from [<c048e730>] (clk_register_divider+0x44/0x54)
> > > [ 0.000000] r10:00000004 r9:00000003 r8:00000001 r7:00000000 r6:00000003 r5:00000001
> > > [ 0.000000] r4:f0810030
> > > [ 0.000000] [<c048e6ec>] (clk_register_divider) from [<c0d3ff58>] (imx7ulp_clocks_init+0x558/0xe98)
> > > [ 0.000000] r7:c0e296f8 r6:c165c808 r5:00000000 r4:c165c808
> > > [ 0.000000] [<c0d3fa00>] (imx7ulp_clocks_init) from [<c0d24db0>] (of_clk_init+0x118/0x1e0)
> > > [ 0.000000] r10:00000001 r9:c0e01f68 r8:00000000 r7:c0e01f60 r6:ef7f8974 r5:ef0035c0
> > > [ 0.000000] r4:00000006
> > > [ 0.000000] [<c0d24c98>] (of_clk_init) from [<c0d04a50>] (time_init+0x2c/0x38)
> > > [ 0.000000] r10:efffed40 r9:c0d61a48 r8:c0e78000 r7:c0e07900 r6:ffffffff r5:c0e78000
> > > [ 0.000000] r4:00000000
> > > [ 0.000000] [<c0d04a24>] (time_init) from [<c0d00b8c>] (start_kernel+0x218/0x394)
> > > [ 0.000000] [<c0d00974>] (start_kernel) from [<6000807c>] (0x6000807c)
> > > [ 0.000000] r10:00000000 r9:410fc075 r8:6000406a r7:c0e0c930 r6:c0d61a44 r5:c0e07918
> > > [ 0.000000] r4:c0e78294
> > > [ 0.000000] ---[ end trace 0000000000000000 ]---
> >
> > Please remove timestamps from logs unless they're important.
> >
>
> Got it.
>
> > >
> > > Fixes: fc8726a2c021 ("clk: core: support clocks which requires parents enable (part 2)")
> > > Cc: Stephen Boyd <[email protected]>
> > > Cc: Michael Turquette <[email protected]>
> > > Cc: Shawn Guo <[email protected]>
> > > Signed-off-by: Dong Aisheng <[email protected]>
> > >
> > > ---
> > > ChangeLog:
> > > v1->v2:
> > > * add more detailed commit messages
> >
> > Thanks for that. We shouldn't be touching the hardware during clk
> > registration though, so something is wrong there. It seems that
> > adding the flag to enable clks when touching their registers has
> > exposed that we should just be doing the toggle of the bookeeping
> > stuff underneath the enable lock here.
> >
> > We know that the clk isn't enabled with any sort of prepare_count
> > here so we don't need to enable anything to prevent a race. And
> > we're holding the prepare mutex so set_rate/set_parent can't race
> > here either.
> >
>
> Well, it looks like a good suggestion and it does make sense.
>
> > Can you try this patch instead?
> >
> > ---8<----
> > diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> > index c8d83acda006..416d44cc772c 100644
> > --- a/drivers/clk/clk.c
> > +++ b/drivers/clk/clk.c
> > @@ -2476,14 +2476,17 @@ static int __clk_core_init(struct clk_core *core)
> > */
> > hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
> > struct clk_core *parent = __clk_init_parent(orphan);
> > + unsigned long flags;
> >
> > /*
> > * we could call __clk_set_parent, but that would result in a
> > * redundant call to the .set_rate op, if it exists
> > */
> > if (parent) {
> > - __clk_set_parent_before(orphan, parent);
> > - __clk_set_parent_after(orphan, parent, NULL);
> > + /* update the clk tree topology */
> > + flags = clk_enable_lock();
> > + clk_reparent(orphan, parent);
> > + clk_enable_unlock(flags);
> > __clk_recalc_accuracies(orphan);
> > __clk_recalc_rates(orphan, 0);
> > }
>
>
> I tested this change worked well.
> I could resent the patch with this new method later.
>
Ok. Great. I'm going to apply this patch now into clk-next to
look for regressions on other platforms. If this was the only
questionable thing about this series then I think I can apply the
rest of it without you needing to resend. I'll check today.
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
On 12/20, Dong Aisheng wrote:
> On Thu, Nov 02, 2017 at 12:50:39AM -0700, Stephen Boyd wrote:
> > On 07/13, Dong Aisheng wrote:
> > > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> > > index 9bb472c..55f8c41 100644
> > > --- a/drivers/clk/clk-divider.c
> > > +++ b/drivers/clk/clk-divider.c
> > > @@ -123,6 +123,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> > > struct clk_divider *divider = to_clk_divider(hw);
> > > unsigned int div;
> > >
> > > + if (flags & CLK_DIVIDER_ZERO_GATE && !val)
> > > + return 0;
> > > +
> > > div = _get_div(table, val, flags, divider->width);
> > > if (!div) {
> > > WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> > > @@ -141,8 +144,13 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> > > struct clk_divider *divider = to_clk_divider(hw);
> > > unsigned int val;
> > >
> > > - val = clk_readl(divider->reg) >> divider->shift;
> > > - val &= div_mask(divider->width);
> > > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > > + !clk_hw_is_enabled(hw)) {
> >
> > This seems racy. Are we holding the register lock here?
> >
>
> Would you please clarify what type of racy you mean?
I mean a race between clk_enable() and clk_set_rate(). A
clk_enable() can happen while a rate change is going on, and then
the clk_hw_is_enabled() check here would be racing, unless this
driver specifically tries to prevent that from happening by
holding a spinlock somewhere.
>
> Currently it only protects register write between set_rate and enable/disable,
> and other register read are not protected.
> e.g. in recalc_rate and is_enabled.
If you're holding some lock that is used to protect the register
writes and also the clk from getting enabled/disabled during a
rate change then it's fine.
>
> And i did see similar users, e.g.
> drivers/clk/sunxi-ng/ccu_mult.c
Sure. Those could also be broken. I'm not sure.
>
> Should we still need protect them here?
>
> > > + val = divider->cached_val;
> > > + } else {
> > > + val = clk_readl(divider->reg) >> divider->shift;
> > > + val &= div_mask(divider->width);
> > > + }
> > >
> > > return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > > divider->flags);
> > > @@ -392,6 +400,12 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > > value = divider_get_val(rate, parent_rate, divider->table,
> > > divider->width, divider->flags);
> > >
> > > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > > + !clk_hw_is_enabled(hw)) {
> >
> > Same racy comment here.
> >
> > > + divider->cached_val = value;
> > > + return 0;
> > > + }
> > > +
> > > if (divider->lock)
> > > spin_lock_irqsave(divider->lock, flags);
> > > else
> > > @@ -414,10 +428,85 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > > return 0;
> > > }
> > >
> > > +static int clk_divider_enable(struct clk_hw *hw)
> > > +{
> > > + struct clk_divider *divider = to_clk_divider(hw);
> > > + unsigned long flags = 0;
> > > + u32 val;
> > > +
> > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > + return 0;
> >
> > This is not good. We will always jump to these functions on
> > enable/disable for a divider although 99.9% of all dividers that
> > exist won't need to run this code at all.
> >
>
> I absolutely understand this concern.
>
> > Can you please move this logic into your own divider
> > implementation? The flag can be added to the generic layer if
> > necessary but I'd prefer to see this logic kept in the driver
> > that uses it. If we get more than one driver doing the cached
> > divider thing then we can think about moving it to the more
> > generic place like here, but for now we should be able to keep
> > this contained away from the basic types and handled by the
> > quirky driver that needs it.
> >
>
> If only for above issue, how about invent a clk_divider_gate_ops
> to separate the users of normal divider and zero gate divider:
>
> diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> index 4ed516c..b51f3f9 100644
> --- a/drivers/clk/clk-divider.c
> +++ b/drivers/clk/clk-divider.c
> @@ -125,6 +125,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
>
> div = _get_div(table, val, flags, divider->width);
> if (!div) {
> + if (flags & CLK_DIVIDER_ZERO_GATE)
> + return 0;
> +
> WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
> clk_hw_get_name(hw));
> @@ -148,6 +151,23 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> divider->flags);
> }
>
> +static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + unsigned int val;
> +
> + if (!clk_hw_is_enabled(hw)) {
> + val = divider->cached_val;
> + } else {
> + val = clk_readl(divider->reg) >> divider->shift;
> + val &= div_mask(divider->width);
> + }
> +
> + return divider_recalc_rate(hw, parent_rate, val, divider->table,
> + divider->flags);
> +}
> +
> static bool _is_valid_table_div(const struct clk_div_table *table,
> unsigned int div)
> {
> @@ -416,6 +436,89 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> return 0;
> }
>
> +static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + int value;
> +
> + if (!clk_hw_is_enabled(hw)) {
> + value = divider_get_val(rate, parent_rate, divider->table,
> + divider->width, divider->flags);
> + if (value < 0)
> + return value;
> +
> + divider->cached_val = value;
> +
> + return 0;
> + }
> +
> + return clk_divider_set_rate(hw, rate, parent_rate);
> +}
> +
> +static int clk_divider_enable(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + unsigned long uninitialized_var(flags);
> + u32 val;
> +
> + if (!divider->cached_val) {
> + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> + return -EINVAL;
> + }
> +
> + if (divider->lock)
> + spin_lock_irqsave(divider->lock, flags);
> + else
> + __acquire(divider->lock);
> +
> + /* restore div val */
> + val = clk_readl(divider->reg);
> + val |= divider->cached_val << divider->shift;
> + clk_writel(val, divider->reg);
> +
> + if (divider->lock)
> + spin_unlock_irqrestore(divider->lock, flags);
> + else
> + __release(divider->lock);
> +
> + return 0;
> +}
> +
> +static void clk_divider_disable(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + unsigned long uninitialized_var(flags);
> + u32 val;
> +
> + if (divider->lock)
> + spin_lock_irqsave(divider->lock, flags);
> + else
> + __acquire(divider->lock);
> +
> + /* store the current div val */
> + val = clk_readl(divider->reg) >> divider->shift;
> + val &= div_mask(divider->width);
> + divider->cached_val = val;
> + clk_writel(0, divider->reg);
> +
> + if (divider->lock)
> + spin_unlock_irqrestore(divider->lock, flags);
> + else
> + __release(divider->lock);
> +}
> +
> +static int clk_divider_is_enabled(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + u32 val;
> +
> + val = clk_readl(divider->reg) >> divider->shift;
> + val &= div_mask(divider->width);
> +
> + return val ? 1 : 0;
> +}
> +
> const struct clk_ops clk_divider_ops = {
> .recalc_rate = clk_divider_recalc_rate,
> .round_rate = clk_divider_round_rate,
> @@ -423,6 +526,16 @@ const struct clk_ops clk_divider_ops = {
> };
> EXPORT_SYMBOL_GPL(clk_divider_ops);
>
> +const struct clk_ops clk_divider_gate_ops = {
> + .recalc_rate = clk_divider_gate_recalc_rate,
> + .round_rate = clk_divider_round_rate,
> + .set_rate = clk_divider_gate_set_rate,
> + .enable = clk_divider_enable,
> + .disable = clk_divider_disable,
> + .is_enabled = clk_divider_is_enabled,
> +};
> +EXPORT_SYMBOL_GPL(clk_divider_gate_ops);
> +
> const struct clk_ops clk_divider_ro_ops = {
> .recalc_rate = clk_divider_recalc_rate,
> .round_rate = clk_divider_round_rate,
> @@ -438,6 +551,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> struct clk_divider *div;
> struct clk_hw *hw;
> struct clk_init_data init;
> + u32 val;
> int ret;
>
> if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> @@ -455,6 +569,8 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> init.name = name;
> if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
> init.ops = &clk_divider_ro_ops;
> + else if (clk_divider_flags & CLK_DIVIDER_ZERO_GATE)
> + init.ops = &clk_divider_gate_ops;
> else
> init.ops = &clk_divider_ops;
> init.flags = flags | CLK_IS_BASIC;
> @@ -470,6 +586,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> div->hw.init = &init;
> div->table = table;
>
> + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> + val = clk_readl(reg) >> shift;
> + val &= div_mask(width);
> + div->cached_val = val;
> + }
> +
> /* register the clock */
> hw = &div->hw;
> ret = clk_hw_register(dev, hw);
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> index 7c925e6..5f33b73 100644
> --- a/include/linux/clk-provider.h
> +++ b/include/linux/clk-provider.h
> @@ -358,6 +358,7 @@ struct clk_div_table {
> * @shift: shift to the divider bit field
> * @width: width of the divider bit field
> * @table: array of value/divider pairs, last entry should have div = 0
> + * @cached_val: cached div hw value used for CLK_DIVIDER_ZERO_GATE
> * @lock: register lock
> *
> * Clock with an adjustable divider affecting its output frequency. Implements
> @@ -386,6 +387,12 @@ struct clk_div_table {
> * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
> * except when the value read from the register is zero, the divisor is
> * 2^width of the field.
> + * CLK_DIVIDER_ZERO_GATE - For dividers which are like CLK_DIVIDER_ONE_BASED
> + * when the value read from the register is zero, it means the divisor
> + * is gated. For this case, the cached_val will be used to store the
> + * intermediate div for the normal rate operation, like set_rate/get_rate/
> + * recalc_rate. When the divider is ungated, the driver will actually
> + * program the hardware to have the requested divider value.
> */
> struct clk_divider {
> struct clk_hw hw;
> @@ -394,6 +401,7 @@ struct clk_divider {
> u8 width;
> u8 flags;
> const struct clk_div_table *table;
> + u32 cached_val;
> spinlock_t *lock;
> };
>
> @@ -406,6 +414,7 @@ struct clk_divider {
> #define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
> #define CLK_DIVIDER_READ_ONLY BIT(5)
> #define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
> +#define CLK_DIVIDER_ZERO_GATE BIT(7)
>
> extern const struct clk_ops clk_divider_ops;
> extern const struct clk_ops clk_divider_ro_ops;
>
> Anyway, if you still think it's not proper, i can put it in platform
> driver as you wish, just in the cost of a few duplicated codes.
Ok. Keeping it in the basic types but split into different ops
path looks good.
>
> > > +
> > > + if (!divider->cached_val) {
> > > + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> > > + return -EINVAL;
> > > + }
> > > +
> > > + if (divider->lock)
> > > + spin_lock_irqsave(divider->lock, flags);
> > > + else
> > > + __acquire(divider->lock);
> > > +
> > > + /* restore div val */
> > > + val = clk_readl(divider->reg);
> > > + val |= divider->cached_val << divider->shift;
> > > + clk_writel(val, divider->reg);
> > > +
> > > + if (divider->lock)
> > > + spin_unlock_irqrestore(divider->lock, flags);
> > > + else
> > > + __release(divider->lock);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void clk_divider_disable(struct clk_hw *hw)
> > > +{
> > > + struct clk_divider *divider = to_clk_divider(hw);
> > > + unsigned long flags = 0;
> > > + u32 val;
> > > +
> > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > + return;
> > > +
> > > + if (divider->lock)
> > > + spin_lock_irqsave(divider->lock, flags);
> > > + else
> > > + __acquire(divider->lock);
> > > +
> > > + /* store the current div val */
> > > + val = clk_readl(divider->reg) >> divider->shift;
> > > + val &= div_mask(divider->width);
> > > + divider->cached_val = val;
> > > + clk_writel(0, divider->reg);
> > > +
> > > + if (divider->lock)
> > > + spin_unlock_irqrestore(divider->lock, flags);
> > > + else
> > > + __release(divider->lock);
> > > +}
> > > +
> > > +static int clk_divider_is_enabled(struct clk_hw *hw)
> > > +{
> > > + struct clk_divider *divider = to_clk_divider(hw);
> > > + u32 val;
> > > +
> > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > + return __clk_get_enable_count(hw->clk);
> >
> > The plan was to delete this API once OMAP stopped using it.
> > clk_hw_is_enabled() doesn't work?
>
> No, it did not work before because clk_hw_is_enabled will result
> in the dead loop by calling .is_enabled() callback again.
>
> That's why __clk_get_enable_count is used instead.
>
> However, with above new patch method, this issue was gone.
Great!
>
> >
> > > +
> > > + val = clk_readl(divider->reg) >> divider->shift;
> > > + val &= div_mask(divider->width);
> > > +
> > > + return val ? 1 : 0;
> > > +}
> > > +
> > > const struct clk_ops clk_divider_ops = {
> > > .recalc_rate = clk_divider_recalc_rate,
> > > .round_rate = clk_divider_round_rate,
> > > .set_rate = clk_divider_set_rate,
> > > + .enable = clk_divider_enable,
> > > + .disable = clk_divider_disable,
> > > + .is_enabled = clk_divider_is_enabled,
> > > };
> > > EXPORT_SYMBOL_GPL(clk_divider_ops);
> > >
> > > @@ -436,6 +525,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > > struct clk_divider *div;
> > > struct clk_hw *hw;
> > > struct clk_init_data init;
> > > + u32 val;
> > > int ret;
> > >
> > > if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > > @@ -468,6 +558,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > > div->hw.init = &init;
> > > div->table = table;
> > >
> > > + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> > > + val = clk_readl(reg) >> shift;
> > > + val &= div_mask(width);
> > > + div->cached_val = val;
> > > + }
> >
> > What if it isn't on? Setting cached_val to 0 is ok?
> >
>
> If it isn't on, then the cache_val should be 0.
> And recalc_rate will catch this case and return 0 as there's
> no proper pre-set rate.
>
Ok.
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
On 12/21, Stephen Boyd wrote:
>
> Ok. Great. I'm going to apply this patch now into clk-next to
> look for regressions on other platforms. If this was the only
> questionable thing about this series then I think I can apply the
> rest of it without you needing to resend. I'll check today.
>
I pushed it into clk-imx7ulp and merged that into clk-next. You
can use that patch in your series.
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
On Thu, Dec 21, 2017 at 05:55:07PM -0800, Stephen Boyd wrote:
> On 12/21, Stephen Boyd wrote:
> >
> > Ok. Great. I'm going to apply this patch now into clk-next to
> > look for regressions on other platforms. If this was the only
> > questionable thing about this series then I think I can apply the
> > rest of it without you needing to resend. I'll check today.
> >
>
> I pushed it into clk-imx7ulp and merged that into clk-next. You
> can use that patch in your series.
>
Okay, great.
Thanks
Regards
Dong Aisheng
On Thu, Dec 21, 2017 at 05:24:01PM -0800, Stephen Boyd wrote:
> On 12/20, Dong Aisheng wrote:
> > On Thu, Nov 02, 2017 at 12:50:39AM -0700, Stephen Boyd wrote:
> > > On 07/13, Dong Aisheng wrote:
> > > > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> > > > index 9bb472c..55f8c41 100644
> > > > --- a/drivers/clk/clk-divider.c
> > > > +++ b/drivers/clk/clk-divider.c
> > > > @@ -123,6 +123,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> > > > struct clk_divider *divider = to_clk_divider(hw);
> > > > unsigned int div;
> > > >
> > > > + if (flags & CLK_DIVIDER_ZERO_GATE && !val)
> > > > + return 0;
> > > > +
> > > > div = _get_div(table, val, flags, divider->width);
> > > > if (!div) {
> > > > WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> > > > @@ -141,8 +144,13 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> > > > struct clk_divider *divider = to_clk_divider(hw);
> > > > unsigned int val;
> > > >
> > > > - val = clk_readl(divider->reg) >> divider->shift;
> > > > - val &= div_mask(divider->width);
> > > > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > > > + !clk_hw_is_enabled(hw)) {
> > >
> > > This seems racy. Are we holding the register lock here?
> > >
> >
> > Would you please clarify what type of racy you mean?
>
> I mean a race between clk_enable() and clk_set_rate(). A
> clk_enable() can happen while a rate change is going on, and then
> the clk_hw_is_enabled() check here would be racing, unless this
> driver specifically tries to prevent that from happening by
> holding a spinlock somewhere.
>
Will this race cause real problems as clk_divider_is_enabled is only
a register read?
And seems either clk_hw_is_enable or __clk_is_enabled is allowed
to be called by anywhere currently. So it may be none of calling
in set_rate or recalc_rate issue.
If they may be race with clk_enable/disable function, seems they may
be better protected by the core.
And i did see some clarify in Documentation/clk.txt:
"The enable lock is a spinlock and is held across calls to the .enable,
.disable and .is_enabled operations. Those operations are thus not allowed to
sleep, and calls to the clk_enable(), clk_disable() and clk_is_enabled() API
functions are allowed in atomic context."
Then how about do like below:
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 8a1860a..ce5fa96 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -191,6 +191,7 @@ static bool clk_core_is_prepared(struct clk_core *core)
static bool clk_core_is_enabled(struct clk_core *core)
{
+ unsigned long flags;
bool ret = false;
/*
@@ -218,7 +219,10 @@ static bool clk_core_is_enabled(struct clk_core *core)
}
}
+ flags = clk_enable_lock();
ret = core->ops->is_enabled(core->hw);
+ clk_enable_unlock(flags);
+
done:
clk_pm_runtime_put(core);
> >
> > Currently it only protects register write between set_rate and enable/disable,
> > and other register read are not protected.
> > e.g. in recalc_rate and is_enabled.
>
> If you're holding some lock that is used to protect the register
> writes and also the clk from getting enabled/disabled during a
> rate change then it's fine.
>
Yes, all possible register write are protected.
Regards
Dong Aisheng
> >
> > And i did see similar users, e.g.
> > drivers/clk/sunxi-ng/ccu_mult.c
>
> Sure. Those could also be broken. I'm not sure.
>
> >
> > Should we still need protect them here?
> >
> > > > + val = divider->cached_val;
> > > > + } else {
> > > > + val = clk_readl(divider->reg) >> divider->shift;
> > > > + val &= div_mask(divider->width);
> > > > + }
> > > >
> > > > return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > > > divider->flags);
> > > > @@ -392,6 +400,12 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > > > value = divider_get_val(rate, parent_rate, divider->table,
> > > > divider->width, divider->flags);
> > > >
> > > > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > > > + !clk_hw_is_enabled(hw)) {
> > >
> > > Same racy comment here.
> > >
> > > > + divider->cached_val = value;
> > > > + return 0;
> > > > + }
> > > > +
> > > > if (divider->lock)
> > > > spin_lock_irqsave(divider->lock, flags);
> > > > else
> > > > @@ -414,10 +428,85 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > > > return 0;
> > > > }
> > > >
> > > > +static int clk_divider_enable(struct clk_hw *hw)
> > > > +{
> > > > + struct clk_divider *divider = to_clk_divider(hw);
> > > > + unsigned long flags = 0;
> > > > + u32 val;
> > > > +
> > > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > > + return 0;
> > >
> > > This is not good. We will always jump to these functions on
> > > enable/disable for a divider although 99.9% of all dividers that
> > > exist won't need to run this code at all.
> > >
> >
> > I absolutely understand this concern.
> >
> > > Can you please move this logic into your own divider
> > > implementation? The flag can be added to the generic layer if
> > > necessary but I'd prefer to see this logic kept in the driver
> > > that uses it. If we get more than one driver doing the cached
> > > divider thing then we can think about moving it to the more
> > > generic place like here, but for now we should be able to keep
> > > this contained away from the basic types and handled by the
> > > quirky driver that needs it.
> > >
> >
> > If only for above issue, how about invent a clk_divider_gate_ops
> > to separate the users of normal divider and zero gate divider:
> >
> > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> > index 4ed516c..b51f3f9 100644
> > --- a/drivers/clk/clk-divider.c
> > +++ b/drivers/clk/clk-divider.c
> > @@ -125,6 +125,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> >
> > div = _get_div(table, val, flags, divider->width);
> > if (!div) {
> > + if (flags & CLK_DIVIDER_ZERO_GATE)
> > + return 0;
> > +
> > WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> > "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
> > clk_hw_get_name(hw));
> > @@ -148,6 +151,23 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> > divider->flags);
> > }
> >
> > +static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,
> > + unsigned long parent_rate)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned int val;
> > +
> > + if (!clk_hw_is_enabled(hw)) {
> > + val = divider->cached_val;
> > + } else {
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > + }
> > +
> > + return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > + divider->flags);
> > +}
> > +
> > static bool _is_valid_table_div(const struct clk_div_table *table,
> > unsigned int div)
> > {
> > @@ -416,6 +436,89 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > return 0;
> > }
> >
> > +static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long parent_rate)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + int value;
> > +
> > + if (!clk_hw_is_enabled(hw)) {
> > + value = divider_get_val(rate, parent_rate, divider->table,
> > + divider->width, divider->flags);
> > + if (value < 0)
> > + return value;
> > +
> > + divider->cached_val = value;
> > +
> > + return 0;
> > + }
> > +
> > + return clk_divider_set_rate(hw, rate, parent_rate);
> > +}
> > +
> > +static int clk_divider_enable(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned long uninitialized_var(flags);
> > + u32 val;
> > +
> > + if (!divider->cached_val) {
> > + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> > + return -EINVAL;
> > + }
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + /* restore div val */
> > + val = clk_readl(divider->reg);
> > + val |= divider->cached_val << divider->shift;
> > + clk_writel(val, divider->reg);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static void clk_divider_disable(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned long uninitialized_var(flags);
> > + u32 val;
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + /* store the current div val */
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > + divider->cached_val = val;
> > + clk_writel(0, divider->reg);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +}
> > +
> > +static int clk_divider_is_enabled(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + u32 val;
> > +
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > +
> > + return val ? 1 : 0;
> > +}
> > +
> > const struct clk_ops clk_divider_ops = {
> > .recalc_rate = clk_divider_recalc_rate,
> > .round_rate = clk_divider_round_rate,
> > @@ -423,6 +526,16 @@ const struct clk_ops clk_divider_ops = {
> > };
> > EXPORT_SYMBOL_GPL(clk_divider_ops);
> >
> > +const struct clk_ops clk_divider_gate_ops = {
> > + .recalc_rate = clk_divider_gate_recalc_rate,
> > + .round_rate = clk_divider_round_rate,
> > + .set_rate = clk_divider_gate_set_rate,
> > + .enable = clk_divider_enable,
> > + .disable = clk_divider_disable,
> > + .is_enabled = clk_divider_is_enabled,
> > +};
> > +EXPORT_SYMBOL_GPL(clk_divider_gate_ops);
> > +
> > const struct clk_ops clk_divider_ro_ops = {
> > .recalc_rate = clk_divider_recalc_rate,
> > .round_rate = clk_divider_round_rate,
> > @@ -438,6 +551,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > struct clk_divider *div;
> > struct clk_hw *hw;
> > struct clk_init_data init;
> > + u32 val;
> > int ret;
> >
> > if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > @@ -455,6 +569,8 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > init.name = name;
> > if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
> > init.ops = &clk_divider_ro_ops;
> > + else if (clk_divider_flags & CLK_DIVIDER_ZERO_GATE)
> > + init.ops = &clk_divider_gate_ops;
> > else
> > init.ops = &clk_divider_ops;
> > init.flags = flags | CLK_IS_BASIC;
> > @@ -470,6 +586,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > div->hw.init = &init;
> > div->table = table;
> >
> > + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> > + val = clk_readl(reg) >> shift;
> > + val &= div_mask(width);
> > + div->cached_val = val;
> > + }
> > +
> > /* register the clock */
> > hw = &div->hw;
> > ret = clk_hw_register(dev, hw);
> > diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> > index 7c925e6..5f33b73 100644
> > --- a/include/linux/clk-provider.h
> > +++ b/include/linux/clk-provider.h
> > @@ -358,6 +358,7 @@ struct clk_div_table {
> > * @shift: shift to the divider bit field
> > * @width: width of the divider bit field
> > * @table: array of value/divider pairs, last entry should have div = 0
> > + * @cached_val: cached div hw value used for CLK_DIVIDER_ZERO_GATE
> > * @lock: register lock
> > *
> > * Clock with an adjustable divider affecting its output frequency. Implements
> > @@ -386,6 +387,12 @@ struct clk_div_table {
> > * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
> > * except when the value read from the register is zero, the divisor is
> > * 2^width of the field.
> > + * CLK_DIVIDER_ZERO_GATE - For dividers which are like CLK_DIVIDER_ONE_BASED
> > + * when the value read from the register is zero, it means the divisor
> > + * is gated. For this case, the cached_val will be used to store the
> > + * intermediate div for the normal rate operation, like set_rate/get_rate/
> > + * recalc_rate. When the divider is ungated, the driver will actually
> > + * program the hardware to have the requested divider value.
> > */
> > struct clk_divider {
> > struct clk_hw hw;
> > @@ -394,6 +401,7 @@ struct clk_divider {
> > u8 width;
> > u8 flags;
> > const struct clk_div_table *table;
> > + u32 cached_val;
> > spinlock_t *lock;
> > };
> >
> > @@ -406,6 +414,7 @@ struct clk_divider {
> > #define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
> > #define CLK_DIVIDER_READ_ONLY BIT(5)
> > #define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
> > +#define CLK_DIVIDER_ZERO_GATE BIT(7)
> >
> > extern const struct clk_ops clk_divider_ops;
> > extern const struct clk_ops clk_divider_ro_ops;
> >
> > Anyway, if you still think it's not proper, i can put it in platform
> > driver as you wish, just in the cost of a few duplicated codes.
>
> Ok. Keeping it in the basic types but split into different ops
> path looks good.
>
> >
> > > > +
> > > > + if (!divider->cached_val) {
> > > > + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + if (divider->lock)
> > > > + spin_lock_irqsave(divider->lock, flags);
> > > > + else
> > > > + __acquire(divider->lock);
> > > > +
> > > > + /* restore div val */
> > > > + val = clk_readl(divider->reg);
> > > > + val |= divider->cached_val << divider->shift;
> > > > + clk_writel(val, divider->reg);
> > > > +
> > > > + if (divider->lock)
> > > > + spin_unlock_irqrestore(divider->lock, flags);
> > > > + else
> > > > + __release(divider->lock);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void clk_divider_disable(struct clk_hw *hw)
> > > > +{
> > > > + struct clk_divider *divider = to_clk_divider(hw);
> > > > + unsigned long flags = 0;
> > > > + u32 val;
> > > > +
> > > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > > + return;
> > > > +
> > > > + if (divider->lock)
> > > > + spin_lock_irqsave(divider->lock, flags);
> > > > + else
> > > > + __acquire(divider->lock);
> > > > +
> > > > + /* store the current div val */
> > > > + val = clk_readl(divider->reg) >> divider->shift;
> > > > + val &= div_mask(divider->width);
> > > > + divider->cached_val = val;
> > > > + clk_writel(0, divider->reg);
> > > > +
> > > > + if (divider->lock)
> > > > + spin_unlock_irqrestore(divider->lock, flags);
> > > > + else
> > > > + __release(divider->lock);
> > > > +}
> > > > +
> > > > +static int clk_divider_is_enabled(struct clk_hw *hw)
> > > > +{
> > > > + struct clk_divider *divider = to_clk_divider(hw);
> > > > + u32 val;
> > > > +
> > > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > > + return __clk_get_enable_count(hw->clk);
> > >
> > > The plan was to delete this API once OMAP stopped using it.
> > > clk_hw_is_enabled() doesn't work?
> >
> > No, it did not work before because clk_hw_is_enabled will result
> > in the dead loop by calling .is_enabled() callback again.
> >
> > That's why __clk_get_enable_count is used instead.
> >
> > However, with above new patch method, this issue was gone.
>
> Great!
>
> >
> > >
> > > > +
> > > > + val = clk_readl(divider->reg) >> divider->shift;
> > > > + val &= div_mask(divider->width);
> > > > +
> > > > + return val ? 1 : 0;
> > > > +}
> > > > +
> > > > const struct clk_ops clk_divider_ops = {
> > > > .recalc_rate = clk_divider_recalc_rate,
> > > > .round_rate = clk_divider_round_rate,
> > > > .set_rate = clk_divider_set_rate,
> > > > + .enable = clk_divider_enable,
> > > > + .disable = clk_divider_disable,
> > > > + .is_enabled = clk_divider_is_enabled,
> > > > };
> > > > EXPORT_SYMBOL_GPL(clk_divider_ops);
> > > >
> > > > @@ -436,6 +525,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > > > struct clk_divider *div;
> > > > struct clk_hw *hw;
> > > > struct clk_init_data init;
> > > > + u32 val;
> > > > int ret;
> > > >
> > > > if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > > > @@ -468,6 +558,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > > > div->hw.init = &init;
> > > > div->table = table;
> > > >
> > > > + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> > > > + val = clk_readl(reg) >> shift;
> > > > + val &= div_mask(width);
> > > > + div->cached_val = val;
> > > > + }
> > >
> > > What if it isn't on? Setting cached_val to 0 is ok?
> > >
> >
> > If it isn't on, then the cache_val should be 0.
> > And recalc_rate will catch this case and return 0 as there's
> > no proper pre-set rate.
> >
>
> Ok.
>
> --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> a Linux Foundation Collaborative Project
On Thu, Dec 21, 2017 at 05:24:01PM -0800, Stephen Boyd wrote:
> On 12/20, Dong Aisheng wrote:
> > On Thu, Nov 02, 2017 at 12:50:39AM -0700, Stephen Boyd wrote:
> > > On 07/13, Dong Aisheng wrote:
> > > > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> > > > index 9bb472c..55f8c41 100644
> > > > --- a/drivers/clk/clk-divider.c
> > > > +++ b/drivers/clk/clk-divider.c
> > > > @@ -123,6 +123,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> > > > struct clk_divider *divider = to_clk_divider(hw);
> > > > unsigned int div;
> > > >
> > > > + if (flags & CLK_DIVIDER_ZERO_GATE && !val)
> > > > + return 0;
> > > > +
> > > > div = _get_div(table, val, flags, divider->width);
> > > > if (!div) {
> > > > WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> > > > @@ -141,8 +144,13 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> > > > struct clk_divider *divider = to_clk_divider(hw);
> > > > unsigned int val;
> > > >
> > > > - val = clk_readl(divider->reg) >> divider->shift;
> > > > - val &= div_mask(divider->width);
> > > > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > > > + !clk_hw_is_enabled(hw)) {
> > >
> > > This seems racy. Are we holding the register lock here?
> > >
> >
> > Would you please clarify what type of racy you mean?
>
> I mean a race between clk_enable() and clk_set_rate(). A
> clk_enable() can happen while a rate change is going on, and then
> the clk_hw_is_enabled() check here would be racing, unless this
> driver specifically tries to prevent that from happening by
> holding a spinlock somewhere.
>
I understand your point now.
Thanks for the careful and professional review.
Will take care of it in the next version.
Thanks
Regards
Dong Aisheng
> >
> > Currently it only protects register write between set_rate and enable/disable,
> > and other register read are not protected.
> > e.g. in recalc_rate and is_enabled.
>
> If you're holding some lock that is used to protect the register
> writes and also the clk from getting enabled/disabled during a
> rate change then it's fine.
>
> >
> > And i did see similar users, e.g.
> > drivers/clk/sunxi-ng/ccu_mult.c
>
> Sure. Those could also be broken. I'm not sure.
>
> >
> > Should we still need protect them here?
> >
> > > > + val = divider->cached_val;
> > > > + } else {
> > > > + val = clk_readl(divider->reg) >> divider->shift;
> > > > + val &= div_mask(divider->width);
> > > > + }
> > > >
> > > > return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > > > divider->flags);
> > > > @@ -392,6 +400,12 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > > > value = divider_get_val(rate, parent_rate, divider->table,
> > > > divider->width, divider->flags);
> > > >
> > > > + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> > > > + !clk_hw_is_enabled(hw)) {
> > >
> > > Same racy comment here.
> > >
> > > > + divider->cached_val = value;
> > > > + return 0;
> > > > + }
> > > > +
> > > > if (divider->lock)
> > > > spin_lock_irqsave(divider->lock, flags);
> > > > else
> > > > @@ -414,10 +428,85 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > > > return 0;
> > > > }
> > > >
> > > > +static int clk_divider_enable(struct clk_hw *hw)
> > > > +{
> > > > + struct clk_divider *divider = to_clk_divider(hw);
> > > > + unsigned long flags = 0;
> > > > + u32 val;
> > > > +
> > > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > > + return 0;
> > >
> > > This is not good. We will always jump to these functions on
> > > enable/disable for a divider although 99.9% of all dividers that
> > > exist won't need to run this code at all.
> > >
> >
> > I absolutely understand this concern.
> >
> > > Can you please move this logic into your own divider
> > > implementation? The flag can be added to the generic layer if
> > > necessary but I'd prefer to see this logic kept in the driver
> > > that uses it. If we get more than one driver doing the cached
> > > divider thing then we can think about moving it to the more
> > > generic place like here, but for now we should be able to keep
> > > this contained away from the basic types and handled by the
> > > quirky driver that needs it.
> > >
> >
> > If only for above issue, how about invent a clk_divider_gate_ops
> > to separate the users of normal divider and zero gate divider:
> >
> > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> > index 4ed516c..b51f3f9 100644
> > --- a/drivers/clk/clk-divider.c
> > +++ b/drivers/clk/clk-divider.c
> > @@ -125,6 +125,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> >
> > div = _get_div(table, val, flags, divider->width);
> > if (!div) {
> > + if (flags & CLK_DIVIDER_ZERO_GATE)
> > + return 0;
> > +
> > WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> > "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
> > clk_hw_get_name(hw));
> > @@ -148,6 +151,23 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> > divider->flags);
> > }
> >
> > +static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,
> > + unsigned long parent_rate)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned int val;
> > +
> > + if (!clk_hw_is_enabled(hw)) {
> > + val = divider->cached_val;
> > + } else {
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > + }
> > +
> > + return divider_recalc_rate(hw, parent_rate, val, divider->table,
> > + divider->flags);
> > +}
> > +
> > static bool _is_valid_table_div(const struct clk_div_table *table,
> > unsigned int div)
> > {
> > @@ -416,6 +436,89 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> > return 0;
> > }
> >
> > +static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long parent_rate)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + int value;
> > +
> > + if (!clk_hw_is_enabled(hw)) {
> > + value = divider_get_val(rate, parent_rate, divider->table,
> > + divider->width, divider->flags);
> > + if (value < 0)
> > + return value;
> > +
> > + divider->cached_val = value;
> > +
> > + return 0;
> > + }
> > +
> > + return clk_divider_set_rate(hw, rate, parent_rate);
> > +}
> > +
> > +static int clk_divider_enable(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned long uninitialized_var(flags);
> > + u32 val;
> > +
> > + if (!divider->cached_val) {
> > + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> > + return -EINVAL;
> > + }
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + /* restore div val */
> > + val = clk_readl(divider->reg);
> > + val |= divider->cached_val << divider->shift;
> > + clk_writel(val, divider->reg);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static void clk_divider_disable(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + unsigned long uninitialized_var(flags);
> > + u32 val;
> > +
> > + if (divider->lock)
> > + spin_lock_irqsave(divider->lock, flags);
> > + else
> > + __acquire(divider->lock);
> > +
> > + /* store the current div val */
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > + divider->cached_val = val;
> > + clk_writel(0, divider->reg);
> > +
> > + if (divider->lock)
> > + spin_unlock_irqrestore(divider->lock, flags);
> > + else
> > + __release(divider->lock);
> > +}
> > +
> > +static int clk_divider_is_enabled(struct clk_hw *hw)
> > +{
> > + struct clk_divider *divider = to_clk_divider(hw);
> > + u32 val;
> > +
> > + val = clk_readl(divider->reg) >> divider->shift;
> > + val &= div_mask(divider->width);
> > +
> > + return val ? 1 : 0;
> > +}
> > +
> > const struct clk_ops clk_divider_ops = {
> > .recalc_rate = clk_divider_recalc_rate,
> > .round_rate = clk_divider_round_rate,
> > @@ -423,6 +526,16 @@ const struct clk_ops clk_divider_ops = {
> > };
> > EXPORT_SYMBOL_GPL(clk_divider_ops);
> >
> > +const struct clk_ops clk_divider_gate_ops = {
> > + .recalc_rate = clk_divider_gate_recalc_rate,
> > + .round_rate = clk_divider_round_rate,
> > + .set_rate = clk_divider_gate_set_rate,
> > + .enable = clk_divider_enable,
> > + .disable = clk_divider_disable,
> > + .is_enabled = clk_divider_is_enabled,
> > +};
> > +EXPORT_SYMBOL_GPL(clk_divider_gate_ops);
> > +
> > const struct clk_ops clk_divider_ro_ops = {
> > .recalc_rate = clk_divider_recalc_rate,
> > .round_rate = clk_divider_round_rate,
> > @@ -438,6 +551,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > struct clk_divider *div;
> > struct clk_hw *hw;
> > struct clk_init_data init;
> > + u32 val;
> > int ret;
> >
> > if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > @@ -455,6 +569,8 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > init.name = name;
> > if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
> > init.ops = &clk_divider_ro_ops;
> > + else if (clk_divider_flags & CLK_DIVIDER_ZERO_GATE)
> > + init.ops = &clk_divider_gate_ops;
> > else
> > init.ops = &clk_divider_ops;
> > init.flags = flags | CLK_IS_BASIC;
> > @@ -470,6 +586,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > div->hw.init = &init;
> > div->table = table;
> >
> > + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> > + val = clk_readl(reg) >> shift;
> > + val &= div_mask(width);
> > + div->cached_val = val;
> > + }
> > +
> > /* register the clock */
> > hw = &div->hw;
> > ret = clk_hw_register(dev, hw);
> > diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> > index 7c925e6..5f33b73 100644
> > --- a/include/linux/clk-provider.h
> > +++ b/include/linux/clk-provider.h
> > @@ -358,6 +358,7 @@ struct clk_div_table {
> > * @shift: shift to the divider bit field
> > * @width: width of the divider bit field
> > * @table: array of value/divider pairs, last entry should have div = 0
> > + * @cached_val: cached div hw value used for CLK_DIVIDER_ZERO_GATE
> > * @lock: register lock
> > *
> > * Clock with an adjustable divider affecting its output frequency. Implements
> > @@ -386,6 +387,12 @@ struct clk_div_table {
> > * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
> > * except when the value read from the register is zero, the divisor is
> > * 2^width of the field.
> > + * CLK_DIVIDER_ZERO_GATE - For dividers which are like CLK_DIVIDER_ONE_BASED
> > + * when the value read from the register is zero, it means the divisor
> > + * is gated. For this case, the cached_val will be used to store the
> > + * intermediate div for the normal rate operation, like set_rate/get_rate/
> > + * recalc_rate. When the divider is ungated, the driver will actually
> > + * program the hardware to have the requested divider value.
> > */
> > struct clk_divider {
> > struct clk_hw hw;
> > @@ -394,6 +401,7 @@ struct clk_divider {
> > u8 width;
> > u8 flags;
> > const struct clk_div_table *table;
> > + u32 cached_val;
> > spinlock_t *lock;
> > };
> >
> > @@ -406,6 +414,7 @@ struct clk_divider {
> > #define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
> > #define CLK_DIVIDER_READ_ONLY BIT(5)
> > #define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
> > +#define CLK_DIVIDER_ZERO_GATE BIT(7)
> >
> > extern const struct clk_ops clk_divider_ops;
> > extern const struct clk_ops clk_divider_ro_ops;
> >
> > Anyway, if you still think it's not proper, i can put it in platform
> > driver as you wish, just in the cost of a few duplicated codes.
>
> Ok. Keeping it in the basic types but split into different ops
> path looks good.
>
> >
> > > > +
> > > > + if (!divider->cached_val) {
> > > > + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + if (divider->lock)
> > > > + spin_lock_irqsave(divider->lock, flags);
> > > > + else
> > > > + __acquire(divider->lock);
> > > > +
> > > > + /* restore div val */
> > > > + val = clk_readl(divider->reg);
> > > > + val |= divider->cached_val << divider->shift;
> > > > + clk_writel(val, divider->reg);
> > > > +
> > > > + if (divider->lock)
> > > > + spin_unlock_irqrestore(divider->lock, flags);
> > > > + else
> > > > + __release(divider->lock);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void clk_divider_disable(struct clk_hw *hw)
> > > > +{
> > > > + struct clk_divider *divider = to_clk_divider(hw);
> > > > + unsigned long flags = 0;
> > > > + u32 val;
> > > > +
> > > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > > + return;
> > > > +
> > > > + if (divider->lock)
> > > > + spin_lock_irqsave(divider->lock, flags);
> > > > + else
> > > > + __acquire(divider->lock);
> > > > +
> > > > + /* store the current div val */
> > > > + val = clk_readl(divider->reg) >> divider->shift;
> > > > + val &= div_mask(divider->width);
> > > > + divider->cached_val = val;
> > > > + clk_writel(0, divider->reg);
> > > > +
> > > > + if (divider->lock)
> > > > + spin_unlock_irqrestore(divider->lock, flags);
> > > > + else
> > > > + __release(divider->lock);
> > > > +}
> > > > +
> > > > +static int clk_divider_is_enabled(struct clk_hw *hw)
> > > > +{
> > > > + struct clk_divider *divider = to_clk_divider(hw);
> > > > + u32 val;
> > > > +
> > > > + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> > > > + return __clk_get_enable_count(hw->clk);
> > >
> > > The plan was to delete this API once OMAP stopped using it.
> > > clk_hw_is_enabled() doesn't work?
> >
> > No, it did not work before because clk_hw_is_enabled will result
> > in the dead loop by calling .is_enabled() callback again.
> >
> > That's why __clk_get_enable_count is used instead.
> >
> > However, with above new patch method, this issue was gone.
>
> Great!
>
> >
> > >
> > > > +
> > > > + val = clk_readl(divider->reg) >> divider->shift;
> > > > + val &= div_mask(divider->width);
> > > > +
> > > > + return val ? 1 : 0;
> > > > +}
> > > > +
> > > > const struct clk_ops clk_divider_ops = {
> > > > .recalc_rate = clk_divider_recalc_rate,
> > > > .round_rate = clk_divider_round_rate,
> > > > .set_rate = clk_divider_set_rate,
> > > > + .enable = clk_divider_enable,
> > > > + .disable = clk_divider_disable,
> > > > + .is_enabled = clk_divider_is_enabled,
> > > > };
> > > > EXPORT_SYMBOL_GPL(clk_divider_ops);
> > > >
> > > > @@ -436,6 +525,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > > > struct clk_divider *div;
> > > > struct clk_hw *hw;
> > > > struct clk_init_data init;
> > > > + u32 val;
> > > > int ret;
> > > >
> > > > if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> > > > @@ -468,6 +558,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> > > > div->hw.init = &init;
> > > > div->table = table;
> > > >
> > > > + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> > > > + val = clk_readl(reg) >> shift;
> > > > + val &= div_mask(width);
> > > > + div->cached_val = val;
> > > > + }
> > >
> > > What if it isn't on? Setting cached_val to 0 is ok?
> > >
> >
> > If it isn't on, then the cache_val should be 0.
> > And recalc_rate will catch this case and return 0 as there's
> > no proper pre-set rate.
> >
>
> Ok.
>
> --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> a Linux Foundation Collaborative Project
On 07/13, Dong Aisheng wrote:
> The orphan clocks reparent operation should be moved after the critical
> clocks enabled, otherwise it may get a chance to disable a newly
> registered critical clock which triggers the following warning.
>
> Assuming we have two clocks: A and B, B is the parent of A.
> Clock A has flag: CLK_OPS_PARENT_ENABLE
> Clock B has flag: CLK_IS_CRITICAL
>
> Step 1:
> Clock A is registered, then it becomes orphan.
>
> Step 2:
> Clock B is registered. Before clock B reach the critical clock enable
> operation, orphan A will find the newly registered parent B and do
> reparent operation, then parent B will be finally disabled in
> __clk_set_parent_after() due to CLK_OPS_PARENT_ENABLE flag as there's
> still no users of B which will then trigger the following warning.
>
> [ 0.000000] WARNING: CPU: 0 PID: 0 at drivers/clk/clk.c:597 clk_core_disable+0xb4/0xe0
> [ 0.000000] Modules linked in:
> [ 0.000000] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.11.0-rc1-00056-gdff1f66-dirty #1373
> [ 0.000000] Hardware name: Generic DT based system
> [ 0.000000] Backtrace:
> [ 0.000000] [<c010c4bc>] (dump_backtrace) from [<c010c764>] (show_stack+0x18/0x1c)
> [ 0.000000] r6:600000d3 r5:00000000 r4:c0e26358 r3:00000000
> [ 0.000000] [<c010c74c>] (show_stack) from [<c040599c>] (dump_stack+0xb4/0xe8)
> [ 0.000000] [<c04058e8>] (dump_stack) from [<c0125c94>] (__warn+0xd8/0x104)
> [ 0.000000] r10:c0c21cd0 r9:c048aa78 r8:00000255 r7:00000009 r6:c0c1cd90 r5:00000000
> [ 0.000000] r4:00000000 r3:c0e01d34
> [ 0.000000] [<c0125bbc>] (__warn) from [<c0125d74>] (warn_slowpath_null+0x28/0x30)
> [ 0.000000] r9:00000000 r8:ef00bf80 r7:c165ac4c r6:ef00bf80 r5:ef00bf80 r4:ef00bf80
> [ 0.000000] [<c0125d4c>] (warn_slowpath_null) from [<c048aa78>] (clk_core_disable+0xb4/0xe0)
> [ 0.000000] [<c048a9c4>] (clk_core_disable) from [<c048be88>] (clk_core_disable_lock+0x20/0x2c)
> [ 0.000000] r4:000000d3 r3:c0e0af00
> [ 0.000000] [<c048be68>] (clk_core_disable_lock) from [<c048c224>] (clk_core_disable_unprepare+0x14/0x28)
> [ 0.000000] r5:00000000 r4:ef00bf80
> [ 0.000000] [<c048c210>] (clk_core_disable_unprepare) from [<c048c270>] (__clk_set_parent_after+0x38/0x54)
> [ 0.000000] r4:ef00bd80 r3:000010a0
> [ 0.000000] [<c048c238>] (__clk_set_parent_after) from [<c048daa8>] (clk_register+0x4d0/0x648)
> [ 0.000000] r6:ef00d500 r5:ef00bf80 r4:ef00bd80 r3:ef00bfd4
> [ 0.000000] [<c048d5d8>] (clk_register) from [<c048dc30>] (clk_hw_register+0x10/0x1c)
> [ 0.000000] r9:00000000 r8:00000003 r7:00000000 r6:00000824 r5:00000001 r4:ef00d500
> [ 0.000000] [<c048dc20>] (clk_hw_register) from [<c048e698>] (_register_divider+0xcc/0x120)
> [ 0.000000] [<c048e5cc>] (_register_divider) from [<c048e730>] (clk_register_divider+0x44/0x54)
> [ 0.000000] r10:00000004 r9:00000003 r8:00000001 r7:00000000 r6:00000003 r5:00000001
> [ 0.000000] r4:f0810030
> [ 0.000000] [<c048e6ec>] (clk_register_divider) from [<c0d3ff58>] (imx7ulp_clocks_init+0x558/0xe98)
> [ 0.000000] r7:c0e296f8 r6:c165c808 r5:00000000 r4:c165c808
> [ 0.000000] [<c0d3fa00>] (imx7ulp_clocks_init) from [<c0d24db0>] (of_clk_init+0x118/0x1e0)
> [ 0.000000] r10:00000001 r9:c0e01f68 r8:00000000 r7:c0e01f60 r6:ef7f8974 r5:ef0035c0
> [ 0.000000] r4:00000006
> [ 0.000000] [<c0d24c98>] (of_clk_init) from [<c0d04a50>] (time_init+0x2c/0x38)
> [ 0.000000] r10:efffed40 r9:c0d61a48 r8:c0e78000 r7:c0e07900 r6:ffffffff r5:c0e78000
> [ 0.000000] r4:00000000
> [ 0.000000] [<c0d04a24>] (time_init) from [<c0d00b8c>] (start_kernel+0x218/0x394)
> [ 0.000000] [<c0d00974>] (start_kernel) from [<6000807c>] (0x6000807c)
> [ 0.000000] r10:00000000 r9:410fc075 r8:6000406a r7:c0e0c930 r6:c0d61a44 r5:c0e07918
> [ 0.000000] r4:c0e78294
> [ 0.000000] ---[ end trace 0000000000000000 ]---
Please remove timestamps from logs unless they're important.
>
> Fixes: fc8726a2c021 ("clk: core: support clocks which requires parents enable (part 2)")
> Cc: Stephen Boyd <[email protected]>
> Cc: Michael Turquette <[email protected]>
> Cc: Shawn Guo <[email protected]>
> Signed-off-by: Dong Aisheng <[email protected]>
>
> ---
> ChangeLog:
> v1->v2:
> * add more detailed commit messages
Thanks for that. We shouldn't be touching the hardware during clk
registration though, so something is wrong there. It seems that
adding the flag to enable clks when touching their registers has
exposed that we should just be doing the toggle of the bookeeping
stuff underneath the enable lock here.
We know that the clk isn't enabled with any sort of prepare_count
here so we don't need to enable anything to prevent a race. And
we're holding the prepare mutex so set_rate/set_parent can't race
here either.
Can you try this patch instead?
---8<----
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index c8d83acda006..416d44cc772c 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -2476,14 +2476,17 @@ static int __clk_core_init(struct clk_core *core)
*/
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
struct clk_core *parent = __clk_init_parent(orphan);
+ unsigned long flags;
/*
* we could call __clk_set_parent, but that would result in a
* redundant call to the .set_rate op, if it exists
*/
if (parent) {
- __clk_set_parent_before(orphan, parent);
- __clk_set_parent_after(orphan, parent, NULL);
+ /* update the clk tree topology */
+ flags = clk_enable_lock();
+ clk_reparent(orphan, parent);
+ clk_enable_unlock(flags);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, 0);
}
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
From 1572808025746226792@xxx Thu Jul 13 11:50:18 +0000 2017
X-GM-THRID: 1572808025746226792
X-Gmail-Labels: Inbox,Category Forums
On 07/13, Dong Aisheng wrote:
> diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> index 9bb472c..55f8c41 100644
> --- a/drivers/clk/clk-divider.c
> +++ b/drivers/clk/clk-divider.c
> @@ -123,6 +123,9 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
> struct clk_divider *divider = to_clk_divider(hw);
> unsigned int div;
>
> + if (flags & CLK_DIVIDER_ZERO_GATE && !val)
> + return 0;
> +
> div = _get_div(table, val, flags, divider->width);
> if (!div) {
> WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> @@ -141,8 +144,13 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
> struct clk_divider *divider = to_clk_divider(hw);
> unsigned int val;
>
> - val = clk_readl(divider->reg) >> divider->shift;
> - val &= div_mask(divider->width);
> + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> + !clk_hw_is_enabled(hw)) {
This seems racy. Are we holding the register lock here?
> + val = divider->cached_val;
> + } else {
> + val = clk_readl(divider->reg) >> divider->shift;
> + val &= div_mask(divider->width);
> + }
>
> return divider_recalc_rate(hw, parent_rate, val, divider->table,
> divider->flags);
> @@ -392,6 +400,12 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> value = divider_get_val(rate, parent_rate, divider->table,
> divider->width, divider->flags);
>
> + if ((divider->flags & CLK_DIVIDER_ZERO_GATE) &&
> + !clk_hw_is_enabled(hw)) {
Same racy comment here.
> + divider->cached_val = value;
> + return 0;
> + }
> +
> if (divider->lock)
> spin_lock_irqsave(divider->lock, flags);
> else
> @@ -414,10 +428,85 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> return 0;
> }
>
> +static int clk_divider_enable(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + unsigned long flags = 0;
> + u32 val;
> +
> + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> + return 0;
This is not good. We will always jump to these functions on
enable/disable for a divider although 99.9% of all dividers that
exist won't need to run this code at all.
Can you please move this logic into your own divider
implementation? The flag can be added to the generic layer if
necessary but I'd prefer to see this logic kept in the driver
that uses it. If we get more than one driver doing the cached
divider thing then we can think about moving it to the more
generic place like here, but for now we should be able to keep
this contained away from the basic types and handled by the
quirky driver that needs it.
> +
> + if (!divider->cached_val) {
> + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));
> + return -EINVAL;
> + }
> +
> + if (divider->lock)
> + spin_lock_irqsave(divider->lock, flags);
> + else
> + __acquire(divider->lock);
> +
> + /* restore div val */
> + val = clk_readl(divider->reg);
> + val |= divider->cached_val << divider->shift;
> + clk_writel(val, divider->reg);
> +
> + if (divider->lock)
> + spin_unlock_irqrestore(divider->lock, flags);
> + else
> + __release(divider->lock);
> +
> + return 0;
> +}
> +
> +static void clk_divider_disable(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + unsigned long flags = 0;
> + u32 val;
> +
> + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> + return;
> +
> + if (divider->lock)
> + spin_lock_irqsave(divider->lock, flags);
> + else
> + __acquire(divider->lock);
> +
> + /* store the current div val */
> + val = clk_readl(divider->reg) >> divider->shift;
> + val &= div_mask(divider->width);
> + divider->cached_val = val;
> + clk_writel(0, divider->reg);
> +
> + if (divider->lock)
> + spin_unlock_irqrestore(divider->lock, flags);
> + else
> + __release(divider->lock);
> +}
> +
> +static int clk_divider_is_enabled(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + u32 val;
> +
> + if (!(divider->flags & CLK_DIVIDER_ZERO_GATE))
> + return __clk_get_enable_count(hw->clk);
The plan was to delete this API once OMAP stopped using it.
clk_hw_is_enabled() doesn't work?
> +
> + val = clk_readl(divider->reg) >> divider->shift;
> + val &= div_mask(divider->width);
> +
> + return val ? 1 : 0;
> +}
> +
> const struct clk_ops clk_divider_ops = {
> .recalc_rate = clk_divider_recalc_rate,
> .round_rate = clk_divider_round_rate,
> .set_rate = clk_divider_set_rate,
> + .enable = clk_divider_enable,
> + .disable = clk_divider_disable,
> + .is_enabled = clk_divider_is_enabled,
> };
> EXPORT_SYMBOL_GPL(clk_divider_ops);
>
> @@ -436,6 +525,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> struct clk_divider *div;
> struct clk_hw *hw;
> struct clk_init_data init;
> + u32 val;
> int ret;
>
> if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> @@ -468,6 +558,12 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
> div->hw.init = &init;
> div->table = table;
>
> + if (div->flags & CLK_DIVIDER_ZERO_GATE) {
> + val = clk_readl(reg) >> shift;
> + val &= div_mask(width);
> + div->cached_val = val;
> + }
What if it isn't on? Setting cached_val to 0 is ok?
> +
> /* register the clock */
> hw = &div->hw;
> ret = clk_hw_register(dev, hw);
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
From 1572807872081216786@xxx Thu Jul 13 11:47:52 +0000 2017
X-GM-THRID: 1572807872081216786
X-Gmail-Labels: Inbox,Category Forums