blob: 1c742b171c8c5da8d8caf68b25ed19f4537690d3 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/*
2 * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14#include <linux/err.h>
15#include <linux/spinlock.h>
16#include <linux/clk.h>
17
18#include "clock.h"
19#include "clock-voter.h"
20
21static DEFINE_SPINLOCK(voter_clk_lock);
22
23/* Aggregate the rate of clocks that are currently on. */
24static unsigned voter_clk_aggregate_rate(const struct clk *parent)
25{
26 struct clk *clk;
27 unsigned rate = 0;
28
29 list_for_each_entry(clk, &parent->children, siblings) {
30 struct clk_voter *v = to_clk_voter(clk);
31 if (v->enabled)
32 rate = max(v->rate, rate);
33 }
34 return rate;
35}
36
37static int voter_clk_set_rate(struct clk *clk, unsigned rate)
38{
39 int ret = 0;
40 unsigned long flags;
41 struct clk *clkp;
42 struct clk_voter *clkh, *v = to_clk_voter(clk);
43 unsigned cur_rate, new_rate, other_rate = 0;
44
45 spin_lock_irqsave(&voter_clk_lock, flags);
46
47 if (v->enabled) {
48 struct clk *parent = v->parent;
49
50 /*
51 * Get the aggregate rate without this clock's vote and update
52 * if the new rate is different than the current rate
53 */
54 list_for_each_entry(clkp, &parent->children, siblings) {
55 clkh = to_clk_voter(clkp);
56 if (clkh->enabled && clkh != v)
57 other_rate = max(clkh->rate, other_rate);
58 }
59
60 cur_rate = max(other_rate, v->rate);
61 new_rate = max(other_rate, rate);
62
63 if (new_rate != cur_rate) {
64 ret = clk_set_min_rate(parent, new_rate);
65 if (ret)
66 goto unlock;
67 }
68 }
69 v->rate = rate;
70unlock:
71 spin_unlock_irqrestore(&voter_clk_lock, flags);
72
73 return ret;
74}
75
76static int voter_clk_enable(struct clk *clk)
77{
78 int ret;
79 unsigned long flags;
80 unsigned cur_rate;
81 struct clk *parent;
82 struct clk_voter *v = to_clk_voter(clk);
83
84 spin_lock_irqsave(&voter_clk_lock, flags);
85 parent = v->parent;
86
87 /*
88 * Increase the rate if this clock is voting for a higher rate
89 * than the current rate.
90 */
91 cur_rate = voter_clk_aggregate_rate(parent);
92 if (v->rate > cur_rate) {
93 ret = clk_set_min_rate(parent, v->rate);
94 if (ret)
95 goto out;
96 }
97 v->enabled = true;
98out:
99 spin_unlock_irqrestore(&voter_clk_lock, flags);
100
101 return ret;
102}
103
104static void voter_clk_disable(struct clk *clk)
105{
106 unsigned long flags;
107 struct clk *parent;
108 struct clk_voter *v = to_clk_voter(clk);
109 unsigned cur_rate, new_rate;
110
111 spin_lock_irqsave(&voter_clk_lock, flags);
112 parent = v->parent;
113
114 /*
115 * Decrease the rate if this clock was the only one voting for
116 * the highest rate.
117 */
118 v->enabled = false;
119 new_rate = voter_clk_aggregate_rate(parent);
120 cur_rate = max(new_rate, v->rate);
121
122 if (new_rate < cur_rate)
123 clk_set_min_rate(parent, new_rate);
124
125 spin_unlock_irqrestore(&voter_clk_lock, flags);
126}
127
128static unsigned voter_clk_get_rate(struct clk *clk)
129{
130 unsigned rate;
131 unsigned long flags;
132 struct clk_voter *v = to_clk_voter(clk);
133
134 spin_lock_irqsave(&voter_clk_lock, flags);
135 rate = v->rate;
136 spin_unlock_irqrestore(&voter_clk_lock, flags);
137
138 return rate;
139}
140
141static int voter_clk_is_enabled(struct clk *clk)
142{
143 struct clk_voter *v = to_clk_voter(clk);
144 return v->enabled;
145}
146
147static long voter_clk_round_rate(struct clk *clk, unsigned rate)
148{
149 struct clk_voter *v = to_clk_voter(clk);
150 return clk_round_rate(v->parent, rate);
151}
152
153static int voter_clk_set_parent(struct clk *clk, struct clk *parent)
154{
155 unsigned long flags;
156
157 spin_lock_irqsave(&voter_clk_lock, flags);
158 if (list_empty(&clk->siblings))
159 list_add(&clk->siblings, &parent->children);
160 spin_unlock_irqrestore(&voter_clk_lock, flags);
161
162 return 0;
163}
164
165static struct clk *voter_clk_get_parent(struct clk *clk)
166{
167 struct clk_voter *v = to_clk_voter(clk);
168 return v->parent;
169}
170
171static bool voter_clk_is_local(struct clk *clk)
172{
173 return true;
174}
175
176struct clk_ops clk_ops_voter = {
177 .enable = voter_clk_enable,
178 .disable = voter_clk_disable,
179 .set_rate = voter_clk_set_rate,
180 .set_min_rate = voter_clk_set_rate,
181 .get_rate = voter_clk_get_rate,
182 .is_enabled = voter_clk_is_enabled,
183 .round_rate = voter_clk_round_rate,
184 .set_parent = voter_clk_set_parent,
185 .get_parent = voter_clk_get_parent,
186 .is_local = voter_clk_is_local,
187};