aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/clk/sunxi-ng/ccu_common.c49
-rw-r--r--drivers/clk/sunxi-ng/ccu_common.h12
2 files changed, 61 insertions, 0 deletions
diff --git a/drivers/clk/sunxi-ng/ccu_common.c b/drivers/clk/sunxi-ng/ccu_common.c
index 8a47bafd7890..9d8724715a43 100644
--- a/drivers/clk/sunxi-ng/ccu_common.c
+++ b/drivers/clk/sunxi-ng/ccu_common.c
@@ -14,11 +14,13 @@
14 * GNU General Public License for more details. 14 * GNU General Public License for more details.
15 */ 15 */
16 16
17#include <linux/clk.h>
17#include <linux/clk-provider.h> 18#include <linux/clk-provider.h>
18#include <linux/iopoll.h> 19#include <linux/iopoll.h>
19#include <linux/slab.h> 20#include <linux/slab.h>
20 21
21#include "ccu_common.h" 22#include "ccu_common.h"
23#include "ccu_gate.h"
22#include "ccu_reset.h" 24#include "ccu_reset.h"
23 25
24static DEFINE_SPINLOCK(ccu_lock); 26static DEFINE_SPINLOCK(ccu_lock);
@@ -39,6 +41,53 @@ void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock)
39 WARN_ON(readl_relaxed_poll_timeout(addr, reg, reg & lock, 100, 70000)); 41 WARN_ON(readl_relaxed_poll_timeout(addr, reg, reg & lock, 100, 70000));
40} 42}
41 43
44/*
45 * This clock notifier is called when the frequency of a PLL clock is
46 * changed. In common PLL designs, changes to the dividers take effect
47 * almost immediately, while changes to the multipliers (implemented
48 * as dividers in the feedback loop) take a few cycles to work into
49 * the feedback loop for the PLL to stablize.
50 *
51 * Sometimes when the PLL clock rate is changed, the decrease in the
52 * divider is too much for the decrease in the multiplier to catch up.
53 * The PLL clock rate will spike, and in some cases, might lock up
54 * completely.
55 *
56 * This notifier callback will gate and then ungate the clock,
57 * effectively resetting it, so it proceeds to work. Care must be
58 * taken to reparent consumers to other temporary clocks during the
59 * rate change, and that this notifier callback must be the first
60 * to be registered.
61 */
62static int ccu_pll_notifier_cb(struct notifier_block *nb,
63 unsigned long event, void *data)
64{
65 struct ccu_pll_nb *pll = to_ccu_pll_nb(nb);
66 int ret = 0;
67
68 if (event != POST_RATE_CHANGE)
69 goto out;
70
71 ccu_gate_helper_disable(pll->common, pll->enable);
72
73 ret = ccu_gate_helper_enable(pll->common, pll->enable);
74 if (ret)
75 goto out;
76
77 ccu_helper_wait_for_lock(pll->common, pll->lock);
78
79out:
80 return notifier_from_errno(ret);
81}
82
83int ccu_pll_notifier_register(struct ccu_pll_nb *pll_nb)
84{
85 pll_nb->clk_nb.notifier_call = ccu_pll_notifier_cb;
86
87 return clk_notifier_register(pll_nb->common->hw.clk,
88 &pll_nb->clk_nb);
89}
90
42int sunxi_ccu_probe(struct device_node *node, void __iomem *reg, 91int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
43 const struct sunxi_ccu_desc *desc) 92 const struct sunxi_ccu_desc *desc)
44{ 93{
diff --git a/drivers/clk/sunxi-ng/ccu_common.h b/drivers/clk/sunxi-ng/ccu_common.h
index 73d81dc58fc5..d6fdd7a789aa 100644
--- a/drivers/clk/sunxi-ng/ccu_common.h
+++ b/drivers/clk/sunxi-ng/ccu_common.h
@@ -83,6 +83,18 @@ struct sunxi_ccu_desc {
83 83
84void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock); 84void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock);
85 85
86struct ccu_pll_nb {
87 struct notifier_block clk_nb;
88 struct ccu_common *common;
89
90 u32 enable;
91 u32 lock;
92};
93
94#define to_ccu_pll_nb(_nb) container_of(_nb, struct ccu_pll_nb, clk_nb)
95
96int ccu_pll_notifier_register(struct ccu_pll_nb *pll_nb);
97
86int sunxi_ccu_probe(struct device_node *node, void __iomem *reg, 98int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
87 const struct sunxi_ccu_desc *desc); 99 const struct sunxi_ccu_desc *desc);
88 100