1 | // SPDX-License-Identifier: GPL-2.0-only |
---|---|
2 | /* |
3 | * Toshiba Bluetooth Enable Driver |
4 | * |
5 | * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com> |
6 | * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.com> |
7 | * |
8 | * Thanks to Matthew Garrett for background info on ACPI innards which |
9 | * normal people aren't meant to understand :-) |
10 | */ |
11 | |
12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
13 | |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/init.h> |
17 | #include <linux/types.h> |
18 | #include <linux/acpi.h> |
19 | #include <linux/rfkill.h> |
20 | |
21 | #define BT_KILLSWITCH_MASK 0x01 |
22 | #define BT_PLUGGED_MASK 0x40 |
23 | #define BT_POWER_MASK 0x80 |
24 | |
25 | MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>"); |
26 | MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); |
27 | MODULE_LICENSE("GPL"); |
28 | |
29 | struct toshiba_bluetooth_dev { |
30 | struct acpi_device *acpi_dev; |
31 | struct rfkill *rfk; |
32 | |
33 | bool killswitch; |
34 | bool plugged; |
35 | bool powered; |
36 | }; |
37 | |
38 | static int toshiba_bt_rfkill_add(struct acpi_device *device); |
39 | static void toshiba_bt_rfkill_remove(struct acpi_device *device); |
40 | static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event); |
41 | |
42 | static const struct acpi_device_id bt_device_ids[] = { |
43 | { "TOS6205", 0}, |
44 | { "", 0}, |
45 | }; |
46 | MODULE_DEVICE_TABLE(acpi, bt_device_ids); |
47 | |
48 | #ifdef CONFIG_PM_SLEEP |
49 | static int toshiba_bt_resume(struct device *dev); |
50 | #endif |
51 | static SIMPLE_DEV_PM_OPS(toshiba_bt_pm, NULL, toshiba_bt_resume); |
52 | |
53 | static struct acpi_driver toshiba_bt_rfkill_driver = { |
54 | .name = "Toshiba BT", |
55 | .class = "Toshiba", |
56 | .ids = bt_device_ids, |
57 | .ops = { |
58 | .add = toshiba_bt_rfkill_add, |
59 | .remove = toshiba_bt_rfkill_remove, |
60 | .notify = toshiba_bt_rfkill_notify, |
61 | }, |
62 | .drv.pm = &toshiba_bt_pm, |
63 | }; |
64 | |
65 | static int toshiba_bluetooth_present(acpi_handle handle) |
66 | { |
67 | acpi_status result; |
68 | u64 bt_present; |
69 | |
70 | /* |
71 | * Some Toshiba laptops may have a fake TOS6205 device in |
72 | * their ACPI BIOS, so query the _STA method to see if there |
73 | * is really anything there. |
74 | */ |
75 | result = acpi_evaluate_integer(handle, pathname: "_STA", NULL, data: &bt_present); |
76 | if (ACPI_FAILURE(result)) { |
77 | pr_err("ACPI call to query Bluetooth presence failed\n"); |
78 | return -ENXIO; |
79 | } |
80 | |
81 | if (!bt_present) { |
82 | pr_info("Bluetooth device not present\n"); |
83 | return -ENODEV; |
84 | } |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int toshiba_bluetooth_status(acpi_handle handle) |
90 | { |
91 | acpi_status result; |
92 | u64 status; |
93 | |
94 | result = acpi_evaluate_integer(handle, pathname: "BTST", NULL, data: &status); |
95 | if (ACPI_FAILURE(result)) { |
96 | pr_err("Could not get Bluetooth device status\n"); |
97 | return -ENXIO; |
98 | } |
99 | |
100 | return status; |
101 | } |
102 | |
103 | static int toshiba_bluetooth_enable(acpi_handle handle) |
104 | { |
105 | acpi_status result; |
106 | |
107 | result = acpi_evaluate_object(object: handle, pathname: "AUSB", NULL, NULL); |
108 | if (ACPI_FAILURE(result)) { |
109 | pr_err("Could not attach USB Bluetooth device\n"); |
110 | return -ENXIO; |
111 | } |
112 | |
113 | result = acpi_evaluate_object(object: handle, pathname: "BTPO", NULL, NULL); |
114 | if (ACPI_FAILURE(result)) { |
115 | pr_err("Could not power ON Bluetooth device\n"); |
116 | return -ENXIO; |
117 | } |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static int toshiba_bluetooth_disable(acpi_handle handle) |
123 | { |
124 | acpi_status result; |
125 | |
126 | result = acpi_evaluate_object(object: handle, pathname: "BTPF", NULL, NULL); |
127 | if (ACPI_FAILURE(result)) { |
128 | pr_err("Could not power OFF Bluetooth device\n"); |
129 | return -ENXIO; |
130 | } |
131 | |
132 | result = acpi_evaluate_object(object: handle, pathname: "DUSB", NULL, NULL); |
133 | if (ACPI_FAILURE(result)) { |
134 | pr_err("Could not detach USB Bluetooth device\n"); |
135 | return -ENXIO; |
136 | } |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | /* Helper function */ |
142 | static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev) |
143 | { |
144 | int status; |
145 | |
146 | status = toshiba_bluetooth_status(handle: bt_dev->acpi_dev->handle); |
147 | if (status < 0) { |
148 | pr_err("Could not sync bluetooth device status\n"); |
149 | return status; |
150 | } |
151 | |
152 | bt_dev->killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; |
153 | bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false; |
154 | bt_dev->powered = (status & BT_POWER_MASK) ? true : false; |
155 | |
156 | pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n", |
157 | status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered); |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | /* RFKill handlers */ |
163 | static int bt_rfkill_set_block(void *data, bool blocked) |
164 | { |
165 | struct toshiba_bluetooth_dev *bt_dev = data; |
166 | int ret; |
167 | |
168 | ret = toshiba_bluetooth_sync_status(bt_dev); |
169 | if (ret) |
170 | return ret; |
171 | |
172 | if (!bt_dev->killswitch) |
173 | return 0; |
174 | |
175 | if (blocked) |
176 | ret = toshiba_bluetooth_disable(handle: bt_dev->acpi_dev->handle); |
177 | else |
178 | ret = toshiba_bluetooth_enable(handle: bt_dev->acpi_dev->handle); |
179 | |
180 | return ret; |
181 | } |
182 | |
183 | static void bt_rfkill_poll(struct rfkill *rfkill, void *data) |
184 | { |
185 | struct toshiba_bluetooth_dev *bt_dev = data; |
186 | |
187 | if (toshiba_bluetooth_sync_status(bt_dev)) |
188 | return; |
189 | |
190 | /* |
191 | * Note the Toshiba Bluetooth RFKill switch seems to be a strange |
192 | * fish. It only provides a BT event when the switch is flipped to |
193 | * the 'on' position. When flipping it to 'off', the USB device is |
194 | * simply pulled away underneath us, without any BT event being |
195 | * delivered. |
196 | */ |
197 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
198 | } |
199 | |
200 | static const struct rfkill_ops rfk_ops = { |
201 | .set_block = bt_rfkill_set_block, |
202 | .poll = bt_rfkill_poll, |
203 | }; |
204 | |
205 | /* ACPI driver functions */ |
206 | static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) |
207 | { |
208 | struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(d: device); |
209 | |
210 | if (toshiba_bluetooth_sync_status(bt_dev)) |
211 | return; |
212 | |
213 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
214 | } |
215 | |
216 | #ifdef CONFIG_PM_SLEEP |
217 | static int toshiba_bt_resume(struct device *dev) |
218 | { |
219 | struct toshiba_bluetooth_dev *bt_dev; |
220 | int ret; |
221 | |
222 | bt_dev = acpi_driver_data(to_acpi_device(dev)); |
223 | |
224 | ret = toshiba_bluetooth_sync_status(bt_dev); |
225 | if (ret) |
226 | return ret; |
227 | |
228 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
229 | |
230 | return 0; |
231 | } |
232 | #endif |
233 | |
234 | static int toshiba_bt_rfkill_add(struct acpi_device *device) |
235 | { |
236 | struct toshiba_bluetooth_dev *bt_dev; |
237 | int result; |
238 | |
239 | result = toshiba_bluetooth_present(handle: device->handle); |
240 | if (result) |
241 | return result; |
242 | |
243 | pr_info("Toshiba ACPI Bluetooth device driver\n"); |
244 | |
245 | bt_dev = kzalloc(sizeof(*bt_dev), GFP_KERNEL); |
246 | if (!bt_dev) |
247 | return -ENOMEM; |
248 | bt_dev->acpi_dev = device; |
249 | device->driver_data = bt_dev; |
250 | dev_set_drvdata(dev: &device->dev, data: bt_dev); |
251 | |
252 | result = toshiba_bluetooth_sync_status(bt_dev); |
253 | if (result) { |
254 | kfree(objp: bt_dev); |
255 | return result; |
256 | } |
257 | |
258 | bt_dev->rfk = rfkill_alloc(name: "Toshiba Bluetooth", |
259 | parent: &device->dev, |
260 | type: RFKILL_TYPE_BLUETOOTH, |
261 | ops: &rfk_ops, |
262 | ops_data: bt_dev); |
263 | if (!bt_dev->rfk) { |
264 | pr_err("Unable to allocate rfkill device\n"); |
265 | kfree(objp: bt_dev); |
266 | return -ENOMEM; |
267 | } |
268 | |
269 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
270 | |
271 | result = rfkill_register(rfkill: bt_dev->rfk); |
272 | if (result) { |
273 | pr_err("Unable to register rfkill device\n"); |
274 | rfkill_destroy(rfkill: bt_dev->rfk); |
275 | kfree(objp: bt_dev); |
276 | } |
277 | |
278 | return result; |
279 | } |
280 | |
281 | static void toshiba_bt_rfkill_remove(struct acpi_device *device) |
282 | { |
283 | struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(d: device); |
284 | |
285 | /* clean up */ |
286 | if (bt_dev->rfk) { |
287 | rfkill_unregister(rfkill: bt_dev->rfk); |
288 | rfkill_destroy(rfkill: bt_dev->rfk); |
289 | } |
290 | |
291 | kfree(objp: bt_dev); |
292 | |
293 | toshiba_bluetooth_disable(handle: device->handle); |
294 | } |
295 | |
296 | module_acpi_driver(toshiba_bt_rfkill_driver); |
297 |
Definitions
- toshiba_bluetooth_dev
- bt_device_ids
- toshiba_bt_pm
- toshiba_bt_rfkill_driver
- toshiba_bluetooth_present
- toshiba_bluetooth_status
- toshiba_bluetooth_enable
- toshiba_bluetooth_disable
- toshiba_bluetooth_sync_status
- bt_rfkill_set_block
- bt_rfkill_poll
- rfk_ops
- toshiba_bt_rfkill_notify
- toshiba_bt_resume
- toshiba_bt_rfkill_add
Improve your Profiling and Debugging skills
Find out more