Re: [PATCH 1/1] toshiba_acpi: Add support for bluetooth toggling through rfkill
From: Ivo van Doorn
Date: Sun Jul 27 2008 - 16:40:30 EST
On Sunday 27 July 2008, Philip Langdale wrote:
> There's been a patch floating around for toshiba_acpi that exports an ad-hoc
> /proc interface to toggle the bluetooth adapter in a large number of Toshiba
> laptops. I'm not sure if it's still relevant for the latest models, but it is
> still required for older models such as my Tecra M3.
>
> This change pulls in the low level Toshiba-specific code from the old patch and
> sets up an rfkill device and a polled input device to track the state of the
> hardware kill-switch.
You don't seem to be using rfkill_force_state() which is required to inform the rfkill
layer about the state changes.
Ivo
> Signed-off-by: Philip Langdale <philipl@xxxxxxxxx>
> ---
> Kconfig | 1
> toshiba_acpi.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
> 2 files changed, 325 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index c52fca8..bf1ae18 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -262,6 +262,7 @@ config ACPI_ASUS
> config ACPI_TOSHIBA
> tristate "Toshiba Laptop Extras"
> depends on X86
> + select INPUT_POLLDEV
> select BACKLIGHT_CLASS_DEVICE
> ---help---
> This driver adds support for access to certain system settings
> diff --git a/drivers/acpi/toshiba_acpi.c b/drivers/acpi/toshiba_acpi.c
> index 0a43c8e..9f747d7 100644
> --- a/drivers/acpi/toshiba_acpi.c
> +++ b/drivers/acpi/toshiba_acpi.c
> @@ -3,6 +3,7 @@
> *
> *
> * Copyright (C) 2002-2004 John Belmonte
> + * Copyright (C) 2008 Philip Langdale
> *
> * 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
> @@ -33,7 +34,7 @@
> *
> */
>
> -#define TOSHIBA_ACPI_VERSION "0.18"
> +#define TOSHIBA_ACPI_VERSION "0.19"
> #define PROC_INTERFACE_VERSION 1
>
> #include <linux/kernel.h>
> @@ -42,6 +43,9 @@
> #include <linux/types.h>
> #include <linux/proc_fs.h>
> #include <linux/backlight.h>
> +#include <linux/platform_device.h>
> +#include <linux/rfkill.h>
> +#include <linux/input-polldev.h>
>
> #include <asm/uaccess.h>
>
> @@ -62,6 +66,10 @@ MODULE_LICENSE("GPL");
> #define METHOD_HCI_2 "\\_SB_.VALZ.GHCI"
> #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX"
>
> +/* Toshiba ACPI Bluetooth object */
> +#define BT_ACPI_OBJECT "\\_SB_.BT"
> +#define BT_ACPI_SOFT_UNBLOCKED_EVENT 0x90
> +
> /* Toshiba HCI interface definitions
> *
> * HCI is Toshiba's "Hardware Control Interface" which is supposed to
> @@ -90,6 +98,7 @@ MODULE_LICENSE("GPL");
> #define HCI_VIDEO_OUT 0x001c
> #define HCI_HOTKEY_EVENT 0x001e
> #define HCI_LCD_BRIGHTNESS 0x002a
> +#define HCI_WIRELESS 0x0056
>
> /* field definitions */
> #define HCI_LCD_BRIGHTNESS_BITS 3
> @@ -98,9 +107,14 @@ MODULE_LICENSE("GPL");
> #define HCI_VIDEO_OUT_LCD 0x1
> #define HCI_VIDEO_OUT_CRT 0x2
> #define HCI_VIDEO_OUT_TV 0x4
> +#define HCI_WIRELESS_KILL_SWITCH 0x01
> +#define HCI_WIRELESS_BT_PRESENT 0x0f
> +#define HCI_WIRELESS_BT_ATTACH 0x40
> +#define HCI_WIRELESS_BT_POWER 0x80
>
> static const struct acpi_device_id toshiba_device_ids[] = {
> {"TOS6200", 0},
> + {"TOS6208", 0},
> {"TOS1900", 0},
> {"", 0},
> };
> @@ -193,7 +207,7 @@ static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS])
> return status;
> }
>
> -/* common hci tasks (get or set one value)
> +/* common hci tasks (get or set one or two value)
> *
> * In addition to the ACPI status, the HCI system returns a result which
> * may be useful (such as "not supported").
> @@ -218,6 +232,191 @@ static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result)
> return status;
> }
>
> +static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32* result)
> +{
> + u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 };
> + u32 out[HCI_WORDS];
> + acpi_status status = hci_raw(in, out);
> + *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
> + return status;
> +}
> +
> +static acpi_status hci_read2(u32 reg, u32* out1, u32* out2, u32* result)
> +{
> + u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 };
> + u32 out[HCI_WORDS];
> + acpi_status status = hci_raw(in, out);
> + *out1 = out[2];
> + *out2 = out[3];
> + *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
> + return status;
> +}
> +
> +struct toshiba_acpi_dev {
> + struct platform_device *p_dev;
> + struct rfkill *rfk_dev;
> + struct input_polled_dev *poll_dev;
> +
> + const char *bt_name;
> + acpi_handle bt_handle;
> + bool ignore_next_bt_event;
> +
> + bool last_rfk_state;
> +
> + struct mutex mutex;
> +};
> +
> +static struct toshiba_acpi_dev toshiba_acpi = {
> + .bt_name = "Toshiba Bluetooth",
> + .ignore_next_bt_event = FALSE,
> + .last_rfk_state = FALSE,
> +};
> +
> +/* Bluetooth rfkill handlers */
> +
> +static u32 hci_get_bt_present(bool *present)
> +{
> + u32 hci_result;
> + u32 value, value2;
> + value = 0;
> + value2 = 0;
> + hci_read2(HCI_WIRELESS, &value, &value2, &hci_result);
> + if (hci_result == HCI_SUCCESS) {
> + *present = (value & HCI_WIRELESS_BT_PRESENT) ? TRUE : FALSE;
> + }
> + return hci_result;
> +}
> +
> +static u32 hci_get_bt_on(bool *on)
> +{
> + u32 hci_result;
> + u32 value, value2;
> + value = 0;
> + value2 = 0x0001;
> + hci_read2(HCI_WIRELESS, &value, &value2, &hci_result);
> + if (hci_result == HCI_SUCCESS) {
> + *on = (value & HCI_WIRELESS_BT_POWER) &&
> + (value & HCI_WIRELESS_BT_ATTACH);
> + }
> + return hci_result;
> +}
> +
> +static u32 hci_get_radio_state(bool *radio_state)
> +{
> + u32 hci_result;
> + u32 value, value2;
> +
> + value = 0;
> + value2 = 0x0001;
> + hci_read2(HCI_WIRELESS, &value, &value2, &hci_result);
> +
> + *radio_state = value & HCI_WIRELESS_KILL_SWITCH;
> + return hci_result;
> +}
> +
> +static int bt_rfkill_toggle_radio(void *data, enum rfkill_state state)
> +{
> + u32 result1, result2;
> + u32 value;
> +
> + struct toshiba_acpi_dev *dev = data;
> +
> + value = state == RFKILL_STATE_UNBLOCKED;
> +
> + if (state == RFKILL_STATE_SOFT_BLOCKED) {
> + /*
> + * The ACPI event is emitted on every transition to
> + * the SOFT_BLOCKED state. We want to respond to
> + * it by turning the bluetooth device on when going
> + * from HARD_BLOCKED, but we certainly don't want
> + * to when going from UNBLOCKED! So we have to explictly
> + * ignore the next event in that case.
> + */
> + dev->ignore_next_bt_event = TRUE;
> + }
> +
> + mutex_lock(&dev->mutex);
> + hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1);
> + hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2);
> + mutex_unlock(&dev->mutex);
> +
> + if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) {
> + return -EFAULT;
> + }
> +
> + return 0;
> +}
> +
> +static int bt_rfkill_get_state(void *data, enum rfkill_state *state)
> +{
> + u32 hci_result;
> + bool radio_on;
> + bool bt_on;
> +
> + hci_result = hci_get_bt_on(&bt_on);
> + if (hci_result != HCI_SUCCESS) {
> + return -EFAULT;
> + }
> +
> + hci_result = hci_get_radio_state(&radio_on);
> + if (hci_result != HCI_SUCCESS) {
> + return -EFAULT;
> + }
> +
> + if (bt_on) {
> + *state = RFKILL_STATE_UNBLOCKED;
> + } else if (radio_on) {
> + *state = RFKILL_STATE_SOFT_BLOCKED;
> + } else {
> + *state = RFKILL_STATE_HARD_BLOCKED;
> + }
> +
> + return 0;
> +}
> +
> +static void bt_acpi_notify(acpi_handle handle, u32 event, void *data)
> +{
> + struct toshiba_acpi_dev *dev = data;
> +
> + switch (event) {
> + case BT_ACPI_SOFT_UNBLOCKED_EVENT:
> + if (!dev->ignore_next_bt_event) {
> + bt_rfkill_toggle_radio(data, RFKILL_STATE_UNBLOCKED);
> + } else {
> + dev->ignore_next_bt_event = FALSE;
> + }
> + break;
> + default:
> + printk(MY_NOTICE "Unknown event notified on BT object\n");
> + break;
> + }
> +}
> +
> +static void bt_poll_rfkill(struct input_polled_dev *poll_dev)
> +{
> + bool state_changed;
> + bool new_rfk_state;
> + bool value;
> + u32 hci_result;
> +
> + struct toshiba_acpi_dev *dev = poll_dev->private;
> +
> + hci_result = hci_get_radio_state(&value);
> + if (hci_result != HCI_SUCCESS) {
> + return; /* Can't do anything useful */
> + }
> + new_rfk_state = !value;
> +
> + mutex_lock(&dev->mutex);
> + state_changed = new_rfk_state != dev->last_rfk_state;
> + dev->last_rfk_state = new_rfk_state;
> + mutex_unlock(&dev->mutex);
> +
> + if (unlikely(state_changed)) {
> + input_report_switch(poll_dev->input, SW_RFKILL_ALL, new_rfk_state);
> + }
> +}
> +
> static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ;
> static struct backlight_device *toshiba_backlight_device;
> static int force_fan;
> @@ -547,6 +746,22 @@ static struct backlight_ops toshiba_backlight_data = {
>
> static void toshiba_acpi_exit(void)
> {
> + if (toshiba_acpi.poll_dev) {
> + input_unregister_polled_device(toshiba_acpi.poll_dev);
> + input_free_polled_device(toshiba_acpi.poll_dev);
> + }
> +
> + if (toshiba_acpi.bt_handle) {
> + acpi_remove_notify_handler(toshiba_acpi.bt_handle,
> + ACPI_ALL_NOTIFY,
> + bt_acpi_notify);
> + }
> +
> + if (toshiba_acpi.rfk_dev) {
> + rfkill_unregister(toshiba_acpi.rfk_dev);
> + rfkill_free(toshiba_acpi.rfk_dev);
> + }
> +
> if (toshiba_backlight_device)
> backlight_device_unregister(toshiba_backlight_device);
>
> @@ -555,6 +770,8 @@ static void toshiba_acpi_exit(void)
> if (toshiba_proc_dir)
> remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
>
> + platform_device_unregister(toshiba_acpi.p_dev);
> +
> return;
> }
>
> @@ -562,6 +779,10 @@ static int __init toshiba_acpi_init(void)
> {
> acpi_status status = AE_OK;
> u32 hci_result;
> + bool bt_present;
> + bool bt_on;
> + bool radio_on;
> + int ret = 0;
>
> if (acpi_disabled)
> return -ENODEV;
> @@ -578,6 +799,18 @@ static int __init toshiba_acpi_init(void)
> TOSHIBA_ACPI_VERSION);
> printk(MY_INFO " HCI method: %s\n", method_hci);
>
> + mutex_init(&toshiba_acpi.mutex);
> +
> + toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi",
> + -1, NULL, 0);
> + if (IS_ERR(toshiba_acpi.p_dev)) {
> + ret = PTR_ERR(toshiba_acpi.p_dev);
> + printk(MY_ERR "unable to register platform device\n");
> + toshiba_acpi.p_dev= NULL;
> + toshiba_acpi_exit();
> + return ret;
> + }
> +
> force_fan = 0;
> key_event_valid = 0;
>
> @@ -586,19 +819,23 @@ static int __init toshiba_acpi_init(void)
>
> toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir);
> if (!toshiba_proc_dir) {
> - status = AE_ERROR;
> + toshiba_acpi_exit();
> + return -ENODEV;
> } else {
> toshiba_proc_dir->owner = THIS_MODULE;
> status = add_device();
> - if (ACPI_FAILURE(status))
> - remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
> + if (ACPI_FAILURE(status)) {
> + toshiba_acpi_exit();
> + return -ENODEV;
> + }
> }
>
> - toshiba_backlight_device = backlight_device_register("toshiba",NULL,
> + toshiba_backlight_device = backlight_device_register("toshiba",
> + &toshiba_acpi.p_dev->dev,
> NULL,
> &toshiba_backlight_data);
> if (IS_ERR(toshiba_backlight_device)) {
> - int ret = PTR_ERR(toshiba_backlight_device);
> + ret = PTR_ERR(toshiba_backlight_device);
>
> printk(KERN_ERR "Could not register toshiba backlight device\n");
> toshiba_backlight_device = NULL;
> @@ -607,7 +844,86 @@ static int __init toshiba_acpi_init(void)
> }
> toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
>
> - return (ACPI_SUCCESS(status)) ? 0 : -ENODEV;
> + /* Register rfkill switch for Bluetooth */
> + if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) {
> + toshiba_acpi.rfk_dev = rfkill_allocate(&toshiba_acpi.p_dev->dev,
> + RFKILL_TYPE_BLUETOOTH);
> + if (!toshiba_acpi.rfk_dev) {
> + printk(MY_ERR "unable to allocate rfkill device\n");
> + toshiba_acpi_exit();
> + return -ENOMEM;
> + }
> +
> + toshiba_acpi.rfk_dev->name = toshiba_acpi.bt_name;
> + toshiba_acpi.rfk_dev->state = RFKILL_STATE_OFF;
> + toshiba_acpi.rfk_dev->toggle_radio = bt_rfkill_toggle_radio;
> + toshiba_acpi.rfk_dev->get_state = bt_rfkill_get_state;
> + toshiba_acpi.rfk_dev->user_claim_unsupported = 1;
> + toshiba_acpi.rfk_dev->data = &toshiba_acpi;
> + toshiba_acpi.rfk_dev->dev.class->suspend = NULL;
> + toshiba_acpi.rfk_dev->dev.class->resume = NULL;
> +
> + ret = rfkill_register(toshiba_acpi.rfk_dev);
> + if (ret) {
> + printk(MY_ERR "unable to register rfkill device\n");
> + toshiba_acpi_exit();
> + return -ENOMEM;
> + }
> +
> + if (hci_get_bt_on(&bt_on) == HCI_SUCCESS && bt_on) {
> + toshiba_acpi.rfk_dev->state = RFKILL_STATE_UNBLOCKED;
> + } else if (hci_get_radio_state(&radio_on) == HCI_SUCCESS && radio_on) {
> + toshiba_acpi.rfk_dev->state = RFKILL_STATE_HARD_BLOCKED;
> + } else {
> + toshiba_acpi.rfk_dev->state = RFKILL_STATE_SOFT_BLOCKED;
> + }
> +
> + /* Register for acpi events on BT ACPI object. */
> + status = acpi_get_handle(NULL, BT_ACPI_OBJECT, &toshiba_acpi.bt_handle);
> + if (ACPI_FAILURE(status)) {
> + printk(MY_ERR "unable to find ACPI Bluetooth object\n");
> + toshiba_acpi.bt_handle = NULL;
> + toshiba_acpi_exit();
> + return -ENODEV;
> + }
> + status = acpi_install_notify_handler(toshiba_acpi.bt_handle,
> + ACPI_ALL_NOTIFY,
> + bt_acpi_notify,
> + &toshiba_acpi);
> + if (ACPI_FAILURE(status)) {
> + printk(MY_ERR "unable to register for events "
> + "on ACPI Bluetooth object\n");
> + toshiba_acpi.bt_handle = NULL;
> + toshiba_acpi_exit();
> + return -ENODEV;
> + }
> + }
> +
> + /* Register input device for kill switch */
> + toshiba_acpi.poll_dev = input_allocate_polled_device();
> + if (!toshiba_acpi.poll_dev) {
> + printk(MY_ERR "unable to allocate kill-switch input device\n");
> + toshiba_acpi_exit();
> + return -ENOMEM;
> + }
> + toshiba_acpi.poll_dev->private = &toshiba_acpi;
> + toshiba_acpi.poll_dev->poll = bt_poll_rfkill;
> + toshiba_acpi.poll_dev->poll_interval = 1000; /* msecs */
> +
> + toshiba_acpi.poll_dev->input->name = toshiba_acpi.bt_name;
> + toshiba_acpi.poll_dev->input->id.bustype = BUS_HOST;
> + toshiba_acpi.poll_dev->input->id.vendor = 0x0930; /* Toshiba USB Vendor ID */
> + toshiba_acpi.poll_dev->input->evbit[0] = BIT(EV_SW);
> + set_bit(SW_RFKILL_ALL, toshiba_acpi.poll_dev->input->keybit);
> +
> + ret = input_register_polled_device(toshiba_acpi.poll_dev);
> + if (ret) {
> + printk(MY_ERR "unable to register kill-switch input device\n");
> + toshiba_acpi_exit();
> + return ret;
> + }
> +
> + return 0;
> }
>
> module_init(toshiba_acpi_init);
>
--
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/