diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/misc/tegra-baseband/bb-power.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/misc/tegra-baseband/bb-power.c')
-rw-r--r-- | drivers/misc/tegra-baseband/bb-power.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/drivers/misc/tegra-baseband/bb-power.c b/drivers/misc/tegra-baseband/bb-power.c new file mode 100644 index 00000000000..9210a8f3e84 --- /dev/null +++ b/drivers/misc/tegra-baseband/bb-power.c | |||
@@ -0,0 +1,337 @@ | |||
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/usb.h> | ||
28 | #include <linux/uaccess.h> | ||
29 | #include <linux/platform_data/tegra_usb.h> | ||
30 | #include <mach/usb_phy.h> | ||
31 | #include <mach/tegra-bb-power.h> | ||
32 | #include "bb-power.h" | ||
33 | |||
34 | static struct tegra_bb_callback *callback; | ||
35 | static int attr_load_val; | ||
36 | static struct tegra_bb_power_mdata *mdata; | ||
37 | static bb_get_cblist get_cblist[] = { | ||
38 | NULL, | ||
39 | NULL, | ||
40 | NULL, | ||
41 | M7400_CB, | ||
42 | }; | ||
43 | |||
44 | static int tegra_bb_power_gpio_init(struct tegra_bb_power_gdata *gdata) | ||
45 | { | ||
46 | int ret; | ||
47 | int irq; | ||
48 | unsigned gpio_id; | ||
49 | const char *gpio_label; | ||
50 | unsigned long gpio_flags; | ||
51 | struct tegra_bb_gpio_data *gpiolist; | ||
52 | struct tegra_bb_gpio_irqdata *gpioirq; | ||
53 | |||
54 | gpiolist = gdata->gpio; | ||
55 | for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) { | ||
56 | gpio_id = (gpiolist->data.gpio); | ||
57 | gpio_label = (gpiolist->data.label); | ||
58 | gpio_flags = (gpiolist->data.flags); | ||
59 | |||
60 | /* Request the gpio */ | ||
61 | ret = gpio_request(gpio_id, gpio_label); | ||
62 | if (ret) { | ||
63 | pr_err("%s: Error: gpio_request for gpio %d failed.\n", | ||
64 | __func__, gpio_id); | ||
65 | return ret; | ||
66 | } | ||
67 | |||
68 | /* Set gpio direction, as requested */ | ||
69 | if (gpio_flags == GPIOF_IN) | ||
70 | gpio_direction_input(gpio_id); | ||
71 | else | ||
72 | gpio_direction_output(gpio_id, (!gpio_flags ? 0 : 1)); | ||
73 | |||
74 | /* Enable the gpio */ | ||
75 | tegra_gpio_enable(gpio_id); | ||
76 | |||
77 | /* Create a sysfs node, if requested */ | ||
78 | if (gpiolist->doexport) | ||
79 | gpio_export(gpio_id, false); | ||
80 | } | ||
81 | |||
82 | gpioirq = gdata->gpioirq; | ||
83 | for (; gpioirq->id != GPIO_INVALID; ++gpioirq) { | ||
84 | |||
85 | /* Create interrupt handler, if requested */ | ||
86 | if (gpioirq->handler != NULL) { | ||
87 | irq = gpio_to_irq(gpioirq->id); | ||
88 | ret = request_threaded_irq(irq, NULL, gpioirq->handler, | ||
89 | gpioirq->flags, gpioirq->name, gpioirq->cookie); | ||
90 | if (ret < 0) { | ||
91 | pr_err("%s: Error: threaded_irq req fail.\n" | ||
92 | , __func__); | ||
93 | return ret; | ||
94 | } | ||
95 | |||
96 | if (gpioirq->wake_capable) { | ||
97 | ret = enable_irq_wake(irq); | ||
98 | if (ret) { | ||
99 | pr_err("%s: Error: irqwake req fail.\n", | ||
100 | __func__); | ||
101 | return ret; | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | static int tegra_bb_power_gpio_deinit(struct tegra_bb_power_gdata *gdata) | ||
110 | { | ||
111 | struct tegra_bb_gpio_data *gpiolist; | ||
112 | struct tegra_bb_gpio_irqdata *gpioirq; | ||
113 | |||
114 | gpiolist = gdata->gpio; | ||
115 | for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) { | ||
116 | |||
117 | /* Free the gpio */ | ||
118 | gpio_free(gpiolist->data.gpio); | ||
119 | } | ||
120 | |||
121 | gpioirq = gdata->gpioirq; | ||
122 | for (; gpioirq->id != GPIO_INVALID; ++gpioirq) { | ||
123 | |||
124 | /* Free the irq */ | ||
125 | free_irq(gpio_to_irq(gpioirq->id), gpioirq->cookie); | ||
126 | } | ||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static ssize_t tegra_bb_attr_write(struct device *dev, | ||
131 | struct device_attribute *attr, | ||
132 | const char *buf, size_t count) | ||
133 | { | ||
134 | int val; | ||
135 | |||
136 | if (sscanf(buf, "%d", &val) != 1) | ||
137 | return -EINVAL; | ||
138 | |||
139 | if (callback && callback->attrib) { | ||
140 | if (!callback->attrib(dev, val)) | ||
141 | attr_load_val = val; | ||
142 | } | ||
143 | return count; | ||
144 | } | ||
145 | |||
146 | static ssize_t tegra_bb_attr_read(struct device *dev, | ||
147 | struct device_attribute *attr, char *buf) | ||
148 | { | ||
149 | return sprintf(buf, "%d", attr_load_val); | ||
150 | } | ||
151 | |||
152 | static DEVICE_ATTR(load, S_IRUSR | S_IWUSR | S_IRGRP, | ||
153 | tegra_bb_attr_read, tegra_bb_attr_write); | ||
154 | |||
155 | static void tegra_usbdevice_added(struct usb_device *udev) | ||
156 | { | ||
157 | const struct usb_device_descriptor *desc = &udev->descriptor; | ||
158 | |||
159 | if (desc->idVendor == mdata->vid && | ||
160 | desc->idProduct == mdata->pid) { | ||
161 | pr_debug("%s: Device %s added.\n", udev->product, __func__); | ||
162 | |||
163 | if (mdata->wake_capable) | ||
164 | device_set_wakeup_enable(&udev->dev, true); | ||
165 | if (mdata->autosuspend_ready) | ||
166 | usb_enable_autosuspend(udev); | ||
167 | if (mdata->reg_cb) | ||
168 | mdata->reg_cb(udev); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | static void tegra_usbdevice_removed(struct usb_device *udev) | ||
173 | { | ||
174 | const struct usb_device_descriptor *desc = &udev->descriptor; | ||
175 | |||
176 | if (desc->idVendor == mdata->vid && | ||
177 | desc->idProduct == mdata->pid) { | ||
178 | pr_debug("%s: Device %s removed.\n", udev->product, __func__); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | static int tegra_usb_notify(struct notifier_block *self, unsigned long action, | ||
183 | void *dev) | ||
184 | { | ||
185 | switch (action) { | ||
186 | case USB_DEVICE_ADD: | ||
187 | tegra_usbdevice_added((struct usb_device *)dev); | ||
188 | break; | ||
189 | case USB_DEVICE_REMOVE: | ||
190 | tegra_usbdevice_removed((struct usb_device *)dev); | ||
191 | break; | ||
192 | } | ||
193 | return NOTIFY_OK; | ||
194 | } | ||
195 | |||
196 | static struct notifier_block tegra_usb_nb = { | ||
197 | .notifier_call = tegra_usb_notify, | ||
198 | }; | ||
199 | |||
200 | static int tegra_bb_power_probe(struct platform_device *device) | ||
201 | { | ||
202 | struct device *dev = &device->dev; | ||
203 | struct tegra_bb_pdata *pdata; | ||
204 | struct tegra_bb_power_data *data; | ||
205 | struct tegra_bb_power_gdata *gdata; | ||
206 | int err; | ||
207 | unsigned int bb_id; | ||
208 | |||
209 | pdata = (struct tegra_bb_pdata *) dev->platform_data; | ||
210 | if (!pdata) { | ||
211 | pr_err("%s - Error: platform data is empty.\n", __func__); | ||
212 | return -ENODEV; | ||
213 | } | ||
214 | |||
215 | /* Obtain BB specific callback list */ | ||
216 | bb_id = pdata->bb_id; | ||
217 | if (get_cblist[bb_id] != NULL) { | ||
218 | callback = (struct tegra_bb_callback *) get_cblist[bb_id](); | ||
219 | if (callback && callback->init) { | ||
220 | data = (struct tegra_bb_power_data *) | ||
221 | callback->init((void *)pdata); | ||
222 | |||
223 | gdata = data->gpio_data; | ||
224 | if (!gdata) { | ||
225 | pr_err("%s - Error: Gpio data is empty.\n", | ||
226 | __func__); | ||
227 | return -ENODEV; | ||
228 | } | ||
229 | |||
230 | /* Initialize gpio as required */ | ||
231 | tegra_bb_power_gpio_init(gdata); | ||
232 | |||
233 | mdata = data->modem_data; | ||
234 | if (mdata && mdata->vid && mdata->pid) | ||
235 | /* Register to notifications from usb core */ | ||
236 | usb_register_notify(&tegra_usb_nb); | ||
237 | } else { | ||
238 | pr_err("%s - Error: init callback is empty.\n", | ||
239 | __func__); | ||
240 | return -ENODEV; | ||
241 | } | ||
242 | } else { | ||
243 | pr_err("%s - Error: callback data is empty.\n", __func__); | ||
244 | return -ENODEV; | ||
245 | } | ||
246 | |||
247 | /* Create the control sysfs node */ | ||
248 | err = device_create_file(dev, &dev_attr_load); | ||
249 | if (err < 0) { | ||
250 | pr_err("%s - Error: device_create_file failed.\n", __func__); | ||
251 | return -ENODEV; | ||
252 | } | ||
253 | attr_load_val = 0; | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static int tegra_bb_power_remove(struct platform_device *device) | ||
259 | { | ||
260 | struct device *dev = &device->dev; | ||
261 | struct tegra_bb_power_data *data; | ||
262 | struct tegra_bb_power_gdata *gdata; | ||
263 | |||
264 | /* BB specific callback */ | ||
265 | if (callback && callback->deinit) { | ||
266 | data = (struct tegra_bb_power_data *) | ||
267 | callback->deinit(); | ||
268 | |||
269 | /* Deinitialize gpios */ | ||
270 | gdata = data->gpio_data; | ||
271 | if (gdata) | ||
272 | tegra_bb_power_gpio_deinit(gdata); | ||
273 | else { | ||
274 | pr_err("%s - Error: Gpio data is empty.\n", __func__); | ||
275 | return -ENODEV; | ||
276 | } | ||
277 | |||
278 | mdata = data->modem_data; | ||
279 | if (mdata && mdata->vid && mdata->pid) | ||
280 | /* Register to notifications from usb core */ | ||
281 | usb_unregister_notify(&tegra_usb_nb); | ||
282 | } | ||
283 | |||
284 | /* Remove the control sysfs node */ | ||
285 | device_remove_file(dev, &dev_attr_load); | ||
286 | |||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | #ifdef CONFIG_PM | ||
291 | static int tegra_bb_power_suspend(struct platform_device *device, | ||
292 | pm_message_t state) | ||
293 | { | ||
294 | /* BB specific callback */ | ||
295 | if (callback && callback->power) | ||
296 | callback->power(PWRSTATE_L2L3); | ||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | static int tegra_bb_power_resume(struct platform_device *device) | ||
301 | { | ||
302 | /* BB specific callback */ | ||
303 | if (callback && callback->power) | ||
304 | callback->power(PWRSTATE_L3L0); | ||
305 | return 0; | ||
306 | } | ||
307 | #endif | ||
308 | |||
309 | static struct platform_driver tegra_bb_power_driver = { | ||
310 | .probe = tegra_bb_power_probe, | ||
311 | .remove = tegra_bb_power_remove, | ||
312 | #ifdef CONFIG_PM | ||
313 | .suspend = tegra_bb_power_suspend, | ||
314 | .resume = tegra_bb_power_resume, | ||
315 | #endif | ||
316 | .driver = { | ||
317 | .name = "tegra_baseband_power", | ||
318 | }, | ||
319 | }; | ||
320 | |||
321 | static int __init tegra_baseband_power_init(void) | ||
322 | { | ||
323 | pr_debug("%s\n", __func__); | ||
324 | return platform_driver_register(&tegra_bb_power_driver); | ||
325 | } | ||
326 | |||
327 | static void __exit tegra_baseband_power_exit(void) | ||
328 | { | ||
329 | pr_debug("%s\n", __func__); | ||
330 | platform_driver_unregister(&tegra_bb_power_driver); | ||
331 | } | ||
332 | |||
333 | module_init(tegra_baseband_power_init) | ||
334 | module_exit(tegra_baseband_power_exit) | ||
335 | MODULE_AUTHOR("NVIDIA Corporation"); | ||
336 | MODULE_DESCRIPTION("Tegra modem power management driver"); | ||
337 | MODULE_LICENSE("GPL"); | ||