diff options
author | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-04-29 10:47:25 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-04-29 10:47:25 -0700 |
commit | 6b06d2cc6d52830e3e0c001006b26255f47184dd (patch) | |
tree | 2df1fbc87b1d83b97b230c207dbe795e2c5c6423 /drivers | |
parent | b9099ff63c75216d6ca10bce5a1abcd9293c27e6 (diff) | |
parent | aac60c11132d7ab0776cf036ac9fd6113a210859 (diff) | |
download | linux-stable-6b06d2cc6d52830e3e0c001006b26255f47184dd.tar.gz linux-stable-6b06d2cc6d52830e3e0c001006b26255f47184dd.tar.bz2 linux-stable-6b06d2cc6d52830e3e0c001006b26255f47184dd.zip |
Merge branch 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6
* 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6: (105 commits)
sonypi: use mutex instead of semaphore
sony-laptop: remove user visible camera controls as platform attributes
meye: make meye use sony-laptop instead of sonypi
sony-laptop: add a meye-usable include file for camera ops
sony-laptop: complete the motion eye camera support in sony-laptop
sonypi: try to detect if sony-laptop has already taken one of the known ioports
sonypi: suggest sonypi users to try sony-laptop instead
sony-laptop: add edge modem support (also called WWAN)
sony-laptop: add locking on accesses to the ioport and global vars
sony-laptop: add camera enable/disable parameter, better handle possible infinite loop
thinkpad-acpi: make drivers/misc/thinkpad_acpi:fan_mutex static
ACPI: thinkpad-acpi: add sysfs support to wan and bluetooth subdrivers
ACPI: thinkpad-acpi: add sysfs support to hotkey subdriver
ACPI: thinkpad-acpi: improve dock subdriver initialization
ACPI: thinkpad-acpi: improve debugging for acpi helpers
ACPI: thinkpad-acpi: improve fan control documentation
ACPI: thinkpad-acpi: map ENXIO to EINVAL for fan sysfs
ACPI: thinkpad-acpi: fix a fan watchdog invocation
ACPI: thinkpad-acpi: do not arm fan watchdog if it would not work
ACPI: thinkpad-acpi: add a fan-control feature master toggle
...
Diffstat (limited to 'drivers')
28 files changed, 7981 insertions, 4537 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index e2ce4a9c1c92..139f41f033d8 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -85,8 +85,8 @@ config ACPI_PROCFS depends on ACPI default y ---help--- - Procfs interface for ACPI is made optional for back-compatible. - As the same functions are duplicated in sysfs interface + The Procfs interface for ACPI is made optional for backward compatibility. + As the same functions are duplicated in the sysfs interface and this proc interface will be removed some time later, it's marked as deprecated. ( /proc/acpi/debug_layer && debug_level are deprecated by @@ -218,43 +218,6 @@ config ACPI_ASUS NOTE: This driver is deprecated and will probably be removed soon, use asus-laptop instead. -config ACPI_IBM - tristate "IBM ThinkPad Laptop Extras" - depends on X86 - select BACKLIGHT_CLASS_DEVICE - ---help--- - This is a Linux ACPI driver for the IBM 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/ibm-acpi.txt> - and <http://ibm-acpi.sf.net/> . - - If you have an IBM ThinkPad laptop, say Y or M here. - -config ACPI_IBM_DOCK - bool "Legacy Docking Station Support" - depends on ACPI_IBM - depends on ACPI_DOCK=n - default n - ---help--- - Allows the ibm_acpi driver to handle docking station events. - This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI. 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 ACPI_IBM_BAY - bool "Legacy Removable Bay Support" - depends on ACPI_IBM - default y - ---help--- - Allows the ibm_acpi driver to handle removable bays. It will allow - disabling 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. - config ACPI_TOSHIBA tristate "Toshiba Laptop Extras" depends on X86 @@ -388,11 +351,10 @@ config ACPI_HOTPLUG_MEMORY config ACPI_SBS tristate "Smart Battery System (EXPERIMENTAL)" - depends on X86 && I2C + depends on X86 depends on EXPERIMENTAL help This driver adds support for the Smart Battery System. - Depends on I2C (Device Drivers ---> I2C support) A "Smart Battery" is quite old and quite rare compared to today's ACPI "Control Method" battery. diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 5956e9f64a8b..d4336f1730e9 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -1,6 +1,6 @@ # # Makefile for the Linux ACPI interpreter -# +# export ACPI_CFLAGS @@ -32,16 +32,17 @@ obj-y += osl.o utils.o \ processor-objs += processor_core.o processor_throttling.o \ processor_idle.o processor_thermal.o ifdef CONFIG_CPU_FREQ -processor-objs += processor_perflib.o +processor-objs += processor_perflib.o endif obj-y += sleep/ obj-y += bus.o glue.o obj-y += scan.o +# Keep EC driver first. Initialization of others depend on it. +obj-$(CONFIG_ACPI_EC) += ec.o obj-$(CONFIG_ACPI_AC) += ac.o obj-$(CONFIG_ACPI_BATTERY) += battery.o obj-$(CONFIG_ACPI_BUTTON) += button.o -obj-$(CONFIG_ACPI_EC) += ec.o obj-$(CONFIG_ACPI_FAN) += fan.o obj-$(CONFIG_ACPI_DOCK) += dock.o obj-$(CONFIG_ACPI_BAY) += bay.o @@ -55,8 +56,7 @@ obj-$(CONFIG_ACPI_SYSTEM) += system.o event.o obj-$(CONFIG_ACPI_DEBUG) += debug.o obj-$(CONFIG_ACPI_NUMA) += numa.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o -obj-$(CONFIG_ACPI_IBM) += ibm_acpi.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o obj-y += cm_sbs.o -obj-$(CONFIG_ACPI_SBS) += i2c_ec.o sbs.o +obj-$(CONFIG_ACPI_SBS) += sbs.o diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index c26172671fd8..e65628a03085 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -44,11 +44,6 @@ MODULE_AUTHOR("Naveen B S <naveen.b.s@intel.com>"); MODULE_DESCRIPTION("Hotplug Mem Driver"); MODULE_LICENSE("GPL"); -/* ACPI _STA method values */ -#define ACPI_MEMORY_STA_PRESENT (0x00000001UL) -#define ACPI_MEMORY_STA_ENABLED (0x00000002UL) -#define ACPI_MEMORY_STA_FUNCTIONAL (0x00000008UL) - /* Memory Device States */ #define MEMORY_INVALID_STATE 0 #define MEMORY_POWER_ON_STATE 1 @@ -204,9 +199,9 @@ static int acpi_memory_check_device(struct acpi_memory_device *mem_device) * Check for device status. Device should be * present/enabled/functioning. */ - if (!((current_status & ACPI_MEMORY_STA_PRESENT) - && (current_status & ACPI_MEMORY_STA_ENABLED) - && (current_status & ACPI_MEMORY_STA_FUNCTIONAL))) + if (!((current_status & ACPI_STA_DEVICE_PRESENT) + && (current_status & ACPI_STA_DEVICE_ENABLED) + && (current_status & ACPI_STA_DEVICE_FUNCTIONING))) return -ENODEV; return 0; @@ -286,7 +281,7 @@ static int acpi_memory_powerdown_device(struct acpi_memory_device *mem_device) return -ENODEV; /* Check for device status. Device should be disabled */ - if (current_status & ACPI_MEMORY_STA_ENABLED) + if (current_status & ACPI_STA_DEVICE_ENABLED) return -EINVAL; return 0; diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index dd49ea0d0ed3..e5084ececb6f 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -103,7 +103,9 @@ int acpi_bus_get_status(struct acpi_device *device) else if (device->parent) device->status = device->parent->status; else - STRUCT_TO_INT(device->status) = 0x0F; + STRUCT_TO_INT(device->status) = + ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED | + ACPI_STA_DEVICE_UI | ACPI_STA_DEVICE_FUNCTIONING; if (device->status.functional && !device->status.present) { printk(KERN_WARNING PREFIX "Device [%s] status [%08x]: " diff --git a/drivers/acpi/container.c b/drivers/acpi/container.c index 0930d9413dfa..0dd3bf7c0ed1 100644 --- a/drivers/acpi/container.c +++ b/drivers/acpi/container.c @@ -49,8 +49,6 @@ MODULE_AUTHOR("Anil S Keshavamurthy"); MODULE_DESCRIPTION("ACPI container driver"); MODULE_LICENSE("GPL"); -#define ACPI_STA_PRESENT (0x00000001) - static int acpi_container_add(struct acpi_device *device); static int acpi_container_remove(struct acpi_device *device, int type); @@ -75,13 +73,13 @@ static int is_device_present(acpi_handle handle) status = acpi_get_handle(handle, "_STA", &temp); if (ACPI_FAILURE(status)) - return 1; /* _STA not found, assmue device present */ + return 1; /* _STA not found, assume device present */ status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); if (ACPI_FAILURE(status)) return 0; /* Firmware error */ - return ((sta & ACPI_STA_PRESENT) == ACPI_STA_PRESENT); + return ((sta & ACPI_STA_DEVICE_PRESENT) == ACPI_STA_DEVICE_PRESENT); } /*******************************************************************/ diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 54a697f9aa18..4546bf873aea 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -29,6 +29,7 @@ #include <linux/notifier.h> #include <linux/platform_device.h> #include <linux/jiffies.h> +#include <linux/stddef.h> #include <acpi/acpi_bus.h> #include <acpi/acpi_drivers.h> @@ -667,6 +668,23 @@ static ssize_t write_undock(struct device *dev, struct device_attribute *attr, } DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); +/* + * show_dock_uid - read method for "uid" file in sysfs + */ +static ssize_t show_dock_uid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long lbuf; + acpi_status status = acpi_evaluate_integer(dock_station->handle, "_UID", NULL, &lbuf); + if(ACPI_FAILURE(status)) { + return 0; + } + return snprintf(buf, PAGE_SIZE, "%lx\n", lbuf); +} +DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); + + + /** * dock_add - add a new dock station * @handle: the dock station handle @@ -715,6 +733,13 @@ static int dock_add(acpi_handle handle) kfree(dock_station); return ret; } + ret = device_create_file(&dock_device.dev, &dev_attr_uid); + if (ret) { + printk("Error %d adding sysfs file\n", ret); + platform_device_unregister(&dock_device); + kfree(dock_station); + return ret; + } /* Find dependent devices */ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index a802962ff2b4..e08cf98f504f 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -1,6 +1,8 @@ /* - * acpi_ec.c - ACPI Embedded Controller Driver ($Revision: 38 $) + * ec.c - ACPI Embedded Controller Driver (v2.0) * + * Copyright (C) 2006, 2007 Alexey Starikovskiy <alexey.y.starikovskiy@intel.com> + * Copyright (C) 2006 Denis Sadykov <denis.m.sadykov@intel.com> * Copyright (C) 2004 Luming Yu <luming.yu@intel.com> * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> @@ -91,9 +93,9 @@ static struct acpi_driver acpi_ec_driver = { }; /* If we find an EC via the ECDT, we need to keep a ptr to its context */ +/* External interfaces use first EC only, so remember */ static struct acpi_ec { acpi_handle handle; - unsigned long uid; unsigned long gpe; unsigned long command_addr; unsigned long data_addr; @@ -101,12 +103,8 @@ static struct acpi_ec { struct mutex lock; atomic_t query_pending; atomic_t event_count; - atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */ wait_queue_head_t wait; -} *ec_ecdt; - -/* External interfaces use first EC only, so remember */ -static struct acpi_device *first_ec; +} *boot_ec, *first_ec; /* -------------------------------------------------------------------------- Transaction Management @@ -173,56 +171,6 @@ static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, unsigned count) return -ETIME; } -#ifdef ACPI_FUTURE_USAGE -/* - * Note: samsung nv5000 doesn't work with ec burst mode. - * http://bugzilla.kernel.org/show_bug.cgi?id=4980 - */ -int acpi_ec_enter_burst_mode(struct acpi_ec *ec) -{ - u8 tmp = 0; - u8 status = 0; - - status = acpi_ec_read_status(ec); - if (status != -EINVAL && !(status & ACPI_EC_FLAG_BURST)) { - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); - if (status) - goto end; - acpi_ec_write_cmd(ec, ACPI_EC_BURST_ENABLE); - status = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1); - tmp = acpi_ec_read_data(ec); - if (tmp != 0x90) { /* Burst ACK byte */ - return -EINVAL; - } - } - - atomic_set(&ec->leaving_burst, 0); - return 0; - end: - ACPI_EXCEPTION((AE_INFO, status, "EC wait, burst mode")); - return -1; -} - -int acpi_ec_leave_burst_mode(struct acpi_ec *ec) -{ - u8 status = 0; - - status = acpi_ec_read_status(ec); - if (status != -EINVAL && (status & ACPI_EC_FLAG_BURST)) { - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); - if (status) - goto end; - acpi_ec_write_cmd(ec, ACPI_EC_BURST_DISABLE); - acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); - } - atomic_set(&ec->leaving_burst, 1); - return 0; - end: - ACPI_EXCEPTION((AE_INFO, status, "EC leave burst mode")); - return -1; -} -#endif /* ACPI_FUTURE_USAGE */ - static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, const u8 * wdata, unsigned wdata_len, u8 * rdata, unsigned rdata_len) @@ -312,6 +260,21 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, return status; } +/* + * Note: samsung nv5000 doesn't work with ec burst mode. + * http://bugzilla.kernel.org/show_bug.cgi?id=4980 + */ +int acpi_ec_burst_enable(struct acpi_ec *ec) +{ + u8 d; + return acpi_ec_transaction(ec, ACPI_EC_BURST_ENABLE, NULL, 0, &d, 1); +} + +int acpi_ec_burst_disable(struct acpi_ec *ec) +{ + return acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, NULL, 0, NULL, 0); +} + static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data) { int result; @@ -333,18 +296,33 @@ static int acpi_ec_write(struct acpi_ec *ec, u8 address, u8 data) /* * Externally callable EC access functions. For now, assume 1 EC only */ +int ec_burst_enable(void) +{ + if (!first_ec) + return -ENODEV; + return acpi_ec_burst_enable(first_ec); +} + +EXPORT_SYMBOL(ec_burst_enable); + +int ec_burst_disable(void) +{ + if (!first_ec) + return -ENODEV; + return acpi_ec_burst_disable(first_ec); +} + +EXPORT_SYMBOL(ec_burst_disable); + int ec_read(u8 addr, u8 * val) { - struct acpi_ec *ec; int err; u8 temp_data; if (!first_ec) return -ENODEV; - ec = acpi_driver_data(first_ec); - - err = acpi_ec_read(ec, addr, &temp_data); + err = acpi_ec_read(first_ec, addr, &temp_data); if (!err) { *val = temp_data; @@ -357,15 +335,12 @@ EXPORT_SYMBOL(ec_read); int ec_write(u8 addr, u8 val) { - struct acpi_ec *ec; int err; if (!first_ec) return -ENODEV; - ec = acpi_driver_data(first_ec); - - err = acpi_ec_write(ec, addr, val); + err = acpi_ec_write(first_ec, addr, val); return err; } @@ -376,14 +351,10 @@ int ec_transaction(u8 command, const u8 * wdata, unsigned wdata_len, u8 * rdata, unsigned rdata_len) { - struct acpi_ec *ec; - if (!first_ec) return -ENODEV; - ec = acpi_driver_data(first_ec); - - return acpi_ec_transaction(ec, command, wdata, + return acpi_ec_transaction(first_ec, command, wdata, wdata_len, rdata, rdata_len); } @@ -420,7 +391,7 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 * data) static void acpi_ec_gpe_query(void *ec_cxt) { - struct acpi_ec *ec = (struct acpi_ec *)ec_cxt; + struct acpi_ec *ec = ec_cxt; u8 value = 0; char object_name[8]; @@ -438,8 +409,9 @@ static u32 acpi_ec_gpe_handler(void *data) { acpi_status status = AE_OK; u8 value; - struct acpi_ec *ec = (struct acpi_ec *)data; + struct acpi_ec *ec = data; atomic_inc(&ec->event_count); + if (acpi_ec_mode == EC_INTR) { wake_up(&ec->wait); } @@ -482,7 +454,7 @@ acpi_ec_space_handler(u32 function, void *handler_context, void *region_context) { int result = 0; - struct acpi_ec *ec = NULL; + struct acpi_ec *ec = handler_context; u64 temp = *value; acpi_integer f_v = 0; int i = 0; @@ -494,8 +466,6 @@ acpi_ec_space_handler(u32 function, return AE_BAD_PARAMETER; } - ec = (struct acpi_ec *)handler_context; - next_byte: switch (function) { case ACPI_READ: @@ -551,18 +521,16 @@ static struct proc_dir_entry *acpi_ec_dir; static int acpi_ec_read_info(struct seq_file *seq, void *offset) { - struct acpi_ec *ec = (struct acpi_ec *)seq->private; + struct acpi_ec *ec = seq->private; if (!ec) goto end; - seq_printf(seq, "gpe: 0x%02x\n", (u32) ec->gpe); - seq_printf(seq, "ports: 0x%02x, 0x%02x\n", - (u32) ec->command_addr, (u32) ec->data_addr); - seq_printf(seq, "use global lock: %s\n", + seq_printf(seq, "gpe:\t\t\t0x%02x\n", (u32) ec->gpe); + seq_printf(seq, "ports:\t\t\t0x%02x, 0x%02x\n", + (unsigned)ec->command_addr, (unsigned)ec->data_addr); + seq_printf(seq, "use global lock:\t%s\n", ec->global_lock ? "yes" : "no"); - acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); - end: return 0; } @@ -619,154 +587,122 @@ static int acpi_ec_remove_fs(struct acpi_device *device) /* -------------------------------------------------------------------------- Driver Interface -------------------------------------------------------------------------- */ +static acpi_status +ec_parse_io_ports(struct acpi_resource *resource, void *context); + +static acpi_status +ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval); + +static struct acpi_ec *make_acpi_ec(void) +{ + struct acpi_ec *ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); + if (!ec) + return NULL; + + atomic_set(&ec->query_pending, 1); + atomic_set(&ec->event_count, 1); + mutex_init(&ec->lock); + init_waitqueue_head(&ec->wait); + + return ec; +} static int acpi_ec_add(struct acpi_device *device) { - int result = 0; acpi_status status = AE_OK; struct acpi_ec *ec = NULL; if (!device) return -EINVAL; - ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); - if (!ec) - return -ENOMEM; - - ec->handle = device->handle; - ec->uid = -1; - mutex_init(&ec->lock); - atomic_set(&ec->query_pending, 0); - atomic_set(&ec->event_count, 1); - if (acpi_ec_mode == EC_INTR) { - atomic_set(&ec->leaving_burst, 1); - init_waitqueue_head(&ec->wait); - } strcpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_EC_CLASS); - acpi_driver_data(device) = ec; - - /* Use the global lock for all EC transactions? */ - acpi_evaluate_integer(ec->handle, "_GLK", NULL, &ec->global_lock); - - /* XXX we don't test uids, because on some boxes ecdt uid = 0, see: - http://bugzilla.kernel.org/show_bug.cgi?id=6111 */ - if (ec_ecdt) { - acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, - ACPI_ADR_SPACE_EC, - &acpi_ec_space_handler); - acpi_remove_gpe_handler(NULL, ec_ecdt->gpe, - &acpi_ec_gpe_handler); + ec = make_acpi_ec(); + if (!ec) + return -ENOMEM; - kfree(ec_ecdt); + status = ec_parse_device(device->handle, 0, ec, NULL); + if (status != AE_CTRL_TERMINATE) { + kfree(ec); + return -EINVAL; } - /* Get GPE bit assignment (EC events). */ - /* TODO: Add support for _GPE returning a package */ - status = acpi_evaluate_integer(ec->handle, "_GPE", NULL, &ec->gpe); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Obtaining GPE bit assignment")); - result = -ENODEV; - goto end; - } + /* Check if we found the boot EC */ + if (boot_ec) { + if (boot_ec->gpe == ec->gpe) { + /* We might have incorrect info for GL at boot time */ + mutex_lock(&boot_ec->lock); + boot_ec->global_lock = ec->global_lock; + mutex_unlock(&boot_ec->lock); + kfree(ec); + ec = boot_ec; + } + } else + first_ec = ec; + ec->handle = device->handle; + acpi_driver_data(device) = ec; - result = acpi_ec_add_fs(device); - if (result) - goto end; + acpi_ec_add_fs(device); ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s [%s] (gpe %d) interrupt mode.", acpi_device_name(device), acpi_device_bid(device), (u32) ec->gpe)); - if (!first_ec) - first_ec = device; - - end: - if (result) - kfree(ec); - - return result; + return 0; } static int acpi_ec_remove(struct acpi_device *device, int type) { - struct acpi_ec *ec = NULL; + struct acpi_ec *ec; if (!device) return -EINVAL; ec = acpi_driver_data(device); - acpi_ec_remove_fs(device); + acpi_driver_data(device) = NULL; + if (ec == first_ec) + first_ec = NULL; - kfree(ec); - + /* Don't touch boot EC */ + if (boot_ec != ec) + kfree(ec); return 0; } static acpi_status -acpi_ec_io_ports(struct acpi_resource *resource, void *context) +ec_parse_io_ports(struct acpi_resource *resource, void *context) { - struct acpi_ec *ec = (struct acpi_ec *)context; + struct acpi_ec *ec = context; - if (resource->type != ACPI_RESOURCE_TYPE_IO) { + if (resource->type != ACPI_RESOURCE_TYPE_IO) return AE_OK; - } /* * The first address region returned is the data port, and * the second address region returned is the status/command * port. */ - if (ec->data_addr == 0) { + if (ec->data_addr == 0) ec->data_addr = resource->data.io.minimum; - } else if (ec->command_addr == 0) { + else if (ec->command_addr == 0) ec->command_addr = resource->data.io.minimum; - } else { + else return AE_CTRL_TERMINATE; - } return AE_OK; } -static int acpi_ec_start(struct acpi_device *device) +static int ec_install_handlers(struct acpi_ec *ec) { - acpi_status status = AE_OK; - struct acpi_ec *ec = NULL; - - if (!device) - return -EINVAL; - - ec = acpi_driver_data(device); - - if (!ec) - return -EINVAL; - - /* - * Get I/O port addresses. Convert to GAS format. - */ - status = acpi_walk_resources(ec->handle, METHOD_NAME__CRS, - acpi_ec_io_ports, ec); - if (ACPI_FAILURE(status) || ec->command_addr == 0) { - ACPI_EXCEPTION((AE_INFO, status, - "Error getting I/O port addresses")); - return -ENODEV; - } - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02lx, ports=0x%2lx,0x%2lx", - ec->gpe, ec->command_addr, ec->data_addr)); - - /* - * Install GPE handler - */ + acpi_status status; status = acpi_install_gpe_handler(NULL, ec->gpe, ACPI_GPE_EDGE_TRIGGERED, &acpi_ec_gpe_handler, ec); - if (ACPI_FAILURE(status)) { + if (ACPI_FAILURE(status)) return -ENODEV; - } + acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME); acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); @@ -779,18 +715,49 @@ static int acpi_ec_start(struct acpi_device *device) return -ENODEV; } - return AE_OK; + /* EC is fully operational, allow queries */ + atomic_set(&ec->query_pending, 0); + + return 0; +} + +static int acpi_ec_start(struct acpi_device *device) +{ + struct acpi_ec *ec; + + if (!device) + return -EINVAL; + + ec = acpi_driver_data(device); + + if (!ec) + return -EINVAL; + + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02lx, ports=0x%2lx,0x%2lx", + ec->gpe, ec->command_addr, ec->data_addr)); + + /* Boot EC is already working */ + if (ec == boot_ec) + return 0; + + return ec_install_handlers(ec); } static int acpi_ec_stop(struct acpi_device *device, int type) { - acpi_status status = AE_OK; - struct acpi_ec *ec = NULL; + acpi_status status; + struct acpi_ec *ec; if (!device) return -EINVAL; ec = acpi_driver_data(device); + if (!ec) + return -EINVAL; + + /* Don't touch boot EC */ + if (ec == boot_ec) + return 0; status = acpi_remove_address_space_handler(ec->handle, ACPI_ADR_SPACE_EC, @@ -805,164 +772,67 @@ static int acpi_ec_stop(struct acpi_device *device, int type) return 0; } -static acpi_status __init -acpi_fake_ecdt_callback(acpi_handle handle, - u32 Level, void *context, void **retval) +static acpi_status +ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval) { acpi_status status; - mutex_init(&ec_ecdt->lock); - atomic_set(&ec_ecdt->event_count, 1); - if (acpi_ec_mode == EC_INTR) { - init_waitqueue_head(&ec_ecdt->wait); - } + struct acpi_ec *ec = context; status = acpi_walk_resources(handle, METHOD_NAME__CRS, - acpi_ec_io_ports, ec_ecdt); + ec_parse_io_ports, ec); if (ACPI_FAILURE(status)) return status; - ec_ecdt->uid = -1; - acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->uid); - - status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec_ecdt->gpe); + /* Get GPE bit assignment (EC events). */ + /* TODO: Add support for _GPE returning a package */ + status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec->gpe); if (ACPI_FAILURE(status)) return status; - ec_ecdt->global_lock = TRUE; - ec_ecdt->handle = handle; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02lx, ports=0x%2lx, 0x%2lx", - ec_ecdt->gpe, ec_ecdt->command_addr, - ec_ecdt->data_addr)); - return AE_CTRL_TERMINATE; -} - -/* - * Some BIOS (such as some from Gateway laptops) access EC region very early - * such as in BAT0._INI or EC._INI before an EC device is found and - * do not provide an ECDT. According to ACPI spec, ECDT isn't mandatorily - * required, but if EC regison is accessed early, it is required. - * The routine tries to workaround the BIOS bug by pre-scan EC device - * It assumes that _CRS, _HID, _GPE, _UID methods of EC don't touch any - * op region (since _REG isn't invoked yet). The assumption is true for - * all systems found. - */ -static int __init acpi_ec_fake_ecdt(void) -{ - acpi_status status; - int ret = 0; + /* Use the global lock for all EC transactions? */ + acpi_evaluate_integer(handle, "_GLK", NULL, &ec->global_lock); - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Try to make an fake ECDT")); + ec->handle = handle; - ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); - if (!ec_ecdt) { - ret = -ENOMEM; - goto error; - } + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02lx, ports=0x%2lx, 0x%2lx", + ec->gpe, ec->command_addr, ec->data_addr)); - status = acpi_get_devices(ACPI_EC_HID, - acpi_fake_ecdt_callback, NULL, NULL); - if (ACPI_FAILURE(status)) { - kfree(ec_ecdt); - ec_ecdt = NULL; - ret = -ENODEV; - ACPI_EXCEPTION((AE_INFO, status, "Can't make an fake ECDT")); - goto error; - } - return 0; - error: - return ret; + return AE_CTRL_TERMINATE; } -static int __init acpi_ec_get_real_ecdt(void) +int __init acpi_ec_ecdt_probe(void) { + int ret; acpi_status status; struct acpi_table_ecdt *ecdt_ptr; - status = acpi_get_table(ACPI_SIG_ECDT, 1, - (struct acpi_table_header **)&ecdt_ptr); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found ECDT")); - + boot_ec = make_acpi_ec(); + if (!boot_ec) + return -ENOMEM; /* - * Generate a temporary ec context to use until the namespace is scanned + * Generate a boot ec context */ - ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); - if (!ec_ecdt) - return -ENOMEM; - mutex_init(&ec_ecdt->lock); - atomic_set(&ec_ecdt->event_count, 1); - if (acpi_ec_mode == EC_INTR) { - init_waitqueue_head(&ec_ecdt->wait); - } - ec_ecdt->command_addr = ecdt_ptr->control.address; - ec_ecdt->data_addr = ecdt_ptr->data.address; - ec_ecdt->gpe = ecdt_ptr->gpe; - /* use the GL just to be safe */ - ec_ecdt->global_lock = TRUE; - ec_ecdt->uid = ecdt_ptr->uid; - - status = acpi_get_handle(NULL, ecdt_ptr->id, &ec_ecdt->handle); - if (ACPI_FAILURE(status)) { + status = acpi_get_table(ACPI_SIG_ECDT, 1, + (struct acpi_table_header **)&ecdt_ptr); + if (ACPI_FAILURE(status)) goto error; - } - - return 0; - error: - ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT")); - kfree(ec_ecdt); - ec_ecdt = NULL; - return -ENODEV; -} - -static int __initdata acpi_fake_ecdt_enabled; -int __init acpi_ec_ecdt_probe(void) -{ - acpi_status status; - int ret; + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found ECDT")); - ret = acpi_ec_get_real_ecdt(); - /* Try to make a fake ECDT */ - if (ret && acpi_fake_ecdt_enabled) { - ret = acpi_ec_fake_ecdt(); - } + boot_ec->command_addr = ecdt_ptr->control.address; + boot_ec->data_addr = ecdt_ptr->data.address; + boot_ec->gpe = ecdt_ptr->gpe; + boot_ec->handle = ACPI_ROOT_OBJECT; - if (ret) + ret = ec_install_handlers(boot_ec); + if (!ret) { + first_ec = boot_ec; return 0; - - /* - * Install GPE handler - */ - status = acpi_install_gpe_handler(NULL, ec_ecdt->gpe, - ACPI_GPE_EDGE_TRIGGERED, - &acpi_ec_gpe_handler, ec_ecdt); - if (ACPI_FAILURE(status)) { - goto error; } - acpi_set_gpe_type(NULL, ec_ecdt->gpe, ACPI_GPE_TYPE_RUNTIME); - acpi_enable_gpe(NULL, ec_ecdt->gpe, ACPI_NOT_ISR); - - status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT, - ACPI_ADR_SPACE_EC, - &acpi_ec_space_handler, - &acpi_ec_space_setup, - ec_ecdt); - if (ACPI_FAILURE(status)) { - acpi_remove_gpe_handler(NULL, ec_ecdt->gpe, - &acpi_ec_gpe_handler); - goto error; - } - - return 0; - error: - ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT")); - kfree(ec_ecdt); - ec_ecdt = NULL; + kfree(boot_ec); + boot_ec = NULL; return -ENODEV; } @@ -1003,13 +873,6 @@ static void __exit acpi_ec_exit(void) } #endif /* 0 */ -static int __init acpi_fake_ecdt_setup(char *str) -{ - acpi_fake_ecdt_enabled = 1; - return 1; -} - -__setup("acpi_fake_ecdt", acpi_fake_ecdt_setup); static int __init acpi_ec_set_intr_mode(char *str) { int intr; @@ -1017,12 +880,8 @@ static int __init acpi_ec_set_intr_mode(char *str) if (!get_option(&str, &intr)) return 0; - if (intr) { - acpi_ec_mode = EC_INTR; - } else { - acpi_ec_mode = EC_POLL; - } - acpi_ec_driver.ops.add = acpi_ec_add; + acpi_ec_mode = (intr) ? EC_INTR : EC_POLL; + printk(KERN_NOTICE PREFIX "%s mode.\n", intr ? "interrupt" : "polling"); return 1; diff --git a/drivers/acpi/i2c_ec.c b/drivers/acpi/i2c_ec.c deleted file mode 100644 index acab4a481897..000000000000 --- a/drivers/acpi/i2c_ec.c +++ /dev/null @@ -1,403 +0,0 @@ -/* - * SMBus driver for ACPI Embedded Controller ($Revision: 1.3 $) - * - * Copyright (c) 2002, 2005 Ducrot Bruno - * Copyright (c) 2005 Rich Townsend (tiny hacks & tweaks) - * - * 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 version 2. - */ - -#include <linux/version.h> -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/kernel.h> -#include <linux/stddef.h> -#include <linux/init.h> -#include <linux/i2c.h> -#include <linux/acpi.h> -#include <linux/delay.h> - -#include "i2c_ec.h" - -#define xudelay(t) udelay(t) -#define xmsleep(t) msleep(t) - -#define ACPI_EC_HC_COMPONENT 0x00080000 -#define ACPI_EC_HC_CLASS "ec_hc_smbus" -#define ACPI_EC_HC_HID "ACPI0001" -#define ACPI_EC_HC_DEVICE_NAME "EC HC smbus" - -#define _COMPONENT ACPI_EC_HC_COMPONENT - -ACPI_MODULE_NAME("i2c_ec"); - -static int acpi_ec_hc_add(struct acpi_device *device); -static int acpi_ec_hc_remove(struct acpi_device *device, int type); - -static struct acpi_driver acpi_ec_hc_driver = { - .name = "i2c_ec", - .class = ACPI_EC_HC_CLASS, - .ids = ACPI_EC_HC_HID, - .ops = { - .add = acpi_ec_hc_add, - .remove = acpi_ec_hc_remove, - }, -}; - -/* Various bit mask for EC_SC (R) */ -#define OBF 0x01 -#define IBF 0x02 -#define CMD 0x08 -#define BURST 0x10 -#define SCI_EVT 0x20 -#define SMI_EVT 0x40 - -/* Commands for EC_SC (W) */ -#define RD_EC 0x80 -#define WR_EC 0x81 -#define BE_EC 0x82 -#define BD_EC 0x83 -#define QR_EC 0x84 - -/* - * ACPI 2.0 chapter 13 SMBus 2.0 EC register model - */ - -#define ACPI_EC_SMB_PRTCL 0x00 /* protocol, PEC */ -#define ACPI_EC_SMB_STS 0x01 /* status */ -#define ACPI_EC_SMB_ADDR 0x02 /* address */ -#define ACPI_EC_SMB_CMD 0x03 /* command */ -#define ACPI_EC_SMB_DATA 0x04 /* 32 data registers */ -#define ACPI_EC_SMB_BCNT 0x24 /* number of data bytes */ -#define ACPI_EC_SMB_ALRM_A 0x25 /* alarm address */ -#define ACPI_EC_SMB_ALRM_D 0x26 /* 2 bytes alarm data */ - -#define ACPI_EC_SMB_STS_DONE 0x80 -#define ACPI_EC_SMB_STS_ALRM 0x40 -#define ACPI_EC_SMB_STS_RES 0x20 -#define ACPI_EC_SMB_STS_STATUS 0x1f - -#define ACPI_EC_SMB_STATUS_OK 0x00 -#define ACPI_EC_SMB_STATUS_FAIL 0x07 -#define ACPI_EC_SMB_STATUS_DNAK 0x10 -#define ACPI_EC_SMB_STATUS_DERR 0x11 -#define ACPI_EC_SMB_STATUS_CMD_DENY 0x12 -#define ACPI_EC_SMB_STATUS_UNKNOWN 0x13 -#define ACPI_EC_SMB_STATUS_ACC_DENY 0x17 -#define ACPI_EC_SMB_STATUS_TIMEOUT 0x18 -#define ACPI_EC_SMB_STATUS_NOTSUP 0x19 -#define ACPI_EC_SMB_STATUS_BUSY 0x1A -#define ACPI_EC_SMB_STATUS_PEC 0x1F - -#define ACPI_EC_SMB_PRTCL_WRITE 0x00 -#define ACPI_EC_SMB_PRTCL_READ 0x01 -#define ACPI_EC_SMB_PRTCL_QUICK 0x02 -#define ACPI_EC_SMB_PRTCL_BYTE 0x04 -#define ACPI_EC_SMB_PRTCL_BYTE_DATA 0x06 -#define ACPI_EC_SMB_PRTCL_WORD_DATA 0x08 -#define ACPI_EC_SMB_PRTCL_BLOCK_DATA 0x0a -#define ACPI_EC_SMB_PRTCL_PROC_CALL 0x0c -#define ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL 0x0d -#define ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA 0x4a -#define ACPI_EC_SMB_PRTCL_PEC 0x80 - -/* Length of pre/post transaction sleep (msec) */ -#define ACPI_EC_SMB_TRANSACTION_SLEEP 1 -#define ACPI_EC_SMB_ACCESS_SLEEP1 1 -#define ACPI_EC_SMB_ACCESS_SLEEP2 10 - -static int acpi_ec_smb_read(struct acpi_ec_smbus *smbus, u8 address, u8 * data) -{ - u8 val; - int err; - - err = ec_read(smbus->base + address, &val); - if (!err) { - *data = val; - } - xmsleep(ACPI_EC_SMB_TRANSACTION_SLEEP); - return (err); -} - -static int acpi_ec_smb_write(struct acpi_ec_smbus *smbus, u8 address, u8 data) -{ - int err; - - err = ec_write(smbus->base + address, data); - return (err); -} - -static int -acpi_ec_smb_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, - char read_write, u8 command, int size, - union i2c_smbus_data *data) -{ - struct acpi_ec_smbus *smbus = adap->algo_data; - unsigned char protocol, len = 0, pec, temp[2] = { 0, 0 }; - int i; - - if (read_write == I2C_SMBUS_READ) { - protocol = ACPI_EC_SMB_PRTCL_READ; - } else { - protocol = ACPI_EC_SMB_PRTCL_WRITE; - } - pec = (flags & I2C_CLIENT_PEC) ? ACPI_EC_SMB_PRTCL_PEC : 0; - - switch (size) { - - case I2C_SMBUS_QUICK: - protocol |= ACPI_EC_SMB_PRTCL_QUICK; - read_write = I2C_SMBUS_WRITE; - break; - - case I2C_SMBUS_BYTE: - if (read_write == I2C_SMBUS_WRITE) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->byte); - } - protocol |= ACPI_EC_SMB_PRTCL_BYTE; - break; - - case I2C_SMBUS_BYTE_DATA: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - if (read_write == I2C_SMBUS_WRITE) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->byte); - } - protocol |= ACPI_EC_SMB_PRTCL_BYTE_DATA; - break; - - case I2C_SMBUS_WORD_DATA: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - if (read_write == I2C_SMBUS_WRITE) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->word); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + 1, - data->word >> 8); - } - protocol |= ACPI_EC_SMB_PRTCL_WORD_DATA | pec; - break; - - case I2C_SMBUS_BLOCK_DATA: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - if (read_write == I2C_SMBUS_WRITE) { - len = min_t(u8, data->block[0], 32); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len); - for (i = 0; i < len; i++) - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i, - data->block[i + 1]); - } - protocol |= ACPI_EC_SMB_PRTCL_BLOCK_DATA | pec; - break; - - case I2C_SMBUS_I2C_BLOCK_DATA: - len = min_t(u8, data->block[0], 32); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len); - if (read_write == I2C_SMBUS_WRITE) { - for (i = 0; i < len; i++) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i, - data->block[i + 1]); - } - } - protocol |= ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA; - break; - - case I2C_SMBUS_PROC_CALL: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->word); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + 1, data->word >> 8); - protocol = ACPI_EC_SMB_PRTCL_PROC_CALL | pec; - read_write = I2C_SMBUS_READ; - break; - - case I2C_SMBUS_BLOCK_PROC_CALL: - protocol |= pec; - len = min_t(u8, data->block[0], 31); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len); - for (i = 0; i < len; i++) - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i, - data->block[i + 1]); - protocol = ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL | pec; - read_write = I2C_SMBUS_READ; - break; - - default: - ACPI_DEBUG_PRINT((ACPI_DB_WARN, "EC SMBus adapter: " - "Unsupported transaction %d\n", size)); - return (-1); - } - - acpi_ec_smb_write(smbus, ACPI_EC_SMB_ADDR, addr << 1); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_PRTCL, protocol); - - acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0); - - if (~temp[0] & ACPI_EC_SMB_STS_DONE) { - xudelay(500); - acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0); - } - if (~temp[0] & ACPI_EC_SMB_STS_DONE) { - xmsleep(ACPI_EC_SMB_ACCESS_SLEEP2); - acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0); - } - if ((~temp[0] & ACPI_EC_SMB_STS_DONE) - || (temp[0] & ACPI_EC_SMB_STS_STATUS)) { - return (-1); - } - - if (read_write == I2C_SMBUS_WRITE) { - return (0); - } - - switch (size) { - - case I2C_SMBUS_BYTE: - case I2C_SMBUS_BYTE_DATA: - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA, &data->byte); - break; - - case I2C_SMBUS_WORD_DATA: - case I2C_SMBUS_PROC_CALL: - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA, temp + 0); - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA + 1, temp + 1); - data->word = (temp[1] << 8) | temp[0]; - break; - - case I2C_SMBUS_BLOCK_DATA: - case I2C_SMBUS_BLOCK_PROC_CALL: - len = 0; - acpi_ec_smb_read(smbus, ACPI_EC_SMB_BCNT, &len); - len = min_t(u8, len, 32); - case I2C_SMBUS_I2C_BLOCK_DATA: - for (i = 0; i < len; i++) - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA + i, - data->block + i + 1); - data->block[0] = len; - break; - } - - return (0); -} - -static u32 acpi_ec_smb_func(struct i2c_adapter *adapter) -{ - - return (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BLOCK_DATA | - I2C_FUNC_SMBUS_PROC_CALL | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_HWPEC_CALC); -} - -static const struct i2c_algorithm acpi_ec_smbus_algorithm = { - .smbus_xfer = acpi_ec_smb_access, - .functionality = acpi_ec_smb_func, -}; - -static int acpi_ec_hc_add(struct acpi_device *device) -{ - int status; - unsigned long val; - struct acpi_ec_hc *ec_hc; - struct acpi_ec_smbus *smbus; - - if (!device) { - return -EINVAL; - } - - ec_hc = kzalloc(sizeof(struct acpi_ec_hc), GFP_KERNEL); - if (!ec_hc) { - return -ENOMEM; - } - - smbus = kzalloc(sizeof(struct acpi_ec_smbus), GFP_KERNEL); - if (!smbus) { - kfree(ec_hc); - return -ENOMEM; - } - - ec_hc->handle = device->handle; - strcpy(acpi_device_name(device), ACPI_EC_HC_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_EC_HC_CLASS); - acpi_driver_data(device) = ec_hc; - - status = acpi_evaluate_integer(ec_hc->handle, "_EC", NULL, &val); - if (ACPI_FAILURE(status)) { - ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error obtaining _EC\n")); - kfree(ec_hc); - kfree(smbus); - return -EIO; - } - - smbus->ec = acpi_driver_data(device->parent); - smbus->base = (val & 0xff00ull) >> 8; - smbus->alert = val & 0xffull; - - smbus->adapter.owner = THIS_MODULE; - smbus->adapter.algo = &acpi_ec_smbus_algorithm; - smbus->adapter.algo_data = smbus; - smbus->adapter.dev.parent = &device->dev; - - if (i2c_add_adapter(&smbus->adapter)) { - ACPI_DEBUG_PRINT((ACPI_DB_WARN, - "EC SMBus adapter: Failed to register adapter\n")); - kfree(smbus); - kfree(ec_hc); - return -EIO; - } - - ec_hc->smbus = smbus; - - printk(KERN_INFO PREFIX "%s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); - - return AE_OK; -} - -static int acpi_ec_hc_remove(struct acpi_device *device, int type) -{ - struct acpi_ec_hc *ec_hc; - - if (!device) { - return -EINVAL; - } - ec_hc = acpi_driver_data(device); - - i2c_del_adapter(&ec_hc->smbus->adapter); - kfree(ec_hc->smbus); - kfree(ec_hc); - - return AE_OK; -} - -static int __init acpi_ec_hc_init(void) -{ - int result; - - result = acpi_bus_register_driver(&acpi_ec_hc_driver); - if (result < 0) { - return -ENODEV; - } - return 0; -} - -static void __exit acpi_ec_hc_exit(void) -{ - acpi_bus_unregister_driver(&acpi_ec_hc_driver); -} - -struct acpi_ec_hc *acpi_get_ec_hc(struct acpi_device *device) -{ - return acpi_driver_data(device->parent); -} - -EXPORT_SYMBOL(acpi_get_ec_hc); - -module_init(acpi_ec_hc_init); -module_exit(acpi_ec_hc_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ducrot Bruno"); -MODULE_DESCRIPTION("ACPI EC SMBus driver"); diff --git a/drivers/acpi/i2c_ec.h b/drivers/acpi/i2c_ec.h deleted file mode 100644 index 7c53fb732d61..000000000000 --- a/drivers/acpi/i2c_ec.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SMBus driver for ACPI Embedded Controller ($Revision: 1.2 $) - * - * Copyright (c) 2002, 2005 Ducrot Bruno - * - * 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 version 2. - */ - -struct acpi_ec_smbus { - struct i2c_adapter adapter; - union acpi_ec *ec; - int base; - int alert; -}; - -struct acpi_ec_hc { - acpi_handle handle; - struct acpi_ec_smbus *smbus; -}; - -struct acpi_ec_hc *acpi_get_ec_hc(struct acpi_device *device); diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c deleted file mode 100644 index dc1096608f43..000000000000 --- a/drivers/acpi/ibm_acpi.c +++ /dev/null @@ -1,2798 +0,0 @@ -/* - * ibm_acpi.c - IBM ThinkPad ACPI Extras - * - * - * Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net> - * Copyright (C) 2006 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define IBM_VERSION "0.13" - -/* - * Changelog: - * - * 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 <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/string.h> - -#include <linux/proc_fs.h> -#include <linux/backlight.h> -#include <linux/fb.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> - -#define IBM_NAME "ibm" -#define IBM_DESC "IBM ThinkPad ACPI Extras" -#define IBM_FILE "ibm_acpi" -#define IBM_URL "http://ibm-acpi.sf.net/" - -MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); -MODULE_DESCRIPTION(IBM_DESC); -MODULE_VERSION(IBM_VERSION); -MODULE_LICENSE("GPL"); - -#define IBM_DIR IBM_NAME - -#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 - -#define __unused __attribute__ ((unused)) - -static int experimental; -module_param(experimental, int, 0); - -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(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 */ - -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 */ -#ifdef CONFIG_ACPI_IBM_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 */ -#endif -#ifdef CONFIG_ACPI_IBM_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 */ -#endif /* CONFIG_ACPI_IBM_BAY */ - -/* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ - -IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ - "^HKEY", /* R30, R31 */ - "HKEY", /* all others */ - ); /* 570 */ - -IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ -IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ - -IBM_HANDLE(led, ec, "SLED", /* 570 */ - "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - "LED", /* all others */ - ); /* R30, R31 */ - -IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ -IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ -IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ -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 */ - -#define IBM_HKEY_HID "IBM0068" -#define IBM_PCI_HID "PNP0A03" - -enum thermal_access_mode { - IBMACPI_THERMAL_NONE = 0, /* No thermal support */ - IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ -}; - -#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -struct ibm_thermal_sensors_struct { - s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; -}; - -/* - * FAN ACCESS MODES - * - * IBMACPI_FAN_RD_ACPI_GFAN: - * ACPI GFAN method: returns fan level - * - * see IBMACPI_FAN_WR_ACPI_SFAN - * EC 0x2f not available if GFAN exists - * - * IBMACPI_FAN_WR_ACPI_SFAN: - * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) - * - * EC 0x2f might be available *for reading*, but never for writing. - * - * IBMACPI_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 disengage mode (takes precedence over bit 7); - * not available on all thinkpads. May disable - * the tachometer, and speeds up fan to 100% duty-cycle, - * which speeds it up 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 - * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this - * register is not invalidated in ThinkPads that disable tachometer - * readings. Thus, the tachometer readings go stale. - * - * For firmware bugs, refer to: - * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues - * - * IBMACPI_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. - * - * IBMACPI_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. - */ - -enum fan_status_access_mode { - IBMACPI_FAN_NONE = 0, /* No fan status or control */ - IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ -}; - -enum fan_control_access_mode { - IBMACPI_FAN_WR_NONE = 0, /* No fan control */ - IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ -}; - -enum fan_control_commands { - IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, - * and also watchdog cmd */ -}; - -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 */ - - IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer - * disengaged */ - IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan - * control */ -}; - -static char *ibm_thinkpad_ec_found = NULL; - -struct ibm_struct { - char *name; - char param[32]; - - char *hid; - struct acpi_driver *driver; - - int (*init) (void); - int (*read) (char *); - int (*write) (char *); - void (*exit) (void); - - void (*notify) (struct ibm_struct *, u32); - acpi_handle *handle; - int type; - struct acpi_device *device; - - int driver_registered; - int proc_created; - int init_called; - int notify_installed; - - int experimental; -}; - -static struct proc_dir_entry *proc_dir = NULL; - -static struct backlight_device *ibm_backlight_device = NULL; - -#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))) - -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 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; -} - -static int ibm_acpi_driver_init(void) -{ - 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 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 int hotkey_supported; -static int hotkey_mask_supported; -static int hotkey_orig_status; -static int hotkey_orig_mask; - -static int hotkey_get(int *status, int *mask) -{ - if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) - return 0; - - if (hotkey_mask_supported) - if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) - return 0; - - return 1; -} - -static int hotkey_set(int status, int mask) -{ - int i; - - if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) - return 0; - - if (hotkey_mask_supported) - for (i = 0; i < 32; i++) { - int bit = ((1 << i) & mask) != 0; - if (!acpi_evalf(hkey_handle, - NULL, "MHKM", "vdd", i + 1, bit)) - return 0; - } - - return 1; -} - -static int hotkey_init(void) -{ - /* hotkey not supported on 570 */ - hotkey_supported = hkey_handle != NULL; - - if (hotkey_supported) { - /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, - A30, R30, R31, T20-22, X20-21, X22-24 */ - hotkey_mask_supported = - acpi_evalf(hkey_handle, NULL, "DHKN", "qv"); - - if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask)) - return -ENODEV; - } - - return 0; -} - -static int hotkey_read(char *p) -{ - int status, mask; - int len = 0; - - if (!hotkey_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; - } - - if (!hotkey_get(&status, &mask)) - return -EIO; - - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); - if (hotkey_mask_supported) { - 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 status, mask; - char *cmd; - int do_cmd = 0; - - if (!hotkey_supported) - return -ENODEV; - - if (!hotkey_get(&status, &mask)) - return -EIO; - - 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 - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !hotkey_set(status, mask)) - return -EIO; - - return 0; -} - -static void hotkey_exit(void) -{ - if (hotkey_supported) - hotkey_set(hotkey_orig_status, hotkey_orig_mask); -} - -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->device, event, hkey); - else { - printk(IBM_ERR "unknown hotkey event %d\n", event); - acpi_bus_generate_event(ibm->device, event, 0); - } -} - -static int bluetooth_supported; - -static int bluetooth_init(void) -{ - /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, - G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ - bluetooth_supported = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); - - return 0; -} - -static int bluetooth_status(void) -{ - int status; - - if (!bluetooth_supported || - !acpi_evalf(hkey_handle, &status, "GBDC", "d")) - status = 0; - - return status; -} - -static int bluetooth_read(char *p) -{ - int len = 0; - int status = bluetooth_status(); - - if (!bluetooth_supported) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!(status & 1)) - len += sprintf(p + len, "status:\t\tnot installed\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; -} - -static int bluetooth_write(char *buf) -{ - int status = bluetooth_status(); - char *cmd; - int do_cmd = 0; - - if (!bluetooth_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status |= 2; - } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) - return -EIO; - - return 0; -} - -static int wan_supported; - -static int wan_init(void) -{ - wan_supported = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); - - return 0; -} - -static int wan_status(void) -{ - int status; - - if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) - status = 0; - - return status; -} - -static int wan_read(char *p) -{ - int len = 0; - int status = wan_status(); - - if (!wan_supported) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!(status & 1)) - len += sprintf(p + len, "status:\t\tnot installed\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; -} - -static int wan_write(char *buf) -{ - int status = wan_status(); - char *cmd; - int do_cmd = 0; - - if (!wan_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status |= 2; - } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) - return -EIO; - - return 0; -} - -enum video_access_mode { - IBMACPI_VIDEO_NONE = 0, - IBMACPI_VIDEO_570, /* 570 */ - IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - IBMACPI_VIDEO_NEW, /* all others */ -}; - -static enum video_access_mode video_supported; -static int video_orig_autosw; - -static int video_init(void) -{ - int ivga; - - 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 = IBMACPI_VIDEO_NONE; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) - /* 570 */ - video_supported = IBMACPI_VIDEO_570; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) - /* 600e/x, 770e, 770x */ - video_supported = IBMACPI_VIDEO_770; - else - /* all others */ - video_supported = IBMACPI_VIDEO_NEW; - - return 0; -} - -static int video_status(void) -{ - int status = 0; - int i; - - if (video_supported == IBMACPI_VIDEO_570) { - if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) - status = i & 3; - } else if (video_supported == IBMACPI_VIDEO_770) { - if (acpi_evalf(NULL, &i, "\\VCDL", "d")) - status |= 0x01 * i; - if (acpi_evalf(NULL, &i, "\\VCDC", "d")) - status |= 0x02 * i; - } else if (video_supported == IBMACPI_VIDEO_NEW) { - acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); - if (acpi_evalf(NULL, &i, "\\VCDC", "d")) - status |= 0x02 * i; - - acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0); - if (acpi_evalf(NULL, &i, "\\VCDL", "d")) - status |= 0x01 * i; - if (acpi_evalf(NULL, &i, "\\VCDD", "d")) - status |= 0x08 * i; - } - - return status; -} - -static int video_autosw(void) -{ - int autosw = 0; - - if (video_supported == IBMACPI_VIDEO_570) - acpi_evalf(vid_handle, &autosw, "SWIT", "d"); - else if (video_supported == IBMACPI_VIDEO_770 || - video_supported == IBMACPI_VIDEO_NEW) - acpi_evalf(vid_handle, &autosw, "^VDEE", "d"); - - return autosw & 1; -} - -static int video_read(char *p) -{ - int status = video_status(); - int autosw = video_autosw(); - int len = 0; - - if (!video_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; - } - - 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 == IBMACPI_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 == IBMACPI_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_switch(void) -{ - int autosw = video_autosw(); - int ret; - - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; - ret = video_supported == IBMACPI_VIDEO_570 ? - acpi_evalf(ec_handle, NULL, "_Q16", "v") : - acpi_evalf(vid_handle, NULL, "VSWT", "v"); - acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); - - return ret; -} - -static int video_expand(void) -{ - if (video_supported == IBMACPI_VIDEO_570) - return acpi_evalf(ec_handle, NULL, "_Q17", "v"); - else if (video_supported == IBMACPI_VIDEO_770) - return acpi_evalf(vid_handle, NULL, "VEXP", "v"); - else - return acpi_evalf(NULL, NULL, "\\VEXP", "v"); -} - -static int video_switch2(int status) -{ - int ret; - - if (video_supported == IBMACPI_VIDEO_570) { - ret = acpi_evalf(NULL, NULL, - "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); - } else if (video_supported == IBMACPI_VIDEO_770) { - int autosw = video_autosw(); - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; - - ret = acpi_evalf(vid_handle, NULL, - "ASWT", "vdd", status * 0x100, 0); - - acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); - } else { - ret = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && - acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); - } - - return ret; -} - -static int video_write(char *buf) -{ - char *cmd; - int enable, disable, status; - - if (!video_supported) - return -ENODEV; - - enable = disable = 0; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "lcd_enable") == 0) { - enable |= 0x01; - } else if (strlencmp(cmd, "lcd_disable") == 0) { - disable |= 0x01; - } else if (strlencmp(cmd, "crt_enable") == 0) { - enable |= 0x02; - } else if (strlencmp(cmd, "crt_disable") == 0) { - disable |= 0x02; - } else if (video_supported == IBMACPI_VIDEO_NEW && - strlencmp(cmd, "dvi_enable") == 0) { - enable |= 0x08; - } else if (video_supported == IBMACPI_VIDEO_NEW && - strlencmp(cmd, "dvi_disable") == 0) { - disable |= 0x08; - } else if (strlencmp(cmd, "auto_enable") == 0) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; - } else if (strlencmp(cmd, "auto_disable") == 0) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0)) - return -EIO; - } else if (strlencmp(cmd, "video_switch") == 0) { - if (!video_switch()) - return -EIO; - } else if (strlencmp(cmd, "expand_toggle") == 0) { - if (!video_expand()) - return -EIO; - } else - return -EINVAL; - } - - if (enable || disable) { - status = (video_status() & 0x0f & ~disable) | enable; - if (!video_switch2(status)) - return -EIO; - } - - return 0; -} - -static void video_exit(void) -{ - acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); -} - -static int light_supported; -static int light_status_supported; - -static int light_init(void) -{ - /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ - light_supported = (cmos_handle || lght_handle) && !ledb_handle; - - if (light_supported) - /* light status not supported on - 570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */ - light_status_supported = acpi_evalf(ec_handle, NULL, - "KBLT", "qv"); - - return 0; -} - -static int light_read(char *p) -{ - int len = 0; - int status = 0; - - if (!light_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - } else if (!light_status_supported) { - 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 (!light_supported) - 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; -} - -#if defined(CONFIG_ACPI_IBM_DOCK) || defined(CONFIG_ACPI_IBM_BAY) -static int _sta(acpi_handle handle) -{ - int status; - - if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) - status = 0; - - return status; -} -#endif - -#ifdef CONFIG_ACPI_IBM_DOCK -#define dock_docked() (_sta(dock_handle) & 1) - -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; -} - -static void dock_notify(struct ibm_struct *ibm, u32 event) -{ - int docked = dock_docked(); - int pci = ibm->hid && strstr(ibm->hid, IBM_PCI_HID); - - if (event == 1 && !pci) /* 570 */ - acpi_bus_generate_event(ibm->device, event, 1); /* button */ - else if (event == 1 && pci) /* 570 */ - acpi_bus_generate_event(ibm->device, event, 3); /* dock */ - else if (event == 3 && docked) - acpi_bus_generate_event(ibm->device, event, 1); /* button */ - else if (event == 3 && !docked) - acpi_bus_generate_event(ibm->device, event, 2); /* undock */ - else if (event == 0 && docked) - acpi_bus_generate_event(ibm->device, event, 3); /* dock */ - else { - printk(IBM_ERR "unknown dock event %d, status %d\n", - event, _sta(dock_handle)); - acpi_bus_generate_event(ibm->device, event, 0); /* unknown */ - } -} -#endif - -#ifdef CONFIG_ACPI_IBM_BAY -static int bay_status_supported; -static int bay_status2_supported; -static int bay_eject_supported; -static int bay_eject2_supported; - -static int bay_init(void) -{ - bay_status_supported = bay_handle && - acpi_evalf(bay_handle, NULL, "_STA", "qv"); - bay_status2_supported = bay2_handle && - acpi_evalf(bay2_handle, NULL, "_STA", "qv"); - - bay_eject_supported = bay_handle && bay_ej_handle && - (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental); - bay_eject2_supported = bay2_handle && bay2_ej_handle && - (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental); - - return 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", bay_status_supported ? - (occupied ? "occupied" : "unoccupied") : - "not supported"); - if (bay_status2_supported) - len += sprintf(p + len, "status2:\t%s\n", occupied2 ? - "occupied" : "unoccupied"); - - eject = bay_eject_supported && occupied; - eject2 = bay_eject2_supported && 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 (!bay_eject_supported && !bay_eject2_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (bay_eject_supported && strlencmp(cmd, "eject") == 0) { - if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1)) - return -EIO; - } else if (bay_eject2_supported && - strlencmp(cmd, "eject2") == 0) { - if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1)) - return -EIO; - } else - return -EINVAL; - } - - return 0; -} - -static void bay_notify(struct ibm_struct *ibm, u32 event) -{ - acpi_bus_generate_event(ibm->device, event, 0); -} -#endif /* CONFIG_ACPI_IBM_BAY */ - -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_eval(int cmos_cmd) -{ - if (cmos_handle) - return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd); - else - return 1; -} - -static int cmos_write(char *buf) -{ - char *cmd; - int cmos_cmd; - - if (!cmos_handle) - return -EINVAL; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%u", &cmos_cmd) == 1 && - cmos_cmd >= 0 && cmos_cmd <= 21) { - /* cmos_cmd set */ - } else - return -EINVAL; - - if (!cmos_eval(cmos_cmd)) - return -EIO; - } - - return 0; -} - -enum led_access_mode { - IBMACPI_LED_NONE = 0, - IBMACPI_LED_570, /* 570 */ - IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - IBMACPI_LED_NEW, /* all others */ -}; -static enum led_access_mode led_supported; - -static int led_init(void) -{ - if (!led_handle) - /* led not supported on R30, R31 */ - led_supported = IBMACPI_LED_NONE; - else if (strlencmp(led_path, "SLED") == 0) - /* 570 */ - led_supported = IBMACPI_LED_570; - else if (strlencmp(led_path, "SYSL") == 0) - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - led_supported = IBMACPI_LED_OLD; - else - /* all others */ - led_supported = IBMACPI_LED_NEW; - - return 0; -} - -#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 == IBMACPI_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 }; - -#define EC_HLCL 0x0c -#define EC_HLBL 0x0d -#define EC_HLMS 0x0e - -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 == IBMACPI_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 == IBMACPI_LED_OLD) { - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ - led = 1 << led; - ret = ec_write(EC_HLMS, led); - if (ret >= 0) - ret = - ec_write(EC_HLBL, led * led_exp_hlbl[ind]); - if (ret >= 0) - ret = - ec_write(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 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 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 enum thermal_access_mode thermal_read_mode; - -static int thermal_init(void) -{ - u8 t, ta1, ta2; - int i; - int 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 (likely(acpi_ec_read(0x78 + i, &t))) { - ta1 |= t; - } else { - ta1 = 0; - break; - } - if (likely(acpi_ec_read(0xC0 + 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 = IBMACPI_THERMAL_ACPI_TMP07; - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "disabling thermal sensors access\n"); - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - } else { - thermal_read_mode = - (ta2 != 0) ? - IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; - } - } else if (acpi_tmp7) { - if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { - /* 600e/x, 770e, 770x */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; - } else { - /* Standard ACPI TMPx access, max 8 sensors */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; - } - } else { - /* temperatures not supported on 570, G4x, R30, R31, R32 */ - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - - return 0; -} - -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) -{ - int i, t; - s8 tmp; - char tmpi[] = "TMPi"; - - if (!s) - return -EINVAL; - - switch (thermal_read_mode) { -#if IBMACPI_MAX_THERMAL_SENSORS >= 16 - case IBMACPI_THERMAL_TPEC_16: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0xC0 + i, &tmp)) - return -EIO; - s->temp[i + 8] = tmp * 1000; - } - /* fallthrough */ -#endif - case IBMACPI_THERMAL_TPEC_8: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0x78 + i, &tmp)) - return -EIO; - s->temp[i] = tmp * 1000; - } - return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; - - case IBMACPI_THERMAL_ACPI_UPDT: - if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) - return -EIO; - for (i = 0; i < 8; i++) { - tmpi[3] = '0' + i; - if (!acpi_evalf(ec_handle, &t, tmpi, "d")) - return -EIO; - s->temp[i] = (t - 2732) * 100; - } - return 8; - - case IBMACPI_THERMAL_ACPI_TMP07: - for (i = 0; i < 8; i++) { - tmpi[3] = '0' + i; - if (!acpi_evalf(ec_handle, &t, tmpi, "d")) - return -EIO; - s->temp[i] = t * 1000; - } - return 8; - - case IBMACPI_THERMAL_NONE: - default: - return 0; - } -} - -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 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 int brightness_offset = 0x31; - -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_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; -} - -#define BRIGHTNESS_UP 4 -#define BRIGHTNESS_DOWN 5 - -static int brightness_set(int value) -{ - int cmos_cmd, inc, i; - int current_value = brightness_get(NULL); - - value &= 7; - - cmos_cmd = value > current_value ? BRIGHTNESS_UP : BRIGHTNESS_DOWN; - inc = value > current_value ? 1 : -1; - for (i = current_value; i != value; i += inc) { - if (!cmos_eval(cmos_cmd)) - return -EIO; - if (!acpi_ec_write(brightness_offset, i + inc)) - return -EIO; - } - - return 0; -} - -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 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 struct backlight_ops ibm_backlight_data = { - .get_brightness = brightness_get, - .update_status = brightness_update_status, -}; - -static int brightness_init(void) -{ - int b; - - b = brightness_get(NULL); - if (b < 0) - return b; - - ibm_backlight_device = backlight_device_register("ibm", 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); - } - - 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) { - backlight_device_unregister(ibm_backlight_device); - ibm_backlight_device = NULL; - } -} - -static int volume_offset = 0x30; - -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; -} - -#define VOLUME_DOWN 0 -#define VOLUME_UP 1 -#define VOLUME_MUTE 2 - -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 ? VOLUME_UP : VOLUME_DOWN; - inc = new_level > level ? 1 : -1; - - if (mute && (!cmos_eval(cmos_cmd) || - !acpi_ec_write(volume_offset, level))) - return -EIO; - - for (i = level; i != new_level; i += inc) - if (!cmos_eval(cmos_cmd) || - !acpi_ec_write(volume_offset, i + inc)) - return -EIO; - - if (mute && (!cmos_eval(VOLUME_MUTE) || - !acpi_ec_write(volume_offset, - new_level + mute))) - return -EIO; - } - - if (new_mute != mute) { /* level doesn't change */ - cmos_cmd = new_mute ? VOLUME_MUTE : VOLUME_UP; - - if (!cmos_eval(cmos_cmd) || - !acpi_ec_write(volume_offset, level + new_mute)) - return -EIO; - } - } - - return 0; -} - -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 int fan_control_status_known; -static u8 fan_control_initial_status; - -static void fan_watchdog_fire(struct work_struct *ignored); -static int fan_watchdog_maxinterval; -static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire); - -static int fan_init(void) -{ - fan_status_access_mode = IBMACPI_FAN_NONE; - fan_control_access_mode = IBMACPI_FAN_WR_NONE; - fan_control_commands = 0; - fan_control_status_known = 1; - fan_watchdog_maxinterval = 0; - - if (gfan_handle) { - /* 570, 600e/x, 770e, 770x */ - fan_status_access_mode = IBMACPI_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 = IBMACPI_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"); - fan_control_status_known = 0; - } - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "fan status and control unavailable\n"); - return 0; - } - } - - if (sfan_handle) { - /* 570, 770x-JL */ - fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | IBMACPI_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 = - IBMACPI_FAN_WR_ACPI_FANS; - fan_control_commands |= - IBMACPI_FAN_CMD_SPEED | - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } else { - fan_control_access_mode = IBMACPI_FAN_WR_TPEC; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } - } - } - - return 0; -} - -static int fan_get_status(u8 *status) -{ - u8 s; - - /* TODO: - * Add IBMACPI_FAN_RD_ACPI_FANS ? */ - - switch (fan_status_access_mode) { - case IBMACPI_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 IBMACPI_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_speed(unsigned int *speed) -{ - u8 hi, lo; - - switch (fan_status_access_mode) { - case IBMACPI_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_exit(void) -{ - cancel_delayed_work(&fan_watchdog_task); - flush_scheduled_work(); -} - -static void fan_watchdog_reset(void) -{ - static int fan_watchdog_active = 0; - - 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_read(char *p) -{ - int len = 0; - int rc; - u8 status; - unsigned int speed = 0; - - switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_ACPI_GFAN: - /* 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&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 IBMACPI_FAN_RD_TPEC: - /* all except 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) - return rc; - - if (unlikely(!fan_control_status_known)) { - if (status != fan_control_initial_status) - fan_control_status_known = 1; - else - /* Return most likely status. In fact, it - * might be the only possible status */ - status = IBMACPI_FAN_EC_AUTO; - } - - len += sprintf(p + len, "status:\t\t%s\n", - (status != 0) ? "enabled" : "disabled"); - - /* No ThinkPad boots on disengaged mode, we can safely - * assume the tachometer is online if fan control status - * was unknown */ - if ((rc = fan_get_speed(&speed)) < 0) - return rc; - - len += sprintf(p + len, "speed:\t\t%d\n", speed); - - if (status & IBMACPI_FAN_EC_DISENGAGED) - /* Disengaged mode takes precedence */ - len += sprintf(p + len, "level:\t\tdisengaged\n"); - else if (status & IBMACPI_FAN_EC_AUTO) - len += sprintf(p + len, "level:\t\tauto\n"); - else - len += sprintf(p + len, "level:\t\t%d\n", status); - break; - - case IBMACPI_FAN_NONE: - default: - len += sprintf(p + len, "status:\t\tnot supported\n"); - } - - if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { - len += sprintf(p + len, "commands:\tlevel <level>"); - - switch (fan_control_access_mode) { - case IBMACPI_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)\n"); - break; - } - } - - if (fan_control_commands & IBMACPI_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 & IBMACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed <speed>" - " (<speed> is 0-65535)\n"); - - return len; -} - -static int fan_set_level(int level) -{ - switch (fan_control_access_mode) { - case IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((level != IBMACPI_FAN_EC_AUTO) && - (level != IBMACPI_FAN_EC_DISENGAGED) && - ((level < 0) || (level > 7))) - return -EINVAL; - - if (!acpi_ec_write(fan_status_offset, level)) - return -EIO; - else - fan_control_status_known = 1; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_enable(void) -{ - u8 s; - int rc; - - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((rc = fan_get_status(&s)) < 0) - return rc; - - /* Don't go out of emergency fan mode */ - if (s != 7) - s = IBMACPI_FAN_EC_AUTO; - - if (!acpi_ec_write(fan_status_offset, s)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_FAN_WR_ACPI_SFAN: - if ((rc = fan_get_status(&s)) < 0) - return rc; - - s &= 0x07; - - /* Set fan to at least level 4 */ - if (s < 4) - s = 4; - - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) - return -EIO; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_disable(void) -{ - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if (!acpi_ec_write(fan_status_offset, 0x00)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_FAN_WR_ACPI_SFAN: - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) - return -EIO; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_speed(int speed) -{ - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - if (speed >= 0 && speed <= 65535) { - if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", - speed, speed, speed)) - return -EIO; - } else - return -EINVAL; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_write_cmd_level(const char *cmd, int *rc) -{ - int level; - - if (strlencmp(cmd, "level auto") == 0) - level = IBMACPI_FAN_EC_AUTO; - else if (strlencmp(cmd, "level disengaged") == 0) - level = IBMACPI_FAN_EC_DISENGAGED; - else if (sscanf(cmd, "level %d", &level) != 1) - return 0; - - if ((*rc = fan_set_level(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 & IBMACPI_FAN_CMD_LEVEL) && - fan_write_cmd_level(cmd, &rc)) && - !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && - (fan_write_cmd_enable(cmd, &rc) || - fan_write_cmd_disable(cmd, &rc) || - fan_write_cmd_watchdog(cmd, &rc))) && - !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && - fan_write_cmd_speed(cmd, &rc)) - ) - rc = -EINVAL; - else if (!rc) - fan_watchdog_reset(); - } - - return rc; -} - -static void fan_watchdog_fire(struct work_struct *ignored) -{ - printk(IBM_NOTICE "fan watchdog: enabling fan\n"); - if (fan_set_enable()) { - printk(IBM_ERR "fan watchdog: error while enabling fan\n"); - /* reschedule for later */ - fan_watchdog_reset(); - } -} - -static struct ibm_struct ibms[] = { - { - .name = "driver", - .init = ibm_acpi_driver_init, - .read = driver_read, - }, - { - .name = "hotkey", - .hid = IBM_HKEY_HID, - .init = hotkey_init, - .read = hotkey_read, - .write = hotkey_write, - .exit = hotkey_exit, - .notify = hotkey_notify, - .handle = &hkey_handle, - .type = ACPI_DEVICE_NOTIFY, - }, - { - .name = "bluetooth", - .init = bluetooth_init, - .read = bluetooth_read, - .write = bluetooth_write, - }, - { - .name = "wan", - .init = wan_init, - .read = wan_read, - .write = wan_write, - .experimental = 1, - }, - { - .name = "video", - .init = video_init, - .read = video_read, - .write = video_write, - .exit = video_exit, - }, - { - .name = "light", - .init = light_init, - .read = light_read, - .write = light_write, - }, -#ifdef CONFIG_ACPI_IBM_DOCK - { - .name = "dock", - .read = dock_read, - .write = dock_write, - .notify = dock_notify, - .handle = &dock_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, - { - .name = "dock", - .hid = IBM_PCI_HID, - .notify = dock_notify, - .handle = &pci_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif -#ifdef CONFIG_ACPI_IBM_BAY - { - .name = "bay", - .init = bay_init, - .read = bay_read, - .write = bay_write, - .notify = bay_notify, - .handle = &bay_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif /* CONFIG_ACPI_IBM_BAY */ - { - .name = "cmos", - .read = cmos_read, - .write = cmos_write, - }, - { - .name = "led", - .init = led_init, - .read = led_read, - .write = led_write, - }, - { - .name = "beep", - .read = beep_read, - .write = beep_write, - }, - { - .name = "thermal", - .init = thermal_init, - .read = thermal_read, - }, - { - .name = "ecdump", - .read = ecdump_read, - .write = ecdump_write, - .experimental = 1, - }, - { - .name = "brightness", - .read = brightness_read, - .write = brightness_write, - .init = brightness_init, - .exit = brightness_exit, - }, - { - .name = "volume", - .read = volume_read, - .write = volume_write, - }, - { - .name = "fan", - .read = fan_read, - .write = fan_write, - .init = fan_init, - .exit = fan_exit, - .experimental = 1, - }, -}; - -static int dispatch_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_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 void dispatch_notify(acpi_handle handle, u32 event, void *data) -{ - struct ibm_struct *ibm = data; - - if (!ibm || !ibm->notify) - return; - - ibm->notify(ibm, event); -} - -static int __init setup_notify(struct ibm_struct *ibm) -{ - acpi_status status; - int ret; - - if (!*ibm->handle) - return 0; - - ret = acpi_bus_get_device(*ibm->handle, &ibm->device); - if (ret < 0) { - printk(IBM_ERR "%s device not present\n", ibm->name); - return -ENODEV; - } - - acpi_driver_data(ibm->device) = ibm; - sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name); - - status = acpi_install_notify_handler(*ibm->handle, ibm->type, - dispatch_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->notify_installed = 1; - return 0; -} - -static int __init ibm_device_add(struct acpi_device *device) -{ - return 0; -} - -static int __init register_driver(struct ibm_struct *ibm) -{ - int ret; - - ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); - if (!ibm->driver) { - printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); - return -1; - } - - sprintf(ibm->driver->name, "%s_%s", IBM_NAME, ibm->name); - ibm->driver->ids = ibm->hid; - ibm->driver->ops.add = &ibm_device_add; - - ret = acpi_bus_register_driver(ibm->driver); - if (ret < 0) { - printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", - ibm->hid, ret); - kfree(ibm->driver); - } - - return ret; -} - -static void ibm_exit(struct ibm_struct *ibm); - -static int __init ibm_init(struct ibm_struct *ibm) -{ - int ret; - struct proc_dir_entry *entry; - - if (ibm->experimental && !experimental) - return 0; - - if (ibm->hid) { - ret = register_driver(ibm); - if (ret < 0) - return ret; - ibm->driver_registered = 1; - } - - if (ibm->init) { - ret = ibm->init(); - if (ret != 0) - return ret; - ibm->init_called = 1; - } - - 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); - return -ENODEV; - } - entry->owner = THIS_MODULE; - entry->data = ibm; - entry->read_proc = &dispatch_read; - if (ibm->write) - entry->write_proc = &dispatch_write; - ibm->proc_created = 1; - } - - if (ibm->notify) { - ret = setup_notify(ibm); - if (ret == -ENODEV) { - printk(IBM_NOTICE "disabling subdriver %s\n", - ibm->name); - ibm_exit(ibm); - return 0; - } - if (ret < 0) - return ret; - } - - return 0; -} - -static void ibm_exit(struct ibm_struct *ibm) -{ - if (ibm->notify_installed) - acpi_remove_notify_handler(*ibm->handle, ibm->type, - dispatch_notify); - - if (ibm->proc_created) - remove_proc_entry(ibm->name, proc_dir); - - if (ibm->init_called && ibm->exit) - ibm->exit(); - - if (ibm->driver_registered) { - acpi_bus_unregister_driver(ibm->driver); - kfree(ibm->driver); - } -} - -static void __init ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, - char **paths, int num_paths, char **path) -{ - int i; - acpi_status status; - - for (i = 0; i < num_paths; i++) { - status = acpi_get_handle(parent, paths[i], handle); - if (ACPI_SUCCESS(status)) { - *path = paths[i]; - return; - } - } - - *handle = NULL; -} - -#define IBM_HANDLE_INIT(object) \ - ibm_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) - -static int __init set_ibm_param(const char *val, struct kernel_param *kp) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(ibms); i++) - if (strcmp(ibms[i].name, kp->name) == 0 && ibms[i].write) { - if (strlen(val) > sizeof(ibms[i].param) - 2) - return -ENOSPC; - strcpy(ibms[i].param, val); - strcat(ibms[i].param, ","); - return 0; - } - - return -EINVAL; -} - -#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_ACPI_IBM_DOCK -IBM_PARAM(dock); -#endif -#ifdef CONFIG_ACPI_IBM_BAY -IBM_PARAM(bay); -#endif /* CONFIG_ACPI_IBM_BAY */ -IBM_PARAM(cmos); -IBM_PARAM(led); -IBM_PARAM(beep); -IBM_PARAM(ecdump); -IBM_PARAM(brightness); -IBM_PARAM(volume); -IBM_PARAM(fan); - -static void acpi_ibm_exit(void) -{ - int i; - - for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) - ibm_exit(&ibms[i]); - - if (proc_dir) - remove_proc_entry(IBM_DIR, acpi_root_dir); - - if (ibm_thinkpad_ec_found) - kfree(ibm_thinkpad_ec_found); -} - -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 acpi_ibm_init(void) -{ - int ret, i; - - if (acpi_disabled) - return -ENODEV; - - /* ec is required because many other handles are relative to it */ - IBM_HANDLE_INIT(ec); - if (!ec_handle) { - printk(IBM_ERR "ec object not found\n"); - return -ENODEV; - } - - /* Models with newer firmware report the EC in DMI */ - ibm_thinkpad_ec_found = check_dmi_for_ec(); - - /* these handles are not required */ - IBM_HANDLE_INIT(vid); - IBM_HANDLE_INIT(vid2); - IBM_HANDLE_INIT(ledb); - IBM_HANDLE_INIT(led); - IBM_HANDLE_INIT(hkey); - IBM_HANDLE_INIT(lght); - IBM_HANDLE_INIT(cmos); -#ifdef CONFIG_ACPI_IBM_DOCK - IBM_HANDLE_INIT(dock); -#endif - IBM_HANDLE_INIT(pci); -#ifdef CONFIG_ACPI_IBM_BAY - IBM_HANDLE_INIT(bay); - if (bay_handle) - IBM_HANDLE_INIT(bay_ej); - IBM_HANDLE_INIT(bay2); - if (bay2_handle) - IBM_HANDLE_INIT(bay2_ej); -#endif /* CONFIG_ACPI_IBM_BAY */ - IBM_HANDLE_INIT(beep); - IBM_HANDLE_INIT(ecrd); - IBM_HANDLE_INIT(ecwr); - IBM_HANDLE_INIT(fans); - IBM_HANDLE_INIT(gfan); - IBM_HANDLE_INIT(sfan); - - proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); - if (!proc_dir) { - printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); - acpi_ibm_exit(); - return -ENODEV; - } - proc_dir->owner = THIS_MODULE; - - for (i = 0; i < ARRAY_SIZE(ibms); i++) { - ret = ibm_init(&ibms[i]); - if (ret >= 0 && *ibms[i].param) - ret = ibms[i].write(ibms[i].param); - if (ret < 0) { - acpi_ibm_exit(); - return ret; - } - } - - return 0; -} - -module_init(acpi_ibm_init); -module_exit(acpi_ibm_exit); diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 99d1516d1e70..f7de02a6f497 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -70,8 +70,6 @@ #define ACPI_PROCESSOR_LIMIT_USER 0 #define ACPI_PROCESSOR_LIMIT_THERMAL 1 -#define ACPI_STA_PRESENT 0x00000001 - #define _COMPONENT ACPI_PROCESSOR_COMPONENT ACPI_MODULE_NAME("processor_core"); @@ -779,7 +777,7 @@ static int is_processor_present(acpi_handle handle) status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); - if (ACPI_FAILURE(status) || !(sta & ACPI_STA_PRESENT)) { + if (ACPI_FAILURE(status) || !(sta & ACPI_STA_DEVICE_PRESENT)) { ACPI_EXCEPTION((AE_INFO, status, "Processor Device is not present")); return 0; } diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index cdf78943af4d..ae0654cd11ea 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -51,14 +51,6 @@ #include <asm/apic.h> #endif -/* - * Include the apic definitions for x86 to have the APIC timer related defines - * available also for UP (on SMP it gets magically included via linux/smp.h). - */ -#ifdef CONFIG_X86 -#include <asm/apic.h> -#endif - #include <asm/io.h> #include <asm/uaccess.h> diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 59640d9a0acc..c1bae106833c 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -30,30 +30,10 @@ #include <linux/seq_file.h> #include <asm/uaccess.h> #include <linux/acpi.h> -#include <linux/i2c.h> +#include <linux/timer.h> +#include <linux/jiffies.h> #include <linux/delay.h> -#include "i2c_ec.h" - -#define DEF_CAPACITY_UNIT 3 -#define MAH_CAPACITY_UNIT 1 -#define MWH_CAPACITY_UNIT 2 -#define CAPACITY_UNIT DEF_CAPACITY_UNIT - -#define REQUEST_UPDATE_MODE 1 -#define QUEUE_UPDATE_MODE 2 - -#define DATA_TYPE_COMMON 0 -#define DATA_TYPE_INFO 1 -#define DATA_TYPE_STATE 2 -#define DATA_TYPE_ALARM 3 -#define DATA_TYPE_AC_STATE 4 - -extern struct proc_dir_entry *acpi_lock_ac_dir(void); -extern struct proc_dir_entry *acpi_lock_battery_dir(void); -extern void acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir); -extern void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); - #define ACPI_SBS_COMPONENT 0x00080000 #define ACPI_SBS_CLASS "sbs" #define ACPI_AC_CLASS "ac_adapter" @@ -74,39 +54,75 @@ extern void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); #define _COMPONENT ACPI_SBS_COMPONENT -#define MAX_SBS_BAT 4 -#define MAX_SMBUS_ERR 1 - ACPI_MODULE_NAME("sbs"); MODULE_AUTHOR("Rich Townsend"); MODULE_DESCRIPTION("Smart Battery System ACPI interface driver"); MODULE_LICENSE("GPL"); -static struct semaphore sbs_sem; +#define xmsleep(t) msleep(t) + +#define ACPI_EC_SMB_PRTCL 0x00 /* protocol, PEC */ + +#define ACPI_EC_SMB_STS 0x01 /* status */ +#define ACPI_EC_SMB_ADDR 0x02 /* address */ +#define ACPI_EC_SMB_CMD 0x03 /* command */ +#define ACPI_EC_SMB_DATA 0x04 /* 32 data registers */ +#define ACPI_EC_SMB_BCNT 0x24 /* number of data bytes */ -#define UPDATE_MODE QUEUE_UPDATE_MODE -/* REQUEST_UPDATE_MODE QUEUE_UPDATE_MODE */ -#define UPDATE_INFO_MODE 0 -#define UPDATE_TIME 60 -#define UPDATE_TIME2 0 +#define ACPI_EC_SMB_STS_DONE 0x80 +#define ACPI_EC_SMB_STS_STATUS 0x1f -static int capacity_mode = CAPACITY_UNIT; -static int update_mode = UPDATE_MODE; -static int update_info_mode = UPDATE_INFO_MODE; -static int update_time = UPDATE_TIME; -static int update_time2 = UPDATE_TIME2; +#define ACPI_EC_SMB_PRTCL_WRITE 0x00 +#define ACPI_EC_SMB_PRTCL_READ 0x01 +#define ACPI_EC_SMB_PRTCL_WORD_DATA 0x08 +#define ACPI_EC_SMB_PRTCL_BLOCK_DATA 0x0a -module_param(capacity_mode, int, 0); -module_param(update_mode, int, 0); -module_param(update_info_mode, int, 0); -module_param(update_time, int, 0); -module_param(update_time2, int, 0); +#define ACPI_EC_SMB_TRANSACTION_SLEEP 1 +#define ACPI_EC_SMB_ACCESS_SLEEP1 1 +#define ACPI_EC_SMB_ACCESS_SLEEP2 10 + +#define DEF_CAPACITY_UNIT 3 +#define MAH_CAPACITY_UNIT 1 +#define MWH_CAPACITY_UNIT 2 +#define CAPACITY_UNIT DEF_CAPACITY_UNIT + +#define REQUEST_UPDATE_MODE 1 +#define QUEUE_UPDATE_MODE 2 + +#define DATA_TYPE_COMMON 0 +#define DATA_TYPE_INFO 1 +#define DATA_TYPE_STATE 2 +#define DATA_TYPE_ALARM 3 +#define DATA_TYPE_AC_STATE 4 + +extern struct proc_dir_entry *acpi_lock_ac_dir(void); +extern struct proc_dir_entry *acpi_lock_battery_dir(void); +extern void acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir); +extern void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); + +#define MAX_SBS_BAT 4 +#define ACPI_SBS_BLOCK_MAX 32 + +#define ACPI_SBS_SMBUS_READ 1 +#define ACPI_SBS_SMBUS_WRITE 2 + +#define ACPI_SBS_WORD_DATA 1 +#define ACPI_SBS_BLOCK_DATA 2 + +#define UPDATE_DELAY 10 + +/* 0 - every time, > 0 - by update_time */ +static unsigned int update_time = 120; + +static unsigned int capacity_mode = CAPACITY_UNIT; + +module_param(update_time, uint, 0644); +module_param(capacity_mode, uint, 0444); static int acpi_sbs_add(struct acpi_device *device); static int acpi_sbs_remove(struct acpi_device *device, int type); -static void acpi_battery_smbus_err_handler(struct acpi_ec_smbus *smbus); -static void acpi_sbs_update_queue(void *data); +static int acpi_sbs_resume(struct acpi_device *device); static struct acpi_driver acpi_sbs_driver = { .name = "sbs", @@ -115,9 +131,14 @@ static struct acpi_driver acpi_sbs_driver = { .ops = { .add = acpi_sbs_add, .remove = acpi_sbs_remove, + .resume = acpi_sbs_resume, }, }; +struct acpi_ac { + int ac_present; +}; + struct acpi_battery_info { int capacity_mode; s16 full_charge_capacity; @@ -126,18 +147,16 @@ struct acpi_battery_info { int vscale; int ipscale; s16 serial_number; - char manufacturer_name[I2C_SMBUS_BLOCK_MAX + 3]; - char device_name[I2C_SMBUS_BLOCK_MAX + 3]; - char device_chemistry[I2C_SMBUS_BLOCK_MAX + 3]; + char manufacturer_name[ACPI_SBS_BLOCK_MAX + 3]; + char device_name[ACPI_SBS_BLOCK_MAX + 3]; + char device_chemistry[ACPI_SBS_BLOCK_MAX + 3]; }; struct acpi_battery_state { s16 voltage; s16 amperage; s16 remaining_capacity; - s16 average_time_to_empty; - s16 average_time_to_full; - s16 battery_status; + s16 battery_state; }; struct acpi_battery_alarm { @@ -146,9 +165,9 @@ struct acpi_battery_alarm { struct acpi_battery { int alive; - int battery_present; int id; int init_state; + int battery_present; struct acpi_sbs *sbs; struct acpi_battery_info info; struct acpi_battery_state state; @@ -158,186 +177,251 @@ struct acpi_battery { struct acpi_sbs { acpi_handle handle; + int base; struct acpi_device *device; struct acpi_ec_smbus *smbus; + struct mutex mutex; int sbsm_present; int sbsm_batteries_supported; - int ac_present; struct proc_dir_entry *ac_entry; + struct acpi_ac ac; struct acpi_battery battery[MAX_SBS_BAT]; - int update_info_mode; int zombie; - int update_time; - int update_time2; struct timer_list update_timer; + int run_cnt; + int update_proc_flg; }; -static void acpi_update_delay(struct acpi_sbs *sbs); -static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type); +static int acpi_sbs_update_run(struct acpi_sbs *sbs, int id, int data_type); +static void acpi_sbs_update_time(void *data); + +union sbs_rw_data { + u16 word; + u8 block[ACPI_SBS_BLOCK_MAX + 2]; +}; + +static int acpi_ec_sbs_access(struct acpi_sbs *sbs, u16 addr, + char read_write, u8 command, int size, + union sbs_rw_data *data); /* -------------------------------------------------------------------------- SMBus Communication -------------------------------------------------------------------------- */ -static void acpi_battery_smbus_err_handler(struct acpi_ec_smbus *smbus) +static int acpi_ec_sbs_read(struct acpi_sbs *sbs, u8 address, u8 * data) { - union i2c_smbus_data data; - int result = 0; - char *err_str; - int err_number; + u8 val; + int err; - data.word = 0; + err = ec_read(sbs->base + address, &val); + if (!err) { + *data = val; + } + xmsleep(ACPI_EC_SMB_TRANSACTION_SLEEP); + return (err); +} - result = smbus->adapter.algo-> - smbus_xfer(&smbus->adapter, - ACPI_SB_SMBUS_ADDR, - 0, I2C_SMBUS_READ, 0x16, I2C_SMBUS_BLOCK_DATA, &data); +static int acpi_ec_sbs_write(struct acpi_sbs *sbs, u8 address, u8 data) +{ + int err; - err_number = (data.word & 0x000f); + err = ec_write(sbs->base + address, data); + return (err); +} - switch (data.word & 0x000f) { - case 0x0000: - err_str = "unexpected bus error"; - break; - case 0x0001: - err_str = "busy"; - break; - case 0x0002: - err_str = "reserved command"; - break; - case 0x0003: - err_str = "unsupported command"; - break; - case 0x0004: - err_str = "access denied"; +static int +acpi_ec_sbs_access(struct acpi_sbs *sbs, u16 addr, + char read_write, u8 command, int size, + union sbs_rw_data *data) +{ + unsigned char protocol, len = 0, temp[2] = { 0, 0 }; + int i; + + if (read_write == ACPI_SBS_SMBUS_READ) { + protocol = ACPI_EC_SMB_PRTCL_READ; + } else { + protocol = ACPI_EC_SMB_PRTCL_WRITE; + } + + switch (size) { + + case ACPI_SBS_WORD_DATA: + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_CMD, command); + if (read_write == ACPI_SBS_SMBUS_WRITE) { + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_DATA, data->word); + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_DATA + 1, + data->word >> 8); + } + protocol |= ACPI_EC_SMB_PRTCL_WORD_DATA; break; - case 0x0005: - err_str = "overflow/underflow"; + case ACPI_SBS_BLOCK_DATA: + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_CMD, command); + if (read_write == ACPI_SBS_SMBUS_WRITE) { + len = min_t(u8, data->block[0], 32); + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_BCNT, len); + for (i = 0; i < len; i++) + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_DATA + i, + data->block[i + 1]); + } + protocol |= ACPI_EC_SMB_PRTCL_BLOCK_DATA; break; - case 0x0006: - err_str = "bad size"; + default: + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "unsupported transaction %d", size)); + return (-1); + } + + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_ADDR, addr << 1); + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_PRTCL, protocol); + + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_STS, temp); + + if (~temp[0] & ACPI_EC_SMB_STS_DONE) { + xmsleep(ACPI_EC_SMB_ACCESS_SLEEP1); + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_STS, temp); + } + if (~temp[0] & ACPI_EC_SMB_STS_DONE) { + xmsleep(ACPI_EC_SMB_ACCESS_SLEEP2); + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_STS, temp); + } + if ((~temp[0] & ACPI_EC_SMB_STS_DONE) + || (temp[0] & ACPI_EC_SMB_STS_STATUS)) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "transaction %d error", size)); + return (-1); + } + + if (read_write == ACPI_SBS_SMBUS_WRITE) { + return (0); + } + + switch (size) { + + case ACPI_SBS_WORD_DATA: + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_DATA, temp); + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_DATA + 1, temp + 1); + data->word = (temp[1] << 8) | temp[0]; break; - case 0x0007: - err_str = "unknown error"; + + case ACPI_SBS_BLOCK_DATA: + len = 0; + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_BCNT, &len); + len = min_t(u8, len, 32); + for (i = 0; i < len; i++) + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_DATA + i, + data->block + i + 1); + data->block[0] = len; break; default: - err_str = "unrecognized error"; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "unsupported transaction %d", size)); + return (-1); } - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "%s: ret %i, err %i\n", err_str, result, err_number)); + + return (0); } static int -acpi_sbs_smbus_read_word(struct acpi_ec_smbus *smbus, int addr, int func, - u16 * word, - void (*err_handler) (struct acpi_ec_smbus * smbus)) +acpi_sbs_read_word(struct acpi_sbs *sbs, int addr, int func, u16 * word) { - union i2c_smbus_data data; + union sbs_rw_data data; int result = 0; - int i; - if (err_handler == NULL) { - err_handler = acpi_battery_smbus_err_handler; - } - - for (i = 0; i < MAX_SMBUS_ERR; i++) { - result = - smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0, - I2C_SMBUS_READ, func, - I2C_SMBUS_WORD_DATA, &data); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "try %i: smbus->adapter.algo->smbus_xfer() failed\n", - i)); - if (err_handler) { - err_handler(smbus); - } - } else { - *word = data.word; - break; - } + result = acpi_ec_sbs_access(sbs, addr, + ACPI_SBS_SMBUS_READ, func, + ACPI_SBS_WORD_DATA, &data); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ec_sbs_access() failed")); + } else { + *word = data.word; } return result; } static int -acpi_sbs_smbus_read_str(struct acpi_ec_smbus *smbus, int addr, int func, - char *str, - void (*err_handler) (struct acpi_ec_smbus * smbus)) +acpi_sbs_read_str(struct acpi_sbs *sbs, int addr, int func, char *str) { - union i2c_smbus_data data; + union sbs_rw_data data; int result = 0; - int i; - - if (err_handler == NULL) { - err_handler = acpi_battery_smbus_err_handler; - } - for (i = 0; i < MAX_SMBUS_ERR; i++) { - result = - smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0, - I2C_SMBUS_READ, func, - I2C_SMBUS_BLOCK_DATA, - &data); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "try %i: smbus->adapter.algo->smbus_xfer() failed\n", - i)); - if (err_handler) { - err_handler(smbus); - } - } else { - strncpy(str, (const char *)data.block + 1, - data.block[0]); - str[data.block[0]] = 0; - break; - } + result = acpi_ec_sbs_access(sbs, addr, + ACPI_SBS_SMBUS_READ, func, + ACPI_SBS_BLOCK_DATA, &data); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ec_sbs_access() failed")); + } else { + strncpy(str, (const char *)data.block + 1, data.block[0]); + str[data.block[0]] = 0; } return result; } static int -acpi_sbs_smbus_write_word(struct acpi_ec_smbus *smbus, int addr, int func, - int word, - void (*err_handler) (struct acpi_ec_smbus * smbus)) +acpi_sbs_write_word(struct acpi_sbs *sbs, int addr, int func, int word) { - union i2c_smbus_data data; + union sbs_rw_data data; int result = 0; - int i; - - if (err_handler == NULL) { - err_handler = acpi_battery_smbus_err_handler; - } data.word = word; - for (i = 0; i < MAX_SMBUS_ERR; i++) { - result = - smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0, - I2C_SMBUS_WRITE, func, - I2C_SMBUS_WORD_DATA, &data); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "try %i: smbus->adapter.algo" - "->smbus_xfer() failed\n", i)); - if (err_handler) { - err_handler(smbus); - } - } else { - break; - } + result = acpi_ec_sbs_access(sbs, addr, + ACPI_SBS_SMBUS_WRITE, func, + ACPI_SBS_WORD_DATA, &data); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ec_sbs_access() failed")); } return result; } +static int sbs_zombie(struct acpi_sbs *sbs) +{ + return (sbs->zombie); +} + +static int sbs_mutex_lock(struct acpi_sbs *sbs) +{ + if (sbs_zombie(sbs)) { + return -ENODEV; + } + mutex_lock(&sbs->mutex); + return 0; +} + +static void sbs_mutex_unlock(struct acpi_sbs *sbs) +{ + mutex_unlock(&sbs->mutex); +} + /* -------------------------------------------------------------------------- Smart Battery System Management -------------------------------------------------------------------------- */ -/* Smart Battery */ +static int acpi_check_update_proc(struct acpi_sbs *sbs) +{ + acpi_status status = AE_OK; + + if (update_time == 0) { + sbs->update_proc_flg = 0; + return 0; + } + if (sbs->update_proc_flg == 0) { + status = acpi_os_execute(OSL_GPE_HANDLER, + acpi_sbs_update_time, sbs); + if (status != AE_OK) { + ACPI_EXCEPTION((AE_INFO, status, + "acpi_os_execute() failed")); + return 1; + } + sbs->update_proc_flg = 1; + } + return 0; +} static int acpi_sbs_generate_event(struct acpi_device *device, int event, int state, char *bid, char *class) @@ -366,12 +450,11 @@ static int acpi_battery_get_present(struct acpi_battery *battery) int result = 0; int is_present = 0; - result = acpi_sbs_smbus_read_word(battery->sbs->smbus, - ACPI_SBSM_SMBUS_ADDR, 0x01, - &state, NULL); + result = acpi_sbs_read_word(battery->sbs, + ACPI_SBSM_SMBUS_ADDR, 0x01, &state); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); } if (!result) { is_present = (state & 0x000f) & (1 << battery->id); @@ -381,45 +464,33 @@ static int acpi_battery_get_present(struct acpi_battery *battery) return result; } -static int acpi_battery_is_present(struct acpi_battery *battery) -{ - return (battery->battery_present); -} - -static int acpi_ac_is_present(struct acpi_sbs *sbs) -{ - return (sbs->ac_present); -} - static int acpi_battery_select(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 state; int foo; - if (battery->sbs->sbsm_present) { + if (sbs->sbsm_present) { /* Take special care not to knobble other nibbles of * state (aka selector_state), since * it causes charging to halt on SBSELs */ result = - acpi_sbs_smbus_read_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x01, - &state, NULL); + acpi_sbs_read_word(sbs, ACPI_SBSM_SMBUS_ADDR, 0x01, &state); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } foo = (state & 0x0fff) | (1 << (battery->id + 12)); result = - acpi_sbs_smbus_write_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x01, - foo, NULL); + acpi_sbs_write_word(sbs, ACPI_SBSM_SMBUS_ADDR, 0x01, foo); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } } @@ -430,15 +501,14 @@ static int acpi_battery_select(struct acpi_battery *battery) static int acpi_sbsm_get_info(struct acpi_sbs *sbs) { - struct acpi_ec_smbus *smbus = sbs->smbus; int result = 0; s16 battery_system_info; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x04, - &battery_system_info, NULL); + result = acpi_sbs_read_word(sbs, ACPI_SBSM_SMBUS_ADDR, 0x04, + &battery_system_info); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -451,53 +521,50 @@ static int acpi_sbsm_get_info(struct acpi_sbs *sbs) static int acpi_battery_get_info(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 battery_mode; s16 specification_info; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x03, + &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } battery->info.capacity_mode = (battery_mode & 0x8000) >> 15; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x10, - &battery->info.full_charge_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x10, + &battery->info.full_charge_capacity); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x18, - &battery->info.design_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x18, + &battery->info.design_capacity); if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x19, - &battery->info.design_voltage, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x19, + &battery->info.design_voltage); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x1a, - &specification_info, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x1a, + &specification_info); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -529,37 +596,35 @@ static int acpi_battery_get_info(struct acpi_battery *battery) battery->info.ipscale = 1; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x1c, - &battery->info.serial_number, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x1c, + &battery->info.serial_number); if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x20, - battery->info.manufacturer_name, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_str(sbs, ACPI_SB_SMBUS_ADDR, 0x20, + battery->info.manufacturer_name); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_str() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_str() failed")); goto end; } - result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x21, - battery->info.device_name, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_str(sbs, ACPI_SB_SMBUS_ADDR, 0x21, + battery->info.device_name); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_str() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_str() failed")); goto end; } - result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x22, - battery->info.device_chemistry, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_str(sbs, ACPI_SB_SMBUS_ADDR, 0x22, + battery->info.device_chemistry); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_str() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_str() failed")); goto end; } @@ -567,103 +632,60 @@ static int acpi_battery_get_info(struct acpi_battery *battery) return result; } -static void acpi_update_delay(struct acpi_sbs *sbs) -{ - if (sbs->zombie) { - return; - } - if (sbs->update_time2 > 0) { - msleep(sbs->update_time2 * 1000); - } -} - static int acpi_battery_get_state(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x09, - &battery->state.voltage, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x09, + &battery->state.voltage); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x0a, - &battery->state.amperage, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x0a, + &battery->state.amperage); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x0f, - &battery->state.remaining_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x0f, + &battery->state.remaining_capacity); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x12, - &battery->state.average_time_to_empty, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x16, + &battery->state.battery_state); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x13, - &battery->state.average_time_to_full, - &acpi_battery_smbus_err_handler); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); - goto end; - } - - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x16, - &battery->state.battery_status, - &acpi_battery_smbus_err_handler); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); - goto end; - } - - acpi_update_delay(battery->sbs); - end: return result; } static int acpi_battery_get_alarm(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01, - &battery->alarm.remaining_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x01, + &battery->alarm.remaining_capacity); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - end: return result; @@ -672,15 +694,15 @@ static int acpi_battery_get_alarm(struct acpi_battery *battery) static int acpi_battery_set_alarm(struct acpi_battery *battery, unsigned long alarm) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 battery_mode; int foo; result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_select() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); goto end; } @@ -688,33 +710,29 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery, if (alarm > 0) { result = - acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, - &acpi_battery_smbus_err_handler); + acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x03, + &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } result = - acpi_sbs_smbus_write_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01, - battery_mode & 0xbfff, - &acpi_battery_smbus_err_handler); + acpi_sbs_write_word(sbs, ACPI_SB_SMBUS_ADDR, 0x01, + battery_mode & 0xbfff); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } } foo = alarm / (battery->info.capacity_mode ? 10 : 1); - result = acpi_sbs_smbus_write_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01, - foo, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_write_word(sbs, ACPI_SB_SMBUS_ADDR, 0x01, foo); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } @@ -725,6 +743,7 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery, static int acpi_battery_set_mode(struct acpi_battery *battery) { + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 battery_mode; @@ -732,12 +751,11 @@ static int acpi_battery_set_mode(struct acpi_battery *battery) goto end; } - result = acpi_sbs_smbus_read_word(battery->sbs->smbus, - ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, NULL); + result = acpi_sbs_read_word(sbs, + ACPI_SB_SMBUS_ADDR, 0x03, &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -746,21 +764,19 @@ static int acpi_battery_set_mode(struct acpi_battery *battery) } else { battery_mode |= 0x8000; } - result = acpi_sbs_smbus_write_word(battery->sbs->smbus, - ACPI_SB_SMBUS_ADDR, 0x03, - battery_mode, NULL); + result = acpi_sbs_write_word(sbs, + ACPI_SB_SMBUS_ADDR, 0x03, battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(battery->sbs->smbus, - ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, NULL); + result = acpi_sbs_read_word(sbs, + ACPI_SB_SMBUS_ADDR, 0x03, &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -774,36 +790,36 @@ static int acpi_battery_init(struct acpi_battery *battery) result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_init() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); goto end; } result = acpi_battery_set_mode(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_set_mode() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_set_mode() failed")); goto end; } result = acpi_battery_get_info(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_info() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_info() failed")); goto end; } result = acpi_battery_get_state(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_state() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_state() failed")); goto end; } result = acpi_battery_get_alarm(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_alarm() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_alarm() failed")); goto end; } @@ -813,20 +829,19 @@ static int acpi_battery_init(struct acpi_battery *battery) static int acpi_ac_get_present(struct acpi_sbs *sbs) { - struct acpi_ec_smbus *smbus = sbs->smbus; int result = 0; s16 charger_status; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SBC_SMBUS_ADDR, 0x13, - &charger_status, NULL); + result = acpi_sbs_read_word(sbs, ACPI_SBC_SMBUS_ADDR, 0x13, + &charger_status); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - sbs->ac_present = (charger_status & 0x8000) >> 15; + sbs->ac.ac_present = (charger_status & 0x8000) >> 15; end: @@ -852,8 +867,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_entry **dir, if (!*dir) { *dir = proc_mkdir(dir_name, parent_dir); if (!*dir) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "proc_mkdir() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "proc_mkdir() failed")); return -ENODEV; } (*dir)->owner = THIS_MODULE; @@ -863,8 +878,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_entry **dir, if (info_fops) { entry = create_proc_entry(ACPI_SBS_FILE_INFO, S_IRUGO, *dir); if (!entry) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "create_proc_entry() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "create_proc_entry() failed")); } else { entry->proc_fops = info_fops; entry->data = data; @@ -876,8 +891,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_entry **dir, if (state_fops) { entry = create_proc_entry(ACPI_SBS_FILE_STATE, S_IRUGO, *dir); if (!entry) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "create_proc_entry() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "create_proc_entry() failed")); } else { entry->proc_fops = state_fops; entry->data = data; @@ -889,8 +904,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_entry **dir, if (alarm_fops) { entry = create_proc_entry(ACPI_SBS_FILE_ALARM, S_IRUGO, *dir); if (!entry) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "create_proc_entry() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "create_proc_entry() failed")); } else { entry->proc_fops = alarm_fops; entry->data = data; @@ -923,24 +938,27 @@ static struct proc_dir_entry *acpi_battery_dir = NULL; static int acpi_battery_read_info(struct seq_file *seq, void *offset) { struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; int cscale; int result = 0; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_INFO); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, battery->id, DATA_TYPE_INFO); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } - if (acpi_battery_is_present(battery)) { + if (battery->battery_present) { seq_printf(seq, "present: yes\n"); } else { seq_printf(seq, "present: no\n"); @@ -952,13 +970,13 @@ static int acpi_battery_read_info(struct seq_file *seq, void *offset) } else { cscale = battery->info.ipscale; } - seq_printf(seq, "design capacity: %i%s", + seq_printf(seq, "design capacity: %i%s\n", battery->info.design_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); - seq_printf(seq, "last full capacity: %i%s", + seq_printf(seq, "last full capacity: %i%s\n", battery->info.full_charge_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); seq_printf(seq, "battery technology: rechargeable\n"); @@ -984,7 +1002,7 @@ static int acpi_battery_read_info(struct seq_file *seq, void *offset) end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); return result; } @@ -996,26 +1014,29 @@ static int acpi_battery_info_open_fs(struct inode *inode, struct file *file) static int acpi_battery_read_state(struct seq_file *seq, void *offset) { - struct acpi_battery *battery = (struct acpi_battery *)seq->private; + struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; int result = 0; int cscale; int foo; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_STATE); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, battery->id, DATA_TYPE_STATE); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } - if (acpi_battery_is_present(battery)) { + if (battery->battery_present) { seq_printf(seq, "present: yes\n"); } else { seq_printf(seq, "present: no\n"); @@ -1028,7 +1049,7 @@ static int acpi_battery_read_state(struct seq_file *seq, void *offset) cscale = battery->info.ipscale; } - if (battery->state.battery_status & 0x0010) { + if (battery->state.battery_state & 0x0010) { seq_printf(seq, "capacity state: critical\n"); } else { seq_printf(seq, "capacity state: ok\n"); @@ -1052,16 +1073,16 @@ static int acpi_battery_read_state(struct seq_file *seq, void *offset) battery->info.capacity_mode ? "mW" : "mA"); } - seq_printf(seq, "remaining capacity: %i%s", + seq_printf(seq, "remaining capacity: %i%s\n", battery->state.remaining_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); seq_printf(seq, "present voltage: %i mV\n", battery->state.voltage * battery->info.vscale); end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); return result; } @@ -1074,24 +1095,27 @@ static int acpi_battery_state_open_fs(struct inode *inode, struct file *file) static int acpi_battery_read_alarm(struct seq_file *seq, void *offset) { struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; int result = 0; int cscale; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_ALARM); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, battery->id, DATA_TYPE_ALARM); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } - if (!acpi_battery_is_present(battery)) { + if (!battery->battery_present) { seq_printf(seq, "present: no\n"); goto end; } @@ -1104,16 +1128,16 @@ static int acpi_battery_read_alarm(struct seq_file *seq, void *offset) seq_printf(seq, "alarm: "); if (battery->alarm.remaining_capacity) { - seq_printf(seq, "%i%s", + seq_printf(seq, "%i%s\n", battery->alarm.remaining_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); } else { seq_printf(seq, "disabled\n"); } end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); return result; } @@ -1124,16 +1148,19 @@ acpi_battery_write_alarm(struct file *file, const char __user * buffer, { struct seq_file *seq = file->private_data; struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; char alarm_string[12] = { '\0' }; int result, old_alarm, new_alarm; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (!acpi_battery_is_present(battery)) { + if (!battery->battery_present) { result = -ENODEV; goto end; } @@ -1155,21 +1182,21 @@ acpi_battery_write_alarm(struct file *file, const char __user * buffer, result = acpi_battery_set_alarm(battery, new_alarm); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_set_alarm() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_set_alarm() failed")); acpi_battery_set_alarm(battery, old_alarm); goto end; } result = acpi_battery_get_alarm(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_alarm() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_alarm() failed")); acpi_battery_set_alarm(battery, old_alarm); goto end; } end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); if (result) { return result; @@ -1217,24 +1244,22 @@ static int acpi_ac_read_state(struct seq_file *seq, void *offset) struct acpi_sbs *sbs = seq->private; int result; - if (sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); - - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(sbs, DATA_TYPE_AC_STATE); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, -1, DATA_TYPE_AC_STATE); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } seq_printf(seq, "state: %s\n", - sbs->ac_present ? "on-line" : "off-line"); + sbs->ac.ac_present ? "on-line" : "off-line"); - up(&sbs_sem); + sbs_mutex_unlock(sbs); return 0; } @@ -1275,25 +1300,25 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_select() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); goto end; } result = acpi_battery_get_present(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_present() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_present() failed")); goto end; } - is_present = acpi_battery_is_present(battery); + is_present = battery->battery_present; if (is_present) { result = acpi_battery_init(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_init() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_init() failed")); goto end; } battery->init_state = 1; @@ -1308,12 +1333,16 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) &acpi_battery_state_fops, &acpi_battery_alarm_fops, battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generic_add_fs() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generic_add_fs() failed")); goto end; } battery->alive = 1; + printk(KERN_INFO PREFIX "%s [%s]: Battery Slot [%s] (battery %s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), dir_name, + sbs->battery->battery_present ? "present" : "absent"); + end: return result; } @@ -1333,8 +1362,8 @@ static int acpi_ac_add(struct acpi_sbs *sbs) result = acpi_ac_get_present(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_ac_get_present() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ac_get_present() failed")); goto end; } @@ -1343,11 +1372,15 @@ static int acpi_ac_add(struct acpi_sbs *sbs) ACPI_AC_DIR_NAME, NULL, &acpi_ac_state_fops, NULL, sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generic_add_fs() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generic_add_fs() failed")); goto end; } + printk(KERN_INFO PREFIX "%s [%s]: AC Adapter [%s] (%s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), + ACPI_AC_DIR_NAME, sbs->ac.ac_present ? "on-line" : "off-line"); + end: return result; @@ -1361,45 +1394,85 @@ static void acpi_ac_remove(struct acpi_sbs *sbs) } } -static void acpi_sbs_update_queue_run(unsigned long data) +static void acpi_sbs_update_time_run(unsigned long data) { - acpi_os_execute(OSL_GPE_HANDLER, acpi_sbs_update_queue, (void *)data); + acpi_os_execute(OSL_GPE_HANDLER, acpi_sbs_update_time, (void *)data); } -static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type) +static int acpi_sbs_update_run(struct acpi_sbs *sbs, int id, int data_type) { struct acpi_battery *battery; - int result = 0; - int old_ac_present; - int old_battery_present; - int new_ac_present; - int new_battery_present; - int id; + int result = 0, cnt; + int old_ac_present = -1; + int old_battery_present = -1; + int new_ac_present = -1; + int new_battery_present = -1; + int id_min = 0, id_max = MAX_SBS_BAT - 1; char dir_name[32]; - int do_battery_init, do_ac_init; - s16 old_remaining_capacity; + int do_battery_init = 0, do_ac_init = 0; + int old_remaining_capacity = 0; + int update_ac = 1, update_battery = 1; + int up_tm = update_time; - if (sbs->zombie) { + if (sbs_zombie(sbs)) { goto end; } - old_ac_present = acpi_ac_is_present(sbs); + if (id >= 0) { + id_min = id_max = id; + } + + if (data_type == DATA_TYPE_COMMON && up_tm > 0) { + cnt = up_tm / (up_tm > UPDATE_DELAY ? UPDATE_DELAY : up_tm); + if (sbs->run_cnt % cnt != 0) { + update_battery = 0; + } + } + + sbs->run_cnt++; + + if (!update_ac && !update_battery) { + goto end; + } + + old_ac_present = sbs->ac.ac_present; result = acpi_ac_get_present(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_ac_get_present() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ac_get_present() failed")); } - new_ac_present = acpi_ac_is_present(sbs); + new_ac_present = sbs->ac.ac_present; do_ac_init = (old_ac_present != new_ac_present); + if (sbs->run_cnt == 1 && data_type == DATA_TYPE_COMMON) { + do_ac_init = 1; + } - if (data_type == DATA_TYPE_AC_STATE) { + if (do_ac_init) { + result = acpi_sbs_generate_event(sbs->device, + ACPI_SBS_AC_NOTIFY_STATUS, + new_ac_present, + ACPI_AC_DIR_NAME, + ACPI_AC_CLASS); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generate_event() failed")); + } + } + + if (data_type == DATA_TYPE_COMMON) { + if (!do_ac_init && !update_battery) { + goto end; + } + } + + if (data_type == DATA_TYPE_AC_STATE && !do_ac_init) { goto end; } - for (id = 0; id < MAX_SBS_BAT; id++) { + for (id = id_min; id <= id_max; id++) { battery = &sbs->battery[id]; if (battery->alive == 0) { continue; @@ -1407,94 +1480,92 @@ static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type) old_remaining_capacity = battery->state.remaining_capacity; - old_battery_present = acpi_battery_is_present(battery); + old_battery_present = battery->battery_present; result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_select() failed\n")); - } - if (sbs->zombie) { - goto end; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); } result = acpi_battery_get_present(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_present() failed\n")); - } - if (sbs->zombie) { - goto end; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_present() failed")); } - new_battery_present = acpi_battery_is_present(battery); + new_battery_present = battery->battery_present; do_battery_init = ((old_battery_present != new_battery_present) && new_battery_present); - - if (sbs->zombie) { + if (!new_battery_present) + goto event; + if (do_ac_init || do_battery_init) { + result = acpi_battery_init(battery); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_init() " + "failed")); + } + } + if (sbs_zombie(sbs)) { goto end; } - if (do_ac_init || do_battery_init || - update_info_mode || sbs->update_info_mode) { - if (sbs->update_info_mode) { - sbs->update_info_mode = 0; - } else { - sbs->update_info_mode = 1; - } - result = acpi_battery_init(battery); + + if ((data_type == DATA_TYPE_COMMON + || data_type == DATA_TYPE_INFO) + && new_battery_present) { + result = acpi_battery_get_info(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_init() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_info() failed")); } } if (data_type == DATA_TYPE_INFO) { continue; } - - if (sbs->zombie) { + if (sbs_zombie(sbs)) { goto end; } - if (new_battery_present) { - result = acpi_battery_get_alarm(battery); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_alarm() " - "failed\n")); - } - if (data_type == DATA_TYPE_ALARM) { - continue; - } + if ((data_type == DATA_TYPE_COMMON + || data_type == DATA_TYPE_STATE) + && new_battery_present) { result = acpi_battery_get_state(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_state() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_state() failed")); } } - if (sbs->zombie) { - goto end; + if (data_type == DATA_TYPE_STATE) { + goto event; } - if (data_type != DATA_TYPE_COMMON) { - continue; + if (sbs_zombie(sbs)) { + goto end; } - if (old_battery_present != new_battery_present) { - sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id); - result = acpi_sbs_generate_event(sbs->device, - ACPI_SBS_BATTERY_NOTIFY_STATUS, - new_battery_present, - dir_name, - ACPI_BATTERY_CLASS); + if ((data_type == DATA_TYPE_COMMON + || data_type == DATA_TYPE_ALARM) + && new_battery_present) { + result = acpi_battery_get_alarm(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generate_event() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_alarm() " + "failed")); } } - if (old_remaining_capacity != battery->state.remaining_capacity) { + if (data_type == DATA_TYPE_ALARM) { + continue; + } + if (sbs_zombie(sbs)) { + goto end; + } + + event: + + if (old_battery_present != new_battery_present || do_ac_init || + old_remaining_capacity != + battery->state.remaining_capacity) { sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id); result = acpi_sbs_generate_event(sbs->device, ACPI_SBS_BATTERY_NOTIFY_STATUS, @@ -1502,138 +1573,120 @@ static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type) dir_name, ACPI_BATTERY_CLASS); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generate_event() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generate_event() " + "failed")); } } - - } - if (sbs->zombie) { - goto end; - } - if (data_type != DATA_TYPE_COMMON) { - goto end; - } - - if (old_ac_present != new_ac_present) { - result = acpi_sbs_generate_event(sbs->device, - ACPI_SBS_AC_NOTIFY_STATUS, - new_ac_present, - ACPI_AC_DIR_NAME, - ACPI_AC_CLASS); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generate_event() failed\n")); - } } end: + return result; } -static void acpi_sbs_update_queue(void *data) +static void acpi_sbs_update_time(void *data) { struct acpi_sbs *sbs = data; unsigned long delay = -1; int result; + unsigned int up_tm = update_time; - if (sbs->zombie) { - goto end; - } + if (sbs_mutex_lock(sbs)) + return; - result = acpi_sbs_update_run(sbs, DATA_TYPE_COMMON); + result = acpi_sbs_update_run(sbs, -1, DATA_TYPE_COMMON); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } - if (sbs->zombie) { + if (sbs_zombie(sbs)) { goto end; } - if (update_mode == REQUEST_UPDATE_MODE) { - goto end; + if (!up_tm) { + if (timer_pending(&sbs->update_timer)) + del_timer(&sbs->update_timer); + } else { + delay = (up_tm > UPDATE_DELAY ? UPDATE_DELAY : up_tm); + delay = jiffies + HZ * delay; + if (timer_pending(&sbs->update_timer)) { + mod_timer(&sbs->update_timer, delay); + } else { + sbs->update_timer.data = (unsigned long)data; + sbs->update_timer.function = acpi_sbs_update_time_run; + sbs->update_timer.expires = delay; + add_timer(&sbs->update_timer); + } } - delay = jiffies + HZ * update_time; - sbs->update_timer.data = (unsigned long)data; - sbs->update_timer.function = acpi_sbs_update_queue_run; - sbs->update_timer.expires = delay; - add_timer(&sbs->update_timer); end: - ; + + sbs_mutex_unlock(sbs); } static int acpi_sbs_add(struct acpi_device *device) { struct acpi_sbs *sbs = NULL; - struct acpi_ec_hc *ec_hc = NULL; - int result, remove_result = 0; + int result = 0, remove_result = 0; unsigned long sbs_obj; - int id, cnt; + int id; acpi_status status = AE_OK; + unsigned long val; + + status = + acpi_evaluate_integer(device->parent->handle, "_EC", NULL, &val); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "Error obtaining _EC")); + return -EIO; + } sbs = kzalloc(sizeof(struct acpi_sbs), GFP_KERNEL); if (!sbs) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "kmalloc() failed\n")); - return -ENOMEM; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "kzalloc() failed")); + result = -ENOMEM; + goto end; } - cnt = 0; - while (cnt < 10) { - cnt++; - ec_hc = acpi_get_ec_hc(device); - if (ec_hc) { - break; - } - msleep(1000); - } + mutex_init(&sbs->mutex); - if (!ec_hc) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_get_ec_hc() failed: " - "NO driver found for EC HC SMBus\n")); - result = -ENODEV; - goto end; - } + sbs_mutex_lock(sbs); + sbs->base = (val & 0xff00ull) >> 8; sbs->device = device; - sbs->smbus = ec_hc->smbus; strcpy(acpi_device_name(device), ACPI_SBS_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_SBS_CLASS); acpi_driver_data(device) = sbs; - sbs->update_time = 0; - sbs->update_time2 = 0; - result = acpi_ac_add(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "acpi_ac_add() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "acpi_ac_add() failed")); goto end; } - result = acpi_evaluate_integer(device->handle, "_SBS", NULL, &sbs_obj); - if (ACPI_FAILURE(result)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_evaluate_integer() failed\n")); + status = acpi_evaluate_integer(device->handle, "_SBS", NULL, &sbs_obj); + if (status) { + ACPI_EXCEPTION((AE_INFO, status, + "acpi_evaluate_integer() failed")); result = -EIO; goto end; } - if (sbs_obj > 0) { result = acpi_sbsm_get_info(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbsm_get_info() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbsm_get_info() failed")); goto end; } sbs->sbsm_present = 1; } + if (sbs->sbsm_present == 0) { result = acpi_battery_add(sbs, 0); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_add() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_add() failed")); goto end; } } else { @@ -1641,9 +1694,8 @@ static int acpi_sbs_add(struct acpi_device *device) if ((sbs->sbsm_batteries_supported & (1 << id))) { result = acpi_battery_add(sbs, id); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_add() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_add() failed")); goto end; } } @@ -1653,33 +1705,26 @@ static int acpi_sbs_add(struct acpi_device *device) sbs->handle = device->handle; init_timer(&sbs->update_timer); - if (update_mode == QUEUE_UPDATE_MODE) { - status = acpi_os_execute(OSL_GPE_HANDLER, - acpi_sbs_update_queue, sbs); - if (status != AE_OK) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_os_execute() failed\n")); - } - } - sbs->update_time = update_time; - sbs->update_time2 = update_time2; - - printk(KERN_INFO PREFIX "%s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); + result = acpi_check_update_proc(sbs); + if (result) + goto end; end: + + sbs_mutex_unlock(sbs); + if (result) { remove_result = acpi_sbs_remove(device, 0); if (remove_result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_remove() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_remove() failed")); } } return result; } -int acpi_sbs_remove(struct acpi_device *device, int type) +static int acpi_sbs_remove(struct acpi_device *device, int type) { struct acpi_sbs *sbs; int id; @@ -1688,15 +1733,14 @@ int acpi_sbs_remove(struct acpi_device *device, int type) return -EINVAL; } - sbs = (struct acpi_sbs *)acpi_driver_data(device); - + sbs = acpi_driver_data(device); if (!sbs) { return -EINVAL; } + sbs_mutex_lock(sbs); + sbs->zombie = 1; - sbs->update_time = 0; - sbs->update_time2 = 0; del_timer_sync(&sbs->update_timer); acpi_os_wait_events_complete(NULL); del_timer_sync(&sbs->update_timer); @@ -1707,11 +1751,41 @@ int acpi_sbs_remove(struct acpi_device *device, int type) acpi_ac_remove(sbs); + sbs_mutex_unlock(sbs); + + mutex_destroy(&sbs->mutex); + kfree(sbs); return 0; } +static void acpi_sbs_rmdirs(void) +{ + if (acpi_ac_dir) { + acpi_unlock_ac_dir(acpi_ac_dir); + acpi_ac_dir = NULL; + } + if (acpi_battery_dir) { + acpi_unlock_battery_dir(acpi_battery_dir); + acpi_battery_dir = NULL; + } +} + +static int acpi_sbs_resume(struct acpi_device *device) +{ + struct acpi_sbs *sbs; + + if (!device) + return -EINVAL; + + sbs = device->driver_data; + + sbs->run_cnt = 0; + + return 0; +} + static int __init acpi_sbs_init(void) { int result = 0; @@ -1719,35 +1793,34 @@ static int __init acpi_sbs_init(void) if (acpi_disabled) return -ENODEV; - init_MUTEX(&sbs_sem); - if (capacity_mode != DEF_CAPACITY_UNIT && capacity_mode != MAH_CAPACITY_UNIT && capacity_mode != MWH_CAPACITY_UNIT) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "acpi_sbs_init: " - "invalid capacity_mode = %d\n", - capacity_mode)); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "invalid capacity_mode = %d", capacity_mode)); return -EINVAL; } acpi_ac_dir = acpi_lock_ac_dir(); if (!acpi_ac_dir) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_lock_ac_dir() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_lock_ac_dir() failed")); return -ENODEV; } acpi_battery_dir = acpi_lock_battery_dir(); if (!acpi_battery_dir) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_lock_battery_dir() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_lock_battery_dir() failed")); + acpi_sbs_rmdirs(); return -ENODEV; } result = acpi_bus_register_driver(&acpi_sbs_driver); if (result < 0) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_bus_register_driver() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_bus_register_driver() failed")); + acpi_sbs_rmdirs(); return -ENODEV; } @@ -1756,13 +1829,9 @@ static int __init acpi_sbs_init(void) static void __exit acpi_sbs_exit(void) { - acpi_bus_unregister_driver(&acpi_sbs_driver); - acpi_unlock_ac_dir(acpi_ac_dir); - acpi_ac_dir = NULL; - acpi_unlock_battery_dir(acpi_battery_dir); - acpi_battery_dir = NULL; + acpi_sbs_rmdirs(); return; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index bb0e0da39fb1..d80dd84e5bfd 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1068,7 +1068,9 @@ acpi_add_single_object(struct acpi_device **child, } break; default: - STRUCT_TO_INT(device->status) = 0x0F; + STRUCT_TO_INT(device->status) = + ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED | + ACPI_STA_DEVICE_UI | ACPI_STA_DEVICE_FUNCTIONING; break; } diff --git a/drivers/acpi/sleep/proc.c b/drivers/acpi/sleep/proc.c index ccc11b33d89c..2d912b71e543 100644 --- a/drivers/acpi/sleep/proc.c +++ b/drivers/acpi/sleep/proc.c @@ -350,21 +350,31 @@ acpi_system_wakeup_device_seq_show(struct seq_file *seq, void *offset) { struct list_head *node, *next; - seq_printf(seq, "Device Sleep state Status\n"); + seq_printf(seq, "Device\tS-state\t Status Sysfs node\n"); spin_lock(&acpi_device_lock); list_for_each_safe(node, next, &acpi_wakeup_device_list) { struct acpi_device *dev = container_of(node, struct acpi_device, wakeup_list); + struct device *ldev; if (!dev->wakeup.flags.valid) continue; spin_unlock(&acpi_device_lock); - seq_printf(seq, "%4s %4d %s%8s\n", + + ldev = acpi_get_physical_device(dev->handle); + seq_printf(seq, "%s\t S%d\t%c%-8s ", dev->pnp.bus_id, (u32) dev->wakeup.sleep_state, - dev->wakeup.flags.run_wake ? "*" : "", + dev->wakeup.flags.run_wake ? '*' : ' ', dev->wakeup.state.enabled ? "enabled" : "disabled"); + if (ldev) + seq_printf(seq, "%s:%s", + ldev->bus ? ldev->bus->name : "no-bus", + ldev->bus_id); + seq_printf(seq, "\n"); + put_device(ldev); + spin_lock(&acpi_device_lock); } spin_unlock(&acpi_device_lock); diff --git a/drivers/acpi/tables/tbfadt.c b/drivers/acpi/tables/tbfadt.c index 807c7116e94b..1db833eb2417 100644 --- a/drivers/acpi/tables/tbfadt.c +++ b/drivers/acpi/tables/tbfadt.c @@ -347,6 +347,18 @@ static void acpi_tb_convert_fadt(void) acpi_gbl_xpm1b_enable.space_id = acpi_gbl_FADT.xpm1a_event_block.space_id; } + + /* + * For ACPI 1.0 FADTs, ensure that reserved fields (which should be zero) + * are indeed zero. This will workaround BIOSs that inadvertently placed + * values in these fields. + */ + if (acpi_gbl_FADT.header.revision < 3) { + acpi_gbl_FADT.preferred_profile = 0; + acpi_gbl_FADT.pstate_control = 0; + acpi_gbl_FADT.cst_control = 0; + acpi_gbl_FADT.boot_flags = 0; + } } /****************************************************************************** diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c index 78237577b05a..3ef593a9015f 100644 --- a/drivers/char/sonypi.c +++ b/drivers/char/sonypi.c @@ -1,6 +1,8 @@ /* * Sony Programmable I/O Control Device driver for VAIO * + * Copyright (C) 2007 Mattia Dongili <malattia@linux.it> + * * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net> * * Copyright (C) 2005 Narayanan R S <nars@kadamba.org> @@ -95,6 +97,11 @@ module_param(useinput, int, 0444); MODULE_PARM_DESC(useinput, "set this if you would like sonypi to feed events to the input subsystem"); +static int check_ioport = 1; +module_param(check_ioport, int, 0444); +MODULE_PARM_DESC(check_ioport, + "set this to 0 if you think the automatic ioport check for sony-laptop is wrong"); + #define SONYPI_DEVICE_MODEL_TYPE1 1 #define SONYPI_DEVICE_MODEL_TYPE2 2 #define SONYPI_DEVICE_MODEL_TYPE3 3 @@ -477,7 +484,7 @@ static struct sonypi_device { u16 evtype_offset; int camera_power; int bluetooth_power; - struct semaphore lock; + struct mutex lock; struct kfifo *fifo; spinlock_t fifo_lock; wait_queue_head_t fifo_proc_list; @@ -884,7 +891,7 @@ int sonypi_camera_command(int command, u8 value) if (!camera) return -EIO; - down(&sonypi_device.lock); + mutex_lock(&sonypi_device.lock); switch (command) { case SONYPI_COMMAND_SETCAMERA: @@ -919,7 +926,7 @@ int sonypi_camera_command(int command, u8 value) command); break; } - up(&sonypi_device.lock); + mutex_unlock(&sonypi_device.lock); return 0; } @@ -938,20 +945,20 @@ static int sonypi_misc_fasync(int fd, struct file *filp, int on) static int sonypi_misc_release(struct inode *inode, struct file *file) { sonypi_misc_fasync(-1, file, 0); - down(&sonypi_device.lock); + mutex_lock(&sonypi_device.lock); sonypi_device.open_count--; - up(&sonypi_device.lock); + mutex_unlock(&sonypi_device.lock); return 0; } static int sonypi_misc_open(struct inode *inode, struct file *file) { - down(&sonypi_device.lock); + mutex_lock(&sonypi_device.lock); /* Flush input queue on first open */ if (!sonypi_device.open_count) kfifo_reset(sonypi_device.fifo); sonypi_device.open_count++; - up(&sonypi_device.lock); + mutex_unlock(&sonypi_device.lock); return 0; } @@ -1001,7 +1008,7 @@ static int sonypi_misc_ioctl(struct inode *ip, struct file *fp, u8 val8; u16 val16; - down(&sonypi_device.lock); + mutex_lock(&sonypi_device.lock); switch (cmd) { case SONYPI_IOCGBRT: if (sonypi_ec_read(SONYPI_LCD_LIGHT, &val8)) { @@ -1101,7 +1108,7 @@ static int sonypi_misc_ioctl(struct inode *ip, struct file *fp, default: ret = -EINVAL; } - up(&sonypi_device.lock); + mutex_unlock(&sonypi_device.lock); return ret; } @@ -1260,6 +1267,28 @@ static int __devinit sonypi_create_input_devices(void) static int __devinit sonypi_setup_ioports(struct sonypi_device *dev, const struct sonypi_ioport_list *ioport_list) { + /* try to detect if sony-laptop is being used and thus + * has already requested one of the known ioports. + * As in the deprecated check_region this is racy has we have + * multiple ioports available and one of them can be requested + * between this check and the subsequent request. Anyway, as an + * attempt to be some more user-friendly as we currently are, + * this is enough. + */ + const struct sonypi_ioport_list *check = ioport_list; + while (check_ioport && check->port1) { + if (!request_region(check->port1, + sonypi_device.region_size, + "Sony Programable I/O Device Check")) { + printk(KERN_ERR "sonypi: ioport 0x%.4x busy, using sony-laptop? " + "if not use check_ioport=0\n", + check->port1); + return -EBUSY; + } + release_region(check->port1, sonypi_device.region_size); + check++; + } + while (ioport_list->port1) { if (request_region(ioport_list->port1, @@ -1321,6 +1350,10 @@ static int __devinit sonypi_probe(struct platform_device *dev) struct pci_dev *pcidev; int error; + printk(KERN_WARNING "sonypi: please try the sony-laptop module instead " + "and report failures, see also " + "http://www.linux.it/~malattia/wiki/index.php/Sony_drivers\n"); + spin_lock_init(&sonypi_device.fifo_lock); sonypi_device.fifo = kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL, &sonypi_device.fifo_lock); @@ -1330,7 +1363,7 @@ static int __devinit sonypi_probe(struct platform_device *dev) } init_waitqueue_head(&sonypi_device.fifo_proc_list); - init_MUTEX(&sonypi_device.lock); + mutex_init(&sonypi_device.lock); sonypi_device.bluetooth_power = -1; if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, diff --git a/drivers/clocksource/acpi_pm.c b/drivers/clocksource/acpi_pm.c index 5ac309ee7f05..5cfcff532545 100644 --- a/drivers/clocksource/acpi_pm.c +++ b/drivers/clocksource/acpi_pm.c @@ -26,7 +26,7 @@ /* * The I/O port the PMTMR resides at. * The location is detected during setup_arch(), - * in arch/i386/acpi/boot.c + * in arch/i386/kernel/acpi/boot.c */ u32 pmtmr_ioport __read_mostly; diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 639e8b6c35b1..bc773781993a 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -577,14 +577,14 @@ config VIDEO_ZORAN_AVS6EYES config VIDEO_MEYE tristate "Sony Vaio Picturebook Motion Eye Video For Linux" - depends on PCI && SONYPI && VIDEO_V4L1 + depends on PCI && SONY_LAPTOP && VIDEO_V4L1 ---help--- This is the video4linux driver for the Motion Eye camera found in the Vaio Picturebook laptops. Please read the material in <file:Documentation/video4linux/meye.txt> for more information. - If you say Y or M here, you need to say Y or M to "Sony Programmable - I/O Control Device" in the character device section. + If you say Y or M here, you need to say Y or M to "Sony Laptop + Extras" in the misc device section. To compile this driver as a module, choose M here: the module will be called meye. diff --git a/drivers/media/video/meye.c b/drivers/media/video/meye.c index 98681da5e3b9..664aba8b4d85 100644 --- a/drivers/media/video/meye.c +++ b/drivers/media/video/meye.c @@ -925,13 +925,13 @@ static int meye_do_ioctl(struct inode *inode, struct file *file, if (p->palette != VIDEO_PALETTE_YUV422 && p->palette != VIDEO_PALETTE_YUYV) return -EINVAL; mutex_lock(&meye.lock); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, p->brightness >> 10); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAHUE, p->hue >> 10); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACOLOR, p->colour >> 10); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACONTRAST, p->contrast >> 10); meye.picture = *p; mutex_unlock(&meye.lock); @@ -1043,11 +1043,11 @@ static int meye_do_ioctl(struct inode *inode, struct file *file, meye.params.quality != jp->quality) mchip_hic_stop(); /* need restart */ meye.params = *jp; - sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERASHARPNESS, meye.params.sharpness); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAAGC, meye.params.agc); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAPICTURE, meye.params.picture); mutex_unlock(&meye.lock); break; @@ -1287,38 +1287,38 @@ static int meye_do_ioctl(struct inode *inode, struct file *file, mutex_lock(&meye.lock); switch (c->id) { case V4L2_CID_BRIGHTNESS: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERABRIGHTNESS, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, c->value); meye.picture.brightness = c->value << 10; break; case V4L2_CID_HUE: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERAHUE, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERAHUE, c->value); meye.picture.hue = c->value << 10; break; case V4L2_CID_CONTRAST: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERACONTRAST, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERACONTRAST, c->value); meye.picture.contrast = c->value << 10; break; case V4L2_CID_SATURATION: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERACOLOR, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERACOLOR, c->value); meye.picture.colour = c->value << 10; break; case V4L2_CID_AGC: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERAAGC, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERAAGC, c->value); meye.params.agc = c->value; break; case V4L2_CID_SHARPNESS: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERASHARPNESS, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERASHARPNESS, c->value); meye.params.sharpness = c->value; break; case V4L2_CID_PICTURE: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERAPICTURE, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERAPICTURE, c->value); meye.params.picture = c->value; break; case V4L2_CID_JPEGQUAL: @@ -1848,7 +1848,7 @@ static int __devinit meye_probe(struct pci_dev *pcidev, memcpy(meye.video_dev, &meye_template, sizeof(meye_template)); meye.video_dev->dev = &meye.mchip_dev->dev; - if ((ret = sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 1))) { + if ((ret = sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 1))) { printk(KERN_ERR "meye: unable to power on the camera\n"); printk(KERN_ERR "meye: did you enable the camera in " "sonypi using the module options ?\n"); @@ -1928,13 +1928,13 @@ static int __devinit meye_probe(struct pci_dev *pcidev, meye.params.picture = 0; meye.params.framerate = 0; - sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, 0); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, 48); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAHUE, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACOLOR, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACONTRAST, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERASHARPNESS, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAPICTURE, 0); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAAGC, 48); printk(KERN_INFO "meye: Motion Eye Camera Driver v%s.\n", MEYE_DRIVER_VERSION); @@ -1953,7 +1953,7 @@ outremap: outregions: pci_disable_device(meye.mchip_dev); outenabledev: - sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 0); outsonypienable: kfifo_free(meye.doneq); outkfifoalloc2: @@ -1986,7 +1986,7 @@ static void __devexit meye_remove(struct pci_dev *pcidev) pci_disable_device(meye.mchip_dev); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 0); kfifo_free(meye.doneq); kfifo_free(meye.grabq); diff --git a/drivers/media/video/meye.h b/drivers/media/video/meye.h index ea107cb5c845..323d0074120d 100644 --- a/drivers/media/video/meye.h +++ b/drivers/media/video/meye.h @@ -255,7 +255,7 @@ /****************************************************************************/ /* Sony Programmable I/O Controller for accessing the camera commands */ -#include <linux/sonypi.h> +#include <linux/sony-laptop.h> /* private API definitions */ #include <linux/meye.h> 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 */ diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index a064f36a0805..b5ac810404c0 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -317,6 +317,10 @@ static int __init acpi_pci_init(void) { int ret; + if (acpi_gbl_FADT.boot_flags & BAF_MSI_NOT_SUPPORTED) { + printk(KERN_INFO"ACPI FADT declares the system doesn't support MSI, so disable it\n"); + pci_no_msi(); + } ret = register_acpi_bus_type(&acpi_pci_bus); if (ret) return 0; |