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-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html