1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
From b7b3ceec506179d59e46e3a8ea8b370872cc7fcb Mon Sep 17 00:00:00 2001
From: Stephen Boyd <sboyd@codeaurora.org>
Date: Mon, 31 Mar 2014 16:52:29 -0700
Subject: [PATCH 126/182] clk: Add safe switch hook
Sometimes clocks can't accept their parent source turning off
while the source is reprogrammed to a different rate. Most
notably CPU clocks require a way to switch away from the current
PLL they're running on, reprogram that PLL to a new rate, and
then switch back to the PLL with the new rate once they're done.
Add a hook that drivers can implement allowing them to return a
'safe parent' that they can switch their parent to while the
upstream source is reprogrammed to support this.
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
---
drivers/clk/clk.c | 53 ++++++++++++++++++++++++++++++++++++------
include/linux/clk-private.h | 2 ++
include/linux/clk-provider.h | 1 +
3 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index b94a311..0582068 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -1356,6 +1356,7 @@ static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
struct clk *new_parent, u8 p_index)
{
struct clk *child;
+ struct clk *parent;
clk->new_rate = new_rate;
clk->new_parent = new_parent;
@@ -1365,6 +1366,17 @@ static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
if (new_parent && new_parent != clk->parent)
new_parent->new_child = clk;
+ if (clk->ops->get_safe_parent) {
+ parent = clk->ops->get_safe_parent(clk->hw);
+ if (parent) {
+ p_index = clk_fetch_parent_index(clk, parent);
+ clk->safe_parent_index = p_index;
+ clk->safe_parent = parent;
+ }
+ } else {
+ clk->safe_parent = NULL;
+ }
+
hlist_for_each_entry(child, &clk->children, child_node) {
if (child->ops->recalc_rate)
child->new_rate = child->ops->recalc_rate(child->hw, new_rate);
@@ -1450,14 +1462,42 @@ out:
static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long event)
{
struct clk *child, *tmp_clk, *fail_clk = NULL;
+ struct clk *old_parent;
int ret = NOTIFY_DONE;
- if (clk->rate == clk->new_rate)
+ if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE)
return NULL;
+ switch (event) {
+ case PRE_RATE_CHANGE:
+ if (clk->safe_parent)
+ clk->ops->set_parent(clk->hw, clk->safe_parent_index);
+ break;
+ case POST_RATE_CHANGE:
+ if (clk->safe_parent) {
+ old_parent = __clk_set_parent_before(clk,
+ clk->new_parent);
+ if (clk->ops->set_rate_and_parent) {
+ clk->ops->set_rate_and_parent(clk->hw,
+ clk->new_rate,
+ clk->new_parent ?
+ clk->new_parent->rate : 0,
+ clk->new_parent_index);
+ } else if (clk->ops->set_parent) {
+ clk->ops->set_parent(clk->hw,
+ clk->new_parent_index);
+ }
+ __clk_set_parent_after(clk, clk->new_parent,
+ old_parent);
+ }
+ break;
+ }
+
if (clk->notifier_count) {
- ret = __clk_notify(clk, event, clk->rate, clk->new_rate);
- if (ret & NOTIFY_STOP_MASK)
+ if (event != POST_RATE_CHANGE)
+ ret = __clk_notify(clk, event, clk->rate,
+ clk->new_rate);
+ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
fail_clk = clk;
}
@@ -1499,7 +1539,8 @@ static void clk_change_rate(struct clk *clk)
else if (clk->parent)
best_parent_rate = clk->parent->rate;
- if (clk->new_parent && clk->new_parent != clk->parent) {
+ if (clk->new_parent && clk->new_parent != clk->parent &&
+ !clk->safe_parent) {
old_parent = __clk_set_parent_before(clk, clk->new_parent);
if (clk->ops->set_rate_and_parent) {
@@ -1522,9 +1563,6 @@ static void clk_change_rate(struct clk *clk)
else
clk->rate = best_parent_rate;
- if (clk->notifier_count && old_rate != clk->rate)
- __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
-
hlist_for_each_entry(child, &clk->children, child_node) {
/* Skip children who will be reparented to another clock */
if (child->new_parent && child->new_parent != clk)
@@ -1598,6 +1636,7 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
/* change the rates */
clk_change_rate(top);
+ clk_propagate_rate_change(top, POST_RATE_CHANGE);
out:
clk_prepare_unlock();
diff --git a/include/linux/clk-private.h b/include/linux/clk-private.h
index efbf70b..f48684a 100644
--- a/include/linux/clk-private.h
+++ b/include/linux/clk-private.h
@@ -38,8 +38,10 @@ struct clk {
struct clk **parents;
u8 num_parents;
u8 new_parent_index;
+ u8 safe_parent_index;
unsigned long rate;
unsigned long new_rate;
+ struct clk *safe_parent;
struct clk *new_parent;
struct clk *new_child;
unsigned long flags;
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 939533d..300fcb8 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -157,6 +157,7 @@ struct clk_ops {
struct clk **best_parent_clk);
int (*set_parent)(struct clk_hw *hw, u8 index);
u8 (*get_parent)(struct clk_hw *hw);
+ struct clk *(*get_safe_parent)(struct clk_hw *hw);
int (*set_rate)(struct clk_hw *hw, unsigned long,
unsigned long);
int (*set_rate_and_parent)(struct clk_hw *hw,
--
1.7.10.4
|