498 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 Dell Airplane Mode Switch driver4 Copyright (C) 2014-2015 Pali Rohár <pali@kernel.org>5 6*/7 8#include <linux/module.h>9#include <linux/acpi.h>10#include <linux/rfkill.h>11#include <linux/input.h>12 13#include "dell-rbtn.h"14 15enum rbtn_type {16 RBTN_UNKNOWN,17 RBTN_TOGGLE,18 RBTN_SLIDER,19};20 21struct rbtn_data {22 enum rbtn_type type;23 struct rfkill *rfkill;24 struct input_dev *input_dev;25 bool suspended;26};27 28 29/*30 * acpi functions31 */32 33static enum rbtn_type rbtn_check(struct acpi_device *device)34{35 unsigned long long output;36 acpi_status status;37 38 status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);39 if (ACPI_FAILURE(status))40 return RBTN_UNKNOWN;41 42 switch (output) {43 case 0:44 case 1:45 return RBTN_TOGGLE;46 case 2:47 case 3:48 return RBTN_SLIDER;49 default:50 return RBTN_UNKNOWN;51 }52}53 54static int rbtn_get(struct acpi_device *device)55{56 unsigned long long output;57 acpi_status status;58 59 status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);60 if (ACPI_FAILURE(status))61 return -EINVAL;62 63 return !output;64}65 66static int rbtn_acquire(struct acpi_device *device, bool enable)67{68 struct acpi_object_list input;69 union acpi_object param;70 acpi_status status;71 72 param.type = ACPI_TYPE_INTEGER;73 param.integer.value = enable;74 input.count = 1;75 input.pointer = ¶m;76 77 status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);78 if (ACPI_FAILURE(status))79 return -EINVAL;80 81 return 0;82}83 84 85/*86 * rfkill device87 */88 89static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)90{91 struct acpi_device *device = data;92 int state;93 94 state = rbtn_get(device);95 if (state < 0)96 return;97 98 rfkill_set_states(rfkill, state, state);99}100 101static int rbtn_rfkill_set_block(void *data, bool blocked)102{103 /* NOTE: setting soft rfkill state is not supported */104 return -EINVAL;105}106 107static const struct rfkill_ops rbtn_ops = {108 .query = rbtn_rfkill_query,109 .set_block = rbtn_rfkill_set_block,110};111 112static int rbtn_rfkill_init(struct acpi_device *device)113{114 struct rbtn_data *rbtn_data = device->driver_data;115 int ret;116 117 if (rbtn_data->rfkill)118 return 0;119 120 /*121 * NOTE: rbtn controls all radio devices, not only WLAN122 * but rfkill interface does not support "ANY" type123 * so "WLAN" type is used124 */125 rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,126 RFKILL_TYPE_WLAN, &rbtn_ops, device);127 if (!rbtn_data->rfkill)128 return -ENOMEM;129 130 ret = rfkill_register(rbtn_data->rfkill);131 if (ret) {132 rfkill_destroy(rbtn_data->rfkill);133 rbtn_data->rfkill = NULL;134 return ret;135 }136 137 return 0;138}139 140static void rbtn_rfkill_exit(struct acpi_device *device)141{142 struct rbtn_data *rbtn_data = device->driver_data;143 144 if (!rbtn_data->rfkill)145 return;146 147 rfkill_unregister(rbtn_data->rfkill);148 rfkill_destroy(rbtn_data->rfkill);149 rbtn_data->rfkill = NULL;150}151 152static void rbtn_rfkill_event(struct acpi_device *device)153{154 struct rbtn_data *rbtn_data = device->driver_data;155 156 if (rbtn_data->rfkill)157 rbtn_rfkill_query(rbtn_data->rfkill, device);158}159 160 161/*162 * input device163 */164 165static int rbtn_input_init(struct rbtn_data *rbtn_data)166{167 int ret;168 169 rbtn_data->input_dev = input_allocate_device();170 if (!rbtn_data->input_dev)171 return -ENOMEM;172 173 rbtn_data->input_dev->name = "DELL Wireless hotkeys";174 rbtn_data->input_dev->phys = "dellabce/input0";175 rbtn_data->input_dev->id.bustype = BUS_HOST;176 rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);177 set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);178 179 ret = input_register_device(rbtn_data->input_dev);180 if (ret) {181 input_free_device(rbtn_data->input_dev);182 rbtn_data->input_dev = NULL;183 return ret;184 }185 186 return 0;187}188 189static void rbtn_input_exit(struct rbtn_data *rbtn_data)190{191 input_unregister_device(rbtn_data->input_dev);192 rbtn_data->input_dev = NULL;193}194 195static void rbtn_input_event(struct rbtn_data *rbtn_data)196{197 input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);198 input_sync(rbtn_data->input_dev);199 input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);200 input_sync(rbtn_data->input_dev);201}202 203 204/*205 * acpi driver206 */207 208static int rbtn_add(struct acpi_device *device);209static void rbtn_remove(struct acpi_device *device);210static void rbtn_notify(struct acpi_device *device, u32 event);211 212static const struct acpi_device_id rbtn_ids[] = {213 { "DELRBTN", 0 },214 { "DELLABCE", 0 },215 216 /*217 * This driver can also handle the "DELLABC6" device that218 * appears on the XPS 13 9350, but that device is disabled by219 * the DSDT unless booted with acpi_osi="!Windows 2012"220 * acpi_osi="!Windows 2013".221 *222 * According to Mario at Dell:223 *224 * DELLABC6 is a custom interface that was created solely to225 * have airplane mode support for Windows 7. For Windows 10226 * the proper interface is to use that which is handled by227 * intel-hid. A OEM airplane mode driver is not used.228 *229 * Since the kernel doesn't identify as Windows 7 it would be230 * incorrect to do attempt to use that interface.231 *232 * Even if we override _OSI and bind to DELLABC6, we end up with233 * inconsistent behavior in which userspace can get out of sync234 * with the rfkill state as it conflicts with events from235 * intel-hid.236 *237 * The upshot is that it is better to just ignore DELLABC6238 * devices.239 */240 241 { "", 0 },242};243 244#ifdef CONFIG_PM_SLEEP245static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context)246{247 struct rbtn_data *rbtn_data = context;248 249 rbtn_data->suspended = false;250}251 252static int rbtn_suspend(struct device *dev)253{254 struct acpi_device *device = to_acpi_device(dev);255 struct rbtn_data *rbtn_data = acpi_driver_data(device);256 257 rbtn_data->suspended = true;258 259 return 0;260}261 262static int rbtn_resume(struct device *dev)263{264 struct acpi_device *device = to_acpi_device(dev);265 struct rbtn_data *rbtn_data = acpi_driver_data(device);266 acpi_status status;267 268 /*269 * Upon resume, some BIOSes send an ACPI notification thet triggers270 * an unwanted input event. In order to ignore it, we use a flag271 * that we set at suspend and clear once we have received the extra272 * ACPI notification. Since ACPI notifications are delivered273 * asynchronously to drivers, we clear the flag from the workqueue274 * used to deliver the notifications. This should be enough275 * to have the flag cleared only after we received the extra276 * notification, if any.277 */278 status = acpi_os_execute(OSL_NOTIFY_HANDLER,279 rbtn_clear_suspended_flag, rbtn_data);280 if (ACPI_FAILURE(status))281 rbtn_clear_suspended_flag(rbtn_data);282 283 return 0;284}285#endif286 287static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume);288 289static struct acpi_driver rbtn_driver = {290 .name = "dell-rbtn",291 .ids = rbtn_ids,292 .drv.pm = &rbtn_pm_ops,293 .ops = {294 .add = rbtn_add,295 .remove = rbtn_remove,296 .notify = rbtn_notify,297 },298};299 300 301/*302 * notifier export functions303 */304 305static bool auto_remove_rfkill = true;306 307static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);308 309static int rbtn_inc_count(struct device *dev, void *data)310{311 struct acpi_device *device = to_acpi_device(dev);312 struct rbtn_data *rbtn_data = device->driver_data;313 int *count = data;314 315 if (rbtn_data->type == RBTN_SLIDER)316 (*count)++;317 318 return 0;319}320 321static int rbtn_switch_dev(struct device *dev, void *data)322{323 struct acpi_device *device = to_acpi_device(dev);324 struct rbtn_data *rbtn_data = device->driver_data;325 bool enable = data;326 327 if (rbtn_data->type != RBTN_SLIDER)328 return 0;329 330 if (enable)331 rbtn_rfkill_init(device);332 else333 rbtn_rfkill_exit(device);334 335 return 0;336}337 338int dell_rbtn_notifier_register(struct notifier_block *nb)339{340 bool first;341 int count;342 int ret;343 344 count = 0;345 ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,346 rbtn_inc_count);347 if (ret || count == 0)348 return -ENODEV;349 350 first = !rbtn_chain_head.head;351 352 ret = atomic_notifier_chain_register(&rbtn_chain_head, nb);353 if (ret != 0)354 return ret;355 356 if (auto_remove_rfkill && first)357 ret = driver_for_each_device(&rbtn_driver.drv, NULL,358 (void *)false, rbtn_switch_dev);359 360 return ret;361}362EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register);363 364int dell_rbtn_notifier_unregister(struct notifier_block *nb)365{366 int ret;367 368 ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb);369 if (ret != 0)370 return ret;371 372 if (auto_remove_rfkill && !rbtn_chain_head.head)373 ret = driver_for_each_device(&rbtn_driver.drv, NULL,374 (void *)true, rbtn_switch_dev);375 376 return ret;377}378EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);379 380 381/*382 * acpi driver functions383 */384 385static int rbtn_add(struct acpi_device *device)386{387 struct rbtn_data *rbtn_data;388 enum rbtn_type type;389 int ret = 0;390 391 type = rbtn_check(device);392 if (type == RBTN_UNKNOWN) {393 dev_info(&device->dev, "Unknown device type\n");394 return -EINVAL;395 }396 397 rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);398 if (!rbtn_data)399 return -ENOMEM;400 401 ret = rbtn_acquire(device, true);402 if (ret < 0) {403 dev_err(&device->dev, "Cannot enable device\n");404 return ret;405 }406 407 rbtn_data->type = type;408 device->driver_data = rbtn_data;409 410 switch (rbtn_data->type) {411 case RBTN_TOGGLE:412 ret = rbtn_input_init(rbtn_data);413 break;414 case RBTN_SLIDER:415 if (auto_remove_rfkill && rbtn_chain_head.head)416 ret = 0;417 else418 ret = rbtn_rfkill_init(device);419 break;420 default:421 ret = -EINVAL;422 break;423 }424 if (ret)425 rbtn_acquire(device, false);426 427 return ret;428}429 430static void rbtn_remove(struct acpi_device *device)431{432 struct rbtn_data *rbtn_data = device->driver_data;433 434 switch (rbtn_data->type) {435 case RBTN_TOGGLE:436 rbtn_input_exit(rbtn_data);437 break;438 case RBTN_SLIDER:439 rbtn_rfkill_exit(device);440 break;441 default:442 break;443 }444 445 rbtn_acquire(device, false);446}447 448static void rbtn_notify(struct acpi_device *device, u32 event)449{450 struct rbtn_data *rbtn_data = device->driver_data;451 452 /*453 * Some BIOSes send a notification at resume.454 * Ignore it to prevent unwanted input events.455 */456 if (rbtn_data->suspended) {457 dev_dbg(&device->dev, "ACPI notification ignored\n");458 return;459 }460 461 if (event != 0x80) {462 dev_info(&device->dev, "Received unknown event (0x%x)\n",463 event);464 return;465 }466 467 switch (rbtn_data->type) {468 case RBTN_TOGGLE:469 rbtn_input_event(rbtn_data);470 break;471 case RBTN_SLIDER:472 rbtn_rfkill_event(device);473 atomic_notifier_call_chain(&rbtn_chain_head, event, device);474 break;475 default:476 break;477 }478}479 480 481/*482 * module functions483 */484 485module_acpi_driver(rbtn_driver);486 487module_param(auto_remove_rfkill, bool, 0444);488 489MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when "490 "other modules start receiving events "491 "from this module and re-add them when "492 "the last module stops receiving events "493 "(default true)");494MODULE_DEVICE_TABLE(acpi, rbtn_ids);495MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");496MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");497MODULE_LICENSE("GPL");498