blob: 2624f6ce9b038f0703ec3e7796d101b2bea35b9a [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. */
Matt Wagantall9de3bfb2011-11-03 20:13:12 -070024static unsigned long voter_clk_aggregate_rate(const struct clk *parent)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070025{
26 struct clk *clk;
Matt Wagantall9de3bfb2011-11-03 20:13:12 -070027 unsigned long rate = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070028
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
Matt Wagantall9de3bfb2011-11-03 20:13:12 -070037static int voter_clk_set_rate(struct clk *clk, unsigned long rate)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070038{
39 int ret = 0;
40 unsigned long flags;
41 struct clk *clkp;
42 struct clk_voter *clkh, *v = to_clk_voter(clk);
Matt Wagantall9de3bfb2011-11-03 20:13:12 -070043 unsigned long cur_rate, new_rate, other_rate = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070044
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) {
Matt Wagantall8e6126f2011-11-08 13:34:19 -080064 ret = clk_set_rate(parent, new_rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070065 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{
Saravana Kannan4d77a4f2011-09-26 19:02:02 -070078 int ret = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070079 unsigned long flags;
Matt Wagantall9de3bfb2011-11-03 20:13:12 -070080 unsigned long cur_rate;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070081 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) {
Matt Wagantall8e6126f2011-11-08 13:34:19 -080093 ret = clk_set_rate(parent, v->rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070094 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{
Matt Wagantall9de3bfb2011-11-03 20:13:12 -0700106 unsigned long flags, cur_rate, new_rate;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700107 struct clk *parent;
108 struct clk_voter *v = to_clk_voter(clk);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700109
110 spin_lock_irqsave(&voter_clk_lock, flags);
111 parent = v->parent;
112
113 /*
114 * Decrease the rate if this clock was the only one voting for
115 * the highest rate.
116 */
117 v->enabled = false;
118 new_rate = voter_clk_aggregate_rate(parent);
119 cur_rate = max(new_rate, v->rate);
120
121 if (new_rate < cur_rate)
Matt Wagantall8e6126f2011-11-08 13:34:19 -0800122 clk_set_rate(parent, new_rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700123
124 spin_unlock_irqrestore(&voter_clk_lock, flags);
125}
126
Matt Wagantall9de3bfb2011-11-03 20:13:12 -0700127static unsigned long voter_clk_get_rate(struct clk *clk)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700128{
Matt Wagantall9de3bfb2011-11-03 20:13:12 -0700129 unsigned long rate, flags;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700130 struct clk_voter *v = to_clk_voter(clk);
131
132 spin_lock_irqsave(&voter_clk_lock, flags);
133 rate = v->rate;
134 spin_unlock_irqrestore(&voter_clk_lock, flags);
135
136 return rate;
137}
138
139static int voter_clk_is_enabled(struct clk *clk)
140{
141 struct clk_voter *v = to_clk_voter(clk);
142 return v->enabled;
143}
144
Matt Wagantall9de3bfb2011-11-03 20:13:12 -0700145static long voter_clk_round_rate(struct clk *clk, unsigned long rate)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700146{
147 struct clk_voter *v = to_clk_voter(clk);
148 return clk_round_rate(v->parent, rate);
149}
150
151static int voter_clk_set_parent(struct clk *clk, struct clk *parent)
152{
153 unsigned long flags;
154
155 spin_lock_irqsave(&voter_clk_lock, flags);
156 if (list_empty(&clk->siblings))
157 list_add(&clk->siblings, &parent->children);
158 spin_unlock_irqrestore(&voter_clk_lock, flags);
159
160 return 0;
161}
162
163static struct clk *voter_clk_get_parent(struct clk *clk)
164{
165 struct clk_voter *v = to_clk_voter(clk);
166 return v->parent;
167}
168
169static bool voter_clk_is_local(struct clk *clk)
170{
171 return true;
172}
173
174struct clk_ops clk_ops_voter = {
175 .enable = voter_clk_enable,
176 .disable = voter_clk_disable,
177 .set_rate = voter_clk_set_rate,
178 .set_min_rate = voter_clk_set_rate,
179 .get_rate = voter_clk_get_rate,
180 .is_enabled = voter_clk_is_enabled,
181 .round_rate = voter_clk_round_rate,
182 .set_parent = voter_clk_set_parent,
183 .get_parent = voter_clk_get_parent,
184 .is_local = voter_clk_is_local,
185};