blob: 97edf8cfa41913088c507144d4865d5bdaa2c9af [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/kernel.h>
16#include <linux/platform_device.h>
17#include <linux/err.h>
18#include <linux/regulator/driver.h>
19#include <linux/regulator/machine.h>
20#include <linux/clk.h>
21#include "footswitch.h"
22#include "proc_comm.h"
23
24/* PCOM power rail IDs */
25#define PCOM_FS_GRP 8
26#define PCOM_FS_GRP_2D 58
27#define PCOM_FS_MDP 14
28#define PCOM_FS_MFC 68
29#define PCOM_FS_ROTATOR 90
30#define PCOM_FS_VFE 41
31#define PCOM_FS_VPE 76
32
33#define PCOM_RAIL_MODE_AUTO 0
34#define PCOM_RAIL_MODE_MANUAL 1
35
36/**
37 * struct footswitch - Per-footswitch data and state
38 * @rdev: Regulator framework device
39 * @desc: Regulator descriptor
40 * @init_data: Regulator platform data
41 * @pcom_id: Proc-comm ID of the footswitch
42 * @is_enabled: Flag set when footswitch is enabled
43 * @is_manual: Flag set when footswitch is in manual proc-comm mode
44 * @core_clk_name: String name of core clock for footswitch power domain
45 * @set_clk_name: String name of clock used to set the core clocks's rate
46 * @ahb_clk_name: String name of AHB clock for footswitch power domain
47 * @core_clk: Clock with name core_clk_name
48 * @set_clk: Clock with name set_clk_name
49 * @abh_clk: Clock with name ahb_clk_name
50 * @set_clk_init_rate: Rate to use for set_clk to if one has not yet been set
51 * @is_rate_set: Flag set if the core clock's rate has been set
52 */
53struct footswitch {
54 struct regulator_dev *rdev;
55 struct regulator_desc desc;
56 struct regulator_init_data init_data;
57 unsigned pcom_id;
58 bool is_enabled;
59 bool is_manual;
60 const char *core_clk_name;
61 const char *set_clk_name;
62 const char *ahb_clk_name;
63 struct clk *core_clk;
64 struct clk *set_clk;
65 struct clk *ahb_clk;
66 const int set_clk_init_rate;
67 bool is_rate_set;
68};
69
70static inline int set_rail_mode(int pcom_id, int mode)
71{
72 int rc;
73
74 rc = msm_proc_comm(PCOM_CLKCTL_RPC_RAIL_CONTROL, &pcom_id, &mode);
75 if (!rc && pcom_id)
76 rc = -EINVAL;
77
78 return rc;
79}
80
81static inline int set_rail_state(int pcom_id, int state)
82{
83 int rc;
84
85 rc = msm_proc_comm(state, &pcom_id, NULL);
86 if (!rc && pcom_id)
87 rc = -EINVAL;
88
89 return rc;
90}
91
92static int enable_clocks(struct footswitch *fs)
93{
94 fs->is_rate_set = !!(clk_get_rate(fs->set_clk));
95 if (!fs->is_rate_set)
96 clk_set_rate(fs->set_clk, fs->set_clk_init_rate);
97 clk_enable(fs->core_clk);
98
99 if (fs->ahb_clk)
100 clk_enable(fs->ahb_clk);
101
102 return 0;
103}
104
105static void disable_clocks(struct footswitch *fs)
106{
107 if (fs->ahb_clk)
108 clk_disable(fs->ahb_clk);
109 clk_disable(fs->core_clk);
110}
111
112static int footswitch_is_enabled(struct regulator_dev *rdev)
113{
114 struct footswitch *fs = rdev_get_drvdata(rdev);
115
116 return fs->is_enabled;
117}
118
119static int footswitch_enable(struct regulator_dev *rdev)
120{
121 struct footswitch *fs = rdev_get_drvdata(rdev);
122 int rc;
123
124 rc = enable_clocks(fs);
125 if (rc)
126 return rc;
127
128 rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE);
129 if (!rc)
130 fs->is_enabled = true;
131
132 disable_clocks(fs);
133
134 return rc;
135}
136
137static int footswitch_disable(struct regulator_dev *rdev)
138{
139 struct footswitch *fs = rdev_get_drvdata(rdev);
140 int rc;
141
142 rc = enable_clocks(fs);
143 if (rc)
144 return rc;
145
146 rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_DISABLE);
147 if (!rc)
148 fs->is_enabled = false;
149
150 disable_clocks(fs);
151
152 return rc;
153}
154
155static struct regulator_ops footswitch_ops = {
156 .is_enabled = footswitch_is_enabled,
157 .enable = footswitch_enable,
158 .disable = footswitch_disable,
159};
160
161#define FOOTSWITCH(_id, _pcom_id, _name, _core_clk, _set_clk, _rate, _ahb_clk) \
162 [_id] = { \
163 .desc = { \
164 .id = _id, \
165 .name = _name, \
166 .ops = &footswitch_ops, \
167 .type = REGULATOR_VOLTAGE, \
168 .owner = THIS_MODULE, \
169 }, \
170 .pcom_id = _pcom_id, \
171 .core_clk_name = _core_clk, \
172 .set_clk_name = _set_clk, \
173 .set_clk_init_rate = _rate, \
174 .ahb_clk_name = _ahb_clk, \
175 }
176static struct footswitch footswitches[] = {
177 FOOTSWITCH(FS_GFX3D, PCOM_FS_GRP, "fs_gfx3d",
178 "grp_clk", "grp_src_clk", 24576000, "grp_pclk"),
179 FOOTSWITCH(FS_GFX2D0, PCOM_FS_GRP_2D, "fs_gfx2d0",
180 "grp_2d_clk", NULL, 24576000, "grp_2d_pclk"),
181 FOOTSWITCH(FS_MDP, PCOM_FS_MDP, "fs_mdp",
182 "mdp_clk", NULL, 24576000, "mdp_pclk"),
183 FOOTSWITCH(FS_MFC, PCOM_FS_MFC, "fs_mfc",
184 "mfc_clk", NULL, 24576000, "mfc_pclk"),
185 FOOTSWITCH(FS_ROT, PCOM_FS_ROTATOR, "fs_rot",
186 "rotator_clk", NULL, 0, "rotator_pclk"),
187 FOOTSWITCH(FS_VFE, PCOM_FS_VFE, "fs_vfe",
188 "vfe_clk", NULL, 24576000, "vfe_pclk"),
189 FOOTSWITCH(FS_VPE, PCOM_FS_VPE, "fs_vpe",
190 "vpe_clk", NULL, 24576000, NULL),
191};
192
193static int get_clocks(struct footswitch *fs)
194{
195 int rc;
196
197 /*
198 * Some SoCs may not have a separate rate-settable clock.
199 * If one can't be found, try to use the core clock for
200 * rate-setting instead.
201 */
202 if (fs->set_clk_name) {
203 fs->set_clk = clk_get(NULL, fs->set_clk_name);
204 if (IS_ERR(fs->set_clk)) {
205 fs->set_clk = clk_get(NULL, fs->core_clk_name);
206 fs->set_clk_name = fs->core_clk_name;
207 }
208 } else {
209 fs->set_clk = clk_get(NULL, fs->core_clk_name);
210 fs->set_clk_name = fs->core_clk_name;
211 }
212 if (IS_ERR(fs->set_clk)) {
213 pr_err("clk_get(%s) failed\n", fs->set_clk_name);
214 rc = PTR_ERR(fs->set_clk);
215 goto err_set_clk;
216 }
217
218 fs->core_clk = clk_get(NULL, fs->core_clk_name);
219 if (IS_ERR(fs->core_clk)) {
220 pr_err("clk_get(%s) failed\n", fs->core_clk_name);
221 rc = PTR_ERR(fs->core_clk);
222 goto err_core_clk;
223 }
224
225 if (fs->ahb_clk_name) {
226 fs->ahb_clk = clk_get(NULL, fs->ahb_clk_name);
227 if (IS_ERR(fs->ahb_clk)) {
228 pr_err("clk_get(%s) failed\n", fs->ahb_clk_name);
229 rc = PTR_ERR(fs->ahb_clk);
230 goto err_ahb_clk;
231 }
232 }
233
234 return 0;
235
236err_ahb_clk:
237 clk_put(fs->core_clk);
238err_core_clk:
239 clk_put(fs->set_clk);
240err_set_clk:
241 return rc;
242}
243
244static void put_clocks(struct footswitch *fs)
245{
246 clk_put(fs->set_clk);
247 clk_put(fs->core_clk);
248 clk_put(fs->ahb_clk);
249}
250
251static int footswitch_probe(struct platform_device *pdev)
252{
253 struct footswitch *fs;
254 struct regulator_init_data *init_data;
255 int rc;
256
257 if (pdev == NULL)
258 return -EINVAL;
259
260 if (pdev->id >= MAX_FS)
261 return -ENODEV;
262
263 fs = &footswitches[pdev->id];
264 if (!fs->is_manual) {
265 pr_err("%s is not in manual mode\n", fs->desc.name);
266 return -EINVAL;
267 }
268 init_data = pdev->dev.platform_data;
269
270 rc = get_clocks(fs);
271 if (rc)
272 return rc;
273
274 fs->rdev = regulator_register(&fs->desc, &pdev->dev, init_data, fs);
275 if (IS_ERR(fs->rdev)) {
276 pr_err("regulator_register(%s) failed\n", fs->desc.name);
277 rc = PTR_ERR(fs->rdev);
278 goto err_register;
279 }
280
281 return 0;
282
283err_register:
284 put_clocks(fs);
285
286 return rc;
287}
288
289static int __devexit footswitch_remove(struct platform_device *pdev)
290{
291 struct footswitch *fs = &footswitches[pdev->id];
292
293 regulator_unregister(fs->rdev);
294 set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_AUTO);
295 put_clocks(fs);
296
297 return 0;
298}
299
300static struct platform_driver footswitch_driver = {
301 .probe = footswitch_probe,
302 .remove = __devexit_p(footswitch_remove),
303 .driver = {
304 .name = "footswitch-pcom",
305 .owner = THIS_MODULE,
306 },
307};
308
309static int __init footswitch_init(void)
310{
311 struct footswitch *fs;
312 int ret;
313
314 /*
315 * Enable all footswitches in manual mode (ie. not controlled along
316 * with pcom clocks).
317 */
318 for (fs = footswitches; fs < footswitches + ARRAY_SIZE(footswitches);
319 fs++) {
320 set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE);
321 ret = set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_MANUAL);
322 if (!ret)
323 fs->is_manual = 1;
324 }
325
326 return platform_driver_register(&footswitch_driver);
327}
328subsys_initcall(footswitch_init);
329
330static void __exit footswitch_exit(void)
331{
332 platform_driver_unregister(&footswitch_driver);
333}
334module_exit(footswitch_exit);
335
336MODULE_LICENSE("GPLv2");
337MODULE_DESCRIPTION("proc_comm rail footswitch");
338MODULE_ALIAS("platform:footswitch-pcom");