brintos

brintos / linux-shallow public Read only

0
0
Text · 10.5 KiB · a415c43 Raw
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 = &param;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