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 | .owner = THIS_MODULE, |
63 | .drv.pm = &toshiba_bt_pm, |
64 | }; |
65 | |
66 | static int toshiba_bluetooth_present(acpi_handle handle) |
67 | { |
68 | acpi_status result; |
69 | u64 bt_present; |
70 | |
71 | /* |
72 | * Some Toshiba laptops may have a fake TOS6205 device in |
73 | * their ACPI BIOS, so query the _STA method to see if there |
74 | * is really anything there. |
75 | */ |
76 | result = acpi_evaluate_integer(handle, pathname: "_STA" , NULL, data: &bt_present); |
77 | if (ACPI_FAILURE(result)) { |
78 | pr_err("ACPI call to query Bluetooth presence failed\n" ); |
79 | return -ENXIO; |
80 | } |
81 | |
82 | if (!bt_present) { |
83 | pr_info("Bluetooth device not present\n" ); |
84 | return -ENODEV; |
85 | } |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static int toshiba_bluetooth_status(acpi_handle handle) |
91 | { |
92 | acpi_status result; |
93 | u64 status; |
94 | |
95 | result = acpi_evaluate_integer(handle, pathname: "BTST" , NULL, data: &status); |
96 | if (ACPI_FAILURE(result)) { |
97 | pr_err("Could not get Bluetooth device status\n" ); |
98 | return -ENXIO; |
99 | } |
100 | |
101 | return status; |
102 | } |
103 | |
104 | static int toshiba_bluetooth_enable(acpi_handle handle) |
105 | { |
106 | acpi_status result; |
107 | |
108 | result = acpi_evaluate_object(object: handle, pathname: "AUSB" , NULL, NULL); |
109 | if (ACPI_FAILURE(result)) { |
110 | pr_err("Could not attach USB Bluetooth device\n" ); |
111 | return -ENXIO; |
112 | } |
113 | |
114 | result = acpi_evaluate_object(object: handle, pathname: "BTPO" , NULL, NULL); |
115 | if (ACPI_FAILURE(result)) { |
116 | pr_err("Could not power ON Bluetooth device\n" ); |
117 | return -ENXIO; |
118 | } |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static int toshiba_bluetooth_disable(acpi_handle handle) |
124 | { |
125 | acpi_status result; |
126 | |
127 | result = acpi_evaluate_object(object: handle, pathname: "BTPF" , NULL, NULL); |
128 | if (ACPI_FAILURE(result)) { |
129 | pr_err("Could not power OFF Bluetooth device\n" ); |
130 | return -ENXIO; |
131 | } |
132 | |
133 | result = acpi_evaluate_object(object: handle, pathname: "DUSB" , NULL, NULL); |
134 | if (ACPI_FAILURE(result)) { |
135 | pr_err("Could not detach USB Bluetooth device\n" ); |
136 | return -ENXIO; |
137 | } |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | /* Helper function */ |
143 | static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev) |
144 | { |
145 | int status; |
146 | |
147 | status = toshiba_bluetooth_status(handle: bt_dev->acpi_dev->handle); |
148 | if (status < 0) { |
149 | pr_err("Could not sync bluetooth device status\n" ); |
150 | return status; |
151 | } |
152 | |
153 | bt_dev->killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; |
154 | bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false; |
155 | bt_dev->powered = (status & BT_POWER_MASK) ? true : false; |
156 | |
157 | pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n" , |
158 | status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered); |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | /* RFKill handlers */ |
164 | static int bt_rfkill_set_block(void *data, bool blocked) |
165 | { |
166 | struct toshiba_bluetooth_dev *bt_dev = data; |
167 | int ret; |
168 | |
169 | ret = toshiba_bluetooth_sync_status(bt_dev); |
170 | if (ret) |
171 | return ret; |
172 | |
173 | if (!bt_dev->killswitch) |
174 | return 0; |
175 | |
176 | if (blocked) |
177 | ret = toshiba_bluetooth_disable(handle: bt_dev->acpi_dev->handle); |
178 | else |
179 | ret = toshiba_bluetooth_enable(handle: bt_dev->acpi_dev->handle); |
180 | |
181 | return ret; |
182 | } |
183 | |
184 | static void bt_rfkill_poll(struct rfkill *rfkill, void *data) |
185 | { |
186 | struct toshiba_bluetooth_dev *bt_dev = data; |
187 | |
188 | if (toshiba_bluetooth_sync_status(bt_dev)) |
189 | return; |
190 | |
191 | /* |
192 | * Note the Toshiba Bluetooth RFKill switch seems to be a strange |
193 | * fish. It only provides a BT event when the switch is flipped to |
194 | * the 'on' position. When flipping it to 'off', the USB device is |
195 | * simply pulled away underneath us, without any BT event being |
196 | * delivered. |
197 | */ |
198 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
199 | } |
200 | |
201 | static const struct rfkill_ops rfk_ops = { |
202 | .set_block = bt_rfkill_set_block, |
203 | .poll = bt_rfkill_poll, |
204 | }; |
205 | |
206 | /* ACPI driver functions */ |
207 | static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) |
208 | { |
209 | struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(d: device); |
210 | |
211 | if (toshiba_bluetooth_sync_status(bt_dev)) |
212 | return; |
213 | |
214 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
215 | } |
216 | |
217 | #ifdef CONFIG_PM_SLEEP |
218 | static int toshiba_bt_resume(struct device *dev) |
219 | { |
220 | struct toshiba_bluetooth_dev *bt_dev; |
221 | int ret; |
222 | |
223 | bt_dev = acpi_driver_data(to_acpi_device(dev)); |
224 | |
225 | ret = toshiba_bluetooth_sync_status(bt_dev); |
226 | if (ret) |
227 | return ret; |
228 | |
229 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
230 | |
231 | return 0; |
232 | } |
233 | #endif |
234 | |
235 | static int toshiba_bt_rfkill_add(struct acpi_device *device) |
236 | { |
237 | struct toshiba_bluetooth_dev *bt_dev; |
238 | int result; |
239 | |
240 | result = toshiba_bluetooth_present(handle: device->handle); |
241 | if (result) |
242 | return result; |
243 | |
244 | pr_info("Toshiba ACPI Bluetooth device driver\n" ); |
245 | |
246 | bt_dev = kzalloc(size: sizeof(*bt_dev), GFP_KERNEL); |
247 | if (!bt_dev) |
248 | return -ENOMEM; |
249 | bt_dev->acpi_dev = device; |
250 | device->driver_data = bt_dev; |
251 | dev_set_drvdata(dev: &device->dev, data: bt_dev); |
252 | |
253 | result = toshiba_bluetooth_sync_status(bt_dev); |
254 | if (result) { |
255 | kfree(objp: bt_dev); |
256 | return result; |
257 | } |
258 | |
259 | bt_dev->rfk = rfkill_alloc(name: "Toshiba Bluetooth" , |
260 | parent: &device->dev, |
261 | type: RFKILL_TYPE_BLUETOOTH, |
262 | ops: &rfk_ops, |
263 | ops_data: bt_dev); |
264 | if (!bt_dev->rfk) { |
265 | pr_err("Unable to allocate rfkill device\n" ); |
266 | kfree(objp: bt_dev); |
267 | return -ENOMEM; |
268 | } |
269 | |
270 | rfkill_set_hw_state(rfkill: bt_dev->rfk, blocked: !bt_dev->killswitch); |
271 | |
272 | result = rfkill_register(rfkill: bt_dev->rfk); |
273 | if (result) { |
274 | pr_err("Unable to register rfkill device\n" ); |
275 | rfkill_destroy(rfkill: bt_dev->rfk); |
276 | kfree(objp: bt_dev); |
277 | } |
278 | |
279 | return result; |
280 | } |
281 | |
282 | static void toshiba_bt_rfkill_remove(struct acpi_device *device) |
283 | { |
284 | struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(d: device); |
285 | |
286 | /* clean up */ |
287 | if (bt_dev->rfk) { |
288 | rfkill_unregister(rfkill: bt_dev->rfk); |
289 | rfkill_destroy(rfkill: bt_dev->rfk); |
290 | } |
291 | |
292 | kfree(objp: bt_dev); |
293 | |
294 | toshiba_bluetooth_disable(handle: device->handle); |
295 | } |
296 | |
297 | module_acpi_driver(toshiba_bt_rfkill_driver); |
298 | |