From b9417f84e17b93a6976a8a88b38bf9567975cb38 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 27 Apr 2009 16:33:36 -0600 Subject: [PATCH 01/53] ACPI: use LNXCPU, not ACPI_CPU, for Linux-specific processor _HID ACPI_PROCESSOR_OBJECT_HID is a synthetic _HID that Linux generates for "Processor" definitions. Unlike "Device" definitions, "Processor" definitions do not have a _HID in the namespace, so we generate a fake _HID. By convention, all these fake _HIDs begin with "LNX". This does change the user-visible _HID for "Processor" objects -- previously, we used "ACPI_CPU" and this changes that to "LNXCPU", which starts with "LNX" as do all the other made-up _HIDs. This change is visible in processor filenames and "hid" files under /sys/devices/LNXSYSTM:00/. Signed-off-by: Bjorn Helgaas Signed-off-by: Len Brown --- include/acpi/acpi_drivers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 0352c8f0b05b..c9922b362005 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -57,7 +57,7 @@ */ #define ACPI_POWER_HID "LNXPOWER" -#define ACPI_PROCESSOR_OBJECT_HID "ACPI_CPU" +#define ACPI_PROCESSOR_OBJECT_HID "LNXCPU" #define ACPI_PROCESSOR_HID "ACPI0007" #define ACPI_SYSTEM_HID "LNXSYSTM" #define ACPI_THERMAL_HID "LNXTHERM" From 6cc73b4806c07b4207780f6d85c456b4c5b29d71 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 27 Apr 2009 16:33:41 -0600 Subject: [PATCH 02/53] ACPI: processor: check for synthetic _HID, default to "Device" declaration This patch inverts the logic that distinguishes "Processor" statements from "Device" statements, so we now check explicitly for "Processor" and default to "Device". This removes the only real use of ACPI_PROCESSOR_HID, so we can then remove the #define. It also has the theoretical advantage that if a new processor _HID were ever added, we wouldn't have to change the code here. Signed-off-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/processor_core.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 45ad3288c5ff..cf627d64cde9 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -596,7 +596,21 @@ static int acpi_processor_get_info(struct acpi_device *device) ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No bus mastering arbitration control\n")); - if (!strcmp(acpi_device_hid(device), ACPI_PROCESSOR_HID)) { + if (!strcmp(acpi_device_hid(device), ACPI_PROCESSOR_OBJECT_HID)) { + /* Declared with "Processor" statement; match ProcessorID */ + status = acpi_evaluate_object(pr->handle, NULL, NULL, &buffer); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX "Evaluating processor object\n"); + return -ENODEV; + } + + /* + * TBD: Synch processor ID (via LAPIC/LSAPIC structures) on SMP. + * >>> 'acpi_get_processor_id(acpi_id, &id)' in + * arch/xxx/acpi.c + */ + pr->acpi_id = object.processor.proc_id; + } else { /* * Declared with "Device" statement; match _UID. * Note that we don't handle string _UIDs yet. @@ -611,20 +625,6 @@ static int acpi_processor_get_info(struct acpi_device *device) } device_declaration = 1; pr->acpi_id = value; - } else { - /* Declared with "Processor" statement; match ProcessorID */ - status = acpi_evaluate_object(pr->handle, NULL, NULL, &buffer); - if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX "Evaluating processor object\n"); - return -ENODEV; - } - - /* - * TBD: Synch processor ID (via LAPIC/LSAPIC structures) on SMP. - * >>> 'acpi_get_processor_id(acpi_id, &id)' in - * arch/xxx/acpi.c - */ - pr->acpi_id = object.processor.proc_id; } cpu_index = get_cpu_id(pr->handle, device_declaration, pr->acpi_id); From 9eccbc2f67efd0d19c47f40182abf2965c287add Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 27 Apr 2009 16:33:46 -0600 Subject: [PATCH 03/53] ACPI: processor: move device _HID into driver The ACPI0007 _HID used for processor "Device" objects in the namespace is not needed outside the processor driver, so move it there. Also, the #define is only used once, so just remove it and hard-code "ACPI0007". Signed-off-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/processor_core.c | 2 +- include/acpi/acpi_drivers.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index cf627d64cde9..cabff4cb21f0 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -89,7 +89,7 @@ static int acpi_processor_handle_eject(struct acpi_processor *pr); static const struct acpi_device_id processor_device_ids[] = { {ACPI_PROCESSOR_OBJECT_HID, 0}, - {ACPI_PROCESSOR_HID, 0}, + {"ACPI0007", 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, processor_device_ids); diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index c9922b362005..5e8ed3a78cd0 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -58,7 +58,6 @@ #define ACPI_POWER_HID "LNXPOWER" #define ACPI_PROCESSOR_OBJECT_HID "LNXCPU" -#define ACPI_PROCESSOR_HID "ACPI0007" #define ACPI_SYSTEM_HID "LNXSYSTM" #define ACPI_THERMAL_HID "LNXTHERM" #define ACPI_BUTTON_HID_POWERF "LNXPWRBN" From 8cb24c8fd70ea8431744de1ca0ca34ab45fbbdaa Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 21 May 2009 15:49:59 -0600 Subject: [PATCH 04/53] PNPACPI: parse Extended Address Space Descriptors Extended Address Space Descriptors are new in ACPI 3.0 and allow the BIOS to communicate device resource cacheability attributes (write-back, write-through, uncacheable, etc) to the OS. Previously, PNPACPI ignored these descriptors, so if a BIOS used them, a device could be responding at addresses the OS doesn't know about. This patch adds support for these descriptors in _CRS and _PRS. We don't attempt to encode them for _SRS (just like we don't attempt to encode the existing 16-, 32-, and 64-bit Address Space Descriptors). Unfortunately, I don't have a way to test this. Signed-off-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/pnp/pnpacpi/rsparser.c | 46 ++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/drivers/pnp/pnpacpi/rsparser.c b/drivers/pnp/pnpacpi/rsparser.c index adf17856bacc..e2a87fcfa6cf 100644 --- a/drivers/pnp/pnpacpi/rsparser.c +++ b/drivers/pnp/pnpacpi/rsparser.c @@ -287,6 +287,25 @@ static void pnpacpi_parse_allocated_address_space(struct pnp_dev *dev, ACPI_DECODE_16); } +static void pnpacpi_parse_allocated_ext_address_space(struct pnp_dev *dev, + struct acpi_resource *res) +{ + struct acpi_resource_extended_address64 *p = &res->data.ext_address64; + + if (p->producer_consumer == ACPI_PRODUCER) + return; + + if (p->resource_type == ACPI_MEMORY_RANGE) + pnpacpi_parse_allocated_memresource(dev, + p->minimum, p->address_length, + p->info.mem.write_protect); + else if (p->resource_type == ACPI_IO_RANGE) + pnpacpi_parse_allocated_ioresource(dev, + p->minimum, p->address_length, + p->granularity == 0xfff ? ACPI_DECODE_10 : + ACPI_DECODE_16); +} + static acpi_status pnpacpi_allocated_resource(struct acpi_resource *res, void *data) { @@ -400,8 +419,7 @@ static acpi_status pnpacpi_allocated_resource(struct acpi_resource *res, break; case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: - if (res->data.ext_address64.producer_consumer == ACPI_PRODUCER) - return AE_OK; + pnpacpi_parse_allocated_ext_address_space(dev, res); break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: @@ -630,6 +648,28 @@ static __init void pnpacpi_parse_address_option(struct pnp_dev *dev, IORESOURCE_IO_FIXED); } +static __init void pnpacpi_parse_ext_address_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource *r) +{ + struct acpi_resource_extended_address64 *p = &r->data.ext_address64; + unsigned char flags = 0; + + if (p->address_length == 0) + return; + + if (p->resource_type == ACPI_MEMORY_RANGE) { + if (p->info.mem.write_protect == ACPI_READ_WRITE_MEMORY) + flags = IORESOURCE_MEM_WRITEABLE; + pnp_register_mem_resource(dev, option_flags, p->minimum, + p->minimum, 0, p->address_length, + flags); + } else if (p->resource_type == ACPI_IO_RANGE) + pnp_register_port_resource(dev, option_flags, p->minimum, + p->minimum, 0, p->address_length, + IORESOURCE_IO_FIXED); +} + struct acpipnp_parse_option_s { struct pnp_dev *dev; unsigned int option_flags; @@ -711,6 +751,7 @@ static __init acpi_status pnpacpi_option_resource(struct acpi_resource *res, break; case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: + pnpacpi_parse_ext_address_option(dev, option_flags, res); break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: @@ -765,6 +806,7 @@ static int pnpacpi_supported_resource(struct acpi_resource *res) case ACPI_RESOURCE_TYPE_ADDRESS16: case ACPI_RESOURCE_TYPE_ADDRESS32: case ACPI_RESOURCE_TYPE_ADDRESS64: + case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: return 1; } From bdf43bbf2e19952d82995a50e00cb4b66afa4f0c Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 21 May 2009 17:28:53 -0600 Subject: [PATCH 05/53] ACPI: don't check power state after _ON/_OFF We used to evaluate _STA to check the power state of a device after running _ON or _OFF. But as far as I can tell, there's no benefit to evaluating _STA, and sometimes we trip over bugs when BIOSes don't implement _STA correctly. Yakui says Windows XP doesn't evaluate _STA during power transition. So let's skip it in Linux, too. It's conceivable that we'll need to check _STA in the future for some reason, but until we do, I don't see a reason to clutter this code path. References: http://bugzilla.kernel.org/show_bug.cgi?id=13243 http://marc.info/?l=linux-acpi&m=124166053803753&w=2 http://marc.info/?l=linux-acpi&m=124175761408256&w=2 http://marc.info/?l=linux-acpi&m=124210593114061&w=2 Signed-off-by: Bjorn Helgaas Acked-by: Matthew Garrett Signed-off-by: Len Brown --- drivers/acpi/power.c | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 56665a63bf19..d74365d4a6e7 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -194,7 +194,7 @@ static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state) static int acpi_power_on(acpi_handle handle, struct acpi_device *dev) { - int result = 0, state; + int result = 0; int found = 0; acpi_status status = AE_OK; struct acpi_power_resource *resource = NULL; @@ -236,18 +236,6 @@ static int acpi_power_on(acpi_handle handle, struct acpi_device *dev) if (ACPI_FAILURE(status)) return -ENODEV; - if (!acpi_power_nocheck) { - /* - * If acpi_power_nocheck is set, it is unnecessary to check - * the power state after power transition. - */ - result = acpi_power_get_state(resource->device->handle, - &state); - if (result) - return result; - if (state != ACPI_POWER_RESOURCE_STATE_ON) - return -ENOEXEC; - } /* Update the power resource's _device_ power state */ resource->device->power.state = ACPI_STATE_D0; @@ -258,7 +246,7 @@ static int acpi_power_on(acpi_handle handle, struct acpi_device *dev) static int acpi_power_off_device(acpi_handle handle, struct acpi_device *dev) { - int result = 0, state; + int result = 0; acpi_status status = AE_OK; struct acpi_power_resource *resource = NULL; struct list_head *node, *next; @@ -293,18 +281,6 @@ static int acpi_power_off_device(acpi_handle handle, struct acpi_device *dev) if (ACPI_FAILURE(status)) return -ENODEV; - if (!acpi_power_nocheck) { - /* - * If acpi_power_nocheck is set, it is unnecessary to check - * the power state after power transition. - */ - result = acpi_power_get_state(handle, &state); - if (result) - return result; - if (state != ACPI_POWER_RESOURCE_STATE_OFF) - return -ENOEXEC; - } - /* Update the power resource's _device_ power state */ resource->device->power.state = ACPI_STATE_D3; From 113b3a2b901573961509e81a28e9546cf9defef0 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Wed, 27 May 2009 21:46:11 -0400 Subject: [PATCH 06/53] ACPI: delete acpi.power_nocheck from kernel-parameters.txt Signed-off-by: Len Brown --- Documentation/kernel-parameters.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index fd5cac013037..ed3d8b966c02 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -229,14 +229,6 @@ and is between 256 and 4096 characters. It is defined in the file to assume that this machine's pmtimer latches its value and always returns good values. - acpi.power_nocheck= [HW,ACPI] - Format: 1/0 enable/disable the check of power state. - On some bogus BIOS the _PSC object/_STA object of - power resource can't return the correct device power - state. In such case it is unneccessary to check its - power state again in power transition. - 1 : disable the power state check - acpi_sci= [HW,ACPI] ACPI System Control Interrupt trigger mode Format: { level | edge | high | low } From ee1ca48fae7e575d5e399d4fdcfe0afc1212a64c Mon Sep 17 00:00:00 2001 From: "Pallipadi, Venkatesh" Date: Thu, 21 May 2009 17:09:10 -0700 Subject: [PATCH 07/53] ACPI: Disable ARB_DISABLE on platforms where it is not needed ARB_DISABLE is a NOP on all of the recent Intel platforms. For such platforms, reduce contention on c3_lock by skipping the fake ARB_DISABLE. Signed-off-by: Venkatesh Pallipadi Signed-off-by: Len Brown --- arch/x86/kernel/acpi/cstate.c | 16 +++++++++++++--- drivers/acpi/processor_idle.c | 7 +++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/arch/x86/kernel/acpi/cstate.c b/arch/x86/kernel/acpi/cstate.c index bbbe4bbb6f34..8c44c232efcb 100644 --- a/arch/x86/kernel/acpi/cstate.c +++ b/arch/x86/kernel/acpi/cstate.c @@ -34,12 +34,22 @@ void acpi_processor_power_init_bm_check(struct acpi_processor_flags *flags, flags->bm_check = 1; else if (c->x86_vendor == X86_VENDOR_INTEL) { /* - * Today all CPUs that support C3 share cache. - * TBD: This needs to look at cache shared map, once - * multi-core detection patch makes to the base. + * Today all MP CPUs that support C3 share cache. + * And caches should not be flushed by software while + * entering C3 type state. */ flags->bm_check = 1; } + + /* + * On all recent Intel platforms, ARB_DISABLE is a nop. + * So, set bm_control to zero to indicate that ARB_DISABLE + * is not required while entering C3 type state on + * P4, Core and beyond CPUs + */ + if (c->x86_vendor == X86_VENDOR_INTEL && + (c->x86 > 0x6 || (c->x86 == 6 && c->x86_model >= 14))) + flags->bm_control = 0; } EXPORT_SYMBOL(acpi_processor_power_init_bm_check); diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 72069ba5f1ed..4840c79fd8e0 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -512,7 +512,8 @@ static void acpi_processor_power_verify_c2(struct acpi_processor_cx *cx) static void acpi_processor_power_verify_c3(struct acpi_processor *pr, struct acpi_processor_cx *cx) { - static int bm_check_flag; + static int bm_check_flag = -1; + static int bm_control_flag = -1; if (!cx->address) @@ -542,12 +543,14 @@ static void acpi_processor_power_verify_c3(struct acpi_processor *pr, } /* All the logic here assumes flags.bm_check is same across all CPUs */ - if (!bm_check_flag) { + if (bm_check_flag == -1) { /* Determine whether bm_check is needed based on CPU */ acpi_processor_power_init_bm_check(&(pr->flags), pr->id); bm_check_flag = pr->flags.bm_check; + bm_control_flag = pr->flags.bm_control; } else { pr->flags.bm_check = bm_check_flag; + pr->flags.bm_control = bm_control_flag; } if (pr->flags.bm_check) { From c636f753b5b943f08fb3490e7f1a9b47aa5cc7d3 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Tue, 19 May 2009 23:47:38 -0400 Subject: [PATCH 08/53] ACPI: delete dead acpi_disabled setting code Testing CONFIG_ACPI inside boot.c is a waste of text, since boot.c is built only when CONFIG_ACPI=y Signed-off-by: Len Brown --- arch/x86/kernel/acpi/boot.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 723989d7f802..d4acedf07866 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -43,11 +43,7 @@ static int __initdata acpi_force = 0; u32 acpi_rsdt_forced; -#ifdef CONFIG_ACPI -int acpi_disabled = 0; -#else -int acpi_disabled = 1; -#endif +int acpi_disabled; EXPORT_SYMBOL(acpi_disabled); #ifdef CONFIG_X86_64 From d023e49118b9c93bbab9aaf798b25f78f1a5803c Mon Sep 17 00:00:00 2001 From: Olivier Berger Date: Thu, 21 May 2009 16:07:38 +0200 Subject: [PATCH 09/53] ACPI: Remove Asus P4B266 from blacklist See http://marc.info/?l=linux-acpi&m=124068823904429&w=2 for discussion Signed-off-by: Olivier Berger Signed-off-by: Len Brown --- arch/x86/kernel/acpi/boot.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index d4acedf07866..817d6a5e115d 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -1563,14 +1563,6 @@ static struct dmi_system_id __initdata acpi_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Workstation W8000"), }, }, - { - .callback = force_acpi_ht, - .ident = "ASUS P4B266", - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."), - DMI_MATCH(DMI_BOARD_NAME, "P4B266"), - }, - }, { .callback = force_acpi_ht, .ident = "ASUS P2B-DS", From 24c5c4c2f506bf87ef2343669fb892c944c3fdde Mon Sep 17 00:00:00 2001 From: Zhao Yakui Date: Thu, 21 May 2009 16:25:35 +0800 Subject: [PATCH 10/53] ACPI: increase size of acpi_bus_id[] Previously [5], now [8]. sprintf(acpi_device_bid(device), "CPU%X", cpu_id) now looks better on systems with more than 0xFF processors. Signed-off-by: Zhao Yakui Signed-off-by: Len Brown --- include/acpi/acpi_bus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index c34b11022908..0be24101a48f 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -168,7 +168,7 @@ struct acpi_device_dir { /* Plug and Play */ -typedef char acpi_bus_id[5]; +typedef char acpi_bus_id[8]; typedef unsigned long acpi_bus_address; typedef char acpi_hardware_id[15]; typedef char acpi_unique_id[9]; From c4bf2f372db09ef8d16a25a60d523bfa1c50f7b5 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Thu, 11 Jun 2009 23:53:55 -0400 Subject: [PATCH 11/53] ACPI, PCI, x86: move MCFG parsing routine from ACPI to PCI file Move arch/x86/kernel/acpi/boot.c: acpi_parse_mcfg() to arch/x86/pci/mmconfig-shared.c: pci_parse_mcfg() where it is used, and make it static. Move associated globals and helper routine with it. No functional change. This code move is in preparation for SFI support, which will allow the PCI code to find the MCFG table on systems which do not support ACPI. Signed-off-by: Len Brown Acked-by: Jesse Barnes --- arch/x86/include/asm/pci_x86.h | 3 ++ arch/x86/kernel/acpi/boot.c | 66 ---------------------------------- arch/x86/pci/mmconfig-shared.c | 65 ++++++++++++++++++++++++++++++++- include/linux/acpi.h | 3 -- 4 files changed, 67 insertions(+), 70 deletions(-) diff --git a/arch/x86/include/asm/pci_x86.h b/arch/x86/include/asm/pci_x86.h index e60fd3e14bdf..b399988eee3a 100644 --- a/arch/x86/include/asm/pci_x86.h +++ b/arch/x86/include/asm/pci_x86.h @@ -121,6 +121,9 @@ extern int __init pcibios_init(void); extern int __init pci_mmcfg_arch_init(void); extern void __init pci_mmcfg_arch_free(void); +extern struct acpi_mcfg_allocation *pci_mmcfg_config; +extern int pci_mmcfg_config_num; + /* * AMD Fam10h CPUs are buggy, and cannot access MMIO config space * on their northbrige except through the * %eax register. As such, you MUST diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 817d6a5e115d..f54e0e557cd2 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -117,72 +117,6 @@ void __init __acpi_unmap_table(char *map, unsigned long size) early_iounmap(map, size); } -#ifdef CONFIG_PCI_MMCONFIG - -static int acpi_mcfg_64bit_base_addr __initdata = FALSE; - -/* The physical address of the MMCONFIG aperture. Set from ACPI tables. */ -struct acpi_mcfg_allocation *pci_mmcfg_config; -int pci_mmcfg_config_num; - -static int __init acpi_mcfg_oem_check(struct acpi_table_mcfg *mcfg) -{ - if (!strcmp(mcfg->header.oem_id, "SGI")) - acpi_mcfg_64bit_base_addr = TRUE; - - return 0; -} - -int __init acpi_parse_mcfg(struct acpi_table_header *header) -{ - struct acpi_table_mcfg *mcfg; - unsigned long i; - int config_size; - - if (!header) - return -EINVAL; - - mcfg = (struct acpi_table_mcfg *)header; - - /* how many config structures do we have */ - pci_mmcfg_config_num = 0; - i = header->length - sizeof(struct acpi_table_mcfg); - while (i >= sizeof(struct acpi_mcfg_allocation)) { - ++pci_mmcfg_config_num; - i -= sizeof(struct acpi_mcfg_allocation); - }; - if (pci_mmcfg_config_num == 0) { - printk(KERN_ERR PREFIX "MMCONFIG has no entries\n"); - return -ENODEV; - } - - config_size = pci_mmcfg_config_num * sizeof(*pci_mmcfg_config); - pci_mmcfg_config = kmalloc(config_size, GFP_KERNEL); - if (!pci_mmcfg_config) { - printk(KERN_WARNING PREFIX - "No memory for MCFG config tables\n"); - return -ENOMEM; - } - - memcpy(pci_mmcfg_config, &mcfg[1], config_size); - - acpi_mcfg_oem_check(mcfg); - - for (i = 0; i < pci_mmcfg_config_num; ++i) { - if ((pci_mmcfg_config[i].address > 0xFFFFFFFF) && - !acpi_mcfg_64bit_base_addr) { - printk(KERN_ERR PREFIX - "MMCONFIG not in low 4GB of memory\n"); - kfree(pci_mmcfg_config); - pci_mmcfg_config_num = 0; - return -ENODEV; - } - } - - return 0; -} -#endif /* CONFIG_PCI_MMCONFIG */ - #ifdef CONFIG_X86_LOCAL_APIC static int __init acpi_parse_madt(struct acpi_table_header *table) { diff --git a/arch/x86/pci/mmconfig-shared.c b/arch/x86/pci/mmconfig-shared.c index 8766b0e216c5..712443ec6d43 100644 --- a/arch/x86/pci/mmconfig-shared.c +++ b/arch/x86/pci/mmconfig-shared.c @@ -523,6 +523,69 @@ reject: static int __initdata known_bridge; +static int acpi_mcfg_64bit_base_addr __initdata = FALSE; + +/* The physical address of the MMCONFIG aperture. Set from ACPI tables. */ +struct acpi_mcfg_allocation *pci_mmcfg_config; +int pci_mmcfg_config_num; + +static int __init acpi_mcfg_oem_check(struct acpi_table_mcfg *mcfg) +{ + if (!strcmp(mcfg->header.oem_id, "SGI")) + acpi_mcfg_64bit_base_addr = TRUE; + + return 0; +} + +static int __init pci_parse_mcfg(struct acpi_table_header *header) +{ + struct acpi_table_mcfg *mcfg; + unsigned long i; + int config_size; + + if (!header) + return -EINVAL; + + mcfg = (struct acpi_table_mcfg *)header; + + /* how many config structures do we have */ + pci_mmcfg_config_num = 0; + i = header->length - sizeof(struct acpi_table_mcfg); + while (i >= sizeof(struct acpi_mcfg_allocation)) { + ++pci_mmcfg_config_num; + i -= sizeof(struct acpi_mcfg_allocation); + }; + if (pci_mmcfg_config_num == 0) { + printk(KERN_ERR PREFIX "MMCONFIG has no entries\n"); + return -ENODEV; + } + + config_size = pci_mmcfg_config_num * sizeof(*pci_mmcfg_config); + pci_mmcfg_config = kmalloc(config_size, GFP_KERNEL); + if (!pci_mmcfg_config) { + printk(KERN_WARNING PREFIX + "No memory for MCFG config tables\n"); + return -ENOMEM; + } + + memcpy(pci_mmcfg_config, &mcfg[1], config_size); + + acpi_mcfg_oem_check(mcfg); + + for (i = 0; i < pci_mmcfg_config_num; ++i) { + if ((pci_mmcfg_config[i].address > 0xFFFFFFFF) && + !acpi_mcfg_64bit_base_addr) { + printk(KERN_ERR PREFIX + "MMCONFIG not in low 4GB of memory\n"); + kfree(pci_mmcfg_config); + pci_mmcfg_config_num = 0; + return -ENODEV; + } + } + + return 0; +} + static void __init __pci_mmcfg_init(int early) { /* MMCONFIG disabled */ @@ -543,7 +606,7 @@ static void __init __pci_mmcfg_init(int early) } if (!known_bridge) - acpi_table_parse(ACPI_SIG_MCFG, acpi_parse_mcfg); + acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg); pci_mmcfg_reject_broken(early); diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 88be890ee3c7..73cb141150df 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -113,9 +113,6 @@ void acpi_irq_stats_init(void); extern u32 acpi_irq_handled; extern u32 acpi_irq_not_handled; -extern struct acpi_mcfg_allocation *pci_mmcfg_config; -extern int pci_mmcfg_config_num; - extern int sbf_port; extern unsigned long acpi_realmode_flags; From 4a7a16dc061e4c57bf288150f51bd4c2ace33723 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Fri, 12 Jun 2009 20:42:08 -0400 Subject: [PATCH 12/53] ACPI: move declaration acpi_early_init() to acpi.h Signed-off-by: Len Brown --- include/linux/acpi.h | 3 +++ init/main.c | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 73cb141150df..bf17681cb06f 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -290,7 +290,10 @@ void __init acpi_s4_no_nvs(void); OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL) extern acpi_status acpi_pci_osc_control_set(acpi_handle handle, u32 flags); +extern void acpi_early_init(void); + #else /* CONFIG_ACPI */ +static inline void acpi_early_init(void) { } static inline int early_acpi_boot_init(void) { diff --git a/init/main.c b/init/main.c index d721dad05dd7..f1b9f0fdb1b4 100644 --- a/init/main.c +++ b/init/main.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -86,11 +87,6 @@ extern void sbus_init(void); extern void prio_tree_init(void); extern void radix_tree_init(void); extern void free_initmem(void); -#ifdef CONFIG_ACPI -extern void acpi_early_init(void); -#else -static inline void acpi_early_init(void) { } -#endif #ifndef CONFIG_DEBUG_RODATA static inline void mark_rodata_ro(void) { } #endif From ab46feae865c5b96dbf5e261be8638165932bfb1 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Fri, 12 Jun 2009 20:47:50 -0400 Subject: [PATCH 13/53] ACPI: #define acpi_disabled 1 for CONFIG_ACPI=n SFI will need to test acpi_disabled no matter the value of CONFIG_ACPI. Signed-off-by: Len Brown --- arch/x86/include/asm/acpi.h | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/include/asm/acpi.h b/arch/x86/include/asm/acpi.h index 4518dc500903..20d1465a2ab0 100644 --- a/arch/x86/include/asm/acpi.h +++ b/arch/x86/include/asm/acpi.h @@ -144,6 +144,7 @@ static inline unsigned int acpi_processor_cstate_check(unsigned int max_cstate) #else /* !CONFIG_ACPI */ +#define acpi_disabled 1 #define acpi_lapic 0 #define acpi_ioapic 0 static inline void acpi_noirq_set(void) { } From 050df107c408a3df048524b3783a5fc6d4dccfdb Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:05 -0300 Subject: [PATCH 14/53] thinkpad-acpi: store fw version with strict checking Extend the thinkpad model and firmware identification data with the release serial number for the BIOS and firmware (when available), as that is easier to parse and compare than the version strings. We're going to greatly extend the use of the ThinkPad DMI data through quirk lists, so it is best to be quite strict and make sure what we get from DMI is exactly what we expect, otherwise quirk matching may result in quite insane things. IBM (and Lenovo, at least for the ThinkPad line) uses this schema for firmware versioning and model: Firmware model: Two digits, [0-9A-Z] Firmware version: AABBCCDD, where AA = firmware model, see above BB = "ET" for BIOS, "HT" for EC CC = release version, two digits, [0-9A-Z], "00" < "09" < "0A" < "10" < "A0" < "ZZ" DD = "WW" Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 46 +++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 912be65b6261..d2a0ef83fbb5 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -284,8 +284,10 @@ struct thinkpad_id_data { char *bios_version_str; /* Something like 1ZET51WW (1.03z) */ char *ec_version_str; /* Something like 1ZHT51WW-1.04a */ - u16 bios_model; /* Big Endian, TP-1Y = 0x5931, 0 = unknown */ + u16 bios_model; /* 1Y = 0x5931, 0 = unknown */ u16 ec_model; + u16 bios_release; /* 1ZETK1WW = 0x314b, 0 = unknown */ + u16 ec_release; char *model_str; /* ThinkPad T43 */ char *nummodel_str; /* 9384A9C for a 9384-A9C model */ @@ -7357,6 +7359,24 @@ err_out: /* Probing */ +static bool __pure __init tpacpi_is_fw_digit(const char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'); +} + +/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */ +static bool __pure __init tpacpi_is_valid_fw_id(const char* const s, + const char t) +{ + return s && strlen(s) >= 8 && + tpacpi_is_fw_digit(s[0]) && + tpacpi_is_fw_digit(s[1]) && + s[2] == t && s[3] == 'T' && + tpacpi_is_fw_digit(s[4]) && + tpacpi_is_fw_digit(s[5]) && + s[6] == 'W' && s[7] == 'W'; +} + /* returns 0 - probe ok, or < 0 - probe error. * Probe ok doesn't mean thinkpad found. * On error, kfree() cleanup on tp->* is not performed, caller must do it */ @@ -7383,10 +7403,15 @@ static int __must_check __init get_thinkpad_model_data( tp->bios_version_str = kstrdup(s, GFP_KERNEL); if (s && !tp->bios_version_str) return -ENOMEM; - if (!tp->bios_version_str) + + /* Really ancient ThinkPad 240X will fail this, which is fine */ + if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E')) return 0; + tp->bios_model = tp->bios_version_str[0] | (tp->bios_version_str[1] << 8); + tp->bios_release = (tp->bios_version_str[4] << 8) + | tp->bios_version_str[5]; /* * ThinkPad T23 or newer, A31 or newer, R50e or newer, @@ -7405,8 +7430,21 @@ static int __must_check __init get_thinkpad_model_data( tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL); if (!tp->ec_version_str) return -ENOMEM; - tp->ec_model = ec_fw_string[0] - | (ec_fw_string[1] << 8); + + if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) { + tp->ec_model = ec_fw_string[0] + | (ec_fw_string[1] << 8); + tp->ec_release = (ec_fw_string[4] << 8) + | ec_fw_string[5]; + } else { + printk(TPACPI_NOTICE + "ThinkPad firmware release %s " + "doesn't match the known patterns\n", + ec_fw_string); + printk(TPACPI_NOTICE + "please report this to %s\n", + TPACPI_MAIL); + } break; } } From 7d95a3d564901e88ed42810f054e579874151999 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:06 -0300 Subject: [PATCH 15/53] thinkpad-acpi: add quirklist engine Add a quirklist engine suitable for matching ThinkPad firmware, and change the code to use it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 119 ++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 21 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index d2a0ef83fbb5..3981b060b7d5 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -364,6 +364,73 @@ static void tpacpi_log_usertask(const char * const what) } \ } while (0) +/* + * Quirk handling helpers + * + * ThinkPad IDs and versions seen in the field so far + * are two-characters from the set [0-9A-Z], i.e. base 36. + * + * We use values well outside that range as specials. + */ + +#define TPACPI_MATCH_ANY 0xffffU +#define TPACPI_MATCH_UNKNOWN 0U + +/* TPID('1', 'Y') == 0x5931 */ +#define TPID(__c1, __c2) (((__c2) << 8) | (__c1)) + +#define TPACPI_Q_IBM(__id1, __id2, __quirk) \ + { .vendor = PCI_VENDOR_ID_IBM, \ + .bios = TPID(__id1, __id2), \ + .ec = TPACPI_MATCH_ANY, \ + .quirks = (__quirk) } + +#define TPACPI_Q_LNV(__id1, __id2, __quirk) \ + { .vendor = PCI_VENDOR_ID_LENOVO, \ + .bios = TPID(__id1, __id2), \ + .ec = TPACPI_MATCH_ANY, \ + .quirks = (__quirk) } + +struct tpacpi_quirk { + unsigned int vendor; + u16 bios; + u16 ec; + unsigned long quirks; +}; + +/** + * tpacpi_check_quirks() - search BIOS/EC version on a list + * @qlist: array of &struct tpacpi_quirk + * @qlist_size: number of elements in @qlist + * + * Iterates over a quirks list until one is found that matches the + * ThinkPad's vendor, BIOS and EC model. + * + * Returns 0 if nothing matches, otherwise returns the quirks field of + * the matching &struct tpacpi_quirk entry. + * + * The match criteria is: vendor, ec and bios much match. + */ +static unsigned long __init tpacpi_check_quirks( + const struct tpacpi_quirk *qlist, + unsigned int qlist_size) +{ + while (qlist_size) { + if ((qlist->vendor == thinkpad_id.vendor || + qlist->vendor == TPACPI_MATCH_ANY) && + (qlist->bios == thinkpad_id.bios_model || + qlist->bios == TPACPI_MATCH_ANY) && + (qlist->ec == thinkpad_id.ec_model || + qlist->ec == TPACPI_MATCH_ANY)) + return qlist->quirks; + + qlist_size--; + qlist++; + } + return 0; +} + + /**************************************************************************** **************************************************************************** * @@ -6223,30 +6290,18 @@ TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */ * We assume 0x07 really means auto mode while this quirk is active, * as this is far more likely than the ThinkPad being in level 7, * which is only used by the firmware during thermal emergencies. + * + * Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52), + * TP-70 (T43, R52), which are known to be buggy. */ -static void fan_quirk1_detect(void) +static void fan_quirk1_setup(void) { - /* In some ThinkPads, neither the EC nor the ACPI - * DSDT initialize the HFSP register, and it ends up - * being initially 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) { - switch (thinkpad_id.ec_model) { - case 0x5931: /* TP-1Y */ - case 0x3837: /* TP-78 */ - case 0x3637: /* TP-76 */ - case 0x3037: /* TP-70 */ - printk(TPACPI_NOTICE - "fan_init: initial fan status is unknown, " - "assuming it is in auto mode\n"); - tp_features.fan_ctrl_status_undef = 1; - ;; - } + printk(TPACPI_NOTICE + "fan_init: initial fan status is unknown, " + "assuming it is in auto mode\n"); + tp_features.fan_ctrl_status_undef = 1; } } @@ -6804,9 +6859,27 @@ static const struct attribute_group fan_attr_group = { .attrs = fan_attributes, }; +#define TPACPI_FAN_Q1 0x0001 + +#define TPACPI_FAN_QI(__id1, __id2, __quirks) \ + { .vendor = PCI_VENDOR_ID_IBM, \ + .bios = TPACPI_MATCH_ANY, \ + .ec = TPID(__id1, __id2), \ + .quirks = __quirks } + +static const struct tpacpi_quirk fan_quirk_table[] __initconst = { + TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1), + TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1), + TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1), + TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1), +}; + +#undef TPACPI_FAN_QI + static int __init fan_init(struct ibm_init_struct *iibm) { int rc; + unsigned long quirks; vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, "initializing fan subdriver\n"); @@ -6823,6 +6896,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) TPACPI_ACPIHANDLE_INIT(gfan); TPACPI_ACPIHANDLE_INIT(sfan); + quirks = tpacpi_check_quirks(fan_quirk_table, + ARRAY_SIZE(fan_quirk_table)); + if (gfan_handle) { /* 570, 600e/x, 770e, 770x */ fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN; @@ -6832,7 +6908,8 @@ static int __init fan_init(struct ibm_init_struct *iibm) if (likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) { fan_status_access_mode = TPACPI_FAN_RD_TPEC; - fan_quirk1_detect(); + if (quirks & TPACPI_FAN_Q1) + fan_quirk1_setup(); } else { printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " From 60201732f03c1231742e5872abe55a3bf59849a5 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:07 -0300 Subject: [PATCH 16/53] thinkpad-acpi: fix BEEP ACPI handler warnings Some ThinkPads want two arguments for BEEP, while others want just one, causing ACPICA to log warnings like this: ACPI Warning (nseval-0177): Excess arguments - method [BEEP] needs 1, found 2 [20080926] Deal with it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3981b060b7d5..da739d5c9210 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -264,6 +264,7 @@ static struct { u32 wan:1; u32 uwb:1; u32 fan_ctrl_status_undef:1; + u32 beep_needs_two_args:1; u32 input_device_registered:1; u32 platform_drv_registered:1; u32 platform_drv_attrs_registered:1; @@ -5142,8 +5143,17 @@ static struct ibm_struct led_driver_data = { TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ +#define TPACPI_BEEP_Q1 0x0001 + +static const struct tpacpi_quirk beep_quirk_table[] __initconst = { + TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */ + TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */ +}; + static int __init beep_init(struct ibm_init_struct *iibm) { + unsigned long quirks; + vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n"); TPACPI_ACPIHANDLE_INIT(beep); @@ -5151,6 +5161,11 @@ static int __init beep_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", str_supported(beep_handle != NULL)); + quirks = tpacpi_check_quirks(beep_quirk_table, + ARRAY_SIZE(beep_quirk_table)); + + tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1); + return (beep_handle)? 0 : 1; } @@ -5182,8 +5197,15 @@ static int beep_write(char *buf) /* beep_cmd set */ } else return -EINVAL; - if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) - return -EIO; + if (tp_features.beep_needs_two_args) { + if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", + beep_cmd, 0)) + return -EIO; + } else { + if (!acpi_evalf(beep_handle, NULL, NULL, "vd", + beep_cmd)) + return -EIO; + } } return 0; From f21179a47ff8d1046a61c1cf5920244997a4a7bb Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:08 -0300 Subject: [PATCH 17/53] thinkpad-acpi: enhance led support Add support for extra LEDs on recent ThinkPads, and avoid registering with the led class the LEDs which are not available for a given ThinkPad model. All non-restricted LEDs are always available through the procfs interface, as the firmware doesn't care if an attempt is made to access an invalid LED. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 23 ++++++- drivers/platform/x86/thinkpad_acpi.c | 88 +++++++++++++++++++++---- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index e7e9a69069e1..88fc0661de56 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -920,7 +920,7 @@ The available commands are: echo ' off' >/proc/acpi/ibm/led echo ' blink' >/proc/acpi/ibm/led -The range is 0 to 7. The set of LEDs that can be +The range is 0 to 15. The set of LEDs that can be controlled varies from model to model. Here is the common ThinkPad mapping: @@ -932,6 +932,11 @@ mapping: 5 - UltraBase battery slot 6 - (unknown) 7 - standby + 8 - dock status 1 + 9 - dock status 2 + 10, 11 - (unknown) + 12 - thinkvantage + 13, 14, 15 - (unknown) All of the above can be turned on and off and can be made to blink. @@ -940,10 +945,12 @@ sysfs notes: The ThinkPad LED sysfs interface is described in detail by the LED class documentation, in Documentation/leds-class.txt. -The leds are named (in LED ID order, from 0 to 7): +The LEDs are named (in LED ID order, from 0 to 12): "tpacpi::power", "tpacpi:orange:batt", "tpacpi:green:batt", "tpacpi::dock_active", "tpacpi::bay_active", "tpacpi::dock_batt", -"tpacpi::unknown_led", "tpacpi::standby". +"tpacpi::unknown_led", "tpacpi::standby", "tpacpi::dock_status1", +"tpacpi::dock_status2", "tpacpi::unknown_led2", "tpacpi::unknown_led3", +"tpacpi::thinkvantage". Due to limitations in the sysfs LED class, if the status of the LED indicators cannot be read due to an error, thinkpad-acpi will report it as @@ -958,6 +965,12 @@ ThinkPad indicator LED should blink in hardware accelerated mode, use the "timer" trigger, and leave the delay_on and delay_off parameters set to zero (to request hardware acceleration autodetection). +LEDs that are known not to exist in a given ThinkPad model are not +made available through the sysfs interface. If you have a dock and you +notice there are LEDs listed for your ThinkPad that do not exist (and +are not in the dock), or if you notice that there are missing LEDs, +a report to ibm-acpi-devel@lists.sourceforge.net is appreciated. + ACPI sounds -- /proc/acpi/ibm/beep ---------------------------------- @@ -1555,3 +1568,7 @@ Sysfs interface changelog: 0x020300: hotkey enable/disable support removed, attributes hotkey_bios_enabled and hotkey_enable deprecated and marked for removal. + +0x020400: Marker for 16 LEDs support. Also, LEDs that are known + to not exist in a given model are not registered with + the LED sysfs class anymore. diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index da739d5c9210..06c7c03c8f2f 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -22,7 +22,7 @@ */ #define TPACPI_VERSION "0.23" -#define TPACPI_SYSFS_VERSION 0x020300 +#define TPACPI_SYSFS_VERSION 0x020400 /* * Changelog: @@ -4815,7 +4815,7 @@ TPACPI_HANDLE(led, ec, "SLED", /* 570 */ "LED", /* all others */ ); /* R30, R31 */ -#define TPACPI_LED_NUMLEDS 8 +#define TPACPI_LED_NUMLEDS 16 static struct tpacpi_led_classdev *tpacpi_leds; static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS]; static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = { @@ -4828,15 +4828,20 @@ static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = { "tpacpi::dock_batt", "tpacpi::unknown_led", "tpacpi::standby", + "tpacpi::dock_status1", + "tpacpi::dock_status2", + "tpacpi::unknown_led2", + "tpacpi::unknown_led3", + "tpacpi::thinkvantage", }; -#define TPACPI_SAFE_LEDS 0x0081U +#define TPACPI_SAFE_LEDS 0x1081U static inline bool tpacpi_is_led_restricted(const unsigned int led) { #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS return false; #else - return (TPACPI_SAFE_LEDS & (1 << led)) == 0; + return (1U & (TPACPI_SAFE_LEDS >> led)) == 0; #endif } @@ -4998,6 +5003,10 @@ static int __init tpacpi_init_led(unsigned int led) tpacpi_leds[led].led = led; + /* LEDs with no name don't get registered */ + if (!tpacpi_led_names[led]) + return 0; + tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set; tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set; if (led_supported == TPACPI_LED_570) @@ -5016,10 +5025,59 @@ static int __init tpacpi_init_led(unsigned int led) return rc; } +static const struct tpacpi_quirk led_useful_qtable[] __initconst = { + TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */ + TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */ + TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */ + + TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */ + TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */ + TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */ + TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */ + TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */ + TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */ + TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */ + TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */ + + TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */ + TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */ + TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */ + TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */ + TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */ + + TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */ + TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */ + TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */ + TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */ + + /* (1) - may have excess leds enabled on MSB */ + + /* Defaults (order matters, keep last, don't reorder!) */ + { /* Lenovo */ + .vendor = PCI_VENDOR_ID_LENOVO, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, + .quirks = 0x1fffU, + }, + { /* IBM ThinkPads with no EC version string */ + .vendor = PCI_VENDOR_ID_IBM, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN, + .quirks = 0x00ffU, + }, + { /* IBM ThinkPads with EC version string */ + .vendor = PCI_VENDOR_ID_IBM, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, + .quirks = 0x00bfU, + }, +}; + +#undef TPACPI_LEDQ_IBM +#undef TPACPI_LEDQ_LNV + static int __init led_init(struct ibm_init_struct *iibm) { unsigned int i; int rc; + unsigned long useful_leds; vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); @@ -5041,6 +5099,9 @@ static int __init led_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n", str_supported(led_supported), led_supported); + if (led_supported == TPACPI_LED_NONE) + return 1; + tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS, GFP_KERNEL); if (!tpacpi_leds) { @@ -5048,8 +5109,12 @@ static int __init led_init(struct ibm_init_struct *iibm) return -ENOMEM; } + useful_leds = tpacpi_check_quirks(led_useful_qtable, + ARRAY_SIZE(led_useful_qtable)); + for (i = 0; i < TPACPI_LED_NUMLEDS; i++) { - if (!tpacpi_is_led_restricted(i)) { + if (!tpacpi_is_led_restricted(i) && + test_bit(i, &useful_leds)) { rc = tpacpi_init_led(i); if (rc < 0) { led_exit(); @@ -5059,12 +5124,11 @@ static int __init led_init(struct ibm_init_struct *iibm) } #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS - if (led_supported != TPACPI_LED_NONE) - printk(TPACPI_NOTICE - "warning: userspace override of important " - "firmware LEDs is enabled\n"); + printk(TPACPI_NOTICE + "warning: userspace override of important " + "firmware LEDs is enabled\n"); #endif - return (led_supported != TPACPI_LED_NONE)? 0 : 1; + return 0; } #define str_led_status(s) \ @@ -5094,7 +5158,7 @@ static int led_read(char *p) } len += sprintf(p + len, "commands:\t" - " on, off, blink ( is 0-7)\n"); + " on, off, blink ( is 0-15)\n"); return len; } @@ -5109,7 +5173,7 @@ static int led_write(char *buf) return -ENODEV; while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) + if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15) return -EINVAL; if (strstr(cmd, "off")) { From 8bf3d4c535c2b9689c2979b281c24e9f59c2f4ad Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:09 -0300 Subject: [PATCH 18/53] thinkpad-acpi: silence bogus warning when ACPI video is disabled Make use of acpi_video_backlight_support() also in hotkey_init, to make sure this doesn't happen: thinkpad_acpi: This ThinkPad has standard ACPI backlight brightness control, supported by the ACPI video driver thinkpad_acpi: Disabling thinkpad-acpi brightness events by default... thinkpad_acpi: Standard ACPI backlight interface not available, thinkpad_acpi native brightness control enabled thinkpad_acpi: detected a 16-level brightness capable ThinkPad Note that this is purely cosmetic, there is absolutely _no_ change in behaviour. Those events are sometimes enabled at runtime by userspace, but the driver never enables them by itself unless someone messed with the default keymaps. Signed-off-by: Henrique de Moraes Holschuh Reported-by: Jochen Schulz Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 06c7c03c8f2f..5a22a064222c 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2655,7 +2655,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* update bright_acpimode... */ tpacpi_check_std_acpi_brightness_support(); - if (tp_features.bright_acpimode) { + if (tp_features.bright_acpimode && acpi_video_backlight_support()) { printk(TPACPI_INFO "This ThinkPad has standard ACPI backlight " "brightness control, supported by the ACPI " From 8b12b922ed5b9b6bfc345d3d6c6de56b2982af7f Mon Sep 17 00:00:00 2001 From: Alex Chiang Date: Thu, 14 May 2009 08:31:32 -0600 Subject: [PATCH 19/53] ACPI: acpi_device_register() should call device_register() There is no apparent reason for acpi_device_register() to manually register a new device in two steps (initialize then add). Just call device_register() directly. Signed-off-by: Alex Chiang Signed-off-by: Len Brown --- drivers/acpi/scan.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 8ff510b91d88..b8f5c005fbb5 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -530,11 +530,10 @@ static int acpi_device_register(struct acpi_device *device, if (device->parent) device->dev.parent = &parent->dev; device->dev.bus = &acpi_bus_type; - device_initialize(&device->dev); device->dev.release = &acpi_device_release; - result = device_add(&device->dev); + result = device_register(&device->dev); if(result) { - dev_err(&device->dev, "Error adding device\n"); + dev_err(&device->dev, "Error registering device\n"); goto end; } From 0c526d96a5bd86c70507b7d9372e6a26a1e3ea43 Mon Sep 17 00:00:00 2001 From: Alex Chiang Date: Thu, 14 May 2009 08:31:37 -0600 Subject: [PATCH 20/53] ACPI: clean up whitespace in drivers/acpi/scan.c Align labels in column 0, adjust spacing in 'if' statements, eliminate trailing and superfluous whitespaces. Signed-off-by: Alex Chiang Signed-off-by: Len Brown --- drivers/acpi/scan.c | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index b8f5c005fbb5..c40515e86187 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -198,12 +198,12 @@ acpi_device_path_show(struct device *dev, struct device_attribute *attr, char *b int result; result = acpi_get_name(acpi_dev->handle, ACPI_FULL_PATHNAME, &path); - if(result) + if (result) goto end; result = sprintf(buf, "%s\n", (char*)path.pointer); kfree(path.pointer); - end: +end: return result; } static DEVICE_ATTR(path, 0444, acpi_device_path_show, NULL); @@ -217,21 +217,21 @@ static int acpi_device_setup_files(struct acpi_device *dev) /* * Devices gotten from FADT don't have a "path" attribute */ - if(dev->handle) { + if (dev->handle) { result = device_create_file(&dev->dev, &dev_attr_path); - if(result) + if (result) goto end; } - if(dev->flags.hardware_id) { + if (dev->flags.hardware_id) { result = device_create_file(&dev->dev, &dev_attr_hid); - if(result) + if (result) goto end; } - if (dev->flags.hardware_id || dev->flags.compatible_ids){ + if (dev->flags.hardware_id || dev->flags.compatible_ids) { result = device_create_file(&dev->dev, &dev_attr_modalias); - if(result) + if (result) goto end; } @@ -242,7 +242,7 @@ static int acpi_device_setup_files(struct acpi_device *dev) status = acpi_get_handle(dev->handle, "_EJ0", &temp); if (ACPI_SUCCESS(status)) result = device_create_file(&dev->dev, &dev_attr_eject); - end: +end: return result; } @@ -262,9 +262,9 @@ static void acpi_device_remove_files(struct acpi_device *dev) if (dev->flags.hardware_id || dev->flags.compatible_ids) device_remove_file(&dev->dev, &dev_attr_modalias); - if(dev->flags.hardware_id) + if (dev->flags.hardware_id) device_remove_file(&dev->dev, &dev_attr_hid); - if(dev->handle) + if (dev->handle) device_remove_file(&dev->dev, &dev_attr_path); } /* -------------------------------------------------------------------------- @@ -512,7 +512,7 @@ static int acpi_device_register(struct acpi_device *device, break; } } - if(!found) { + if (!found) { acpi_device_bus_id = new_bus_id; strcpy(acpi_device_bus_id->bus_id, device->flags.hardware_id ? device->pnp.hardware_id : "device"); acpi_device_bus_id->instance_no = 0; @@ -532,19 +532,19 @@ static int acpi_device_register(struct acpi_device *device, device->dev.bus = &acpi_bus_type; device->dev.release = &acpi_device_release; result = device_register(&device->dev); - if(result) { + if (result) { dev_err(&device->dev, "Error registering device\n"); goto end; } result = acpi_device_setup_files(device); - if(result) + if (result) printk(KERN_ERR PREFIX "Error creating sysfs interface for device %s\n", dev_name(&device->dev)); device->removal_type = ACPI_BUS_REMOVAL_NORMAL; return 0; - end: +end: mutex_lock(&acpi_device_lock); if (device->parent) list_del(&device->node); @@ -576,7 +576,7 @@ static void acpi_device_unregister(struct acpi_device *device, int type) * @device: the device to add and initialize * @driver: driver for the device * - * Used to initialize a device via its device driver. Called whenever a + * Used to initialize a device via its device driver. Called whenever a * driver is bound to a device. Invokes the driver's add() ops. */ static int @@ -584,7 +584,6 @@ acpi_bus_driver_init(struct acpi_device *device, struct acpi_driver *driver) { int result = 0; - if (!device || !driver) return -EINVAL; @@ -801,7 +800,7 @@ static int acpi_bus_get_wakeup_device_flags(struct acpi_device *device) if (!acpi_match_device_ids(device, button_device_ids)) device->wakeup.flags.run_wake = 1; - end: +end: if (ACPI_FAILURE(status)) device->flags.wake_capable = 0; return 0; @@ -1069,7 +1068,7 @@ static void acpi_device_set_id(struct acpi_device *device, break; } - /* + /* * \_SB * ---- * Fix for the system root bus device -- the only root-level device. @@ -1319,7 +1318,7 @@ acpi_add_single_object(struct acpi_device **child, device->parent->ops.bind(device); } - end: +end: if (!result) *child = device; else { @@ -1463,7 +1462,6 @@ acpi_bus_add(struct acpi_device **child, return result; } - EXPORT_SYMBOL(acpi_bus_add); int acpi_bus_start(struct acpi_device *device) @@ -1483,7 +1481,6 @@ int acpi_bus_start(struct acpi_device *device) } return result; } - EXPORT_SYMBOL(acpi_bus_start); int acpi_bus_trim(struct acpi_device *start, int rmdevice) @@ -1541,7 +1538,6 @@ int acpi_bus_trim(struct acpi_device *start, int rmdevice) } EXPORT_SYMBOL_GPL(acpi_bus_trim); - static int acpi_bus_scan_fixed(struct acpi_device *root) { int result = 0; @@ -1609,6 +1605,6 @@ int __init acpi_scan_init(void) if (result) acpi_device_unregister(acpi_root, ACPI_BUS_REMOVAL_NORMAL); - Done: +Done: return result; } From ce597bb42aa84bc73db80509b7c37e7fbc0b75c4 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:09 +0000 Subject: [PATCH 21/53] ACPI: make acpi_pci_bind() static acpi_pci_root_add() explicitly assigns device->ops.bind, and later calls acpi_pci_bind_root(), which also does the same thing. We don't need to repeat ourselves; removing the explicit assignment allows us to make acpi_pci_bind() static. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 3 ++- drivers/acpi/pci_root.c | 2 -- include/acpi/acpi_drivers.h | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index bc46de3d967f..236765c6017b 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -44,6 +44,7 @@ struct acpi_pci_data { struct pci_dev *dev; }; +static int acpi_pci_bind(struct acpi_device *device); static int acpi_pci_unbind(struct acpi_device *device); static void acpi_pci_data_handler(acpi_handle handle, u32 function, @@ -108,7 +109,7 @@ acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id) EXPORT_SYMBOL(acpi_get_pci_id); -int acpi_pci_bind(struct acpi_device *device) +static int acpi_pci_bind(struct acpi_device *device) { int result = 0; acpi_status status; diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 196f97d00956..ca8dba3b40b9 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -386,8 +386,6 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); device->driver_data = root; - device->ops.bind = acpi_pci_bind; - /* * All supported architectures that use ACPI have support for * PCI domains, so we indicate this in _OSC support capabilities. diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 0352c8f0b05b..7a2ce53146aa 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -99,7 +99,6 @@ void acpi_pci_irq_del_prt(int segment, int bus); struct pci_bus; acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id); -int acpi_pci_bind(struct acpi_device *device); int acpi_pci_bind_root(struct acpi_device *device, struct acpi_pci_id *id, struct pci_bus *bus); From 275582031f9b3597a1b973f3ff617adfe639faa2 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:14 +0000 Subject: [PATCH 22/53] ACPI: Introduce acpi_is_root_bridge() Returns whether an ACPI CA node is a PCI root bridge or not. This API is generically useful, and shouldn't just be a hotplug function. The implementation becomes much simpler as well. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 24 ++++++++++++++++++ drivers/pci/hotplug/acpi_pcihp.c | 40 ++---------------------------- drivers/pci/hotplug/acpiphp_glue.c | 2 +- include/acpi/acpi_bus.h | 1 + include/linux/pci_hotplug.h | 1 - 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index ca8dba3b40b9..888cb9f5c5fb 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -142,6 +142,30 @@ acpi_handle acpi_get_pci_rootbridge_handle(unsigned int seg, unsigned int bus) EXPORT_SYMBOL_GPL(acpi_get_pci_rootbridge_handle); +/** + * acpi_is_root_bridge - determine whether an ACPI CA node is a PCI root bridge + * @handle - the ACPI CA node in question. + * + * Note: we could make this API take a struct acpi_device * instead, but + * for now, it's more convenient to operate on an acpi_handle. + */ +int acpi_is_root_bridge(acpi_handle handle) +{ + int ret; + struct acpi_device *device; + + ret = acpi_bus_get_device(handle, &device); + if (ret) + return 0; + + ret = acpi_match_device_ids(device, root_device_ids); + if (ret) + return 0; + else + return 1; +} +EXPORT_SYMBOL_GPL(acpi_is_root_bridge); + static acpi_status get_root_bridge_busnr_callback(struct acpi_resource *resource, void *data) { diff --git a/drivers/pci/hotplug/acpi_pcihp.c b/drivers/pci/hotplug/acpi_pcihp.c index fbc63d5e459f..eb159587d0bf 100644 --- a/drivers/pci/hotplug/acpi_pcihp.c +++ b/drivers/pci/hotplug/acpi_pcihp.c @@ -354,7 +354,7 @@ acpi_status acpi_get_hp_params_from_firmware(struct pci_bus *bus, status = acpi_run_hpp(handle, hpp); if (ACPI_SUCCESS(status)) break; - if (acpi_root_bridge(handle)) + if (acpi_is_root_bridge(handle)) break; status = acpi_get_parent(handle, &phandle); if (ACPI_FAILURE(status)) @@ -428,7 +428,7 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev, u32 flags) status = acpi_run_oshp(handle); if (ACPI_SUCCESS(status)) goto got_one; - if (acpi_root_bridge(handle)) + if (acpi_is_root_bridge(handle)) break; chandle = handle; status = acpi_get_parent(chandle, &handle); @@ -449,42 +449,6 @@ got_one: } EXPORT_SYMBOL(acpi_get_hp_hw_control_from_firmware); -/* acpi_root_bridge - check to see if this acpi object is a root bridge - * - * @handle - the acpi object in question. - */ -int acpi_root_bridge(acpi_handle handle) -{ - acpi_status status; - struct acpi_device_info *info; - struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; - int i; - - status = acpi_get_object_info(handle, &buffer); - if (ACPI_SUCCESS(status)) { - info = buffer.pointer; - if ((info->valid & ACPI_VALID_HID) && - !strcmp(PCI_ROOT_HID_STRING, - info->hardware_id.value)) { - kfree(buffer.pointer); - return 1; - } - if (info->valid & ACPI_VALID_CID) { - for (i=0; i < info->compatibility_id.count; i++) { - if (!strcmp(PCI_ROOT_HID_STRING, - info->compatibility_id.id[i].value)) { - kfree(buffer.pointer); - return 1; - } - } - } - kfree(buffer.pointer); - } - return 0; -} -EXPORT_SYMBOL_GPL(acpi_root_bridge); - - static int is_ejectable(acpi_handle handle) { acpi_status status; diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index 3a6064bce561..fc6636e3300b 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -1631,7 +1631,7 @@ find_root_bridges(acpi_handle handle, u32 lvl, void *context, void **rv) { int *count = (int *)context; - if (acpi_root_bridge(handle)) { + if (acpi_is_root_bridge(handle)) { acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, handle_hotplug_event_bridge, NULL); (*count)++; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index c34b11022908..96d593ee4859 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -369,6 +369,7 @@ struct device *acpi_get_physical_pci_device(acpi_handle); /* helper */ acpi_handle acpi_get_child(acpi_handle, acpi_integer); +int acpi_is_root_bridge(acpi_handle); acpi_handle acpi_get_pci_rootbridge_handle(unsigned int, unsigned int); #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle)) diff --git a/include/linux/pci_hotplug.h b/include/linux/pci_hotplug.h index 20998746518e..a3576ef9fc74 100644 --- a/include/linux/pci_hotplug.h +++ b/include/linux/pci_hotplug.h @@ -226,7 +226,6 @@ struct hotplug_params { extern acpi_status acpi_get_hp_params_from_firmware(struct pci_bus *bus, struct hotplug_params *hpp); int acpi_get_hp_hw_control_from_firmware(struct pci_dev *dev, u32 flags); -int acpi_root_bridge(acpi_handle handle); int acpi_pci_check_ejectable(struct pci_bus *pbus, acpi_handle handle); int acpi_pci_detect_ejectable(struct pci_bus *pbus); #endif From 2f7bbceb5b6aa938024bb4dad93c410fa59ed3b9 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:20 +0000 Subject: [PATCH 23/53] ACPI: Introduce acpi_get_pci_dev() Convert an ACPI CA handle to a struct pci_dev. Performing this lookup dynamically allows us to get rid of the ACPI-PCI binding code, which: - eliminates struct acpi_device vs struct pci_dev lifetime issues - lays more groundwork for eliminating .start from acpi_device_ops and thus simplifying ACPI drivers - whacks out a lot of code This change lays the groundwork for eliminating much of pci_bind.c. Although pci_root.c may not be the most logical place for this change, putting it here saves us from having to export acpi_pci_find_root. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 81 +++++++++++++++++++++++++++++++++++++ include/acpi/acpi_drivers.h | 1 + 2 files changed, 82 insertions(+) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 888cb9f5c5fb..e5099919e574 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -329,6 +329,87 @@ static struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle) return NULL; } +struct acpi_handle_node { + struct list_head node; + acpi_handle handle; +}; + +/** + * acpi_get_pci_dev - convert ACPI CA handle to struct pci_dev + * @handle: the handle in question + * + * Given an ACPI CA handle, the desired PCI device is located in the + * list of PCI devices. + * + * If the device is found, its reference count is increased and this + * function returns a pointer to its data structure. The caller must + * decrement the reference count by calling pci_dev_put(). + * If no device is found, %NULL is returned. + */ +struct pci_dev *acpi_get_pci_dev(acpi_handle handle) +{ + int dev, fn; + unsigned long long adr; + acpi_status status; + acpi_handle phandle; + struct pci_bus *pbus; + struct pci_dev *pdev = NULL; + struct acpi_handle_node *node, *tmp; + struct acpi_pci_root *root; + LIST_HEAD(device_list); + + /* + * Walk up the ACPI CA namespace until we reach a PCI root bridge. + */ + phandle = handle; + while (!acpi_is_root_bridge(phandle)) { + node = kzalloc(sizeof(struct acpi_handle_node), GFP_KERNEL); + if (!node) + goto out; + + INIT_LIST_HEAD(&node->node); + node->handle = phandle; + list_add(&node->node, &device_list); + + status = acpi_get_parent(phandle, &phandle); + if (ACPI_FAILURE(status)) + goto out; + } + + root = acpi_pci_find_root(phandle); + if (!root) + goto out; + + pbus = root->bus; + + /* + * Now, walk back down the PCI device tree until we return to our + * original handle. Assumes that everything between the PCI root + * bridge and the device we're looking for must be a P2P bridge. + */ + list_for_each_entry(node, &device_list, node) { + acpi_handle hnd = node->handle; + status = acpi_evaluate_integer(hnd, "_ADR", NULL, &adr); + if (ACPI_FAILURE(status)) + goto out; + dev = (adr >> 16) & 0xffff; + fn = adr & 0xffff; + + pdev = pci_get_slot(pbus, PCI_DEVFN(dev, fn)); + if (hnd == handle) + break; + + pbus = pdev->subordinate; + pci_dev_put(pdev); + } +out: + list_for_each_entry_safe(node, tmp, &device_list, node) + kfree(node); + + return pdev; +} +EXPORT_SYMBOL_GPL(acpi_get_pci_dev); + /** * acpi_pci_osc_control_set - commit requested control to Firmware * @handle: acpi_handle for the target ACPI object diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 7a2ce53146aa..dbe3989952ee 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -98,6 +98,7 @@ void acpi_pci_irq_del_prt(int segment, int bus); struct pci_bus; +struct pci_dev *acpi_get_pci_dev(acpi_handle); acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id); int acpi_pci_bind_root(struct acpi_device *device, struct acpi_pci_id *id, struct pci_bus *bus); From c22d7f5a389dad15de448b142f44e4000b3426f0 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:25 +0000 Subject: [PATCH 24/53] ACPI: rearrange acpi_pci_bind/acpi_pci_unbind in pci_bind.c This is a pure code movement patch that does $subject in order to make the following patch easier to read and review. No functional change. Signed-off-by: Alex Chiang Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 90 ++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index 236765c6017b..c283c29662a7 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -109,6 +109,51 @@ acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id) EXPORT_SYMBOL(acpi_get_pci_id); +static int acpi_pci_unbind(struct acpi_device *device) +{ + int result = 0; + acpi_status status; + struct acpi_pci_data *data; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + + if (!device || !device->parent) + return -EINVAL; + + status = acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unbinding PCI device [%s]...\n", + (char *) buffer.pointer)); + kfree(buffer.pointer); + + status = + acpi_get_data(device->handle, acpi_pci_data_handler, + (void **)&data); + if (ACPI_FAILURE(status)) { + result = -ENODEV; + goto end; + } + + status = acpi_detach_data(device->handle, acpi_pci_data_handler); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, + "Unable to detach data from device %s", + acpi_device_bid(device))); + result = -ENODEV; + goto end; + } + if (data->dev->subordinate) { + acpi_pci_irq_del_prt(data->id.segment, data->bus->number); + } + pci_dev_put(data->dev); + kfree(data); + + end: + return result; +} + static int acpi_pci_bind(struct acpi_device *device) { int result = 0; @@ -253,51 +298,6 @@ static int acpi_pci_bind(struct acpi_device *device) return result; } -static int acpi_pci_unbind(struct acpi_device *device) -{ - int result = 0; - acpi_status status; - struct acpi_pci_data *data; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - - - if (!device || !device->parent) - return -EINVAL; - - status = acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unbinding PCI device [%s]...\n", - (char *) buffer.pointer)); - kfree(buffer.pointer); - - status = - acpi_get_data(device->handle, acpi_pci_data_handler, - (void **)&data); - if (ACPI_FAILURE(status)) { - result = -ENODEV; - goto end; - } - - status = acpi_detach_data(device->handle, acpi_pci_data_handler); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Unable to detach data from device %s", - acpi_device_bid(device))); - result = -ENODEV; - goto end; - } - if (data->dev->subordinate) { - acpi_pci_irq_del_prt(data->id.segment, data->bus->number); - } - pci_dev_put(data->dev); - kfree(data); - - end: - return result; -} - int acpi_pci_bind_root(struct acpi_device *device, struct acpi_pci_id *id, struct pci_bus *bus) From 499650de6906722184b639989b47227a362b62f8 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:30 +0000 Subject: [PATCH 25/53] ACPI: eviscerate pci_bind.c Now that we can dynamically convert an ACPI CA handle to a struct pci_dev at runtime, there's no need to statically bind them during boot. acpi_pci_bind/unbind are vastly simplified, and are only used to evaluate _PRT methods on P2P bridges and non-bridge children. This patch also changes the time-space tradeoff ever so slightly. Looking up the ACPI-PCI binding is never in the performance path, and by eliminating this caching, we save 24 bytes for each _ADR device in the ACPI namespace. This patch lays further groundwork to eventually eliminate the acpi_driver_ops.bind callback. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 245 ++++++------------------------------ drivers/acpi/pci_root.c | 2 +- include/acpi/acpi_drivers.h | 3 +- 3 files changed, 39 insertions(+), 211 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index c283c29662a7..703d2a3e8012 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -24,12 +24,7 @@ */ #include -#include -#include #include -#include -#include -#include #include #include #include @@ -111,238 +106,72 @@ EXPORT_SYMBOL(acpi_get_pci_id); static int acpi_pci_unbind(struct acpi_device *device) { - int result = 0; - acpi_status status; - struct acpi_pci_data *data; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct pci_dev *dev; + dev = acpi_get_pci_dev(device->handle); + if (!dev) + return 0; - if (!device || !device->parent) - return -EINVAL; + if (dev->subordinate) + acpi_pci_irq_del_prt(pci_domain_nr(dev->bus), + dev->subordinate->number); - status = acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unbinding PCI device [%s]...\n", - (char *) buffer.pointer)); - kfree(buffer.pointer); - - status = - acpi_get_data(device->handle, acpi_pci_data_handler, - (void **)&data); - if (ACPI_FAILURE(status)) { - result = -ENODEV; - goto end; - } - - status = acpi_detach_data(device->handle, acpi_pci_data_handler); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Unable to detach data from device %s", - acpi_device_bid(device))); - result = -ENODEV; - goto end; - } - if (data->dev->subordinate) { - acpi_pci_irq_del_prt(data->id.segment, data->bus->number); - } - pci_dev_put(data->dev); - kfree(data); - - end: - return result; + pci_dev_put(dev); + return 0; } static int acpi_pci_bind(struct acpi_device *device) { - int result = 0; acpi_status status; - struct acpi_pci_data *data; - struct acpi_pci_data *pdata; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; acpi_handle handle; + unsigned char bus; + struct pci_dev *dev; - if (!device || !device->parent) - return -EINVAL; - - data = kzalloc(sizeof(struct acpi_pci_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - status = acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); - if (ACPI_FAILURE(status)) { - kfree(data); - return -ENODEV; - } - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Binding PCI device [%s]...\n", - (char *)buffer.pointer)); - - /* - * Segment & Bus - * ------------- - * These are obtained via the parent device's ACPI-PCI context. - */ - status = acpi_get_data(device->parent->handle, acpi_pci_data_handler, - (void **)&pdata); - if (ACPI_FAILURE(status) || !pdata || !pdata->bus) { - ACPI_EXCEPTION((AE_INFO, status, - "Invalid ACPI-PCI context for parent device %s", - acpi_device_bid(device->parent))); - result = -ENODEV; - goto end; - } - data->id.segment = pdata->id.segment; - data->id.bus = pdata->bus->number; + dev = acpi_get_pci_dev(device->handle); + if (!dev) + return 0; /* - * Device & Function - * ----------------- - * These are simply obtained from the device's _ADR method. Note - * that a value of zero is valid. + * Install the 'bind' function to facilitate callbacks for + * children of the P2P bridge. */ - data->id.device = device->pnp.bus_address >> 16; - data->id.function = device->pnp.bus_address & 0xFFFF; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "...to %04x:%02x:%02x.%d\n", - data->id.segment, data->id.bus, data->id.device, - data->id.function)); - - /* - * TBD: Support slot devices (e.g. function=0xFFFF). - */ - - /* - * Locate PCI Device - * ----------------- - * Locate matching device in PCI namespace. If it doesn't exist - * this typically means that the device isn't currently inserted - * (e.g. docking station, port replicator, etc.). - */ - data->dev = pci_get_slot(pdata->bus, - PCI_DEVFN(data->id.device, data->id.function)); - if (!data->dev) { - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "Device %04x:%02x:%02x.%d not present in PCI namespace\n", - data->id.segment, data->id.bus, - data->id.device, data->id.function)); - result = -ENODEV; - goto end; - } - if (!data->dev->bus) { - printk(KERN_ERR PREFIX - "Device %04x:%02x:%02x.%d has invalid 'bus' field\n", - data->id.segment, data->id.bus, - data->id.device, data->id.function); - result = -ENODEV; - goto end; - } - - /* - * PCI Bridge? - * ----------- - * If so, set the 'bus' field and install the 'bind' function to - * facilitate callbacks for all of its children. - */ - if (data->dev->subordinate) { + if (dev->subordinate) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device %04x:%02x:%02x.%d is a PCI bridge\n", - data->id.segment, data->id.bus, - data->id.device, data->id.function)); - data->bus = data->dev->subordinate; + pci_domain_nr(dev->bus), dev->bus->number, + PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn))); device->ops.bind = acpi_pci_bind; device->ops.unbind = acpi_pci_unbind; } /* - * Attach ACPI-PCI Context - * ----------------------- - * Thus binding the ACPI and PCI devices. - */ - status = acpi_attach_data(device->handle, acpi_pci_data_handler, data); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Unable to attach ACPI-PCI context to device %s", - acpi_device_bid(device))); - result = -ENODEV; - goto end; - } - - /* - * PCI Routing Table - * ----------------- - * Evaluate and parse _PRT, if exists. This code is independent of - * PCI bridges (above) to allow parsing of _PRT objects within the - * scope of non-bridge devices. Note that _PRTs within the scope of - * a PCI bridge assume the bridge's subordinate bus number. + * Evaluate and parse _PRT, if exists. This code allows parsing of + * _PRT objects within the scope of non-bridge devices. Note that + * _PRTs within the scope of a PCI bridge assume the bridge's + * subordinate bus number. * * TBD: Can _PRTs exist within the scope of non-bridge PCI devices? */ status = acpi_get_handle(device->handle, METHOD_NAME__PRT, &handle); - if (ACPI_SUCCESS(status)) { - if (data->bus) /* PCI-PCI bridge */ - acpi_pci_irq_add_prt(device->handle, data->id.segment, - data->bus->number); - else /* non-bridge PCI device */ - acpi_pci_irq_add_prt(device->handle, data->id.segment, - data->id.bus); - } + if (ACPI_FAILURE(status)) + goto out; - end: - kfree(buffer.pointer); - if (result) { - pci_dev_put(data->dev); - kfree(data); - } - return result; + if (dev->subordinate) + bus = dev->subordinate->number; + else + bus = dev->bus->number; + + acpi_pci_irq_add_prt(device->handle, pci_domain_nr(dev->bus), bus); + +out: + pci_dev_put(dev); + return 0; } -int -acpi_pci_bind_root(struct acpi_device *device, - struct acpi_pci_id *id, struct pci_bus *bus) +int acpi_pci_bind_root(struct acpi_device *device) { - int result = 0; - acpi_status status; - struct acpi_pci_data *data = NULL; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - - if (!device || !id || !bus) { - return -EINVAL; - } - - data = kzalloc(sizeof(struct acpi_pci_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->id = *id; - data->bus = bus; device->ops.bind = acpi_pci_bind; device->ops.unbind = acpi_pci_unbind; - status = acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); - if (ACPI_FAILURE(status)) { - kfree (data); - return -ENODEV; - } - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Binding PCI root bridge [%s] to " - "%04x:%02x\n", (char *)buffer.pointer, - id->segment, id->bus)); - - status = acpi_attach_data(device->handle, acpi_pci_data_handler, data); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Unable to attach ACPI-PCI context to device %s", - (char *)buffer.pointer)); - result = -ENODEV; - goto end; - } - - end: - kfree(buffer.pointer); - if (result != 0) - kfree(data); - - return result; + return 0; } diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index e5099919e574..f23fcc5c9674 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -603,7 +603,7 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) * ----------------------- * Thus binding the ACPI and PCI devices. */ - result = acpi_pci_bind_root(device, &root->id, root->bus); + result = acpi_pci_bind_root(device); if (result) goto end; diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index dbe3989952ee..2740a2894837 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -100,8 +100,7 @@ struct pci_bus; struct pci_dev *acpi_get_pci_dev(acpi_handle); acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id); -int acpi_pci_bind_root(struct acpi_device *device, struct acpi_pci_id *id, - struct pci_bus *bus); +int acpi_pci_bind_root(struct acpi_device *device); /* Arch-defined function to add a bus to the system */ From 859a3f86ca83346f4097e956d0b27d96aa7a1cff Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:35 +0000 Subject: [PATCH 26/53] ACPI: simplify acpi_pci_irq_add_prt() API A PCI domain cannot change as you descend down subordinate buses, which makes the 'segment' argument to acpi_pci_irq_add_prt() useless. Change the interface to take a struct pci_bus *, from whence we can derive the bus number and segment. Reducing the number of arguments makes life simpler for callers. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 8 ++++---- drivers/acpi/pci_irq.c | 10 +++++----- drivers/acpi/pci_root.c | 3 +-- include/acpi/acpi_drivers.h | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index 703d2a3e8012..6eb58ef366ef 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -124,7 +124,7 @@ static int acpi_pci_bind(struct acpi_device *device) { acpi_status status; acpi_handle handle; - unsigned char bus; + struct pci_bus *bus; struct pci_dev *dev; dev = acpi_get_pci_dev(device->handle); @@ -157,11 +157,11 @@ static int acpi_pci_bind(struct acpi_device *device) goto out; if (dev->subordinate) - bus = dev->subordinate->number; + bus = dev->subordinate; else - bus = dev->bus->number; + bus = dev->bus; - acpi_pci_irq_add_prt(device->handle, pci_domain_nr(dev->bus), bus); + acpi_pci_irq_add_prt(device->handle, bus); out: pci_dev_put(dev); diff --git a/drivers/acpi/pci_irq.c b/drivers/acpi/pci_irq.c index 51b9f8280f88..3ed944cefb36 100644 --- a/drivers/acpi/pci_irq.c +++ b/drivers/acpi/pci_irq.c @@ -182,7 +182,7 @@ static void do_prt_fixups(struct acpi_prt_entry *entry, } } -static int acpi_pci_irq_add_entry(acpi_handle handle, int segment, int bus, +static int acpi_pci_irq_add_entry(acpi_handle handle, struct pci_bus *bus, struct acpi_pci_routing_table *prt) { struct acpi_prt_entry *entry; @@ -196,8 +196,8 @@ static int acpi_pci_irq_add_entry(acpi_handle handle, int segment, int bus, * 1=INTA, 2=INTB. We use the PCI encoding throughout, so convert * it here. */ - entry->id.segment = segment; - entry->id.bus = bus; + entry->id.segment = pci_domain_nr(bus); + entry->id.bus = bus->number; entry->id.device = (prt->address >> 16) & 0xFFFF; entry->pin = prt->pin + 1; @@ -242,7 +242,7 @@ static int acpi_pci_irq_add_entry(acpi_handle handle, int segment, int bus, return 0; } -int acpi_pci_irq_add_prt(acpi_handle handle, int segment, int bus) +int acpi_pci_irq_add_prt(acpi_handle handle, struct pci_bus *bus) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -271,7 +271,7 @@ int acpi_pci_irq_add_prt(acpi_handle handle, int segment, int bus) entry = buffer.pointer; while (entry && (entry->length > 0)) { - acpi_pci_irq_add_entry(handle, segment, bus, entry); + acpi_pci_irq_add_entry(handle, bus, entry); entry = (struct acpi_pci_routing_table *) ((unsigned long)entry + entry->length); } diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index f23fcc5c9674..f341b0756c9e 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -614,8 +614,7 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) */ status = acpi_get_handle(device->handle, METHOD_NAME__PRT, &handle); if (ACPI_SUCCESS(status)) - result = acpi_pci_irq_add_prt(device->handle, root->id.segment, - root->id.bus); + result = acpi_pci_irq_add_prt(device->handle, root->bus); /* * Scan and bind all _ADR-Based Devices diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 2740a2894837..686a960bde39 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -91,7 +91,7 @@ int acpi_pci_link_free_irq(acpi_handle handle); /* ACPI PCI Interrupt Routing (pci_irq.c) */ -int acpi_pci_irq_add_prt(acpi_handle handle, int segment, int bus); +int acpi_pci_irq_add_prt(acpi_handle handle, struct pci_bus *bus); void acpi_pci_irq_del_prt(int segment, int bus); /* ACPI PCI Device Binding (pci_bind.c) */ From d9efae3688addb15994c9ad9761dada6f988bc14 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:40 +0000 Subject: [PATCH 27/53] ACPI: simplify acpi_pci_irq_del_prt() API There is no need to pass a segment/bus tuple to this API, as the callsite always has a struct pci_bus. We can derive segment/bus from the struct pci_bus, so let's take this opportunit to simplify the API and make life easier for the callers. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 3 +-- drivers/acpi/pci_irq.c | 7 ++++--- include/acpi/acpi_drivers.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index 6eb58ef366ef..62cb383222f8 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -113,8 +113,7 @@ static int acpi_pci_unbind(struct acpi_device *device) return 0; if (dev->subordinate) - acpi_pci_irq_del_prt(pci_domain_nr(dev->bus), - dev->subordinate->number); + acpi_pci_irq_del_prt(dev->subordinate); pci_dev_put(dev); return 0; diff --git a/drivers/acpi/pci_irq.c b/drivers/acpi/pci_irq.c index 3ed944cefb36..ef9509e33191 100644 --- a/drivers/acpi/pci_irq.c +++ b/drivers/acpi/pci_irq.c @@ -280,16 +280,17 @@ int acpi_pci_irq_add_prt(acpi_handle handle, struct pci_bus *bus) return 0; } -void acpi_pci_irq_del_prt(int segment, int bus) +void acpi_pci_irq_del_prt(struct pci_bus *bus) { struct acpi_prt_entry *entry, *tmp; printk(KERN_DEBUG "ACPI: Delete PCI Interrupt Routing Table for %04x:%02x\n", - segment, bus); + pci_domain_nr(bus), bus->number); spin_lock(&acpi_prt_lock); list_for_each_entry_safe(entry, tmp, &acpi_prt_list, list) { - if (segment == entry->id.segment && bus == entry->id.bus) { + if (pci_domain_nr(bus) == entry->id.segment + && bus->number == entry->id.bus) { list_del(&entry->list); kfree(entry); } diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 686a960bde39..98ebaaedc07f 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -92,7 +92,7 @@ int acpi_pci_link_free_irq(acpi_handle handle); /* ACPI PCI Interrupt Routing (pci_irq.c) */ int acpi_pci_irq_add_prt(acpi_handle handle, struct pci_bus *bus); -void acpi_pci_irq_del_prt(int segment, int bus); +void acpi_pci_irq_del_prt(struct pci_bus *bus); /* ACPI PCI Device Binding (pci_bind.c) */ From 97719a8726fe8d3ea12a85fbf4f514a915ba30ec Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:45 +0000 Subject: [PATCH 28/53] ACPI: acpi_pci_unbind should clean up properly after acpi_pci_bind In acpi_pci_bind, we set device->ops.bind and device->ops.unbind, but never clear them out. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index 62cb383222f8..a205769f1d00 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -109,12 +109,15 @@ static int acpi_pci_unbind(struct acpi_device *device) struct pci_dev *dev; dev = acpi_get_pci_dev(device->handle); - if (!dev) - return 0; + if (!dev || !dev->subordinate) + goto out; - if (dev->subordinate) - acpi_pci_irq_del_prt(dev->subordinate); + acpi_pci_irq_del_prt(dev->subordinate); + device->ops.bind = NULL; + device->ops.unbind = NULL; + +out: pci_dev_put(dev); return 0; } From d6aa484c1c0cd39ff3a42f4050b55d2a5b285ef5 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:50 +0000 Subject: [PATCH 29/53] PCI Hotplug: acpiphp: convert to acpi_get_pci_dev Now that acpi_get_pci_dev is available, let's use it instead of acpi_get_pci_id. Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Acked-by: Jesse Barnes Signed-off-by: Len Brown --- drivers/pci/hotplug/acpiphp_glue.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index fc6636e3300b..0cb0f830a993 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -678,18 +678,9 @@ static void remove_bridge(acpi_handle handle) static struct pci_dev * get_apic_pci_info(acpi_handle handle) { - struct acpi_pci_id id; - struct pci_bus *bus; struct pci_dev *dev; - if (ACPI_FAILURE(acpi_get_pci_id(handle, &id))) - return NULL; - - bus = pci_find_bus(id.segment, id.bus); - if (!bus) - return NULL; - - dev = pci_get_slot(bus, PCI_DEVFN(id.device, id.function)); + dev = acpi_get_pci_dev(handle); if (!dev) return NULL; @@ -1396,19 +1387,16 @@ static void acpiphp_sanitize_bus(struct pci_bus *bus) /* Program resources in newly inserted bridge */ static int acpiphp_configure_bridge (acpi_handle handle) { - struct acpi_pci_id pci_id; + struct pci_dev *dev; struct pci_bus *bus; - if (ACPI_FAILURE(acpi_get_pci_id(handle, &pci_id))) { + dev = acpi_get_pci_dev(handle); + if (!dev) { err("cannot get PCI domain and bus number for bridge\n"); return -EINVAL; } - bus = pci_find_bus(pci_id.segment, pci_id.bus); - if (!bus) { - err("cannot find bus %d:%d\n", - pci_id.segment, pci_id.bus); - return -EINVAL; - } + + bus = dev->bus; pci_bus_size_bridges(bus); pci_bus_assign_resources(bus); @@ -1416,6 +1404,7 @@ static int acpiphp_configure_bridge (acpi_handle handle) acpiphp_set_hpp_values(handle, bus); pci_enable_bridges(bus); acpiphp_configure_ioapics(handle); + pci_dev_put(dev); return 0; } From 80ffdedf6020a77adcd06c01cfe6c488312b28f8 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:55:55 +0000 Subject: [PATCH 30/53] ACPI: kill acpi_get_pci_id acpi_get_pci_dev() is better, and all callers have been converted, so eliminate acpi_get_pci_id(). Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_bind.c | 71 ------------------------------------- include/acpi/acpi_drivers.h | 1 - 2 files changed, 72 deletions(-) diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index a205769f1d00..a5a77b78a723 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -33,77 +33,6 @@ #define _COMPONENT ACPI_PCI_COMPONENT ACPI_MODULE_NAME("pci_bind"); -struct acpi_pci_data { - struct acpi_pci_id id; - struct pci_bus *bus; - struct pci_dev *dev; -}; - -static int acpi_pci_bind(struct acpi_device *device); -static int acpi_pci_unbind(struct acpi_device *device); - -static void acpi_pci_data_handler(acpi_handle handle, u32 function, - void *context) -{ - - /* TBD: Anything we need to do here? */ - - return; -} - -/** - * acpi_get_pci_id - * ------------------ - * This function is used by the ACPI Interpreter (a.k.a. Core Subsystem) - * to resolve PCI information for ACPI-PCI devices defined in the namespace. - * This typically occurs when resolving PCI operation region information. - */ -acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id) -{ - int result = 0; - acpi_status status = AE_OK; - struct acpi_device *device = NULL; - struct acpi_pci_data *data = NULL; - - - if (!id) - return AE_BAD_PARAMETER; - - result = acpi_bus_get_device(handle, &device); - if (result) { - printk(KERN_ERR PREFIX - "Invalid ACPI Bus context for device %s\n", - acpi_device_bid(device)); - return AE_NOT_EXIST; - } - - status = acpi_get_data(handle, acpi_pci_data_handler, (void **)&data); - if (ACPI_FAILURE(status) || !data) { - ACPI_EXCEPTION((AE_INFO, status, - "Invalid ACPI-PCI context for device %s", - acpi_device_bid(device))); - return status; - } - - *id = data->id; - - /* - id->segment = data->id.segment; - id->bus = data->id.bus; - id->device = data->id.device; - id->function = data->id.function; - */ - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "Device %s has PCI address %04x:%02x:%02x.%d\n", - acpi_device_bid(device), id->segment, id->bus, - id->device, id->function)); - - return AE_OK; -} - -EXPORT_SYMBOL(acpi_get_pci_id); - static int acpi_pci_unbind(struct acpi_device *device) { struct pci_dev *dev; diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 98ebaaedc07f..b69285773177 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -99,7 +99,6 @@ void acpi_pci_irq_del_prt(struct pci_bus *bus); struct pci_bus; struct pci_dev *acpi_get_pci_dev(acpi_handle); -acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id); int acpi_pci_bind_root(struct acpi_device *device); /* Arch-defined function to add a bus to the system */ From 1e4cffe78e1decd937c7b78410eec87da6b87954 Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:56:00 +0000 Subject: [PATCH 31/53] ACPI: video: convert to acpi_get_pci_dev Now that acpi_get_pci_dev is available, let's use it instead of acpi_get_physical_pci_device() Cc: Thomas Renninger Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/video.c | 6 +++--- drivers/acpi/video_detect.c | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 1bdfb37377e3..5adbf9361e60 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -1054,15 +1054,15 @@ static void acpi_video_bus_find_cap(struct acpi_video_bus *video) static int acpi_video_bus_check(struct acpi_video_bus *video) { acpi_status status = -ENOENT; - struct device *dev; + struct pci_dev *dev; if (!video) return -EINVAL; - dev = acpi_get_physical_pci_device(video->device->handle); + dev = acpi_get_pci_dev(video->device->handle); if (!dev) return -ENODEV; - put_device(dev); + pci_dev_put(dev); /* Since there is no HID, CID and so on for VGA driver, we have * to check well known required nodes. diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 09737275e25f..7cd2b63435ea 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -10,7 +10,7 @@ * assinged * * After PCI devices are glued with ACPI devices - * acpi_get_physical_pci_device() can be called to identify ACPI graphics + * acpi_get_pci_dev() can be called to identify ACPI graphics * devices for which a real graphics card is plugged in * * Now acpi_video_get_capabilities() can be called to check which @@ -36,6 +36,7 @@ #include #include +#include ACPI_MODULE_NAME("video"); #define _COMPONENT ACPI_VIDEO_COMPONENT @@ -109,7 +110,7 @@ static acpi_status find_video(acpi_handle handle, u32 lvl, void *context, void **rv) { long *cap = context; - struct device *dev; + struct pci_dev *dev; struct acpi_device *acpi_dev; const struct acpi_device_id video_ids[] = { @@ -120,10 +121,10 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv) return AE_OK; if (!acpi_match_device_ids(acpi_dev, video_ids)) { - dev = acpi_get_physical_pci_device(handle); + dev = acpi_get_pci_dev(handle); if (!dev) return AE_OK; - put_device(dev); + pci_dev_put(dev); *cap |= acpi_is_video_device(acpi_dev); } return AE_OK; From 7fe2a6c275a5bcec52fb3ef643daaf8265b7af0d Mon Sep 17 00:00:00 2001 From: Alexander Chiang Date: Wed, 10 Jun 2009 19:56:05 +0000 Subject: [PATCH 32/53] ACPI: kill acpi_get_physical_pci_device() acpi_get_pci_dev() is (hopefully) better, and all callers have been converted, so let's get rid of this duplicated functionality. Cc: Thomas Renninger Signed-off-by: Alex Chiang Acked-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/glue.c | 40 ---------------------------------------- include/acpi/acpi_bus.h | 1 - 2 files changed, 41 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 8bd2c2a6884d..a8a5c29958c8 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -140,46 +140,6 @@ struct device *acpi_get_physical_device(acpi_handle handle) EXPORT_SYMBOL(acpi_get_physical_device); -/* ToDo: When a PCI bridge is found, return the PCI device behind the bridge - * This should work in general, but did not on a Lenovo T61 for the - * graphics card. But this must be fixed when the PCI device is - * bound and the kernel device struct is attached to the acpi device - * Note: A success call will increase reference count by one - * Do call put_device(dev) on the returned device then - */ -struct device *acpi_get_physical_pci_device(acpi_handle handle) -{ - struct device *dev; - long long device_id; - acpi_status status; - - status = - acpi_evaluate_integer(handle, "_ADR", NULL, &device_id); - - if (ACPI_FAILURE(status)) - return NULL; - - /* We need to attempt to determine whether the _ADR refers to a - PCI device or not. There's no terribly good way to do this, - so the best we can hope for is to assume that there'll never - be a device in the host bridge */ - if (device_id >= 0x10000) { - /* It looks like a PCI device. Does it exist? */ - dev = acpi_get_physical_device(handle); - } else { - /* It doesn't look like a PCI device. Does its parent - exist? */ - acpi_handle phandle; - if (acpi_get_parent(handle, &phandle)) - return NULL; - dev = acpi_get_physical_device(phandle); - } - if (!dev) - return NULL; - return dev; -} -EXPORT_SYMBOL(acpi_get_physical_pci_device); - static int acpi_bind_one(struct device *dev, acpi_handle handle) { struct acpi_device *acpi_dev; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 96d593ee4859..700b263c898a 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -365,7 +365,6 @@ struct acpi_bus_type { int register_acpi_bus_type(struct acpi_bus_type *); int unregister_acpi_bus_type(struct acpi_bus_type *); struct device *acpi_get_physical_device(acpi_handle); -struct device *acpi_get_physical_pci_device(acpi_handle); /* helper */ acpi_handle acpi_get_child(acpi_handle, acpi_integer); From 75d71c40dde5a9474c09ee291df22d50a1215bef Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 10 Jun 2009 19:40:46 +0000 Subject: [PATCH 33/53] dell-wmi: mask off upper bytes of event response In debugging with some future machines that actually contain BIOS level support for dell-wmi, I've determined that the upper half of the data that comes back from wmi_get_event_data may sometimes contain extra information that isn't currently relevant when pulling scan codes out of the data. This causes dell-wmi to improperly respond to these events. Signed-off-by: Mario Limonciello Signed-off-by: Matthew Garrett Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/platform/x86/dell-wmi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 2fab94162147..8a0d39ee9217 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -122,7 +122,12 @@ static void dell_wmi_notify(u32 value, void *context) if (obj && obj->type == ACPI_TYPE_BUFFER) { int *buffer = (int *)obj->buffer.pointer; - key = dell_wmi_get_entry_by_scancode(buffer[1]); + /* + * The upper bytes of the event may contain + * additional information, so mask them off for the + * scancode lookup + */ + key = dell_wmi_get_entry_by_scancode(buffer[1] & 0xFFFF); if (key) { input_report_key(dell_wmi_input_dev, key->keycode, 1); input_sync(dell_wmi_input_dev); From 5cab0098171712a9fd51399b06181c8dfdebe9c9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 10 Jun 2009 19:40:47 +0000 Subject: [PATCH 34/53] dell-wmi: add additional keyboard events Upcoming Dell hardware will send more keyboard events via WMI. Add support for them. Signed-off-by: Mario Limonciello Signed-off-by: Matthew Garrett Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/platform/x86/dell-wmi.c | 45 ++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 8a0d39ee9217..9f345dc2d1b1 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -46,10 +46,53 @@ struct key_entry { u16 keycode; }; -enum { KE_KEY, KE_SW, KE_END }; +enum { KE_KEY, KE_SW, KE_IGNORE, KE_END }; + +/* + * Certain keys are flagged as KE_IGNORE. All of these are either + * notifications (rather than requests for change) or are also sent + * via the keyboard controller so should not be sent again. + */ static struct key_entry dell_wmi_keymap[] = { {KE_KEY, 0xe045, KEY_PROG1}, + {KE_KEY, 0xe009, KEY_EJECTCD}, + + /* These also contain the brightness level at offset 6 */ + {KE_KEY, 0xe006, KEY_BRIGHTNESSUP}, + {KE_KEY, 0xe005, KEY_BRIGHTNESSDOWN}, + + /* Battery health status button */ + {KE_KEY, 0xe007, KEY_BATTERY}, + + /* This is actually for all radios. Although physically a + * switch, the notification does not provide an indication of + * state and so it should be reported as a key */ + {KE_KEY, 0xe008, KEY_WLAN}, + + /* The next device is at offset 6, the active devices are at + offset 8 and the attached devices at offset 10 */ + {KE_KEY, 0xe00b, KEY_DISPLAYTOGGLE}, + + {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, + + /* BIOS error detected */ + {KE_IGNORE, 0xe00d, KEY_RESERVED}, + + /* Wifi Catcher */ + {KE_KEY, 0xe011, KEY_PROG2}, + + /* Ambient light sensor toggle */ + {KE_IGNORE, 0xe013, KEY_RESERVED}, + + {KE_IGNORE, 0xe020, KEY_MUTE}, + {KE_IGNORE, 0xe02e, KEY_VOLUMEDOWN}, + {KE_IGNORE, 0xe030, KEY_VOLUMEUP}, + {KE_IGNORE, 0xe033, KEY_KBDILLUMUP}, + {KE_IGNORE, 0xe034, KEY_KBDILLUMDOWN}, + {KE_IGNORE, 0xe03a, KEY_CAPSLOCK}, + {KE_IGNORE, 0xe045, KEY_NUMLOCK}, + {KE_IGNORE, 0xe046, KEY_SCROLLLOCK}, {KE_END, 0} }; From db18b040af6571a7eeed9e1adc2e92c9c87e4b1a Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Wed, 10 Jun 2009 19:40:48 +0000 Subject: [PATCH 35/53] dell-wmi: don't generate errors on empty messages There's no point in generating kernel messages if we didn't receive a parsable keyboard event - only do so if there appeared to be a scancode. Signed-off-by: Matthew Garrett Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/platform/x86/dell-wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 9f345dc2d1b1..0f900cc9fa7a 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -176,9 +176,9 @@ static void dell_wmi_notify(u32 value, void *context) input_sync(dell_wmi_input_dev); input_report_key(dell_wmi_input_dev, key->keycode, 0); input_sync(dell_wmi_input_dev); - } else + } else if (buffer[1] & 0xFFFF) printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", - buffer[1]); + buffer[1] & 0xFFFF); } } From 871043bc463e7d191e7b5b00436a8852921dd833 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Mon, 1 Jun 2009 15:25:45 +0100 Subject: [PATCH 36/53] hp-wmi: Add support for reporting tablet state HP tablets send a WMI event when a tablet state change occurs, but use the same method as is used for reporting docking and undocking. The same query is used to obtain the state of the hardware. Bit 0 indicates the docking state, while bit 2 indicates the tablet state. This patch breaks these out and sends separate input events for tablet and dock state changes. An additional sysfs file is added to report the tablet state. Signed-off-by: Matthew Garrett Signed-off-by: Len Brown --- drivers/platform/x86/hp-wmi.c | 87 ++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 50d9019de2be..46a7a6e8ed91 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -47,7 +47,7 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); #define HPWMI_DISPLAY_QUERY 0x1 #define HPWMI_HDDTEMP_QUERY 0x2 #define HPWMI_ALS_QUERY 0x3 -#define HPWMI_DOCK_QUERY 0x4 +#define HPWMI_HARDWARE_QUERY 0x4 #define HPWMI_WIRELESS_QUERY 0x5 #define HPWMI_HOTKEY_QUERY 0xc @@ -75,10 +75,9 @@ struct key_entry { u16 keycode; }; -enum { KE_KEY, KE_SW, KE_END }; +enum { KE_KEY, KE_END }; static struct key_entry hp_wmi_keymap[] = { - {KE_SW, 0x01, SW_DOCK}, {KE_KEY, 0x02, KEY_BRIGHTNESSUP}, {KE_KEY, 0x03, KEY_BRIGHTNESSDOWN}, {KE_KEY, 0x20e6, KEY_PROG1}, @@ -151,7 +150,22 @@ static int hp_wmi_als_state(void) static int hp_wmi_dock_state(void) { - return hp_wmi_perform_query(HPWMI_DOCK_QUERY, 0, 0); + int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); + + if (ret < 0) + return ret; + + return ret & 0x1; +} + +static int hp_wmi_tablet_state(void) +{ + int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); + + if (ret < 0) + return ret; + + return (ret & 0x4) ? 1 : 0; } static int hp_wmi_wifi_set(void *data, enum rfkill_state state) @@ -244,6 +258,15 @@ static ssize_t show_dock(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%d\n", value); } +static ssize_t show_tablet(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_tablet_state(); + if (value < 0) + return -EINVAL; + return sprintf(buf, "%d\n", value); +} + static ssize_t set_als(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -256,6 +279,7 @@ static DEVICE_ATTR(display, S_IRUGO, show_display, NULL); static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL); static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als); static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL); +static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL); static struct key_entry *hp_wmi_get_entry_by_scancode(int code) { @@ -338,13 +362,13 @@ static void hp_wmi_notify(u32 value, void *context) key->keycode, 0); input_sync(hp_wmi_input_dev); break; - case KE_SW: - input_report_switch(hp_wmi_input_dev, - key->keycode, - hp_wmi_dock_state()); - input_sync(hp_wmi_input_dev); - break; } + } else if (eventcode == 0x1) { + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_dock_state()); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_tablet_state()); + input_sync(hp_wmi_input_dev); } else if (eventcode == 0x5) { if (wifi_rfkill) rfkill_force_state(wifi_rfkill, @@ -381,18 +405,19 @@ static int __init hp_wmi_input_setup(void) set_bit(EV_KEY, hp_wmi_input_dev->evbit); set_bit(key->keycode, hp_wmi_input_dev->keybit); break; - case KE_SW: - set_bit(EV_SW, hp_wmi_input_dev->evbit); - set_bit(key->keycode, hp_wmi_input_dev->swbit); - - /* Set initial dock state */ - input_report_switch(hp_wmi_input_dev, key->keycode, - hp_wmi_dock_state()); - input_sync(hp_wmi_input_dev); - break; } } + set_bit(EV_SW, hp_wmi_input_dev->evbit); + set_bit(SW_DOCK, hp_wmi_input_dev->swbit); + set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + + /* Set initial hardware state */ + input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_tablet_state()); + input_sync(hp_wmi_input_dev); + err = input_register_device(hp_wmi_input_dev); if (err) { @@ -409,6 +434,7 @@ static void cleanup_sysfs(struct platform_device *device) device_remove_file(&device->dev, &dev_attr_hddtemp); device_remove_file(&device->dev, &dev_attr_als); device_remove_file(&device->dev, &dev_attr_dock); + device_remove_file(&device->dev, &dev_attr_tablet); } static int __init hp_wmi_bios_setup(struct platform_device *device) @@ -426,6 +452,9 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) if (err) goto add_sysfs_error; err = device_create_file(&device->dev, &dev_attr_dock); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_tablet); if (err) goto add_sysfs_error; @@ -491,23 +520,17 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device) static int hp_wmi_resume_handler(struct platform_device *device) { - struct key_entry *key; - /* - * Docking state may have changed while suspended, so trigger - * an input event for the current state. As this is a switch, + * Hardware state may have changed while suspended, so trigger + * input events for the current state. As this is a switch, * the input layer will only actually pass it on if the state * changed. */ - for (key = hp_wmi_keymap; key->type != KE_END; key++) { - switch (key->type) { - case KE_SW: - input_report_switch(hp_wmi_input_dev, key->keycode, - hp_wmi_dock_state()); - input_sync(hp_wmi_input_dev); - break; - } - } + + input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_tablet_state()); + input_sync(hp_wmi_input_dev); return 0; } From 7e275cc4e8e20f82740bf40ae2f5695e9e35ff09 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Fri, 15 May 2009 02:08:44 -0400 Subject: [PATCH 37/53] ACPI: idle: rename lapic timer workaround routines cosmetic only. The lapic_timer workaround routines are specific to the lapic_timer, and are not acpi-generic. old: acpi_timer_check_state() acpi_propagate_timer_broadcast() acpi_state_timer_broadcast() new: lapic_timer_check_state() lapic_timer_propagate_broadcast() lapic_timer_state_broadcast() also, simplify the code in acpi_processor_power_verify() so that lapic_timer_check_state() is simply called from one place for all valid C-states, including C1. Signed-off-by: Len Brown --- drivers/acpi/processor_idle.c | 40 ++++++++++++++++------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 10a2d913635a..1f60ccbd4c39 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -139,7 +139,7 @@ static void acpi_safe_halt(void) * are affected too. We pick the most conservative approach: we assume * that the local APIC stops in both C2 and C3. */ -static void acpi_timer_check_state(int state, struct acpi_processor *pr, +static void lapic_timer_check_state(int state, struct acpi_processor *pr, struct acpi_processor_cx *cx) { struct acpi_processor_power *pwr = &pr->power; @@ -162,7 +162,7 @@ static void acpi_timer_check_state(int state, struct acpi_processor *pr, pr->power.timer_broadcast_on_state = state; } -static void acpi_propagate_timer_broadcast(struct acpi_processor *pr) +static void lapic_timer_propagate_broadcast(struct acpi_processor *pr) { unsigned long reason; @@ -173,7 +173,7 @@ static void acpi_propagate_timer_broadcast(struct acpi_processor *pr) } /* Power(C) State timer broadcast control */ -static void acpi_state_timer_broadcast(struct acpi_processor *pr, +static void lapic_timer_state_broadcast(struct acpi_processor *pr, struct acpi_processor_cx *cx, int broadcast) { @@ -190,10 +190,10 @@ static void acpi_state_timer_broadcast(struct acpi_processor *pr, #else -static void acpi_timer_check_state(int state, struct acpi_processor *pr, +static void lapic_timer_check_state(int state, struct acpi_processor *pr, struct acpi_processor_cx *cstate) { } -static void acpi_propagate_timer_broadcast(struct acpi_processor *pr) { } -static void acpi_state_timer_broadcast(struct acpi_processor *pr, +static void lapic_timer_propagate_broadcast(struct acpi_processor *pr) { } +static void lapic_timer_state_broadcast(struct acpi_processor *pr, struct acpi_processor_cx *cx, int broadcast) { @@ -614,29 +614,25 @@ static int acpi_processor_power_verify(struct acpi_processor *pr) switch (cx->type) { case ACPI_STATE_C1: cx->valid = 1; - acpi_timer_check_state(i, pr, cx); break; case ACPI_STATE_C2: acpi_processor_power_verify_c2(cx); - if (cx->valid) - acpi_timer_check_state(i, pr, cx); break; case ACPI_STATE_C3: acpi_processor_power_verify_c3(pr, cx); - if (cx->valid) - acpi_timer_check_state(i, pr, cx); break; } - if (cx->valid) - tsc_check_state(cx->type); + if (!cx->valid) + continue; - if (cx->valid) - working++; + lapic_timer_check_state(i, pr, cx); + tsc_check_state(cx->type); + working++; } - acpi_propagate_timer_broadcast(pr); + lapic_timer_propagate_broadcast(pr); return (working); } @@ -839,7 +835,7 @@ static int acpi_idle_enter_c1(struct cpuidle_device *dev, return 0; } - acpi_state_timer_broadcast(pr, cx, 1); + lapic_timer_state_broadcast(pr, cx, 1); kt1 = ktime_get_real(); acpi_idle_do_entry(cx); kt2 = ktime_get_real(); @@ -847,7 +843,7 @@ static int acpi_idle_enter_c1(struct cpuidle_device *dev, local_irq_enable(); cx->usage++; - acpi_state_timer_broadcast(pr, cx, 0); + lapic_timer_state_broadcast(pr, cx, 0); return idle_time; } @@ -892,7 +888,7 @@ static int acpi_idle_enter_simple(struct cpuidle_device *dev, * Must be done before busmaster disable as we might need to * access HPET ! */ - acpi_state_timer_broadcast(pr, cx, 1); + lapic_timer_state_broadcast(pr, cx, 1); if (cx->type == ACPI_STATE_C3) ACPI_FLUSH_CPU_CACHE(); @@ -914,7 +910,7 @@ static int acpi_idle_enter_simple(struct cpuidle_device *dev, cx->usage++; - acpi_state_timer_broadcast(pr, cx, 0); + lapic_timer_state_broadcast(pr, cx, 0); cx->time += sleep_ticks; return idle_time; } @@ -981,7 +977,7 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev, * Must be done before busmaster disable as we might need to * access HPET ! */ - acpi_state_timer_broadcast(pr, cx, 1); + lapic_timer_state_broadcast(pr, cx, 1); kt1 = ktime_get_real(); /* @@ -1026,7 +1022,7 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev, cx->usage++; - acpi_state_timer_broadcast(pr, cx, 0); + lapic_timer_state_broadcast(pr, cx, 0); cx->time += sleep_ticks; return idle_time; } From d7880f10c5d42ba182a97c1fd41d41d0b8837097 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 18 Jun 2009 00:40:16 -0300 Subject: [PATCH 38/53] thinkpad-acpi: forbid the use of HBRV on Lenovo ThinkPads Forcing thinkpad-acpi to do EC-based brightness control (HBRV) on a X61 has very... interesting effects, instead of doing nothing (since it doesn't have EC-based backlight control), it causes "weirdness" in the fan tachometer readings, for example. This means the EC register that used to be HBRV has been reused by Lenovo for something else, but they didn't remove it from the DSDT. Make sure the documentation reflects this data, and forbid the user from forcing the driver to access HBRV on Lenovo ThinkPads. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 14 ++++++++------ drivers/platform/x86/thinkpad_acpi.c | 10 ++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index 88fc0661de56..f2ff638cce8d 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -1169,17 +1169,19 @@ may not be distinct. Later Lenovo models that implement the ACPI display backlight brightness control methods have 16 levels, ranging from 0 to 15. -There are two interfaces to the firmware for direct brightness control, -EC and UCMS (or CMOS). To select which one should be used, use the -brightness_mode module parameter: brightness_mode=1 selects EC mode, -brightness_mode=2 selects UCMS mode, brightness_mode=3 selects EC -mode with NVRAM backing (so that brightness changes are remembered -across shutdown/reboot). +For IBM ThinkPads, there are two interfaces to the firmware for direct +brightness control, EC and UCMS (or CMOS). To select which one should be +used, use the brightness_mode module parameter: brightness_mode=1 selects +EC mode, brightness_mode=2 selects UCMS mode, brightness_mode=3 selects EC +mode with NVRAM backing (so that brightness changes are remembered across +shutdown/reboot). The driver tries to select which interface to use from a table of defaults for each ThinkPad model. If it makes a wrong choice, please report this as a bug, so that we can fix it. +Lenovo ThinkPads only support brightness_mode=2 (UCMS). + When display backlight brightness controls are available through the standard ACPI interface, it is best to use it instead of this direct ThinkPad-specific interface. The driver will disable its native diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 5a22a064222c..c8d74dbacbbd 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -5696,6 +5696,10 @@ static struct ibm_struct ecdump_driver_data = { * Bit 3-0: backlight brightness level * * brightness_get_raw returns status data in the HBRV layout + * + * WARNING: The X61 has been verified to use HBRV for something else, so + * this should be used _only_ on IBM ThinkPads, and maybe with some careful + * testing on the very early *60 Lenovo models... */ enum { @@ -5996,6 +6000,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm) brightness_mode); } + /* Safety */ + if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM && + (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM || + brightness_mode == TPACPI_BRGHT_MODE_EC)) + return -EINVAL; + if (tpacpi_brightness_get_raw(&b) < 0) return 1; From d73772474f6ebbacbe820c31c0fa1cffa7160246 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 18 Jun 2009 00:40:17 -0300 Subject: [PATCH 39/53] thinkpad-acpi: support the second fan on the X61 Support reading the tachometer of the auxiliary fan of a X60/X61. It was found out by sheer luck, that bit 0 of EC register 0x31 (formely HBRV) selects which fan is active for tachometer readings through EC 0x84/0x085: 0 for fan1, 1 for fan2. Many thanks to Christoph Kl??nter, to Whoopie, and to weasel, who helped confirm that behaviour. Fan control through EC HFSP applies to both fans equally, regardless of the state of bit 0 of EC 0x31. That matches the way the DSDT uses HFSP. In order to better support the secondary fan, export a second tachometer over hwmon, and add defensive measures to make sure we are reading the correct tachometer. Support for the second fan is whitelist-based, as I have not found anything obvious to look for in the DSDT to detect the presence of the second fan. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 10 +- drivers/platform/x86/thinkpad_acpi.c | 122 +++++++++++++++++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index f2ff638cce8d..0d5e91379ae8 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -1269,7 +1269,7 @@ Fan control and monitoring: fan speed, fan enable/disable procfs: /proc/acpi/ibm/fan sysfs device attributes: (hwmon "thinkpad") fan1_input, pwm1, - pwm1_enable + pwm1_enable, fan2_input sysfs hwmon driver attributes: fan_watchdog NOTE NOTE NOTE: fan control operations are disabled by default for @@ -1282,6 +1282,9 @@ from the hardware registers of the embedded controller. This is known to work on later R, T, X and Z series ThinkPads but may show a bogus value on other models. +Some Lenovo ThinkPads support a secondary fan. This fan cannot be +controlled separately, it shares the main fan control. + Fan levels: Most ThinkPad fans work in "levels" at the firmware interface. Level 0 @@ -1412,6 +1415,11 @@ hwmon device attribute fan1_input: which can take up to two minutes. May return rubbish on older ThinkPads. +hwmon device attribute fan2_input: + Fan tachometer reading, in RPM, for the secondary fan. + Available only on some ThinkPads. If the secondary fan is + not installed, will always read 0. + hwmon driver attribute fan_watchdog: Fan safety watchdog timer interval, in seconds. Minimum is 1 second, maximum is 120 seconds. 0 disables the watchdog. diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c8d74dbacbbd..27ca676a7092 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -264,6 +264,7 @@ static struct { u32 wan:1; u32 uwb:1; u32 fan_ctrl_status_undef:1; + u32 second_fan:1; u32 beep_needs_two_args:1; u32 input_device_registered:1; u32 platform_drv_registered:1; @@ -6298,6 +6299,21 @@ static struct ibm_struct volume_driver_data = { * For firmware bugs, refer to: * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues * + * ---- + * + * ThinkPad EC register 0x31 bit 0 (only on select models) + * + * When bit 0 of EC register 0x31 is zero, the tachometer registers + * show the speed of the main fan. When bit 0 of EC register 0x31 + * is one, the tachometer registers show the speed of the auxiliary + * fan. + * + * Fan control seems to affect both fans, regardless of the state + * of this bit. + * + * So far, only the firmware for the X60/X61 non-tablet versions + * seem to support this (firmware TP-7M). + * * TPACPI_FAN_WR_ACPI_FANS: * ThinkPad X31, X40, X41. Not available in the X60. * @@ -6324,6 +6340,8 @@ 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 */ + fan_select_offset = 0x31, /* EC register 0x31 (Firmware 7M) + bit 0 selects which fan is active */ TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ @@ -6417,6 +6435,38 @@ static void fan_quirk1_handle(u8 *fan_status) } } +/* Select main fan on X60/X61, NOOP on others */ +static bool fan_select_fan1(void) +{ + if (tp_features.second_fan) { + u8 val; + + if (ec_read(fan_select_offset, &val) < 0) + return false; + val &= 0xFEU; + if (ec_write(fan_select_offset, val) < 0) + return false; + } + return true; +} + +/* Select secondary fan on X60/X61 */ +static bool fan_select_fan2(void) +{ + u8 val; + + if (!tp_features.second_fan) + return false; + + if (ec_read(fan_select_offset, &val) < 0) + return false; + val |= 0x01U; + if (ec_write(fan_select_offset, val) < 0) + return false; + + return true; +} + /* * Call with fan_mutex held */ @@ -6494,6 +6544,8 @@ static int fan_get_speed(unsigned int *speed) switch (fan_status_access_mode) { case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!fan_select_fan1())) + return -EIO; if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) || !acpi_ec_read(fan_rpm_offset + 1, &hi))) return -EIO; @@ -6510,6 +6562,34 @@ static int fan_get_speed(unsigned int *speed) return 0; } +static int fan2_get_speed(unsigned int *speed) +{ + u8 hi, lo; + bool rc; + + switch (fan_status_access_mode) { + case TPACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!fan_select_fan2())) + return -EIO; + rc = !acpi_ec_read(fan_rpm_offset, &lo) || + !acpi_ec_read(fan_rpm_offset + 1, &hi); + fan_select_fan1(); /* play it safe */ + if (rc) + return -EIO; + + if (likely(speed)) + *speed = (hi << 8) | lo; + + break; + + default: + return -ENXIO; + } + + return 0; +} + static int fan_set_level(int level) { if (!fan_control_allowed) @@ -6915,6 +6995,25 @@ static struct device_attribute dev_attr_fan_fan1_input = __ATTR(fan1_input, S_IRUGO, fan_fan1_input_show, NULL); +/* sysfs fan fan2_input ------------------------------------------------ */ +static ssize_t fan_fan2_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + unsigned int speed; + + res = fan2_get_speed(&speed); + if (res < 0) + return res; + + return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} + +static struct device_attribute dev_attr_fan_fan2_input = + __ATTR(fan2_input, S_IRUGO, + fan_fan2_input_show, NULL); + /* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ static ssize_t fan_fan_watchdog_show(struct device_driver *drv, char *buf) @@ -6948,6 +7047,7 @@ static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, static struct attribute *fan_attributes[] = { &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, &dev_attr_fan_fan1_input.attr, + NULL, /* for fan2_input */ NULL }; @@ -6955,7 +7055,8 @@ static const struct attribute_group fan_attr_group = { .attrs = fan_attributes, }; -#define TPACPI_FAN_Q1 0x0001 +#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */ +#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */ #define TPACPI_FAN_QI(__id1, __id2, __quirks) \ { .vendor = PCI_VENDOR_ID_IBM, \ @@ -6963,13 +7064,21 @@ static const struct attribute_group fan_attr_group = { .ec = TPID(__id1, __id2), \ .quirks = __quirks } +#define TPACPI_FAN_QL(__id1, __id2, __quirks) \ + { .vendor = PCI_VENDOR_ID_LENOVO, \ + .bios = TPACPI_MATCH_ANY, \ + .ec = TPID(__id1, __id2), \ + .quirks = __quirks } + static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1), TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1), TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1), TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1), + TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN), }; +#undef TPACPI_FAN_QL #undef TPACPI_FAN_QI static int __init fan_init(struct ibm_init_struct *iibm) @@ -6986,6 +7095,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_control_commands = 0; fan_watchdog_maxinterval = 0; tp_features.fan_ctrl_status_undef = 0; + tp_features.second_fan = 0; fan_control_desired_level = 7; TPACPI_ACPIHANDLE_INIT(fans); @@ -7006,6 +7116,11 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_status_access_mode = TPACPI_FAN_RD_TPEC; if (quirks & TPACPI_FAN_Q1) fan_quirk1_setup(); + if (quirks & TPACPI_FAN_2FAN) { + tp_features.second_fan = 1; + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, + "secondary fan support enabled\n"); + } } else { printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " @@ -7061,6 +7176,11 @@ static int __init fan_init(struct ibm_init_struct *iibm) if (fan_status_access_mode != TPACPI_FAN_NONE || fan_control_access_mode != TPACPI_FAN_WR_NONE) { + if (tp_features.second_fan) { + /* attach second fan tachometer */ + fan_attributes[ARRAY_SIZE(fan_attributes)-2] = + &dev_attr_fan_fan2_input.attr; + } rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group); if (rc < 0) From fbe2b31b4b6dfa790cbc88e00631f3112c4fc54e Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 18 Jun 2009 14:46:47 -0600 Subject: [PATCH 40/53] ACPI: pci_root: check _CRS, then _BBN for downstream bus number To find a host bridge's downstream bus number, we currently look at _BBN first. If _BBN returns a bus number we've already seen, we conclude that _BBN was wrong and look for a bus number in _CRS. However, the spec[1] (figure 5-5 and the example in sec 9.12.1) and an ACPI FAQ[2] suggest that the OS should use _CRS to discover the bus number range, and that _BBN is really intended to bootstrap _CRS methods that reference PCI opregions. This patch makes us always look at _CRS first. If _CRS doesn't supply a bus number, we look at _BBN. If _BBN doesn't exist, we default to zero. This makes the behavior consistent regardless of device discovery order. Previously, if A and B had duplicate _BBNs and we found A first, we'd only look at B's _CRS, whereas if we found B first, we'd only look at A's _CRS. I'm told that Windows discovers host bridge bus numbers using _CRS, so it should be fairly safe to rely on this BIOS functionality. This patch also removes two misleading messages: we printed the "Wrong _BBN value, reboot and use option 'pci=noacpi'" message before looking at _CRS, so we would likely find the bus number in _CRS, the system would work fine, and the user would be confused. The "PCI _CRS %d overrides _BBN 0" message incorrectly assumes _BBN was zero, and it's useless anyway because we print the segment/bus number a few lines later. References: [1] http://www.acpi.info/DOWNLOADS/ACPIspec30b.pdf [2] http://www.acpi.info/acpi_faq.htm _BBN/_CRS discussion http://download.microsoft.com/download/9/8/f/98f3fe47-dfc3-4e74-92a3-088782200fe7/TWAR05005_WinHEC05.ppt (slide 17) http://bugzilla.kernel.org/show_bug.cgi?id=1662 ASUS PR-DLS http://bugzilla.kernel.org/show_bug.cgi?id=1127 ASUS PR-DLSW http://bugzilla.kernel.org/show_bug.cgi?id=1741 ASUS PR-DLS533 Signed-off-by: Bjorn Helgaas Reviewed-by: Alex Chiang CC: Shaohua Li CC: Kenji Kaneshige Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 54 ++++++++++++----------------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 196f97d00956..a8b250783937 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -365,12 +365,12 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) { int result = 0; struct acpi_pci_root *root = NULL; - struct acpi_pci_root *tmp; acpi_status status = AE_OK; unsigned long long value = 0; acpi_handle handle = NULL; struct acpi_device *child; u32 flags, base_flags; + int bus; if (!device) @@ -420,46 +420,24 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) /* * Bus * --- - * Obtained via _BBN, if exists, otherwise assumed to be zero (0). + * Check _CRS first, then _BBN. If no _BBN, default to zero. */ - status = acpi_evaluate_integer(device->handle, METHOD_NAME__BBN, NULL, - &value); - switch (status) { - case AE_OK: - root->id.bus = (u16) value; - break; - case AE_NOT_FOUND: - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Assuming bus 0 (no _BBN)\n")); - root->id.bus = 0; - break; - default: - ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BBN")); - result = -ENODEV; - goto end; - } - - /* Some systems have wrong _BBN */ - list_for_each_entry(tmp, &acpi_pci_roots, node) { - if ((tmp->id.segment == root->id.segment) - && (tmp->id.bus == root->id.bus)) { - int bus = 0; - acpi_status status; - - printk(KERN_ERR PREFIX - "Wrong _BBN value, reboot" - " and use option 'pci=noacpi'\n"); - - status = try_get_root_bridge_busnr(device->handle, &bus); - if (ACPI_FAILURE(status)) - break; - if (bus != root->id.bus) { - printk(KERN_INFO PREFIX - "PCI _CRS %d overrides _BBN 0\n", bus); - root->id.bus = bus; - } - break; + status = try_get_root_bridge_busnr(device->handle, &bus); + if (ACPI_SUCCESS(status)) + root->id.bus = bus; + else { + status = acpi_evaluate_integer(device->handle, METHOD_NAME__BBN, NULL, &value); + if (ACPI_SUCCESS(status)) + root->id.bus = (u16) value; + else if (status == AE_NOT_FOUND) + root->id.bus = 0; + else { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BBN")); + result = -ENODEV; + goto end; } } + /* * Device & Function * ----------------- From f5eebbe119a861b5e4f5c67c886eab0937c686ed Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 18 Jun 2009 14:46:52 -0600 Subject: [PATCH 41/53] ACPI: pci_root: simplify acpi_pci_root_add() control flow By looking up the segment & bus number earlier, we don't have to worry about cleaning up if it fails. Signed-off-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 100 +++++++++++++++------------------------- 1 file changed, 38 insertions(+), 62 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index a8b250783937..0d69c0348c58 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -161,19 +161,22 @@ get_root_bridge_busnr_callback(struct acpi_resource *resource, void *data) return AE_OK; } -static acpi_status try_get_root_bridge_busnr(acpi_handle handle, int *busnum) +static acpi_status try_get_root_bridge_busnr(acpi_handle handle, + unsigned long long *bus) { acpi_status status; + int busnum; - *busnum = -1; + busnum = -1; status = acpi_walk_resources(handle, METHOD_NAME__CRS, - get_root_bridge_busnr_callback, busnum); + get_root_bridge_busnr_callback, &busnum); if (ACPI_FAILURE(status)) return status; /* Check if we really get a bus number from _CRS */ - if (*busnum == -1) + if (busnum == -1) return AE_ERROR; + *bus = busnum; return AE_OK; } @@ -363,24 +366,39 @@ EXPORT_SYMBOL(acpi_pci_osc_control_set); static int __devinit acpi_pci_root_add(struct acpi_device *device) { - int result = 0; - struct acpi_pci_root *root = NULL; - acpi_status status = AE_OK; - unsigned long long value = 0; - acpi_handle handle = NULL; + unsigned long long segment, bus; + acpi_status status; + int result; + struct acpi_pci_root *root; + acpi_handle handle; struct acpi_device *child; u32 flags, base_flags; - int bus; + segment = 0; + status = acpi_evaluate_integer(device->handle, METHOD_NAME__SEG, NULL, + &segment); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + printk(KERN_ERR PREFIX "can't evaluate _SEG\n"); + return -ENODEV; + } - if (!device) - return -EINVAL; + /* Check _CRS first, then _BBN. If no _BBN, default to zero. */ + bus = 0; + status = try_get_root_bridge_busnr(device->handle, &bus); + if (ACPI_FAILURE(status)) { + status = acpi_evaluate_integer(device->handle, METHOD_NAME__BBN, NULL, &bus); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + printk(KERN_ERR PREFIX + "no bus number in _CRS and can't evaluate _BBN\n"); + return -ENODEV; + } + } root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL); if (!root) return -ENOMEM; - INIT_LIST_HEAD(&root->node); + INIT_LIST_HEAD(&root->node); root->device = device; strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); @@ -395,54 +413,13 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) flags = base_flags = OSC_PCI_SEGMENT_GROUPS_SUPPORT; acpi_pci_osc_support(root, flags); - /* - * Segment - * ------- - * Obtained via _SEG, if exists, otherwise assumed to be zero (0). - */ - status = acpi_evaluate_integer(device->handle, METHOD_NAME__SEG, NULL, - &value); - switch (status) { - case AE_OK: - root->id.segment = (u16) value; - break; - case AE_NOT_FOUND: - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "Assuming segment 0 (no _SEG)\n")); - root->id.segment = 0; - break; - default: - ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SEG")); - result = -ENODEV; - goto end; - } - - /* - * Bus - * --- - * Check _CRS first, then _BBN. If no _BBN, default to zero. - */ - status = try_get_root_bridge_busnr(device->handle, &bus); - if (ACPI_SUCCESS(status)) - root->id.bus = bus; - else { - status = acpi_evaluate_integer(device->handle, METHOD_NAME__BBN, NULL, &value); - if (ACPI_SUCCESS(status)) - root->id.bus = (u16) value; - else if (status == AE_NOT_FOUND) - root->id.bus = 0; - else { - ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BBN")); - result = -ENODEV; - goto end; - } - } - /* * Device & Function * ----------------- * Obtained from _ADR (which has already been evaluated for us). */ + root->id.segment = segment & 0xFFFF; + root->id.bus = bus & 0xFF; root->id.device = device->pnp.bus_address >> 16; root->id.function = device->pnp.bus_address & 0xFFFF; @@ -509,13 +486,12 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) if (flags != base_flags) acpi_pci_osc_support(root, flags); - end: - if (result) { - if (!list_empty(&root->node)) - list_del(&root->node); - kfree(root); - } + return 0; +end: + if (!list_empty(&root->node)) + list_del(&root->node); + kfree(root); return result; } From caf420c68afe01acd7c458ce40b85b3db5330ff5 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 18 Jun 2009 14:46:57 -0600 Subject: [PATCH 42/53] ACPI: pci_root: use driver data rather than list lookup There's no need to search the list to find the acpi_pci_root structure. We saved it as device->driver_data when we added the device. Signed-off-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 0d69c0348c58..7984e00540fa 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -497,30 +497,17 @@ end: static int acpi_pci_root_start(struct acpi_device *device) { - struct acpi_pci_root *root; + struct acpi_pci_root *root = acpi_driver_data(device); - - list_for_each_entry(root, &acpi_pci_roots, node) { - if (root->device == device) { - pci_bus_add_devices(root->bus); - return 0; - } - } - return -ENODEV; + pci_bus_add_devices(root->bus); + return 0; } static int acpi_pci_root_remove(struct acpi_device *device, int type) { - struct acpi_pci_root *root = NULL; - - - if (!device || !acpi_driver_data(device)) - return -EINVAL; - - root = acpi_driver_data(device); + struct acpi_pci_root *root = acpi_driver_data(device); kfree(root); - return 0; } From c1aec8341627dad5d63cc24aa6746dc077f5b706 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 18 Jun 2009 14:47:02 -0600 Subject: [PATCH 43/53] ACPI: pci_root: simplify list traversals Using list_for_each_entry() makes traversing the root list easier. Signed-off-by: Bjorn Helgaas Reviewed-by: Alex Chiang Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 7984e00540fa..4fb747205418 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -82,7 +82,7 @@ static DEFINE_MUTEX(osc_lock); int acpi_pci_register_driver(struct acpi_pci_driver *driver) { int n = 0; - struct list_head *entry; + struct acpi_pci_root *root; struct acpi_pci_driver **pptr = &sub_driver; while (*pptr) @@ -92,9 +92,7 @@ int acpi_pci_register_driver(struct acpi_pci_driver *driver) if (!driver->add) return 0; - list_for_each(entry, &acpi_pci_roots) { - struct acpi_pci_root *root; - root = list_entry(entry, struct acpi_pci_root, node); + list_for_each_entry(root, &acpi_pci_roots, node) { driver->add(root->device->handle); n++; } @@ -106,7 +104,7 @@ EXPORT_SYMBOL(acpi_pci_register_driver); void acpi_pci_unregister_driver(struct acpi_pci_driver *driver) { - struct list_head *entry; + struct acpi_pci_root *root; struct acpi_pci_driver **pptr = &sub_driver; while (*pptr) { @@ -120,23 +118,19 @@ void acpi_pci_unregister_driver(struct acpi_pci_driver *driver) if (!driver->remove) return; - list_for_each(entry, &acpi_pci_roots) { - struct acpi_pci_root *root; - root = list_entry(entry, struct acpi_pci_root, node); + list_for_each_entry(root, &acpi_pci_roots, node) driver->remove(root->device->handle); - } } EXPORT_SYMBOL(acpi_pci_unregister_driver); acpi_handle acpi_get_pci_rootbridge_handle(unsigned int seg, unsigned int bus) { - struct acpi_pci_root *tmp; + struct acpi_pci_root *root; - list_for_each_entry(tmp, &acpi_pci_roots, node) { - if ((tmp->id.segment == (u16) seg) && (tmp->id.bus == (u16) bus)) - return tmp->device->handle; - } + list_for_each_entry(root, &acpi_pci_roots, node) + if ((root->id.segment == (u16) seg) && (root->id.bus == (u16) bus)) + return root->device->handle; return NULL; } @@ -301,6 +295,7 @@ static acpi_status acpi_pci_osc_support(struct acpi_pci_root *root, u32 flags) static struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle) { struct acpi_pci_root *root; + list_for_each_entry(root, &acpi_pci_roots, node) { if (root->device->handle == handle) return root; From 0705495d9010048e293013d9d129cf723363a0a8 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 18 Jun 2009 14:47:07 -0600 Subject: [PATCH 44/53] ACPI: pci_root: remove unused dev/fn information We never use the PCI device & function number, so remove it to make it clear that it's not needed. Many PCI host bridges don't even appear in config space, so it's meaningless to look at stuff from _ADR, which doesn't exist in that case. Signed-off-by: Bjorn Helgaas Reviewed-by: Alex Chiang Signed-off-by: Len Brown --- drivers/acpi/pci_root.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 4fb747205418..e95b5ac2e609 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -63,9 +63,10 @@ static struct acpi_driver acpi_pci_root_driver = { struct acpi_pci_root { struct list_head node; - struct acpi_device * device; - struct acpi_pci_id id; + struct acpi_device *device; struct pci_bus *bus; + u16 segment; + u8 bus_nr; u32 osc_support_set; /* _OSC state of support bits */ u32 osc_control_set; /* _OSC state of control bits */ @@ -129,7 +130,7 @@ acpi_handle acpi_get_pci_rootbridge_handle(unsigned int seg, unsigned int bus) struct acpi_pci_root *root; list_for_each_entry(root, &acpi_pci_roots, node) - if ((root->id.segment == (u16) seg) && (root->id.bus == (u16) bus)) + if ((root->segment == (u16) seg) && (root->bus_nr == (u16) bus)) return root->device->handle; return NULL; } @@ -395,6 +396,8 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) INIT_LIST_HEAD(&root->node); root->device = device; + root->segment = segment & 0xFFFF; + root->bus_nr = bus & 0xFF; strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); device->driver_data = root; @@ -408,16 +411,6 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) flags = base_flags = OSC_PCI_SEGMENT_GROUPS_SUPPORT; acpi_pci_osc_support(root, flags); - /* - * Device & Function - * ----------------- - * Obtained from _ADR (which has already been evaluated for us). - */ - root->id.segment = segment & 0xFFFF; - root->id.bus = bus & 0xFF; - root->id.device = device->pnp.bus_address >> 16; - root->id.function = device->pnp.bus_address & 0xFFFF; - /* * TBD: Need PCI interface for enumeration/configuration of roots. */ @@ -427,7 +420,7 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) printk(KERN_INFO PREFIX "%s [%s] (%04x:%02x)\n", acpi_device_name(device), acpi_device_bid(device), - root->id.segment, root->id.bus); + root->segment, root->bus_nr); /* * Scan the Root Bridge @@ -436,11 +429,11 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) * PCI namespace does not get created until this call is made (and * thus the root bridge's pci_dev does not exist). */ - root->bus = pci_acpi_scan_root(device, root->id.segment, root->id.bus); + root->bus = pci_acpi_scan_root(device, segment, bus); if (!root->bus) { printk(KERN_ERR PREFIX "Bus %04x:%02x not present in PCI namespace\n", - root->id.segment, root->id.bus); + root->segment, root->bus_nr); result = -ENODEV; goto end; } From 7b768f07dce463a054c9dd84862d15ccc3d2b712 Mon Sep 17 00:00:00 2001 From: "Pallipadi, Venkatesh" Date: Fri, 19 Jun 2009 17:14:59 -0700 Subject: [PATCH 45/53] ACPI: pdc init related memory leak with physical CPU hotplug arch_acpi_processor_cleanup_pdc() in x86 and ia64 results in memory allocated for _PDC objects that is never freed and will cause memory leak in case of physical CPU remove and add. Patch fixes the memory leak by freeing the objects soon after _PDC is evaluated. Reported-by: Bjorn Helgaas Signed-off-by: Venkatesh Pallipadi Signed-off-by: Len Brown --- arch/ia64/kernel/acpi-processor.c | 12 ++++++++++++ arch/x86/kernel/acpi/processor.c | 13 +++++++++++++ drivers/acpi/processor_core.c | 2 ++ include/acpi/processor.h | 1 + 4 files changed, 28 insertions(+) diff --git a/arch/ia64/kernel/acpi-processor.c b/arch/ia64/kernel/acpi-processor.c index cbe6cee5a550..dbda7bde6112 100644 --- a/arch/ia64/kernel/acpi-processor.c +++ b/arch/ia64/kernel/acpi-processor.c @@ -71,3 +71,15 @@ void arch_acpi_processor_init_pdc(struct acpi_processor *pr) } EXPORT_SYMBOL(arch_acpi_processor_init_pdc); + +void arch_acpi_processor_cleanup_pdc(struct acpi_processor *pr) +{ + if (pr->pdc) { + kfree(pr->pdc->pointer->buffer.pointer); + kfree(pr->pdc->pointer); + kfree(pr->pdc); + pr->pdc = NULL; + } +} + +EXPORT_SYMBOL(arch_acpi_processor_cleanup_pdc); diff --git a/arch/x86/kernel/acpi/processor.c b/arch/x86/kernel/acpi/processor.c index 7c074eec39fb..d296f4a195c9 100644 --- a/arch/x86/kernel/acpi/processor.c +++ b/arch/x86/kernel/acpi/processor.c @@ -72,6 +72,7 @@ static void init_intel_pdc(struct acpi_processor *pr, struct cpuinfo_x86 *c) return; } + /* Initialize _PDC data based on the CPU vendor */ void arch_acpi_processor_init_pdc(struct acpi_processor *pr) { @@ -85,3 +86,15 @@ void arch_acpi_processor_init_pdc(struct acpi_processor *pr) } EXPORT_SYMBOL(arch_acpi_processor_init_pdc); + +void arch_acpi_processor_cleanup_pdc(struct acpi_processor *pr) +{ + if (pr->pdc) { + kfree(pr->pdc->pointer->buffer.pointer); + kfree(pr->pdc->pointer); + kfree(pr->pdc); + pr->pdc = NULL; + } +} + +EXPORT_SYMBOL(arch_acpi_processor_cleanup_pdc); diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 23f0fb84f1c1..d40d45e904a5 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -731,6 +731,8 @@ static int __cpuinit acpi_processor_start(struct acpi_device *device) /* _PDC call should be done before doing anything else (if reqd.). */ arch_acpi_processor_init_pdc(pr); acpi_processor_set_pdc(pr); + arch_acpi_processor_cleanup_pdc(pr); + #ifdef CONFIG_CPU_FREQ acpi_processor_ppc_has_changed(pr); #endif diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 4927c063347c..baf1e0a9a7ee 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -258,6 +258,7 @@ DECLARE_PER_CPU(struct acpi_processor *, processors); extern struct acpi_processor_errata errata; void arch_acpi_processor_init_pdc(struct acpi_processor *pr); +void arch_acpi_processor_cleanup_pdc(struct acpi_processor *pr); #ifdef ARCH_HAS_POWER_INIT void acpi_processor_power_init_bm_check(struct acpi_processor_flags *flags, From 056c308d3e4859334b519033d62ef050f0e0e261 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Mon, 22 Jun 2009 11:31:14 +0800 Subject: [PATCH 46/53] Show the physical device node of backlight class device. Create symbol link from backlight class device to ACPI video device. More and more laptops are shipped with multiple ACPI video devices, while we export only one of them to userspace. With this patch applied, we can know which ACPI video device is used by "cat /sys/class/backlight/acpi_video0/device/path". Signed-off-by: Zhang Rui Signed-off-by: Len Brown --- drivers/acpi/video.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 1bdfb37377e3..9de143af3625 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -976,6 +976,11 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device) device->backlight->props.max_brightness = device->brightness->count-3; kfree(name); + result = sysfs_create_link(&device->backlight->dev.kobj, + &device->dev->dev.kobj, "device"); + if (result) + printk(KERN_ERR PREFIX "Create sysfs link\n"); + device->cdev = thermal_cooling_device_register("LCD", device->dev, &video_cooling_ops); if (IS_ERR(device->cdev)) @@ -1990,6 +1995,7 @@ static int acpi_video_bus_put_one_device(struct acpi_video_device *device) status = acpi_remove_notify_handler(device->dev->handle, ACPI_DEVICE_NOTIFY, acpi_video_device_notify); + sysfs_remove_link(&device->backlight->dev.kobj, "device"); backlight_device_unregister(device->backlight); if (device->cdev) { sysfs_remove_link(&device->dev->dev.kobj, From c02256be79a1a3557332ac51e653d574a2a7d2b5 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Tue, 23 Jun 2009 10:20:29 +0800 Subject: [PATCH 47/53] ACPI: fix a deadlock in hotplug case we used to run the hotplug code in keventd_wq. But when hot removing the ACPI battery device, power_supply_unregister invokes flush_scheduled_work. This causes a deadlock. i.e 1. When dock is unplugged, all the hotplug code is run on kevent_wq. 2. the hotplug code removes all the child devices of dock device. 3. removing the child device may invoke flush_scheduled_work 4. flush_scheduled_work waits until all the work on kevent_wq to be finished, while this will never be true because the hotplug code is running on keventd_wq... Introduce a new workqueue for hotplug in this patch. http://bugzilla.kernel.org/show_bug.cgi?id=13533 Tested-by: Paul Martin Tested-by: Vojtech Gondzala Signed-off-by: Zhang Rui Reviewed-by: Bjorn Helgaas Signed-off-by: Len Brown --- drivers/acpi/osl.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index d916bea729f1..71670719d61a 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -79,6 +79,7 @@ static acpi_osd_handler acpi_irq_handler; static void *acpi_irq_context; static struct workqueue_struct *kacpid_wq; static struct workqueue_struct *kacpi_notify_wq; +static struct workqueue_struct *kacpi_hotplug_wq; struct acpi_res_list { resource_size_t start; @@ -192,8 +193,10 @@ acpi_status acpi_os_initialize1(void) { kacpid_wq = create_singlethread_workqueue("kacpid"); kacpi_notify_wq = create_singlethread_workqueue("kacpi_notify"); + kacpi_hotplug_wq = create_singlethread_workqueue("kacpi_hotplug"); BUG_ON(!kacpid_wq); BUG_ON(!kacpi_notify_wq); + BUG_ON(!kacpi_hotplug_wq); return AE_OK; } @@ -206,6 +209,7 @@ acpi_status acpi_os_terminate(void) destroy_workqueue(kacpid_wq); destroy_workqueue(kacpi_notify_wq); + destroy_workqueue(kacpi_hotplug_wq); return AE_OK; } @@ -716,6 +720,7 @@ static acpi_status __acpi_os_execute(acpi_execute_type type, acpi_status status = AE_OK; struct acpi_os_dpc *dpc; struct workqueue_struct *queue; + work_func_t func; int ret; ACPI_DEBUG_PRINT((ACPI_DB_EXEC, "Scheduling function [%p(%p)] for deferred execution.\n", @@ -740,15 +745,17 @@ static acpi_status __acpi_os_execute(acpi_execute_type type, dpc->function = function; dpc->context = context; - if (!hp) { - INIT_WORK(&dpc->work, acpi_os_execute_deferred); - queue = (type == OSL_NOTIFY_HANDLER) ? - kacpi_notify_wq : kacpid_wq; - ret = queue_work(queue, &dpc->work); - } else { - INIT_WORK(&dpc->work, acpi_os_execute_hp_deferred); - ret = schedule_work(&dpc->work); - } + /* + * We can't run hotplug code in keventd_wq/kacpid_wq/kacpid_notify_wq + * because the hotplug code may call driver .remove() functions, + * which invoke flush_scheduled_work/acpi_os_wait_events_complete + * to flush these workqueues. + */ + queue = hp ? kacpi_hotplug_wq : + (type == OSL_NOTIFY_HANDLER ? kacpi_notify_wq : kacpid_wq); + func = hp ? acpi_os_execute_hp_deferred : acpi_os_execute_deferred; + INIT_WORK(&dpc->work, func); + ret = queue_work(queue, &dpc->work); if (!ret) { printk(KERN_ERR PREFIX From 35a7c64fbc77bab4ca8ae477e8ab278ccd679ce2 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Mon, 22 Jun 2009 11:31:17 +0800 Subject: [PATCH 48/53] ACPI: DMI to disable Vista compatibility on some Sony laptops Linux claims Vista compatibility to the BIOS for a number of reasons, but this brings hard lockup on some Sony laptops. Disable Vista compatibility via DMI for these laptops unless we can figure out what Vista is doing for this platform. http://bugzilla.kernel.org/show_bug.cgi?id=12904 Signed-off-by: Zhang Rui Signed-off-by: Len Brown --- drivers/acpi/blacklist.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/acpi/blacklist.c b/drivers/acpi/blacklist.c index 09c69806c1fc..f6baa77deefb 100644 --- a/drivers/acpi/blacklist.c +++ b/drivers/acpi/blacklist.c @@ -192,6 +192,22 @@ static struct dmi_system_id acpi_osi_dmi_table[] __initdata = { DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO Mobile V5505"), }, }, + { + .callback = dmi_disable_osi_vista, + .ident = "Sony VGN-NS10J_S", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS10J_S"), + }, + }, + { + .callback = dmi_disable_osi_vista, + .ident = "Sony VGN-SR290J", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Sony VGN-SR290J"), + }, + }, /* * BIOS invocation of _OSI(Linux) is almost always a BIOS bug. From 86e437f077c68112edcb6854ec036ed7e3f9a7f3 Mon Sep 17 00:00:00 2001 From: Zhao Yakui Date: Tue, 16 Jun 2009 11:23:13 +0800 Subject: [PATCH 49/53] ACPI: Add the reference count to avoid unloading ACPI video bus twice Sometimes both acpi video and i915 driver are compiled as modules. And there exists the strict dependency between the two drivers. The acpi video bus will be unloaded in course of unloading the i915 driver. If we unload the acpi video driver, then the kernel oops will be triggered. Add the reference count to avoid unloading the ACPI video bus twice. The reference count should be checked before unregistering the acpi video bus. If the reference count is already zero, it won't unregister it again. And after the acpi video bus is already unregistered, the reference count will be set to zero. http://bugzilla.kernel.org/show_bug.cgi?id=13396 Signed-off-by: Zhao Yakui Acked-by: Zhang Rui Signed-off-by: Len Brown --- drivers/acpi/video.c | 41 ++++++++++++++++++++++++---- drivers/gpu/drm/i915/i915_opregion.c | 2 +- include/acpi/video.h | 4 +-- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 1bdfb37377e3..a63566ba230b 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); static int brightness_switch_enabled = 1; module_param(brightness_switch_enabled, bool, 0644); +static int register_count = 0; static int acpi_video_bus_add(struct acpi_device *device); static int acpi_video_bus_remove(struct acpi_device *device, int type); static int acpi_video_resume(struct acpi_device *device); @@ -2318,6 +2319,13 @@ static int __init intel_opregion_present(void) int acpi_video_register(void) { int result = 0; + if (register_count) { + /* + * if the function of acpi_video_register is already called, + * don't register the acpi_vide_bus again and return no error. + */ + return 0; + } acpi_video_dir = proc_mkdir(ACPI_VIDEO_CLASS, acpi_root_dir); if (!acpi_video_dir) @@ -2329,10 +2337,35 @@ int acpi_video_register(void) return -ENODEV; } + /* + * When the acpi_video_bus is loaded successfully, increase + * the counter reference. + */ + register_count = 1; + return 0; } EXPORT_SYMBOL(acpi_video_register); +void acpi_video_unregister(void) +{ + if (!register_count) { + /* + * If the acpi video bus is already unloaded, don't + * unload it again and return directly. + */ + return; + } + acpi_bus_unregister_driver(&acpi_video_bus); + + remove_proc_entry(ACPI_VIDEO_CLASS, acpi_root_dir); + + register_count = 0; + + return; +} +EXPORT_SYMBOL(acpi_video_unregister); + /* * This is kind of nasty. Hardware using Intel chipsets may require * the video opregion code to be run first in order to initialise @@ -2350,16 +2383,12 @@ static int __init acpi_video_init(void) return acpi_video_register(); } -void acpi_video_exit(void) +static void __exit acpi_video_exit(void) { - - acpi_bus_unregister_driver(&acpi_video_bus); - - remove_proc_entry(ACPI_VIDEO_CLASS, acpi_root_dir); + acpi_video_unregister(); return; } -EXPORT_SYMBOL(acpi_video_exit); module_init(acpi_video_init); module_exit(acpi_video_exit); diff --git a/drivers/gpu/drm/i915/i915_opregion.c b/drivers/gpu/drm/i915/i915_opregion.c index dc425e74a268..e4b4e8898e39 100644 --- a/drivers/gpu/drm/i915/i915_opregion.c +++ b/drivers/gpu/drm/i915/i915_opregion.c @@ -419,7 +419,7 @@ void intel_opregion_free(struct drm_device *dev, int suspend) return; if (!suspend) - acpi_video_exit(); + acpi_video_unregister(); opregion->acpi->drdy = 0; diff --git a/include/acpi/video.h b/include/acpi/video.h index af6fe95fd3d0..cf7be3dd157b 100644 --- a/include/acpi/video.h +++ b/include/acpi/video.h @@ -3,10 +3,10 @@ #if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) extern int acpi_video_register(void); -extern int acpi_video_exit(void); +extern void acpi_video_unregister(void); #else static inline int acpi_video_register(void) { return 0; } -static inline void acpi_video_exit(void) { return; } +static inline void acpi_video_unregister(void) { return; } #endif #endif From c8d72a5e76988140bfdfd8722f2228d94e7fa10f Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Mon, 22 Jun 2009 11:31:16 +0800 Subject: [PATCH 50/53] ACPI: run ACPI device hot removal in kacpi_hotplug_wq Now that new interface is available, convert to using it rather than creating a new kernel thread. Signed-off-by: Zhang Rui Signed-off-by: Len Brown --- drivers/acpi/scan.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 8ff510b91d88..9c6e42e7cd13 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -95,7 +95,7 @@ acpi_device_modalias_show(struct device *dev, struct device_attribute *attr, cha } static DEVICE_ATTR(modalias, 0444, acpi_device_modalias_show, NULL); -static int acpi_bus_hot_remove_device(void *context) +static void acpi_bus_hot_remove_device(void *context) { struct acpi_device *device; acpi_handle handle = context; @@ -104,10 +104,10 @@ static int acpi_bus_hot_remove_device(void *context) acpi_status status = AE_OK; if (acpi_bus_get_device(handle, &device)) - return 0; + return; if (!device) - return 0; + return; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Hot-removing device %s...\n", dev_name(&device->dev))); @@ -115,7 +115,7 @@ static int acpi_bus_hot_remove_device(void *context) if (acpi_bus_trim(device, 1)) { printk(KERN_ERR PREFIX "Removing device failed\n"); - return -1; + return; } /* power off device */ @@ -142,9 +142,10 @@ static int acpi_bus_hot_remove_device(void *context) */ status = acpi_evaluate_object(handle, "_EJ0", &arg_list, NULL); if (ACPI_FAILURE(status)) - return -ENODEV; + printk(KERN_WARNING PREFIX + "Eject device failed\n"); - return 0; + return; } static ssize_t @@ -155,7 +156,6 @@ acpi_eject_store(struct device *d, struct device_attribute *attr, acpi_status status; acpi_object_type type = 0; struct acpi_device *acpi_device = to_acpi_device(d); - struct task_struct *task; if ((!count) || (buf[0] != '1')) { return -EINVAL; @@ -172,11 +172,7 @@ acpi_eject_store(struct device *d, struct device_attribute *attr, goto err; } - /* remove the device in another thread to fix the deadlock issue */ - task = kthread_run(acpi_bus_hot_remove_device, - acpi_device->handle, "acpi_hot_remove_device"); - if (IS_ERR(task)) - ret = PTR_ERR(task); + acpi_os_hotplug_execute(acpi_bus_hot_remove_device, acpi_device->handle); err: return ret; } From 152a4e630f7ffdd7ff64427c4ba488dc0bce76af Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Mon, 22 Jun 2009 11:31:18 +0800 Subject: [PATCH 51/53] ACPI: video: DMI workaround broken Acer 7720 BIOS enabling display brightness http://bugzilla.kernel.org/show_bug.cgi?id=13121 Signed-off-by: Zhang Rui Signed-off-by: Len Brown --- drivers/acpi/video.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 1bdfb37377e3..1eff1625672a 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -586,6 +586,14 @@ static struct dmi_system_id video_dmi_table[] __initdata = { DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5315"), }, }, + { + .callback = video_set_bqc_offset, + .ident = "Acer Aspire 7720", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 7720"), + }, + }, {} }; From e86435eb91b2bff114c5a02e46e16ce21b647ebe Mon Sep 17 00:00:00 2001 From: Peter Feuerer Date: Sun, 21 Jun 2009 18:53:03 +0200 Subject: [PATCH 52/53] acerhdf: Acer Aspire One fan control Acerhdf is a driver for Acer Aspire One netbooks. It allows to access the temperature sensor and to control the fan. Signed-off-by: Peter Feuerer Signed-off-by: Andreas Mohr Signed-off-by: Borislav Petkov Signed-off-by: Len Brown --- MAINTAINERS | 7 + drivers/platform/x86/Kconfig | 17 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/acerhdf.c | 602 +++++++++++++++++++++++++++++++++ 4 files changed, 627 insertions(+) create mode 100644 drivers/platform/x86/acerhdf.c diff --git a/MAINTAINERS b/MAINTAINERS index cf4abddfc8a4..e9457feb7eba 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -222,6 +222,13 @@ L: linux-acenic@sunsite.dk S: Maintained F: drivers/net/acenic* +ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER +P: Peter Feuerer +M: peter@piie.net +W: http://piie.net/?section=acerhdf +S: Maintained +F: drivers/platform/x86/acerhdf.c + ACER WMI LAPTOP EXTRAS P: Carlos Corbacho M: carlos@strangeworlds.co.uk diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 284ebaca6e45..7f79737b3947 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -34,6 +34,23 @@ config ACER_WMI If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M here. +config ACERHDF + tristate "Acer Aspire One temperature and fan driver" + depends on THERMAL && THERMAL_HWMON && ACPI + ---help--- + This is a driver for Acer Aspire One netbooks. It allows to access + the temperature sensor and to control the fan. + + After loading this driver the BIOS is still in control of the fan. + To let the kernel handle the fan, do: + echo -n enabled > /sys/class/thermal/thermal_zone0/mode + + For more information about this driver see + + + If you have an Acer Aspire One netbook, say Y or M + here. + config ASUS_LAPTOP tristate "Asus Laptop Extras (EXPERIMENTAL)" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e40c7bd1b87e..641b8bfa5538 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o +obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c new file mode 100644 index 000000000000..bdfee177eefb --- /dev/null +++ b/drivers/platform/x86/acerhdf.c @@ -0,0 +1,602 @@ +/* + * acerhdf - A driver which monitors the temperature + * of the aspire one netbook, turns on/off the fan + * as soon as the upper/lower threshold is reached. + * + * (C) 2009 - Peter Feuerer peter (a) piie.net + * http://piie.net + * 2009 Borislav Petkov + * + * Inspired by and many thanks to: + * o acerfand - Rachel Greenham + * o acer_ec.pl - Michael Kurz michi.kurz (at) googlemail.com + * - Petr Tomasek tomasek (#) etf,cuni,cz + * - Carlos Corbacho cathectic (at) gmail.com + * o lkml - Matthew Garrett + * - Borislav Petkov + * - Andreas Mohr + * + * 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 pr_fmt(fmt) "acerhdf: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The driver is started with "kernel mode off" by default. That means, the BIOS + * is still in control of the fan. In this mode the driver allows to read the + * temperature of the cpu and a userspace tool may take over control of the fan. + * If the driver is switched to "kernel mode" (e.g. via module parameter) the + * driver is in full control of the fan. If you want the module to be started in + * kernel mode by default, define the following: + */ +#undef START_IN_KERNEL_MODE + +#define DRV_VER "0.5.13" + +/* + * According to the Atom N270 datasheet, + * (http://download.intel.com/design/processor/datashts/320032.pdf) the + * CPU's optimal operating limits denoted in junction temperature as + * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So, + * assume 89°C is critical temperature. + */ +#define ACERHDF_TEMP_CRIT 89 +#define ACERHDF_FAN_OFF 0 +#define ACERHDF_FAN_AUTO 1 + +/* + * No matter what value the user puts into the fanon variable, turn on the fan + * at 80 degree Celsius to prevent hardware damage + */ +#define ACERHDF_MAX_FANON 80 + +/* + * Maximum interval between two temperature checks is 15 seconds, as the die + * can get hot really fast under heavy load (plus we shouldn't forget about + * possible impact of _external_ aggressive sources such as heaters, sun etc.) + */ +#define ACERHDF_MAX_INTERVAL 15 + +#ifdef START_IN_KERNEL_MODE +static int kernelmode = 1; +#else +static int kernelmode; +#endif + +static unsigned int interval = 10; +static unsigned int fanon = 63; +static unsigned int fanoff = 58; +static unsigned int verbose; +static unsigned int fanstate = ACERHDF_FAN_AUTO; +static char force_bios[16]; +static unsigned int prev_interval; +struct thermal_zone_device *thz_dev; +struct thermal_cooling_device *cl_dev; +struct platform_device *acerhdf_dev; + +module_param(kernelmode, uint, 0); +MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off"); +module_param(interval, uint, 0600); +MODULE_PARM_DESC(interval, "Polling interval of temperature check"); +module_param(fanon, uint, 0600); +MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature"); +module_param(fanoff, uint, 0600); +MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature"); +module_param(verbose, uint, 0600); +MODULE_PARM_DESC(verbose, "Enable verbose dmesg output"); +module_param_string(force_bios, force_bios, 16, 0); +MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check"); + +/* BIOS settings */ +struct bios_settings_t { + const char *vendor; + const char *version; + unsigned char fanreg; + unsigned char tempreg; + unsigned char fancmd[2]; /* fan off and auto commands */ +}; + +/* Register addresses and values for different BIOS versions */ +static const struct bios_settings_t bios_tbl[] = { + {"Acer", "v0.3109", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "v0.3301", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "v0.3304", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "v0.3305", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "v0.3308", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "v0.3309", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "v0.3310", 0x55, 0x58, {0x21, 0x00} }, + {"Gateway", "v0.3103", 0x55, 0x58, {0x21, 0x00} }, + {"Packard Bell", "v0.3105", 0x55, 0x58, {0x21, 0x00} }, + {"", "", 0, 0, {0, 0} } +}; + +static const struct bios_settings_t *bios_cfg __read_mostly; + + +static int acerhdf_get_temp(int *temp) +{ + u8 read_temp; + + if (ec_read(bios_cfg->tempreg, &read_temp)) + return -EINVAL; + + *temp = read_temp; + + return 0; +} + +static int acerhdf_get_fanstate(int *state) +{ + u8 fan; + bool tmp; + + if (ec_read(bios_cfg->fanreg, &fan)) + return -EINVAL; + + tmp = (fan == bios_cfg->fancmd[ACERHDF_FAN_OFF]); + *state = tmp ? ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO; + + return 0; +} + +static void acerhdf_change_fanstate(int state) +{ + unsigned char cmd; + + if (verbose) + pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ? + "OFF" : "ON"); + + if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) { + pr_err("invalid fan state %d requested, setting to auto!\n", + state); + state = ACERHDF_FAN_AUTO; + } + + cmd = bios_cfg->fancmd[state]; + fanstate = state; + + ec_write(bios_cfg->fanreg, cmd); +} + +static void acerhdf_check_param(struct thermal_zone_device *thermal) +{ + if (fanon > ACERHDF_MAX_FANON) { + pr_err("fanon temperature too high, set to %d\n", + ACERHDF_MAX_FANON); + fanon = ACERHDF_MAX_FANON; + } + + if (kernelmode && prev_interval != interval) { + if (interval > ACERHDF_MAX_INTERVAL) { + pr_err("interval too high, set to %d\n", + ACERHDF_MAX_INTERVAL); + interval = ACERHDF_MAX_INTERVAL; + } + if (verbose) + pr_notice("interval changed to: %d\n", + interval); + thermal->polling_delay = interval*1000; + prev_interval = interval; + } +} + +/* + * This is the thermal zone callback which does the delayed polling of the fan + * state. We do check /sysfs-originating settings here in acerhdf_check_param() + * as late as the polling interval is since we can't do that in the respective + * accessors of the module parameters. + */ +static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal, + unsigned long *t) +{ + int temp, err = 0; + + acerhdf_check_param(thermal); + + err = acerhdf_get_temp(&temp); + if (err) + return err; + + if (verbose) + pr_notice("temp %d\n", temp); + + *t = temp; + return 0; +} + +static int acerhdf_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + /* if the cooling device is the one from acerhdf bind it */ + if (cdev != cl_dev) + return 0; + + if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) { + pr_err("error binding cooling dev\n"); + return -EINVAL; + } + return 0; +} + +static int acerhdf_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + if (cdev != cl_dev) + return 0; + + if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) { + pr_err("error unbinding cooling dev\n"); + return -EINVAL; + } + return 0; +} + +static inline void acerhdf_revert_to_bios_mode(void) +{ + acerhdf_change_fanstate(ACERHDF_FAN_AUTO); + kernelmode = 0; + if (thz_dev) + thz_dev->polling_delay = 0; + pr_notice("kernel mode fan control OFF\n"); +} +static inline void acerhdf_enable_kernelmode(void) +{ + kernelmode = 1; + + thz_dev->polling_delay = interval*1000; + thermal_zone_device_update(thz_dev); + pr_notice("kernel mode fan control ON\n"); +} + +static int acerhdf_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + if (verbose) + pr_notice("kernel mode fan control %d\n", kernelmode); + + *mode = (kernelmode) ? THERMAL_DEVICE_ENABLED + : THERMAL_DEVICE_DISABLED; + + return 0; +} + +/* + * set operation mode; + * enabled: the thermal layer of the kernel takes care about + * the temperature and the fan. + * disabled: the BIOS takes control of the fan. + */ +static int acerhdf_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + if (mode == THERMAL_DEVICE_DISABLED && kernelmode) + acerhdf_revert_to_bios_mode(); + else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode) + acerhdf_enable_kernelmode(); + + return 0; +} + +static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip, + enum thermal_trip_type *type) +{ + if (trip == 0) + *type = THERMAL_TRIP_ACTIVE; + + return 0; +} + +static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip, + unsigned long *temp) +{ + if (trip == 0) + *temp = fanon; + + return 0; +} + +static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temperature) +{ + *temperature = ACERHDF_TEMP_CRIT; + return 0; +} + +/* bind callback functions to thermalzone */ +struct thermal_zone_device_ops acerhdf_dev_ops = { + .bind = acerhdf_bind, + .unbind = acerhdf_unbind, + .get_temp = acerhdf_get_ec_temp, + .get_mode = acerhdf_get_mode, + .set_mode = acerhdf_set_mode, + .get_trip_type = acerhdf_get_trip_type, + .get_trip_temp = acerhdf_get_trip_temp, + .get_crit_temp = acerhdf_get_crit_temp, +}; + + +/* + * cooling device callback functions + * get maximal fan cooling state + */ +static int acerhdf_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = 1; + + return 0; +} + +static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + int err = 0, tmp; + + err = acerhdf_get_fanstate(&tmp); + if (err) + return err; + + *state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0; + return 0; +} + +/* change current fan state - is overwritten when running in kernel mode */ +static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + int cur_temp, cur_state, err = 0; + + if (!kernelmode) + return 0; + + err = acerhdf_get_temp(&cur_temp); + if (err) { + pr_err("error reading temperature, hand off control to BIOS\n"); + goto err_out; + } + + err = acerhdf_get_fanstate(&cur_state); + if (err) { + pr_err("error reading fan state, hand off control to BIOS\n"); + goto err_out; + } + + if (state == 0) { + /* turn fan off only if below fanoff temperature */ + if ((cur_state == ACERHDF_FAN_AUTO) && + (cur_temp < fanoff)) + acerhdf_change_fanstate(ACERHDF_FAN_OFF); + } else { + if (cur_state == ACERHDF_FAN_OFF) + acerhdf_change_fanstate(ACERHDF_FAN_AUTO); + } + return 0; + +err_out: + acerhdf_revert_to_bios_mode(); + return -EINVAL; +} + +/* bind fan callbacks to fan device */ +struct thermal_cooling_device_ops acerhdf_cooling_ops = { + .get_max_state = acerhdf_get_max_state, + .get_cur_state = acerhdf_get_cur_state, + .set_cur_state = acerhdf_set_cur_state, +}; + +/* suspend / resume functionality */ +static int acerhdf_suspend(struct platform_device *dev, pm_message_t state) +{ + if (kernelmode) + acerhdf_change_fanstate(ACERHDF_FAN_AUTO); + + if (verbose) + pr_notice("going suspend\n"); + + return 0; +} + +static int acerhdf_resume(struct platform_device *device) +{ + if (verbose) + pr_notice("resuming\n"); + + return 0; +} + +static int __devinit acerhdf_probe(struct platform_device *device) +{ + return 0; +} + +static int acerhdf_remove(struct platform_device *device) +{ + return 0; +} + +struct platform_driver acerhdf_drv = { + .driver = { + .name = "acerhdf", + .owner = THIS_MODULE, + }, + .probe = acerhdf_probe, + .remove = acerhdf_remove, + .suspend = acerhdf_suspend, + .resume = acerhdf_resume, +}; + + +/* check hardware */ +static int acerhdf_check_hardware(void) +{ + char const *vendor, *version, *product; + int i; + + /* get BIOS data */ + vendor = dmi_get_system_info(DMI_SYS_VENDOR); + version = dmi_get_system_info(DMI_BIOS_VERSION); + product = dmi_get_system_info(DMI_PRODUCT_NAME); + + pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER); + + if (!force_bios[0]) { + if (strncmp(product, "AO", 2)) { + pr_err("no Aspire One hardware found\n"); + return -EINVAL; + } + } else { + pr_info("forcing BIOS version: %s\n", version); + version = force_bios; + kernelmode = 0; + } + + if (verbose) + pr_info("BIOS info: %s %s, product: %s\n", + vendor, version, product); + + /* search BIOS version and vendor in BIOS settings table */ + for (i = 0; bios_tbl[i].version[0]; i++) { + if (!strcmp(bios_tbl[i].vendor, vendor) && + !strcmp(bios_tbl[i].version, version)) { + bios_cfg = &bios_tbl[i]; + break; + } + } + + if (!bios_cfg) { + pr_err("unknown (unsupported) BIOS version %s/%s, " + "please report, aborting!\n", vendor, version); + return -EINVAL; + } + + /* + * if started with kernel mode off, prevent the kernel from switching + * off the fan + */ + if (!kernelmode) { + pr_notice("Fan control off, to enable do:\n"); + pr_notice("echo -n \"enabled\" > " + "/sys/class/thermal/thermal_zone0/mode\n"); + } + + return 0; +} + +static int acerhdf_register_platform(void) +{ + int err = 0; + + err = platform_driver_register(&acerhdf_drv); + if (err) + return err; + + acerhdf_dev = platform_device_alloc("acerhdf", -1); + platform_device_add(acerhdf_dev); + + return 0; +} + +static void acerhdf_unregister_platform(void) +{ + if (!acerhdf_dev) + return; + + platform_device_del(acerhdf_dev); + platform_driver_unregister(&acerhdf_drv); +} + +static int acerhdf_register_thermal(void) +{ + cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL, + &acerhdf_cooling_ops); + + if (IS_ERR(cl_dev)) + return -EINVAL; + + thz_dev = thermal_zone_device_register("acerhdf", 1, NULL, + &acerhdf_dev_ops, 0, 0, 0, + (kernelmode) ? interval*1000 : 0); + if (IS_ERR(thz_dev)) + return -EINVAL; + + return 0; +} + +static void acerhdf_unregister_thermal(void) +{ + if (cl_dev) { + thermal_cooling_device_unregister(cl_dev); + cl_dev = NULL; + } + + if (thz_dev) { + thermal_zone_device_unregister(thz_dev); + thz_dev = NULL; + } +} + +static int __init acerhdf_init(void) +{ + int err = 0; + + err = acerhdf_check_hardware(); + if (err) + goto out_err; + + err = acerhdf_register_platform(); + if (err) + goto err_unreg; + + err = acerhdf_register_thermal(); + if (err) + goto err_unreg; + + return 0; + +err_unreg: + acerhdf_unregister_thermal(); + acerhdf_unregister_platform(); + +out_err: + return -ENODEV; +} + +static void __exit acerhdf_exit(void) +{ + acerhdf_change_fanstate(ACERHDF_FAN_AUTO); + acerhdf_unregister_thermal(); + acerhdf_unregister_platform(); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Feuerer"); +MODULE_DESCRIPTION("Aspire One temperature and fan driver"); +MODULE_ALIAS("dmi:*:*Acer*:*:"); +MODULE_ALIAS("dmi:*:*Gateway*:*:"); +MODULE_ALIAS("dmi:*:*Packard Bell*:*:"); + +module_init(acerhdf_init); +module_exit(acerhdf_exit); From 7a04b8491a077471a34938b8ca060c37220953be Mon Sep 17 00:00:00 2001 From: Zhao Yakui Date: Wed, 24 Jun 2009 11:46:44 +0800 Subject: [PATCH 53/53] ACPI: Rename ACPI processor device bus ID Some BIOS re-use the same processor bus id in different scope: \_SB.SCK0.CPU0 \_SB.SCK1.CPU0 But the (deprecated) /proc/acpi/ interface assumes the bus-id's are unique, resulting in an OOPS when the processor driver is loaded: WARNING: at fs/proc/generic.c:590 proc_register+0x148/0x180() Hardware name: Sunrise Ridge proc_dir_entry 'processor/CPU0' already registered Call Trace: [] warn_slowpath+0xb1/0xe5 [] ? ida_get_new_above+0x190/0x1b1 [] ? idr_pre_get+0x5f/0x75 [] proc_register+0x148/0x180 [] proc_mkdir_mode+0x3d/0x52 [] proc_mkdir+0x11/0x13 [] acpi_processor_start+0x755/0x9bc [processor] Rename the processor device bus id. And the new bus id will be generated as the following format: CPU+ CPU ID For example: If the cpu ID is 5, then the bus ID will be "CPU5". If the CPU ID is 10, then the bus ID will be "CPUA". Yes, this will change the directory names seen in /proc/acpi/processor/* on some systems. Before this patch, those directory names where totally arbitrary strings based on the interal AML device strings. http://bugzilla.kernel.org/show_bug.cgi?id=13612 Signed-off-by: Zhao Yakui Signed-off-by: Len Brown --- drivers/acpi/processor_core.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 23f0fb84f1c1..105562e375ac 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -649,7 +649,16 @@ static int acpi_processor_get_info(struct acpi_device *device) return -ENODEV; } } - + /* + * On some boxes several processors use the same processor bus id. + * But they are located in different scope. For example: + * \_SB.SCK0.CPU0 + * \_SB.SCK1.CPU0 + * Rename the processor device bus id. And the new bus id will be + * generated as the following format: + * CPU+CPU ID. + */ + sprintf(acpi_device_bid(device), "CPU%X", pr->id); ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Processor [%d:%d]\n", pr->id, pr->acpi_id));