Merge branch 'release-2.6.27' of git://git.kernel.org/pub/scm/linux/kernel/git/ak...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 24 Jul 2008 20:57:37 +0000 (13:57 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 24 Jul 2008 20:57:37 +0000 (13:57 -0700)
* 'release-2.6.27' of git://git.kernel.org/pub/scm/linux/kernel/git/ak/linux-acpi-2.6:
  acpi: fix crash in core ACPI code, triggered by CONFIG_ACPI_PCI_SLOT=y
  ACPI: thinkpad-acpi: don't misdetect in get_thinkpad_model_data() on -ENOMEM
  ACPI: thinkpad-acpi: bump up version to 0.21
  ACPI: thinkpad-acpi: add bluetooth and WWAN rfkill support
  ACPI: thinkpad-acpi: WLSW overrides other rfkill switches
  ACPI: thinkpad-acpi: prepare for bluetooth and wwan rfkill support
  ACPI: thinkpad-acpi: consolidate wlsw notification function
  ACPI: thinkpad-acpi: minor refactor on radio switch init
  Revert "ACPI: don't walk tables if ACPI was disabled"
  Revert "dock: bay: Don't call acpi_walk_namespace() when ACPI is disabled."
  Revert "Fix FADT parsing"
  ACPI : Set FAN device to correct state in boot phase
  ACPI: Ignore _BQC object when registering backlight device
  ACPI: stop complaints about interrupt link End Tags and blank IRQ descriptors

Documentation/laptops/thinkpad-acpi.txt
drivers/acpi/namespace/nsnames.c
drivers/acpi/pci_link.c
drivers/acpi/tables/tbfadt.c
drivers/acpi/thermal.c
drivers/acpi/utilities/utalloc.c
drivers/acpi/video.c
drivers/misc/Kconfig
drivers/misc/thinkpad_acpi.c

index 64b3f14..02dc748 100644 (file)
@@ -1,7 +1,7 @@
                     ThinkPad ACPI Extras Driver
 
-                            Version 0.20
-                          April 09th, 2008
+                            Version 0.21
+                           May 29th, 2008
 
                Borislav Deianov <borislav@users.sf.net>
              Henrique de Moraes Holschuh <hmh@hmh.eng.br>
@@ -621,7 +621,8 @@ Bluetooth
 ---------
 
 procfs: /proc/acpi/ibm/bluetooth
-sysfs device attribute: bluetooth_enable
+sysfs device attribute: bluetooth_enable (deprecated)
+sysfs rfkill class: switch "tpacpi_bluetooth_sw"
 
 This feature shows the presence and current state of a ThinkPad
 Bluetooth device in the internal ThinkPad CDC slot.
@@ -643,8 +644,12 @@ Sysfs notes:
                0: disables Bluetooth / Bluetooth is disabled
                1: enables Bluetooth / Bluetooth is enabled.
 
-       Note: this interface will be probably be superseded by the
-       generic rfkill class, so it is NOT to be considered stable yet.
+       Note: this interface has been superseded by the generic rfkill
+       class.  It has been deprecated, and it will be removed in year
+       2010.
+
+       rfkill controller switch "tpacpi_bluetooth_sw": refer to
+       Documentation/rfkill.txt for details.
 
 Video output control -- /proc/acpi/ibm/video
 --------------------------------------------
@@ -1374,7 +1379,8 @@ EXPERIMENTAL: WAN
 -----------------
 
 procfs: /proc/acpi/ibm/wan
-sysfs device attribute: wwan_enable
+sysfs device attribute: wwan_enable (deprecated)
+sysfs rfkill class: switch "tpacpi_wwan_sw"
 
 This feature is marked EXPERIMENTAL because the implementation
 directly accesses hardware registers and may not work as expected. USE
@@ -1404,8 +1410,12 @@ Sysfs notes:
                0: disables WWAN card / WWAN card is disabled
                1: enables WWAN card / WWAN card is enabled.
 
-       Note: this interface will be probably be superseded by the
-       generic rfkill class, so it is NOT to be considered stable yet.
+       Note: this interface has been superseded by the generic rfkill
+       class.  It has been deprecated, and it will be removed in year
+       2010.
+
+       rfkill controller switch "tpacpi_wwan_sw": refer to
+       Documentation/rfkill.txt for details.
 
 Multiple Commands, Module Parameters
 ------------------------------------
index cffef1b..549db42 100644 (file)
@@ -137,6 +137,10 @@ char *acpi_ns_get_external_pathname(struct acpi_namespace_node *node)
        /* Calculate required buffer size based on depth below root */
 
        size = acpi_ns_get_pathname_length(node);
+       if (!size) {
+               ACPI_ERROR((AE_INFO, "Invalid node failure"));
+               return_PTR(NULL);
+       }
 
        /* Allocate a buffer to be returned to caller */
 
@@ -229,6 +233,10 @@ acpi_ns_handle_to_pathname(acpi_handle target_handle,
        /* Determine size required for the caller buffer */
 
        required_size = acpi_ns_get_pathname_length(node);
+       if (!required_size) {
+               ACPI_ERROR((AE_INFO, "Invalid node failure"));
+               return_ACPI_STATUS(AE_ERROR);
+       }
 
        /* Validate/Allocate/Clear caller buffer */
 
index 233c40c..89f3b2a 100644 (file)
@@ -113,20 +113,23 @@ acpi_pci_link_check_possible(struct acpi_resource *resource, void *context)
 
        switch (resource->type) {
        case ACPI_RESOURCE_TYPE_START_DEPENDENT:
+       case ACPI_RESOURCE_TYPE_END_TAG:
                return AE_OK;
        case ACPI_RESOURCE_TYPE_IRQ:
                {
                        struct acpi_resource_irq *p = &resource->data.irq;
                        if (!p || !p->interrupt_count) {
-                               printk(KERN_WARNING PREFIX "Blank IRQ resource\n");
+                               ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+                                                 "Blank _PRS IRQ resource\n"));
                                return AE_OK;
                        }
                        for (i = 0;
                             (i < p->interrupt_count
                              && i < ACPI_PCI_LINK_MAX_POSSIBLE); i++) {
                                if (!p->interrupts[i]) {
-                                       printk(KERN_WARNING PREFIX "Invalid IRQ %d\n",
-                                                     p->interrupts[i]);
+                                       printk(KERN_WARNING PREFIX
+                                              "Invalid _PRS IRQ %d\n",
+                                              p->interrupts[i]);
                                        continue;
                                }
                                link->irq.possible[i] = p->interrupts[i];
@@ -143,15 +146,16 @@ acpi_pci_link_check_possible(struct acpi_resource *resource, void *context)
                            &resource->data.extended_irq;
                        if (!p || !p->interrupt_count) {
                                printk(KERN_WARNING PREFIX
-                                             "Blank EXT IRQ resource\n");
+                                             "Blank _PRS EXT IRQ resource\n");
                                return AE_OK;
                        }
                        for (i = 0;
                             (i < p->interrupt_count
                              && i < ACPI_PCI_LINK_MAX_POSSIBLE); i++) {
                                if (!p->interrupts[i]) {
-                                       printk(KERN_WARNING PREFIX "Invalid IRQ %d\n",
-                                                     p->interrupts[i]);
+                                       printk(KERN_WARNING PREFIX
+                                              "Invalid _PRS IRQ %d\n",
+                                              p->interrupts[i]);
                                        continue;
                                }
                                link->irq.possible[i] = p->interrupts[i];
@@ -163,7 +167,8 @@ acpi_pci_link_check_possible(struct acpi_resource *resource, void *context)
                        break;
                }
        default:
-               printk(KERN_ERR PREFIX "Resource is not an IRQ entry\n");
+               printk(KERN_ERR PREFIX "_PRS resource type 0x%x isn't an IRQ\n",
+                      resource->type);
                return AE_OK;
        }
 
@@ -199,6 +204,9 @@ acpi_pci_link_check_current(struct acpi_resource *resource, void *context)
 
 
        switch (resource->type) {
+       case ACPI_RESOURCE_TYPE_START_DEPENDENT:
+       case ACPI_RESOURCE_TYPE_END_TAG:
+               return AE_OK;
        case ACPI_RESOURCE_TYPE_IRQ:
                {
                        struct acpi_resource_irq *p = &resource->data.irq;
@@ -208,7 +216,7 @@ acpi_pci_link_check_current(struct acpi_resource *resource, void *context)
                                 * particularly those those w/ _STA disabled
                                 */
                                ACPI_DEBUG_PRINT((ACPI_DB_INFO,
-                                                 "Blank IRQ resource\n"));
+                                                 "Blank _CRS IRQ resource\n"));
                                return AE_OK;
                        }
                        *irq = p->interrupts[0];
@@ -224,7 +232,7 @@ acpi_pci_link_check_current(struct acpi_resource *resource, void *context)
                                 * return at least 1 IRQ
                                 */
                                printk(KERN_WARNING PREFIX
-                                             "Blank EXT IRQ resource\n");
+                                             "Blank _CRS EXT IRQ resource\n");
                                return AE_OK;
                        }
                        *irq = p->interrupts[0];
@@ -232,10 +240,11 @@ acpi_pci_link_check_current(struct acpi_resource *resource, void *context)
                }
                break;
        default:
-               printk(KERN_ERR PREFIX "Resource %d isn't an IRQ\n", resource->type);
-       case ACPI_RESOURCE_TYPE_END_TAG:
+               printk(KERN_ERR PREFIX "_CRS resource type 0x%x isn't an IRQ\n",
+                      resource->type);
                return AE_OK;
        }
+
        return AE_CTRL_TERMINATE;
 }
 
index ccb5b64..a4a41ba 100644 (file)
@@ -124,7 +124,7 @@ static struct acpi_fadt_info fadt_info_table[] = {
 
 static void inline
 acpi_tb_init_generic_address(struct acpi_generic_address *generic_address,
-                            u8 byte_width, u64 address)
+                            u8 bit_width, u64 address)
 {
 
        /*
@@ -136,7 +136,7 @@ acpi_tb_init_generic_address(struct acpi_generic_address *generic_address,
        /* All other fields are byte-wide */
 
        generic_address->space_id = ACPI_ADR_SPACE_SYSTEM_IO;
-       generic_address->bit_width = byte_width << 3;
+       generic_address->bit_width = bit_width;
        generic_address->bit_offset = 0;
        generic_address->access_width = 0;
 }
@@ -343,11 +343,9 @@ static void acpi_tb_convert_fadt(void)
         *
         * The PM event blocks are split into two register blocks, first is the
         * PM Status Register block, followed immediately by the PM Enable Register
-        * block. Each is of length (xpm1x_event_block.bit_width/2)
+        * block. Each is of length (pm1_event_length/2)
         */
-       WARN_ON(ACPI_MOD_16(acpi_gbl_FADT.xpm1a_event_block.bit_width));
-       pm1_register_length = (u8) ACPI_DIV_16(acpi_gbl_FADT
-                                              .xpm1a_event_block.bit_width);
+       pm1_register_length = (u8) ACPI_DIV_2(acpi_gbl_FADT.pm1_event_length);
 
        /* The PM1A register block is required */
 
@@ -362,17 +360,14 @@ static void acpi_tb_convert_fadt(void)
        /* The PM1B register block is optional, ignore if not present */
 
        if (acpi_gbl_FADT.xpm1b_event_block.address) {
-               WARN_ON(ACPI_MOD_16(acpi_gbl_FADT.xpm1b_event_block.bit_width));
-               pm1_register_length = (u8) ACPI_DIV_16(acpi_gbl_FADT
-                                                      .xpm1b_event_block
-                                                      .bit_width);
                acpi_tb_init_generic_address(&acpi_gbl_xpm1b_enable,
                                             pm1_register_length,
                                             (acpi_gbl_FADT.xpm1b_event_block.
                                              address + pm1_register_length));
                /* Don't forget to copy space_id of the GAS */
                acpi_gbl_xpm1b_enable.space_id =
-                   acpi_gbl_FADT.xpm1b_event_block.space_id;
+                   acpi_gbl_FADT.xpm1a_event_block.space_id;
+
        }
 }
 
index 30a3413..9127036 100644 (file)
@@ -769,6 +769,47 @@ static void acpi_thermal_run(unsigned long data)
                acpi_os_execute(OSL_GPE_HANDLER, acpi_thermal_check, (void *)data);
 }
 
+static void acpi_thermal_active_off(void *data)
+{
+       int result = 0;
+       struct acpi_thermal *tz = data;
+       int i = 0;
+       int j = 0;
+       struct acpi_thermal_active *active = NULL;
+
+       if (!tz) {
+               printk(KERN_ERR PREFIX "Invalid (NULL) context\n");
+               return;
+       }
+
+       result = acpi_thermal_get_temperature(tz);
+       if (result)
+               return;
+
+       for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
+               active = &(tz->trips.active[i]);
+               if (!active || !active->flags.valid)
+                       break;
+               if (tz->temperature >= active->temperature) {
+                       /*
+                        * If the thermal temperature is greater than the
+                        * active threshod, unnecessary to turn off the
+                        * the active cooling device.
+                        */
+                       continue;
+               }
+               /*
+                * Below Threshold?
+                * ----------------
+                * Turn OFF all cooling devices associated with this
+                * threshold.
+                */
+               for (j = 0; j < active->devices.count; j++)
+                       result = acpi_bus_set_power(active->devices.handles[j],
+                                                   ACPI_STATE_D3);
+       }
+}
+
 static void acpi_thermal_check(void *data)
 {
        int result = 0;
@@ -1624,6 +1665,8 @@ static int acpi_thermal_add(struct acpi_device *device)
 
        init_timer(&tz->timer);
 
+       acpi_thermal_active_off(tz);
+
        acpi_thermal_check(tz);
 
        status = acpi_install_notify_handler(device->handle,
index 3dfb8a4..e7bf34a 100644 (file)
@@ -242,6 +242,10 @@ acpi_ut_initialize_buffer(struct acpi_buffer * buffer,
 {
        acpi_status status = AE_OK;
 
+       if (!required_length) {
+               WARN_ON(1);
+               return AE_ERROR;
+       }
        switch (buffer->length) {
        case ACPI_NO_BUFFER:
 
index 37b9e16..e8a51a1 100644 (file)
@@ -741,7 +741,7 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device)
 
        max_level = acpi_video_init_brightness(device);
 
-       if (device->cap._BCL && device->cap._BCM && device->cap._BQC && max_level > 0){
+       if (device->cap._BCL && device->cap._BCM && max_level > 0) {
                int result;
                static int count = 0;
                char *name;
@@ -753,7 +753,17 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device)
                device->backlight = backlight_device_register(name,
                        NULL, device, &acpi_backlight_ops);
                device->backlight->props.max_brightness = device->brightness->count-3;
-               device->backlight->props.brightness = acpi_video_get_brightness(device->backlight);
+               /*
+                * If there exists the _BQC object, the _BQC object will be
+                * called to get the current backlight brightness. Otherwise
+                * the brightness will be set to the maximum.
+                */
+               if (device->cap._BQC)
+                       device->backlight->props.brightness =
+                               acpi_video_get_brightness(device->backlight);
+               else
+                       device->backlight->props.brightness =
+                               device->backlight->props.max_brightness;
                backlight_update_status(device->backlight);
                kfree(name);
 
index ce67d97..d5bc288 100644 (file)
@@ -279,6 +279,8 @@ config THINKPAD_ACPI
        select INPUT
        select NEW_LEDS
        select LEDS_CLASS
+       select NET
+       select RFKILL
        ---help---
          This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
          support for Fn-Fx key combinations, Bluetooth control, video
index b596929..d3eb790 100644 (file)
@@ -21,7 +21,7 @@
  *  02110-1301, USA.
  */
 
-#define TPACPI_VERSION "0.20"
+#define TPACPI_VERSION "0.21"
 #define TPACPI_SYSFS_VERSION 0x020200
 
 /*
@@ -68,6 +68,7 @@
 #include <linux/hwmon-sysfs.h>
 #include <linux/input.h>
 #include <linux/leds.h>
+#include <linux/rfkill.h>
 #include <asm/uaccess.h>
 
 #include <linux/dmi.h>
@@ -144,6 +145,12 @@ enum {
 
 #define TPACPI_MAX_ACPI_ARGS 3
 
+/* rfkill switches */
+enum {
+       TPACPI_RFK_BLUETOOTH_SW_ID = 0,
+       TPACPI_RFK_WWAN_SW_ID,
+};
+
 /* Debugging */
 #define TPACPI_LOG TPACPI_FILE ": "
 #define TPACPI_ERR        KERN_ERR    TPACPI_LOG
@@ -905,6 +912,43 @@ static int __init tpacpi_check_std_acpi_brightness_support(void)
        return 0;
 }
 
+static int __init tpacpi_new_rfkill(const unsigned int id,
+                       struct rfkill **rfk,
+                       const enum rfkill_type rfktype,
+                       const char *name,
+                       int (*toggle_radio)(void *, enum rfkill_state),
+                       int (*get_state)(void *, enum rfkill_state *))
+{
+       int res;
+       enum rfkill_state initial_state;
+
+       *rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype);
+       if (!*rfk) {
+               printk(TPACPI_ERR
+                       "failed to allocate memory for rfkill class\n");
+               return -ENOMEM;
+       }
+
+       (*rfk)->name = name;
+       (*rfk)->get_state = get_state;
+       (*rfk)->toggle_radio = toggle_radio;
+
+       if (!get_state(NULL, &initial_state))
+               (*rfk)->state = initial_state;
+
+       res = rfkill_register(*rfk);
+       if (res < 0) {
+               printk(TPACPI_ERR
+                       "failed to register %s rfkill switch: %d\n",
+                       name, res);
+               rfkill_free(*rfk);
+               *rfk = NULL;
+               return res;
+       }
+
+       return 0;
+}
+
 /*************************************************************************
  * thinkpad-acpi driver attributes
  */
@@ -1285,21 +1329,6 @@ static int hotkey_status_set(int status)
        return 0;
 }
 
-static void tpacpi_input_send_radiosw(void)
-{
-       int wlsw;
-
-       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
-               mutex_lock(&tpacpi_inputdev_send_mutex);
-
-               input_report_switch(tpacpi_inputdev,
-                                   SW_RFKILL_ALL, !!wlsw);
-               input_sync(tpacpi_inputdev);
-
-               mutex_unlock(&tpacpi_inputdev_send_mutex);
-       }
-}
-
 static void tpacpi_input_send_tabletsw(void)
 {
        int state;
@@ -1921,6 +1950,30 @@ static struct attribute *hotkey_mask_attributes[] __initdata = {
        &dev_attr_hotkey_wakeup_hotunplug_complete.attr,
 };
 
+static void bluetooth_update_rfk(void);
+static void wan_update_rfk(void);
+static void tpacpi_send_radiosw_update(void)
+{
+       int wlsw;
+
+       /* Sync these BEFORE sending any rfkill events */
+       if (tp_features.bluetooth)
+               bluetooth_update_rfk();
+       if (tp_features.wan)
+               wan_update_rfk();
+
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
+               mutex_lock(&tpacpi_inputdev_send_mutex);
+
+               input_report_switch(tpacpi_inputdev,
+                                   SW_RFKILL_ALL, !!wlsw);
+               input_sync(tpacpi_inputdev);
+
+               mutex_unlock(&tpacpi_inputdev_send_mutex);
+       }
+       hotkey_radio_sw_notify_change();
+}
+
 static void hotkey_exit(void)
 {
 #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
@@ -2167,9 +2220,10 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
                printk(TPACPI_INFO
                        "radio switch found; radios are %s\n",
                        enabled(status, 0));
+       }
+       if (tp_features.hotkey_wlsw)
                res = add_to_attr_set(hotkey_dev_attributes,
                                &dev_attr_hotkey_radio_sw.attr);
-       }
 
        /* For X41t, X60t, X61t Tablets... */
        if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
@@ -2287,7 +2341,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        tpacpi_inputdev->close = &hotkey_inputdev_close;
 
        hotkey_poll_setup_safe(1);
-       tpacpi_input_send_radiosw();
+       tpacpi_send_radiosw_update();
        tpacpi_input_send_tabletsw();
 
        return 0;
@@ -2419,8 +2473,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
                case 7:
                        /* 0x7000-0x7FFF: misc */
                        if (tp_features.hotkey_wlsw && hkey == 0x7000) {
-                               tpacpi_input_send_radiosw();
-                               hotkey_radio_sw_notify_change();
+                               tpacpi_send_radiosw_update();
                                send_acpi_ev = 0;
                                break;
                        }
@@ -2463,8 +2516,7 @@ static void hotkey_resume(void)
                printk(TPACPI_ERR
                       "error while trying to read hot key mask "
                       "from firmware\n");
-       tpacpi_input_send_radiosw();
-       hotkey_radio_sw_notify_change();
+       tpacpi_send_radiosw_update();
        hotkey_tablet_mode_notify_change();
        hotkey_wakeup_reason_notify_change();
        hotkey_wakeup_hotunplug_complete_notify_change();
@@ -2581,8 +2633,66 @@ enum {
        TP_ACPI_BLUETOOTH_UNK           = 0x04, /* unknown function */
 };
 
-static int bluetooth_get_radiosw(void);
-static int bluetooth_set_radiosw(int radio_on);
+static struct rfkill *tpacpi_bluetooth_rfkill;
+
+static int bluetooth_get_radiosw(void)
+{
+       int status;
+
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+
+       /* WLSW overrides bluetooth in firmware/hardware, reflect that */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
+               return RFKILL_STATE_HARD_BLOCKED;
+
+       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+               return -EIO;
+
+       return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
+               RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
+}
+
+static void bluetooth_update_rfk(void)
+{
+       int status;
+
+       if (!tpacpi_bluetooth_rfkill)
+               return;
+
+       status = bluetooth_get_radiosw();
+       if (status < 0)
+               return;
+       rfkill_force_state(tpacpi_bluetooth_rfkill, status);
+}
+
+static int bluetooth_set_radiosw(int radio_on, int update_rfk)
+{
+       int status;
+
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+
+       /* WLSW overrides bluetooth in firmware/hardware, but there is no
+        * reason to risk weird behaviour. */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
+           && radio_on)
+               return -EPERM;
+
+       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+               return -EIO;
+       if (radio_on)
+               status |= TP_ACPI_BLUETOOTH_RADIOSSW;
+       else
+               status &= ~TP_ACPI_BLUETOOTH_RADIOSSW;
+       if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
+               return -EIO;
+
+       if (update_rfk)
+               bluetooth_update_rfk();
+
+       return 0;
+}
 
 /* sysfs bluetooth enable ---------------------------------------------- */
 static ssize_t bluetooth_enable_show(struct device *dev,
@@ -2595,7 +2705,8 @@ static ssize_t bluetooth_enable_show(struct device *dev,
        if (status < 0)
                return status;
 
-       return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
+       return snprintf(buf, PAGE_SIZE, "%d\n",
+                       (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0);
 }
 
 static ssize_t bluetooth_enable_store(struct device *dev,
@@ -2608,7 +2719,7 @@ static ssize_t bluetooth_enable_store(struct device *dev,
        if (parse_strtoul(buf, 1, &t))
                return -EINVAL;
 
-       res = bluetooth_set_radiosw(t);
+       res = bluetooth_set_radiosw(t, 1);
 
        return (res) ? res : count;
 }
@@ -2628,6 +2739,31 @@ static const struct attribute_group bluetooth_attr_group = {
        .attrs = bluetooth_attributes,
 };
 
+static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state)
+{
+       int bts = bluetooth_get_radiosw();
+
+       if (bts < 0)
+               return bts;
+
+       *state = bts;
+       return 0;
+}
+
+static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state)
+{
+       return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
+}
+
+static void bluetooth_exit(void)
+{
+       if (tpacpi_bluetooth_rfkill)
+               rfkill_unregister(tpacpi_bluetooth_rfkill);
+
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+                       &bluetooth_attr_group);
+}
+
 static int __init bluetooth_init(struct ibm_init_struct *iibm)
 {
        int res;
@@ -2646,57 +2782,32 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
                str_supported(tp_features.bluetooth),
                status);
 
-       if (tp_features.bluetooth) {
-               if (!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
-                       /* no bluetooth hardware present in system */
-                       tp_features.bluetooth = 0;
-                       dbg_printk(TPACPI_DBG_INIT,
-                                  "bluetooth hardware not installed\n");
-               } else {
-                       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
-                                       &bluetooth_attr_group);
-                       if (res)
-                               return res;
-               }
+       if (tp_features.bluetooth &&
+           !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
+               /* no bluetooth hardware present in system */
+               tp_features.bluetooth = 0;
+               dbg_printk(TPACPI_DBG_INIT,
+                          "bluetooth hardware not installed\n");
        }
 
-       return (tp_features.bluetooth)? 0 : 1;
-}
-
-static void bluetooth_exit(void)
-{
-       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
-                       &bluetooth_attr_group);
-}
-
-static int bluetooth_get_radiosw(void)
-{
-       int status;
-
        if (!tp_features.bluetooth)
-               return -ENODEV;
-
-       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
-               return -EIO;
-
-       return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0);
-}
-
-static int bluetooth_set_radiosw(int radio_on)
-{
-       int status;
+               return 1;
 
-       if (!tp_features.bluetooth)
-               return -ENODEV;
+       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                               &bluetooth_attr_group);
+       if (res)
+               return res;
 
-       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
-               return -EIO;
-       if (radio_on)
-               status |= TP_ACPI_BLUETOOTH_RADIOSSW;
-       else
-               status &= ~TP_ACPI_BLUETOOTH_RADIOSSW;
-       if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
-               return -EIO;
+       res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
+                               &tpacpi_bluetooth_rfkill,
+                               RFKILL_TYPE_BLUETOOTH,
+                               "tpacpi_bluetooth_sw",
+                               tpacpi_bluetooth_rfk_set,
+                               tpacpi_bluetooth_rfk_get);
+       if (res) {
+               bluetooth_exit();
+               return res;
+       }
 
        return 0;
 }
@@ -2711,7 +2822,8 @@ static int bluetooth_read(char *p)
                len += sprintf(p + len, "status:\t\tnot supported\n");
        else {
                len += sprintf(p + len, "status:\t\t%s\n",
-                               (status)? "enabled" : "disabled");
+                               (status == RFKILL_STATE_UNBLOCKED) ?
+                                       "enabled" : "disabled");
                len += sprintf(p + len, "commands:\tenable, disable\n");
        }
 
@@ -2727,9 +2839,9 @@ static int bluetooth_write(char *buf)
 
        while ((cmd = next_cmd(&buf))) {
                if (strlencmp(cmd, "enable") == 0) {
-                       bluetooth_set_radiosw(1);
+                       bluetooth_set_radiosw(1, 1);
                } else if (strlencmp(cmd, "disable") == 0) {
-                       bluetooth_set_radiosw(0);
+                       bluetooth_set_radiosw(0, 1);
                } else
                        return -EINVAL;
        }
@@ -2755,8 +2867,66 @@ enum {
        TP_ACPI_WANCARD_UNK             = 0x04, /* unknown function */
 };
 
-static int wan_get_radiosw(void);
-static int wan_set_radiosw(int radio_on);
+static struct rfkill *tpacpi_wan_rfkill;
+
+static int wan_get_radiosw(void)
+{
+       int status;
+
+       if (!tp_features.wan)
+               return -ENODEV;
+
+       /* WLSW overrides WWAN in firmware/hardware, reflect that */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
+               return RFKILL_STATE_HARD_BLOCKED;
+
+       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+               return -EIO;
+
+       return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
+               RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
+}
+
+static void wan_update_rfk(void)
+{
+       int status;
+
+       if (!tpacpi_wan_rfkill)
+               return;
+
+       status = wan_get_radiosw();
+       if (status < 0)
+               return;
+       rfkill_force_state(tpacpi_wan_rfkill, status);
+}
+
+static int wan_set_radiosw(int radio_on, int update_rfk)
+{
+       int status;
+
+       if (!tp_features.wan)
+               return -ENODEV;
+
+       /* WLSW overrides bluetooth in firmware/hardware, but there is no
+        * reason to risk weird behaviour. */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
+           && radio_on)
+               return -EPERM;
+
+       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+               return -EIO;
+       if (radio_on)
+               status |= TP_ACPI_WANCARD_RADIOSSW;
+       else
+               status &= ~TP_ACPI_WANCARD_RADIOSSW;
+       if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
+               return -EIO;
+
+       if (update_rfk)
+               wan_update_rfk();
+
+       return 0;
+}
 
 /* sysfs wan enable ---------------------------------------------------- */
 static ssize_t wan_enable_show(struct device *dev,
@@ -2769,7 +2939,8 @@ static ssize_t wan_enable_show(struct device *dev,
        if (status < 0)
                return status;
 
-       return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
+       return snprintf(buf, PAGE_SIZE, "%d\n",
+                       (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0);
 }
 
 static ssize_t wan_enable_store(struct device *dev,
@@ -2782,7 +2953,7 @@ static ssize_t wan_enable_store(struct device *dev,
        if (parse_strtoul(buf, 1, &t))
                return -EINVAL;
 
-       res = wan_set_radiosw(t);
+       res = wan_set_radiosw(t, 1);
 
        return (res) ? res : count;
 }
@@ -2802,6 +2973,31 @@ static const struct attribute_group wan_attr_group = {
        .attrs = wan_attributes,
 };
 
+static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state)
+{
+       int wans = wan_get_radiosw();
+
+       if (wans < 0)
+               return wans;
+
+       *state = wans;
+       return 0;
+}
+
+static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state)
+{
+       return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
+}
+
+static void wan_exit(void)
+{
+       if (tpacpi_wan_rfkill)
+               rfkill_unregister(tpacpi_wan_rfkill);
+
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+               &wan_attr_group);
+}
+
 static int __init wan_init(struct ibm_init_struct *iibm)
 {
        int res;
@@ -2818,57 +3014,32 @@ static int __init wan_init(struct ibm_init_struct *iibm)
                str_supported(tp_features.wan),
                status);
 
-       if (tp_features.wan) {
-               if (!(status & TP_ACPI_WANCARD_HWPRESENT)) {
-                       /* no wan hardware present in system */
-                       tp_features.wan = 0;
-                       dbg_printk(TPACPI_DBG_INIT,
-                                  "wan hardware not installed\n");
-               } else {
-                       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
-                                       &wan_attr_group);
-                       if (res)
-                               return res;
-               }
+       if (tp_features.wan &&
+           !(status & TP_ACPI_WANCARD_HWPRESENT)) {
+               /* no wan hardware present in system */
+               tp_features.wan = 0;
+               dbg_printk(TPACPI_DBG_INIT,
+                          "wan hardware not installed\n");
        }
 
-       return (tp_features.wan)? 0 : 1;
-}
-
-static void wan_exit(void)
-{
-       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
-               &wan_attr_group);
-}
-
-static int wan_get_radiosw(void)
-{
-       int status;
-
        if (!tp_features.wan)
-               return -ENODEV;
-
-       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
-               return -EIO;
-
-       return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0);
-}
-
-static int wan_set_radiosw(int radio_on)
-{
-       int status;
+               return 1;
 
-       if (!tp_features.wan)
-               return -ENODEV;
+       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                               &wan_attr_group);
+       if (res)
+               return res;
 
-       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
-               return -EIO;
-       if (radio_on)
-               status |= TP_ACPI_WANCARD_RADIOSSW;
-       else
-               status &= ~TP_ACPI_WANCARD_RADIOSSW;
-       if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
-               return -EIO;
+       res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
+                               &tpacpi_wan_rfkill,
+                               RFKILL_TYPE_WWAN,
+                               "tpacpi_wwan_sw",
+                               tpacpi_wan_rfk_set,
+                               tpacpi_wan_rfk_get);
+       if (res) {
+               wan_exit();
+               return res;
+       }
 
        return 0;
 }
@@ -2883,7 +3054,8 @@ static int wan_read(char *p)
                len += sprintf(p + len, "status:\t\tnot supported\n");
        else {
                len += sprintf(p + len, "status:\t\t%s\n",
-                               (status)? "enabled" : "disabled");
+                               (status == RFKILL_STATE_UNBLOCKED) ?
+                                       "enabled" : "disabled");
                len += sprintf(p + len, "commands:\tenable, disable\n");
        }
 
@@ -2899,9 +3071,9 @@ static int wan_write(char *buf)
 
        while ((cmd = next_cmd(&buf))) {
                if (strlencmp(cmd, "enable") == 0) {
-                       wan_set_radiosw(1);
+                       wan_set_radiosw(1, 1);
                } else if (strlencmp(cmd, "disable") == 0) {
-                       wan_set_radiosw(0);
+                       wan_set_radiosw(0, 1);
                } else
                        return -EINVAL;
        }
@@ -6168,13 +6340,18 @@ err_out:
 
 /* Probing */
 
-static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp)
+/* returns 0 - probe ok, or < 0 - probe error.
+ * Probe ok doesn't mean thinkpad found.
+ * On error, kfree() cleanup on tp->* is not performed, caller must do it */
+static int __must_check __init get_thinkpad_model_data(
+                                               struct thinkpad_id_data *tp)
 {
        const struct dmi_device *dev = NULL;
        char ec_fw_string[18];
+       char const *s;
 
        if (!tp)
-               return;
+               return -EINVAL;
 
        memset(tp, 0, sizeof(*tp));
 
@@ -6183,12 +6360,14 @@ static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp)
        else if (dmi_name_in_vendors("LENOVO"))
                tp->vendor = PCI_VENDOR_ID_LENOVO;
        else
-               return;
+               return 0;
 
-       tp->bios_version_str = kstrdup(dmi_get_system_info(DMI_BIOS_VERSION),
-                                       GFP_KERNEL);
+       s = dmi_get_system_info(DMI_BIOS_VERSION);
+       tp->bios_version_str = kstrdup(s, GFP_KERNEL);
+       if (s && !tp->bios_version_str)
+               return -ENOMEM;
        if (!tp->bios_version_str)
-               return;
+               return 0;
        tp->bios_model = tp->bios_version_str[0]
                         | (tp->bios_version_str[1] << 8);
 
@@ -6207,21 +6386,27 @@ static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp)
                        ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
 
                        tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
+                       if (!tp->ec_version_str)
+                               return -ENOMEM;
                        tp->ec_model = ec_fw_string[0]
                                        | (ec_fw_string[1] << 8);
                        break;
                }
        }
 
-       tp->model_str = kstrdup(dmi_get_system_info(DMI_PRODUCT_VERSION),
-                                       GFP_KERNEL);
-       if (tp->model_str && strnicmp(tp->model_str, "ThinkPad", 8) != 0) {
-               kfree(tp->model_str);
-               tp->model_str = NULL;
+       s = dmi_get_system_info(DMI_PRODUCT_VERSION);
+       if (s && !strnicmp(s, "ThinkPad", 8)) {
+               tp->model_str = kstrdup(s, GFP_KERNEL);
+               if (!tp->model_str)
+                       return -ENOMEM;
        }
 
-       tp->nummodel_str = kstrdup(dmi_get_system_info(DMI_PRODUCT_NAME),
-                                       GFP_KERNEL);
+       s = dmi_get_system_info(DMI_PRODUCT_NAME);
+       tp->nummodel_str = kstrdup(s, GFP_KERNEL);
+       if (s && !tp->nummodel_str)
+               return -ENOMEM;
+
+       return 0;
 }
 
 static int __init probe_for_thinkpad(void)
@@ -6484,7 +6669,13 @@ static int __init thinkpad_acpi_module_init(void)
 
        /* Driver-level probe */
 
-       get_thinkpad_model_data(&thinkpad_id);
+       ret = get_thinkpad_model_data(&thinkpad_id);
+       if (ret) {
+               printk(TPACPI_ERR
+                       "unable to get DMI data: %d\n", ret);
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
        ret = probe_for_thinkpad();
        if (ret) {
                thinkpad_acpi_module_exit();