summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGaurav Asati <gasati@nvidia.com>2016-05-08 13:17:45 -0400
committermobile promotions <svcmobile_promotions@nvidia.com>2018-07-19 16:54:30 -0400
commita38c50d1ffef55dd73505e360672fab3095e2bbb (patch)
tree4dab2870da0f76f50d3f8a5fc67f873b6c47770e
parent0b4617dc859097866aa62be19cc59e69bc0d579b (diff)
drivers: misc: add bluedroid_pm drver
This driver is implemented to actively manage the bluetooth radio power and control wake interface from bluetooth radio to host processor. Two interfaces are used to manage bluetooth radio power RFKILL: This interface is used to toggle rfkill switches which are controlled by gpios and voltage regulators. This interface is created only when bluetooth radio requires one of rfkill switches [GPIO/ Voltage regulators] Driver assumes vdd_bt_3v3 as consumer name for 3V regulator and vddio_bt_1v8 as consumer name for 1.8V regulator PROCFS: Using procfs interface, driver controls BT chip ext_wake GPIO to enable/disable BT chip LPM. This interface is created only when bluetooth module supports LPM mode control using GPIO This driver assumes platform data is passed from the board files to configure IO parameters needed to control bluetooth radio. "shutdown_gpio" is resource name to register radio's Enable/Shutdown gpio "reset_gpio" is resource name to regster radio's reset gpio "gpio_ext_wake" is resource name to register radio's ext_wake gpio "gpio_host_wake" is resource name to register host wake gpio "host_wake" is resource name to register host wake irq Bug 200197107 Change-Id: Ida4c9388547e01ba15def93f91af595a79fa4497 Signed-off-by: Gaurav Asati <gasati@nvidia.com> Signed-off-by: Nagarjuna Kristam <nkristam@nvidia.com> Reviewed-on: http://git-master/r/1143283 (cherry picked from k4.9 commit 3f81df5f0050a3326df38961bc33036f757a6fa1) Reviewed-on: https://git-master.nvidia.com/r/1775102 Tested-by: Bitan Biswas <bbiswas@nvidia.com> Reviewed-by: Sachin Nikam <snikam@nvidia.com> GVS: Gerrit_Virtual_Submit Reviewed-by: Bitan Biswas <bbiswas@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
-rw-r--r--drivers/misc/Kconfig10
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/bluedroid_pm.c682
3 files changed, 693 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index eb2eebff4..58ae030a3 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -92,4 +92,14 @@ source "drivers/misc/tegra-profiler/Kconfig"
92source "drivers/misc/eventlib/Kconfig" 92source "drivers/misc/eventlib/Kconfig"
93endif 93endif
94 94
95config BLUEDROID_PM
96 tristate "Bluedroid_pm driver support"
97 help
98 Bluetooth Bluedroid power management Driver.
99 This driver provides power control and dynamic active power saving
100 mechanism for bluetooth radio devices.
101
102 Say Y here to compile support for bluedroid_pm support into the kernel
103 or say M to compile it as module (bluedroid_pm).
104
95endmenu 105endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 76d0eee95..606fcc626 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_TEGRA_PROFILER) += tegra-profiler/
6obj-$(CONFIG_NVS_DFSH) += nvs-dfsh/ 6obj-$(CONFIG_NVS_DFSH) += nvs-dfsh/
7obj-$(CONFIG_SAF775x_HWDEP) += saf775x/ 7obj-$(CONFIG_SAF775x_HWDEP) += saf775x/
8obj-$(CONFIG_SAF36XX_HWDEP) += saf775x/ 8obj-$(CONFIG_SAF36XX_HWDEP) += saf775x/
9obj-$(CONFIG_BLUEDROID_PM) += bluedroid_pm.o
9obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o 10obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o
10obj-$(CONFIG_TEGRA_CPC) += tegra_cpc.o 11obj-$(CONFIG_TEGRA_CPC) += tegra_cpc.o
11obj-$(CONFIG_THERM_EST) += therm_est.o 12obj-$(CONFIG_THERM_EST) += therm_est.o
diff --git a/drivers/misc/bluedroid_pm.c b/drivers/misc/bluedroid_pm.c
new file mode 100644
index 000000000..63f661787
--- /dev/null
+++ b/drivers/misc/bluedroid_pm.c
@@ -0,0 +1,682 @@
1/*
2 * drivers/misc/bluedroid_pm.c
3 *
4 # Copyright (c) 2013-2015, NVIDIA CORPORATION. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include <linux/err.h>
22#include <linux/types.h>
23#include <linux/uaccess.h>
24#include <linux/fs.h>
25#include <linux/gpio.h>
26#include <linux/init.h>
27#include <linux/kernel.h>
28#include <linux/miscdevice.h>
29#include <linux/module.h>
30#include <linux/proc_fs.h>
31#include <linux/regulator/consumer.h>
32#include <linux/rfkill.h>
33#include <linux/platform_device.h>
34#include <linux/clk.h>
35#include <linux/interrupt.h>
36#include <linux/wakelock.h>
37#include <linux/slab.h>
38#include <linux/pm_qos.h>
39#include <linux/bluedroid_pm.h>
40#include <linux/delay.h>
41#include <linux/timer.h>
42#include <linux/of.h>
43#include <linux/of_gpio.h>
44
45#define PROC_DIR "bluetooth/sleep"
46
47/* 5 seconds of Min CPU configurations during resume */
48#define DEFAULT_RESUME_CPU_TIMEOUT 5000000
49
50#define TX_TIMER_INTERVAL 5
51
52/* Macro to enable or disable debug logging */
53/* #define BLUEDROID_PM_DBG */
54#ifndef BLUEDROID_PM_DBG
55#define BDP_DBG(fmt, ...) pr_debug("%s: " fmt, __func__, ##__VA_ARGS__)
56#else
57#define BDP_DBG(fmt, ...) pr_warn("%s: " fmt, __func__, ##__VA_ARGS__)
58#endif
59
60#define BDP_WARN(fmt, ...) pr_warn("%s: " fmt, __func__, ##__VA_ARGS__)
61#define BDP_ERR(fmt, ...) pr_err("%s: " fmt, __func__, ##__VA_ARGS__)
62
63/* status flags for bluedroid_pm_driver */
64#define BT_WAKE 0x01
65
66struct bluedroid_pm_data {
67 struct platform_device *pdev;
68 int gpio_reset;
69 int gpio_shutdown;
70 int host_wake;
71 int ext_wake;
72 int is_blocked;
73 int resume_min_frequency;
74 unsigned long flags;
75 int host_wake_irq;
76 struct regulator *vdd_3v3;
77 struct regulator *vdd_1v8;
78 struct rfkill *rfkill;
79 struct wake_lock wake_lock;
80 struct pm_qos_request resume_cpu_freq_req;
81 bool resumed;
82 struct work_struct work;
83 spinlock_t lock;
84};
85
86struct proc_dir_entry *proc_bt_dir, *bluetooth_sleep_dir;
87static bool bluedroid_pm_blocked = 1;
88
89static int create_bt_proc_interface(void *drv_data);
90static void remove_bt_proc_interface(void);
91
92static DEFINE_MUTEX(bt_wlan_sync);
93
94void bt_wlan_lock(void)
95{
96 mutex_lock(&bt_wlan_sync);
97}
98EXPORT_SYMBOL(bt_wlan_lock);
99
100void bt_wlan_unlock(void)
101{
102 mutex_unlock(&bt_wlan_sync);
103}
104EXPORT_SYMBOL(bt_wlan_unlock);
105
106/** bluedroid_m busy timer */
107static void bluedroid_pm_timer_expire(unsigned long data);
108static DEFINE_TIMER(bluedroid_pm_timer, bluedroid_pm_timer_expire, 0, 0);
109static int bluedroid_pm_gpio_get_value(unsigned int gpio);
110static void bluedroid_pm_gpio_set_value(unsigned int gpio, int value);
111
112static void bluedroid_work(struct work_struct *data)
113{
114 struct bluedroid_pm_data *bluedroid_pm =
115 container_of(data, struct bluedroid_pm_data, work);
116 struct device *dev = &bluedroid_pm->pdev->dev;
117 char *resumed[2] = { "BT_STATE=RESUMED", NULL };
118 char **uevent_envp = NULL;
119 unsigned long flags;
120
121 spin_lock_irqsave(&bluedroid_pm->lock, flags);
122 if (!bluedroid_pm->is_blocked && bluedroid_pm->resumed)
123 uevent_envp = resumed;
124 spin_unlock_irqrestore(&bluedroid_pm->lock, flags);
125
126 if (uevent_envp) {
127 kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, uevent_envp);
128 BDP_DBG("is_blocked=%d, resumed=%d, uevent %s\n",
129 bluedroid_pm->is_blocked, bluedroid_pm->resumed,
130 uevent_envp[0]);
131 } else {
132 BDP_DBG("is_blocked=%d, resumed=%d, no uevent\n",
133 bluedroid_pm->is_blocked, bluedroid_pm->resumed);
134 }
135}
136
137static irqreturn_t bluedroid_pm_hostwake_isr(int irq, void *dev_id)
138{
139 /* schedule a tasklet to handle the change in the host wake line */
140 return IRQ_HANDLED;
141}
142
143static int bluedroid_pm_gpio_get_value(unsigned int gpio)
144{
145 if (gpio_cansleep(gpio))
146 return gpio_get_value_cansleep(gpio);
147 else
148 return gpio_get_value(gpio);
149}
150
151static void bluedroid_pm_gpio_set_value(unsigned int gpio, int value)
152{
153 if (gpio_cansleep(gpio))
154 gpio_set_value_cansleep(gpio, value);
155 else
156 gpio_set_value(gpio, value);
157}
158
159/**
160 * Handles bluedroid_pm busy timer expiration.
161 * @param data: bluedroid_pm strcuture.
162 */
163static void bluedroid_pm_timer_expire(unsigned long data)
164{
165 struct bluedroid_pm_data *bluedroid_pm =
166 (struct bluedroid_pm_data *)data;
167
168 /*
169 * if bluedroid_pm data is NULL or timer is deleted with TX busy.
170 * return from the function.
171 */
172 if (!bluedroid_pm || test_bit(BT_WAKE, &bluedroid_pm->flags))
173 return;
174
175 if (!bluedroid_pm_gpio_get_value(bluedroid_pm->host_wake)) {
176 /* BT can sleep */
177 BDP_DBG("Tx and Rx are idle, BT sleeping");
178 bluedroid_pm_gpio_set_value(bluedroid_pm->ext_wake, 0);
179 wake_unlock(&bluedroid_pm->wake_lock);
180 } else {
181 /* BT Rx is busy, Reset Timer */
182 BDP_DBG("Rx is busy, restarting the timer");
183 mod_timer(&bluedroid_pm_timer,
184 jiffies + (TX_TIMER_INTERVAL * HZ));
185 }
186}
187
188static int bluedroid_pm_rfkill_set_power(void *data, bool blocked)
189{
190 struct bluedroid_pm_data *bluedroid_pm = data;
191 int ret = 0;
192
193 mdelay(100);
194 if (blocked) {
195 if (gpio_is_valid(bluedroid_pm->gpio_shutdown))
196 bluedroid_pm_gpio_set_value(
197 bluedroid_pm->gpio_shutdown, 0);
198 if (gpio_is_valid(bluedroid_pm->gpio_reset))
199 bluedroid_pm_gpio_set_value(
200 bluedroid_pm->gpio_reset, 0);
201 if (bluedroid_pm->vdd_3v3)
202 ret |= regulator_disable(bluedroid_pm->vdd_3v3);
203 if (bluedroid_pm->vdd_1v8)
204 ret |= regulator_disable(bluedroid_pm->vdd_1v8);
205 if (gpio_is_valid(bluedroid_pm->ext_wake))
206 wake_unlock(&bluedroid_pm->wake_lock);
207 if (bluedroid_pm->resume_min_frequency)
208 pm_qos_remove_request(&bluedroid_pm->
209 resume_cpu_freq_req);
210 } else {
211 if (bluedroid_pm->vdd_3v3)
212 ret |= regulator_enable(bluedroid_pm->vdd_3v3);
213 if (bluedroid_pm->vdd_1v8)
214 ret |= regulator_enable(bluedroid_pm->vdd_1v8);
215 if (gpio_is_valid(bluedroid_pm->gpio_shutdown))
216 bluedroid_pm_gpio_set_value(
217 bluedroid_pm->gpio_shutdown, 1);
218 if (gpio_is_valid(bluedroid_pm->gpio_reset))
219 bluedroid_pm_gpio_set_value(
220 bluedroid_pm->gpio_reset, 1);
221 if (bluedroid_pm->resume_min_frequency)
222 pm_qos_add_request(&bluedroid_pm->
223 resume_cpu_freq_req,
224 PM_QOS_CPU_FREQ_MIN,
225 PM_QOS_DEFAULT_VALUE);
226 }
227 bluedroid_pm->is_blocked = blocked;
228 mdelay(100);
229
230 return ret;
231}
232
233static const struct rfkill_ops bluedroid_pm_rfkill_ops = {
234 .set_block = bluedroid_pm_rfkill_set_power,
235};
236
237/*
238 * This API is added to set block state by ext driver,
239 * when bluedroid_pm rfkill is not used but host_wake functionality to be used.
240 * Eg: btwilink driver
241 */
242void bluedroid_pm_set_ext_state(bool blocked)
243{
244 bluedroid_pm_blocked = blocked;
245}
246EXPORT_SYMBOL(bluedroid_pm_set_ext_state);
247
248static int bluedroid_pm_probe(struct platform_device *pdev)
249{
250 static struct bluedroid_pm_data *bluedroid_pm;
251 struct bluedroid_pm_platform_data *pdata = pdev->dev.platform_data;
252 struct rfkill *rfkill;
253 struct resource *res;
254 int ret;
255 bool enable = false; /* off */
256 struct device_node *node;
257
258 bluedroid_pm = kzalloc(sizeof(*bluedroid_pm), GFP_KERNEL);
259 if (!bluedroid_pm)
260 return -ENOMEM;
261
262 bluedroid_pm->vdd_3v3 = regulator_get(&pdev->dev, "avdd");
263 if (IS_ERR(bluedroid_pm->vdd_3v3)) {
264 pr_warn("%s: regulator avdd not available\n", __func__);
265 bluedroid_pm->vdd_3v3 = NULL;
266 }
267 bluedroid_pm->vdd_1v8 = regulator_get(&pdev->dev, "dvdd");
268 if (IS_ERR(bluedroid_pm->vdd_1v8)) {
269 pr_warn("%s: regulator dvdd not available\n", __func__);
270 bluedroid_pm->vdd_1v8 = NULL;
271 }
272
273 if (pdev->dev.of_node) {
274 node = pdev->dev.of_node;
275
276 bluedroid_pm->gpio_reset =
277 of_get_named_gpio(node, "bluedroid_pm,reset-gpio", 0);
278 bluedroid_pm->gpio_shutdown =
279 of_get_named_gpio(node, "bluedroid_pm,shutdown-gpio", 0);
280 bluedroid_pm->host_wake =
281 of_get_named_gpio(node, "bluedroid_pm,host-wake-gpio", 0);
282 bluedroid_pm->host_wake_irq = platform_get_irq(pdev, 0);
283 bluedroid_pm->ext_wake =
284 of_get_named_gpio(node, "bluedroid_pm,ext-wake-gpio", 0);
285 } else {
286 res = platform_get_resource_byname(pdev, IORESOURCE_IO,
287 "reset_gpio");
288 if (res)
289 bluedroid_pm->gpio_reset = res->start;
290 else
291 bluedroid_pm->gpio_reset = -1;
292
293 res = platform_get_resource_byname(pdev, IORESOURCE_IO,
294 "shutdown_gpio");
295 if (res)
296 bluedroid_pm->gpio_shutdown = res->start;
297 else
298 bluedroid_pm->gpio_shutdown = -1;
299
300 res = platform_get_resource_byname(pdev, IORESOURCE_IO,
301 "gpio_host_wake");
302 if (res)
303 bluedroid_pm->host_wake = res->start;
304 else
305 bluedroid_pm->host_wake = -1;
306
307 res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
308 "host_wake");
309 if (res)
310 bluedroid_pm->host_wake_irq = res->start;
311 else
312 bluedroid_pm->host_wake_irq = -1;
313
314 res = platform_get_resource_byname(pdev, IORESOURCE_IO,
315 "gpio_ext_wake");
316 if (res)
317 bluedroid_pm->ext_wake = res->start;
318 else
319 bluedroid_pm->ext_wake = -1;
320 }
321
322 if (gpio_is_valid(bluedroid_pm->gpio_reset)) {
323 ret = gpio_request(bluedroid_pm->gpio_reset, "reset_gpio");
324 if (ret) {
325 BDP_ERR("Failed to get reset gpio\n");
326 goto free_res;
327 }
328 gpio_direction_output(bluedroid_pm->gpio_reset, enable);
329 } else
330 BDP_DBG("Reset gpio not registered.\n");
331
332 if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) {
333 ret = gpio_request(bluedroid_pm->gpio_shutdown,
334 "shutdown_gpio");
335 if (ret) {
336 BDP_ERR("Failed to get shutdown gpio\n");
337 goto free_res;
338 }
339 gpio_direction_output(bluedroid_pm->gpio_shutdown, enable);
340 } else
341 BDP_DBG("shutdown gpio not registered\n");
342
343 /*
344 * make sure at-least one of the GPIO or regulators avaiable to
345 * register with rfkill is defined
346 */
347 if ((gpio_is_valid(bluedroid_pm->gpio_reset)) ||
348 (gpio_is_valid(bluedroid_pm->gpio_shutdown)) ||
349 bluedroid_pm->vdd_1v8 || bluedroid_pm->vdd_3v3) {
350 rfkill = rfkill_alloc(pdev->name, &pdev->dev,
351 RFKILL_TYPE_BLUETOOTH, &bluedroid_pm_rfkill_ops,
352 bluedroid_pm);
353
354 if (unlikely(!rfkill))
355 goto free_res;
356
357 bluedroid_pm->is_blocked = !enable;
358 rfkill_set_states(rfkill, bluedroid_pm->is_blocked, false);
359
360 ret = rfkill_register(rfkill);
361
362 if (unlikely(ret)) {
363 rfkill_destroy(rfkill);
364 kfree(rfkill);
365 goto free_res;
366 }
367 bluedroid_pm->rfkill = rfkill;
368 }
369
370 if (gpio_is_valid(bluedroid_pm->host_wake)) {
371 ret = gpio_request(bluedroid_pm->host_wake, "bt_host_wake");
372 if (ret) {
373 BDP_ERR("Failed to get host_wake gpio\n");
374 goto free_res;
375 }
376 /* configure host_wake as input */
377 gpio_direction_input(bluedroid_pm->host_wake);
378 } else
379 BDP_DBG("gpio_host_wake not registered\n");
380
381 if (bluedroid_pm->host_wake_irq > -1) {
382 BDP_DBG("found host_wake irq\n");
383 ret = request_irq(bluedroid_pm->host_wake_irq,
384 bluedroid_pm_hostwake_isr,
385 IRQF_DISABLED | IRQF_TRIGGER_RISING,
386 "bluetooth hostwake", bluedroid_pm);
387 if (ret) {
388 BDP_ERR("Failed to get host_wake irq\n");
389 goto free_res;
390 }
391 } else
392 BDP_DBG("host_wake not registered\n");
393
394 if (gpio_is_valid(bluedroid_pm->ext_wake)) {
395 ret = gpio_request(bluedroid_pm->ext_wake, "bt_ext_wake");
396 if (ret) {
397 BDP_ERR("Failed to get ext_wake gpio\n");
398 goto free_res;
399 }
400 /* configure ext_wake as output mode*/
401 gpio_direction_output(bluedroid_pm->ext_wake, 1);
402 if (create_bt_proc_interface(bluedroid_pm)) {
403 BDP_ERR("Failed to create proc interface");
404 goto free_res;
405 }
406 /* initialize wake lock */
407 wake_lock_init(&bluedroid_pm->wake_lock, WAKE_LOCK_SUSPEND,
408 "bluedroid_pm");
409 /* Initialize timer */
410 init_timer(&bluedroid_pm_timer);
411 bluedroid_pm_timer.function = bluedroid_pm_timer_expire;
412 bluedroid_pm_timer.data = (unsigned long)bluedroid_pm;
413 } else
414 BDP_DBG("gpio_ext_wake not registered\n");
415
416 /* Update resume_min_frequency, if pdata is passed from board files */
417 if (pdata)
418 bluedroid_pm->resume_min_frequency =
419 pdata->resume_min_frequency;
420
421 INIT_WORK(&bluedroid_pm->work, bluedroid_work);
422 spin_lock_init(&bluedroid_pm->lock);
423
424 bluedroid_pm->pdev = pdev;
425 platform_set_drvdata(pdev, bluedroid_pm);
426 BDP_DBG("driver successfully registered");
427 return 0;
428
429free_res:
430 if (bluedroid_pm->vdd_3v3)
431 regulator_put(bluedroid_pm->vdd_3v3);
432 if (bluedroid_pm->vdd_1v8)
433 regulator_put(bluedroid_pm->vdd_1v8);
434 if (gpio_is_valid(bluedroid_pm->gpio_shutdown))
435 gpio_free(bluedroid_pm->gpio_shutdown);
436 if (gpio_is_valid(bluedroid_pm->gpio_reset))
437 gpio_free(bluedroid_pm->gpio_reset);
438 if (gpio_is_valid(bluedroid_pm->ext_wake))
439 gpio_free(bluedroid_pm->ext_wake);
440 if (gpio_is_valid(bluedroid_pm->host_wake))
441 gpio_free(bluedroid_pm->host_wake);
442 if (bluedroid_pm->rfkill) {
443 rfkill_unregister(bluedroid_pm->rfkill);
444 rfkill_destroy(bluedroid_pm->rfkill);
445 kfree(bluedroid_pm->rfkill);
446 }
447 kfree(bluedroid_pm);
448 return -ENODEV;
449}
450
451static int bluedroid_pm_remove(struct platform_device *pdev)
452{
453 struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev);
454
455 cancel_work_sync(&bluedroid_pm->work);
456
457 if (bluedroid_pm->host_wake)
458 gpio_free(bluedroid_pm->host_wake);
459 if (bluedroid_pm->host_wake_irq)
460 free_irq(bluedroid_pm->host_wake_irq, NULL);
461 if (bluedroid_pm->ext_wake) {
462 wake_lock_destroy(&bluedroid_pm->wake_lock);
463 gpio_free(bluedroid_pm->ext_wake);
464 remove_bt_proc_interface();
465 del_timer(&bluedroid_pm_timer);
466 }
467 if (bluedroid_pm->gpio_reset || bluedroid_pm->gpio_shutdown ||
468 bluedroid_pm->vdd_1v8 || bluedroid_pm->vdd_3v3) {
469 rfkill_unregister(bluedroid_pm->rfkill);
470 rfkill_destroy(bluedroid_pm->rfkill);
471 kfree(bluedroid_pm->rfkill);
472 }
473 if (bluedroid_pm->gpio_shutdown)
474 gpio_free(bluedroid_pm->gpio_shutdown);
475 if (bluedroid_pm->gpio_reset)
476 gpio_free(bluedroid_pm->gpio_reset);
477 if (bluedroid_pm->vdd_3v3)
478 regulator_put(bluedroid_pm->vdd_3v3);
479 if (bluedroid_pm->vdd_1v8)
480 regulator_put(bluedroid_pm->vdd_1v8);
481 kfree(bluedroid_pm);
482
483 return 0;
484}
485
486static int bluedroid_pm_suspend(struct platform_device *pdev,
487 pm_message_t state)
488{
489 struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev);
490 unsigned long flags;
491
492 if (bluedroid_pm->host_wake)
493 if (!bluedroid_pm->is_blocked || !bluedroid_pm_blocked)
494 enable_irq_wake(bluedroid_pm->host_wake_irq);
495
496 spin_lock_irqsave(&bluedroid_pm->lock, flags);
497 bluedroid_pm->resumed = false;
498 spin_unlock_irqrestore(&bluedroid_pm->lock, flags);
499 cancel_work_sync(&bluedroid_pm->work);
500
501 return 0;
502}
503
504static int bluedroid_pm_resume(struct platform_device *pdev)
505{
506 struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev);
507 unsigned long flags;
508
509 if (bluedroid_pm->host_wake)
510 if (!bluedroid_pm->is_blocked || !bluedroid_pm_blocked)
511 disable_irq_wake(bluedroid_pm->host_wake_irq);
512
513 spin_lock_irqsave(&bluedroid_pm->lock, flags);
514 bluedroid_pm->resumed = true;
515 schedule_work(&bluedroid_pm->work);
516 spin_unlock_irqrestore(&bluedroid_pm->lock, flags);
517
518 return 0;
519}
520
521static void bluedroid_pm_shutdown(struct platform_device *pdev)
522{
523 struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev);
524
525 cancel_work_sync(&bluedroid_pm->work);
526
527 if (gpio_is_valid(bluedroid_pm->gpio_shutdown))
528 bluedroid_pm_gpio_set_value(
529 bluedroid_pm->gpio_shutdown, 0);
530 if (gpio_is_valid(bluedroid_pm->gpio_reset))
531 bluedroid_pm_gpio_set_value(
532 bluedroid_pm->gpio_reset, 0);
533 if (bluedroid_pm->vdd_3v3)
534 regulator_disable(bluedroid_pm->vdd_3v3);
535 if (bluedroid_pm->vdd_1v8)
536 regulator_disable(bluedroid_pm->vdd_1v8);
537
538}
539
540static struct of_device_id bdroid_of_match[] = {
541 { .compatible = "nvidia,tegra-bluedroid_pm", },
542 { },
543};
544MODULE_DEVICE_TABLE(of, bdroid_of_match);
545
546static struct platform_driver bluedroid_pm_driver = {
547 .probe = bluedroid_pm_probe,
548 .remove = bluedroid_pm_remove,
549 .suspend = bluedroid_pm_suspend,
550 .resume = bluedroid_pm_resume,
551 .shutdown = bluedroid_pm_shutdown,
552 .driver = {
553 .name = "bluedroid_pm",
554 .of_match_table = of_match_ptr(bdroid_of_match),
555 .owner = THIS_MODULE,
556 },
557};
558
559static ssize_t lpm_read_proc(struct file *file, char __user *buf, size_t size,
560 loff_t *ppos)
561{
562 char msg[50];
563 struct bluedroid_pm_data *bluedroid_pm = PDE_DATA(file_inode(file));
564
565 sprintf(msg, "BT LPM Status: TX %x, RX %x\n",
566 bluedroid_pm_gpio_get_value(bluedroid_pm->ext_wake),
567 bluedroid_pm_gpio_get_value(bluedroid_pm->host_wake));
568 return simple_read_from_buffer(buf, size, ppos, msg, strlen(msg));
569}
570
571static ssize_t lpm_write_proc(struct file *file, const char __user *buffer,
572 size_t count, loff_t *ppos)
573{
574 char *buf;
575 struct bluedroid_pm_data *bluedroid_pm = PDE_DATA(file_inode(file));
576
577 if (count < 1)
578 return -EINVAL;
579
580 buf = kmalloc(count, GFP_KERNEL);
581 if (!buf)
582 return -ENOMEM;
583
584 if (copy_from_user(buf, buffer, count)) {
585 kfree(buf);
586 return -EFAULT;
587 }
588
589 if (!bluedroid_pm->is_blocked) {
590 if (buf[0] == '0') {
591 if (!bluedroid_pm_gpio_get_value(
592 bluedroid_pm->host_wake)) {
593 /* BT can sleep */
594 BDP_DBG("Tx and Rx are idle, BT sleeping");
595 bluedroid_pm_gpio_set_value(
596 bluedroid_pm->ext_wake, 0);
597 wake_unlock(&bluedroid_pm->wake_lock);
598 } else {
599 /* Reset Timer */
600 BDP_DBG("Rx is busy, restarting the timer");
601 mod_timer(&bluedroid_pm_timer,
602 jiffies + (TX_TIMER_INTERVAL * HZ));
603 }
604 clear_bit(BT_WAKE, &bluedroid_pm->flags);
605 } else if (buf[0] == '1') {
606 BDP_DBG("Tx is busy, wake_lock taken, delete timer");
607 bluedroid_pm_gpio_set_value(
608 bluedroid_pm->ext_wake, 1);
609 wake_lock(&bluedroid_pm->wake_lock);
610 del_timer(&bluedroid_pm_timer);
611 set_bit(BT_WAKE, &bluedroid_pm->flags);
612 } else {
613 kfree(buf);
614 return -EINVAL;
615 }
616 }
617
618 kfree(buf);
619 return count;
620}
621
622static const struct file_operations lpm_fops = {
623 .read = lpm_read_proc,
624 .write = lpm_write_proc,
625 .llseek = default_llseek,
626};
627
628static void remove_bt_proc_interface(void)
629{
630 remove_proc_entry("lpm", bluetooth_sleep_dir);
631 remove_proc_entry("sleep", proc_bt_dir);
632 remove_proc_entry("bluetooth", 0);
633}
634
635static int create_bt_proc_interface(void *drv_data)
636{
637 int retval;
638 struct proc_dir_entry *ent;
639
640 proc_bt_dir = proc_mkdir("bluetooth", NULL);
641 if (proc_bt_dir == NULL) {
642 BDP_ERR("Unable to create /proc/bluetooth directory");
643 return -ENOMEM;
644 }
645
646 bluetooth_sleep_dir = proc_mkdir("sleep", proc_bt_dir);
647 if (proc_bt_dir == NULL) {
648 BDP_ERR("Unable to create /proc/bluetooth directory");
649 return -ENOMEM;
650 }
651
652 /* Creating read/write "btwake" entry */
653 ent = proc_create_data("lpm", 0622, bluetooth_sleep_dir, &lpm_fops, drv_data);
654 if (ent == NULL) {
655 BDP_ERR("Unable to create /proc/%s/btwake entry", PROC_DIR);
656 retval = -ENOMEM;
657 goto fail;
658 }
659 return 0;
660fail:
661 remove_proc_entry("lpm", bluetooth_sleep_dir);
662 remove_proc_entry("sleep", proc_bt_dir);
663 remove_proc_entry("bluetooth", 0);
664 return retval;
665}
666
667static int __init bluedroid_pm_init(void)
668{
669 return platform_driver_register(&bluedroid_pm_driver);
670}
671
672static void __exit bluedroid_pm_exit(void)
673{
674 platform_driver_unregister(&bluedroid_pm_driver);
675}
676
677module_init(bluedroid_pm_init);
678module_exit(bluedroid_pm_exit);
679
680MODULE_DESCRIPTION("bluedroid PM");
681MODULE_AUTHOR("NVIDIA");
682MODULE_LICENSE("GPL");