[PATCH v2 1/3] platform: x86: dell-rbtn: Dell Airplane Mode Switch driver

From: Pali RohÃr
Date: Wed Apr 29 2015 - 05:51:30 EST


This is an ACPI driver for Dell laptops which receive HW switch events.
It exports rfkill device dell-rbtn which provide correct hard rfkill state.

Alex Hung added code for supporting Dell laptops which have toggle button
instead HW slider switch. On these laptops toggle button event is reported
by new input device (instead rfkill) as they do not have hw radio switch.

It looks like those are two different functions (rfkill, input device), but
Dell BIOS exports them via same ACPI device and uses same ACPI functions.
So code is in one kernel driver.

Signed-off-by: Pali RohÃr <pali.rohar@xxxxxxxxx>
Cc: Alex Hung <alex.hung@xxxxxxxxxxxxx>
---
drivers/platform/x86/Kconfig | 13 ++
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/dell-rbtn.c | 344 ++++++++++++++++++++++++++++++++++++++
3 files changed, 358 insertions(+)
create mode 100644 drivers/platform/x86/dell-rbtn.c

diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index f9f205c..9d065c2 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -138,6 +138,19 @@ config DELL_SMO8800
To compile this driver as a module, choose M here: the module will
be called dell-smo8800.

+config DELL_RBTN
+ tristate "Dell Airplane Mode Switch driver"
+ depends on ACPI
+ depends on RFKILL
+ ---help---
+ Say Y here if you want to support Dell Airplane Mode Switch ACPI
+ device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN.
+ This driver register rfkill device and receives HW rfkill events
+ (when wifi/bluetooth was enabled) and set correct hard rfkill state.
+
+ To compile this driver as a module, choose M here: the module will
+ be called dell-rbtn.
+

config FUJITSU_LAPTOP
tristate "Fujitsu Laptop Extras"
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index f82232b..b3e54ed 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
+obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
obj-$(CONFIG_ACERHDF) += acerhdf.o
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c
new file mode 100644
index 0000000..894cd9a
--- /dev/null
+++ b/drivers/platform/x86/dell-rbtn.c
@@ -0,0 +1,344 @@
+/*
+ Dell Airplane Mode Switch driver
+ Copyright (C) 2014-2015 Pali RohÃr <pali.rohar@xxxxxxxxx>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/input.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;
+};
+
+
+/*
+ * 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_radio_enable(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 = &param;
+
+ 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;
+}
+
+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 },
+ { "", 0 },
+};
+
+static struct acpi_driver rbtn_driver = {
+ .name = "dell-rbtn",
+ .ids = rbtn_ids,
+ .ops = {
+ .add = rbtn_add,
+ .remove = rbtn_remove,
+ .notify = rbtn_notify,
+ },
+ .owner = THIS_MODULE,
+};
+
+
+/*
+ * 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_radio_enable(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:
+ 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;
+ int ret;
+
+ switch (rbtn_data->type) {
+ case RBTN_TOGGLE:
+ rbtn_input_exit(rbtn_data);
+ break;
+ case RBTN_SLIDER:
+ rbtn_rfkill_exit(device);
+ break;
+ default:
+ break;
+ }
+
+ ret = rbtn_radio_enable(device, false);
+ if (ret < 0)
+ return ret;
+
+ device->driver_data = NULL;
+ return 0;
+}
+
+static void rbtn_notify(struct acpi_device *device, u32 event)
+{
+ struct rbtn_data *rbtn_data = device->driver_data;
+
+ 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);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/*
+ * module functions
+ */
+
+static int __init rbtn_init(void)
+{
+ return acpi_bus_register_driver(&rbtn_driver);
+}
+
+static void __exit rbtn_exit(void)
+{
+ acpi_bus_unregister_driver(&rbtn_driver);
+}
+
+module_init(rbtn_init);
+module_exit(rbtn_exit);
+
+MODULE_DEVICE_TABLE(acpi, rbtn_ids);
+MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
+MODULE_AUTHOR("Pali RohÃr <pali.rohar@xxxxxxxxx>");
+MODULE_LICENSE("GPL");
--
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/