Commit 817a5cdb authored by Pali Rohár's avatar Pali Rohár Committed by Darren Hart

dell-rbtn: Dell Airplane Mode Switch driver

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.
Signed-off-by: default avatarPali Rohár <pali.rohar@gmail.com>
Tested-by: default avatarGabriele Mazzotta <gabriele.mzt@gmail.com>
Cc: Alex Hung <alex.hung@canonical.com>
[fengguang.wu@intel.com: rbtn_ops can be static]
Signed-off-by: default avatarFengguang Wu <fengguang.wu@intel.com>
[dvhart@linux.intel.com: Correct multi-line comment formatting]
Signed-off-by: default avatarDarren Hart <dvhart@linux.intel.com>
parent 9330dcdd
......@@ -3071,6 +3071,11 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell-laptop.c
DELL LAPTOP RBTN DRIVER
M: Pali Rohár <pali.rohar@gmail.com>
S: Maintained
F: drivers/platform/x86/dell-rbtn.*
DELL LAPTOP FREEFALL DRIVER
M: Pali Rohár <pali.rohar@gmail.com>
S: Maintained
......
......@@ -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"
......
......@@ -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
......
/*
Dell Airplane Mode Switch driver
Copyright (C) 2014-2015 Pali Rohár <pali.rohar@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 = &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;
}
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@gmail.com>");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment