diff options
Diffstat (limited to 'drivers/platform/x86/sony-laptop.c')
-rw-r--r-- | drivers/platform/x86/sony-laptop.c | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index f6cdc8929fd..f458870c30b 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -64,6 +64,7 @@ #include <asm/uaccess.h> #include <linux/sonypi.h> #include <linux/sony-laptop.h> +#include <linux/rfkill.h> #ifdef CONFIG_SONYPI_COMPAT #include <linux/poll.h> #include <linux/miscdevice.h> @@ -123,6 +124,18 @@ MODULE_PARM_DESC(minor, "default is -1 (automatic)"); #endif +enum sony_nc_rfkill { + SONY_WIFI, + SONY_BLUETOOTH, + SONY_WWAN, + SONY_WIMAX, + SONY_RFKILL_MAX, +}; + +static struct rfkill *sony_rfkill_devices[SONY_RFKILL_MAX]; +static int sony_rfkill_address[SONY_RFKILL_MAX] = {0x300, 0x500, 0x700, 0x900}; +static void sony_nc_rfkill_update(void); + /*********** Input Devices ***********/ #define SONY_LAPTOP_BUF_SIZE 128 @@ -134,6 +147,7 @@ struct sony_laptop_input_s { spinlock_t fifo_lock; struct workqueue_struct *wq; }; + static struct sony_laptop_input_s sony_laptop_input = { .users = ATOMIC_INIT(0), }; @@ -891,6 +905,9 @@ static void sony_acpi_notify(acpi_handle handle, u32 event, void *data) if (!sony_nc_events[i].data) printk(KERN_INFO DRV_PFX "Unknown event: %x %x\n", origev, ev); + } else if (sony_find_snc_handle(0x124) == ev) { + sony_nc_rfkill_update(); + return; } } @@ -973,6 +990,172 @@ static int sony_nc_resume(struct acpi_device *device) return 0; } +static void sony_nc_rfkill_cleanup(void) +{ + int i; + + for (i = 0; i < SONY_RFKILL_MAX; i++) { + if (sony_rfkill_devices[i]) + rfkill_unregister(sony_rfkill_devices[i]); + } +} + +static int sony_nc_rfkill_get(void *data, enum rfkill_state *state) +{ + int result; + int argument = sony_rfkill_address[(long) data]; + + sony_call_snc_handle(0x124, 0x200, &result); + if (result & 0x1) { + sony_call_snc_handle(0x124, argument, &result); + if (result & 0xf) + *state = RFKILL_STATE_UNBLOCKED; + else + *state = RFKILL_STATE_SOFT_BLOCKED; + } else { + *state = RFKILL_STATE_HARD_BLOCKED; + } + + return 0; +} + +static int sony_nc_rfkill_set(void *data, enum rfkill_state state) +{ + int result; + int argument = sony_rfkill_address[(long) data] + 0x100; + + if (state == RFKILL_STATE_UNBLOCKED) + argument |= 0xff0000; + + return sony_call_snc_handle(0x124, argument, &result); +} + +static int sony_nc_setup_wifi_rfkill(struct acpi_device *device) +{ + int err = 0; + struct rfkill *sony_wifi_rfkill; + + sony_wifi_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WLAN); + if (!sony_wifi_rfkill) + return -1; + sony_wifi_rfkill->name = "sony-wifi"; + sony_wifi_rfkill->toggle_radio = sony_nc_rfkill_set; + sony_wifi_rfkill->get_state = sony_nc_rfkill_get; + sony_wifi_rfkill->user_claim_unsupported = 1; + sony_wifi_rfkill->data = (void *)SONY_WIFI; + err = rfkill_register(sony_wifi_rfkill); + if (err) + rfkill_free(sony_wifi_rfkill); + else + sony_rfkill_devices[SONY_WIFI] = sony_wifi_rfkill; + return err; +} + +static int sony_nc_setup_bluetooth_rfkill(struct acpi_device *device) +{ + int err = 0; + struct rfkill *sony_bluetooth_rfkill; + + sony_bluetooth_rfkill = rfkill_allocate(&device->dev, + RFKILL_TYPE_BLUETOOTH); + if (!sony_bluetooth_rfkill) + return -1; + sony_bluetooth_rfkill->name = "sony-bluetooth"; + sony_bluetooth_rfkill->toggle_radio = sony_nc_rfkill_set; + sony_bluetooth_rfkill->get_state = sony_nc_rfkill_get; + sony_bluetooth_rfkill->user_claim_unsupported = 1; + sony_bluetooth_rfkill->data = (void *)SONY_BLUETOOTH; + err = rfkill_register(sony_bluetooth_rfkill); + if (err) + rfkill_free(sony_bluetooth_rfkill); + else + sony_rfkill_devices[SONY_BLUETOOTH] = sony_bluetooth_rfkill; + return err; +} + +static int sony_nc_setup_wwan_rfkill(struct acpi_device *device) +{ + int err = 0; + struct rfkill *sony_wwan_rfkill; + + sony_wwan_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WWAN); + if (!sony_wwan_rfkill) + return -1; + sony_wwan_rfkill->name = "sony-wwan"; + sony_wwan_rfkill->toggle_radio = sony_nc_rfkill_set; + sony_wwan_rfkill->get_state = sony_nc_rfkill_get; + sony_wwan_rfkill->user_claim_unsupported = 1; + sony_wwan_rfkill->data = (void *)SONY_WWAN; + err = rfkill_register(sony_wwan_rfkill); + if (err) + rfkill_free(sony_wwan_rfkill); + else + sony_rfkill_devices[SONY_WWAN] = sony_wwan_rfkill; + return err; +} + +static int sony_nc_setup_wimax_rfkill(struct acpi_device *device) +{ + int err = 0; + struct rfkill *sony_wimax_rfkill; + + sony_wimax_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WIMAX); + if (!sony_wimax_rfkill) + return -1; + sony_wimax_rfkill->name = "sony-wimax"; + sony_wimax_rfkill->toggle_radio = sony_nc_rfkill_set; + sony_wimax_rfkill->get_state = sony_nc_rfkill_get; + sony_wimax_rfkill->user_claim_unsupported = 1; + sony_wimax_rfkill->data = (void *)SONY_WIMAX; + err = rfkill_register(sony_wimax_rfkill); + if (err) + rfkill_free(sony_wimax_rfkill); + else + sony_rfkill_devices[SONY_WIMAX] = sony_wimax_rfkill; + return err; +} + +static void sony_nc_rfkill_update() +{ + int i; + enum rfkill_state state; + + for (i = 0; i < SONY_RFKILL_MAX; i++) { + if (sony_rfkill_devices[i]) { + sony_rfkill_devices[i]-> + get_state(sony_rfkill_devices[i]->data, + &state); + rfkill_force_state(sony_rfkill_devices[i], state); + } + } +} + +static int sony_nc_rfkill_setup(struct acpi_device *device) +{ + int result, ret; + + if (sony_find_snc_handle(0x124) == -1) + return -1; + + ret = sony_call_snc_handle(0x124, 0xb00, &result); + if (ret) { + printk(KERN_INFO DRV_PFX + "Unable to enumerate rfkill devices: %x\n", ret); + return ret; + } + + if (result & 0x1) + sony_nc_setup_wifi_rfkill(device); + if (result & 0x2) + sony_nc_setup_bluetooth_rfkill(device); + if (result & 0x1c) + sony_nc_setup_wwan_rfkill(device); + if (result & 0x20) + sony_nc_setup_wimax_rfkill(device); + + return 0; +} + static int sony_nc_add(struct acpi_device *device) { acpi_status status; @@ -1026,6 +1209,7 @@ static int sony_nc_add(struct acpi_device *device) &handle))) { dprintk("Doing SNC setup\n"); sony_nc_function_setup(device); + sony_nc_rfkill_setup(device); } /* setup input devices and helper fifo */ @@ -1132,6 +1316,7 @@ static int sony_nc_add(struct acpi_device *device) sony_laptop_remove_input(); outwalk: + sony_nc_rfkill_cleanup(); return result; } @@ -1157,6 +1342,7 @@ static int sony_nc_remove(struct acpi_device *device, int type) sony_pf_remove(); sony_laptop_remove_input(); + sony_nc_rfkill_cleanup(); dprintk(SONY_NC_DRIVER_NAME " removed.\n"); return 0; |