Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Pali Rohár | 1496 | 89.42% | 3 | 37.50% |
Gabriele Mazzotta | 171 | 10.22% | 1 | 12.50% |
Valdis Kletnieks | 3 | 0.18% | 1 | 12.50% |
Andrew Lutomirski | 1 | 0.06% | 1 | 12.50% |
Thomas Gleixner | 1 | 0.06% | 1 | 12.50% |
Bhumika Goyal | 1 | 0.06% | 1 | 12.50% |
Total | 1673 | 8 |
// SPDX-License-Identifier: GPL-2.0-or-later /* Dell Airplane Mode Switch driver Copyright (C) 2014-2015 Pali Rohár <pali@kernel.org> */ #include <linux/module.h> #include <linux/acpi.h> #include <linux/rfkill.h> #include <linux/input.h> #include "dell-rbtn.h" enum rbtn_type { RBTN_UNKNOWN, RBTN_TOGGLE, RBTN_SLIDER, }; struct rbtn_data { enum rbtn_type type; struct rfkill *rfkill; struct input_dev *input_dev; bool suspended; }; /* * acpi functions */ static enum rbtn_type rbtn_check(struct acpi_device *device) { unsigned long long output; acpi_status status; status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); if (ACPI_FAILURE(status)) return RBTN_UNKNOWN; switch (output) { case 0: case 1: return RBTN_TOGGLE; case 2: case 3: return RBTN_SLIDER; default: return RBTN_UNKNOWN; } } static int rbtn_get(struct acpi_device *device) { unsigned long long output; acpi_status status; status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); if (ACPI_FAILURE(status)) return -EINVAL; return !output; } static int rbtn_acquire(struct acpi_device *device, bool enable) { struct acpi_object_list input; union acpi_object param; acpi_status status; param.type = ACPI_TYPE_INTEGER; param.integer.value = enable; input.count = 1; input.pointer = ¶m; status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); if (ACPI_FAILURE(status)) return -EINVAL; return 0; } /* * rfkill device */ static void rbtn_rfkill_query(struct rfkill *rfkill, void *data) { struct acpi_device *device = data; int state; state = rbtn_get(device); if (state < 0) return; rfkill_set_states(rfkill, state, state); } static int rbtn_rfkill_set_block(void *data, bool blocked) { /* NOTE: setting soft rfkill state is not supported */ return -EINVAL; } static const struct rfkill_ops rbtn_ops = { .query = rbtn_rfkill_query, .set_block = rbtn_rfkill_set_block, }; static int rbtn_rfkill_init(struct acpi_device *device) { struct rbtn_data *rbtn_data = device->driver_data; int ret; if (rbtn_data->rfkill) return 0; /* * NOTE: rbtn controls all radio devices, not only WLAN * but rfkill interface does not support "ANY" type * so "WLAN" type is used */ rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, RFKILL_TYPE_WLAN, &rbtn_ops, device); if (!rbtn_data->rfkill) return -ENOMEM; ret = rfkill_register(rbtn_data->rfkill); if (ret) { rfkill_destroy(rbtn_data->rfkill); rbtn_data->rfkill = NULL; return ret; } return 0; } static void rbtn_rfkill_exit(struct acpi_device *device) { struct rbtn_data *rbtn_data = device->driver_data; if (!rbtn_data->rfkill) return; rfkill_unregister(rbtn_data->rfkill); rfkill_destroy(rbtn_data->rfkill); rbtn_data->rfkill = NULL; } static void rbtn_rfkill_event(struct acpi_device *device) { struct rbtn_data *rbtn_data = device->driver_data; if (rbtn_data->rfkill) rbtn_rfkill_query(rbtn_data->rfkill, device); } /* * input device */ static int rbtn_input_init(struct rbtn_data *rbtn_data) { int ret; rbtn_data->input_dev = input_allocate_device(); if (!rbtn_data->input_dev) return -ENOMEM; rbtn_data->input_dev->name = "DELL Wireless hotkeys"; rbtn_data->input_dev->phys = "dellabce/input0"; rbtn_data->input_dev->id.bustype = BUS_HOST; rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); ret = input_register_device(rbtn_data->input_dev); if (ret) { input_free_device(rbtn_data->input_dev); rbtn_data->input_dev = NULL; return ret; } return 0; } static void rbtn_input_exit(struct rbtn_data *rbtn_data) { input_unregister_device(rbtn_data->input_dev); rbtn_data->input_dev = NULL; } static void rbtn_input_event(struct rbtn_data *rbtn_data) { input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); input_sync(rbtn_data->input_dev); input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); input_sync(rbtn_data->input_dev); } /* * acpi driver */ static int rbtn_add(struct acpi_device *device); static int rbtn_remove(struct acpi_device *device); static void rbtn_notify(struct acpi_device *device, u32 event); static const struct acpi_device_id rbtn_ids[] = { { "DELRBTN", 0 }, { "DELLABCE", 0 }, /* * This driver can also handle the "DELLABC6" device that * appears on the XPS 13 9350, but that device is disabled by * the DSDT unless booted with acpi_osi="!Windows 2012" * acpi_osi="!Windows 2013". * * According to Mario at Dell: * * DELLABC6 is a custom interface that was created solely to * have airplane mode support for Windows 7. For Windows 10 * the proper interface is to use that which is handled by * intel-hid. A OEM airplane mode driver is not used. * * Since the kernel doesn't identify as Windows 7 it would be * incorrect to do attempt to use that interface. * * Even if we override _OSI and bind to DELLABC6, we end up with * inconsistent behavior in which userspace can get out of sync * with the rfkill state as it conflicts with events from * intel-hid. * * The upshot is that it is better to just ignore DELLABC6 * devices. */ { "", 0 }, }; #ifdef CONFIG_PM_SLEEP static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) { struct rbtn_data *rbtn_data = context; rbtn_data->suspended = false; } static int rbtn_suspend(struct device *dev) { struct acpi_device *device = to_acpi_device(dev); struct rbtn_data *rbtn_data = acpi_driver_data(device); rbtn_data->suspended = true; return 0; } static int rbtn_resume(struct device *dev) { struct acpi_device *device = to_acpi_device(dev); struct rbtn_data *rbtn_data = acpi_driver_data(device); acpi_status status; /* * Upon resume, some BIOSes send an ACPI notification thet triggers * an unwanted input event. In order to ignore it, we use a flag * that we set at suspend and clear once we have received the extra * ACPI notification. Since ACPI notifications are delivered * asynchronously to drivers, we clear the flag from the workqueue * used to deliver the notifications. This should be enough * to have the flag cleared only after we received the extra * notification, if any. */ status = acpi_os_execute(OSL_NOTIFY_HANDLER, rbtn_clear_suspended_flag, rbtn_data); if (ACPI_FAILURE(status)) rbtn_clear_suspended_flag(rbtn_data); return 0; } #endif static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); static struct acpi_driver rbtn_driver = { .name = "dell-rbtn", .ids = rbtn_ids, .drv.pm = &rbtn_pm_ops, .ops = { .add = rbtn_add, .remove = rbtn_remove, .notify = rbtn_notify, }, .owner = THIS_MODULE, }; /* * notifier export functions */ static bool auto_remove_rfkill = true; static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); static int rbtn_inc_count(struct device *dev, void *data) { struct acpi_device *device = to_acpi_device(dev); struct rbtn_data *rbtn_data = device->driver_data; int *count = data; if (rbtn_data->type == RBTN_SLIDER) (*count)++; return 0; } static int rbtn_switch_dev(struct device *dev, void *data) { struct acpi_device *device = to_acpi_device(dev); struct rbtn_data *rbtn_data = device->driver_data; bool enable = data; if (rbtn_data->type != RBTN_SLIDER) return 0; if (enable) rbtn_rfkill_init(device); else rbtn_rfkill_exit(device); return 0; } int dell_rbtn_notifier_register(struct notifier_block *nb) { bool first; int count; int ret; count = 0; ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, rbtn_inc_count); if (ret || count == 0) return -ENODEV; first = !rbtn_chain_head.head; ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); if (ret != 0) return ret; if (auto_remove_rfkill && first) ret = driver_for_each_device(&rbtn_driver.drv, NULL, (void *)false, rbtn_switch_dev); return ret; } EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); int dell_rbtn_notifier_unregister(struct notifier_block *nb) { int ret; ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); if (ret != 0) return ret; if (auto_remove_rfkill && !rbtn_chain_head.head) ret = driver_for_each_device(&rbtn_driver.drv, NULL, (void *)true, rbtn_switch_dev); return ret; } EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); /* * acpi driver functions */ static int rbtn_add(struct acpi_device *device) { struct rbtn_data *rbtn_data; enum rbtn_type type; int ret = 0; type = rbtn_check(device); if (type == RBTN_UNKNOWN) { dev_info(&device->dev, "Unknown device type\n"); return -EINVAL; } ret = rbtn_acquire(device, true); if (ret < 0) { dev_err(&device->dev, "Cannot enable device\n"); return ret; } rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); if (!rbtn_data) return -ENOMEM; rbtn_data->type = type; device->driver_data = rbtn_data; switch (rbtn_data->type) { case RBTN_TOGGLE: ret = rbtn_input_init(rbtn_data); break; case RBTN_SLIDER: if (auto_remove_rfkill && rbtn_chain_head.head) ret = 0; else ret = rbtn_rfkill_init(device); break; default: ret = -EINVAL; } return ret; } static int rbtn_remove(struct acpi_device *device) { struct rbtn_data *rbtn_data = device->driver_data; switch (rbtn_data->type) { case RBTN_TOGGLE: rbtn_input_exit(rbtn_data); break; case RBTN_SLIDER: rbtn_rfkill_exit(device); break; default: break; } rbtn_acquire(device, false); device->driver_data = NULL; return 0; } static void rbtn_notify(struct acpi_device *device, u32 event) { struct rbtn_data *rbtn_data = device->driver_data; /* * Some BIOSes send a notification at resume. * Ignore it to prevent unwanted input events. */ if (rbtn_data->suspended) { dev_dbg(&device->dev, "ACPI notification ignored\n"); return; } if (event != 0x80) { dev_info(&device->dev, "Received unknown event (0x%x)\n", event); return; } switch (rbtn_data->type) { case RBTN_TOGGLE: rbtn_input_event(rbtn_data); break; case RBTN_SLIDER: rbtn_rfkill_event(device); atomic_notifier_call_chain(&rbtn_chain_head, event, device); break; default: break; } } /* * module functions */ module_acpi_driver(rbtn_driver); module_param(auto_remove_rfkill, bool, 0444); MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " "other modules start receiving events " "from this module and re-add them when " "the last module stops receiving events " "(default true)"); MODULE_DEVICE_TABLE(acpi, rbtn_ids); MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1