162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci Dell Airplane Mode Switch driver 462306a36Sopenharmony_ci Copyright (C) 2014-2015 Pali Rohár <pali@kernel.org> 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci*/ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/acpi.h> 1062306a36Sopenharmony_ci#include <linux/rfkill.h> 1162306a36Sopenharmony_ci#include <linux/input.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "dell-rbtn.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cienum rbtn_type { 1662306a36Sopenharmony_ci RBTN_UNKNOWN, 1762306a36Sopenharmony_ci RBTN_TOGGLE, 1862306a36Sopenharmony_ci RBTN_SLIDER, 1962306a36Sopenharmony_ci}; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistruct rbtn_data { 2262306a36Sopenharmony_ci enum rbtn_type type; 2362306a36Sopenharmony_ci struct rfkill *rfkill; 2462306a36Sopenharmony_ci struct input_dev *input_dev; 2562306a36Sopenharmony_ci bool suspended; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 3062306a36Sopenharmony_ci * acpi functions 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic enum rbtn_type rbtn_check(struct acpi_device *device) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci unsigned long long output; 3662306a36Sopenharmony_ci acpi_status status; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); 3962306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 4062306a36Sopenharmony_ci return RBTN_UNKNOWN; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci switch (output) { 4362306a36Sopenharmony_ci case 0: 4462306a36Sopenharmony_ci case 1: 4562306a36Sopenharmony_ci return RBTN_TOGGLE; 4662306a36Sopenharmony_ci case 2: 4762306a36Sopenharmony_ci case 3: 4862306a36Sopenharmony_ci return RBTN_SLIDER; 4962306a36Sopenharmony_ci default: 5062306a36Sopenharmony_ci return RBTN_UNKNOWN; 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int rbtn_get(struct acpi_device *device) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci unsigned long long output; 5762306a36Sopenharmony_ci acpi_status status; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); 6062306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 6162306a36Sopenharmony_ci return -EINVAL; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci return !output; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic int rbtn_acquire(struct acpi_device *device, bool enable) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct acpi_object_list input; 6962306a36Sopenharmony_ci union acpi_object param; 7062306a36Sopenharmony_ci acpi_status status; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci param.type = ACPI_TYPE_INTEGER; 7362306a36Sopenharmony_ci param.integer.value = enable; 7462306a36Sopenharmony_ci input.count = 1; 7562306a36Sopenharmony_ci input.pointer = ¶m; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); 7862306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 7962306a36Sopenharmony_ci return -EINVAL; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return 0; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci/* 8662306a36Sopenharmony_ci * rfkill device 8762306a36Sopenharmony_ci */ 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic void rbtn_rfkill_query(struct rfkill *rfkill, void *data) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct acpi_device *device = data; 9262306a36Sopenharmony_ci int state; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci state = rbtn_get(device); 9562306a36Sopenharmony_ci if (state < 0) 9662306a36Sopenharmony_ci return; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci rfkill_set_states(rfkill, state, state); 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic int rbtn_rfkill_set_block(void *data, bool blocked) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci /* NOTE: setting soft rfkill state is not supported */ 10462306a36Sopenharmony_ci return -EINVAL; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic const struct rfkill_ops rbtn_ops = { 10862306a36Sopenharmony_ci .query = rbtn_rfkill_query, 10962306a36Sopenharmony_ci .set_block = rbtn_rfkill_set_block, 11062306a36Sopenharmony_ci}; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic int rbtn_rfkill_init(struct acpi_device *device) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 11562306a36Sopenharmony_ci int ret; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (rbtn_data->rfkill) 11862306a36Sopenharmony_ci return 0; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* 12162306a36Sopenharmony_ci * NOTE: rbtn controls all radio devices, not only WLAN 12262306a36Sopenharmony_ci * but rfkill interface does not support "ANY" type 12362306a36Sopenharmony_ci * so "WLAN" type is used 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_ci rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, 12662306a36Sopenharmony_ci RFKILL_TYPE_WLAN, &rbtn_ops, device); 12762306a36Sopenharmony_ci if (!rbtn_data->rfkill) 12862306a36Sopenharmony_ci return -ENOMEM; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci ret = rfkill_register(rbtn_data->rfkill); 13162306a36Sopenharmony_ci if (ret) { 13262306a36Sopenharmony_ci rfkill_destroy(rbtn_data->rfkill); 13362306a36Sopenharmony_ci rbtn_data->rfkill = NULL; 13462306a36Sopenharmony_ci return ret; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void rbtn_rfkill_exit(struct acpi_device *device) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (!rbtn_data->rfkill) 14562306a36Sopenharmony_ci return; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci rfkill_unregister(rbtn_data->rfkill); 14862306a36Sopenharmony_ci rfkill_destroy(rbtn_data->rfkill); 14962306a36Sopenharmony_ci rbtn_data->rfkill = NULL; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic void rbtn_rfkill_event(struct acpi_device *device) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (rbtn_data->rfkill) 15762306a36Sopenharmony_ci rbtn_rfkill_query(rbtn_data->rfkill, device); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/* 16262306a36Sopenharmony_ci * input device 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic int rbtn_input_init(struct rbtn_data *rbtn_data) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci int ret; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci rbtn_data->input_dev = input_allocate_device(); 17062306a36Sopenharmony_ci if (!rbtn_data->input_dev) 17162306a36Sopenharmony_ci return -ENOMEM; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci rbtn_data->input_dev->name = "DELL Wireless hotkeys"; 17462306a36Sopenharmony_ci rbtn_data->input_dev->phys = "dellabce/input0"; 17562306a36Sopenharmony_ci rbtn_data->input_dev->id.bustype = BUS_HOST; 17662306a36Sopenharmony_ci rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); 17762306a36Sopenharmony_ci set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci ret = input_register_device(rbtn_data->input_dev); 18062306a36Sopenharmony_ci if (ret) { 18162306a36Sopenharmony_ci input_free_device(rbtn_data->input_dev); 18262306a36Sopenharmony_ci rbtn_data->input_dev = NULL; 18362306a36Sopenharmony_ci return ret; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic void rbtn_input_exit(struct rbtn_data *rbtn_data) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci input_unregister_device(rbtn_data->input_dev); 19262306a36Sopenharmony_ci rbtn_data->input_dev = NULL; 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic void rbtn_input_event(struct rbtn_data *rbtn_data) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); 19862306a36Sopenharmony_ci input_sync(rbtn_data->input_dev); 19962306a36Sopenharmony_ci input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); 20062306a36Sopenharmony_ci input_sync(rbtn_data->input_dev); 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci/* 20562306a36Sopenharmony_ci * acpi driver 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic int rbtn_add(struct acpi_device *device); 20962306a36Sopenharmony_cistatic void rbtn_remove(struct acpi_device *device); 21062306a36Sopenharmony_cistatic void rbtn_notify(struct acpi_device *device, u32 event); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic const struct acpi_device_id rbtn_ids[] = { 21362306a36Sopenharmony_ci { "DELRBTN", 0 }, 21462306a36Sopenharmony_ci { "DELLABCE", 0 }, 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci /* 21762306a36Sopenharmony_ci * This driver can also handle the "DELLABC6" device that 21862306a36Sopenharmony_ci * appears on the XPS 13 9350, but that device is disabled by 21962306a36Sopenharmony_ci * the DSDT unless booted with acpi_osi="!Windows 2012" 22062306a36Sopenharmony_ci * acpi_osi="!Windows 2013". 22162306a36Sopenharmony_ci * 22262306a36Sopenharmony_ci * According to Mario at Dell: 22362306a36Sopenharmony_ci * 22462306a36Sopenharmony_ci * DELLABC6 is a custom interface that was created solely to 22562306a36Sopenharmony_ci * have airplane mode support for Windows 7. For Windows 10 22662306a36Sopenharmony_ci * the proper interface is to use that which is handled by 22762306a36Sopenharmony_ci * intel-hid. A OEM airplane mode driver is not used. 22862306a36Sopenharmony_ci * 22962306a36Sopenharmony_ci * Since the kernel doesn't identify as Windows 7 it would be 23062306a36Sopenharmony_ci * incorrect to do attempt to use that interface. 23162306a36Sopenharmony_ci * 23262306a36Sopenharmony_ci * Even if we override _OSI and bind to DELLABC6, we end up with 23362306a36Sopenharmony_ci * inconsistent behavior in which userspace can get out of sync 23462306a36Sopenharmony_ci * with the rfkill state as it conflicts with events from 23562306a36Sopenharmony_ci * intel-hid. 23662306a36Sopenharmony_ci * 23762306a36Sopenharmony_ci * The upshot is that it is better to just ignore DELLABC6 23862306a36Sopenharmony_ci * devices. 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci { "", 0 }, 24262306a36Sopenharmony_ci}; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 24562306a36Sopenharmony_cistatic void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci struct rbtn_data *rbtn_data = context; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci rbtn_data->suspended = false; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic int rbtn_suspend(struct device *dev) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 25562306a36Sopenharmony_ci struct rbtn_data *rbtn_data = acpi_driver_data(device); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci rbtn_data->suspended = true; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci return 0; 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic int rbtn_resume(struct device *dev) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 26562306a36Sopenharmony_ci struct rbtn_data *rbtn_data = acpi_driver_data(device); 26662306a36Sopenharmony_ci acpi_status status; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci /* 26962306a36Sopenharmony_ci * Upon resume, some BIOSes send an ACPI notification thet triggers 27062306a36Sopenharmony_ci * an unwanted input event. In order to ignore it, we use a flag 27162306a36Sopenharmony_ci * that we set at suspend and clear once we have received the extra 27262306a36Sopenharmony_ci * ACPI notification. Since ACPI notifications are delivered 27362306a36Sopenharmony_ci * asynchronously to drivers, we clear the flag from the workqueue 27462306a36Sopenharmony_ci * used to deliver the notifications. This should be enough 27562306a36Sopenharmony_ci * to have the flag cleared only after we received the extra 27662306a36Sopenharmony_ci * notification, if any. 27762306a36Sopenharmony_ci */ 27862306a36Sopenharmony_ci status = acpi_os_execute(OSL_NOTIFY_HANDLER, 27962306a36Sopenharmony_ci rbtn_clear_suspended_flag, rbtn_data); 28062306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 28162306a36Sopenharmony_ci rbtn_clear_suspended_flag(rbtn_data); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci return 0; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci#endif 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic struct acpi_driver rbtn_driver = { 29062306a36Sopenharmony_ci .name = "dell-rbtn", 29162306a36Sopenharmony_ci .ids = rbtn_ids, 29262306a36Sopenharmony_ci .drv.pm = &rbtn_pm_ops, 29362306a36Sopenharmony_ci .ops = { 29462306a36Sopenharmony_ci .add = rbtn_add, 29562306a36Sopenharmony_ci .remove = rbtn_remove, 29662306a36Sopenharmony_ci .notify = rbtn_notify, 29762306a36Sopenharmony_ci }, 29862306a36Sopenharmony_ci .owner = THIS_MODULE, 29962306a36Sopenharmony_ci}; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci/* 30362306a36Sopenharmony_ci * notifier export functions 30462306a36Sopenharmony_ci */ 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cistatic bool auto_remove_rfkill = true; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic int rbtn_inc_count(struct device *dev, void *data) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 31362306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 31462306a36Sopenharmony_ci int *count = data; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (rbtn_data->type == RBTN_SLIDER) 31762306a36Sopenharmony_ci (*count)++; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return 0; 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic int rbtn_switch_dev(struct device *dev, void *data) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci struct acpi_device *device = to_acpi_device(dev); 32562306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 32662306a36Sopenharmony_ci bool enable = data; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci if (rbtn_data->type != RBTN_SLIDER) 32962306a36Sopenharmony_ci return 0; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci if (enable) 33262306a36Sopenharmony_ci rbtn_rfkill_init(device); 33362306a36Sopenharmony_ci else 33462306a36Sopenharmony_ci rbtn_rfkill_exit(device); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci return 0; 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ciint dell_rbtn_notifier_register(struct notifier_block *nb) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci bool first; 34262306a36Sopenharmony_ci int count; 34362306a36Sopenharmony_ci int ret; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci count = 0; 34662306a36Sopenharmony_ci ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, 34762306a36Sopenharmony_ci rbtn_inc_count); 34862306a36Sopenharmony_ci if (ret || count == 0) 34962306a36Sopenharmony_ci return -ENODEV; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci first = !rbtn_chain_head.head; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); 35462306a36Sopenharmony_ci if (ret != 0) 35562306a36Sopenharmony_ci return ret; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci if (auto_remove_rfkill && first) 35862306a36Sopenharmony_ci ret = driver_for_each_device(&rbtn_driver.drv, NULL, 35962306a36Sopenharmony_ci (void *)false, rbtn_switch_dev); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci return ret; 36262306a36Sopenharmony_ci} 36362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ciint dell_rbtn_notifier_unregister(struct notifier_block *nb) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci int ret; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); 37062306a36Sopenharmony_ci if (ret != 0) 37162306a36Sopenharmony_ci return ret; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci if (auto_remove_rfkill && !rbtn_chain_head.head) 37462306a36Sopenharmony_ci ret = driver_for_each_device(&rbtn_driver.drv, NULL, 37562306a36Sopenharmony_ci (void *)true, rbtn_switch_dev); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci return ret; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci/* 38362306a36Sopenharmony_ci * acpi driver functions 38462306a36Sopenharmony_ci */ 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_cistatic int rbtn_add(struct acpi_device *device) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci struct rbtn_data *rbtn_data; 38962306a36Sopenharmony_ci enum rbtn_type type; 39062306a36Sopenharmony_ci int ret = 0; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci type = rbtn_check(device); 39362306a36Sopenharmony_ci if (type == RBTN_UNKNOWN) { 39462306a36Sopenharmony_ci dev_info(&device->dev, "Unknown device type\n"); 39562306a36Sopenharmony_ci return -EINVAL; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); 39962306a36Sopenharmony_ci if (!rbtn_data) 40062306a36Sopenharmony_ci return -ENOMEM; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci ret = rbtn_acquire(device, true); 40362306a36Sopenharmony_ci if (ret < 0) { 40462306a36Sopenharmony_ci dev_err(&device->dev, "Cannot enable device\n"); 40562306a36Sopenharmony_ci return ret; 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci rbtn_data->type = type; 40962306a36Sopenharmony_ci device->driver_data = rbtn_data; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci switch (rbtn_data->type) { 41262306a36Sopenharmony_ci case RBTN_TOGGLE: 41362306a36Sopenharmony_ci ret = rbtn_input_init(rbtn_data); 41462306a36Sopenharmony_ci break; 41562306a36Sopenharmony_ci case RBTN_SLIDER: 41662306a36Sopenharmony_ci if (auto_remove_rfkill && rbtn_chain_head.head) 41762306a36Sopenharmony_ci ret = 0; 41862306a36Sopenharmony_ci else 41962306a36Sopenharmony_ci ret = rbtn_rfkill_init(device); 42062306a36Sopenharmony_ci break; 42162306a36Sopenharmony_ci default: 42262306a36Sopenharmony_ci ret = -EINVAL; 42362306a36Sopenharmony_ci break; 42462306a36Sopenharmony_ci } 42562306a36Sopenharmony_ci if (ret) 42662306a36Sopenharmony_ci rbtn_acquire(device, false); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci return ret; 42962306a36Sopenharmony_ci} 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_cistatic void rbtn_remove(struct acpi_device *device) 43262306a36Sopenharmony_ci{ 43362306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci switch (rbtn_data->type) { 43662306a36Sopenharmony_ci case RBTN_TOGGLE: 43762306a36Sopenharmony_ci rbtn_input_exit(rbtn_data); 43862306a36Sopenharmony_ci break; 43962306a36Sopenharmony_ci case RBTN_SLIDER: 44062306a36Sopenharmony_ci rbtn_rfkill_exit(device); 44162306a36Sopenharmony_ci break; 44262306a36Sopenharmony_ci default: 44362306a36Sopenharmony_ci break; 44462306a36Sopenharmony_ci } 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci rbtn_acquire(device, false); 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_cistatic void rbtn_notify(struct acpi_device *device, u32 event) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci struct rbtn_data *rbtn_data = device->driver_data; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci /* 45462306a36Sopenharmony_ci * Some BIOSes send a notification at resume. 45562306a36Sopenharmony_ci * Ignore it to prevent unwanted input events. 45662306a36Sopenharmony_ci */ 45762306a36Sopenharmony_ci if (rbtn_data->suspended) { 45862306a36Sopenharmony_ci dev_dbg(&device->dev, "ACPI notification ignored\n"); 45962306a36Sopenharmony_ci return; 46062306a36Sopenharmony_ci } 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci if (event != 0x80) { 46362306a36Sopenharmony_ci dev_info(&device->dev, "Received unknown event (0x%x)\n", 46462306a36Sopenharmony_ci event); 46562306a36Sopenharmony_ci return; 46662306a36Sopenharmony_ci } 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci switch (rbtn_data->type) { 46962306a36Sopenharmony_ci case RBTN_TOGGLE: 47062306a36Sopenharmony_ci rbtn_input_event(rbtn_data); 47162306a36Sopenharmony_ci break; 47262306a36Sopenharmony_ci case RBTN_SLIDER: 47362306a36Sopenharmony_ci rbtn_rfkill_event(device); 47462306a36Sopenharmony_ci atomic_notifier_call_chain(&rbtn_chain_head, event, device); 47562306a36Sopenharmony_ci break; 47662306a36Sopenharmony_ci default: 47762306a36Sopenharmony_ci break; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci/* 48362306a36Sopenharmony_ci * module functions 48462306a36Sopenharmony_ci */ 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_cimodule_acpi_driver(rbtn_driver); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_cimodule_param(auto_remove_rfkill, bool, 0444); 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ciMODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " 49162306a36Sopenharmony_ci "other modules start receiving events " 49262306a36Sopenharmony_ci "from this module and re-add them when " 49362306a36Sopenharmony_ci "the last module stops receiving events " 49462306a36Sopenharmony_ci "(default true)"); 49562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, rbtn_ids); 49662306a36Sopenharmony_ciMODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); 49762306a36Sopenharmony_ciMODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); 49862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 499