[PATCH] dell-rbtn: Dell Airplane Mode Switch driver

Andy Whitcroft apw at canonical.com
Mon Jul 13 08:41:29 UTC 2015


On Mon, Jul 13, 2015 at 03:04:11PM +0800, Alex Hung wrote:
> From: Pali Rohár <pali.rohar at gmail.com>
> 
> This is an ACPI driver for Dell laptops which receive HW slider radio
> switch or hotkey toggle wifi button events. It exports rfkill device
> dell-rbtn (which provide correct hard rfkill state) or hotkey input device.
> 
> Alex Hung is author of original hotkey input device code.
> 
> BugLink: http://bugs.launchpad.net/bugs/1440949
> 
> Signed-off-by: Pali Rohár <pali.rohar at gmail.com>
> Tested-by: Gabriele Mazzotta <gabriele.mzt at gmail.com>
> Cc: Alex Hung <alex.hung at canonical.com>
> [fengguang.wu at intel.com: rbtn_ops can be static]
> Signed-off-by: Fengguang Wu <fengguang.wu at intel.com>
> [dvhart at linux.intel.com: Correct multi-line comment formatting]
> Signed-off-by: Darren Hart <dvhart at linux.intel.com>
> (cherry picked from commit 817a5cdb40c8115eafe631b8e1de37cf8fe9fab8)
> 
> Signed-off-by: Alex Hung <alex.hung at canonical.com>
> Reviewed-by: Keng-Yu Lin <kengyu at canonical.com>
> ---
>  MAINTAINERS                      |   10 ++
>  drivers/platform/x86/Kconfig     |   16 ++
>  drivers/platform/x86/Makefile    |    1 +
>  drivers/platform/x86/dell-rbtn.c |  332 ++++++++++++++++++++++++++++++++++++++
>  4 files changed, 359 insertions(+)
>  create mode 100644 drivers/platform/x86/dell-rbtn.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1c113a4..fe46274 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3030,6 +3030,16 @@ L:	platform-driver-x86 at vger.kernel.org
>  S:	Maintained
>  F:	drivers/platform/x86/dell-laptop.c
>  
> +DELL LAPTOP RBTN DRIVER
> +M:	Pali Rohár <pali.rohar at gmail.com>
> +S:	Maintained
> +F:	drivers/platform/x86/dell-rbtn.*
> +
> +DELL LAPTOP FREEFALL DRIVER
> +M:	Pali Rohár <pali.rohar at gmail.com>
> +S:	Maintained
> +F:	drivers/platform/x86/dell-smo8800.c
> +
>  DELL LAPTOP SMM DRIVER
>  M:	Guenter Roeck <linux at roeck-us.net>
>  F:	drivers/char/i8k.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 638e7970..4fff77d 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -138,6 +138,22 @@ 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 INPUT
> +	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 or input hotkey device depending
> +	  on hardware type (hw switch slider or keyboard toggle button). For
> +	  rfkill devices it receive HW switch events 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..1c19fff
> --- /dev/null
> +++ b/drivers/platform/x86/dell-rbtn.c
> @@ -0,0 +1,332 @@
> +/*
> +    Dell Airplane Mode Switch driver
> +    Copyright (C) 2014-2015  Pali Rohár <pali.rohar at gmail.com>
> +
> +    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_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 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_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:
> +		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;
> +
> +	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
> + */
> +
> +module_acpi_driver(rbtn_driver);
> +
> +MODULE_DEVICE_TABLE(acpi, rbtn_ids);
> +MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
> +MODULE_AUTHOR("Pali Rohár <pali.rohar at gmail.com>");
> +MODULE_LICENSE("GPL");

So this adds a new rfkill device on machines which have this ACPI
object.  But there is nothing here to stop the existing driver also
making its rfkill device.  This seems unexpected?  This feels like we
will end up with two devices and much confusion.

A quick look in the upstream tree shows there are two additional
commmits which appear to address this issue:

  commit b05ffc95f9ed986534b67538e239e9c4ba254b55
  Author: Pali Rohár <pali.rohar at gmail.com>
  Date:   Sat Jun 6 10:23:29 2015 +0200

    dell-rbtn: Export notifier for other kernel modules

  commit f8358578e2f23bd82d0454c17676bdb28a40664a
  Author: Pali Rohár <pali.rohar at gmail.com>
  Date:   Sat Jun 6 10:23:30 2015 +0200

    dell-laptop: Use dell-rbtn instead i8042 filter when possible

Do we need those as well?

-apw




More information about the kernel-team mailing list