misc: tegra-baseband: Add power management driver.
[linux-2.6.git] / drivers / misc / tegra-baseband / bb-power.c
1 /*
2  * drivers/misc/tegra-baseband/bb-power.c
3  *
4  * Copyright (C) 2011 NVIDIA Corporation
5  *
6  * This software is licensed under the terms of the GNU General Public
7  * License version 2, as published by the Free Software Foundation, and
8  * may be copied, distributed, and modified under those terms.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  */
16
17 #include <linux/kernel.h>
18 #include <linux/init.h>
19 #include <linux/module.h>
20 #include <linux/moduleparam.h>
21 #include <linux/platform_device.h>
22 #include <linux/gpio.h>
23 #include <linux/interrupt.h>
24 #include <linux/workqueue.h>
25 #include <linux/delay.h>
26 #include <linux/fs.h>
27 #include <linux/uaccess.h>
28 #include <linux/platform_data/tegra_usb.h>
29 #include <mach/usb_phy.h>
30 #include <mach/tegra-bb-power.h>
31 #include "bb-power.h"
32
33 static int bb_id;
34 static bool bb_registered;
35
36 static bb_init_cb init_cb_list[] = {
37         NULL,
38         NULL,
39         NULL,
40         NULL,
41 };
42
43 static bb_power_cb power_cb_list[] = {
44         NULL,
45         NULL,
46         NULL,
47         NULL,
48 };
49
50 static int tegra_bb_power_gpio_init(struct tegra_bb_power_gdata *gdata)
51 {
52         int ret;
53         int irq;
54         unsigned gpio_id;
55         const char *gpio_label;
56         unsigned long gpio_flags;
57         struct tegra_bb_gpio_data *gpiolist;
58         struct tegra_bb_gpio_irqdata *gpioirq;
59
60         gpiolist = gdata->gpio;
61         for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) {
62                 gpio_id = (gpiolist->data.gpio);
63                 gpio_label = (gpiolist->data.label);
64                 gpio_flags = (gpiolist->data.flags);
65
66                 /* Request the gpio */
67                 ret = gpio_request(gpio_id, gpio_label);
68                 if (ret) {
69                         pr_err("%s: gpio_request for gpio %d failed.\n",
70                                                          __func__, gpio_id);
71                         return ret;
72                 }
73
74                 /* Set gpio direction, as requested */
75                 if (gpio_flags == GPIOF_IN)
76                         gpio_direction_input(gpio_id);
77                 else
78                         gpio_direction_output(gpio_id, (!gpio_flags ? 0 : 1));
79
80                 /* Enable the gpio */
81                 tegra_gpio_enable(gpio_id);
82
83                 /* Create a sysfs node, if requested */
84                 if (gpiolist->doexport)
85                         gpio_export(gpio_id, false);
86         }
87
88         gpioirq = gdata->gpioirq;
89         for (; gpioirq->id != GPIO_INVALID; ++gpioirq) {
90
91                 /* Create interrupt handler, if requested */
92                 if (gpioirq->handler != NULL) {
93                         irq = gpio_to_irq(gpioirq->id);
94                         ret = request_threaded_irq(irq, NULL, gpioirq->handler,
95                                 gpioirq->flags, gpioirq->name, gpioirq->cookie);
96                         if (ret < 0) {
97                                 pr_err("%s: request_threaded_irq error\n",
98                                                                  __func__);
99                                 return ret;
100                         }
101                         ret = enable_irq_wake(irq);
102                         if (ret) {
103                                 pr_err("%s: enable_irq_wake error\n", __func__);
104                                 return ret;
105                         }
106                 }
107         }
108         return 0;
109 }
110
111 static int tegra_bb_power_gpio_deinit(struct tegra_bb_power_gdata *gdata)
112 {
113         struct tegra_bb_gpio_data *gpiolist;
114         struct tegra_bb_gpio_irqdata *gpioirq;
115
116         gpiolist = gdata->gpio;
117         for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) {
118
119                 /* Free the gpio */
120                 gpio_free(gpiolist->data.gpio);
121         }
122
123         gpioirq = gdata->gpioirq;
124         for (; gpioirq->id != GPIO_INVALID; ++gpioirq) {
125
126                 /* Free the irq */
127                 free_irq(gpio_to_irq(gpioirq->id), gpioirq->cookie);
128         }
129         return 0;
130 }
131
132 static int baseband_l2_suspend(void)
133 {
134         /* BB specific callback */
135         if (power_cb_list[bb_id] != NULL)
136                 power_cb_list[bb_id](CB_CODE_L0L2);
137         return 0;
138 }
139
140 static int baseband_l2_resume(void)
141 {
142         /* BB specific callback */
143         if (power_cb_list[bb_id] != NULL)
144                 power_cb_list[bb_id](CB_CODE_L2L0);
145         return 0;
146 }
147
148 static ssize_t tegra_bb_attr_write(struct device *dev,
149                         struct device_attribute *attr,
150                         const char *buf, size_t count)
151 {
152         struct tegra_bb_pdata *pdata;
153         struct tegra_ehci_platform_data *ehci_data;
154         struct tegra_uhsic_config *hsic_config;
155         int load;
156
157         if (sscanf(buf, "%d", &load) != 1)
158                 return -EINVAL;
159
160         if (load == 1 && !bb_registered) {
161                 pdata = (struct tegra_bb_pdata *) dev->platform_data;
162                 ehci_data = (struct tegra_ehci_platform_data *)
163                                         pdata->device->dev.platform_data;
164                 hsic_config = (struct tegra_uhsic_config *)
165                                         ehci_data->phy_config;
166
167                 /* Register PHY callbacks */
168                 hsic_config->postsuspend = baseband_l2_suspend;
169                 hsic_config->preresume = baseband_l2_resume;
170
171                 /* Override required settings */
172                 ehci_data->power_down_on_bus_suspend = 0;
173
174                 /* Register the ehci device. */
175                 platform_device_register(pdata->device);
176                 bb_registered = true;
177         }
178
179         return count;
180 }
181
182 static ssize_t tegra_bb_attr_read(struct device *dev,
183                         struct device_attribute *attr, char *buf)
184 {
185         int ret = 0;
186         return sprintf(buf, "%d", ret);
187 }
188
189 static DEVICE_ATTR(load, S_IRUSR | S_IWUSR | S_IRGRP,
190                 tegra_bb_attr_read, tegra_bb_attr_write);
191
192 static int tegra_bb_power_probe(struct platform_device *device)
193 {
194         struct device *dev = &device->dev;
195         struct tegra_bb_pdata *pdata;
196         struct tegra_bb_power_gdata *gdata;
197         int err;
198
199         pdata = (struct tegra_bb_pdata *) dev->platform_data;
200         if (!pdata) {
201                 pr_err("%s - Error: platform data is empty.\n", __func__);
202                 return -ENODEV;
203         }
204
205         /* BB specific callback */
206         bb_id = pdata->bb_id;
207         if (init_cb_list[bb_id] != NULL) {
208                 gdata = init_cb_list[pdata->bb_id](pdata, CB_CODE_INIT);
209
210                 if (!gdata) {
211                         pr_err("%s - Error: Gpio data is empty.\n", __func__);
212                         return -ENODEV;
213                 }
214
215                 /* Initialize gpio as required */
216                 tegra_bb_power_gpio_init(gdata);
217         }
218
219         bb_registered = false;
220
221         /* Create the control sysfs node */
222         err = device_create_file(dev, &dev_attr_load);
223         if (err < 0) {
224                 pr_err("%s - device_create_file failed\n", __func__);
225                 return -ENODEV;
226         }
227
228         return 0;
229 }
230
231 static int tegra_bb_power_remove(struct platform_device *device)
232 {
233         struct device *dev = &device->dev;
234         struct tegra_bb_pdata *pdata;
235         struct tegra_bb_power_gdata *gdata;
236
237         /* BB specific callback */
238         if (init_cb_list[bb_id] != NULL) {
239                 pdata = (struct tegra_bb_pdata *) dev->platform_data;
240                 gdata = init_cb_list[bb_id](pdata, CB_CODE_DEINIT);
241
242                 /* Deinitialize gpios */
243                 if (gdata)
244                         tegra_bb_power_gpio_deinit(gdata);
245         }
246
247         /* Remove the control sysfs node */
248         device_remove_file(dev, &dev_attr_load);
249
250         return 0;
251 }
252
253 #ifdef CONFIG_PM
254 static int tegra_bb_power_suspend(struct platform_device *device,
255         pm_message_t state)
256 {
257         /* BB specific callback */
258         if (power_cb_list[bb_id] != NULL)
259                 power_cb_list[bb_id](CB_CODE_L2L3);
260
261         return 0;
262 }
263
264 static int tegra_bb_power_resume(struct platform_device *device)
265 {
266         /* BB specific callback */
267         if (power_cb_list[bb_id] != NULL)
268                 power_cb_list[bb_id](CB_CODE_L3L0);
269
270         return 0;
271 }
272 #endif
273
274 static struct platform_driver tegra_bb_power_driver = {
275         .probe = tegra_bb_power_probe,
276         .remove = tegra_bb_power_remove,
277 #ifdef CONFIG_PM
278         .suspend = tegra_bb_power_suspend,
279         .resume = tegra_bb_power_resume,
280 #endif
281         .driver = {
282                 .name = "tegra_baseband_power",
283         },
284 };
285
286 static int __init tegra_baseband_power_init(void)
287 {
288         pr_debug("%s\n", __func__);
289         return platform_driver_register(&tegra_bb_power_driver);
290 }
291
292 static void __exit tegra_baseband_power_exit(void)
293 {
294         pr_debug("%s\n", __func__);
295         platform_driver_unregister(&tegra_bb_power_driver);
296 }
297
298 module_init(tegra_baseband_power_init)
299 module_exit(tegra_baseband_power_exit)
300 MODULE_AUTHOR("NVIDIA Corporation");
301 MODULE_DESCRIPTION("Tegra modem power management driver");
302 MODULE_LICENSE("GPL");