msm: clock: Support clk_prepare/unprepare()
Fill in the boiler plate prepare()/unprepare() code. Add a
prepare count and a prepare lock to protect the prepare/unprepare
paths. Also handle the parent/child relationship similarly to how
enable/disable is done.
In particular, WARN() whenever a driver calls clk_enable() before
calling clk_prepare() and whenever a driver calls clk_unprepare()
before calling clk_disable(). This should catch a few bugs even
though it's technically not SMP safe if an enable call is racing
with an unprepare call for example.
We also mark PLLs and XOs as already warned since those are
mostly internal clocks that driver authors aren't aware of. The
markings will be removed when the local clock driver is updated
to handle prepare/unprepare in another patch.
Change-Id: I41dd39d27a279f3477c6e1cef292ac5308d65243
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
diff --git a/arch/arm/mach-msm/clock-7x30.c b/arch/arm/mach-msm/clock-7x30.c
index 8d040fe..24a676f 100644
--- a/arch/arm/mach-msm/clock-7x30.c
+++ b/arch/arm/mach-msm/clock-7x30.c
@@ -234,6 +234,7 @@
.rate = 19200000,
.ops = &clk_ops_tcxo,
CLK_INIT(tcxo_clk.c),
+ .warned = true,
},
};
@@ -259,6 +260,7 @@
.rate = 24576000,
.ops = &clk_ops_lpxo,
CLK_INIT(lpxo_clk.c),
+ .warned = true,
},
};
@@ -272,6 +274,7 @@
.rate = 768000000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll1_clk.c),
+ .warned = true,
},
};
@@ -285,6 +288,7 @@
.rate = 806400000, /* TODO: Support scaling */
.ops = &clk_ops_pll_vote,
CLK_INIT(pll2_clk.c),
+ .warned = true,
},
};
@@ -311,6 +315,7 @@
.rate = 891000000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll4_clk.c),
+ .warned = true,
},
};
diff --git a/arch/arm/mach-msm/clock-8960.c b/arch/arm/mach-msm/clock-8960.c
index 272163a..98a32c6 100644
--- a/arch/arm/mach-msm/clock-8960.c
+++ b/arch/arm/mach-msm/clock-8960.c
@@ -478,6 +478,7 @@
.rate = 800000000,
.ops = &clk_ops_pll,
CLK_INIT(pll2_clk.c),
+ .warned = true,
},
};
@@ -491,6 +492,7 @@
.vdd_class = &vdd_sr2_pll,
.fmax[VDD_SR2_PLL_ON] = ULONG_MAX,
CLK_INIT(pll3_clk.c),
+ .warned = true,
},
};
@@ -504,6 +506,7 @@
.rate = 393216000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll4_clk.c),
+ .warned = true,
},
};
@@ -517,6 +520,7 @@
.rate = 384000000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll8_clk.c),
+ .warned = true,
},
};
@@ -530,6 +534,7 @@
.rate = 480000000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll14_clk.c),
+ .warned = true,
},
};
@@ -541,6 +546,7 @@
.rate = 975000000,
.ops = &clk_ops_pll,
CLK_INIT(pll15_clk.c),
+ .warned = true,
},
};
diff --git a/arch/arm/mach-msm/clock-8x60.c b/arch/arm/mach-msm/clock-8x60.c
index 75e5727..4da4454 100644
--- a/arch/arm/mach-msm/clock-8x60.c
+++ b/arch/arm/mach-msm/clock-8x60.c
@@ -310,6 +310,7 @@
.rate = 384000000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll8_clk.c),
+ .warned = true,
},
};
@@ -321,6 +322,7 @@
.rate = 800000000,
.ops = &clk_ops_pll,
CLK_INIT(pll2_clk.c),
+ .warned = true,
},
};
@@ -332,6 +334,7 @@
.rate = 0, /* TODO: Detect rate dynamically */
.ops = &clk_ops_pll,
CLK_INIT(pll3_clk.c),
+ .warned = true,
},
};
@@ -370,6 +373,7 @@
.rate = 540672000,
.ops = &clk_ops_pll4,
CLK_INIT(pll4_clk.c),
+ .warned = true,
},
};
diff --git a/arch/arm/mach-msm/clock-9615.c b/arch/arm/mach-msm/clock-9615.c
index c891f50..3e059b6 100644
--- a/arch/arm/mach-msm/clock-9615.c
+++ b/arch/arm/mach-msm/clock-9615.c
@@ -268,6 +268,7 @@
.rate = 276000000,
.ops = &clk_ops_pll_acpu_vote,
CLK_INIT(pll0_clk.c),
+ .warned = true,
},
};
@@ -282,6 +283,7 @@
.rate = 276000000,
.ops = &clk_ops_pll_acpu_vote,
CLK_INIT(pll0_acpu_clk.c),
+ .warned = true,
},
};
@@ -295,6 +297,7 @@
.rate = 393216000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll4_clk.c),
+ .warned = true,
},
};
@@ -312,6 +315,7 @@
.rate = 384000000,
.ops = &clk_ops_pll_acpu_vote,
CLK_INIT(pll8_clk.c),
+ .warned = true,
},
};
@@ -326,6 +330,7 @@
.rate = 384000000,
.ops = &clk_ops_pll_acpu_vote,
CLK_INIT(pll8_acpu_clk.c),
+ .warned = true,
},
};
@@ -336,6 +341,7 @@
.rate = 440000000,
.ops = &clk_ops_pll,
CLK_INIT(pll9_acpu_clk.c),
+ .warned = true,
},
};
@@ -349,6 +355,7 @@
.rate = 480000000,
.ops = &clk_ops_pll_vote,
CLK_INIT(pll14_clk.c),
+ .warned = true,
},
};
diff --git a/arch/arm/mach-msm/clock-debug.c b/arch/arm/mach-msm/clock-debug.c
index 411a272..31be6af 100644
--- a/arch/arm/mach-msm/clock-debug.c
+++ b/arch/arm/mach-msm/clock-debug.c
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2007 Google, Inc.
- * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2007-2012, Code Aurora Forum. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -92,9 +92,9 @@
int rc = 0;
if (val)
- rc = clk_enable(clock);
+ rc = clk_prepare_enable(clock);
else
- clk_disable(clock);
+ clk_disable_unprepare(clock);
return rc;
}
diff --git a/arch/arm/mach-msm/clock-rpm.h b/arch/arm/mach-msm/clock-rpm.h
index 392d534..b0d5693 100644
--- a/arch/arm/mach-msm/clock-rpm.h
+++ b/arch/arm/mach-msm/clock-rpm.h
@@ -82,6 +82,7 @@
.dbg_name = #name, \
.rate = (r), \
CLK_INIT(name.c), \
+ .warned = true, \
}, \
}; \
static struct rpm_clk active = { \
@@ -97,6 +98,7 @@
.dbg_name = #active, \
.rate = (r), \
CLK_INIT(active.c), \
+ .warned = true, \
}, \
};
diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c
index 5b89fa9..7f0cafd 100644
--- a/arch/arm/mach-msm/clock.c
+++ b/arch/arm/mach-msm/clock.c
@@ -127,6 +127,41 @@
unvote_vdd_level(clk->vdd_class, level);
}
+int clk_prepare(struct clk *clk)
+{
+ int ret = 0;
+ struct clk *parent;
+ if (!clk)
+ return 0;
+
+ mutex_lock(&clk->prepare_lock);
+ if (clk->prepare_count == 0) {
+ parent = clk_get_parent(clk);
+
+ ret = clk_prepare(parent);
+ if (ret)
+ goto out;
+ ret = clk_prepare(clk->depends);
+ if (ret)
+ goto err_prepare_depends;
+
+ if (clk->ops->prepare)
+ ret = clk->ops->prepare(clk);
+ if (ret)
+ goto err_prepare_clock;
+ }
+ clk->prepare_count++;
+out:
+ mutex_unlock(&clk->prepare_lock);
+ return ret;
+err_prepare_clock:
+ clk_unprepare(clk->depends);
+err_prepare_depends:
+ clk_unprepare(parent);
+ goto out;
+}
+EXPORT_SYMBOL(clk_prepare);
+
/*
* Standard clock functions defined in include/linux/clk.h
*/
@@ -140,6 +175,10 @@
return 0;
spin_lock_irqsave(&clk->lock, flags);
+ if (WARN(!clk->warned && !clk->prepare_count,
+ "%s: Don't call enable on unprepared clocks\n",
+ clk->dbg_name))
+ clk->warned = true;
if (clk->count == 0) {
parent = clk_get_parent(clk);
@@ -193,6 +232,11 @@
return;
spin_lock_irqsave(&clk->lock, flags);
+ if (WARN(!clk->warned && !clk->prepare_count,
+ "%s: Never called prepare or calling disable "
+ "after unprepare\n",
+ clk->dbg_name))
+ clk->warned = true;
if (WARN(clk->count == 0, "%s is unbalanced", clk->dbg_name))
goto out;
if (clk->count == 1) {
@@ -210,6 +254,37 @@
}
EXPORT_SYMBOL(clk_disable);
+void clk_unprepare(struct clk *clk)
+{
+ if (!clk)
+ return;
+
+ mutex_lock(&clk->prepare_lock);
+ if (!clk->prepare_count) {
+ if (WARN(!clk->warned, "%s is unbalanced (prepare)",
+ clk->dbg_name))
+ clk->warned = true;
+ goto out;
+ }
+ if (clk->prepare_count == 1) {
+ struct clk *parent = clk_get_parent(clk);
+
+ if (WARN(!clk->warned && clk->count,
+ "%s: Don't call unprepare when the clock is enabled\n",
+ clk->dbg_name))
+ clk->warned = true;
+
+ if (clk->ops->unprepare)
+ clk->ops->unprepare(clk);
+ clk_unprepare(clk->depends);
+ clk_unprepare(parent);
+ }
+ clk->prepare_count--;
+out:
+ mutex_unlock(&clk->prepare_lock);
+}
+EXPORT_SYMBOL(clk_unprepare);
+
int clk_reset(struct clk *clk, enum clk_reset_action action)
{
if (!clk->ops->reset)
@@ -335,7 +410,7 @@
if (clk->ops->handoff && !(clk->flags & CLKFLAG_HANDOFF_RATE)) {
if (clk->ops->handoff(clk)) {
clk->flags |= CLKFLAG_HANDOFF_RATE;
- clk_enable(clk);
+ clk_prepare_enable(clk);
}
}
}
@@ -372,11 +447,11 @@
}
spin_unlock_irqrestore(&clk->lock, flags);
/*
- * Calling clk_disable() outside the lock is safe since
+ * Calling this outside the lock is safe since
* it doesn't need to be atomic with the flag change.
*/
if (handoff)
- clk_disable(clk);
+ clk_disable_unprepare(clk);
}
}
pr_info("clock_late_init() disabled %d unused clocks\n", count);
diff --git a/arch/arm/mach-msm/clock.h b/arch/arm/mach-msm/clock.h
index 4210bd9..bce2e0f 100644
--- a/arch/arm/mach-msm/clock.h
+++ b/arch/arm/mach-msm/clock.h
@@ -21,6 +21,7 @@
#include <linux/list.h>
#include <linux/clkdev.h>
#include <linux/spinlock.h>
+#include <linux/mutex.h>
#include <mach/clk.h>
@@ -63,8 +64,10 @@
}
struct clk_ops {
+ int (*prepare)(struct clk *clk);
int (*enable)(struct clk *clk);
void (*disable)(struct clk *clk);
+ void (*unprepare)(struct clk *clk);
void (*auto_off)(struct clk *clk);
void (*enable_hwcg)(struct clk *clk);
void (*disable_hwcg)(struct clk *clk);
@@ -85,11 +88,14 @@
/**
* struct clk
+ * @prepare_count: prepare refcount
+ * @prepare_lock: protects clk_prepare()/clk_unprepare() path and @prepare_count
* @count: enable refcount
* @lock: protects clk_enable()/clk_disable() path and @count
* @depends: non-direct parent of clock to enable when this clock is enabled
* @vdd_class: voltage scaling requirement class
* @fmax: maximum frequency in Hz supported at each voltage level
+ * @warned: true if the clock has warned of incorrect usage, false otherwise
*/
struct clk {
uint32_t flags;
@@ -103,12 +109,16 @@
struct list_head children;
struct list_head siblings;
+ bool warned;
unsigned count;
spinlock_t lock;
+ unsigned prepare_count;
+ struct mutex prepare_lock;
};
#define CLK_INIT(name) \
.lock = __SPIN_LOCK_UNLOCKED((name).lock), \
+ .prepare_lock = __MUTEX_INITIALIZER((name).prepare_lock), \
.children = LIST_HEAD_INIT((name).children), \
.siblings = LIST_HEAD_INIT((name).siblings)