18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci Dell Airplane Mode Switch driver 48c2ecf20Sopenharmony_ci Copyright (C) 2014-2015 Pali Rohár <pali@kernel.org> 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci*/ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/acpi.h> 108c2ecf20Sopenharmony_ci#include <linux/rfkill.h> 118c2ecf20Sopenharmony_ci#include <linux/input.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include "dell-rbtn.h" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cienum rbtn_type { 168c2ecf20Sopenharmony_ci RBTN_UNKNOWN, 178c2ecf20Sopenharmony_ci RBTN_TOGGLE, 188c2ecf20Sopenharmony_ci RBTN_SLIDER, 198c2ecf20Sopenharmony_ci}; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct rbtn_data { 228c2ecf20Sopenharmony_ci enum rbtn_type type; 238c2ecf20Sopenharmony_ci struct rfkill *rfkill; 248c2ecf20Sopenharmony_ci struct input_dev *input_dev; 258c2ecf20Sopenharmony_ci bool suspended; 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* 308c2ecf20Sopenharmony_ci * acpi functions 318c2ecf20Sopenharmony_ci */ 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic enum rbtn_type rbtn_check(struct acpi_device *device) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci unsigned long long output; 368c2ecf20Sopenharmony_ci acpi_status status; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); 398c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 408c2ecf20Sopenharmony_ci return RBTN_UNKNOWN; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci switch (output) { 438c2ecf20Sopenharmony_ci case 0: 448c2ecf20Sopenharmony_ci case 1: 458c2ecf20Sopenharmony_ci return RBTN_TOGGLE; 468c2ecf20Sopenharmony_ci case 2: 478c2ecf20Sopenharmony_ci case 3: 488c2ecf20Sopenharmony_ci return RBTN_SLIDER; 498c2ecf20Sopenharmony_ci default: 508c2ecf20Sopenharmony_ci return RBTN_UNKNOWN; 518c2ecf20Sopenharmony_ci } 528c2ecf20Sopenharmony_ci} 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic int rbtn_get(struct acpi_device *device) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci unsigned long long output; 578c2ecf20Sopenharmony_ci acpi_status status; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); 608c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 618c2ecf20Sopenharmony_ci return -EINVAL; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci return !output; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic int rbtn_acquire(struct acpi_device *device, bool enable) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct acpi_object_list input; 698c2ecf20Sopenharmony_ci union acpi_object param; 708c2ecf20Sopenharmony_ci acpi_status status; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci param.type = ACPI_TYPE_INTEGER; 738c2ecf20Sopenharmony_ci param.integer.value = enable; 748c2ecf20Sopenharmony_ci input.count = 1; 758c2ecf20Sopenharmony_ci input.pointer = ¶m; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); 788c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 798c2ecf20Sopenharmony_ci return -EINVAL; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return 0; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci/* 868c2ecf20Sopenharmony_ci * rfkill device 878c2ecf20Sopenharmony_ci */ 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic void rbtn_rfkill_query(struct rfkill *rfkill, void *data) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct acpi_device *device = data; 928c2ecf20Sopenharmony_ci int state; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci state = rbtn_get(device); 958c2ecf20Sopenharmony_ci if (state < 0) 968c2ecf20Sopenharmony_ci return; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci rfkill_set_states(rfkill, state, state); 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic int rbtn_rfkill_set_block(void *data, bool blocked) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci /* NOTE: setting soft rfkill state is not supported */ 1048c2ecf20Sopenharmony_ci return -EINVAL; 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic const struct rfkill_ops rbtn_ops = { 1088c2ecf20Sopenharmony_ci .query = rbtn_rfkill_query, 1098c2ecf20Sopenharmony_ci .set_block = rbtn_rfkill_set_block, 1108c2ecf20Sopenharmony_ci}; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic int rbtn_rfkill_init(struct acpi_device *device) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 1158c2ecf20Sopenharmony_ci int ret; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (rbtn_data->rfkill) 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* 1218c2ecf20Sopenharmony_ci * NOTE: rbtn controls all radio devices, not only WLAN 1228c2ecf20Sopenharmony_ci * but rfkill interface does not support "ANY" type 1238c2ecf20Sopenharmony_ci * so "WLAN" type is used 1248c2ecf20Sopenharmony_ci */ 1258c2ecf20Sopenharmony_ci rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, 1268c2ecf20Sopenharmony_ci RFKILL_TYPE_WLAN, &rbtn_ops, device); 1278c2ecf20Sopenharmony_ci if (!rbtn_data->rfkill) 1288c2ecf20Sopenharmony_ci return -ENOMEM; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci ret = rfkill_register(rbtn_data->rfkill); 1318c2ecf20Sopenharmony_ci if (ret) { 1328c2ecf20Sopenharmony_ci rfkill_destroy(rbtn_data->rfkill); 1338c2ecf20Sopenharmony_ci rbtn_data->rfkill = NULL; 1348c2ecf20Sopenharmony_ci return ret; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci return 0; 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic void rbtn_rfkill_exit(struct acpi_device *device) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci if (!rbtn_data->rfkill) 1458c2ecf20Sopenharmony_ci return; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci rfkill_unregister(rbtn_data->rfkill); 1488c2ecf20Sopenharmony_ci rfkill_destroy(rbtn_data->rfkill); 1498c2ecf20Sopenharmony_ci rbtn_data->rfkill = NULL; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic void rbtn_rfkill_event(struct acpi_device *device) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci if (rbtn_data->rfkill) 1578c2ecf20Sopenharmony_ci rbtn_rfkill_query(rbtn_data->rfkill, device); 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci/* 1628c2ecf20Sopenharmony_ci * input device 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int rbtn_input_init(struct rbtn_data *rbtn_data) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci int ret; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci rbtn_data->input_dev = input_allocate_device(); 1708c2ecf20Sopenharmony_ci if (!rbtn_data->input_dev) 1718c2ecf20Sopenharmony_ci return -ENOMEM; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci rbtn_data->input_dev->name = "DELL Wireless hotkeys"; 1748c2ecf20Sopenharmony_ci rbtn_data->input_dev->phys = "dellabce/input0"; 1758c2ecf20Sopenharmony_ci rbtn_data->input_dev->id.bustype = BUS_HOST; 1768c2ecf20Sopenharmony_ci rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); 1778c2ecf20Sopenharmony_ci set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci ret = input_register_device(rbtn_data->input_dev); 1808c2ecf20Sopenharmony_ci if (ret) { 1818c2ecf20Sopenharmony_ci input_free_device(rbtn_data->input_dev); 1828c2ecf20Sopenharmony_ci rbtn_data->input_dev = NULL; 1838c2ecf20Sopenharmony_ci return ret; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci return 0; 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic void rbtn_input_exit(struct rbtn_data *rbtn_data) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci input_unregister_device(rbtn_data->input_dev); 1928c2ecf20Sopenharmony_ci rbtn_data->input_dev = NULL; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic void rbtn_input_event(struct rbtn_data *rbtn_data) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); 1988c2ecf20Sopenharmony_ci input_sync(rbtn_data->input_dev); 1998c2ecf20Sopenharmony_ci input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); 2008c2ecf20Sopenharmony_ci input_sync(rbtn_data->input_dev); 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci/* 2058c2ecf20Sopenharmony_ci * acpi driver 2068c2ecf20Sopenharmony_ci */ 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic int rbtn_add(struct acpi_device *device); 2098c2ecf20Sopenharmony_cistatic int rbtn_remove(struct acpi_device *device); 2108c2ecf20Sopenharmony_cistatic void rbtn_notify(struct acpi_device *device, u32 event); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic const struct acpi_device_id rbtn_ids[] = { 2138c2ecf20Sopenharmony_ci { "DELRBTN", 0 }, 2148c2ecf20Sopenharmony_ci { "DELLABCE", 0 }, 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci /* 2178c2ecf20Sopenharmony_ci * This driver can also handle the "DELLABC6" device that 2188c2ecf20Sopenharmony_ci * appears on the XPS 13 9350, but that device is disabled by 2198c2ecf20Sopenharmony_ci * the DSDT unless booted with acpi_osi="!Windows 2012" 2208c2ecf20Sopenharmony_ci * acpi_osi="!Windows 2013". 2218c2ecf20Sopenharmony_ci * 2228c2ecf20Sopenharmony_ci * According to Mario at Dell: 2238c2ecf20Sopenharmony_ci * 2248c2ecf20Sopenharmony_ci * DELLABC6 is a custom interface that was created solely to 2258c2ecf20Sopenharmony_ci * have airplane mode support for Windows 7. For Windows 10 2268c2ecf20Sopenharmony_ci * the proper interface is to use that which is handled by 2278c2ecf20Sopenharmony_ci * intel-hid. A OEM airplane mode driver is not used. 2288c2ecf20Sopenharmony_ci * 2298c2ecf20Sopenharmony_ci * Since the kernel doesn't identify as Windows 7 it would be 2308c2ecf20Sopenharmony_ci * incorrect to do attempt to use that interface. 2318c2ecf20Sopenharmony_ci * 2328c2ecf20Sopenharmony_ci * Even if we override _OSI and bind to DELLABC6, we end up with 2338c2ecf20Sopenharmony_ci * inconsistent behavior in which userspace can get out of sync 2348c2ecf20Sopenharmony_ci * with the rfkill state as it conflicts with events from 2358c2ecf20Sopenharmony_ci * intel-hid. 2368c2ecf20Sopenharmony_ci * 2378c2ecf20Sopenharmony_ci * The upshot is that it is better to just ignore DELLABC6 2388c2ecf20Sopenharmony_ci * devices. 2398c2ecf20Sopenharmony_ci */ 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci { "", 0 }, 2428c2ecf20Sopenharmony_ci}; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 2458c2ecf20Sopenharmony_cistatic void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) 2468c2ecf20Sopenharmony_ci{ 2478c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = context; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci rbtn_data->suspended = false; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic int rbtn_suspend(struct device *dev) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 2558c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = acpi_driver_data(device); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci rbtn_data->suspended = true; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci return 0; 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic int rbtn_resume(struct device *dev) 2638c2ecf20Sopenharmony_ci{ 2648c2ecf20Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 2658c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = acpi_driver_data(device); 2668c2ecf20Sopenharmony_ci acpi_status status; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci /* 2698c2ecf20Sopenharmony_ci * Upon resume, some BIOSes send an ACPI notification thet triggers 2708c2ecf20Sopenharmony_ci * an unwanted input event. In order to ignore it, we use a flag 2718c2ecf20Sopenharmony_ci * that we set at suspend and clear once we have received the extra 2728c2ecf20Sopenharmony_ci * ACPI notification. Since ACPI notifications are delivered 2738c2ecf20Sopenharmony_ci * asynchronously to drivers, we clear the flag from the workqueue 2748c2ecf20Sopenharmony_ci * used to deliver the notifications. This should be enough 2758c2ecf20Sopenharmony_ci * to have the flag cleared only after we received the extra 2768c2ecf20Sopenharmony_ci * notification, if any. 2778c2ecf20Sopenharmony_ci */ 2788c2ecf20Sopenharmony_ci status = acpi_os_execute(OSL_NOTIFY_HANDLER, 2798c2ecf20Sopenharmony_ci rbtn_clear_suspended_flag, rbtn_data); 2808c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 2818c2ecf20Sopenharmony_ci rbtn_clear_suspended_flag(rbtn_data); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci#endif 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic struct acpi_driver rbtn_driver = { 2908c2ecf20Sopenharmony_ci .name = "dell-rbtn", 2918c2ecf20Sopenharmony_ci .ids = rbtn_ids, 2928c2ecf20Sopenharmony_ci .drv.pm = &rbtn_pm_ops, 2938c2ecf20Sopenharmony_ci .ops = { 2948c2ecf20Sopenharmony_ci .add = rbtn_add, 2958c2ecf20Sopenharmony_ci .remove = rbtn_remove, 2968c2ecf20Sopenharmony_ci .notify = rbtn_notify, 2978c2ecf20Sopenharmony_ci }, 2988c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2998c2ecf20Sopenharmony_ci}; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci/* 3038c2ecf20Sopenharmony_ci * notifier export functions 3048c2ecf20Sopenharmony_ci */ 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic bool auto_remove_rfkill = true; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_cistatic ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic int rbtn_inc_count(struct device *dev, void *data) 3118c2ecf20Sopenharmony_ci{ 3128c2ecf20Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 3138c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 3148c2ecf20Sopenharmony_ci int *count = data; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci if (rbtn_data->type == RBTN_SLIDER) 3178c2ecf20Sopenharmony_ci (*count)++; 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci return 0; 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_cistatic int rbtn_switch_dev(struct device *dev, void *data) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 3258c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 3268c2ecf20Sopenharmony_ci bool enable = data; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci if (rbtn_data->type != RBTN_SLIDER) 3298c2ecf20Sopenharmony_ci return 0; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci if (enable) 3328c2ecf20Sopenharmony_ci rbtn_rfkill_init(device); 3338c2ecf20Sopenharmony_ci else 3348c2ecf20Sopenharmony_ci rbtn_rfkill_exit(device); 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci return 0; 3378c2ecf20Sopenharmony_ci} 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ciint dell_rbtn_notifier_register(struct notifier_block *nb) 3408c2ecf20Sopenharmony_ci{ 3418c2ecf20Sopenharmony_ci bool first; 3428c2ecf20Sopenharmony_ci int count; 3438c2ecf20Sopenharmony_ci int ret; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci count = 0; 3468c2ecf20Sopenharmony_ci ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, 3478c2ecf20Sopenharmony_ci rbtn_inc_count); 3488c2ecf20Sopenharmony_ci if (ret || count == 0) 3498c2ecf20Sopenharmony_ci return -ENODEV; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci first = !rbtn_chain_head.head; 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); 3548c2ecf20Sopenharmony_ci if (ret != 0) 3558c2ecf20Sopenharmony_ci return ret; 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci if (auto_remove_rfkill && first) 3588c2ecf20Sopenharmony_ci ret = driver_for_each_device(&rbtn_driver.drv, NULL, 3598c2ecf20Sopenharmony_ci (void *)false, rbtn_switch_dev); 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci return ret; 3628c2ecf20Sopenharmony_ci} 3638c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ciint dell_rbtn_notifier_unregister(struct notifier_block *nb) 3668c2ecf20Sopenharmony_ci{ 3678c2ecf20Sopenharmony_ci int ret; 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); 3708c2ecf20Sopenharmony_ci if (ret != 0) 3718c2ecf20Sopenharmony_ci return ret; 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci if (auto_remove_rfkill && !rbtn_chain_head.head) 3748c2ecf20Sopenharmony_ci ret = driver_for_each_device(&rbtn_driver.drv, NULL, 3758c2ecf20Sopenharmony_ci (void *)true, rbtn_switch_dev); 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci return ret; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci/* 3838c2ecf20Sopenharmony_ci * acpi driver functions 3848c2ecf20Sopenharmony_ci */ 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_cistatic int rbtn_add(struct acpi_device *device) 3878c2ecf20Sopenharmony_ci{ 3888c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data; 3898c2ecf20Sopenharmony_ci enum rbtn_type type; 3908c2ecf20Sopenharmony_ci int ret = 0; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci type = rbtn_check(device); 3938c2ecf20Sopenharmony_ci if (type == RBTN_UNKNOWN) { 3948c2ecf20Sopenharmony_ci dev_info(&device->dev, "Unknown device type\n"); 3958c2ecf20Sopenharmony_ci return -EINVAL; 3968c2ecf20Sopenharmony_ci } 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci ret = rbtn_acquire(device, true); 3998c2ecf20Sopenharmony_ci if (ret < 0) { 4008c2ecf20Sopenharmony_ci dev_err(&device->dev, "Cannot enable device\n"); 4018c2ecf20Sopenharmony_ci return ret; 4028c2ecf20Sopenharmony_ci } 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); 4058c2ecf20Sopenharmony_ci if (!rbtn_data) 4068c2ecf20Sopenharmony_ci return -ENOMEM; 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci rbtn_data->type = type; 4098c2ecf20Sopenharmony_ci device->driver_data = rbtn_data; 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci switch (rbtn_data->type) { 4128c2ecf20Sopenharmony_ci case RBTN_TOGGLE: 4138c2ecf20Sopenharmony_ci ret = rbtn_input_init(rbtn_data); 4148c2ecf20Sopenharmony_ci break; 4158c2ecf20Sopenharmony_ci case RBTN_SLIDER: 4168c2ecf20Sopenharmony_ci if (auto_remove_rfkill && rbtn_chain_head.head) 4178c2ecf20Sopenharmony_ci ret = 0; 4188c2ecf20Sopenharmony_ci else 4198c2ecf20Sopenharmony_ci ret = rbtn_rfkill_init(device); 4208c2ecf20Sopenharmony_ci break; 4218c2ecf20Sopenharmony_ci default: 4228c2ecf20Sopenharmony_ci ret = -EINVAL; 4238c2ecf20Sopenharmony_ci } 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci return ret; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci} 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_cistatic int rbtn_remove(struct acpi_device *device) 4308c2ecf20Sopenharmony_ci{ 4318c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci switch (rbtn_data->type) { 4348c2ecf20Sopenharmony_ci case RBTN_TOGGLE: 4358c2ecf20Sopenharmony_ci rbtn_input_exit(rbtn_data); 4368c2ecf20Sopenharmony_ci break; 4378c2ecf20Sopenharmony_ci case RBTN_SLIDER: 4388c2ecf20Sopenharmony_ci rbtn_rfkill_exit(device); 4398c2ecf20Sopenharmony_ci break; 4408c2ecf20Sopenharmony_ci default: 4418c2ecf20Sopenharmony_ci break; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci rbtn_acquire(device, false); 4458c2ecf20Sopenharmony_ci device->driver_data = NULL; 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci return 0; 4488c2ecf20Sopenharmony_ci} 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_cistatic void rbtn_notify(struct acpi_device *device, u32 event) 4518c2ecf20Sopenharmony_ci{ 4528c2ecf20Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci /* 4558c2ecf20Sopenharmony_ci * Some BIOSes send a notification at resume. 4568c2ecf20Sopenharmony_ci * Ignore it to prevent unwanted input events. 4578c2ecf20Sopenharmony_ci */ 4588c2ecf20Sopenharmony_ci if (rbtn_data->suspended) { 4598c2ecf20Sopenharmony_ci dev_dbg(&device->dev, "ACPI notification ignored\n"); 4608c2ecf20Sopenharmony_ci return; 4618c2ecf20Sopenharmony_ci } 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci if (event != 0x80) { 4648c2ecf20Sopenharmony_ci dev_info(&device->dev, "Received unknown event (0x%x)\n", 4658c2ecf20Sopenharmony_ci event); 4668c2ecf20Sopenharmony_ci return; 4678c2ecf20Sopenharmony_ci } 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci switch (rbtn_data->type) { 4708c2ecf20Sopenharmony_ci case RBTN_TOGGLE: 4718c2ecf20Sopenharmony_ci rbtn_input_event(rbtn_data); 4728c2ecf20Sopenharmony_ci break; 4738c2ecf20Sopenharmony_ci case RBTN_SLIDER: 4748c2ecf20Sopenharmony_ci rbtn_rfkill_event(device); 4758c2ecf20Sopenharmony_ci atomic_notifier_call_chain(&rbtn_chain_head, event, device); 4768c2ecf20Sopenharmony_ci break; 4778c2ecf20Sopenharmony_ci default: 4788c2ecf20Sopenharmony_ci break; 4798c2ecf20Sopenharmony_ci } 4808c2ecf20Sopenharmony_ci} 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci/* 4848c2ecf20Sopenharmony_ci * module functions 4858c2ecf20Sopenharmony_ci */ 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_cimodule_acpi_driver(rbtn_driver); 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_cimodule_param(auto_remove_rfkill, bool, 0444); 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ciMODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " 4928c2ecf20Sopenharmony_ci "other modules start receiving events " 4938c2ecf20Sopenharmony_ci "from this module and re-add them when " 4948c2ecf20Sopenharmony_ci "the last module stops receiving events " 4958c2ecf20Sopenharmony_ci "(default true)"); 4968c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, rbtn_ids); 4978c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); 4988c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); 4998c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 500