aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ipq806x/patches-4.9/0037-clk-Add-safe-switch-hook.patch
blob: bff605f8de9aff4538e1e967fbe8b12cf64c9c38 (plain)
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
156
157
158
From a1adfb782789ae9b25c928dfe3d639288563a86c Mon Sep 17 00:00:00 2001
From: Stephen Boyd <sboyd@codeaurora.org>
Date: Fri, 20 Mar 2015 23:45:23 -0700
Subject: [PATCH 37/69] 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' and 'safe frequency' that they can switch their
parent to while the upstream source is reprogrammed to support
this.

Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
---
 drivers/clk/clk.c            | 73 +++++++++++++++++++++++++++++++++++++++++---
 include/linux/clk-provider.h |  2 ++
 2 files changed, 70 insertions(+), 5 deletions(-)

--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -51,9 +51,13 @@ struct clk_core {
 	struct clk_core		**parents;
 	u8			num_parents;
 	u8			new_parent_index;
+	u8			safe_parent_index;
 	unsigned long		rate;
 	unsigned long		req_rate;
+	unsigned long		old_rate;
 	unsigned long		new_rate;
+	unsigned long		safe_freq;
+	struct clk_core		*safe_parent;
 	struct clk_core		*new_parent;
 	struct clk_core		*new_child;
 	unsigned long		flags;
@@ -1310,7 +1314,9 @@ out:
 static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
 			     struct clk_core *new_parent, u8 p_index)
 {
-	struct clk_core *child;
+	struct clk_core *child, *parent;
+	struct clk_hw *parent_hw;
+	unsigned long safe_freq = 0;
 
 	core->new_rate = new_rate;
 	core->new_parent = new_parent;
@@ -1320,6 +1326,23 @@ static void clk_calc_subtree(struct clk_
 	if (new_parent && new_parent != core->parent)
 		new_parent->new_child = core;
 
+	if (core->ops->get_safe_parent) {
+		parent_hw = core->ops->get_safe_parent(core->hw, &safe_freq);
+		if (parent_hw) {
+			parent = parent_hw->core;
+			p_index = clk_fetch_parent_index(core, parent);
+			core->safe_parent_index = p_index;
+			core->safe_parent = parent;
+			if (safe_freq)
+				core->safe_freq = safe_freq;
+			else
+				core->safe_freq = 0;
+		}
+	} else {
+		core->safe_parent = NULL;
+		core->safe_freq = 0;
+	}
+
 	hlist_for_each_entry(child, &core->children, child_node) {
 		child->new_rate = clk_recalc(child, new_rate);
 		clk_calc_subtree(child, child->new_rate, NULL, 0);
@@ -1432,14 +1455,51 @@ static struct clk_core *clk_propagate_ra
 						  unsigned long event)
 {
 	struct clk_core *child, *tmp_clk, *fail_clk = NULL;
+	struct clk_core *old_parent;
 	int ret = NOTIFY_DONE;
 
-	if (core->rate == core->new_rate)
+	if (core->rate == core->new_rate && event != POST_RATE_CHANGE)
 		return NULL;
 
+	switch (event) {
+	case PRE_RATE_CHANGE:
+		if (core->safe_parent) {
+			if (core->safe_freq)
+				core->ops->set_rate_and_parent(core->hw,
+						core->safe_freq,
+						core->safe_parent->rate,
+						core->safe_parent_index);
+			else
+				core->ops->set_parent(core->hw,
+						core->safe_parent_index);
+		}
+		core->old_rate = core->rate;
+		break;
+	case POST_RATE_CHANGE:
+		if (core->safe_parent) {
+			old_parent = __clk_set_parent_before(core,
+							     core->new_parent);
+			if (core->ops->set_rate_and_parent) {
+				core->ops->set_rate_and_parent(core->hw,
+						core->new_rate,
+						core->new_parent ?
+						core->new_parent->rate : 0,
+						core->new_parent_index);
+			} else if (core->ops->set_parent) {
+				core->ops->set_parent(core->hw,
+						core->new_parent_index);
+			}
+			__clk_set_parent_after(core, core->new_parent,
+					       old_parent);
+		}
+		break;
+	}
+
 	if (core->notifier_count) {
-		ret = __clk_notify(core, event, core->rate, core->new_rate);
-		if (ret & NOTIFY_STOP_MASK)
+		if (event != POST_RATE_CHANGE || core->old_rate != core->rate)
+			ret = __clk_notify(core, event, core->old_rate,
+					   core->new_rate);
+		if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
 			fail_clk = core;
 	}
 
@@ -1495,7 +1555,8 @@ clk_change_rate(struct clk_core *core, u
 		clk_enable_unlock(flags);
 	}
 
-	if (core->new_parent && core->new_parent != core->parent) {
+	if (core->new_parent && core->new_parent != core->parent &&
+	    !core->safe_parent) {
 		old_parent = __clk_set_parent_before(core, core->new_parent);
 		trace_clk_set_parent(core, core->new_parent);
 
@@ -1601,6 +1662,8 @@ static int clk_core_set_rate_nolock(stru
 
 	core->req_rate = req_rate;
 
+	clk_propagate_rate_change(top, POST_RATE_CHANGE);
+
 	return 0;
 }
 
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -206,6 +206,8 @@ struct clk_ops {
 					  struct clk_rate_request *req);
 	int		(*set_parent)(struct clk_hw *hw, u8 index);
 	u8		(*get_parent)(struct clk_hw *hw);
+	struct clk_hw	*(*get_safe_parent)(struct clk_hw *hw,
+					    unsigned long *safe_freq);
 	int		(*set_rate)(struct clk_hw *hw, unsigned long rate,
 				    unsigned long parent_rate);
 	int		(*set_rate_and_parent)(struct clk_hw *hw,