diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 66 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/asus-laptop.c | 151 | ||||
-rw-r--r-- | drivers/misc/sony-laptop.c | 2131 | ||||
-rw-r--r-- | drivers/misc/thinkpad_acpi.c | 4312 | ||||
-rw-r--r-- | drivers/misc/thinkpad_acpi.h | 572 |
6 files changed, 6970 insertions, 263 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 80b199fa0aa9..a3c525b2616a 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -112,14 +112,70 @@ config SONY_LAPTOP depends on X86 && ACPI select BACKLIGHT_CLASS_DEVICE ---help--- - This mini-driver drives the SNC device present in the ACPI BIOS of - the Sony Vaio laptops. + This mini-driver drives the SNC and SPIC devices present in the ACPI + BIOS of the Sony Vaio laptops. - It gives access to some extra laptop functionalities. In its current - form, this driver let the user set or query the screen brightness - through the backlight subsystem and remove/apply power to some + It gives access to some extra laptop functionalities like Bluetooth, + screen brightness control, Fn keys and allows powering on/off some devices. Read <file:Documentation/sony-laptop.txt> for more information. +config SONY_LAPTOP_OLD + bool "Sonypi compatibility" + depends on SONY_LAPTOP + ---help--- + Build the sonypi driver compatibility code into the sony-laptop driver. + +config THINKPAD_ACPI + tristate "ThinkPad ACPI Laptop Extras" + depends on X86 && ACPI + select BACKLIGHT_CLASS_DEVICE + select HWMON + ---help--- + This is a driver for the IBM and Lenovo ThinkPad laptops. It adds + support for Fn-Fx key combinations, Bluetooth control, video + output switching, ThinkLight control, UltraBay eject and more. + For more information about this driver see + <file:Documentation/thinkpad-acpi.txt> and <http://ibm-acpi.sf.net/> . + + This driver was formely known as ibm-acpi. + + If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. + +config THINKPAD_ACPI_DEBUG + bool "Verbose debug mode" + depends on THINKPAD_ACPI + default n + ---help--- + Enables extra debugging information, at the expense of a slightly + increase in driver size. + + If you are not sure, say N here. + +config THINKPAD_ACPI_DOCK + bool "Legacy Docking Station Support" + depends on THINKPAD_ACPI + depends on ACPI_DOCK=n + default n + ---help--- + Allows the thinkpad_acpi driver to handle docking station events. + This support was made obsolete by the generic ACPI docking station + support (CONFIG_ACPI_DOCK). It will allow locking and removing the + laptop from the docking station, but will not properly connect PCI + devices. + + If you are not sure, say N here. + +config THINKPAD_ACPI_BAY + bool "Legacy Removable Bay Support" + depends on THINKPAD_ACPI + default y + ---help--- + Allows the thinkpad_acpi driver to handle removable bays. It will + eletrically disable the device in the bay, and also generate + notifications when the bay lever is ejected or inserted. + + If you are not sure, say Y here. + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7793ccd79049..e32516459138 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c index 4b232124a1ab..65c32a95e121 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c @@ -3,7 +3,7 @@ * * * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor - * Copyright (C) 2006 Corentin Chary + * Copyright (C) 2006-2007 Corentin Chary * * 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 @@ -48,7 +48,7 @@ #include <acpi/acpi_bus.h> #include <asm/uaccess.h> -#define ASUS_LAPTOP_VERSION "0.40" +#define ASUS_LAPTOP_VERSION "0.41" #define ASUS_HOTK_NAME "Asus Laptop Support" #define ASUS_HOTK_CLASS "hotkey" @@ -81,7 +81,8 @@ #define TLED_ON 0x08 //touchpad LED #define RLED_ON 0x10 //Record LED #define PLED_ON 0x20 //Phone LED -#define LCD_ON 0x40 //LCD backlight +#define GLED_ON 0x40 //Gaming LED +#define LCD_ON 0x80 //LCD backlight #define ASUS_LOG ASUS_HOTK_FILE ": " #define ASUS_ERR KERN_ERR ASUS_LOG @@ -94,6 +95,19 @@ MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary"); MODULE_DESCRIPTION(ASUS_HOTK_NAME); MODULE_LICENSE("GPL"); +/* WAPF defines the behavior of the Fn+Fx wlan key + * The significance of values is yet to be found, but + * most of the time: + * 0x0 will do nothing + * 0x1 will allow to control the device with Fn+Fx key. + * 0x4 will send an ACPI event (0x88) while pressing the Fn+Fx key + * 0x5 like 0x1 or 0x4 + * So, if something doesn't work as you want, just try other values =) + */ +static uint wapf = 1; +module_param(wapf, uint, 0644); +MODULE_PARM_DESC(wapf, "WAPF value"); + #define ASUS_HANDLE(object, paths...) \ static acpi_handle object##_handle = NULL; \ static char *object##_paths[] = { paths } @@ -103,6 +117,7 @@ ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED"); ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED"); ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */ ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */ +ASUS_HANDLE(gled_set, ASUS_HOTK_PREFIX "GLED"); /* G1, G2 (probably) */ /* LEDD */ ASUS_HANDLE(ledd_set, ASUS_HOTK_PREFIX "SLCM"); @@ -221,6 +236,7 @@ ASUS_LED(mled, "mail"); ASUS_LED(tled, "touchpad"); ASUS_LED(rled, "record"); ASUS_LED(pled, "phone"); +ASUS_LED(gled, "gaming"); /* * This function evaluates an ACPI method, given an int as parameter, the @@ -245,32 +261,19 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val, return (status == AE_OK); } -static int read_acpi_int(acpi_handle handle, const char *method, int *val, - struct acpi_object_list *params) -{ - struct acpi_buffer output; - union acpi_object out_obj; - acpi_status status; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = acpi_evaluate_object(handle, (char *)method, params, &output); - *val = out_obj.integer.value; - return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); -} - static int read_wireless_status(int mask) { - int status; + ulong status; + acpi_status rv = AE_OK; if (!wireless_status_handle) return (hotk->status & mask) ? 1 : 0; - if (read_acpi_int(wireless_status_handle, NULL, &status, NULL)) { - return (status & mask) ? 1 : 0; - } else + rv = acpi_evaluate_integer(wireless_status_handle, NULL, NULL, &status); + if (ACPI_FAILURE(rv)) printk(ASUS_WARNING "Error reading Wireless status\n"); + else + return (status & mask) ? 1 : 0; return (hotk->status & mask) ? 1 : 0; } @@ -285,19 +288,28 @@ static int read_status(int mask) return (hotk->status & mask) ? 1 : 0; } -static void write_status(acpi_handle handle, int out, int mask, int invert) +static void write_status(acpi_handle handle, int out, int mask) { hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask); - if (invert) /* invert target value */ + switch (mask) { + case MLED_ON: out = !out & 0x1; + break; + case GLED_ON: + out = (out & 0x1) + 1; + break; + default: + out &= 0x1; + break; + } if (handle && !write_acpi_int(handle, NULL, out, NULL)) - printk(ASUS_WARNING " write failed\n"); + printk(ASUS_WARNING " write failed %x\n", mask); } /* /sys/class/led handlers */ -#define ASUS_LED_HANDLER(object, mask, invert) \ +#define ASUS_LED_HANDLER(object, mask) \ static void object##_led_set(struct led_classdev *led_cdev, \ enum led_brightness value) \ { \ @@ -307,13 +319,14 @@ static void write_status(acpi_handle handle, int out, int mask, int invert) static void object##_led_update(struct work_struct *ignored) \ { \ int value = object##_led_wk; \ - write_status(object##_set_handle, value, (mask), (invert)); \ + write_status(object##_set_handle, value, (mask)); \ } -ASUS_LED_HANDLER(mled, MLED_ON, 1); -ASUS_LED_HANDLER(pled, PLED_ON, 0); -ASUS_LED_HANDLER(rled, RLED_ON, 0); -ASUS_LED_HANDLER(tled, TLED_ON, 0); +ASUS_LED_HANDLER(mled, MLED_ON); +ASUS_LED_HANDLER(pled, PLED_ON); +ASUS_LED_HANDLER(rled, RLED_ON); +ASUS_LED_HANDLER(tled, TLED_ON); +ASUS_LED_HANDLER(gled, GLED_ON); static int get_lcd_state(void) { @@ -338,7 +351,7 @@ static int set_lcd_state(int value) printk(ASUS_WARNING "Error switching LCD\n"); } - write_status(NULL, lcd, LCD_ON, 0); + write_status(NULL, lcd, LCD_ON); return 0; } @@ -354,9 +367,11 @@ static void lcd_blank(int blank) static int read_brightness(struct backlight_device *bd) { - int value; + ulong value; + acpi_status rv = AE_OK; - if (!read_acpi_int(brightness_get_handle, NULL, &value, NULL)) + rv = acpi_evaluate_integer(brightness_get_handle, NULL, NULL, &value); + if (ACPI_FAILURE(rv)) printk(ASUS_WARNING "Error reading brightness\n"); return value; @@ -403,8 +418,10 @@ static ssize_t show_infos(struct device *dev, struct device_attribute *attr, char *page) { int len = 0; - int temp; + ulong temp; char buf[16]; //enough for all info + acpi_status rv = AE_OK; + /* * We use the easy way, we don't care of off and count, so we don't set eof * to 1 @@ -418,9 +435,10 @@ static ssize_t show_infos(struct device *dev, * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. * The significance of others is yet to be found. */ - if (read_acpi_int(hotk->handle, "SFUN", &temp, NULL)) - len += - sprintf(page + len, "SFUN value : 0x%04x\n", temp); + rv = acpi_evaluate_integer(hotk->handle, "SFUN", NULL, &temp); + if (!ACPI_FAILURE(rv)) + len += sprintf(page + len, "SFUN value : 0x%04x\n", + (uint) temp); /* * Another value for userspace: the ASYM method returns 0x02 for * battery low and 0x04 for battery critical, its readings tend to be @@ -428,9 +446,10 @@ static ssize_t show_infos(struct device *dev, * Note: since not all the laptops provide this method, errors are * silently ignored. */ - if (read_acpi_int(hotk->handle, "ASYM", &temp, NULL)) - len += - sprintf(page + len, "ASYM value : 0x%04x\n", temp); + rv = acpi_evaluate_integer(hotk->handle, "ASYM", NULL, &temp); + if (!ACPI_FAILURE(rv)) + len += sprintf(page + len, "ASYM value : 0x%04x\n", + (uint) temp); if (asus_info) { snprintf(buf, 16, "%d", asus_info->length); len += sprintf(page + len, "DSDT length : %s\n", buf); @@ -465,7 +484,7 @@ static int parse_arg(const char *buf, unsigned long count, int *val) } static ssize_t store_status(const char *buf, size_t count, - acpi_handle handle, int mask, int invert) + acpi_handle handle, int mask) { int rv, value; int out = 0; @@ -474,7 +493,7 @@ static ssize_t store_status(const char *buf, size_t count, if (rv > 0) out = value ? 1 : 0; - write_status(handle, out, mask, invert); + write_status(handle, out, mask); return rv; } @@ -515,7 +534,7 @@ static ssize_t show_wlan(struct device *dev, static ssize_t store_wlan(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, wl_switch_handle, WL_ON, 0); + return store_status(buf, count, wl_switch_handle, WL_ON); } /* @@ -531,7 +550,7 @@ static ssize_t store_bluetooth(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, bt_switch_handle, BT_ON, 0); + return store_status(buf, count, bt_switch_handle, BT_ON); } /* @@ -547,12 +566,15 @@ static void set_display(int value) static int read_display(void) { - int value = 0; + ulong value = 0; + acpi_status rv = AE_OK; /* In most of the case, we know how to set the display, but sometime we can't read it */ if (display_get_handle) { - if (!read_acpi_int(display_get_handle, NULL, &value, NULL)) + rv = acpi_evaluate_integer(display_get_handle, NULL, + NULL, &value); + if (ACPI_FAILURE(rv)) printk(ASUS_WARNING "Error reading display status\n"); } @@ -656,10 +678,10 @@ static void asus_hotk_notify(acpi_handle handle, u32 event, void *data) * switched */ if (event == ATKD_LCD_ON) { - write_status(NULL, 1, LCD_ON, 0); + write_status(NULL, 1, LCD_ON); lcd_blank(FB_BLANK_UNBLANK); } else if (event == ATKD_LCD_OFF) { - write_status(NULL, 0, LCD_ON, 0); + write_status(NULL, 0, LCD_ON); lcd_blank(FB_BLANK_POWERDOWN); } @@ -771,7 +793,7 @@ static int asus_hotk_get_info(void) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *model = NULL; - int bsts_result, hwrs_result; + ulong bsts_result, hwrs_result; char *string = NULL; acpi_status status; @@ -794,11 +816,16 @@ static int asus_hotk_get_info(void) } /* This needs to be called for some laptops to init properly */ - if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result, NULL)) + status = + acpi_evaluate_integer(hotk->handle, "BSTS", NULL, &bsts_result); + if (ACPI_FAILURE(status)) printk(ASUS_WARNING "Error calling BSTS\n"); else if (bsts_result) printk(ASUS_NOTICE "BSTS called, 0x%02x returned\n", - bsts_result); + (uint) bsts_result); + + /* This too ... */ + write_acpi_int(hotk->handle, "CWAP", wapf, NULL); /* * Try to match the object returned by INIT to the specific model. @@ -831,6 +858,7 @@ static int asus_hotk_get_info(void) ASUS_HANDLE_INIT(tled_set); ASUS_HANDLE_INIT(rled_set); ASUS_HANDLE_INIT(pled_set); + ASUS_HANDLE_INIT(gled_set); ASUS_HANDLE_INIT(ledd_set); @@ -840,7 +868,9 @@ static int asus_hotk_get_info(void) * The significance of others is yet to be found. * If we don't find the method, we assume the device are present. */ - if (!read_acpi_int(hotk->handle, "HRWS", &hwrs_result, NULL)) + status = + acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &hwrs_result); + if (ACPI_FAILURE(status)) hwrs_result = WL_HWRS | BT_HWRS; if (hwrs_result & WL_HWRS) @@ -928,11 +958,15 @@ static int asus_hotk_add(struct acpi_device *device) asus_hotk_found = 1; /* WLED and BLED are on by default */ - write_status(bt_switch_handle, 1, BT_ON, 0); - write_status(wl_switch_handle, 1, WL_ON, 0); + write_status(bt_switch_handle, 1, BT_ON); + write_status(wl_switch_handle, 1, WL_ON); + + /* If the h/w switch is off, we need to check the real status */ + write_status(NULL, read_status(BT_ON), BT_ON); + write_status(NULL, read_status(WL_ON), WL_ON); /* LCD Backlight is on by default */ - write_status(NULL, 1, LCD_ON, 0); + write_status(NULL, 1, LCD_ON); /* LED display is off by default */ hotk->ledd_status = 0xFFF; @@ -991,6 +1025,7 @@ static void asus_led_exit(void) ASUS_LED_UNREGISTER(tled); ASUS_LED_UNREGISTER(pled); ASUS_LED_UNREGISTER(rled); + ASUS_LED_UNREGISTER(gled); destroy_workqueue(led_workqueue); } @@ -1062,6 +1097,10 @@ static int asus_led_init(struct device *dev) if (rv) return rv; + rv = ASUS_LED_REGISTER(gled, dev); + if (rv) + return rv; + led_workqueue = create_singlethread_workqueue("led_workqueue"); if (!led_workqueue) return -ENOMEM; diff --git a/drivers/misc/sony-laptop.c b/drivers/misc/sony-laptop.c index ac708bc2f9f3..c15c1f61bd1b 100644 --- a/drivers/misc/sony-laptop.c +++ b/drivers/misc/sony-laptop.c @@ -1,5 +1,5 @@ /* - * ACPI Sony Notebook Control Driver (SNC) + * ACPI Sony Notebook Control Driver (SNC and SPIC) * * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net> * Copyright (C) 2007 Mattia Dongili <malattia@linux.it> @@ -7,6 +7,25 @@ * Parts of this driver inspired from asus_acpi.c and ibm_acpi.c * which are copyrighted by their respective authors. * + * The SNY6001 driver part is based on the sonypi driver which includes + * material from: + * + * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net> + * + * Copyright (C) 2005 Narayanan R S <nars@kadamba.org> + * + * Copyright (C) 2001-2002 Alcôve <www.alcove.com> + * + * Copyright (C) 2001 Michael Ashley <m.ashley@unsw.edu.au> + * + * Copyright (C) 2001 Junichi Morita <jun1m@mars.dti.ne.jp> + * + * Copyright (C) 2000 Takaya Kinjo <t-kinjo@tc4.so-net.ne.jp> + * + * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com> + * + * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras. + * * 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 @@ -31,40 +50,404 @@ #include <linux/backlight.h> #include <linux/platform_device.h> #include <linux/err.h> +#include <linux/dmi.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/kfifo.h> +#include <linux/workqueue.h> +#include <linux/acpi.h> #include <acpi/acpi_drivers.h> #include <acpi/acpi_bus.h> #include <asm/uaccess.h> +#include <linux/sonypi.h> +#include <linux/sony-laptop.h> +#ifdef CONFIG_SONY_LAPTOP_OLD +#include <linux/poll.h> +#include <linux/miscdevice.h> +#endif -#define ACPI_SNC_CLASS "sony" -#define ACPI_SNC_HID "SNY5001" -#define ACPI_SNC_DRIVER_NAME "ACPI Sony Notebook Control Driver v0.4" +#define DRV_PFX "sony-laptop: " +#define dprintk(msg...) do { \ + if (debug) printk(KERN_WARNING DRV_PFX msg); \ +} while (0) -/* the device uses 1-based values, while the backlight subsystem uses - 0-based values */ -#define SONY_MAX_BRIGHTNESS 8 +#define SONY_LAPTOP_DRIVER_VERSION "0.5" + +#define SONY_NC_CLASS "sony-nc" +#define SONY_NC_HID "SNY5001" +#define SONY_NC_DRIVER_NAME "Sony Notebook Control Driver" -#define LOG_PFX KERN_WARNING "sony-laptop: " +#define SONY_PIC_CLASS "sony-pic" +#define SONY_PIC_HID "SNY6001" +#define SONY_PIC_DRIVER_NAME "Sony Programmable IO Control Driver" MODULE_AUTHOR("Stelian Pop, Mattia Dongili"); -MODULE_DESCRIPTION(ACPI_SNC_DRIVER_NAME); +MODULE_DESCRIPTION("Sony laptop extras driver (SPIC and SNC ACPI device)"); MODULE_LICENSE("GPL"); +MODULE_VERSION(SONY_LAPTOP_DRIVER_VERSION); static int debug; module_param(debug, int, 0); MODULE_PARM_DESC(debug, "set this to 1 (and RTFM) if you want to help " "the development of this driver"); -static ssize_t sony_acpi_show(struct device *, struct device_attribute *, +static int no_spic; /* = 0 */ +module_param(no_spic, int, 0444); +MODULE_PARM_DESC(no_spic, + "set this if you don't want to enable the SPIC device"); + +static int compat; /* = 0 */ +module_param(compat, int, 0444); +MODULE_PARM_DESC(compat, + "set this if you want to enable backward compatibility mode"); + +static unsigned long mask = 0xffffffff; +module_param(mask, ulong, 0644); +MODULE_PARM_DESC(mask, + "set this to the mask of event you want to enable (see doc)"); + +static int camera; /* = 0 */ +module_param(camera, int, 0444); +MODULE_PARM_DESC(camera, + "set this to 1 to enable Motion Eye camera controls " + "(only use it if you have a C1VE or C1VN model)"); + +#ifdef CONFIG_SONY_LAPTOP_OLD +static int minor = -1; +module_param(minor, int, 0); +MODULE_PARM_DESC(minor, + "minor number of the misc device for the SPIC compatibility code, " + "default is -1 (automatic)"); +#endif + +/*********** Input Devices ***********/ + +#define SONY_LAPTOP_BUF_SIZE 128 +struct sony_laptop_input_s { + atomic_t users; + struct input_dev *jog_dev; + struct input_dev *key_dev; + struct kfifo *fifo; + spinlock_t fifo_lock; + struct workqueue_struct *wq; +}; +static struct sony_laptop_input_s sony_laptop_input = { + .users = ATOMIC_INIT(0), +}; + +struct sony_laptop_keypress { + struct input_dev *dev; + int key; +}; + +/* Correspondance table between sonypi events and input layer events */ +static struct { + int sonypiev; + int inputev; +} sony_laptop_inputkeys[] = { + { SONYPI_EVENT_CAPTURE_PRESSED, KEY_CAMERA }, + { SONYPI_EVENT_FNKEY_ONLY, KEY_FN }, + { SONYPI_EVENT_FNKEY_ESC, KEY_FN_ESC }, + { SONYPI_EVENT_FNKEY_F1, KEY_FN_F1 }, + { SONYPI_EVENT_FNKEY_F2, KEY_FN_F2 }, + { SONYPI_EVENT_FNKEY_F3, KEY_FN_F3 }, + { SONYPI_EVENT_FNKEY_F4, KEY_FN_F4 }, + { SONYPI_EVENT_FNKEY_F5, KEY_FN_F5 }, + { SONYPI_EVENT_FNKEY_F6, KEY_FN_F6 }, + { SONYPI_EVENT_FNKEY_F7, KEY_FN_F7 }, + { SONYPI_EVENT_FNKEY_F8, KEY_FN_F8 }, + { SONYPI_EVENT_FNKEY_F9, KEY_FN_F9 }, + { SONYPI_EVENT_FNKEY_F10, KEY_FN_F10 }, + { SONYPI_EVENT_FNKEY_F11, KEY_FN_F11 }, + { SONYPI_EVENT_FNKEY_F12, KEY_FN_F12 }, + { SONYPI_EVENT_FNKEY_1, KEY_FN_1 }, + { SONYPI_EVENT_FNKEY_2, KEY_FN_2 }, + { SONYPI_EVENT_FNKEY_D, KEY_FN_D }, + { SONYPI_EVENT_FNKEY_E, KEY_FN_E }, + { SONYPI_EVENT_FNKEY_F, KEY_FN_F }, + { SONYPI_EVENT_FNKEY_S, KEY_FN_S }, + { SONYPI_EVENT_FNKEY_B, KEY_FN_B }, + { SONYPI_EVENT_BLUETOOTH_PRESSED, KEY_BLUE }, + { SONYPI_EVENT_BLUETOOTH_ON, KEY_BLUE }, + { SONYPI_EVENT_PKEY_P1, KEY_PROG1 }, + { SONYPI_EVENT_PKEY_P2, KEY_PROG2 }, + { SONYPI_EVENT_PKEY_P3, KEY_PROG3 }, + { SONYPI_EVENT_BACK_PRESSED, KEY_BACK }, + { SONYPI_EVENT_HELP_PRESSED, KEY_HELP }, + { SONYPI_EVENT_ZOOM_PRESSED, KEY_ZOOM }, + { SONYPI_EVENT_THUMBPHRASE_PRESSED, BTN_THUMB }, + { 0, 0 }, +}; + +/* release buttons after a short delay if pressed */ +static void do_sony_laptop_release_key(struct work_struct *work) +{ + struct sony_laptop_keypress kp; + + while (kfifo_get(sony_laptop_input.fifo, (unsigned char *)&kp, + sizeof(kp)) == sizeof(kp)) { + msleep(10); + input_report_key(kp.dev, kp.key, 0); + input_sync(kp.dev); + } +} +static DECLARE_WORK(sony_laptop_release_key_work, + do_sony_laptop_release_key); + +/* forward event to the input subsytem */ +static void sony_laptop_report_input_event(u8 event) +{ + struct input_dev *jog_dev = sony_laptop_input.jog_dev; + struct input_dev *key_dev = sony_laptop_input.key_dev; + struct sony_laptop_keypress kp = { NULL }; + int i; + + if (event == SONYPI_EVENT_FNKEY_RELEASED) { + /* Nothing, not all VAIOs generate this event */ + return; + } + + /* report events */ + switch (event) { + /* jog_dev events */ + case SONYPI_EVENT_JOGDIAL_UP: + case SONYPI_EVENT_JOGDIAL_UP_PRESSED: + input_report_rel(jog_dev, REL_WHEEL, 1); + input_sync(jog_dev); + return; + + case SONYPI_EVENT_JOGDIAL_DOWN: + case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED: + input_report_rel(jog_dev, REL_WHEEL, -1); + input_sync(jog_dev); + return; + + /* key_dev events */ + case SONYPI_EVENT_JOGDIAL_PRESSED: + kp.key = BTN_MIDDLE; + kp.dev = jog_dev; + break; + + default: + for (i = 0; sony_laptop_inputkeys[i].sonypiev; i++) + if (event == sony_laptop_inputkeys[i].sonypiev) { + kp.dev = key_dev; + kp.key = sony_laptop_inputkeys[i].inputev; + break; + } + break; + } + + if (kp.dev) { + input_report_key(kp.dev, kp.key, 1); + input_sync(kp.dev); + kfifo_put(sony_laptop_input.fifo, + (unsigned char *)&kp, sizeof(kp)); + + if (!work_pending(&sony_laptop_release_key_work)) + queue_work(sony_laptop_input.wq, + &sony_laptop_release_key_work); + } else + dprintk("unknown input event %.2x\n", event); +} + +static int sony_laptop_setup_input(void) +{ + struct input_dev *jog_dev; + struct input_dev *key_dev; + int i; + int error; + + /* don't run again if already initialized */ + if (atomic_add_return(1, &sony_laptop_input.users) > 1) + return 0; + + /* kfifo */ + spin_lock_init(&sony_laptop_input.fifo_lock); + sony_laptop_input.fifo = + kfifo_alloc(SONY_LAPTOP_BUF_SIZE, GFP_KERNEL, + &sony_laptop_input.fifo_lock); + if (IS_ERR(sony_laptop_input.fifo)) { + printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n"); + error = PTR_ERR(sony_laptop_input.fifo); + goto err_dec_users; + } + + /* init workqueue */ + sony_laptop_input.wq = create_singlethread_workqueue("sony-laptop"); + if (!sony_laptop_input.wq) { + printk(KERN_ERR DRV_PFX + "Unabe to create workqueue.\n"); + error = -ENXIO; + goto err_free_kfifo; + } + + /* input keys */ + key_dev = input_allocate_device(); + if (!key_dev) { + error = -ENOMEM; + goto err_destroy_wq; + } + + key_dev->name = "Sony Vaio Keys"; + key_dev->id.bustype = BUS_ISA; + key_dev->id.vendor = PCI_VENDOR_ID_SONY; + + /* Initialize the Input Drivers: special keys */ + key_dev->evbit[0] = BIT(EV_KEY); + for (i = 0; sony_laptop_inputkeys[i].sonypiev; i++) + if (sony_laptop_inputkeys[i].inputev) + set_bit(sony_laptop_inputkeys[i].inputev, + key_dev->keybit); + + error = input_register_device(key_dev); + if (error) + goto err_free_keydev; + + sony_laptop_input.key_dev = key_dev; + + /* jogdial */ + jog_dev = input_allocate_device(); + if (!jog_dev) { + error = -ENOMEM; + goto err_unregister_keydev; + } + + jog_dev->name = "Sony Vaio Jogdial"; + jog_dev->id.bustype = BUS_ISA; + jog_dev->id.vendor = PCI_VENDOR_ID_SONY; + + jog_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + jog_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_MIDDLE); + jog_dev->relbit[0] = BIT(REL_WHEEL); + + error = input_register_device(jog_dev); + if (error) + goto err_free_jogdev; + + sony_laptop_input.jog_dev = jog_dev; + + return 0; + +err_free_jogdev: + input_free_device(jog_dev); + +err_unregister_keydev: + input_unregister_device(key_dev); + /* to avoid kref underflow below at input_free_device */ + key_dev = NULL; + +err_free_keydev: + input_free_device(key_dev); + +err_destroy_wq: + destroy_workqueue(sony_laptop_input.wq); + +err_free_kfifo: + kfifo_free(sony_laptop_input.fifo); + +err_dec_users: + atomic_dec(&sony_laptop_input.users); + return error; +} + +static void sony_laptop_remove_input(void) +{ + /* cleanup only after the last user has gone */ + if (!atomic_dec_and_test(&sony_laptop_input.users)) + return; + + /* flush workqueue first */ + flush_workqueue(sony_laptop_input.wq); + + /* destroy input devs */ + input_unregister_device(sony_laptop_input.key_dev); + sony_laptop_input.key_dev = NULL; + + if (sony_laptop_input.jog_dev) { + input_unregister_device(sony_laptop_input.jog_dev); + sony_laptop_input.jog_dev = NULL; + } + + destroy_workqueue(sony_laptop_input.wq); + kfifo_free(sony_laptop_input.fifo); +} + +/*********** Platform Device ***********/ + +static atomic_t sony_pf_users = ATOMIC_INIT(0); +static struct platform_driver sony_pf_driver = { + .driver = { + .name = "sony-laptop", + .owner = THIS_MODULE, + } +}; +static struct platform_device *sony_pf_device; + +static int sony_pf_add(void) +{ + int ret = 0; + + /* don't run again if already initialized */ + if (atomic_add_return(1, &sony_pf_users) > 1) + return 0; + + ret = platform_driver_register(&sony_pf_driver); + if (ret) + goto out; + + sony_pf_device = platform_device_alloc("sony-laptop", -1); + if (!sony_pf_device) { + ret = -ENOMEM; + goto out_platform_registered; + } + + ret = platform_device_add(sony_pf_device); + if (ret) + goto out_platform_alloced; + + return 0; + + out_platform_alloced: + platform_device_put(sony_pf_device); + sony_pf_device = NULL; + out_platform_registered: + platform_driver_unregister(&sony_pf_driver); + out: + atomic_dec(&sony_pf_users); + return ret; +} + +static void sony_pf_remove(void) +{ + /* deregister only after the last user has gone */ + if (!atomic_dec_and_test(&sony_pf_users)) + return; + + platform_device_del(sony_pf_device); + platform_device_put(sony_pf_device); + platform_driver_unregister(&sony_pf_driver); +} + +/*********** SNC (SNY5001) Device ***********/ + +/* the device uses 1-based values, while the backlight subsystem uses + 0-based values */ +#define SONY_MAX_BRIGHTNESS 8 + +#define SNC_VALIDATE_IN 0 +#define SNC_VALIDATE_OUT 1 + +static ssize_t sony_nc_sysfs_show(struct device *, struct device_attribute *, char *); -static ssize_t sony_acpi_store(struct device *, struct device_attribute *, +static ssize_t sony_nc_sysfs_store(struct device *, struct device_attribute *, const char *, size_t); static int boolean_validate(const int, const int); static int brightness_default_validate(const int, const int); -#define SNC_VALIDATE_IN 0 -#define SNC_VALIDATE_OUT 1 - -struct sony_acpi_value { +struct sony_nc_value { char *name; /* name of the entry */ char **acpiget; /* names of the ACPI get function */ char **acpiset; /* names of the ACPI set function */ @@ -75,65 +458,65 @@ struct sony_acpi_value { struct device_attribute devattr; /* sysfs atribute */ }; -#define HANDLE_NAMES(_name, _values...) \ +#define SNC_HANDLE_NAMES(_name, _values...) \ static char *snc_##_name[] = { _values, NULL } -#define SONY_ACPI_VALUE(_name, _getters, _setters, _validate, _debug) \ +#define SNC_HANDLE(_name, _getters, _setters, _validate, _debug) \ { \ .name = __stringify(_name), \ .acpiget = _getters, \ .acpiset = _setters, \ .validate = _validate, \ .debug = _debug, \ - .devattr = __ATTR(_name, 0, sony_acpi_show, sony_acpi_store), \ + .devattr = __ATTR(_name, 0, sony_nc_sysfs_show, sony_nc_sysfs_store), \ } -#define SONY_ACPI_VALUE_NULL { .name = NULL } +#define SNC_HANDLE_NULL { .name = NULL } -HANDLE_NAMES(fnkey_get, "GHKE"); +SNC_HANDLE_NAMES(fnkey_get, "GHKE"); -HANDLE_NAMES(brightness_def_get, "GPBR"); -HANDLE_NAMES(brightness_def_set, "SPBR"); +SNC_HANDLE_NAMES(brightness_def_get, "GPBR"); +SNC_HANDLE_NAMES(brightness_def_set, "SPBR"); -HANDLE_NAMES(cdpower_get, "GCDP"); -HANDLE_NAMES(cdpower_set, "SCDP", "CDPW"); +SNC_HANDLE_NAMES(cdpower_get, "GCDP"); +SNC_HANDLE_NAMES(cdpower_set, "SCDP", "CDPW"); -HANDLE_NAMES(audiopower_get, "GAZP"); -HANDLE_NAMES(audiopower_set, "AZPW"); +SNC_HANDLE_NAMES(audiopower_get, "GAZP"); +SNC_HANDLE_NAMES(audiopower_set, "AZPW"); -HANDLE_NAMES(lanpower_get, "GLNP"); -HANDLE_NAMES(lanpower_set, "LNPW"); +SNC_HANDLE_NAMES(lanpower_get, "GLNP"); +SNC_HANDLE_NAMES(lanpower_set, "LNPW"); -HANDLE_NAMES(PID_get, "GPID"); +SNC_HANDLE_NAMES(PID_get, "GPID"); -HANDLE_NAMES(CTR_get, "GCTR"); -HANDLE_NAMES(CTR_set, "SCTR"); +SNC_HANDLE_NAMES(CTR_get, "GCTR"); +SNC_HANDLE_NAMES(CTR_set, "SCTR"); -HANDLE_NAMES(PCR_get, "GPCR"); -HANDLE_NAMES(PCR_set, "SPCR"); +SNC_HANDLE_NAMES(PCR_get, "GPCR"); +SNC_HANDLE_NAMES(PCR_set, "SPCR"); -HANDLE_NAMES(CMI_get, "GCMI"); -HANDLE_NAMES(CMI_set, "SCMI"); +SNC_HANDLE_NAMES(CMI_get, "GCMI"); +SNC_HANDLE_NAMES(CMI_set, "SCMI"); -static struct sony_acpi_value sony_acpi_values[] = { - SONY_ACPI_VALUE(brightness_default, snc_brightness_def_get, +static struct sony_nc_value sony_nc_values[] = { + SNC_HANDLE(brightness_default, snc_brightness_def_get, snc_brightness_def_set, brightness_default_validate, 0), - SONY_ACPI_VALUE(fnkey, snc_fnkey_get, NULL, NULL, 0), - SONY_ACPI_VALUE(cdpower, snc_cdpower_get, snc_cdpower_set, boolean_validate, 0), - SONY_ACPI_VALUE(audiopower, snc_audiopower_get, snc_audiopower_set, + SNC_HANDLE(fnkey, snc_fnkey_get, NULL, NULL, 0), + SNC_HANDLE(cdpower, snc_cdpower_get, snc_cdpower_set, boolean_validate, 0), + SNC_HANDLE(audiopower, snc_audiopower_get, snc_audiopower_set, boolean_validate, 0), - SONY_ACPI_VALUE(lanpower, snc_lanpower_get, snc_lanpower_set, + SNC_HANDLE(lanpower, snc_lanpower_get, snc_lanpower_set, boolean_validate, 1), /* unknown methods */ - SONY_ACPI_VALUE(PID, snc_PID_get, NULL, NULL, 1), - SONY_ACPI_VALUE(CTR, snc_CTR_get, snc_CTR_set, NULL, 1), - SONY_ACPI_VALUE(PCR, snc_PCR_get, snc_PCR_set, NULL, 1), - SONY_ACPI_VALUE(CMI, snc_CMI_get, snc_CMI_set, NULL, 1), - SONY_ACPI_VALUE_NULL + SNC_HANDLE(PID, snc_PID_get, NULL, NULL, 1), + SNC_HANDLE(CTR, snc_CTR_get, snc_CTR_set, NULL, 1), + SNC_HANDLE(PCR, snc_PCR_get, snc_PCR_set, NULL, 1), + SNC_HANDLE(CMI, snc_CMI_get, snc_CMI_set, NULL, 1), + SNC_HANDLE_NULL }; -static acpi_handle sony_acpi_handle; -static struct acpi_device *sony_acpi_acpi_device = NULL; +static acpi_handle sony_nc_acpi_handle; +static struct acpi_device *sony_nc_acpi_device = NULL; /* * acpi_evaluate_object wrappers @@ -153,7 +536,7 @@ static int acpi_callgetfunc(acpi_handle handle, char *name, int *result) return 0; } - printk(LOG_PFX "acpi_callreadfunc failed\n"); + printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n"); return -1; } @@ -179,7 +562,7 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value, if (status == AE_OK) { if (result != NULL) { if (out_obj.type != ACPI_TYPE_INTEGER) { - printk(LOG_PFX "acpi_evaluate_object bad " + printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad " "return type\n"); return -1; } @@ -188,13 +571,13 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value, return 0; } - printk(LOG_PFX "acpi_evaluate_object failed\n"); + printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n"); return -1; } /* - * sony_acpi_values input/output validate functions + * sony_nc_values input/output validate functions */ /* brightness_default_validate: @@ -229,19 +612,19 @@ static int boolean_validate(const int direction, const int value) } /* - * Sysfs show/store common to all sony_acpi_values + * Sysfs show/store common to all sony_nc_values */ -static ssize_t sony_acpi_show(struct device *dev, struct device_attribute *attr, +static ssize_t sony_nc_sysfs_show(struct device *dev, struct device_attribute *attr, char *buffer) { int value; - struct sony_acpi_value *item = - container_of(attr, struct sony_acpi_value, devattr); + struct sony_nc_value *item = + container_of(attr, struct sony_nc_value, devattr); if (!*item->acpiget) return -EIO; - if (acpi_callgetfunc(sony_acpi_handle, *item->acpiget, &value) < 0) + if (acpi_callgetfunc(sony_nc_acpi_handle, *item->acpiget, &value) < 0) return -EIO; if (item->validate) @@ -250,13 +633,13 @@ static ssize_t sony_acpi_show(struct device *dev, struct device_attribute *attr, return snprintf(buffer, PAGE_SIZE, "%d\n", value); } -static ssize_t sony_acpi_store(struct device *dev, +static ssize_t sony_nc_sysfs_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count) { int value; - struct sony_acpi_value *item = - container_of(attr, struct sony_acpi_value, devattr); + struct sony_nc_value *item = + container_of(attr, struct sony_nc_value, devattr); if (!item->acpiset) return -EIO; @@ -272,118 +655,20 @@ static ssize_t sony_acpi_store(struct device *dev, if (value < 0) return value; - if (acpi_callsetfunc(sony_acpi_handle, *item->acpiset, value, NULL) < 0) + if (acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, value, NULL) < 0) return -EIO; item->value = value; item->valid = 1; return count; } -/* - * Platform device - */ -static struct platform_driver sncpf_driver = { - .driver = { - .name = "sony-laptop", - .owner = THIS_MODULE, - } -}; -static struct platform_device *sncpf_device; - -static int sony_snc_pf_add(void) -{ - acpi_handle handle; - struct sony_acpi_value *item; - int ret = 0; - - ret = platform_driver_register(&sncpf_driver); - if (ret) - goto out; - - sncpf_device = platform_device_alloc("sony-laptop", -1); - if (!sncpf_device) { - ret = -ENOMEM; - goto out_platform_registered; - } - - ret = platform_device_add(sncpf_device); - if (ret) - goto out_platform_alloced; - - for (item = sony_acpi_values; item->name; ++item) { - - if (!debug && item->debug) - continue; - - /* find the available acpiget as described in the DSDT */ - for (; item->acpiget && *item->acpiget; ++item->acpiget) { - if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle, - *item->acpiget, - &handle))) { - if (debug) - printk(LOG_PFX "Found %s getter: %s\n", - item->name, *item->acpiget); - item->devattr.attr.mode |= S_IRUGO; - break; - } - } - - /* find the available acpiset as described in the DSDT */ - for (; item->acpiset && *item->acpiset; ++item->acpiset) { - if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle, - *item->acpiset, - &handle))) { - if (debug) - printk(LOG_PFX "Found %s setter: %s\n", - item->name, *item->acpiset); - item->devattr.attr.mode |= S_IWUSR; - break; - } - } - - if (item->devattr.attr.mode != 0) { - ret = - device_create_file(&sncpf_device->dev, - &item->devattr); - if (ret) - goto out_sysfs; - } - } - - return 0; - - out_sysfs: - for (item = sony_acpi_values; item->name; ++item) { - device_remove_file(&sncpf_device->dev, &item->devattr); - } - platform_device_del(sncpf_device); - out_platform_alloced: - platform_device_put(sncpf_device); - out_platform_registered: - platform_driver_unregister(&sncpf_driver); - out: - return ret; -} - -static void sony_snc_pf_remove(void) -{ - struct sony_acpi_value *item; - - for (item = sony_acpi_values; item->name; ++item) { - device_remove_file(&sncpf_device->dev, &item->devattr); - } - - platform_device_del(sncpf_device); - platform_device_put(sncpf_device); - platform_driver_unregister(&sncpf_driver); -} /* * Backlight device */ static int sony_backlight_update_status(struct backlight_device *bd) { - return acpi_callsetfunc(sony_acpi_handle, "SBRT", + return acpi_callsetfunc(sony_nc_acpi_handle, "SBRT", bd->props.brightness + 1, NULL); } @@ -391,7 +676,7 @@ static int sony_backlight_get_brightness(struct backlight_device *bd) { int value; - if (acpi_callgetfunc(sony_acpi_handle, "GBRT", &value)) + if (acpi_callgetfunc(sony_nc_acpi_handle, "GBRT", &value)) return 0; /* brightness levels are 1-based, while backlight ones are 0-based */ return value - 1; @@ -408,9 +693,9 @@ static struct backlight_ops sony_backlight_ops = { */ static void sony_acpi_notify(acpi_handle handle, u32 event, void *data) { - if (debug) - printk(LOG_PFX "sony_acpi_notify, event: %d\n", event); - acpi_bus_generate_event(sony_acpi_acpi_device, 1, event); + dprintk("sony_acpi_notify, event: %d\n", event); + sony_laptop_report_input_event(event); + acpi_bus_generate_event(sony_nc_acpi_device, 1, event); } static acpi_status sony_walk_callback(acpi_handle handle, u32 level, @@ -422,7 +707,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level, node = (struct acpi_namespace_node *)handle; operand = (union acpi_operand_object *)node->object; - printk(LOG_PFX "method: name: %4.4s, args %X\n", node->name.ascii, + printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n", node->name.ascii, (u32) operand->method.param_count); return AE_OK; @@ -431,16 +716,16 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level, /* * ACPI device */ -static int sony_acpi_resume(struct acpi_device *device) +static int sony_nc_resume(struct acpi_device *device) { - struct sony_acpi_value *item; + struct sony_nc_value *item; - for (item = sony_acpi_values; item->name; item++) { + for (item = sony_nc_values; item->name; item++) { int ret; if (!item->valid) continue; - ret = acpi_callsetfunc(sony_acpi_handle, *item->acpiset, + ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, item->value, NULL); if (ret < 0) { printk("%s: %d\n", __FUNCTION__, ret); @@ -450,42 +735,55 @@ static int sony_acpi_resume(struct acpi_device *device) return 0; } -static int sony_acpi_add(struct acpi_device *device) +static int sony_nc_add(struct acpi_device *device) { acpi_status status; int result = 0; acpi_handle handle; + struct sony_nc_value *item; + + printk(KERN_INFO DRV_PFX "%s v%s.\n", + SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - sony_acpi_acpi_device = device; + sony_nc_acpi_device = device; + strcpy(acpi_device_class(device), "sony/hotkey"); - sony_acpi_handle = device->handle; + sony_nc_acpi_handle = device->handle; if (debug) { - status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_acpi_handle, + status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_nc_acpi_handle, 1, sony_walk_callback, NULL, NULL); if (ACPI_FAILURE(status)) { - printk(LOG_PFX "unable to walk acpi resources\n"); + printk(KERN_WARNING DRV_PFX "unable to walk acpi resources\n"); result = -ENODEV; goto outwalk; } } - status = acpi_install_notify_handler(sony_acpi_handle, + /* setup input devices and helper fifo */ + result = sony_laptop_setup_input(); + if (result) { + printk(KERN_ERR DRV_PFX + "Unabe to create input devices.\n"); + goto outwalk; + } + + status = acpi_install_notify_handler(sony_nc_acpi_handle, ACPI_DEVICE_NOTIFY, sony_acpi_notify, NULL); if (ACPI_FAILURE(status)) { - printk(LOG_PFX "unable to install notify handler\n"); + printk(KERN_WARNING DRV_PFX "unable to install notify handler\n"); result = -ENODEV; - goto outwalk; + goto outinput; } - if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle, "GBRT", &handle))) { + if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT", &handle))) { sony_backlight_device = backlight_device_register("sony", NULL, NULL, &sony_backlight_ops); if (IS_ERR(sony_backlight_device)) { - printk(LOG_PFX "unable to register backlight device\n"); + printk(KERN_WARNING DRV_PFX "unable to register backlight device\n"); sony_backlight_device = NULL; } else { sony_backlight_device->props.brightness = @@ -497,68 +795,1497 @@ static int sony_acpi_add(struct acpi_device *device) } - if (sony_snc_pf_add()) + result = sony_pf_add(); + if (result) goto outbacklight; - printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully installed\n"); + /* create sony_pf sysfs attributes related to the SNC device */ + for (item = sony_nc_values; item->name; ++item) { + + if (!debug && item->debug) + continue; + + /* find the available acpiget as described in the DSDT */ + for (; item->acpiget && *item->acpiget; ++item->acpiget) { + if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, + *item->acpiget, + &handle))) { + dprintk("Found %s getter: %s\n", + item->name, *item->acpiget); + item->devattr.attr.mode |= S_IRUGO; + break; + } + } + + /* find the available acpiset as described in the DSDT */ + for (; item->acpiset && *item->acpiset; ++item->acpiset) { + if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, + *item->acpiset, + &handle))) { + dprintk("Found %s setter: %s\n", + item->name, *item->acpiset); + item->devattr.attr.mode |= S_IWUSR; + break; + } + } + + if (item->devattr.attr.mode != 0) { + result = + device_create_file(&sony_pf_device->dev, + &item->devattr); + if (result) + goto out_sysfs; + } + } return 0; + out_sysfs: + for (item = sony_nc_values; item->name; ++item) { + device_remove_file(&sony_pf_device->dev, &item->devattr); + } + sony_pf_remove(); + outbacklight: if (sony_backlight_device) backlight_device_unregister(sony_backlight_device); - status = acpi_remove_notify_handler(sony_acpi_handle, + status = acpi_remove_notify_handler(sony_nc_acpi_handle, ACPI_DEVICE_NOTIFY, sony_acpi_notify); if (ACPI_FAILURE(status)) - printk(LOG_PFX "unable to remove notify handler\n"); + printk(KERN_WARNING DRV_PFX "unable to remove notify handler\n"); + + outinput: + sony_laptop_remove_input(); + outwalk: return result; } -static int sony_acpi_remove(struct acpi_device *device, int type) +static int sony_nc_remove(struct acpi_device *device, int type) { acpi_status status; + struct sony_nc_value *item; if (sony_backlight_device) backlight_device_unregister(sony_backlight_device); - sony_acpi_acpi_device = NULL; + sony_nc_acpi_device = NULL; - status = acpi_remove_notify_handler(sony_acpi_handle, + status = acpi_remove_notify_handler(sony_nc_acpi_handle, ACPI_DEVICE_NOTIFY, sony_acpi_notify); if (ACPI_FAILURE(status)) - printk(LOG_PFX "unable to remove notify handler\n"); + printk(KERN_WARNING DRV_PFX "unable to remove notify handler\n"); - sony_snc_pf_remove(); + for (item = sony_nc_values; item->name; ++item) { + device_remove_file(&sony_pf_device->dev, &item->devattr); + } - printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully removed\n"); + sony_pf_remove(); + sony_laptop_remove_input(); + dprintk(SONY_NC_DRIVER_NAME " removed.\n"); return 0; } -static struct acpi_driver sony_acpi_driver = { - .name = ACPI_SNC_DRIVER_NAME, - .class = ACPI_SNC_CLASS, - .ids = ACPI_SNC_HID, +static struct acpi_driver sony_nc_driver = { + .name = SONY_NC_DRIVER_NAME, + .class = SONY_NC_CLASS, + .ids = SONY_NC_HID, + .owner = THIS_MODULE, .ops = { - .add = sony_acpi_add, - .remove = sony_acpi_remove, - .resume = sony_acpi_resume, + .add = sony_nc_add, + .remove = sony_nc_remove, + .resume = sony_nc_resume, }, }; -static int __init sony_acpi_init(void) +/*********** SPIC (SNY6001) Device ***********/ + +#define SONYPI_DEVICE_TYPE1 0x00000001 +#define SONYPI_DEVICE_TYPE2 0x00000002 +#define SONYPI_DEVICE_TYPE3 0x00000004 + +#define SONY_PIC_EV_MASK 0xff + +struct sony_pic_ioport { + struct acpi_resource_io io; + struct list_head list; +}; + +struct sony_pic_irq { + struct acpi_resource_irq irq; + struct list_head list; +}; + +struct sony_pic_dev { + int model; + u8 camera_power; + u8 bluetooth_power; + u8 wwan_power; + struct acpi_device *acpi_dev; + struct sony_pic_irq *cur_irq; + struct sony_pic_ioport *cur_ioport; + struct list_head interrupts; + struct list_head ioports; + struct mutex lock; +}; + +static struct sony_pic_dev spic_dev = { + .interrupts = LIST_HEAD_INIT(spic_dev.interrupts), + .ioports = LIST_HEAD_INIT(spic_dev.ioports), +}; + +/* Event masks */ +#define SONYPI_JOGGER_MASK 0x00000001 +#define SONYPI_CAPTURE_MASK 0x00000002 +#define SONYPI_FNKEY_MASK 0x00000004 +#define SONYPI_BLUETOOTH_MASK 0x00000008 +#define SONYPI_PKEY_MASK 0x00000010 +#define SONYPI_BACK_MASK 0x00000020 +#define SONYPI_HELP_MASK 0x00000040 +#define SONYPI_LID_MASK 0x00000080 +#define SONYPI_ZOOM_MASK 0x00000100 +#define SONYPI_THUMBPHRASE_MASK 0x00000200 +#define SONYPI_MEYE_MASK 0x00000400 +#define SONYPI_MEMORYSTICK_MASK 0x00000800 +#define SONYPI_BATTERY_MASK 0x00001000 +#define SONYPI_WIRELESS_MASK 0x00002000 + +struct sonypi_event { + u8 data; + u8 event; +}; + +/* The set of possible button release events */ +static struct sonypi_event sonypi_releaseev[] = { + { 0x00, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0, 0 } +}; + +/* The set of possible jogger events */ +static struct sonypi_event sonypi_joggerev[] = { + { 0x1f, SONYPI_EVENT_JOGDIAL_UP }, + { 0x01, SONYPI_EVENT_JOGDIAL_DOWN }, + { 0x5f, SONYPI_EVENT_JOGDIAL_UP_PRESSED }, + { 0x41, SONYPI_EVENT_JOGDIAL_DOWN_PRESSED }, + { 0x1e, SONYPI_EVENT_JOGDIAL_FAST_UP }, + { 0x02, SONYPI_EVENT_JOGDIAL_FAST_DOWN }, + { 0x5e, SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED }, + { 0x42, SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED }, + { 0x1d, SONYPI_EVENT_JOGDIAL_VFAST_UP }, + { 0x03, SONYPI_EVENT_JOGDIAL_VFAST_DOWN }, + { 0x5d, SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED }, + { 0x43, SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED }, + { 0x40, SONYPI_EVENT_JOGDIAL_PRESSED }, + { 0, 0 } +}; + +/* The set of possible capture button events */ +static struct sonypi_event sonypi_captureev[] = { + { 0x05, SONYPI_EVENT_CAPTURE_PARTIALPRESSED }, + { 0x07, SONYPI_EVENT_CAPTURE_PRESSED }, + { 0x01, SONYPI_EVENT_CAPTURE_PARTIALRELEASED }, + { 0, 0 } +}; + +/* The set of possible fnkeys events */ +static struct sonypi_event sonypi_fnkeyev[] = { + { 0x10, SONYPI_EVENT_FNKEY_ESC }, + { 0x11, SONYPI_EVENT_FNKEY_F1 }, + { 0x12, SONYPI_EVENT_FNKEY_F2 }, + { 0x13, SONYPI_EVENT_FNKEY_F3 }, + { 0x14, SONYPI_EVENT_FNKEY_F4 }, + { 0x15, SONYPI_EVENT_FNKEY_F5 }, + { 0x16, SONYPI_EVENT_FNKEY_F6 }, + { 0x17, SONYPI_EVENT_FNKEY_F7 }, + { 0x18, SONYPI_EVENT_FNKEY_F8 }, + { 0x19, SONYPI_EVENT_FNKEY_F9 }, + { 0x1a, SONYPI_EVENT_FNKEY_F10 }, + { 0x1b, SONYPI_EVENT_FNKEY_F11 }, + { 0x1c, SONYPI_EVENT_FNKEY_F12 }, + { 0x1f, SONYPI_EVENT_FNKEY_RELEASED }, + { 0x21, SONYPI_EVENT_FNKEY_1 }, + { 0x22, SONYPI_EVENT_FNKEY_2 }, + { 0x31, SONYPI_EVENT_FNKEY_D }, + { 0x32, SONYPI_EVENT_FNKEY_E }, + { 0x33, SONYPI_EVENT_FNKEY_F }, + { 0x34, SONYPI_EVENT_FNKEY_S }, + { 0x35, SONYPI_EVENT_FNKEY_B }, + { 0x36, SONYPI_EVENT_FNKEY_ONLY }, + { 0, 0 } +}; + +/* The set of possible program key events */ +static struct sonypi_event sonypi_pkeyev[] = { + { 0x01, SONYPI_EVENT_PKEY_P1 }, + { 0x02, SONYPI_EVENT_PKEY_P2 }, + { 0x04, SONYPI_EVENT_PKEY_P3 }, + { 0x5c, SONYPI_EVENT_PKEY_P1 }, + { 0, 0 } +}; + +/* The set of possible bluetooth events */ +static struct sonypi_event sonypi_blueev[] = { + { 0x55, SONYPI_EVENT_BLUETOOTH_PRESSED }, + { 0x59, SONYPI_EVENT_BLUETOOTH_ON }, + { 0x5a, SONYPI_EVENT_BLUETOOTH_OFF }, + { 0, 0 } +}; + +/* The set of possible wireless events */ +static struct sonypi_event sonypi_wlessev[] = { + { 0x59, SONYPI_EVENT_WIRELESS_ON }, + { 0x5a, SONYPI_EVENT_WIRELESS_OFF }, + { 0, 0 } +}; + +/* The set of possible back button events */ +static struct sonypi_event sonypi_backev[] = { + { 0x20, SONYPI_EVENT_BACK_PRESSED }, + { 0, 0 } +}; + +/* The set of possible help button events */ +static struct sonypi_event sonypi_helpev[] = { + { 0x3b, SONYPI_EVENT_HELP_PRESSED }, + { 0, 0 } +}; + + +/* The set of possible lid events */ +static struct sonypi_event sonypi_lidev[] = { + { 0x51, SONYPI_EVENT_LID_CLOSED }, + { 0x50, SONYPI_EVENT_LID_OPENED }, + { 0, 0 } +}; + +/* The set of possible zoom events */ +static struct sonypi_event sonypi_zoomev[] = { + { 0x39, SONYPI_EVENT_ZOOM_PRESSED }, + { 0, 0 } +}; + +/* The set of possible thumbphrase events */ +static struct sonypi_event sonypi_thumbphraseev[] = { + { 0x3a, SONYPI_EVENT_THUMBPHRASE_PRESSED }, + { 0, 0 } +}; + +/* The set of possible motioneye camera events */ +static struct sonypi_event sonypi_meyeev[] = { + { 0x00, SONYPI_EVENT_MEYE_FACE }, + { 0x01, SONYPI_EVENT_MEYE_OPPOSITE }, + { 0, 0 } +}; + +/* The set of possible memorystick events */ +static struct sonypi_event sonypi_memorystickev[] = { + { 0x53, SONYPI_EVENT_MEMORYSTICK_INSERT }, + { 0x54, SONYPI_EVENT_MEMORYSTICK_EJECT }, + { 0, 0 } +}; + +/* The set of possible battery events */ +static struct sonypi_event sonypi_batteryev[] = { + { 0x20, SONYPI_EVENT_BATTERY_INSERT }, + { 0x30, SONYPI_EVENT_BATTERY_REMOVE }, + { 0, 0 } +}; + +static struct sonypi_eventtypes { + int model; + u8 data; + unsigned long mask; + struct sonypi_event * events; +} sony_pic_eventtypes[] = { + { SONYPI_DEVICE_TYPE1, 0, 0xffffffff, sonypi_releaseev }, + { SONYPI_DEVICE_TYPE1, 0x70, SONYPI_MEYE_MASK, sonypi_meyeev }, + { SONYPI_DEVICE_TYPE1, 0x30, SONYPI_LID_MASK, sonypi_lidev }, + { SONYPI_DEVICE_TYPE1, 0x60, SONYPI_CAPTURE_MASK, sonypi_captureev }, + { SONYPI_DEVICE_TYPE1, 0x10, SONYPI_JOGGER_MASK, sonypi_joggerev }, + { SONYPI_DEVICE_TYPE1, 0x20, SONYPI_FNKEY_MASK, sonypi_fnkeyev }, + { SONYPI_DEVICE_TYPE1, 0x30, SONYPI_BLUETOOTH_MASK, sonypi_blueev }, + { SONYPI_DEVICE_TYPE1, 0x40, SONYPI_PKEY_MASK, sonypi_pkeyev }, + { SONYPI_DEVICE_TYPE1, 0x30, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev }, + { SONYPI_DEVICE_TYPE1, 0x40, SONYPI_BATTERY_MASK, sonypi_batteryev }, + + { SONYPI_DEVICE_TYPE2, 0, 0xffffffff, sonypi_releaseev }, + { SONYPI_DEVICE_TYPE2, 0x38, SONYPI_LID_MASK, sonypi_lidev }, + { SONYPI_DEVICE_TYPE2, 0x11, SONYPI_JOGGER_MASK, sonypi_joggerev }, + { SONYPI_DEVICE_TYPE2, 0x61, SONYPI_CAPTURE_MASK, sonypi_captureev }, + { SONYPI_DEVICE_TYPE2, 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev }, + { SONYPI_DEVICE_TYPE2, 0x31, SONYPI_BLUETOOTH_MASK, sonypi_blueev }, + { SONYPI_DEVICE_TYPE2, 0x08, SONYPI_PKEY_MASK, sonypi_pkeyev }, + { SONYPI_DEVICE_TYPE2, 0x11, SONYPI_BACK_MASK, sonypi_backev }, + { SONYPI_DEVICE_TYPE2, 0x21, SONYPI_HELP_MASK, sonypi_helpev }, + { SONYPI_DEVICE_TYPE2, 0x21, SONYPI_ZOOM_MASK, sonypi_zoomev }, + { SONYPI_DEVICE_TYPE2, 0x20, SONYPI_THUMBPHRASE_MASK, sonypi_thumbphraseev }, + { SONYPI_DEVICE_TYPE2, 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev }, + { SONYPI_DEVICE_TYPE2, 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev }, + { SONYPI_DEVICE_TYPE2, 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev }, + + { SONYPI_DEVICE_TYPE3, 0, 0xffffffff, sonypi_releaseev }, + { SONYPI_DEVICE_TYPE3, 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev }, + { SONYPI_DEVICE_TYPE3, 0x31, SONYPI_WIRELESS_MASK, sonypi_wlessev }, + { SONYPI_DEVICE_TYPE3, 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev }, + { SONYPI_DEVICE_TYPE3, 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev }, + { SONYPI_DEVICE_TYPE3, 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev }, + { 0 } +}; + +static int sony_pic_detect_device_type(void) +{ + struct pci_dev *pcidev; + int model = 0; + + if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82371AB_3, NULL))) + model = SONYPI_DEVICE_TYPE1; + + else if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_ICH6_1, NULL))) + model = SONYPI_DEVICE_TYPE3; + + else if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_ICH7_1, NULL))) + model = SONYPI_DEVICE_TYPE3; + + else + model = SONYPI_DEVICE_TYPE2; + + if (pcidev) + pci_dev_put(pcidev); + + printk(KERN_INFO DRV_PFX "detected Type%d model\n", + model == SONYPI_DEVICE_TYPE1 ? 1 : + model == SONYPI_DEVICE_TYPE2 ? 2 : 3); + return model; +} + +#define ITERATIONS_LONG 10000 +#define ITERATIONS_SHORT 10 +#define wait_on_command(command, iterations) { \ + unsigned int n = iterations; \ + while (--n && (command)) \ + udelay(1); \ + if (!n) \ + dprintk("command failed at %s : %s (line %d)\n", \ + __FILE__, __FUNCTION__, __LINE__); \ +} + +static u8 sony_pic_call1(u8 dev) +{ + u8 v1, v2; + + wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, + ITERATIONS_LONG); + outb(dev, spic_dev.cur_ioport->io.minimum + 4); + v1 = inb_p(spic_dev.cur_ioport->io.minimum + 4); + v2 = inb_p(spic_dev.cur_ioport->io.minimum); + dprintk("sony_pic_call1: 0x%.4x\n", (v2 << 8) | v1); + return v2; +} + +static u8 sony_pic_call2(u8 dev, u8 fn) +{ + u8 v1; + + wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, + ITERATIONS_LONG); + outb(dev, spic_dev.cur_ioport->io.minimum + 4); + wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, + ITERATIONS_LONG); + outb(fn, spic_dev.cur_ioport->io.minimum); + v1 = inb_p(spic_dev.cur_ioport->io.minimum); + dprintk("sony_pic_call2: 0x%.4x\n", v1); + return v1; +} + +static u8 sony_pic_call3(u8 dev, u8 fn, u8 v) +{ + u8 v1; + + wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, ITERATIONS_LONG); + outb(dev, spic_dev.cur_ioport->io.minimum + 4); + wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, ITERATIONS_LONG); + outb(fn, spic_dev.cur_ioport->io.minimum); + wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, ITERATIONS_LONG); + outb(v, spic_dev.cur_ioport->io.minimum); + v1 = inb_p(spic_dev.cur_ioport->io.minimum); + dprintk("sony_pic_call3: 0x%.4x\n", v1); + return v1; +} + +/* camera tests and poweron/poweroff */ +#define SONYPI_CAMERA_PICTURE 5 +#define SONYPI_CAMERA_CONTROL 0x10 + +#define SONYPI_CAMERA_BRIGHTNESS 0 +#define SONYPI_CAMERA_CONTRAST 1 +#define SONYPI_CAMERA_HUE 2 +#define SONYPI_CAMERA_COLOR 3 +#define SONYPI_CAMERA_SHARPNESS 4 + +#define SONYPI_CAMERA_EXPOSURE_MASK 0xC +#define SONYPI_CAMERA_WHITE_BALANCE_MASK 0x3 +#define SONYPI_CAMERA_PICTURE_MODE_MASK 0x30 +#define SONYPI_CAMERA_MUTE_MASK 0x40 + +/* the rest don't need a loop until not 0xff */ +#define SONYPI_CAMERA_AGC 6 +#define SONYPI_CAMERA_AGC_MASK 0x30 +#define SONYPI_CAMERA_SHUTTER_MASK 0x7 + +#define SONYPI_CAMERA_SHUTDOWN_REQUEST 7 +#define SONYPI_CAMERA_CONTROL 0x10 + +#define SONYPI_CAMERA_STATUS 7 +#define SONYPI_CAMERA_STATUS_READY 0x2 +#define SONYPI_CAMERA_STATUS_POSITION 0x4 + +#define SONYPI_DIRECTION_BACKWARDS 0x4 + +#define SONYPI_CAMERA_REVISION 8 +#define SONYPI_CAMERA_ROMVERSION 9 + +static int __sony_pic_camera_ready(void) +{ + u8 v; + + v = sony_pic_call2(0x8f, SONYPI_CAMERA_STATUS); + return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY)); +} + +static int __sony_pic_camera_off(void) +{ + if (!camera) { + printk(KERN_WARNING DRV_PFX "camera control not enabled\n"); + return -ENODEV; + } + + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, + SONYPI_CAMERA_MUTE_MASK), + ITERATIONS_SHORT); + + if (spic_dev.camera_power) { + sony_pic_call2(0x91, 0); + spic_dev.camera_power = 0; + } + return 0; +} + +static int __sony_pic_camera_on(void) { - return acpi_bus_register_driver(&sony_acpi_driver); + int i, j, x; + + if (!camera) { + printk(KERN_WARNING DRV_PFX "camera control not enabled\n"); + return -ENODEV; + } + + if (spic_dev.camera_power) + return 0; + + for (j = 5; j > 0; j--) { + + for (x = 0; x < 100 && sony_pic_call2(0x91, 0x1); x++) + msleep(10); + sony_pic_call1(0x93); + + for (i = 400; i > 0; i--) { + if (__sony_pic_camera_ready()) + break; + msleep(10); + } + if (i) + break; + } + + if (j == 0) { + printk(KERN_WARNING DRV_PFX "failed to power on camera\n"); + return -ENODEV; + } + + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTROL, + 0x5a), + ITERATIONS_SHORT); + + spic_dev.camera_power = 1; + return 0; +} + +/* External camera command (exported to the motion eye v4l driver) */ +int sony_pic_camera_command(int command, u8 value) +{ + if (!camera) + return -EIO; + + mutex_lock(&spic_dev.lock); + + switch (command) { + case SONY_PIC_COMMAND_SETCAMERA: + if (value) + __sony_pic_camera_on(); + else + __sony_pic_camera_off(); + break; + case SONY_PIC_COMMAND_SETCAMERABRIGHTNESS: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_BRIGHTNESS, value), + ITERATIONS_SHORT); + break; + case SONY_PIC_COMMAND_SETCAMERACONTRAST: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTRAST, value), + ITERATIONS_SHORT); + break; + case SONY_PIC_COMMAND_SETCAMERAHUE: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_HUE, value), + ITERATIONS_SHORT); + break; + case SONY_PIC_COMMAND_SETCAMERACOLOR: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_COLOR, value), + ITERATIONS_SHORT); + break; + case SONY_PIC_COMMAND_SETCAMERASHARPNESS: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_SHARPNESS, value), + ITERATIONS_SHORT); + break; + case SONY_PIC_COMMAND_SETCAMERAPICTURE: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, value), + ITERATIONS_SHORT); + break; + case SONY_PIC_COMMAND_SETCAMERAAGC: + wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_AGC, value), + ITERATIONS_SHORT); + break; + default: + printk(KERN_ERR DRV_PFX "sony_pic_camera_command invalid: %d\n", + command); + break; + } + mutex_unlock(&spic_dev.lock); + return 0; +} +EXPORT_SYMBOL(sony_pic_camera_command); + +/* gprs/edge modem (SZ460N and SZ210P), thanks to Joshua Wise */ +static void sony_pic_set_wwanpower(u8 state) +{ + state = !!state; + mutex_lock(&spic_dev.lock); + if (spic_dev.wwan_power == state) { + mutex_unlock(&spic_dev.lock); + return; + } + sony_pic_call2(0xB0, state); + spic_dev.wwan_power = state; + mutex_unlock(&spic_dev.lock); +} + +static ssize_t sony_pic_wwanpower_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned long value; + if (count > 31) + return -EINVAL; + + value = simple_strtoul(buffer, NULL, 10); + sony_pic_set_wwanpower(value); + + return count; +} + +static ssize_t sony_pic_wwanpower_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t count; + mutex_lock(&spic_dev.lock); + count = snprintf(buffer, PAGE_SIZE, "%d\n", spic_dev.wwan_power); + mutex_unlock(&spic_dev.lock); + return count; +} + +/* bluetooth subsystem power state */ +static void __sony_pic_set_bluetoothpower(u8 state) +{ + state = !!state; + if (spic_dev.bluetooth_power == state) + return; + sony_pic_call2(0x96, state); + sony_pic_call1(0x82); + spic_dev.bluetooth_power = state; +} + +static ssize_t sony_pic_bluetoothpower_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned long value; + if (count > 31) + return -EINVAL; + + value = simple_strtoul(buffer, NULL, 10); + mutex_lock(&spic_dev.lock); + __sony_pic_set_bluetoothpower(value); + mutex_unlock(&spic_dev.lock); + + return count; +} + +static ssize_t sony_pic_bluetoothpower_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t count = 0; + mutex_lock(&spic_dev.lock); + count = snprintf(buffer, PAGE_SIZE, "%d\n", spic_dev.bluetooth_power); + mutex_unlock(&spic_dev.lock); + return count; +} + +/* fan speed */ +/* FAN0 information (reverse engineered from ACPI tables) */ +#define SONY_PIC_FAN0_STATUS 0x93 +static int sony_pic_set_fanspeed(unsigned long value) +{ + return ec_write(SONY_PIC_FAN0_STATUS, value); +} + +static int sony_pic_get_fanspeed(u8 *value) +{ + return ec_read(SONY_PIC_FAN0_STATUS, value); +} + +static ssize_t sony_pic_fanspeed_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned long value; + if (count > 31) + return -EINVAL; + + value = simple_strtoul(buffer, NULL, 10); + if (sony_pic_set_fanspeed(value)) + return -EIO; + + return count; +} + +static ssize_t sony_pic_fanspeed_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + u8 value = 0; + if (sony_pic_get_fanspeed(&value)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", value); +} + +#define SPIC_ATTR(_name, _mode) \ +struct device_attribute spic_attr_##_name = __ATTR(_name, \ + _mode, sony_pic_## _name ##_show, \ + sony_pic_## _name ##_store) + +static SPIC_ATTR(bluetoothpower, 0644); +static SPIC_ATTR(wwanpower, 0644); +static SPIC_ATTR(fanspeed, 0644); + +static struct attribute *spic_attributes[] = { + &spic_attr_bluetoothpower.attr, + &spic_attr_wwanpower.attr, + &spic_attr_fanspeed.attr, + NULL +}; + +static struct attribute_group spic_attribute_group = { + .attrs = spic_attributes +}; + +/******** SONYPI compatibility **********/ +#ifdef CONFIG_SONY_LAPTOP_OLD + +/* battery / brightness / temperature addresses */ +#define SONYPI_BAT_FLAGS 0x81 +#define SONYPI_LCD_LIGHT 0x96 +#define SONYPI_BAT1_PCTRM 0xa0 +#define SONYPI_BAT1_LEFT 0xa2 +#define SONYPI_BAT1_MAXRT 0xa4 +#define SONYPI_BAT2_PCTRM 0xa8 +#define SONYPI_BAT2_LEFT 0xaa +#define SONYPI_BAT2_MAXRT 0xac +#define SONYPI_BAT1_MAXTK 0xb0 +#define SONYPI_BAT1_FULL 0xb2 +#define SONYPI_BAT2_MAXTK 0xb8 +#define SONYPI_BAT2_FULL 0xba +#define SONYPI_TEMP_STATUS 0xC1 + +struct sonypi_compat_s { + struct fasync_struct *fifo_async; + struct kfifo *fifo; + spinlock_t fifo_lock; + wait_queue_head_t fifo_proc_list; + atomic_t open_count; +}; +static struct sonypi_compat_s sonypi_compat = { + .open_count = ATOMIC_INIT(0), +}; + +static int sonypi_misc_fasync(int fd, struct file *filp, int on) +{ + int retval; + + retval = fasync_helper(fd, filp, on, &sonypi_compat.fifo_async); + if (retval < 0) + return retval; + return 0; +} + +static int sonypi_misc_release(struct inode *inode, struct file *file) +{ + sonypi_misc_fasync(-1, file, 0); + atomic_dec(&sonypi_compat.open_count); + return 0; +} + +static int sonypi_misc_open(struct inode *inode, struct file *file) +{ + /* Flush input queue on first open */ + if (atomic_inc_return(&sonypi_compat.open_count) == 1) + kfifo_reset(sonypi_compat.fifo); + return 0; +} + +static ssize_t sonypi_misc_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + ssize_t ret; + unsigned char c; + + if ((kfifo_len(sonypi_compat.fifo) == 0) && + (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + ret = wait_event_interruptible(sonypi_compat.fifo_proc_list, + kfifo_len(sonypi_compat.fifo) != 0); + if (ret) + return ret; + + while (ret < count && + (kfifo_get(sonypi_compat.fifo, &c, sizeof(c)) == sizeof(c))) { + if (put_user(c, buf++)) + return -EFAULT; + ret++; + } + + if (ret > 0) { + struct inode *inode = file->f_path.dentry->d_inode; + inode->i_atime = current_fs_time(inode->i_sb); + } + + return ret; +} + +static unsigned int sonypi_misc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &sonypi_compat.fifo_proc_list, wait); + if (kfifo_len(sonypi_compat.fifo)) + return POLLIN | POLLRDNORM; + return 0; +} + +static int ec_read16(u8 addr, u16 *value) +{ + u8 val_lb, val_hb; + if (ec_read(addr, &val_lb)) + return -1; + if (ec_read(addr + 1, &val_hb)) + return -1; + *value = val_lb | (val_hb << 8); + return 0; +} + +static int sonypi_misc_ioctl(struct inode *ip, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + void __user *argp = (void __user *)arg; + u8 val8; + u16 val16; + int value; + + mutex_lock(&spic_dev.lock); + switch (cmd) { + case SONYPI_IOCGBRT: + if (sony_backlight_device == NULL) { + ret = -EIO; + break; + } + if (acpi_callgetfunc(sony_nc_acpi_handle, "GBRT", &value)) { + ret = -EIO; + break; + } + val8 = ((value & 0xff) - 1) << 5; + if (copy_to_user(argp, &val8, sizeof(val8))) + ret = -EFAULT; + break; + case SONYPI_IOCSBRT: + if (sony_backlight_device == NULL) { + ret = -EIO; + break; + } + if (copy_from_user(&val8, argp, sizeof(val8))) { + ret = -EFAULT; + break; + } + if (acpi_callsetfunc(sony_nc_acpi_handle, "SBRT", + (val8 >> 5) + 1, NULL)) { + ret = -EIO; + break; + } + /* sync the backlight device status */ + sony_backlight_device->props.brightness = + sony_backlight_get_brightness(sony_backlight_device); + break; + case SONYPI_IOCGBAT1CAP: + if (ec_read16(SONYPI_BAT1_FULL, &val16)) { + ret = -EIO; + break; + } + if (copy_to_user(argp, &val16, sizeof(val16))) + ret = -EFAULT; + break; + case SONYPI_IOCGBAT1REM: + if (ec_read16(SONYPI_BAT1_LEFT, &val16)) { + ret = -EIO; + break; + } + if (copy_to_user(argp, &val16, sizeof(val16))) + ret = -EFAULT; + break; + case SONYPI_IOCGBAT2CAP: + if (ec_read16(SONYPI_BAT2_FULL, &val16)) { + ret = -EIO; + break; + } + if (copy_to_user(argp, &val16, sizeof(val16))) + ret = -EFAULT; + break; + case SONYPI_IOCGBAT2REM: + if (ec_read16(SONYPI_BAT2_LEFT, &val16)) { + ret = -EIO; + break; + } + if (copy_to_user(argp, &val16, sizeof(val16))) + ret = -EFAULT; + break; + case SONYPI_IOCGBATFLAGS: + if (ec_read(SONYPI_BAT_FLAGS, &val8)) { + ret = -EIO; + break; + } + val8 &= 0x07; + if (copy_to_user(argp, &val8, sizeof(val8))) + ret = -EFAULT; + break; + case SONYPI_IOCGBLUE: + val8 = spic_dev.bluetooth_power; + if (copy_to_user(argp, &val8, sizeof(val8))) + ret = -EFAULT; + break; + case SONYPI_IOCSBLUE: + if (copy_from_user(&val8, argp, sizeof(val8))) { + ret = -EFAULT; + break; + } + __sony_pic_set_bluetoothpower(val8); + break; + /* FAN Controls */ + case SONYPI_IOCGFAN: + if (sony_pic_get_fanspeed(&val8)) { + ret = -EIO; + break; + } + if (copy_to_user(argp, &val8, sizeof(val8))) + ret = -EFAULT; + break; + case SONYPI_IOCSFAN: + if (copy_from_user(&val8, argp, sizeof(val8))) { + ret = -EFAULT; + break; + } + if (sony_pic_set_fanspeed(val8)) + ret = -EIO; + break; + /* GET Temperature (useful under APM) */ + case SONYPI_IOCGTEMP: + if (ec_read(SONYPI_TEMP_STATUS, &val8)) { + ret = -EIO; + break; + } + if (copy_to_user(argp, &val8, sizeof(val8))) + ret = -EFAULT; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&spic_dev.lock); + return ret; +} + +static const struct file_operations sonypi_misc_fops = { + .owner = THIS_MODULE, + .read = sonypi_misc_read, + .poll = sonypi_misc_poll, + .open = sonypi_misc_open, + .release = sonypi_misc_release, + .fasync = sonypi_misc_fasync, + .ioctl = sonypi_misc_ioctl, +}; + +static struct miscdevice sonypi_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "sonypi", + .fops = &sonypi_misc_fops, +}; + +static void sonypi_compat_report_event(u8 event) +{ + kfifo_put(sonypi_compat.fifo, (unsigned char *)&event, sizeof(event)); + kill_fasync(&sonypi_compat.fifo_async, SIGIO, POLL_IN); + wake_up_interruptible(&sonypi_compat.fifo_proc_list); +} + +static int sonypi_compat_init(void) +{ + int error; + + spin_lock_init(&sonypi_compat.fifo_lock); + sonypi_compat.fifo = kfifo_alloc(SONY_LAPTOP_BUF_SIZE, GFP_KERNEL, + &sonypi_compat.fifo_lock); + if (IS_ERR(sonypi_compat.fifo)) { + printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n"); + return PTR_ERR(sonypi_compat.fifo); + } + + init_waitqueue_head(&sonypi_compat.fifo_proc_list); + + if (minor != -1) + sonypi_misc_device.minor = minor; + error = misc_register(&sonypi_misc_device); + if (error) { + printk(KERN_ERR DRV_PFX "misc_register failed\n"); + goto err_free_kfifo; + } + if (minor == -1) + printk(KERN_INFO DRV_PFX "device allocated minor is %d\n", + sonypi_misc_device.minor); + + return 0; + +err_free_kfifo: + kfifo_free(sonypi_compat.fifo); + return error; +} + +static void sonypi_compat_exit(void) +{ + misc_deregister(&sonypi_misc_device); + kfifo_free(sonypi_compat.fifo); +} +#else +static int sonypi_compat_init(void) { return 0; } +static void sonypi_compat_exit(void) { } +static void sonypi_compat_report_event(u8 event) { } +#endif /* CONFIG_SONY_LAPTOP_OLD */ + +/* + * ACPI callbacks + */ +static acpi_status +sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) +{ + u32 i; + struct sony_pic_dev *dev = (struct sony_pic_dev *)context; + + switch (resource->type) { + case ACPI_RESOURCE_TYPE_START_DEPENDENT: + case ACPI_RESOURCE_TYPE_END_DEPENDENT: + return AE_OK; + + case ACPI_RESOURCE_TYPE_IRQ: + { + struct acpi_resource_irq *p = &resource->data.irq; + struct sony_pic_irq *interrupt = NULL; + if (!p || !p->interrupt_count) { + /* + * IRQ descriptors may have no IRQ# bits set, + * particularly those those w/ _STA disabled + */ + dprintk("Blank IRQ resource\n"); + return AE_OK; + } + for (i = 0; i < p->interrupt_count; i++) { + if (!p->interrupts[i]) { + printk(KERN_WARNING DRV_PFX + "Invalid IRQ %d\n", + p->interrupts[i]); + continue; + } + interrupt = kzalloc(sizeof(*interrupt), + GFP_KERNEL); + if (!interrupt) + return AE_ERROR; + + list_add_tail(&interrupt->list, &dev->interrupts); + interrupt->irq.triggering = p->triggering; + interrupt->irq.polarity = p->polarity; + interrupt->irq.sharable = p->sharable; + interrupt->irq.interrupt_count = 1; + interrupt->irq.interrupts[0] = p->interrupts[i]; + } + return AE_OK; + } + case ACPI_RESOURCE_TYPE_IO: + { + struct acpi_resource_io *io = &resource->data.io; + struct sony_pic_ioport *ioport = NULL; + if (!io) { + dprintk("Blank IO resource\n"); + return AE_OK; + } + + ioport = kzalloc(sizeof(*ioport), GFP_KERNEL); + if (!ioport) + return AE_ERROR; + + list_add_tail(&ioport->list, &dev->ioports); + memcpy(&ioport->io, io, sizeof(*io)); + return AE_OK; + } + default: + dprintk("Resource %d isn't an IRQ nor an IO port\n", + resource->type); + + case ACPI_RESOURCE_TYPE_END_TAG: + return AE_OK; + } + return AE_CTRL_TERMINATE; +} + +static int sony_pic_possible_resources(struct acpi_device *device) +{ + int result = 0; + acpi_status status = AE_OK; + + if (!device) + return -EINVAL; + + /* get device status */ + /* see acpi_pci_link_get_current acpi_pci_link_get_possible */ + dprintk("Evaluating _STA\n"); + result = acpi_bus_get_status(device); + if (result) { + printk(KERN_WARNING DRV_PFX "Unable to read status\n"); + goto end; + } + + if (!device->status.enabled) + dprintk("Device disabled\n"); + else + dprintk("Device enabled\n"); + + /* + * Query and parse 'method' + */ + dprintk("Evaluating %s\n", METHOD_NAME__PRS); + status = acpi_walk_resources(device->handle, METHOD_NAME__PRS, + sony_pic_read_possible_resource, &spic_dev); + if (ACPI_FAILURE(status)) { + printk(KERN_WARNING DRV_PFX + "Failure evaluating %s\n", + METHOD_NAME__PRS); + result = -ENODEV; + } +end: + return result; +} + +/* + * Disable the spic device by calling its _DIS method + */ +static int sony_pic_disable(struct acpi_device *device) +{ + if (ACPI_FAILURE(acpi_evaluate_object(device->handle, "_DIS", 0, NULL))) + return -ENXIO; + + dprintk("Device disabled\n"); + return 0; +} + + +/* + * Based on drivers/acpi/pci_link.c:acpi_pci_link_set + * + * Call _SRS to set current resources + */ +static int sony_pic_enable(struct acpi_device *device, + struct sony_pic_ioport *ioport, struct sony_pic_irq *irq) +{ + acpi_status status; + int result = 0; + struct { + struct acpi_resource io_res; + struct acpi_resource irq_res; + struct acpi_resource end; + } *resource; + struct acpi_buffer buffer = { 0, NULL }; + + if (!ioport || !irq) + return -EINVAL; + + /* init acpi_buffer */ + resource = kzalloc(sizeof(*resource) + 1, GFP_KERNEL); + if (!resource) + return -ENOMEM; + + buffer.length = sizeof(*resource) + 1; + buffer.pointer = resource; + + /* setup io resource */ + resource->io_res.type = ACPI_RESOURCE_TYPE_IO; + resource->io_res.length = sizeof(struct acpi_resource); + memcpy(&resource->io_res.data.io, &ioport->io, + sizeof(struct acpi_resource_io)); + + /* setup irq resource */ + resource->irq_res.type = ACPI_RESOURCE_TYPE_IRQ; + resource->irq_res.length = sizeof(struct acpi_resource); + memcpy(&resource->irq_res.data.irq, &irq->irq, + sizeof(struct acpi_resource_irq)); + /* we requested a shared irq */ + resource->irq_res.data.irq.sharable = ACPI_SHARED; + + resource->end.type = ACPI_RESOURCE_TYPE_END_TAG; + + /* Attempt to set the resource */ + dprintk("Evaluating _SRS\n"); + status = acpi_set_current_resources(device->handle, &buffer); + + /* check for total failure */ + if (ACPI_FAILURE(status)) { + printk(KERN_ERR DRV_PFX "Error evaluating _SRS"); + result = -ENODEV; + goto end; + } + + /* Necessary device initializations calls (from sonypi) */ + sony_pic_call1(0x82); + sony_pic_call2(0x81, 0xff); + sony_pic_call1(compat ? 0x92 : 0x82); + +end: + kfree(resource); + return result; +} + +/***************** + * + * ISR: some event is available + * + *****************/ +static irqreturn_t sony_pic_irq(int irq, void *dev_id) +{ + int i, j; + u32 port_val = 0; + u8 ev = 0; + u8 data_mask = 0; + u8 device_event = 0; + + struct sony_pic_dev *dev = (struct sony_pic_dev *) dev_id; + + acpi_os_read_port(dev->cur_ioport->io.minimum, &port_val, + dev->cur_ioport->io.address_length); + ev = port_val & SONY_PIC_EV_MASK; + data_mask = 0xff & (port_val >> (dev->cur_ioport->io.address_length - 8)); + + dprintk("event (0x%.8x [%.2x] [%.2x]) at port 0x%.4x\n", + port_val, ev, data_mask, dev->cur_ioport->io.minimum); + + if (ev == 0x00 || ev == 0xff) + return IRQ_HANDLED; + + for (i = 0; sony_pic_eventtypes[i].model; i++) { + + if (spic_dev.model != sony_pic_eventtypes[i].model) + continue; + + if ((data_mask & sony_pic_eventtypes[i].data) != + sony_pic_eventtypes[i].data) + continue; + + if (!(mask & sony_pic_eventtypes[i].mask)) + continue; + + for (j = 0; sony_pic_eventtypes[i].events[j].event; j++) { + if (ev == sony_pic_eventtypes[i].events[j].data) { + device_event = + sony_pic_eventtypes[i].events[j].event; + goto found; + } + } + } + return IRQ_HANDLED; + +found: + sony_laptop_report_input_event(device_event); + acpi_bus_generate_event(spic_dev.acpi_dev, 1, device_event); + sonypi_compat_report_event(device_event); + + return IRQ_HANDLED; +} + +/***************** + * + * ACPI driver + * + *****************/ +static int sony_pic_remove(struct acpi_device *device, int type) +{ + struct sony_pic_ioport *io, *tmp_io; + struct sony_pic_irq *irq, *tmp_irq; + + sonypi_compat_exit(); + + if (sony_pic_disable(device)) { + printk(KERN_ERR DRV_PFX "Couldn't disable device.\n"); + return -ENXIO; + } + + free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev); + release_region(spic_dev.cur_ioport->io.minimum, + spic_dev.cur_ioport->io.address_length); + + sony_laptop_remove_input(); + + /* pf attrs */ + sysfs_remove_group(&sony_pf_device->dev.kobj, &spic_attribute_group); + sony_pf_remove(); + + list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) { + list_del(&io->list); + kfree(io); + } + list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) { + list_del(&irq->list); + kfree(irq); + } + spic_dev.cur_ioport = NULL; + spic_dev.cur_irq = NULL; + + dprintk(SONY_PIC_DRIVER_NAME " removed.\n"); + return 0; +} + +static int sony_pic_add(struct acpi_device *device) +{ + int result; + struct sony_pic_ioport *io, *tmp_io; + struct sony_pic_irq *irq, *tmp_irq; + + printk(KERN_INFO DRV_PFX "%s v%s.\n", + SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); + + spic_dev.acpi_dev = device; + strcpy(acpi_device_class(device), "sony/hotkey"); + spic_dev.model = sony_pic_detect_device_type(); + mutex_init(&spic_dev.lock); + + /* read _PRS resources */ + result = sony_pic_possible_resources(device); + if (result) { + printk(KERN_ERR DRV_PFX + "Unabe to read possible resources.\n"); + goto err_free_resources; + } + + /* setup input devices and helper fifo */ + result = sony_laptop_setup_input(); + if (result) { + printk(KERN_ERR DRV_PFX + "Unabe to create input devices.\n"); + goto err_free_resources; + } + + /* request io port */ + list_for_each_entry(io, &spic_dev.ioports, list) { + if (request_region(io->io.minimum, io->io.address_length, + "Sony Programable I/O Device")) { + dprintk("I/O port: 0x%.4x (0x%.4x) + 0x%.2x\n", + io->io.minimum, io->io.maximum, + io->io.address_length); + spic_dev.cur_ioport = io; + break; + } + } + if (!spic_dev.cur_ioport) { + printk(KERN_ERR DRV_PFX "Failed to request_region.\n"); + result = -ENODEV; + goto err_remove_input; + } + + /* request IRQ */ + list_for_each_entry(irq, &spic_dev.interrupts, list) { + if (!request_irq(irq->irq.interrupts[0], sony_pic_irq, + IRQF_SHARED, "sony-laptop", &spic_dev)) { + dprintk("IRQ: %d - triggering: %d - " + "polarity: %d - shr: %d\n", + irq->irq.interrupts[0], + irq->irq.triggering, + irq->irq.polarity, + irq->irq.sharable); + spic_dev.cur_irq = irq; + break; + } + } + if (!spic_dev.cur_irq) { + printk(KERN_ERR DRV_PFX "Failed to request_irq.\n"); + result = -ENODEV; + goto err_release_region; + } + + /* set resource status _SRS */ + result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); + if (result) { + printk(KERN_ERR DRV_PFX "Couldn't enable device.\n"); + goto err_free_irq; + } + + spic_dev.bluetooth_power = -1; + /* create device attributes */ + result = sony_pf_add(); + if (result) + goto err_disable_device; + + result = sysfs_create_group(&sony_pf_device->dev.kobj, &spic_attribute_group); + if (result) + goto err_remove_pf; + + if (sonypi_compat_init()) + goto err_remove_pf; + + return 0; + +err_remove_pf: + sony_pf_remove(); + +err_disable_device: + sony_pic_disable(device); + +err_free_irq: + free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev); + +err_release_region: + release_region(spic_dev.cur_ioport->io.minimum, + spic_dev.cur_ioport->io.address_length); + +err_remove_input: + sony_laptop_remove_input(); + +err_free_resources: + list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) { + list_del(&io->list); + kfree(io); + } + list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) { + list_del(&irq->list); + kfree(irq); + } + spic_dev.cur_ioport = NULL; + spic_dev.cur_irq = NULL; + + return result; +} + +static int sony_pic_suspend(struct acpi_device *device, pm_message_t state) +{ + if (sony_pic_disable(device)) + return -ENXIO; + return 0; +} + +static int sony_pic_resume(struct acpi_device *device) +{ + sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); + return 0; +} + +static struct acpi_driver sony_pic_driver = { + .name = SONY_PIC_DRIVER_NAME, + .class = SONY_PIC_CLASS, + .ids = SONY_PIC_HID, + .owner = THIS_MODULE, + .ops = { + .add = sony_pic_add, + .remove = sony_pic_remove, + .suspend = sony_pic_suspend, + .resume = sony_pic_resume, + }, +}; + +static struct dmi_system_id __initdata sonypi_dmi_table[] = { + { + .ident = "Sony Vaio", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "PCG-"), + }, + }, + { + .ident = "Sony Vaio", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "VGN-"), + }, + }, + { } +}; + +static int __init sony_laptop_init(void) +{ + int result; + + if (!no_spic && dmi_check_system(sonypi_dmi_table)) { + result = acpi_bus_register_driver(&sony_pic_driver); + if (result) { + printk(KERN_ERR DRV_PFX + "Unable to register SPIC driver."); + goto out; + } + } + + result = acpi_bus_register_driver(&sony_nc_driver); + if (result) { + printk(KERN_ERR DRV_PFX "Unable to register SNC driver."); + goto out_unregister_pic; + } + + return 0; + +out_unregister_pic: + if (!no_spic) + acpi_bus_unregister_driver(&sony_pic_driver); +out: + return result; } -static void __exit sony_acpi_exit(void) +static void __exit sony_laptop_exit(void) { - acpi_bus_unregister_driver(&sony_acpi_driver); + acpi_bus_unregister_driver(&sony_nc_driver); + if (!no_spic) + acpi_bus_unregister_driver(&sony_pic_driver); } -module_init(sony_acpi_init); -module_exit(sony_acpi_exit); +module_init(sony_laptop_init); +module_exit(sony_laptop_exit); diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c new file mode 100644 index 000000000000..6c36a55cb3d1 --- /dev/null +++ b/drivers/misc/thinkpad_acpi.c @@ -0,0 +1,4312 @@ +/* + * thinkpad_acpi.c - ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net> + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh <hmh@hmh.eng.br> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#define IBM_VERSION "0.14" +#define TPACPI_SYSFS_VERSION 0x000100 + +/* + * Changelog: + * 2007-03-27 0.14 renamed to thinkpad_acpi and moved to + * drivers/misc. + * + * 2006-11-22 0.13 new maintainer + * changelog now lives in git commit history, and will + * not be updated further in-file. + * + * 2005-08-17 0.12 fix compilation on 2.6.13-rc kernels + * 2005-03-17 0.11 support for 600e, 770x + * thanks to Jamie Lentin <lentinj@dial.pipex.com> + * support for 770e, G41 + * G40 and G41 don't have a thinklight + * temperatures no longer experimental + * experimental brightness control + * experimental volume control + * experimental fan enable/disable + * 2005-01-16 0.10 fix module loading on R30, R31 + * 2005-01-16 0.9 support for 570, R30, R31 + * ultrabay support on A22p, A3x + * limit arg for cmos, led, beep, drop experimental status + * more capable led control on A21e, A22p, T20-22, X20 + * experimental temperatures and fan speed + * experimental embedded controller register dump + * mark more functions as __init, drop incorrect __exit + * use MODULE_VERSION + * thanks to Henrik Brix Andersen <brix@gentoo.org> + * fix parameter passing on module loading + * thanks to Rusty Russell <rusty@rustcorp.com.au> + * thanks to Jim Radford <radford@blackbean.org> + * 2004-11-08 0.8 fix init error case, don't return from a macro + * thanks to Chris Wright <chrisw@osdl.org> + * 2004-10-23 0.7 fix module loading on A21e, A22p, T20, T21, X20 + * fix led control on A21e + * 2004-10-19 0.6 use acpi_bus_register_driver() to claim HKEY device + * 2004-10-18 0.5 thinklight support on A21e, G40, R32, T20, T21, X20 + * proc file format changed + * video_switch command + * experimental cmos control + * experimental led control + * experimental acpi sounds + * 2004-09-16 0.4 support for module parameters + * hotkey mask can be prefixed by 0x + * video output switching + * video expansion control + * ultrabay eject support + * removed lcd brightness/on/off control, didn't work + * 2004-08-17 0.3 support for R40 + * lcd off, brightness control + * thinklight on/off + * 2004-08-14 0.2 support for T series, X20 + * bluetooth enable/disable + * hotkey events disabled by default + * removed fan control, currently useless + * 2004-08-09 0.1 initial release, support for X series + */ + +#include "thinkpad_acpi.h" + +MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); +MODULE_DESCRIPTION(IBM_DESC); +MODULE_VERSION(IBM_VERSION); +MODULE_LICENSE("GPL"); + +/* Please remove this in year 2009 */ +MODULE_ALIAS("ibm_acpi"); + +#define __unused __attribute__ ((unused)) + +/**************************************************************************** + **************************************************************************** + * + * ACPI Helpers and device model + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ACPI basic handles + */ + +static acpi_handle root_handle = NULL; + +#define IBM_HANDLE(object, parent, paths...) \ + static acpi_handle object##_handle; \ + static acpi_handle *object##_parent = &parent##_handle; \ + static char *object##_path; \ + static char *object##_paths[] = { paths } + +IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ + "\\_SB.PCI.ISA.EC", /* 570 */ + "\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */ + "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */ + "\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */ + "\\_SB.PCI0.ICH3.EC0", /* R31 */ + "\\_SB.PCI0.LPC.EC", /* all others */ + ); + +IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ +IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ + + +/************************************************************************* + * Misc ACPI handles + */ + +IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */ + "\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */ + "\\CMS", /* R40, R40e */ + ); /* all others */ + +IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ + "^HKEY", /* R30, R31 */ + "HKEY", /* all others */ + ); /* 570 */ + + +/************************************************************************* + * ACPI helpers + */ + +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...) +{ + char *fmt0 = fmt; + struct acpi_object_list params; + union acpi_object in_objs[IBM_MAX_ACPI_ARGS]; + struct acpi_buffer result, *resultp; + union acpi_object out_obj; + acpi_status status; + va_list ap; + char res_type; + int success; + int quiet; + + if (!*fmt) { + printk(IBM_ERR "acpi_evalf() called with empty format\n"); + return 0; + } + + if (*fmt == 'q') { + quiet = 1; + fmt++; + } else + quiet = 0; + + res_type = *(fmt++); + + params.count = 0; + params.pointer = &in_objs[0]; + + va_start(ap, fmt); + while (*fmt) { + char c = *(fmt++); + switch (c) { + case 'd': /* int */ + in_objs[params.count].integer.value = va_arg(ap, int); + in_objs[params.count++].type = ACPI_TYPE_INTEGER; + break; + /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", c); + return 0; + } + } + va_end(ap); + + if (res_type != 'v') { + result.length = sizeof(out_obj); + result.pointer = &out_obj; + resultp = &result; + } else + resultp = NULL; + + status = acpi_evaluate_object(handle, method, ¶ms, resultp); + + switch (res_type) { + case 'd': /* int */ + if (res) + *(int *)res = out_obj.integer.value; + success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; + break; + case 'v': /* void */ + success = status == AE_OK; + break; + /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", res_type); + return 0; + } + + if (!success && !quiet) + printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", + method, fmt0, status); + + return success; +} + +static void __unused acpi_print_int(acpi_handle handle, char *method) +{ + int i; + + if (acpi_evalf(handle, &i, method, "d")) + printk(IBM_INFO "%s = 0x%x\n", method, i); + else + printk(IBM_ERR "error calling %s\n", method); +} + +static int acpi_ec_read(int i, u8 * p) +{ + int v; + + if (ecrd_handle) { + if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i)) + return 0; + *p = v; + } else { + if (ec_read(i, p) < 0) + return 0; + } + + return 1; +} + +static int acpi_ec_write(int i, u8 v) +{ + if (ecwr_handle) { + if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v)) + return 0; + } else { + if (ec_write(i, v) < 0) + return 0; + } + + return 1; +} + +static int _sta(acpi_handle handle) +{ + int status; + + if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) + status = 0; + + return status; +} + +static int issue_thinkpad_cmos_command(int cmos_cmd) +{ + if (!cmos_handle) + return -ENXIO; + + if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd)) + return -EIO; + + return 0; +} + +/************************************************************************* + * ACPI device model + */ + +static void drv_acpi_handle_init(char *name, + acpi_handle *handle, acpi_handle parent, + char **paths, int num_paths, char **path) +{ + int i; + acpi_status status; + + vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n", + name); + + for (i = 0; i < num_paths; i++) { + status = acpi_get_handle(parent, paths[i], handle); + if (ACPI_SUCCESS(status)) { + *path = paths[i]; + dbg_printk(TPACPI_DBG_INIT, + "Found ACPI handle %s for %s\n", + *path, name); + return; + } + } + + vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n", + name); + *handle = NULL; +} + +static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct ibm_struct *ibm = data; + + if (!ibm || !ibm->acpi || !ibm->acpi->notify) + return; + + ibm->acpi->notify(ibm, event); +} + +static int __init setup_acpi_notify(struct ibm_struct *ibm) +{ + acpi_status status; + int rc; + + BUG_ON(!ibm->acpi); + + if (!*ibm->acpi->handle) + return 0; + + vdbg_printk(TPACPI_DBG_INIT, + "setting up ACPI notify for %s\n", ibm->name); + + rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); + if (rc < 0) { + printk(IBM_ERR "acpi_bus_get_device(%s) failed: %d\n", + ibm->name, rc); + return -ENODEV; + } + + acpi_driver_data(ibm->acpi->device) = ibm; + sprintf(acpi_device_class(ibm->acpi->device), "%s/%s", + IBM_ACPI_EVENT_PREFIX, + ibm->name); + + status = acpi_install_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, dispatch_acpi_notify, ibm); + if (ACPI_FAILURE(status)) { + if (status == AE_ALREADY_EXISTS) { + printk(IBM_NOTICE "another device driver is already handling %s events\n", + ibm->name); + } else { + printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n", + ibm->name, status); + } + return -ENODEV; + } + ibm->flags.acpi_notify_installed = 1; + return 0; +} + +static int __init tpacpi_device_add(struct acpi_device *device) +{ + return 0; +} + +static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) +{ + int rc; + + dbg_printk(TPACPI_DBG_INIT, + "registering %s as an ACPI driver\n", ibm->name); + + BUG_ON(!ibm->acpi); + + ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->acpi->driver) { + printk(IBM_ERR "kzalloc(ibm->driver) failed\n"); + return -ENOMEM; + } + + sprintf(ibm->acpi->driver->name, "%s_%s", IBM_NAME, ibm->name); + ibm->acpi->driver->ids = ibm->acpi->hid; + ibm->acpi->driver->ops.add = &tpacpi_device_add; + + rc = acpi_bus_register_driver(ibm->acpi->driver); + if (rc < 0) { + printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", + ibm->acpi->hid, rc); + kfree(ibm->acpi->driver); + ibm->acpi->driver = NULL; + } else if (!rc) + ibm->flags.acpi_driver_registered = 1; + + return rc; +} + + +/**************************************************************************** + **************************************************************************** + * + * Procfs Helpers + * + **************************************************************************** + ****************************************************************************/ + +static int dispatch_procfs_read(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct ibm_struct *ibm = data; + int len; + + if (!ibm || !ibm->read) + return -EINVAL; + + len = ibm->read(page); + if (len < 0) + return len; + + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + + return len; +} + +static int dispatch_procfs_write(struct file *file, + const char __user * userbuf, + unsigned long count, void *data) +{ + struct ibm_struct *ibm = data; + char *kernbuf; + int ret; + + if (!ibm || !ibm->write) + return -EINVAL; + + kernbuf = kmalloc(count + 2, GFP_KERNEL); + if (!kernbuf) + return -ENOMEM; + + if (copy_from_user(kernbuf, userbuf, count)) { + kfree(kernbuf); + return -EFAULT; + } + + kernbuf[count] = 0; + strcat(kernbuf, ","); + ret = ibm->write(kernbuf); + if (ret == 0) + ret = count; + + kfree(kernbuf); + + return ret; +} + +static char *next_cmd(char **cmds) +{ + char *start = *cmds; + char *end; + + while ((end = strchr(start, ',')) && end == start) + start = end + 1; + + if (!end) + return NULL; + + *end = 0; + *cmds = end + 1; + return start; +} + + +/**************************************************************************** + **************************************************************************** + * + * Device model: hwmon and platform + * + **************************************************************************** + ****************************************************************************/ + +static struct platform_device *tpacpi_pdev = NULL; +static struct class_device *tpacpi_hwmon = NULL; + +static struct platform_driver tpacpi_pdriver = { + .driver = { + .name = IBM_DRVR_NAME, + .owner = THIS_MODULE, + }, +}; + + +/************************************************************************* + * thinkpad-acpi driver attributes + */ + +/* interface_version --------------------------------------------------- */ +static ssize_t tpacpi_driver_interface_version_show( + struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION); +} + +static DRIVER_ATTR(interface_version, S_IRUGO, + tpacpi_driver_interface_version_show, NULL); + +/* debug_level --------------------------------------------------------- */ +static ssize_t tpacpi_driver_debug_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level); +} + +static ssize_t tpacpi_driver_debug_store(struct device_driver *drv, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 0xffff, &t)) + return -EINVAL; + + dbg_level = t; + + return count; +} + +static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, + tpacpi_driver_debug_show, tpacpi_driver_debug_store); + +/* version ------------------------------------------------------------- */ +static ssize_t tpacpi_driver_version_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION); +} + +static DRIVER_ATTR(version, S_IRUGO, + tpacpi_driver_version_show, NULL); + +/* --------------------------------------------------------------------- */ + +static struct driver_attribute* tpacpi_driver_attributes[] = { + &driver_attr_debug_level, &driver_attr_version, + &driver_attr_interface_version, +}; + +static int __init tpacpi_create_driver_attributes(struct device_driver *drv) +{ + int i, res; + + i = 0; + res = 0; + while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) { + res = driver_create_file(drv, tpacpi_driver_attributes[i]); + i++; + } + + return res; +} + +static void tpacpi_remove_driver_attributes(struct device_driver *drv) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++) + driver_remove_file(drv, tpacpi_driver_attributes[i]); +} + +/************************************************************************* + * sysfs support helpers + */ + +struct attribute_set_obj { + struct attribute_set s; + struct attribute *a; +} __attribute__((packed)); + +static struct attribute_set *create_attr_set(unsigned int max_members, + const char* name) +{ + struct attribute_set_obj *sobj; + + if (max_members == 0) + return NULL; + + /* Allocates space for implicit NULL at the end too */ + sobj = kzalloc(sizeof(struct attribute_set_obj) + + max_members * sizeof(struct attribute *), + GFP_KERNEL); + if (!sobj) + return NULL; + sobj->s.max_members = max_members; + sobj->s.group.attrs = &sobj->a; + sobj->s.group.name = name; + + return &sobj->s; +} + +/* not multi-threaded safe, use it in a single thread per set */ +static int add_to_attr_set(struct attribute_set* s, struct attribute *attr) +{ + if (!s || !attr) + return -EINVAL; + + if (s->members >= s->max_members) + return -ENOMEM; + + s->group.attrs[s->members] = attr; + s->members++; + + return 0; +} + +static int add_many_to_attr_set(struct attribute_set* s, + struct attribute **attr, + unsigned int count) +{ + int i, res; + + for (i = 0; i < count; i++) { + res = add_to_attr_set(s, attr[i]); + if (res) + return res; + } + + return 0; +} + +static void delete_attr_set(struct attribute_set* s, struct kobject *kobj) +{ + sysfs_remove_group(kobj, &s->group); + destroy_attr_set(s); +} + +static int parse_strtoul(const char *buf, + unsigned long max, unsigned long *value) +{ + char *endp; + + *value = simple_strtoul(buf, &endp, 0); + while (*endp && isspace(*endp)) + endp++; + if (*endp || *value > max) + return -EINVAL; + + return 0; +} + +/**************************************************************************** + **************************************************************************** + * + * Subdrivers + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * thinkpad-acpi init subdriver + */ + +static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm) +{ + printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); + printk(IBM_INFO "%s\n", IBM_URL); + + if (ibm_thinkpad_ec_found) + printk(IBM_INFO "ThinkPad EC firmware %s\n", + ibm_thinkpad_ec_found); + + return 0; +} + +static int thinkpad_acpi_driver_read(char *p) +{ + int len = 0; + + len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC); + len += sprintf(p + len, "version:\t%s\n", IBM_VERSION); + + return len; +} + +static struct ibm_struct thinkpad_acpi_driver_data = { + .name = "driver", + .read = thinkpad_acpi_driver_read, +}; + +/************************************************************************* + * Hotkey subdriver + */ + +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static struct attribute_set *hotkey_dev_attributes = NULL; + +/* sysfs hotkey enable ------------------------------------------------- */ +static ssize_t hotkey_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, status, mask; + + res = hotkey_get(&status, &mask); + if (res) + return res; + + return snprintf(buf, PAGE_SIZE, "%d\n", status); +} + +static ssize_t hotkey_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, status, mask; + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + res = hotkey_get(&status, &mask); + if (!res) + res = hotkey_set(t, mask); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_hotkey_enable = + __ATTR(enable, S_IWUSR | S_IRUGO, + hotkey_enable_show, hotkey_enable_store); + +/* sysfs hotkey mask --------------------------------------------------- */ +static ssize_t hotkey_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, status, mask; + + res = hotkey_get(&status, &mask); + if (res) + return res; + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", mask); +} + +static ssize_t hotkey_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, status, mask; + + if (parse_strtoul(buf, 0xffff, &t)) + return -EINVAL; + + res = hotkey_get(&status, &mask); + if (!res) + hotkey_set(status, t); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_hotkey_mask = + __ATTR(mask, S_IWUSR | S_IRUGO, + hotkey_mask_show, hotkey_mask_store); + +/* sysfs hotkey bios_enabled ------------------------------------------- */ +static ssize_t hotkey_bios_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_orig_status); +} + +static struct device_attribute dev_attr_hotkey_bios_enabled = + __ATTR(bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL); + +/* sysfs hotkey bios_mask ---------------------------------------------- */ +static ssize_t hotkey_bios_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%04x\n", hotkey_orig_mask); +} + +static struct device_attribute dev_attr_hotkey_bios_mask = + __ATTR(bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL); + +/* --------------------------------------------------------------------- */ + +static struct attribute *hotkey_mask_attributes[] = { + &dev_attr_hotkey_mask.attr, + &dev_attr_hotkey_bios_enabled.attr, + &dev_attr_hotkey_bios_mask.attr, +}; + +static int __init hotkey_init(struct ibm_init_struct *iibm) +{ + int res; + + vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n"); + + IBM_ACPIHANDLE_INIT(hkey); + mutex_init(&hotkey_mutex); + + /* hotkey not supported on 570 */ + tp_features.hotkey = hkey_handle != NULL; + + vdbg_printk(TPACPI_DBG_INIT, "hotkeys are %s\n", + str_supported(tp_features.hotkey)); + + if (tp_features.hotkey) { + hotkey_dev_attributes = create_attr_set(4, + TPACPI_HOTKEY_SYSFS_GROUP); + if (!hotkey_dev_attributes) + return -ENOMEM; + res = add_to_attr_set(hotkey_dev_attributes, + &dev_attr_hotkey_enable.attr); + if (res) + return res; + + /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, + A30, R30, R31, T20-22, X20-21, X22-24 */ + tp_features.hotkey_mask = + acpi_evalf(hkey_handle, NULL, "DHKN", "qv"); + + vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n", + str_supported(tp_features.hotkey_mask)); + + res = hotkey_get(&hotkey_orig_status, &hotkey_orig_mask); + if (!res && tp_features.hotkey_mask) { + res = add_many_to_attr_set(hotkey_dev_attributes, + hotkey_mask_attributes, + ARRAY_SIZE(hotkey_mask_attributes)); + } + if (!res) + res = register_attr_set_with_sysfs( + hotkey_dev_attributes, + &tpacpi_pdev->dev.kobj); + + if (res) + return res; + } + + return (tp_features.hotkey)? 0 : 1; +} + +static void hotkey_exit(void) +{ + int res; + + if (tp_features.hotkey) { + dbg_printk(TPACPI_DBG_EXIT, "restoring original hotkey mask\n"); + res = hotkey_set(hotkey_orig_status, hotkey_orig_mask); + if (res) + printk(IBM_ERR "failed to restore hotkey to BIOS defaults\n"); + } + + if (hotkey_dev_attributes) { + delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj); + hotkey_dev_attributes = NULL; + } +} + +static void hotkey_notify(struct ibm_struct *ibm, u32 event) +{ + int hkey; + + if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) + acpi_bus_generate_event(ibm->acpi->device, event, hkey); + else { + printk(IBM_ERR "unknown hotkey event %d\n", event); + acpi_bus_generate_event(ibm->acpi->device, event, 0); + } +} + +/* + * Call with hotkey_mutex held + */ +static int hotkey_get(int *status, int *mask) +{ + if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) + return -EIO; + + if (tp_features.hotkey_mask) + if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) + return -EIO; + + return 0; +} + +/* + * Call with hotkey_mutex held + */ +static int hotkey_set(int status, int mask) +{ + int i; + + if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) + return -EIO; + + if (tp_features.hotkey_mask) + for (i = 0; i < 32; i++) { + int bit = ((1 << i) & mask) != 0; + if (!acpi_evalf(hkey_handle, + NULL, "MHKM", "vdd", i + 1, bit)) + return -EIO; + } + + return 0; +} + +/* procfs -------------------------------------------------------------- */ +static int hotkey_read(char *p) +{ + int res, status, mask; + int len = 0; + + if (!tp_features.hotkey) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + return len; + } + + res = mutex_lock_interruptible(&hotkey_mutex); + if (res < 0) + return res; + res = hotkey_get(&status, &mask); + mutex_unlock(&hotkey_mutex); + if (res) + return res; + + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); + if (tp_features.hotkey_mask) { + len += sprintf(p + len, "mask:\t\t0x%04x\n", mask); + len += sprintf(p + len, + "commands:\tenable, disable, reset, <mask>\n"); + } else { + len += sprintf(p + len, "mask:\t\tnot supported\n"); + len += sprintf(p + len, "commands:\tenable, disable, reset\n"); + } + + return len; +} + +static int hotkey_write(char *buf) +{ + int res, status, mask; + char *cmd; + int do_cmd = 0; + + if (!tp_features.hotkey) + return -ENODEV; + + res = mutex_lock_interruptible(&hotkey_mutex); + if (res < 0) + return res; + + res = hotkey_get(&status, &mask); + if (res) + goto errexit; + + res = 0; + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status = 1; + } else if (strlencmp(cmd, "disable") == 0) { + status = 0; + } else if (strlencmp(cmd, "reset") == 0) { + status = hotkey_orig_status; + mask = hotkey_orig_mask; + } else if (sscanf(cmd, "0x%x", &mask) == 1) { + /* mask set */ + } else if (sscanf(cmd, "%x", &mask) == 1) { + /* mask set */ + } else { + res = -EINVAL; + goto errexit; + } + do_cmd = 1; + } + + if (do_cmd) + res = hotkey_set(status, mask); + +errexit: + mutex_unlock(&hotkey_mutex); + return res; +} + +static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = { + .hid = IBM_HKEY_HID, + .notify = hotkey_notify, + .handle = &hkey_handle, + .type = ACPI_DEVICE_NOTIFY, +}; + +static struct ibm_struct hotkey_driver_data = { + .name = "hotkey", + .read = hotkey_read, + .write = hotkey_write, + .exit = hotkey_exit, + .acpi = &ibm_hotkey_acpidriver, +}; + +/************************************************************************* + * Bluetooth subdriver + */ + +/* sysfs bluetooth enable ---------------------------------------------- */ +static ssize_t bluetooth_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int status; + + status = bluetooth_get_radiosw(); + if (status < 0) + return status; + + return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); +} + +static ssize_t bluetooth_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + res = bluetooth_set_radiosw(t); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_bluetooth_enable = + __ATTR(enable, S_IWUSR | S_IRUGO, + bluetooth_enable_show, bluetooth_enable_store); + +/* --------------------------------------------------------------------- */ + +static struct attribute *bluetooth_attributes[] = { + &dev_attr_bluetooth_enable.attr, + NULL +}; + +static const struct attribute_group bluetooth_attr_group = { + .name = TPACPI_BLUETH_SYSFS_GROUP, + .attrs = bluetooth_attributes, +}; + +static int __init bluetooth_init(struct ibm_init_struct *iibm) +{ + int res; + int status = 0; + + vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n"); + + IBM_ACPIHANDLE_INIT(hkey); + + /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, + G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ + tp_features.bluetooth = hkey_handle && + acpi_evalf(hkey_handle, &status, "GBDC", "qd"); + + vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s, status 0x%02x\n", + 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; + } + } + + 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; + + if (!tp_features.bluetooth) + return -ENODEV; + + 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; + + return 0; +} + +/* procfs -------------------------------------------------------------- */ +static int bluetooth_read(char *p) +{ + int len = 0; + int status = bluetooth_get_radiosw(); + + if (!tp_features.bluetooth) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else { + len += sprintf(p + len, "status:\t\t%s\n", + (status)? "enabled" : "disabled"); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int bluetooth_write(char *buf) +{ + char *cmd; + + if (!tp_features.bluetooth) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + bluetooth_set_radiosw(1); + } else if (strlencmp(cmd, "disable") == 0) { + bluetooth_set_radiosw(0); + } else + return -EINVAL; + } + + return 0; +} + +static struct ibm_struct bluetooth_driver_data = { + .name = "bluetooth", + .read = bluetooth_read, + .write = bluetooth_write, + .exit = bluetooth_exit, +}; + +/************************************************************************* + * Wan subdriver + */ + +/* sysfs wan enable ---------------------------------------------------- */ +static ssize_t wan_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int status; + + status = wan_get_radiosw(); + if (status < 0) + return status; + + return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); +} + +static ssize_t wan_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + res = wan_set_radiosw(t); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_wan_enable = + __ATTR(enable, S_IWUSR | S_IRUGO, + wan_enable_show, wan_enable_store); + +/* --------------------------------------------------------------------- */ + +static struct attribute *wan_attributes[] = { + &dev_attr_wan_enable.attr, + NULL +}; + +static const struct attribute_group wan_attr_group = { + .name = TPACPI_WAN_SYSFS_GROUP, + .attrs = wan_attributes, +}; + +static int __init wan_init(struct ibm_init_struct *iibm) +{ + int res; + int status = 0; + + vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n"); + + IBM_ACPIHANDLE_INIT(hkey); + + tp_features.wan = hkey_handle && + acpi_evalf(hkey_handle, &status, "GWAN", "qd"); + + vdbg_printk(TPACPI_DBG_INIT, "wan is %s, status 0x%02x\n", + 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; + } + } + + 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; + + if (!tp_features.wan) + return -ENODEV; + + 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; + + return 0; +} + +/* procfs -------------------------------------------------------------- */ +static int wan_read(char *p) +{ + int len = 0; + int status = wan_get_radiosw(); + + if (!tp_features.wan) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else { + len += sprintf(p + len, "status:\t\t%s\n", + (status)? "enabled" : "disabled"); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int wan_write(char *buf) +{ + char *cmd; + + if (!tp_features.wan) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + wan_set_radiosw(1); + } else if (strlencmp(cmd, "disable") == 0) { + wan_set_radiosw(0); + } else + return -EINVAL; + } + + return 0; +} + +static struct ibm_struct wan_driver_data = { + .name = "wan", + .read = wan_read, + .write = wan_write, + .exit = wan_exit, + .flags.experimental = 1, +}; + +/************************************************************************* + * Video subdriver + */ + +static enum video_access_mode video_supported; +static int video_orig_autosw; + +IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ + "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */ + "\\_SB.PCI0.VID0", /* 770e */ + "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */ + "\\_SB.PCI0.AGP.VID", /* all others */ + ); /* R30, R31 */ + +IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ + +static int __init video_init(struct ibm_init_struct *iibm) +{ + int ivga; + + vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n"); + + IBM_ACPIHANDLE_INIT(vid); + IBM_ACPIHANDLE_INIT(vid2); + + if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) + /* G41, assume IVGA doesn't change */ + vid_handle = vid2_handle; + + if (!vid_handle) + /* video switching not supported on R30, R31 */ + video_supported = TPACPI_VIDEO_NONE; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) + /* 570 */ + video_supported = TPACPI_VIDEO_570; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) + /* 600e/x, 770e, 770x */ + video_supported = TPACPI_VIDEO_770; + else + /* all others */ + video_supported = TPACPI_VIDEO_NEW; + + vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n", + str_supported(video_supported != TPACPI_VIDEO_NONE), + video_supported); + + return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1; +} + +static void video_exit(void) +{ + dbg_printk(TPACPI_DBG_EXIT, + "restoring original video autoswitch mode\n"); + if (video_autosw_set(video_orig_autosw)) + printk(IBM_ERR "error while trying to restore original " + "video autoswitch mode\n"); +} + +static int video_outputsw_get(void) +{ + int status = 0; + int i; + + switch (video_supported) { + case TPACPI_VIDEO_570: + if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", + TP_ACPI_VIDEO_570_PHSCMD)) + return -EIO; + status = i & TP_ACPI_VIDEO_570_PHSMASK; + break; + case TPACPI_VIDEO_770: + if (!acpi_evalf(NULL, &i, "\\VCDL", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_LCD; + if (!acpi_evalf(NULL, &i, "\\VCDC", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_CRT; + break; + case TPACPI_VIDEO_NEW: + if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) || + !acpi_evalf(NULL, &i, "\\VCDC", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_CRT; + + if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) || + !acpi_evalf(NULL, &i, "\\VCDL", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_LCD; + if (!acpi_evalf(NULL, &i, "\\VCDD", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_DVI; + break; + default: + return -ENOSYS; + } + + return status; +} + +static int video_outputsw_set(int status) +{ + int autosw; + int res = 0; + + switch (video_supported) { + case TPACPI_VIDEO_570: + res = acpi_evalf(NULL, NULL, + "\\_SB.PHS2", "vdd", + TP_ACPI_VIDEO_570_PHS2CMD, + status | TP_ACPI_VIDEO_570_PHS2SET); + break; + case TPACPI_VIDEO_770: + autosw = video_autosw_get(); + if (autosw < 0) + return autosw; + + res = video_autosw_set(1); + if (res) + return res; + res = acpi_evalf(vid_handle, NULL, + "ASWT", "vdd", status * 0x100, 0); + if (!autosw && video_autosw_set(autosw)) { + printk(IBM_ERR "video auto-switch left enabled due to error\n"); + return -EIO; + } + break; + case TPACPI_VIDEO_NEW: + res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && + acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); + break; + default: + return -ENOSYS; + } + + return (res)? 0 : -EIO; +} + +static int video_autosw_get(void) +{ + int autosw = 0; + + switch (video_supported) { + case TPACPI_VIDEO_570: + if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d")) + return -EIO; + break; + case TPACPI_VIDEO_770: + case TPACPI_VIDEO_NEW: + if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d")) + return -EIO; + break; + default: + return -ENOSYS; + } + + return autosw & 1; +} + +static int video_autosw_set(int enable) +{ + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0)) + return -EIO; + return 0; +} + +static int video_outputsw_cycle(void) +{ + int autosw = video_autosw_get(); + int res; + + if (autosw < 0) + return autosw; + + switch (video_supported) { + case TPACPI_VIDEO_570: + res = video_autosw_set(1); + if (res) + return res; + res = acpi_evalf(ec_handle, NULL, "_Q16", "v"); + break; + case TPACPI_VIDEO_770: + case TPACPI_VIDEO_NEW: + res = video_autosw_set(1); + if (res) + return res; + res = acpi_evalf(vid_handle, NULL, "VSWT", "v"); + break; + default: + return -ENOSYS; + } + if (!autosw && video_autosw_set(autosw)) { + printk(IBM_ERR "video auto-switch left enabled due to error\n"); + return -EIO; + } + + return (res)? 0 : -EIO; +} + +static int video_expand_toggle(void) +{ + switch (video_supported) { + case TPACPI_VIDEO_570: + return acpi_evalf(ec_handle, NULL, "_Q17", "v")? + 0 : -EIO; + case TPACPI_VIDEO_770: + return acpi_evalf(vid_handle, NULL, "VEXP", "v")? + 0 : -EIO; + case TPACPI_VIDEO_NEW: + return acpi_evalf(NULL, NULL, "\\VEXP", "v")? + 0 : -EIO; + default: + return -ENOSYS; + } + /* not reached */ +} + +static int video_read(char *p) +{ + int status, autosw; + int len = 0; + + if (video_supported == TPACPI_VIDEO_NONE) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + return len; + } + + status = video_outputsw_get(); + if (status < 0) + return status; + + autosw = video_autosw_get(); + if (autosw < 0) + return autosw; + + len += sprintf(p + len, "status:\t\tsupported\n"); + len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); + len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); + if (video_supported == TPACPI_VIDEO_NEW) + len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); + len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0)); + len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n"); + len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n"); + if (video_supported == TPACPI_VIDEO_NEW) + len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n"); + len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n"); + len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); + + return len; +} + +static int video_write(char *buf) +{ + char *cmd; + int enable, disable, status; + int res; + + if (video_supported == TPACPI_VIDEO_NONE) + return -ENODEV; + + enable = 0; + disable = 0; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "lcd_enable") == 0) { + enable |= TP_ACPI_VIDEO_S_LCD; + } else if (strlencmp(cmd, "lcd_disable") == 0) { + disable |= TP_ACPI_VIDEO_S_LCD; + } else if (strlencmp(cmd, "crt_enable") == 0) { + enable |= TP_ACPI_VIDEO_S_CRT; + } else if (strlencmp(cmd, "crt_disable") == 0) { + disable |= TP_ACPI_VIDEO_S_CRT; + } else if (video_supported == TPACPI_VIDEO_NEW && + strlencmp(cmd, "dvi_enable") == 0) { + enable |= TP_ACPI_VIDEO_S_DVI; + } else if (video_supported == TPACPI_VIDEO_NEW && + strlencmp(cmd, "dvi_disable") == 0) { + disable |= TP_ACPI_VIDEO_S_DVI; + } else if (strlencmp(cmd, "auto_enable") == 0) { + res = video_autosw_set(1); + if (res) + return res; + } else if (strlencmp(cmd, "auto_disable") == 0) { + res = video_autosw_set(0); + if (res) + return res; + } else if (strlencmp(cmd, "video_switch") == 0) { + res = video_outputsw_cycle(); + if (res) + return res; + } else if (strlencmp(cmd, "expand_toggle") == 0) { + res = video_expand_toggle(); + if (res) + return res; + } else + return -EINVAL; + } + + if (enable || disable) { + status = video_outputsw_get(); + if (status < 0) + return status; + res = video_outputsw_set((status & ~disable) | enable); + if (res) + return res; + } + + return 0; +} + +static struct ibm_struct video_driver_data = { + .name = "video", + .read = video_read, + .write = video_write, + .exit = video_exit, +}; + +/************************************************************************* + * Light (thinklight) subdriver + */ + +IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ +IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ + +static int __init light_init(struct ibm_init_struct *iibm) +{ + vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); + + IBM_ACPIHANDLE_INIT(ledb); + IBM_ACPIHANDLE_INIT(lght); + IBM_ACPIHANDLE_INIT(cmos); + + /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ + tp_features.light = (cmos_handle || lght_handle) && !ledb_handle; + + if (tp_features.light) + /* light status not supported on + 570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */ + tp_features.light_status = + acpi_evalf(ec_handle, NULL, "KBLT", "qv"); + + vdbg_printk(TPACPI_DBG_INIT, "light is %s\n", + str_supported(tp_features.light)); + + return (tp_features.light)? 0 : 1; +} + +static int light_read(char *p) +{ + int len = 0; + int status = 0; + + if (!tp_features.light) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + } else if (!tp_features.light_status) { + len += sprintf(p + len, "status:\t\tunknown\n"); + len += sprintf(p + len, "commands:\ton, off\n"); + } else { + if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) + return -EIO; + len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); + len += sprintf(p + len, "commands:\ton, off\n"); + } + + return len; +} + +static int light_write(char *buf) +{ + int cmos_cmd, lght_cmd; + char *cmd; + int success; + + if (!tp_features.light) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "on") == 0) { + cmos_cmd = 0x0c; + lght_cmd = 1; + } else if (strlencmp(cmd, "off") == 0) { + cmos_cmd = 0x0d; + lght_cmd = 0; + } else + return -EINVAL; + + success = cmos_handle ? + acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : + acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); + if (!success) + return -EIO; + } + + return 0; +} + +static struct ibm_struct light_driver_data = { + .name = "light", + .read = light_read, + .write = light_write, +}; + +/************************************************************************* + * Dock subdriver + */ + +#ifdef CONFIG_THINKPAD_ACPI_DOCK + +IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ + "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ + "\\_SB.PCI0.PCI1.DOCK", /* all others */ + "\\_SB.PCI.ISA.SLCE", /* 570 */ + ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ + +/* don't list other alternatives as we install a notify handler on the 570 */ +IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ + +static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = { + { + .notify = dock_notify, + .handle = &dock_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, + { + .hid = IBM_PCI_HID, + .notify = dock_notify, + .handle = &pci_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, +}; + +static struct ibm_struct dock_driver_data[2] = { + { + .name = "dock", + .read = dock_read, + .write = dock_write, + .acpi = &ibm_dock_acpidriver[0], + }, + { + .name = "dock", + .acpi = &ibm_dock_acpidriver[1], + }, +}; + +#define dock_docked() (_sta(dock_handle) & 1) + +static int __init dock_init(struct ibm_init_struct *iibm) +{ + vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n"); + + IBM_ACPIHANDLE_INIT(dock); + + vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n", + str_supported(dock_handle != NULL)); + + return (dock_handle)? 0 : 1; +} + +static int __init dock_init2(struct ibm_init_struct *iibm) +{ + int dock2_needed; + + vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n"); + + if (dock_driver_data[0].flags.acpi_driver_registered && + dock_driver_data[0].flags.acpi_notify_installed) { + IBM_ACPIHANDLE_INIT(pci); + dock2_needed = (pci_handle != NULL); + vdbg_printk(TPACPI_DBG_INIT, + "dock PCI handler for the TP 570 is %s\n", + str_supported(dock2_needed)); + } else { + vdbg_printk(TPACPI_DBG_INIT, + "dock subdriver part 2 not required\n"); + dock2_needed = 0; + } + + return (dock2_needed)? 0 : 1; +} + +static void dock_notify(struct ibm_struct *ibm, u32 event) +{ + int docked = dock_docked(); + int pci = ibm->acpi->hid && strstr(ibm->acpi->hid, IBM_PCI_HID); + + if (event == 1 && !pci) /* 570 */ + acpi_bus_generate_event(ibm->acpi->device, event, 1); /* button */ + else if (event == 1 && pci) /* 570 */ + acpi_bus_generate_event(ibm->acpi->device, event, 3); /* dock */ + else if (event == 3 && docked) + acpi_bus_generate_event(ibm->acpi->device, event, 1); /* button */ + else if (event == 3 && !docked) + acpi_bus_generate_event(ibm->acpi->device, event, 2); /* undock */ + else if (event == 0 && docked) + acpi_bus_generate_event(ibm->acpi->device, event, 3); /* dock */ + else { + printk(IBM_ERR "unknown dock event %d, status %d\n", + event, _sta(dock_handle)); + acpi_bus_generate_event(ibm->acpi->device, event, 0); /* unknown */ + } +} + +static int dock_read(char *p) +{ + int len = 0; + int docked = dock_docked(); + + if (!dock_handle) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!docked) + len += sprintf(p + len, "status:\t\tundocked\n"); + else { + len += sprintf(p + len, "status:\t\tdocked\n"); + len += sprintf(p + len, "commands:\tdock, undock\n"); + } + + return len; +} + +static int dock_write(char *buf) +{ + char *cmd; + + if (!dock_docked()) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "undock") == 0) { + if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) || + !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1)) + return -EIO; + } else if (strlencmp(cmd, "dock") == 0) { + if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +#endif /* CONFIG_THINKPAD_ACPI_DOCK */ + +/************************************************************************* + * Bay subdriver + */ + +#ifdef CONFIG_THINKPAD_ACPI_BAY +IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */ + "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */ + "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */ + "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */ + ); /* A21e, R30, R31 */ +IBM_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */ + "_EJ0", /* all others */ + ); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */ +IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */ + "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */ + ); /* all others */ +IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ + "_EJ0", /* 770x */ + ); /* all others */ + +static int __init bay_init(struct ibm_init_struct *iibm) +{ + vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n"); + + IBM_ACPIHANDLE_INIT(bay); + if (bay_handle) + IBM_ACPIHANDLE_INIT(bay_ej); + IBM_ACPIHANDLE_INIT(bay2); + if (bay2_handle) + IBM_ACPIHANDLE_INIT(bay2_ej); + + tp_features.bay_status = bay_handle && + acpi_evalf(bay_handle, NULL, "_STA", "qv"); + tp_features.bay_status2 = bay2_handle && + acpi_evalf(bay2_handle, NULL, "_STA", "qv"); + + tp_features.bay_eject = bay_handle && bay_ej_handle && + (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental); + tp_features.bay_eject2 = bay2_handle && bay2_ej_handle && + (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental); + + vdbg_printk(TPACPI_DBG_INIT, + "bay 1: status %s, eject %s; bay 2: status %s, eject %s\n", + str_supported(tp_features.bay_status), + str_supported(tp_features.bay_eject), + str_supported(tp_features.bay_status2), + str_supported(tp_features.bay_eject2)); + + return (tp_features.bay_status || tp_features.bay_eject || + tp_features.bay_status2 || tp_features.bay_eject2)? 0 : 1; +} + +static void bay_notify(struct ibm_struct *ibm, u32 event) +{ + acpi_bus_generate_event(ibm->acpi->device, event, 0); +} + +#define bay_occupied(b) (_sta(b##_handle) & 1) + +static int bay_read(char *p) +{ + int len = 0; + int occupied = bay_occupied(bay); + int occupied2 = bay_occupied(bay2); + int eject, eject2; + + len += sprintf(p + len, "status:\t\t%s\n", + tp_features.bay_status ? + (occupied ? "occupied" : "unoccupied") : + "not supported"); + if (tp_features.bay_status2) + len += sprintf(p + len, "status2:\t%s\n", occupied2 ? + "occupied" : "unoccupied"); + + eject = tp_features.bay_eject && occupied; + eject2 = tp_features.bay_eject2 && occupied2; + + if (eject && eject2) + len += sprintf(p + len, "commands:\teject, eject2\n"); + else if (eject) + len += sprintf(p + len, "commands:\teject\n"); + else if (eject2) + len += sprintf(p + len, "commands:\teject2\n"); + + return len; +} + +static int bay_write(char *buf) +{ + char *cmd; + + if (!tp_features.bay_eject && !tp_features.bay_eject2) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (tp_features.bay_eject && strlencmp(cmd, "eject") == 0) { + if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1)) + return -EIO; + } else if (tp_features.bay_eject2 && + strlencmp(cmd, "eject2") == 0) { + if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +static struct tp_acpi_drv_struct ibm_bay_acpidriver = { + .notify = bay_notify, + .handle = &bay_handle, + .type = ACPI_SYSTEM_NOTIFY, +}; + +static struct ibm_struct bay_driver_data = { + .name = "bay", + .read = bay_read, + .write = bay_write, + .acpi = &ibm_bay_acpidriver, +}; + +#endif /* CONFIG_THINKPAD_ACPI_BAY */ + +/************************************************************************* + * CMOS subdriver + */ + +/* sysfs cmos_command -------------------------------------------------- */ +static ssize_t cmos_command_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long cmos_cmd; + int res; + + if (parse_strtoul(buf, 21, &cmos_cmd)) + return -EINVAL; + + res = issue_thinkpad_cmos_command(cmos_cmd); + return (res)? res : count; +} + +static struct device_attribute dev_attr_cmos_command = + __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store); + +/* --------------------------------------------------------------------- */ + +static int __init cmos_init(struct ibm_init_struct *iibm) +{ + int res; + + vdbg_printk(TPACPI_DBG_INIT, + "initializing cmos commands subdriver\n"); + + IBM_ACPIHANDLE_INIT(cmos); + + vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n", + str_supported(cmos_handle != NULL)); + + res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command); + if (res) + return res; + + return (cmos_handle)? 0 : 1; +} + +static void cmos_exit(void) +{ + device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command); +} + +static int cmos_read(char *p) +{ + int len = 0; + + /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, + R30, R31, T20-22, X20-21 */ + if (!cmos_handle) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else { + len += sprintf(p + len, "status:\t\tsupported\n"); + len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n"); + } + + return len; +} + +static int cmos_write(char *buf) +{ + char *cmd; + int cmos_cmd, res; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%u", &cmos_cmd) == 1 && + cmos_cmd >= 0 && cmos_cmd <= 21) { + /* cmos_cmd set */ + } else + return -EINVAL; + + res = issue_thinkpad_cmos_command(cmos_cmd); + if (res) + return res; + } + + return 0; +} + +static struct ibm_struct cmos_driver_data = { + .name = "cmos", + .read = cmos_read, + .write = cmos_write, + .exit = cmos_exit, +}; + +/************************************************************************* + * LED subdriver + */ + +static enum led_access_mode led_supported; + +IBM_HANDLE(led, ec, "SLED", /* 570 */ + "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + "LED", /* all others */ + ); /* R30, R31 */ + +static int __init led_init(struct ibm_init_struct *iibm) +{ + vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); + + IBM_ACPIHANDLE_INIT(led); + + if (!led_handle) + /* led not supported on R30, R31 */ + led_supported = TPACPI_LED_NONE; + else if (strlencmp(led_path, "SLED") == 0) + /* 570 */ + led_supported = TPACPI_LED_570; + else if (strlencmp(led_path, "SYSL") == 0) + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + led_supported = TPACPI_LED_OLD; + else + /* all others */ + led_supported = TPACPI_LED_NEW; + + vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n", + str_supported(led_supported), led_supported); + + return (led_supported != TPACPI_LED_NONE)? 0 : 1; +} + +#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking")) + +static int led_read(char *p) +{ + int len = 0; + + if (!led_supported) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + return len; + } + len += sprintf(p + len, "status:\t\tsupported\n"); + + if (led_supported == TPACPI_LED_570) { + /* 570 */ + int i, status; + for (i = 0; i < 8; i++) { + if (!acpi_evalf(ec_handle, + &status, "GLED", "dd", 1 << i)) + return -EIO; + len += sprintf(p + len, "%d:\t\t%s\n", + i, led_status(status)); + } + } + + len += sprintf(p + len, "commands:\t" + "<led> on, <led> off, <led> blink (<led> is 0-7)\n"); + + return len; +} + +/* off, on, blink */ +static const int led_sled_arg1[] = { 0, 1, 3 }; +static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ +static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ +static const int led_led_arg1[] = { 0, 0x80, 0xc0 }; + +static int led_write(char *buf) +{ + char *cmd; + int led, ind, ret; + + if (!led_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) + return -EINVAL; + + if (strstr(cmd, "off")) { + ind = 0; + } else if (strstr(cmd, "on")) { + ind = 1; + } else if (strstr(cmd, "blink")) { + ind = 2; + } else + return -EINVAL; + + if (led_supported == TPACPI_LED_570) { + /* 570 */ + led = 1 << led; + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + led, led_sled_arg1[ind])) + return -EIO; + } else if (led_supported == TPACPI_LED_OLD) { + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ + led = 1 << led; + ret = ec_write(TPACPI_LED_EC_HLMS, led); + if (ret >= 0) + ret = + ec_write(TPACPI_LED_EC_HLBL, + led * led_exp_hlbl[ind]); + if (ret >= 0) + ret = + ec_write(TPACPI_LED_EC_HLCL, + led * led_exp_hlcl[ind]); + if (ret < 0) + return ret; + } else { + /* all others */ + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + led, led_led_arg1[ind])) + return -EIO; + } + } + + return 0; +} + +static struct ibm_struct led_driver_data = { + .name = "led", + .read = led_read, + .write = led_write, +}; + +/************************************************************************* + * Beep subdriver + */ + +IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ + +static int __init beep_init(struct ibm_init_struct *iibm) +{ + vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n"); + + IBM_ACPIHANDLE_INIT(beep); + + vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", + str_supported(beep_handle != NULL)); + + return (beep_handle)? 0 : 1; +} + +static int beep_read(char *p) +{ + int len = 0; + + if (!beep_handle) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else { + len += sprintf(p + len, "status:\t\tsupported\n"); + len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n"); + } + + return len; +} + +static int beep_write(char *buf) +{ + char *cmd; + int beep_cmd; + + if (!beep_handle) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%u", &beep_cmd) == 1 && + beep_cmd >= 0 && beep_cmd <= 17) { + /* beep_cmd set */ + } else + return -EINVAL; + if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) + return -EIO; + } + + return 0; +} + +static struct ibm_struct beep_driver_data = { + .name = "beep", + .read = beep_read, + .write = beep_write, +}; + +/************************************************************************* + * Thermal subdriver + */ + +static enum thermal_access_mode thermal_read_mode; + +/* sysfs temp##_input -------------------------------------------------- */ + +static ssize_t thermal_temp_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = + to_sensor_dev_attr(attr); + int idx = sensor_attr->index; + s32 value; + int res; + + res = thermal_get_sensor(idx, &value); + if (res) + return res; + if (value == TP_EC_THERMAL_TMP_NA * 1000) + return -ENXIO; + + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \ + SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, thermal_temp_input_show, NULL, _idxB) + +static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = { + THERMAL_SENSOR_ATTR_TEMP(1, 0), + THERMAL_SENSOR_ATTR_TEMP(2, 1), + THERMAL_SENSOR_ATTR_TEMP(3, 2), + THERMAL_SENSOR_ATTR_TEMP(4, 3), + THERMAL_SENSOR_ATTR_TEMP(5, 4), + THERMAL_SENSOR_ATTR_TEMP(6, 5), + THERMAL_SENSOR_ATTR_TEMP(7, 6), + THERMAL_SENSOR_ATTR_TEMP(8, 7), + THERMAL_SENSOR_ATTR_TEMP(9, 8), + THERMAL_SENSOR_ATTR_TEMP(10, 9), + THERMAL_SENSOR_ATTR_TEMP(11, 10), + THERMAL_SENSOR_ATTR_TEMP(12, 11), + THERMAL_SENSOR_ATTR_TEMP(13, 12), + THERMAL_SENSOR_ATTR_TEMP(14, 13), + THERMAL_SENSOR_ATTR_TEMP(15, 14), + THERMAL_SENSOR_ATTR_TEMP(16, 15), +}; + +#define THERMAL_ATTRS(X) \ + &sensor_dev_attr_thermal_temp_input[X].dev_attr.attr + +static struct attribute *thermal_temp_input_attr[] = { + THERMAL_ATTRS(8), + THERMAL_ATTRS(9), + THERMAL_ATTRS(10), + THERMAL_ATTRS(11), + THERMAL_ATTRS(12), + THERMAL_ATTRS(13), + THERMAL_ATTRS(14), + THERMAL_ATTRS(15), + THERMAL_ATTRS(0), + THERMAL_ATTRS(1), + THERMAL_ATTRS(2), + THERMAL_ATTRS(3), + THERMAL_ATTRS(4), + THERMAL_ATTRS(5), + THERMAL_ATTRS(6), + THERMAL_ATTRS(7), + NULL +}; + +static const struct attribute_group thermal_temp_input16_group = { + .attrs = thermal_temp_input_attr +}; + +static const struct attribute_group thermal_temp_input8_group = { + .attrs = &thermal_temp_input_attr[8] +}; + +#undef THERMAL_SENSOR_ATTR_TEMP +#undef THERMAL_ATTRS + +/* --------------------------------------------------------------------- */ + +static int __init thermal_init(struct ibm_init_struct *iibm) +{ + u8 t, ta1, ta2; + int i; + int acpi_tmp7; + int res; + + vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n"); + + acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); + + if (ibm_thinkpad_ec_found && experimental) { + /* + * Direct EC access mode: sensors at registers + * 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for + * non-implemented, thermal sensors return 0x80 when + * not available + */ + + ta1 = ta2 = 0; + for (i = 0; i < 8; i++) { + if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) { + ta1 |= t; + } else { + ta1 = 0; + break; + } + if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) { + ta2 |= t; + } else { + ta1 = 0; + break; + } + } + if (ta1 == 0) { + /* This is sheer paranoia, but we handle it anyway */ + if (acpi_tmp7) { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "falling back to ACPI TMPx access mode\n"); + thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; + } else { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "disabling thermal sensors access\n"); + thermal_read_mode = TPACPI_THERMAL_NONE; + } + } else { + thermal_read_mode = + (ta2 != 0) ? + TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8; + } + } else if (acpi_tmp7) { + if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { + /* 600e/x, 770e, 770x */ + thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT; + } else { + /* Standard ACPI TMPx access, max 8 sensors */ + thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; + } + } else { + /* temperatures not supported on 570, G4x, R30, R31, R32 */ + thermal_read_mode = TPACPI_THERMAL_NONE; + } + + vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n", + str_supported(thermal_read_mode != TPACPI_THERMAL_NONE), + thermal_read_mode); + + switch(thermal_read_mode) { + case TPACPI_THERMAL_TPEC_16: + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input16_group); + if (res) + return res; + break; + case TPACPI_THERMAL_TPEC_8: + case TPACPI_THERMAL_ACPI_TMP07: + case TPACPI_THERMAL_ACPI_UPDT: + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input8_group); + if (res) + return res; + break; + case TPACPI_THERMAL_NONE: + default: + return 1; + } + + return 0; +} + +static void thermal_exit(void) +{ + switch(thermal_read_mode) { + case TPACPI_THERMAL_TPEC_16: + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input16_group); + break; + case TPACPI_THERMAL_TPEC_8: + case TPACPI_THERMAL_ACPI_TMP07: + case TPACPI_THERMAL_ACPI_UPDT: + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input16_group); + break; + case TPACPI_THERMAL_NONE: + default: + break; + } +} + +/* idx is zero-based */ +static int thermal_get_sensor(int idx, s32 *value) +{ + int t; + s8 tmp; + char tmpi[5]; + + t = TP_EC_THERMAL_TMP0; + + switch (thermal_read_mode) { +#if TPACPI_MAX_THERMAL_SENSORS >= 16 + case TPACPI_THERMAL_TPEC_16: + if (idx >= 8 && idx <= 15) { + t = TP_EC_THERMAL_TMP8; + idx -= 8; + } + /* fallthrough */ +#endif + case TPACPI_THERMAL_TPEC_8: + if (idx <= 7) { + if (!acpi_ec_read(t + idx, &tmp)) + return -EIO; + *value = tmp * 1000; + return 0; + } + break; + + case TPACPI_THERMAL_ACPI_UPDT: + if (idx <= 7) { + snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); + if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) + return -EIO; + if (!acpi_evalf(ec_handle, &t, tmpi, "d")) + return -EIO; + *value = (t - 2732) * 100; + return 0; + } + break; + + case TPACPI_THERMAL_ACPI_TMP07: + if (idx <= 7) { + snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); + if (!acpi_evalf(ec_handle, &t, tmpi, "d")) + return -EIO; + *value = t * 1000; + return 0; + } + break; + + case TPACPI_THERMAL_NONE: + default: + return -ENOSYS; + } + + return -EINVAL; +} + +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +{ + int res, i; + int n; + + n = 8; + i = 0; + + if (!s) + return -EINVAL; + + if (thermal_read_mode == TPACPI_THERMAL_TPEC_16) + n = 16; + + for(i = 0 ; i < n; i++) { + res = thermal_get_sensor(i, &s->temp[i]); + if (res) + return res; + } + + return n; +} + +static int thermal_read(char *p) +{ + int len = 0; + int n, i; + struct ibm_thermal_sensors_struct t; + + n = thermal_get_sensors(&t); + if (unlikely(n < 0)) + return n; + + len += sprintf(p + len, "temperatures:\t"); + + if (n > 0) { + for (i = 0; i < (n - 1); i++) + len += sprintf(p + len, "%d ", t.temp[i] / 1000); + len += sprintf(p + len, "%d\n", t.temp[i] / 1000); + } else + len += sprintf(p + len, "not supported\n"); + + return len; +} + +static struct ibm_struct thermal_driver_data = { + .name = "thermal", + .read = thermal_read, + .exit = thermal_exit, +}; + +/************************************************************************* + * EC Dump subdriver + */ + +static u8 ecdump_regs[256]; + +static int ecdump_read(char *p) +{ + int len = 0; + int i, j; + u8 v; + + len += sprintf(p + len, "EC " + " +00 +01 +02 +03 +04 +05 +06 +07" + " +08 +09 +0a +0b +0c +0d +0e +0f\n"); + for (i = 0; i < 256; i += 16) { + len += sprintf(p + len, "EC 0x%02x:", i); + for (j = 0; j < 16; j++) { + if (!acpi_ec_read(i + j, &v)) + break; + if (v != ecdump_regs[i + j]) + len += sprintf(p + len, " *%02x", v); + else + len += sprintf(p + len, " %02x", v); + ecdump_regs[i + j] = v; + } + len += sprintf(p + len, "\n"); + if (j != 16) + break; + } + + /* These are way too dangerous to advertise openly... */ +#if 0 + len += sprintf(p + len, "commands:\t0x<offset> 0x<value>" + " (<offset> is 00-ff, <value> is 00-ff)\n"); + len += sprintf(p + len, "commands:\t0x<offset> <value> " + " (<offset> is 00-ff, <value> is 0-255)\n"); +#endif + return len; +} + +static int ecdump_write(char *buf) +{ + char *cmd; + int i, v; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) { + /* i and v set */ + } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) { + /* i and v set */ + } else + return -EINVAL; + if (i >= 0 && i < 256 && v >= 0 && v < 256) { + if (!acpi_ec_write(i, v)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +static struct ibm_struct ecdump_driver_data = { + .name = "ecdump", + .read = ecdump_read, + .write = ecdump_write, + .flags.experimental = 1, +}; + +/************************************************************************* + * Backlight/brightness subdriver + */ + +static struct backlight_device *ibm_backlight_device = NULL; + +static struct backlight_ops ibm_backlight_data = { + .get_brightness = brightness_get, + .update_status = brightness_update_status, +}; + +static int __init brightness_init(struct ibm_init_struct *iibm) +{ + int b; + + vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n"); + + b = brightness_get(NULL); + if (b < 0) + return b; + + ibm_backlight_device = backlight_device_register( + TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL, + &ibm_backlight_data); + if (IS_ERR(ibm_backlight_device)) { + printk(IBM_ERR "Could not register backlight device\n"); + return PTR_ERR(ibm_backlight_device); + } + vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n"); + + ibm_backlight_device->props.max_brightness = 7; + ibm_backlight_device->props.brightness = b; + backlight_update_status(ibm_backlight_device); + + return 0; +} + +static void brightness_exit(void) +{ + if (ibm_backlight_device) { + vdbg_printk(TPACPI_DBG_EXIT, + "calling backlight_device_unregister()\n"); + backlight_device_unregister(ibm_backlight_device); + ibm_backlight_device = NULL; + } +} + +static int brightness_update_status(struct backlight_device *bd) +{ + return brightness_set( + (bd->props.fb_blank == FB_BLANK_UNBLANK && + bd->props.power == FB_BLANK_UNBLANK) ? + bd->props.brightness : 0); +} + +static int brightness_get(struct backlight_device *bd) +{ + u8 level; + if (!acpi_ec_read(brightness_offset, &level)) + return -EIO; + + level &= 0x7; + + return level; +} + +static int brightness_set(int value) +{ + int cmos_cmd, inc, i; + int current_value = brightness_get(NULL); + + value &= 7; + + cmos_cmd = value > current_value ? TP_CMOS_BRIGHTNESS_UP : TP_CMOS_BRIGHTNESS_DOWN; + inc = value > current_value ? 1 : -1; + for (i = current_value; i != value; i += inc) { + if (issue_thinkpad_cmos_command(cmos_cmd)) + return -EIO; + if (!acpi_ec_write(brightness_offset, i + inc)) + return -EIO; + } + + return 0; +} + +static int brightness_read(char *p) +{ + int len = 0; + int level; + + if ((level = brightness_get(NULL)) < 0) { + len += sprintf(p + len, "level:\t\tunreadable\n"); + } else { + len += sprintf(p + len, "level:\t\t%d\n", level & 0x7); + len += sprintf(p + len, "commands:\tup, down\n"); + len += sprintf(p + len, "commands:\tlevel <level>" + " (<level> is 0-7)\n"); + } + + return len; +} + +static int brightness_write(char *buf) +{ + int level; + int new_level; + char *cmd; + + while ((cmd = next_cmd(&buf))) { + if ((level = brightness_get(NULL)) < 0) + return level; + level &= 7; + + if (strlencmp(cmd, "up") == 0) { + new_level = level == 7 ? 7 : level + 1; + } else if (strlencmp(cmd, "down") == 0) { + new_level = level == 0 ? 0 : level - 1; + } else if (sscanf(cmd, "level %d", &new_level) == 1 && + new_level >= 0 && new_level <= 7) { + /* new_level set */ + } else + return -EINVAL; + + brightness_set(new_level); + } + + return 0; +} + +static struct ibm_struct brightness_driver_data = { + .name = "brightness", + .read = brightness_read, + .write = brightness_write, + .exit = brightness_exit, +}; + +/************************************************************************* + * Volume subdriver + */ + +static int volume_read(char *p) +{ + int len = 0; + u8 level; + + if (!acpi_ec_read(volume_offset, &level)) { + len += sprintf(p + len, "level:\t\tunreadable\n"); + } else { + len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); + len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); + len += sprintf(p + len, "commands:\tup, down, mute\n"); + len += sprintf(p + len, "commands:\tlevel <level>" + " (<level> is 0-15)\n"); + } + + return len; +} + +static int volume_write(char *buf) +{ + int cmos_cmd, inc, i; + u8 level, mute; + int new_level, new_mute; + char *cmd; + + while ((cmd = next_cmd(&buf))) { + if (!acpi_ec_read(volume_offset, &level)) + return -EIO; + new_mute = mute = level & 0x40; + new_level = level = level & 0xf; + + if (strlencmp(cmd, "up") == 0) { + if (mute) + new_mute = 0; + else + new_level = level == 15 ? 15 : level + 1; + } else if (strlencmp(cmd, "down") == 0) { + if (mute) + new_mute = 0; + else + new_level = level == 0 ? 0 : level - 1; + } else if (sscanf(cmd, "level %d", &new_level) == 1 && + new_level >= 0 && new_level <= 15) { + /* new_level set */ + } else if (strlencmp(cmd, "mute") == 0) { + new_mute = 0x40; + } else + return -EINVAL; + + if (new_level != level) { /* mute doesn't change */ + cmos_cmd = new_level > level ? TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; + inc = new_level > level ? 1 : -1; + + if (mute && (issue_thinkpad_cmos_command(cmos_cmd) || + !acpi_ec_write(volume_offset, level))) + return -EIO; + + for (i = level; i != new_level; i += inc) + if (issue_thinkpad_cmos_command(cmos_cmd) || + !acpi_ec_write(volume_offset, i + inc)) + return -EIO; + + if (mute && (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) || + !acpi_ec_write(volume_offset, + new_level + mute))) + return -EIO; + } + + if (new_mute != mute) { /* level doesn't change */ + cmos_cmd = new_mute ? TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; + + if (issue_thinkpad_cmos_command(cmos_cmd) || + !acpi_ec_write(volume_offset, level + new_mute)) + return -EIO; + } + } + + return 0; +} + +static struct ibm_struct volume_driver_data = { + .name = "volume", + .read = volume_read, + .write = volume_write, +}; + +/************************************************************************* + * Fan subdriver + */ + +/* + * FAN ACCESS MODES + * + * TPACPI_FAN_RD_ACPI_GFAN: + * ACPI GFAN method: returns fan level + * + * see TPACPI_FAN_WR_ACPI_SFAN + * EC 0x2f (HFSP) not available if GFAN exists + * + * TPACPI_FAN_WR_ACPI_SFAN: + * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) + * + * EC 0x2f (HFSP) might be available *for reading*, but do not use + * it for writing. + * + * TPACPI_FAN_WR_TPEC: + * ThinkPad EC register 0x2f (HFSP): fan control loop mode + * Supported on almost all ThinkPads + * + * Fan speed changes of any sort (including those caused by the + * disengaged mode) are usually done slowly by the firmware as the + * maximum ammount of fan duty cycle change per second seems to be + * limited. + * + * Reading is not available if GFAN exists. + * Writing is not available if SFAN exists. + * + * Bits + * 7 automatic mode engaged; + * (default operation mode of the ThinkPad) + * fan level is ignored in this mode. + * 6 full speed mode (takes precedence over bit 7); + * not available on all thinkpads. May disable + * the tachometer while the fan controller ramps up + * the speed (which can take up to a few *minutes*). + * Speeds up fan to 100% duty-cycle, which is far above + * the standard RPM levels. It is not impossible that + * it could cause hardware damage. + * 5-3 unused in some models. Extra bits for fan level + * in others, but still useless as all values above + * 7 map to the same speed as level 7 in these models. + * 2-0 fan level (0..7 usually) + * 0x00 = stop + * 0x07 = max (set when temperatures critical) + * Some ThinkPads may have other levels, see + * TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41) + * + * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at + * boot. Apparently the EC does not intialize it, so unless ACPI DSDT + * does so, its initial value is meaningless (0x07). + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * ---- + * + * ThinkPad EC register 0x84 (LSB), 0x85 (MSB): + * Main fan tachometer reading (in RPM) + * + * This register is present on all ThinkPads with a new-style EC, and + * it is known not to be present on the A21m/e, and T22, as there is + * something else in offset 0x84 according to the ACPI DSDT. Other + * ThinkPads from this same time period (and earlier) probably lack the + * tachometer as well. + * + * Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare + * was never fixed by IBM to report the EC firmware version string + * probably support the tachometer (like the early X models), so + * detecting it is quite hard. We need more data to know for sure. + * + * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings + * might result. + * + * FIRMWARE BUG: may go stale while the EC is switching to full speed + * mode. + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * TPACPI_FAN_WR_ACPI_FANS: + * ThinkPad X31, X40, X41. Not available in the X60. + * + * FANS ACPI handle: takes three arguments: low speed, medium speed, + * high speed. ACPI DSDT seems to map these three speeds to levels + * as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH + * (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3") + * + * The speeds are stored on handles + * (FANA:FAN9), (FANC:FANB), (FANE:FAND). + * + * There are three default speed sets, acessible as handles: + * FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H + * + * ACPI DSDT switches which set is in use depending on various + * factors. + * + * TPACPI_FAN_WR_TPEC is also available and should be used to + * command the fan. The X31/X40/X41 seems to have 8 fan levels, + * but the ACPI tables just mention level 7. + */ + +static enum fan_status_access_mode fan_status_access_mode; +static enum fan_control_access_mode fan_control_access_mode; +static enum fan_control_commands fan_control_commands; + +static u8 fan_control_initial_status; +static u8 fan_control_desired_level; + +static void fan_watchdog_fire(struct work_struct *ignored); +static int fan_watchdog_maxinterval; +static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire); + +IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */ +IBM_HANDLE(gfan, ec, "GFAN", /* 570 */ + "\\FSPD", /* 600e/x, 770e, 770x */ + ); /* all others */ +IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ + "JFNS", /* 770x-JL */ + ); /* all others */ + +/* + * SYSFS fan layout: hwmon compatible (device) + * + * pwm*_enable: + * 0: "disengaged" mode + * 1: manual mode + * 2: native EC "auto" mode (recommended, hardware default) + * + * pwm*: set speed in manual mode, ignored otherwise. + * 0 is level 0; 255 is level 7. Intermediate points done with linear + * interpolation. + * + * fan*_input: tachometer reading, RPM + * + * + * SYSFS fan layout: extensions + * + * fan_watchdog (driver): + * fan watchdog interval in seconds, 0 disables (default), max 120 + */ + +/* sysfs fan pwm1_enable ----------------------------------------------- */ +static ssize_t fan_pwm1_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, mode; + u8 status; + + res = fan_get_status_safe(&status); + if (res) + return res; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + /* Return most likely status. In fact, it + * might be the only possible status */ + status = TP_EC_FAN_AUTO; + } + } + + if (status & TP_EC_FAN_FULLSPEED) { + mode = 0; + } else if (status & TP_EC_FAN_AUTO) { + mode = 2; + } else + mode = 1; + + return snprintf(buf, PAGE_SIZE, "%d\n", mode); +} + +static ssize_t fan_pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, level; + + if (parse_strtoul(buf, 2, &t)) + return -EINVAL; + + switch (t) { + case 0: + level = TP_EC_FAN_FULLSPEED; + break; + case 1: + level = TPACPI_FAN_LAST_LEVEL; + break; + case 2: + level = TP_EC_FAN_AUTO; + break; + case 3: + /* reserved for software-controlled auto mode */ + return -ENOSYS; + default: + return -EINVAL; + } + + res = fan_set_level_safe(level); + if (res == -ENXIO) + return -EINVAL; + else if (res < 0) + return res; + + fan_watchdog_reset(); + + return count; +} + +static struct device_attribute dev_attr_fan_pwm1_enable = + __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + fan_pwm1_enable_show, fan_pwm1_enable_store); + +/* sysfs fan pwm1 ------------------------------------------------------ */ +static ssize_t fan_pwm1_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + u8 status; + + res = fan_get_status_safe(&status); + if (res) + return res; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + status = TP_EC_FAN_AUTO; + } + } + + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0) + status = fan_control_desired_level; + + if (status > 7) + status = 7; + + return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7); +} + +static ssize_t fan_pwm1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long s; + int rc; + u8 status, newlevel; + + if (parse_strtoul(buf, 255, &s)) + return -EINVAL; + + /* scale down from 0-255 to 0-7 */ + newlevel = (s >> 5) & 0x07; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = fan_get_status(&status); + if (!rc && (status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + rc = fan_set_level(newlevel); + if (rc == -ENXIO) + rc = -EINVAL; + else if (!rc) { + fan_update_desired_level(newlevel); + fan_watchdog_reset(); + } + } + + mutex_unlock(&fan_mutex); + return (rc)? rc : count; +} + +static struct device_attribute dev_attr_fan_pwm1 = + __ATTR(pwm1, S_IWUSR | S_IRUGO, + fan_pwm1_show, fan_pwm1_store); + +/* sysfs fan fan1_input ------------------------------------------------ */ +static ssize_t fan_fan1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + unsigned int speed; + + res = fan_get_speed(&speed); + if (res < 0) + return res; + + return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} + +static struct device_attribute dev_attr_fan_fan1_input = + __ATTR(fan1_input, S_IRUGO, + fan_fan1_input_show, NULL); + +/* sysfs fan fan_watchdog (driver) ------------------------------------- */ +static ssize_t fan_fan_watchdog_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval); +} + +static ssize_t fan_fan_watchdog_store(struct device_driver *drv, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 120, &t)) + return -EINVAL; + + if (!fan_control_allowed) + return -EPERM; + + fan_watchdog_maxinterval = t; + fan_watchdog_reset(); + + return count; +} + +static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, + fan_fan_watchdog_show, fan_fan_watchdog_store); + +/* --------------------------------------------------------------------- */ +static struct attribute *fan_attributes[] = { + &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, + &dev_attr_fan_fan1_input.attr, + NULL +}; + +static const struct attribute_group fan_attr_group = { + .attrs = fan_attributes, +}; + +static int __init fan_init(struct ibm_init_struct *iibm) +{ + int rc; + + vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); + + mutex_init(&fan_mutex); + fan_status_access_mode = TPACPI_FAN_NONE; + fan_control_access_mode = TPACPI_FAN_WR_NONE; + fan_control_commands = 0; + fan_watchdog_maxinterval = 0; + tp_features.fan_ctrl_status_undef = 0; + fan_control_desired_level = 7; + + IBM_ACPIHANDLE_INIT(fans); + IBM_ACPIHANDLE_INIT(gfan); + IBM_ACPIHANDLE_INIT(sfan); + + if (gfan_handle) { + /* 570, 600e/x, 770e, 770x */ + fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN; + } else { + /* all other ThinkPads: note that even old-style + * ThinkPad ECs supports the fan control register */ + if (likely(acpi_ec_read(fan_status_offset, + &fan_control_initial_status))) { + fan_status_access_mode = TPACPI_FAN_RD_TPEC; + + /* In some ThinkPads, neither the EC nor the ACPI + * DSDT initialize the fan status, and it ends up + * being set to 0x07 when it *could* be either + * 0x07 or 0x80. + * + * Enable for TP-1Y (T43), TP-78 (R51e), + * TP-76 (R52), TP-70 (T43, R52), which are known + * to be buggy. */ + if (fan_control_initial_status == 0x07 && + ibm_thinkpad_ec_found && + ((ibm_thinkpad_ec_found[0] == '1' && + ibm_thinkpad_ec_found[1] == 'Y') || + (ibm_thinkpad_ec_found[0] == '7' && + (ibm_thinkpad_ec_found[1] == '6' || + ibm_thinkpad_ec_found[1] == '8' || + ibm_thinkpad_ec_found[1] == '0')) + )) { + printk(IBM_NOTICE + "fan_init: initial fan status is " + "unknown, assuming it is in auto " + "mode\n"); + tp_features.fan_ctrl_status_undef = 1; + } + } else { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "fan status and control unavailable\n"); + return 1; + } + } + + if (sfan_handle) { + /* 570, 770x-JL */ + fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN; + fan_control_commands |= + TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE; + } else { + if (!gfan_handle) { + /* gfan without sfan means no fan control */ + /* all other models implement TP EC 0x2f control */ + + if (fans_handle) { + /* X31, X40, X41 */ + fan_control_access_mode = + TPACPI_FAN_WR_ACPI_FANS; + fan_control_commands |= + TPACPI_FAN_CMD_SPEED | + TPACPI_FAN_CMD_LEVEL | + TPACPI_FAN_CMD_ENABLE; + } else { + fan_control_access_mode = TPACPI_FAN_WR_TPEC; + fan_control_commands |= + TPACPI_FAN_CMD_LEVEL | + TPACPI_FAN_CMD_ENABLE; + } + } + } + + vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n", + str_supported(fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE), + fan_status_access_mode, fan_control_access_mode); + + /* fan control master switch */ + if (!fan_control_allowed) { + fan_control_access_mode = TPACPI_FAN_WR_NONE; + fan_control_commands = 0; + dbg_printk(TPACPI_DBG_INIT, + "fan control features disabled by parameter\n"); + } + + /* update fan_control_desired_level */ + if (fan_status_access_mode != TPACPI_FAN_NONE) + fan_get_status_safe(NULL); + + if (fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE) { + rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &fan_attr_group); + if (!(rc < 0)) + rc = driver_create_file(&tpacpi_pdriver.driver, + &driver_attr_fan_watchdog); + if (rc < 0) + return rc; + return 0; + } else + return 1; +} + +/* + * Call with fan_mutex held + */ +static void fan_update_desired_level(u8 status) +{ + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + if (status > 7) + fan_control_desired_level = 7; + else + fan_control_desired_level = status; + } +} + +static int fan_get_status(u8 *status) +{ + u8 s; + + /* TODO: + * Add TPACPI_FAN_RD_ACPI_FANS ? */ + + switch (fan_status_access_mode) { + case TPACPI_FAN_RD_ACPI_GFAN: + /* 570, 600e/x, 770e, 770x */ + + if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) + return -EIO; + + if (likely(status)) + *status = s & 0x07; + + break; + + case TPACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!acpi_ec_read(fan_status_offset, &s))) + return -EIO; + + if (likely(status)) + *status = s; + + break; + + default: + return -ENXIO; + } + + return 0; +} + +static int fan_get_status_safe(u8 *status) +{ + int rc; + u8 s; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + rc = fan_get_status(&s); + if (!rc) + fan_update_desired_level(s); + mutex_unlock(&fan_mutex); + + if (status) + *status = s; + + return rc; +} + +static void fan_exit(void) +{ + vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n"); + + /* FIXME: can we really do this unconditionally? */ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &fan_attr_group); + driver_remove_file(&tpacpi_pdriver.driver, &driver_attr_fan_watchdog); + + cancel_delayed_work(&fan_watchdog_task); + flush_scheduled_work(); +} + +static int fan_get_speed(unsigned int *speed) +{ + u8 hi, lo; + + switch (fan_status_access_mode) { + case TPACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) || + !acpi_ec_read(fan_rpm_offset + 1, &hi))) + return -EIO; + + if (likely(speed)) + *speed = (hi << 8) | lo; + + break; + + default: + return -ENXIO; + } + + return 0; +} + +static void fan_watchdog_fire(struct work_struct *ignored) +{ + int rc; + + printk(IBM_NOTICE "fan watchdog: enabling fan\n"); + rc = fan_set_enable(); + if (rc < 0) { + printk(IBM_ERR "fan watchdog: error %d while enabling fan, " + "will try again later...\n", -rc); + /* reschedule for later */ + fan_watchdog_reset(); + } +} + +static void fan_watchdog_reset(void) +{ + static int fan_watchdog_active = 0; + + if (fan_control_access_mode == TPACPI_FAN_WR_NONE) + return; + + if (fan_watchdog_active) + cancel_delayed_work(&fan_watchdog_task); + + if (fan_watchdog_maxinterval > 0) { + fan_watchdog_active = 1; + if (!schedule_delayed_work(&fan_watchdog_task, + msecs_to_jiffies(fan_watchdog_maxinterval + * 1000))) { + printk(IBM_ERR "failed to schedule the fan watchdog, " + "watchdog will not trigger\n"); + } + } else + fan_watchdog_active = 0; +} + +static int fan_set_level(int level) +{ + if (!fan_control_allowed) + return -EPERM; + + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_SFAN: + if (level >= 0 && level <= 7) { + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) + return -EIO; + } else + return -EINVAL; + break; + + case TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: + if ((level != TP_EC_FAN_AUTO) && + (level != TP_EC_FAN_FULLSPEED) && + ((level < 0) || (level > 7))) + return -EINVAL; + + /* safety net should the EC not support AUTO + * or FULLSPEED mode bits and just ignore them */ + if (level & TP_EC_FAN_FULLSPEED) + level |= 7; /* safety min speed 7 */ + else if (level & TP_EC_FAN_FULLSPEED) + level |= 4; /* safety min speed 4 */ + + if (!acpi_ec_write(fan_status_offset, level)) + return -EIO; + else + tp_features.fan_ctrl_status_undef = 0; + break; + + default: + return -ENXIO; + } + return 0; +} + +static int fan_set_level_safe(int level) +{ + int rc; + + if (!fan_control_allowed) + return -EPERM; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + if (level == TPACPI_FAN_LAST_LEVEL) + level = fan_control_desired_level; + + rc = fan_set_level(level); + if (!rc) + fan_update_desired_level(level); + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_set_enable(void) +{ + u8 s; + int rc; + + if (!fan_control_allowed) + return -EPERM; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: + rc = fan_get_status(&s); + if (rc < 0) + break; + + /* Don't go out of emergency fan mode */ + if (s != 7) { + s &= 0x07; + s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */ + } + + if (!acpi_ec_write(fan_status_offset, s)) + rc = -EIO; + else { + tp_features.fan_ctrl_status_undef = 0; + rc = 0; + } + break; + + case TPACPI_FAN_WR_ACPI_SFAN: + rc = fan_get_status(&s); + if (rc < 0) + break; + + s &= 0x07; + + /* Set fan to at least level 4 */ + s |= 4; + + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) + rc= -EIO; + else + rc = 0; + break; + + default: + rc = -ENXIO; + } + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_set_disable(void) +{ + int rc; + + if (!fan_control_allowed) + return -EPERM; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = 0; + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: + if (!acpi_ec_write(fan_status_offset, 0x00)) + rc = -EIO; + else { + fan_control_desired_level = 0; + tp_features.fan_ctrl_status_undef = 0; + } + break; + + case TPACPI_FAN_WR_ACPI_SFAN: + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) + rc = -EIO; + else + fan_control_desired_level = 0; + break; + + default: + rc = -ENXIO; + } + + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_set_speed(int speed) +{ + int rc; + + if (!fan_control_allowed) + return -EPERM; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = 0; + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_FANS: + if (speed >= 0 && speed <= 65535) { + if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", + speed, speed, speed)) + rc = -EIO; + } else + rc = -EINVAL; + break; + + default: + rc = -ENXIO; + } + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_read(char *p) +{ + int len = 0; + int rc; + u8 status; + unsigned int speed = 0; + + switch (fan_status_access_mode) { + case TPACPI_FAN_RD_ACPI_GFAN: + /* 570, 600e/x, 770e, 770x */ + if ((rc = fan_get_status_safe(&status)) < 0) + return rc; + + len += sprintf(p + len, "status:\t\t%s\n" + "level:\t\t%d\n", + (status != 0) ? "enabled" : "disabled", status); + break; + + case TPACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if ((rc = fan_get_status_safe(&status)) < 0) + return rc; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) + tp_features.fan_ctrl_status_undef = 0; + else + /* Return most likely status. In fact, it + * might be the only possible status */ + status = TP_EC_FAN_AUTO; + } + + len += sprintf(p + len, "status:\t\t%s\n", + (status != 0) ? "enabled" : "disabled"); + + if ((rc = fan_get_speed(&speed)) < 0) + return rc; + + len += sprintf(p + len, "speed:\t\t%d\n", speed); + + if (status & TP_EC_FAN_FULLSPEED) + /* Disengaged mode takes precedence */ + len += sprintf(p + len, "level:\t\tdisengaged\n"); + else if (status & TP_EC_FAN_AUTO) + len += sprintf(p + len, "level:\t\tauto\n"); + else + len += sprintf(p + len, "level:\t\t%d\n", status); + break; + + case TPACPI_FAN_NONE: + default: + len += sprintf(p + len, "status:\t\tnot supported\n"); + } + + if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) { + len += sprintf(p + len, "commands:\tlevel <level>"); + + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_SFAN: + len += sprintf(p + len, " (<level> is 0-7)\n"); + break; + + default: + len += sprintf(p + len, " (<level> is 0-7, " + "auto, disengaged, full-speed)\n"); + break; + } + } + + if (fan_control_commands & TPACPI_FAN_CMD_ENABLE) + len += sprintf(p + len, "commands:\tenable, disable\n" + "commands:\twatchdog <timeout> (<timeout> is 0 (off), " + "1-120 (seconds))\n"); + + if (fan_control_commands & TPACPI_FAN_CMD_SPEED) + len += sprintf(p + len, "commands:\tspeed <speed>" + " (<speed> is 0-65535)\n"); + + return len; +} + +static int fan_write_cmd_level(const char *cmd, int *rc) +{ + int level; + + if (strlencmp(cmd, "level auto") == 0) + level = TP_EC_FAN_AUTO; + else if ((strlencmp(cmd, "level disengaged") == 0) | + (strlencmp(cmd, "level full-speed") == 0)) + level = TP_EC_FAN_FULLSPEED; + else if (sscanf(cmd, "level %d", &level) != 1) + return 0; + + if ((*rc = fan_set_level_safe(level)) == -ENXIO) + printk(IBM_ERR "level command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_enable(const char *cmd, int *rc) +{ + if (strlencmp(cmd, "enable") != 0) + return 0; + + if ((*rc = fan_set_enable()) == -ENXIO) + printk(IBM_ERR "enable command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_disable(const char *cmd, int *rc) +{ + if (strlencmp(cmd, "disable") != 0) + return 0; + + if ((*rc = fan_set_disable()) == -ENXIO) + printk(IBM_ERR "disable command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_speed(const char *cmd, int *rc) +{ + int speed; + + /* TODO: + * Support speed <low> <medium> <high> ? */ + + if (sscanf(cmd, "speed %d", &speed) != 1) + return 0; + + if ((*rc = fan_set_speed(speed)) == -ENXIO) + printk(IBM_ERR "speed command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_watchdog(const char *cmd, int *rc) +{ + int interval; + + if (sscanf(cmd, "watchdog %d", &interval) != 1) + return 0; + + if (interval < 0 || interval > 120) + *rc = -EINVAL; + else + fan_watchdog_maxinterval = interval; + + return 1; +} + +static int fan_write(char *buf) +{ + char *cmd; + int rc = 0; + + while (!rc && (cmd = next_cmd(&buf))) { + if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) && + fan_write_cmd_level(cmd, &rc)) && + !((fan_control_commands & TPACPI_FAN_CMD_ENABLE) && + (fan_write_cmd_enable(cmd, &rc) || + fan_write_cmd_disable(cmd, &rc) || + fan_write_cmd_watchdog(cmd, &rc))) && + !((fan_control_commands & TPACPI_FAN_CMD_SPEED) && + fan_write_cmd_speed(cmd, &rc)) + ) + rc = -EINVAL; + else if (!rc) + fan_watchdog_reset(); + } + + return rc; +} + +static struct ibm_struct fan_driver_data = { + .name = "fan", + .read = fan_read, + .write = fan_write, + .exit = fan_exit, +}; + +/**************************************************************************** + **************************************************************************** + * + * Infrastructure + * + **************************************************************************** + ****************************************************************************/ + +/* /proc support */ +static struct proc_dir_entry *proc_dir = NULL; + +/* Subdriver registry */ +static LIST_HEAD(tpacpi_all_drivers); + + +/* + * Module and infrastructure proble, init and exit handling + */ + +#ifdef CONFIG_THINKPAD_ACPI_DEBUG +static const char * __init str_supported(int is_supported) +{ + static char text_unsupported[] __initdata = "not supported"; + + return (is_supported)? &text_unsupported[4] : &text_unsupported[0]; +} +#endif /* CONFIG_THINKPAD_ACPI_DEBUG */ + +static int __init ibm_init(struct ibm_init_struct *iibm) +{ + int ret; + struct ibm_struct *ibm = iibm->data; + struct proc_dir_entry *entry; + + BUG_ON(ibm == NULL); + + INIT_LIST_HEAD(&ibm->all_drivers); + + if (ibm->flags.experimental && !experimental) + return 0; + + dbg_printk(TPACPI_DBG_INIT, + "probing for %s\n", ibm->name); + + if (iibm->init) { + ret = iibm->init(iibm); + if (ret > 0) + return 0; /* probe failed */ + if (ret) + return ret; + + ibm->flags.init_called = 1; + } + + if (ibm->acpi) { + if (ibm->acpi->hid) { + ret = register_tpacpi_subdriver(ibm); + if (ret) + goto err_out; + } + + if (ibm->acpi->notify) { + ret = setup_acpi_notify(ibm); + if (ret == -ENODEV) { + printk(IBM_NOTICE "disabling subdriver %s\n", + ibm->name); + ret = 0; + goto err_out; + } + if (ret < 0) + goto err_out; + } + } + + dbg_printk(TPACPI_DBG_INIT, + "%s installed\n", ibm->name); + + if (ibm->read) { + entry = create_proc_entry(ibm->name, + S_IFREG | S_IRUGO | S_IWUSR, + proc_dir); + if (!entry) { + printk(IBM_ERR "unable to create proc entry %s\n", + ibm->name); + ret = -ENODEV; + goto err_out; + } + entry->owner = THIS_MODULE; + entry->data = ibm; + entry->read_proc = &dispatch_procfs_read; + if (ibm->write) + entry->write_proc = &dispatch_procfs_write; + ibm->flags.proc_created = 1; + } + + list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers); + + return 0; + +err_out: + dbg_printk(TPACPI_DBG_INIT, + "%s: at error exit path with result %d\n", + ibm->name, ret); + + ibm_exit(ibm); + return (ret < 0)? ret : 0; +} + +static void ibm_exit(struct ibm_struct *ibm) +{ + dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name); + + list_del_init(&ibm->all_drivers); + + if (ibm->flags.acpi_notify_installed) { + dbg_printk(TPACPI_DBG_EXIT, + "%s: acpi_remove_notify_handler\n", ibm->name); + BUG_ON(!ibm->acpi); + acpi_remove_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, + dispatch_acpi_notify); + ibm->flags.acpi_notify_installed = 0; + ibm->flags.acpi_notify_installed = 0; + } + + if (ibm->flags.proc_created) { + dbg_printk(TPACPI_DBG_EXIT, + "%s: remove_proc_entry\n", ibm->name); + remove_proc_entry(ibm->name, proc_dir); + ibm->flags.proc_created = 0; + } + + if (ibm->flags.acpi_driver_registered) { + dbg_printk(TPACPI_DBG_EXIT, + "%s: acpi_bus_unregister_driver\n", ibm->name); + BUG_ON(!ibm->acpi); + acpi_bus_unregister_driver(ibm->acpi->driver); + kfree(ibm->acpi->driver); + ibm->acpi->driver = NULL; + ibm->flags.acpi_driver_registered = 0; + } + + if (ibm->flags.init_called && ibm->exit) { + ibm->exit(); + ibm->flags.init_called = 0; + } + + dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name); +} + +/* Probing */ + +static char *ibm_thinkpad_ec_found = NULL; + +static char* __init check_dmi_for_ec(void) +{ + struct dmi_device *dev = NULL; + char ec_fw_string[18]; + + /* + * ThinkPad T23 or newer, A31 or newer, R50e or newer, + * X32 or newer, all Z series; Some models must have an + * up-to-date BIOS or they will not be detected. + * + * See http://thinkwiki.org/wiki/List_of_DMI_IDs + */ + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { + if (sscanf(dev->name, + "IBM ThinkPad Embedded Controller -[%17c", + ec_fw_string) == 1) { + ec_fw_string[sizeof(ec_fw_string) - 1] = 0; + ec_fw_string[strcspn(ec_fw_string, " ]")] = 0; + return kstrdup(ec_fw_string, GFP_KERNEL); + } + } + return NULL; +} + +static int __init probe_for_thinkpad(void) +{ + int is_thinkpad; + + if (acpi_disabled) + return -ENODEV; + + /* + * Non-ancient models have better DMI tagging, but very old models + * don't. + */ + is_thinkpad = dmi_name_in_vendors("ThinkPad"); + + /* ec is required because many other handles are relative to it */ + IBM_ACPIHANDLE_INIT(ec); + if (!ec_handle) { + if (is_thinkpad) + printk(IBM_ERR + "Not yet supported ThinkPad detected!\n"); + return -ENODEV; + } + + /* + * Risks a regression on very old machines, but reduces potential + * false positives a damn great deal + */ + if (!is_thinkpad) + is_thinkpad = dmi_name_in_vendors("IBM"); + + if (!is_thinkpad && !force_load) + return -ENODEV; + + return 0; +} + + +/* Module init, exit, parameters */ + +static struct ibm_init_struct ibms_init[] __initdata = { + { + .init = thinkpad_acpi_driver_init, + .data = &thinkpad_acpi_driver_data, + }, + { + .init = hotkey_init, + .data = &hotkey_driver_data, + }, + { + .init = bluetooth_init, + .data = &bluetooth_driver_data, + }, + { + .init = wan_init, + .data = &wan_driver_data, + }, + { + .init = video_init, + .data = &video_driver_data, + }, + { + .init = light_init, + .data = &light_driver_data, + }, +#ifdef CONFIG_THINKPAD_ACPI_DOCK + { + .init = dock_init, + .data = &dock_driver_data[0], + }, + { + .init = dock_init2, + .data = &dock_driver_data[1], + }, +#endif +#ifdef CONFIG_THINKPAD_ACPI_BAY + { + .init = bay_init, + .data = &bay_driver_data, + }, +#endif + { + .init = cmos_init, + .data = &cmos_driver_data, + }, + { + .init = led_init, + .data = &led_driver_data, + }, + { + .init = beep_init, + .data = &beep_driver_data, + }, + { + .init = thermal_init, + .data = &thermal_driver_data, + }, + { + .data = &ecdump_driver_data, + }, + { + .init = brightness_init, + .data = &brightness_driver_data, + }, + { + .data = &volume_driver_data, + }, + { + .init = fan_init, + .data = &fan_driver_data, + }, +}; + +static int __init set_ibm_param(const char *val, struct kernel_param *kp) +{ + unsigned int i; + struct ibm_struct *ibm; + + for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { + ibm = ibms_init[i].data; + BUG_ON(ibm == NULL); + + if (strcmp(ibm->name, kp->name) == 0 && ibm->write) { + if (strlen(val) > sizeof(ibms_init[i].param) - 2) + return -ENOSPC; + strcpy(ibms_init[i].param, val); + strcat(ibms_init[i].param, ","); + return 0; + } + } + + return -EINVAL; +} + +static int experimental; +module_param(experimental, int, 0); + +static u32 dbg_level; +module_param_named(debug, dbg_level, uint, 0); + +static int force_load; +module_param(force_load, int, 0); + +static int fan_control_allowed; +module_param_named(fan_control, fan_control_allowed, int, 0); + +#define IBM_PARAM(feature) \ + module_param_call(feature, set_ibm_param, NULL, NULL, 0) + +IBM_PARAM(hotkey); +IBM_PARAM(bluetooth); +IBM_PARAM(video); +IBM_PARAM(light); +#ifdef CONFIG_THINKPAD_ACPI_DOCK +IBM_PARAM(dock); +#endif +#ifdef CONFIG_THINKPAD_ACPI_BAY +IBM_PARAM(bay); +#endif /* CONFIG_THINKPAD_ACPI_BAY */ +IBM_PARAM(cmos); +IBM_PARAM(led); +IBM_PARAM(beep); +IBM_PARAM(ecdump); +IBM_PARAM(brightness); +IBM_PARAM(volume); +IBM_PARAM(fan); + +static int __init thinkpad_acpi_module_init(void) +{ + int ret, i; + + /* Driver-level probe */ + ret = probe_for_thinkpad(); + if (ret) + return ret; + + /* Driver initialization */ + ibm_thinkpad_ec_found = check_dmi_for_ec(); + IBM_ACPIHANDLE_INIT(ecrd); + IBM_ACPIHANDLE_INIT(ecwr); + + proc_dir = proc_mkdir(IBM_PROC_DIR, acpi_root_dir); + if (!proc_dir) { + printk(IBM_ERR "unable to create proc dir " IBM_PROC_DIR); + thinkpad_acpi_module_exit(); + return -ENODEV; + } + proc_dir->owner = THIS_MODULE; + + ret = platform_driver_register(&tpacpi_pdriver); + if (ret) { + printk(IBM_ERR "unable to register platform driver\n"); + thinkpad_acpi_module_exit(); + return ret; + } + ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver); + if (ret) { + printk(IBM_ERR "unable to create sysfs driver attributes\n"); + thinkpad_acpi_module_exit(); + return ret; + } + + + /* Device initialization */ + tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1, + NULL, 0); + if (IS_ERR(tpacpi_pdev)) { + ret = PTR_ERR(tpacpi_pdev); + tpacpi_pdev = NULL; + printk(IBM_ERR "unable to register platform device\n"); + thinkpad_acpi_module_exit(); + return ret; + } + tpacpi_hwmon = hwmon_device_register(&tpacpi_pdev->dev); + if (IS_ERR(tpacpi_hwmon)) { + ret = PTR_ERR(tpacpi_hwmon); + tpacpi_hwmon = NULL; + printk(IBM_ERR "unable to register hwmon device\n"); + thinkpad_acpi_module_exit(); + return ret; + } + for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { + ret = ibm_init(&ibms_init[i]); + if (ret >= 0 && *ibms_init[i].param) + ret = ibms_init[i].data->write(ibms_init[i].param); + if (ret < 0) { + thinkpad_acpi_module_exit(); + return ret; + } + } + + return 0; +} + +static void thinkpad_acpi_module_exit(void) +{ + struct ibm_struct *ibm, *itmp; + + list_for_each_entry_safe_reverse(ibm, itmp, + &tpacpi_all_drivers, + all_drivers) { + ibm_exit(ibm); + } + + dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n"); + + if (tpacpi_hwmon) + hwmon_device_unregister(tpacpi_hwmon); + + if (tpacpi_pdev) + platform_device_unregister(tpacpi_pdev); + + tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver); + platform_driver_unregister(&tpacpi_pdriver); + + if (proc_dir) + remove_proc_entry(IBM_PROC_DIR, acpi_root_dir); + + kfree(ibm_thinkpad_ec_found); +} + +module_init(thinkpad_acpi_module_init); +module_exit(thinkpad_acpi_module_exit); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h new file mode 100644 index 000000000000..440145a02617 --- /dev/null +++ b/drivers/misc/thinkpad_acpi.h @@ -0,0 +1,572 @@ +/* + * thinkpad_acpi.h - ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net> + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh <hmh@hmh.eng.br> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __THINKPAD_ACPI_H__ +#define __THINKPAD_ACPI_H__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/list.h> +#include <linux/mutex.h> + +#include <linux/proc_fs.h> +#include <linux/sysfs.h> +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/platform_device.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <asm/uaccess.h> + +#include <linux/dmi.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> + +#include <acpi/acpi_drivers.h> +#include <acpi/acnamesp.h> + + +/**************************************************************************** + * Main driver + */ + +#define IBM_NAME "thinkpad" +#define IBM_DESC "ThinkPad ACPI Extras" +#define IBM_FILE "thinkpad_acpi" +#define IBM_URL "http://ibm-acpi.sf.net/" +#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net" + +#define IBM_PROC_DIR "ibm" +#define IBM_ACPI_EVENT_PREFIX "ibm" +#define IBM_DRVR_NAME IBM_FILE + +#define IBM_LOG IBM_FILE ": " +#define IBM_ERR KERN_ERR IBM_LOG +#define IBM_NOTICE KERN_NOTICE IBM_LOG +#define IBM_INFO KERN_INFO IBM_LOG +#define IBM_DEBUG KERN_DEBUG IBM_LOG + +#define IBM_MAX_ACPI_ARGS 3 + +/* ThinkPad CMOS commands */ +#define TP_CMOS_VOLUME_DOWN 0 +#define TP_CMOS_VOLUME_UP 1 +#define TP_CMOS_VOLUME_MUTE 2 +#define TP_CMOS_BRIGHTNESS_UP 4 +#define TP_CMOS_BRIGHTNESS_DOWN 5 + +#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off") +#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") +#define strlencmp(a,b) (strncmp((a), (b), strlen(b))) + +/* Debugging */ +#define TPACPI_DBG_ALL 0xffff +#define TPACPI_DBG_ALL 0xffff +#define TPACPI_DBG_INIT 0x0001 +#define TPACPI_DBG_EXIT 0x0002 +#define dbg_printk(a_dbg_level, format, arg...) \ + do { if (dbg_level & a_dbg_level) \ + printk(IBM_DEBUG "%s: " format, __func__ , ## arg); } while (0) +#ifdef CONFIG_THINKPAD_ACPI_DEBUG +#define vdbg_printk(a_dbg_level, format, arg...) \ + dbg_printk(a_dbg_level, format, ## arg) +static const char *str_supported(int is_supported); +#else +#define vdbg_printk(a_dbg_level, format, arg...) +#endif + +/* ACPI HIDs */ +#define IBM_HKEY_HID "IBM0068" +#define IBM_PCI_HID "PNP0A03" + +/* ACPI helpers */ +static int __must_check acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...); +static int __must_check acpi_ec_read(int i, u8 * p); +static int __must_check acpi_ec_write(int i, u8 v); +static int __must_check _sta(acpi_handle handle); + +/* ACPI handles */ +static acpi_handle root_handle; /* root namespace */ +static acpi_handle ec_handle; /* EC */ +static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ +static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ + +static void drv_acpi_handle_init(char *name, + acpi_handle *handle, acpi_handle parent, + char **paths, int num_paths, char **path); +#define IBM_ACPIHANDLE_INIT(object) \ + drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \ + object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + +/* ThinkPad ACPI helpers */ +static int issue_thinkpad_cmos_command(int cmos_cmd); + +/* procfs support */ +static struct proc_dir_entry *proc_dir; + +/* procfs helpers */ +static int dispatch_procfs_read(char *page, char **start, off_t off, + int count, int *eof, void *data); +static int dispatch_procfs_write(struct file *file, + const char __user * userbuf, + unsigned long count, void *data); +static char *next_cmd(char **cmds); + +/* sysfs support */ +struct attribute_set { + unsigned int members, max_members; + struct attribute_group group; +}; + +static struct attribute_set *create_attr_set(unsigned int max_members, + const char* name); +#define destroy_attr_set(_set) \ + kfree(_set); +static int add_to_attr_set(struct attribute_set* s, struct attribute *attr); +static int add_many_to_attr_set(struct attribute_set* s, + struct attribute **attr, + unsigned int count); +#define register_attr_set_with_sysfs(_attr_set, _kobj) \ + sysfs_create_group(_kobj, &_attr_set->group) +static void delete_attr_set(struct attribute_set* s, struct kobject *kobj); + +static int parse_strtoul(const char *buf, unsigned long max, + unsigned long *value); + +/* Device model */ +static struct platform_device *tpacpi_pdev; +static struct class_device *tpacpi_hwmon; +static struct platform_driver tpacpi_pdriver; +static int tpacpi_create_driver_attributes(struct device_driver *drv); +static void tpacpi_remove_driver_attributes(struct device_driver *drv); + +/* Module */ +static int experimental; +static u32 dbg_level; +static int force_load; +static char *ibm_thinkpad_ec_found; + +static char* check_dmi_for_ec(void); +static int thinkpad_acpi_module_init(void); +static void thinkpad_acpi_module_exit(void); + + +/**************************************************************************** + * Subdrivers + */ + +struct ibm_struct; + +struct tp_acpi_drv_struct { + char *hid; + struct acpi_driver *driver; + + void (*notify) (struct ibm_struct *, u32); + acpi_handle *handle; + u32 type; + struct acpi_device *device; +}; + +struct ibm_struct { + char *name; + + int (*read) (char *); + int (*write) (char *); + void (*exit) (void); + + struct list_head all_drivers; + + struct tp_acpi_drv_struct *acpi; + + struct { + u8 acpi_driver_registered:1; + u8 acpi_notify_installed:1; + u8 proc_created:1; + u8 init_called:1; + u8 experimental:1; + } flags; +}; + +struct ibm_init_struct { + char param[32]; + + int (*init) (struct ibm_init_struct *); + struct ibm_struct *data; +}; + +static struct { +#ifdef CONFIG_THINKPAD_ACPI_BAY + u16 bay_status:1; + u16 bay_eject:1; + u16 bay_status2:1; + u16 bay_eject2:1; +#endif + u16 bluetooth:1; + u16 hotkey:1; + u16 hotkey_mask:1; + u16 light:1; + u16 light_status:1; + u16 wan:1; + u16 fan_ctrl_status_undef:1; +} tp_features; + +static struct list_head tpacpi_all_drivers; + +static struct ibm_init_struct ibms_init[]; +static int set_ibm_param(const char *val, struct kernel_param *kp); +static int ibm_init(struct ibm_init_struct *iibm); +static void ibm_exit(struct ibm_struct *ibm); + + +/* + * procfs master subdriver + */ +static int thinkpad_acpi_driver_init(struct ibm_init_struct *iibm); +static int thinkpad_acpi_driver_read(char *p); + + +/* + * Bay subdriver + */ + +#ifdef CONFIG_THINKPAD_ACPI_BAY +static acpi_handle bay_handle, bay_ej_handle; +static acpi_handle bay2_handle, bay2_ej_handle; + +static int bay_init(struct ibm_init_struct *iibm); +static void bay_notify(struct ibm_struct *ibm, u32 event); +static int bay_read(char *p); +static int bay_write(char *buf); +#endif /* CONFIG_THINKPAD_ACPI_BAY */ + + +/* + * Beep subdriver + */ + +static acpi_handle beep_handle; + +static int beep_read(char *p); +static int beep_write(char *buf); + + +/* + * Bluetooth subdriver + */ + +#define TPACPI_BLUETH_SYSFS_GROUP "bluetooth" + +enum { + /* ACPI GBDC/SBDC bits */ + TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ + TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */ + TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */ +}; + +static int bluetooth_init(struct ibm_init_struct *iibm); +static int bluetooth_get_radiosw(void); +static int bluetooth_set_radiosw(int radio_on); +static int bluetooth_read(char *p); +static int bluetooth_write(char *buf); + + +/* + * Brightness (backlight) subdriver + */ + +#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen" + +static struct backlight_device *ibm_backlight_device; +static int brightness_offset = 0x31; + +static int brightness_init(struct ibm_init_struct *iibm); +static void brightness_exit(void); +static int brightness_get(struct backlight_device *bd); +static int brightness_set(int value); +static int brightness_update_status(struct backlight_device *bd); +static int brightness_read(char *p); +static int brightness_write(char *buf); + + +/* + * CMOS subdriver + */ + +static int cmos_read(char *p); +static int cmos_write(char *buf); + + +/* + * Dock subdriver + */ + +#ifdef CONFIG_THINKPAD_ACPI_DOCK +static acpi_handle pci_handle; +static acpi_handle dock_handle; + +static void dock_notify(struct ibm_struct *ibm, u32 event); +static int dock_read(char *p); +static int dock_write(char *buf); +#endif /* CONFIG_THINKPAD_ACPI_DOCK */ + + +/* + * EC dump subdriver + */ + +static int ecdump_read(char *p) ; +static int ecdump_write(char *buf); + + +/* + * Fan subdriver + */ + +enum { /* Fan control constants */ + fan_status_offset = 0x2f, /* EC register 0x2f */ + fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) + * 0x84 must be read before 0x85 */ + + TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ + TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ + + TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */ +}; + +enum fan_status_access_mode { + TPACPI_FAN_NONE = 0, /* No fan status or control */ + TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ +}; + +enum fan_control_access_mode { + TPACPI_FAN_WR_NONE = 0, /* No fan control */ + TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ +}; + +enum fan_control_commands { + TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + * and also watchdog cmd */ +}; + +static int fan_control_allowed; + +static enum fan_status_access_mode fan_status_access_mode; +static enum fan_control_access_mode fan_control_access_mode; +static enum fan_control_commands fan_control_commands; +static u8 fan_control_initial_status; +static u8 fan_control_desired_level; +static int fan_watchdog_maxinterval; + +static struct mutex fan_mutex; + +static acpi_handle fans_handle, gfan_handle, sfan_handle; + +static int fan_init(struct ibm_init_struct *iibm); +static void fan_exit(void); +static int fan_get_status(u8 *status); +static int fan_get_status_safe(u8 *status); +static int fan_get_speed(unsigned int *speed); +static void fan_update_desired_level(u8 status); +static void fan_watchdog_fire(struct work_struct *ignored); +static void fan_watchdog_reset(void); +static int fan_set_level(int level); +static int fan_set_level_safe(int level); +static int fan_set_enable(void); +static int fan_set_disable(void); +static int fan_set_speed(int speed); +static int fan_read(char *p); +static int fan_write(char *buf); +static int fan_write_cmd_level(const char *cmd, int *rc); +static int fan_write_cmd_enable(const char *cmd, int *rc); +static int fan_write_cmd_disable(const char *cmd, int *rc); +static int fan_write_cmd_speed(const char *cmd, int *rc); +static int fan_write_cmd_watchdog(const char *cmd, int *rc); + + +/* + * Hotkey subdriver + */ + +#define TPACPI_HOTKEY_SYSFS_GROUP "hotkey" + +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static struct mutex hotkey_mutex; + +static int hotkey_init(struct ibm_init_struct *iibm); +static void hotkey_exit(void); +static int hotkey_get(int *status, int *mask); +static int hotkey_set(int status, int mask); +static void hotkey_notify(struct ibm_struct *ibm, u32 event); +static int hotkey_read(char *p); +static int hotkey_write(char *buf); + + +/* + * LED subdriver + */ + +enum led_access_mode { + TPACPI_LED_NONE = 0, + TPACPI_LED_570, /* 570 */ + TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + TPACPI_LED_NEW, /* all others */ +}; + +enum { /* For TPACPI_LED_OLD */ + TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +}; + +static enum led_access_mode led_supported; +static acpi_handle led_handle; + +static int led_init(struct ibm_init_struct *iibm); +static int led_read(char *p); +static int led_write(char *buf); + +/* + * Light (thinklight) subdriver + */ + +static acpi_handle lght_handle, ledb_handle; + +static int light_init(struct ibm_init_struct *iibm); +static int light_read(char *p); +static int light_write(char *buf); + + +/* + * Thermal subdriver + */ + +enum thermal_access_mode { + TPACPI_THERMAL_NONE = 0, /* No thermal support */ + TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ +}; + +enum { /* TPACPI_THERMAL_TPEC_* */ + TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ + TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ + TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ +}; + +#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +struct ibm_thermal_sensors_struct { + s32 temp[TPACPI_MAX_THERMAL_SENSORS]; +}; + +static enum thermal_access_mode thermal_read_mode; + +static int thermal_init(struct ibm_init_struct *iibm); +static int thermal_get_sensor(int idx, s32 *value); +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); +static int thermal_read(char *p); + + +/* + * Video subdriver + */ + +enum video_access_mode { + TPACPI_VIDEO_NONE = 0, + TPACPI_VIDEO_570, /* 570 */ + TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + TPACPI_VIDEO_NEW, /* all others */ +}; + +enum { /* video status flags, based on VIDEO_570 */ + TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */ + TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */ + TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */ +}; + +enum { /* TPACPI_VIDEO_570 constants */ + TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */ + TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to + * video_status_flags */ + TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */ + TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */ +}; + +static enum video_access_mode video_supported; +static int video_orig_autosw; +static acpi_handle vid_handle, vid2_handle; + +static int video_init(struct ibm_init_struct *iibm); +static void video_exit(void); +static int video_outputsw_get(void); +static int video_outputsw_set(int status); +static int video_autosw_get(void); +static int video_autosw_set(int enable); +static int video_outputsw_cycle(void); +static int video_expand_toggle(void); +static int video_read(char *p); +static int video_write(char *buf); + + +/* + * Volume subdriver + */ + +static int volume_offset = 0x30; + +static int volume_read(char *p); +static int volume_write(char *buf); + + +/* + * Wan subdriver + */ + +#define TPACPI_WAN_SYSFS_GROUP "wwan" + +enum { + /* ACPI GWAN/SWAN bits */ + TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ + TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */ + TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */ +}; + +static int wan_init(struct ibm_init_struct *iibm); +static int wan_get_radiosw(void); +static int wan_set_radiosw(int radio_on); +static int wan_read(char *p); +static int wan_write(char *buf); + + +#endif /* __THINKPAD_ACPI_H */ |