blob: 4cd9b1c8662972b3d5ff177fd0c1f117a51363b6 [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)
Matt Wagantalldd63ac32012-04-05 13:17:31 -070032 rate = max(clk->rate, rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070033 }
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)
Matt Wagantalldd63ac32012-04-05 13:17:31 -070057 other_rate = max(clkp->rate, other_rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070058 }
59
Matt Wagantalldd63ac32012-04-05 13:17:31 -070060 cur_rate = max(other_rate, clk->rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070061 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 }
Matt Wagantalldd63ac32012-04-05 13:17:31 -070069 clk->rate = rate;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070070unlock:
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);
Matt Wagantalldd63ac32012-04-05 13:17:31 -070092 if (clk->rate > cur_rate) {
93 ret = clk_set_rate(parent, clk->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);
Matt Wagantalldd63ac32012-04-05 13:17:31 -0700119 cur_rate = max(new_rate, clk->rate);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700120
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
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700127static int voter_clk_is_enabled(struct clk *clk)
128{
129 struct clk_voter *v = to_clk_voter(clk);
130 return v->enabled;
131}
132
Matt Wagantall9de3bfb2011-11-03 20:13:12 -0700133static long voter_clk_round_rate(struct clk *clk, unsigned long rate)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700134{
135 struct clk_voter *v = to_clk_voter(clk);
136 return clk_round_rate(v->parent, rate);
137}
138
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700139static struct clk *voter_clk_get_parent(struct clk *clk)
140{
141 struct clk_voter *v = to_clk_voter(clk);
142 return v->parent;
143}
144
145static bool voter_clk_is_local(struct clk *clk)
146{
147 return true;
148}
149
Matt Wagantallbdd6e902012-05-09 20:48:25 -0700150static enum handoff voter_clk_handoff(struct clk *clk)
Matt Wagantall35e78fc2012-04-05 14:18:44 -0700151{
152 /* Apply default rate vote */
153 if (clk->rate)
154 return HANDOFF_ENABLED_CLK;
155
156 return HANDOFF_DISABLED_CLK;
157}
158
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700159struct clk_ops clk_ops_voter = {
160 .enable = voter_clk_enable,
161 .disable = voter_clk_disable,
162 .set_rate = voter_clk_set_rate,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700163 .is_enabled = voter_clk_is_enabled,
164 .round_rate = voter_clk_round_rate,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700165 .get_parent = voter_clk_get_parent,
166 .is_local = voter_clk_is_local,
Matt Wagantallbdd6e902012-05-09 20:48:25 -0700167 .handoff = voter_clk_handoff,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700168};