diff options
author | Gaurav Asati <gasati@nvidia.com> | 2016-05-08 13:17:45 -0400 |
---|---|---|
committer | mobile promotions <svcmobile_promotions@nvidia.com> | 2018-07-19 16:54:30 -0400 |
commit | a38c50d1ffef55dd73505e360672fab3095e2bbb (patch) | |
tree | 4dab2870da0f76f50d3f8a5fc67f873b6c47770e | |
parent | 0b4617dc859097866aa62be19cc59e69bc0d579b (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/Kconfig | 10 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/bluedroid_pm.c | 682 |
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" | |||
92 | source "drivers/misc/eventlib/Kconfig" | 92 | source "drivers/misc/eventlib/Kconfig" |
93 | endif | 93 | endif |
94 | 94 | ||
95 | config 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 | |||
95 | endmenu | 105 | endmenu |
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/ | |||
6 | obj-$(CONFIG_NVS_DFSH) += nvs-dfsh/ | 6 | obj-$(CONFIG_NVS_DFSH) += nvs-dfsh/ |
7 | obj-$(CONFIG_SAF775x_HWDEP) += saf775x/ | 7 | obj-$(CONFIG_SAF775x_HWDEP) += saf775x/ |
8 | obj-$(CONFIG_SAF36XX_HWDEP) += saf775x/ | 8 | obj-$(CONFIG_SAF36XX_HWDEP) += saf775x/ |
9 | obj-$(CONFIG_BLUEDROID_PM) += bluedroid_pm.o | ||
9 | obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o | 10 | obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o |
10 | obj-$(CONFIG_TEGRA_CPC) += tegra_cpc.o | 11 | obj-$(CONFIG_TEGRA_CPC) += tegra_cpc.o |
11 | obj-$(CONFIG_THERM_EST) += therm_est.o | 12 | obj-$(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 | |||
66 | struct 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 | |||
86 | struct proc_dir_entry *proc_bt_dir, *bluetooth_sleep_dir; | ||
87 | static bool bluedroid_pm_blocked = 1; | ||
88 | |||
89 | static int create_bt_proc_interface(void *drv_data); | ||
90 | static void remove_bt_proc_interface(void); | ||
91 | |||
92 | static DEFINE_MUTEX(bt_wlan_sync); | ||
93 | |||
94 | void bt_wlan_lock(void) | ||
95 | { | ||
96 | mutex_lock(&bt_wlan_sync); | ||
97 | } | ||
98 | EXPORT_SYMBOL(bt_wlan_lock); | ||
99 | |||
100 | void bt_wlan_unlock(void) | ||
101 | { | ||
102 | mutex_unlock(&bt_wlan_sync); | ||
103 | } | ||
104 | EXPORT_SYMBOL(bt_wlan_unlock); | ||
105 | |||
106 | /** bluedroid_m busy timer */ | ||
107 | static void bluedroid_pm_timer_expire(unsigned long data); | ||
108 | static DEFINE_TIMER(bluedroid_pm_timer, bluedroid_pm_timer_expire, 0, 0); | ||
109 | static int bluedroid_pm_gpio_get_value(unsigned int gpio); | ||
110 | static void bluedroid_pm_gpio_set_value(unsigned int gpio, int value); | ||
111 | |||
112 | static 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 | |||
137 | static 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 | |||
143 | static 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 | |||
151 | static 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 | */ | ||
163 | static 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 | |||
188 | static 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 | |||
233 | static 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 | */ | ||
242 | void bluedroid_pm_set_ext_state(bool blocked) | ||
243 | { | ||
244 | bluedroid_pm_blocked = blocked; | ||
245 | } | ||
246 | EXPORT_SYMBOL(bluedroid_pm_set_ext_state); | ||
247 | |||
248 | static 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 | |||
429 | free_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 | |||
451 | static 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 | |||
486 | static 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 | |||
504 | static 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 | |||
521 | static 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 | |||
540 | static struct of_device_id bdroid_of_match[] = { | ||
541 | { .compatible = "nvidia,tegra-bluedroid_pm", }, | ||
542 | { }, | ||
543 | }; | ||
544 | MODULE_DEVICE_TABLE(of, bdroid_of_match); | ||
545 | |||
546 | static 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 | |||
559 | static 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 | |||
571 | static 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 | |||
622 | static const struct file_operations lpm_fops = { | ||
623 | .read = lpm_read_proc, | ||
624 | .write = lpm_write_proc, | ||
625 | .llseek = default_llseek, | ||
626 | }; | ||
627 | |||
628 | static 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 | |||
635 | static 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; | ||
660 | fail: | ||
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 | |||
667 | static int __init bluedroid_pm_init(void) | ||
668 | { | ||
669 | return platform_driver_register(&bluedroid_pm_driver); | ||
670 | } | ||
671 | |||
672 | static void __exit bluedroid_pm_exit(void) | ||
673 | { | ||
674 | platform_driver_unregister(&bluedroid_pm_driver); | ||
675 | } | ||
676 | |||
677 | module_init(bluedroid_pm_init); | ||
678 | module_exit(bluedroid_pm_exit); | ||
679 | |||
680 | MODULE_DESCRIPTION("bluedroid PM"); | ||
681 | MODULE_AUTHOR("NVIDIA"); | ||
682 | MODULE_LICENSE("GPL"); | ||