OSDN Git Service

change x41t device tree into thinkpad for all thinkpads, merge latest changes from...
authorStefan Seidel <android@stefanseidel.info>
Sat, 28 Apr 2012 12:11:56 +0000 (14:11 +0200)
committerStefan Seidel <android@stefanseidel.info>
Sat, 28 Apr 2012 12:11:56 +0000 (14:11 +0200)
26 files changed:
AndroidBoard.mk
AndroidProducts.mk
BoardConfig.mk
init.thinkpad.rc [moved from init.thinkpad_x41t.rc with 89% similarity]
overlays/frameworks/base/core/res/res/values/config.xml
patches/buildspec.mk
patches/kernel-suspend.patch
phc-intel/Android.mk [new file with mode: 0644]
phc-intel/Makefile [new file with mode: 0644]
phc-intel/phc-intel.c [new file with mode: 0644]
system.prop
thinkpad.mk [new file with mode: 0644]
thinkpad_defconfig [moved from x41t_defconfig with 98% similarity]
thinkpad_info [moved from x41t_info with 58% similarity]
tp_smapi/Android.mk [new file with mode: 0644]
tp_smapi/Makefile [new file with mode: 0644]
tp_smapi/hdaps.c [new file with mode: 0644]
tp_smapi/thinkpad_ec.c [new file with mode: 0644]
tp_smapi/thinkpad_ec.h [new file with mode: 0644]
tp_smapi/tp_smapi.c [new file with mode: 0644]
ueventd.thinkpad.rc [moved from ueventd.thinkpad_x41t.rc with 100% similarity]
vendorsetup.sh
wakeup_button/Android.mk [new file with mode: 0644]
wakeup_button/Makefile [new file with mode: 0644]
wakeup_button/wakeup_button.c [new file with mode: 0644]
x41t.mk [deleted file]

index 6b3f899..646ca10 100644 (file)
@@ -1,7 +1,8 @@
 LOCAL_PATH := $(call my-dir)
-TARGET_KERNEL_CONFIG := $(LOCAL_PATH)/x41t_defconfig
-TARGET_INITRD_SCRIPTS := $(LOCAL_PATH)/x41t_info
+TARGET_KERNEL_CONFIG := $(LOCAL_PATH)/thinkpad_defconfig
+TARGET_INITRD_SCRIPTS := $(LOCAL_PATH)/thinkpad_info
 TARGET_PREBUILT_APPS := $(subst $(LOCAL_PATH)/,,$(wildcard $(LOCAL_PATH)/app/*))
 $(call add-prebuilt-targets,$(TARGET_OUT),$(TARGET_PREBUILT_APPS))
+TARGET_EXTRA_KERNEL_MODULES := wakeup_button phc-intel tp_smapi
 
 include $(GENERIC_X86_ANDROID_MK)
index 3a6091b..3f5f9aa 100644 (file)
@@ -26,4 +26,4 @@
 #
 
 PRODUCT_MAKEFILES := \
-    $(LOCAL_DIR)/x41t.mk
+    $(LOCAL_DIR)/thinkpad.mk
index 73f88ca..8218ba4 100644 (file)
@@ -14,11 +14,11 @@ BOARD_GPU_DRIVERS := i915 i965 r300g r600g nouveau
 BOARD_USES_KBDSENSOR := false
 BOARD_USES_HDAPS_ACCEL := true
 #TARGET_CPU_SMP := false
-BOARD_KERNEL_CMDLINE := root=/dev/ram0 quiet androidboot.hardware=$(TARGET_PRODUCT) video=1024x768 i915.lvds_downclock=1 i915.powersave=1 usbcore.autosuspend=2
 WPA_SUPPLICANT_VERSION := VER_0_6_X
 BOARD_WPA_SUPPLICANT_DRIVER := AWEXT
 BOARD_WPA_SUPPLICANT_PRIVATE_LIB := 
 
 include $(GENERIC_X86_CONFIG_MK)
+BOARD_KERNEL_CMDLINE := root=/dev/ram0 quiet androidboot.hardware=$(TARGET_PRODUCT) video=1024x768 i915.lvds_downclock=1 i915.powersave=1 usbcore.autosuspend=2
 
 #BOARD_KERNEL_CMDLINE += SDCARD=sdc
similarity index 89%
rename from init.thinkpad_x41t.rc
rename to init.thinkpad.rc
index 699abf7..0a8f13b 100755 (executable)
@@ -33,7 +33,9 @@ on property:dev.bootcomplete=1
     start amixer-speaker
 
     chmod 660 /sys/class/rfkill/rfkill0/state
+    chmod 660 /sys/class/rfkill/rfkill1/state
     chown system bluetooth /sys/class/rfkill/rfkill0/state
+    chown system bluetooth /sys/class/rfkill/rfkill1/state
 
 on property:sys.boot_completed=1
     start tablet-mode
index 9a3bd96..00439b1 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <bool name="config_reverseDefaultRotation">true</bool>
+    <!--bool name="config_reverseDefaultRotation">true</bool-->
     <integer name="config_lidOpenRotation">0</integer>
     <!-- Indicate whether the lid state impacts the accessibility of
          the physical keyboard.  0 means it doesn't, 1 means it is accessible
index c5d46ac..dc04586 100644 (file)
@@ -1,4 +1,4 @@
-TARGET_PRODUCT:=thinkpad_x41t
+TARGET_PRODUCT:=thinkpad
 #TARGET_BUILD_VARIANT:=eng
 TARGET_BUILD_VARIANT:=user
 TARGET_BUILD_TYPE:=release
index 2922973..faef861 100644 (file)
@@ -1,18 +1,5 @@
-diff --git a/kernel/power/Makefile b/kernel/power/Makefile
-index 9b224e1..2f2b431 100644
---- a/kernel/power/Makefile
-+++ b/kernel/power/Makefile
-@@ -10,7 +10,7 @@ obj-$(CONFIG_HIBERNATION)    += hibernate.o snapshot.o swap.o user.o \
-                                  block_io.o
- obj-$(CONFIG_WAKELOCK)                += wakelock.o
- obj-$(CONFIG_USER_WAKELOCK)   += userwakelock.o
--obj-$(CONFIG_EARLYSUSPEND)    += earlysuspend.o
-+obj-$(CONFIG_EARLYSUSPEND)    += earlysuspend.o wakeup_button.o
- obj-$(CONFIG_CONSOLE_EARLYSUSPEND)    += consoleearlysuspend.o
- obj-$(CONFIG_FB_EARLYSUSPEND) += fbearlysuspend.o
- obj-$(CONFIG_SUSPEND_TIME)    += suspend_time.o
 diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c
-index 63774df..0646b15 100644
+index 18cf0fc..0646b15 100644
 --- a/kernel/power/suspend.c
 +++ b/kernel/power/suspend.c
 @@ -94,7 +94,9 @@ static int suspend_prepare(void)
@@ -35,13 +22,7 @@ index 63774df..0646b15 100644
        return error;
  }
  
-@@ -250,10 +254,15 @@ int suspend_devices_and_enter(suspend_state_t state)
-  */
- static void suspend_finish(void)
- {
-+#ifdef CONFIG_EARLYSUSPEND
-+      request_suspend_state(PM_SUSPEND_ON);
-+#endif
+@@ -256,7 +260,9 @@ static void suspend_finish(void)
        suspend_thaw_processes();
        usermodehelper_enable();
        pm_notifier_call_chain(PM_POST_SUSPEND);
@@ -51,75 +32,3 @@ index 63774df..0646b15 100644
  }
  
  /**
-diff --git a/kernel/power/wakeup_button.c b/kernel/power/wakeup_button.c
-new file mode 100644
-index 0000000..dcc3158
---- /dev/null
-+++ b/kernel/power/wakeup_button.c
-@@ -0,0 +1,66 @@
-+/*
-+ * wakeup_button.c - Power Button which is pushed on resume from standby.
-+ *
-+ * Copyright (c) 2011 Stefan Seidel
-+ *
-+ * This file is released under the GPLv2 or later.
-+ */
-+#include <linux/module.h>
-+#include <linux/init.h>
-+#include <linux/earlysuspend.h>
-+#include <linux/input.h>
-+
-+MODULE_LICENSE("GPL");
-+MODULE_AUTHOR("Stefan Seidel <android@stefanseidel.info>");
-+MODULE_DESCRIPTION("Sets up a virtual input device and sends a power key event during early resume. Needed for some to make Android on x86 wake up properly.");
-+
-+static struct input_dev *input;
-+
-+static void wakeup_button_early_suspend(struct early_suspend *h)
-+{
-+      return;
-+}
-+
-+static void wakeup_button_early_resume(struct early_suspend *h)
-+{
-+      printk("Early resume, push virtual power button!\n");
-+      input_report_key(input, KEY_POWER, 1);
-+      input_sync(input);
-+      input_report_key(input, KEY_POWER, 0);
-+      input_sync(input);
-+}
-+
-+static struct early_suspend wakeup_button_early_suspend_handlers = {
-+      .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN - 1, // very late resume
-+      .suspend = wakeup_button_early_suspend,
-+      .resume = wakeup_button_early_resume
-+};
-+
-+static int __init wakeup_button_init(void)
-+{
-+      int error;
-+      printk("Registering Android Wakeup Button.\n");
-+      input = input_allocate_device();
-+      input->name = "Wakeup Button";
-+      input->id.bustype = BUS_USB; // use BUS_USB here so that Android registers this as an external key
-+      input->evbit[0] = BIT_MASK(EV_KEY);
-+      set_bit(KEY_POWER, input->keybit);
-+      error = input_register_device(input);
-+      if (error) {
-+              input_free_device(input);
-+      } else {
-+              register_early_suspend(&wakeup_button_early_suspend_handlers);
-+      }
-+      return error;
-+}
-+
-+static void __exit wakeup_button_exit(void)
-+{
-+      printk("Unregistering Android Wakeup Button.\n");
-+      unregister_early_suspend(&wakeup_button_early_suspend_handlers);
-+      input_unregister_device(input);
-+      return;
-+}
-+
-+module_init(wakeup_button_init);
-+module_exit(wakeup_button_exit);
diff --git a/phc-intel/Android.mk b/phc-intel/Android.mk
new file mode 100644 (file)
index 0000000..9730748
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# Copyright (C) 2009-2011 The Android-x86 Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := phc-intel
+EXTRA_KERNEL_MODULE_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH)
diff --git a/phc-intel/Makefile b/phc-intel/Makefile
new file mode 100644 (file)
index 0000000..bdf1583
--- /dev/null
@@ -0,0 +1 @@
+obj-m += phc-intel.o
diff --git a/phc-intel/phc-intel.c b/phc-intel/phc-intel.c
new file mode 100644 (file)
index 0000000..b911dfa
--- /dev/null
@@ -0,0 +1,1261 @@
+/*
+ * acpi-cpufreq.c - ACPI Processor P-States Driver
+ *
+ *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
+ *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
+ *  Copyright (C) 2002 - 2004 Dominik Brodowski <linux@brodo.de>
+ *  Copyright (C) 2006       Denis Sadykov <denis.m.sadykov@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+/* This file has been patched with Linux PHC: www.linux-phc.org
+* Patch version: linux-phc-0.3.2
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/smp.h>
+#include <linux/sched.h>
+#include <linux/compiler.h>
+#include <linux/dmi.h>
+#include <linux/slab.h>
+#include <linux/cpufreq.h>
+
+#include <linux/acpi.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+
+#include <acpi/processor.h>
+
+#include <asm/msr.h>
+#include <asm/processor.h>
+#include <asm/cpufeature.h>
+#include "../drivers/cpufreq/mperf.h"
+
+MODULE_AUTHOR("Paul Diefenbaugh, Dominik Brodowski");
+MODULE_DESCRIPTION("ACPI Processor P-States Driver");
+MODULE_LICENSE("GPL");
+
+enum {
+       UNDEFINED_CAPABLE = 0,
+       SYSTEM_INTEL_MSR_CAPABLE,
+       SYSTEM_IO_CAPABLE,
+};
+
+#define INTEL_MSR_RANGE                (0xffff)
+#define INTEL_MSR_VID_MASK     (0x00ff)
+#define INTEL_MSR_FID_MASK     (0xff00)
+#define INTEL_MSR_FID_SHIFT    (0x8)
+#define PHC_VERSION_STRING     "0.3.2:2"
+
+struct acpi_cpufreq_data {
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int resume;
+       unsigned int cpu_feature;
+       acpi_integer *original_controls;
+};
+
+static DEFINE_PER_CPU(struct acpi_cpufreq_data *, acfreq_data);
+
+/* acpi_perf_data is a pointer to percpu data. */
+static struct acpi_processor_performance __percpu *acpi_perf_data;
+
+static struct cpufreq_driver acpi_cpufreq_driver;
+
+static unsigned int acpi_pstate_strict;
+
+static int check_est_cpu(unsigned int cpuid)
+{
+       struct cpuinfo_x86 *cpu = &cpu_data(cpuid);
+
+       return cpu_has(cpu, X86_FEATURE_EST);
+}
+
+static unsigned extract_io(u32 value, struct acpi_cpufreq_data *data)
+{
+       struct acpi_processor_performance *perf;
+       int i;
+
+       perf = data->acpi_data;
+
+       for (i = 0; i < perf->state_count; i++) {
+               if (value == perf->states[i].status)
+                       return data->freq_table[i].frequency;
+       }
+       return 0;
+}
+
+static unsigned extract_msr(u32 msr, struct acpi_cpufreq_data *data)
+{
+       int i;
+       u32 fid;
+       struct acpi_processor_performance *perf;
+
+       fid = msr & INTEL_MSR_FID_MASK;
+       perf = data->acpi_data;
+
+       for (i = 0; data->freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+               if (fid == (perf->states[data->freq_table[i].index].status & INTEL_MSR_FID_MASK))
+                       return data->freq_table[i].frequency;
+       }
+       return data->freq_table[0].frequency;
+}
+
+static unsigned extract_freq(u32 val, struct acpi_cpufreq_data *data)
+{
+       switch (data->cpu_feature) {
+       case SYSTEM_INTEL_MSR_CAPABLE:
+               return extract_msr(val, data);
+       case SYSTEM_IO_CAPABLE:
+               return extract_io(val, data);
+       default:
+               return 0;
+       }
+}
+
+struct msr_addr {
+       u32 reg;
+};
+
+struct io_addr {
+       u16 port;
+       u8 bit_width;
+};
+
+struct drv_cmd {
+       unsigned int type;
+       const struct cpumask *mask;
+       union {
+               struct msr_addr msr;
+               struct io_addr io;
+       } addr;
+       u32 val;
+};
+
+/* Called via smp_call_function_single(), on the target CPU */
+static void do_drv_read(void *_cmd)
+{
+       struct drv_cmd *cmd = _cmd;
+       u32 h;
+
+       switch (cmd->type) {
+       case SYSTEM_INTEL_MSR_CAPABLE:
+               rdmsr(cmd->addr.msr.reg, cmd->val, h);
+               break;
+       case SYSTEM_IO_CAPABLE:
+               acpi_os_read_port((acpi_io_address)cmd->addr.io.port,
+                               &cmd->val,
+                               (u32)cmd->addr.io.bit_width);
+               break;
+       default:
+               break;
+       }
+}
+
+/* Called via smp_call_function_many(), on the target CPUs */
+static void do_drv_write(void *_cmd)
+{
+       struct drv_cmd *cmd = _cmd;
+       u32 lo, hi;
+
+       switch (cmd->type) {
+       case SYSTEM_INTEL_MSR_CAPABLE:
+               rdmsr(cmd->addr.msr.reg, lo, hi);
+               lo = (lo & ~INTEL_MSR_RANGE) | (cmd->val & INTEL_MSR_RANGE);
+               wrmsr(cmd->addr.msr.reg, lo, hi);
+               break;
+       case SYSTEM_IO_CAPABLE:
+               acpi_os_write_port((acpi_io_address)cmd->addr.io.port,
+                               cmd->val,
+                               (u32)cmd->addr.io.bit_width);
+               break;
+       default:
+               break;
+       }
+}
+
+static void drv_read(struct drv_cmd *cmd)
+{
+       int err;
+       cmd->val = 0;
+
+       err = smp_call_function_any(cmd->mask, do_drv_read, cmd, 1);
+       WARN_ON_ONCE(err);      /* smp_call_function_any() was buggy? */
+}
+
+static void drv_write(struct drv_cmd *cmd)
+{
+       int this_cpu;
+
+       this_cpu = get_cpu();
+       if (cpumask_test_cpu(this_cpu, cmd->mask))
+               do_drv_write(cmd);
+       smp_call_function_many(cmd->mask, do_drv_write, cmd, 1);
+       put_cpu();
+}
+
+static u32 get_cur_val(const struct cpumask *mask)
+{
+       struct acpi_processor_performance *perf;
+       struct drv_cmd cmd;
+
+       if (unlikely(cpumask_empty(mask)))
+               return 0;
+
+       switch (per_cpu(acfreq_data, cpumask_first(mask))->cpu_feature) {
+       case SYSTEM_INTEL_MSR_CAPABLE:
+               cmd.type = SYSTEM_INTEL_MSR_CAPABLE;
+               cmd.addr.msr.reg = MSR_IA32_PERF_STATUS;
+               break;
+       case SYSTEM_IO_CAPABLE:
+               cmd.type = SYSTEM_IO_CAPABLE;
+               perf = per_cpu(acfreq_data, cpumask_first(mask))->acpi_data;
+               cmd.addr.io.port = perf->control_register.address;
+               cmd.addr.io.bit_width = perf->control_register.bit_width;
+               break;
+       default:
+               return 0;
+       }
+
+       cmd.mask = mask;
+       drv_read(&cmd);
+
+       pr_debug("get_cur_val = %u\n", cmd.val);
+
+       return cmd.val;
+}
+
+static unsigned int get_cur_freq_on_cpu(unsigned int cpu)
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, cpu);
+       unsigned int freq;
+       unsigned int cached_freq;
+
+       pr_debug("get_cur_freq_on_cpu (%d)\n", cpu);
+
+       if (unlikely(data == NULL ||
+                    data->acpi_data == NULL || data->freq_table == NULL)) {
+               return 0;
+       }
+
+       cached_freq = data->freq_table[data->acpi_data->state].frequency;
+       freq = extract_freq(get_cur_val(cpumask_of(cpu)), data);
+       if (freq != cached_freq) {
+               /*
+                * The dreaded BIOS frequency change behind our back.
+                * Force set the frequency on next target call.
+                */
+               data->resume = 1;
+       }
+
+       pr_debug("cur freq = %u\n", freq);
+
+       return freq;
+}
+
+static unsigned int check_freqs(const struct cpumask *mask, unsigned int freq,
+                               struct acpi_cpufreq_data *data)
+{
+       unsigned int cur_freq;
+       unsigned int i;
+
+       for (i = 0; i < 100; i++) {
+               cur_freq = extract_freq(get_cur_val(mask), data);
+               if (cur_freq == freq)
+                       return 1;
+               udelay(10);
+       }
+       return 0;
+}
+
+static int acpi_cpufreq_target(struct cpufreq_policy *policy,
+                              unsigned int target_freq, unsigned int relation)
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct acpi_processor_performance *perf;
+       struct cpufreq_freqs freqs;
+       struct drv_cmd cmd;
+       unsigned int next_state = 0; /* Index into freq_table */
+       unsigned int next_perf_state = 0; /* Index into perf table */
+       unsigned int i;
+       int result = 0;
+
+       pr_debug("acpi_cpufreq_target %d (%d)\n", target_freq, policy->cpu);
+
+       if (unlikely(data == NULL ||
+            data->acpi_data == NULL || data->freq_table == NULL)) {
+               return -ENODEV;
+       }
+
+       perf = data->acpi_data;
+       result = cpufreq_frequency_table_target(policy,
+                                               data->freq_table,
+                                               target_freq,
+                                               relation, &next_state);
+       if (unlikely(result)) {
+               result = -ENODEV;
+               goto out;
+       }
+
+       next_perf_state = data->freq_table[next_state].index;
+       if (perf->state == next_perf_state) {
+               if (unlikely(data->resume)) {
+                       pr_debug("Called after resume, resetting to P%d\n",
+                               next_perf_state);
+                       data->resume = 0;
+               } else {
+                       pr_debug("Already at target state (P%d)\n",
+                               next_perf_state);
+                       goto out;
+               }
+       }
+
+       switch (data->cpu_feature) {
+       case SYSTEM_INTEL_MSR_CAPABLE:
+               cmd.type = SYSTEM_INTEL_MSR_CAPABLE;
+               cmd.addr.msr.reg = MSR_IA32_PERF_CTL;
+               cmd.val = (u32) perf->states[next_perf_state].control;
+               break;
+       case SYSTEM_IO_CAPABLE:
+               cmd.type = SYSTEM_IO_CAPABLE;
+               cmd.addr.io.port = perf->control_register.address;
+               cmd.addr.io.bit_width = perf->control_register.bit_width;
+               cmd.val = (u32) perf->states[next_perf_state].control;
+               break;
+       default:
+               result = -ENODEV;
+               goto out;
+       }
+
+       /* cpufreq holds the hotplug lock, so we are safe from here on */
+       if (policy->shared_type != CPUFREQ_SHARED_TYPE_ANY)
+               cmd.mask = policy->cpus;
+       else
+               cmd.mask = cpumask_of(policy->cpu);
+
+       freqs.old = perf->states[perf->state].core_frequency * 1000;
+       freqs.new = data->freq_table[next_state].frequency;
+       for_each_cpu(i, policy->cpus) {
+               freqs.cpu = i;
+               cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+       }
+
+       drv_write(&cmd);
+
+       if (acpi_pstate_strict) {
+               if (!check_freqs(cmd.mask, freqs.new, data)) {
+                       pr_debug("acpi_cpufreq_target failed (%d)\n",
+                               policy->cpu);
+                       result = -EAGAIN;
+                       goto out;
+               }
+       }
+
+       for_each_cpu(i, policy->cpus) {
+               freqs.cpu = i;
+               cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+       }
+       perf->state = next_perf_state;
+
+out:
+       return result;
+}
+
+static int acpi_cpufreq_verify(struct cpufreq_policy *policy)
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+
+       pr_debug("acpi_cpufreq_verify\n");
+
+       return cpufreq_frequency_table_verify(policy, data->freq_table);
+}
+
+static unsigned long
+acpi_cpufreq_guess_freq(struct acpi_cpufreq_data *data, unsigned int cpu)
+{
+       struct acpi_processor_performance *perf = data->acpi_data;
+
+       if (cpu_khz) {
+               /* search the closest match to cpu_khz */
+               unsigned int i;
+               unsigned long freq;
+               unsigned long freqn = perf->states[0].core_frequency * 1000;
+
+               for (i = 0; i < (perf->state_count-1); i++) {
+                       freq = freqn;
+                       freqn = perf->states[i+1].core_frequency * 1000;
+                       if ((2 * cpu_khz) > (freqn + freq)) {
+                               perf->state = i;
+                               return freq;
+                       }
+               }
+               perf->state = perf->state_count-1;
+               return freqn;
+       } else {
+               /* assume CPU is at P0... */
+               perf->state = 0;
+               return perf->states[0].core_frequency * 1000;
+       }
+}
+
+static void free_acpi_perf_data(void)
+{
+       unsigned int i;
+
+       /* Freeing a NULL pointer is OK, and alloc_percpu zeroes. */
+       for_each_possible_cpu(i)
+               free_cpumask_var(per_cpu_ptr(acpi_perf_data, i)
+                                ->shared_cpu_map);
+       free_percpu(acpi_perf_data);
+}
+
+/*
+ * acpi_cpufreq_early_init - initialize ACPI P-States library
+ *
+ * Initialize the ACPI P-States library (drivers/acpi/processor_perflib.c)
+ * in order to determine correct frequency and voltage pairings. We can
+ * do _PDC and _PSD and find out the processor dependency for the
+ * actual init that will happen later...
+ */
+static int __init acpi_cpufreq_early_init(void)
+{
+       unsigned int i;
+       pr_debug("acpi_cpufreq_early_init\n");
+
+       acpi_perf_data = alloc_percpu(struct acpi_processor_performance);
+       if (!acpi_perf_data) {
+               pr_debug("Memory allocation error for acpi_perf_data.\n");
+               return -ENOMEM;
+       }
+       for_each_possible_cpu(i) {
+               if (!zalloc_cpumask_var_node(
+                       &per_cpu_ptr(acpi_perf_data, i)->shared_cpu_map,
+                       GFP_KERNEL, cpu_to_node(i))) {
+
+                       /* Freeing a NULL pointer is OK: alloc_percpu zeroes. */
+                       free_acpi_perf_data();
+                       return -ENOMEM;
+               }
+       }
+
+       /* Do initialization in ACPI core */
+       acpi_processor_preregister_performance(acpi_perf_data);
+       return 0;
+}
+
+#ifdef CONFIG_SMP
+/*
+ * Some BIOSes do SW_ANY coordination internally, either set it up in hw
+ * or do it in BIOS firmware and won't inform about it to OS. If not
+ * detected, this has a side effect of making CPU run at a different speed
+ * than OS intended it to run at. Detect it and handle it cleanly.
+ */
+static int bios_with_sw_any_bug;
+
+static int sw_any_bug_found(const struct dmi_system_id *d)
+{
+       bios_with_sw_any_bug = 1;
+       return 0;
+}
+
+static const struct dmi_system_id sw_any_bug_dmi_table[] = {
+       {
+               .callback = sw_any_bug_found,
+               .ident = "Supermicro Server X6DLP",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Supermicro"),
+                       DMI_MATCH(DMI_BIOS_VERSION, "080010"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "X6DLP"),
+               },
+       },
+       { }
+};
+
+static int acpi_cpufreq_blacklist(struct cpuinfo_x86 *c)
+{
+       /* Intel Xeon Processor 7100 Series Specification Update
+        * http://www.intel.com/Assets/PDF/specupdate/314554.pdf
+        * AL30: A Machine Check Exception (MCE) Occurring during an
+        * Enhanced Intel SpeedStep Technology Ratio Change May Cause
+        * Both Processor Cores to Lock Up. */
+       if (c->x86_vendor == X86_VENDOR_INTEL) {
+               if ((c->x86 == 15) &&
+                   (c->x86_model == 6) &&
+                   (c->x86_mask == 8)) {
+                       printk(KERN_INFO "acpi-cpufreq: Intel(R) "
+                           "Xeon(R) 7100 Errata AL30, processors may "
+                           "lock up on frequency changes: disabling "
+                           "acpi-cpufreq.\n");
+                       return -ENODEV;
+                   }
+               }
+       return 0;
+}
+#endif
+
+static int acpi_cpufreq_cpu_init(struct cpufreq_policy *policy)
+{
+       unsigned int i;
+       unsigned int valid_states = 0;
+       unsigned int cpu = policy->cpu;
+       struct acpi_cpufreq_data *data;
+       unsigned int result = 0;
+       struct cpuinfo_x86 *c = &cpu_data(policy->cpu);
+       struct acpi_processor_performance *perf;
+#ifdef CONFIG_SMP
+       static int blacklisted;
+#endif
+
+       pr_debug("acpi_cpufreq_cpu_init\n");
+
+#ifdef CONFIG_SMP
+       if (blacklisted)
+               return blacklisted;
+       blacklisted = acpi_cpufreq_blacklist(c);
+       if (blacklisted)
+               return blacklisted;
+#endif
+
+       data = kzalloc(sizeof(struct acpi_cpufreq_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->acpi_data = per_cpu_ptr(acpi_perf_data, cpu);
+       per_cpu(acfreq_data, cpu) = data;
+
+       if (cpu_has(c, X86_FEATURE_CONSTANT_TSC))
+               acpi_cpufreq_driver.flags |= CPUFREQ_CONST_LOOPS;
+
+       result = acpi_processor_register_performance(data->acpi_data, cpu);
+       if (result)
+               goto err_free;
+
+       perf = data->acpi_data;
+       policy->shared_type = perf->shared_type;
+
+       /*
+        * Will let policy->cpus know about dependency only when software
+        * coordination is required.
+        */
+       if (policy->shared_type == CPUFREQ_SHARED_TYPE_ALL ||
+           policy->shared_type == CPUFREQ_SHARED_TYPE_ANY) {
+               cpumask_copy(policy->cpus, perf->shared_cpu_map);
+       }
+       cpumask_copy(policy->related_cpus, perf->shared_cpu_map);
+
+#ifdef CONFIG_SMP
+       dmi_check_system(sw_any_bug_dmi_table);
+       if (bios_with_sw_any_bug && cpumask_weight(policy->cpus) == 1) {
+               policy->shared_type = CPUFREQ_SHARED_TYPE_ALL;
+               cpumask_copy(policy->cpus, cpu_core_mask(cpu));
+       }
+#endif
+
+       /* capability check */
+       if (perf->state_count <= 1) {
+               pr_debug("No P-States\n");
+               result = -ENODEV;
+               goto err_unreg;
+       }
+
+       if (perf->control_register.space_id != perf->status_register.space_id) {
+               result = -ENODEV;
+               goto err_unreg;
+       }
+
+       switch (perf->control_register.space_id) {
+       case ACPI_ADR_SPACE_SYSTEM_IO:
+               pr_debug("SYSTEM IO addr space\n");
+               data->cpu_feature = SYSTEM_IO_CAPABLE;
+               break;
+       case ACPI_ADR_SPACE_FIXED_HARDWARE:
+               pr_debug("HARDWARE addr space\n");
+               if (!check_est_cpu(cpu)) {
+                       result = -ENODEV;
+                       goto err_unreg;
+               }
+               data->cpu_feature = SYSTEM_INTEL_MSR_CAPABLE;
+               break;
+       default:
+               pr_debug("Unknown addr space %d\n",
+                       (u32) (perf->control_register.space_id));
+               result = -ENODEV;
+               goto err_unreg;
+       }
+
+       data->freq_table = kmalloc(sizeof(struct cpufreq_frequency_table) *
+                   (perf->state_count+1), GFP_KERNEL);
+       if (!data->freq_table) {
+               result = -ENOMEM;
+               goto err_unreg;
+       }
+
+       /* detect transition latency */
+       policy->cpuinfo.transition_latency = 0;
+       for (i = 0; i < perf->state_count; i++) {
+               if ((perf->states[i].transition_latency * 1000) >
+                   policy->cpuinfo.transition_latency)
+                       policy->cpuinfo.transition_latency =
+                           perf->states[i].transition_latency * 1000;
+       }
+
+       /* Check for high latency (>20uS) from buggy BIOSes, like on T42 */
+       if (perf->control_register.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE &&
+           policy->cpuinfo.transition_latency > 20 * 1000) {
+               policy->cpuinfo.transition_latency = 20 * 1000;
+               printk_once(KERN_INFO
+                           "P-state transition latency capped at 20 uS\n");
+       }
+
+       /* table init */
+       for (i = 0; i < perf->state_count; i++) {
+               if (i > 0 && perf->states[i].core_frequency >=
+                   data->freq_table[valid_states-1].frequency / 1000)
+                       continue;
+
+               data->freq_table[valid_states].index = i;
+               data->freq_table[valid_states].frequency =
+                   perf->states[i].core_frequency * 1000;
+               valid_states++;
+       }
+       data->freq_table[valid_states].frequency = CPUFREQ_TABLE_END;
+       perf->state = 0;
+
+       result = cpufreq_frequency_table_cpuinfo(policy, data->freq_table);
+       if (result)
+               goto err_freqfree;
+
+       if (perf->states[0].core_frequency * 1000 != policy->cpuinfo.max_freq)
+               printk(KERN_WARNING FW_WARN "P-state 0 is not max freq\n");
+
+       switch (perf->control_register.space_id) {
+       case ACPI_ADR_SPACE_SYSTEM_IO:
+               /* Current speed is unknown and not detectable by IO port */
+               policy->cur = acpi_cpufreq_guess_freq(data, policy->cpu);
+               break;
+       case ACPI_ADR_SPACE_FIXED_HARDWARE:
+               acpi_cpufreq_driver.get = get_cur_freq_on_cpu;
+               policy->cur = get_cur_freq_on_cpu(cpu);
+               break;
+       default:
+               break;
+       }
+
+       /* notify BIOS that we exist */
+       acpi_processor_notify_smm(THIS_MODULE);
+
+       /* Check for APERF/MPERF support in hardware */
+       if (cpu_has(c, X86_FEATURE_APERFMPERF))
+               acpi_cpufreq_driver.getavg = cpufreq_get_measured_perf;
+
+       pr_debug("CPU%u - ACPI performance management activated.\n", cpu);
+       for (i = 0; i < perf->state_count; i++)
+               pr_debug("     %cP%d: %d MHz, %d mW, %d uS\n",
+                       (i == perf->state ? '*' : ' '), i,
+                       (u32) perf->states[i].core_frequency,
+                       (u32) perf->states[i].power,
+                       (u32) perf->states[i].transition_latency);
+
+       cpufreq_frequency_table_get_attr(data->freq_table, policy->cpu);
+
+       /*
+        * the first call to ->target() should result in us actually
+        * writing something to the appropriate registers.
+        */
+       data->resume = 1;
+
+       return result;
+
+err_freqfree:
+       kfree(data->freq_table);
+err_unreg:
+       acpi_processor_unregister_performance(perf, cpu);
+err_free:
+       kfree(data);
+       per_cpu(acfreq_data, cpu) = NULL;
+
+       return result;
+}
+
+static int acpi_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+
+       pr_debug("acpi_cpufreq_cpu_exit\n");
+
+       if (data) {
+               cpufreq_frequency_table_put_attr(policy->cpu);
+               per_cpu(acfreq_data, policy->cpu) = NULL;
+               acpi_processor_unregister_performance(data->acpi_data,
+                                                     policy->cpu);
+               if (data->original_controls)
+                       kfree(data->original_controls);
+               kfree(data->freq_table);
+               kfree(data);
+       }
+
+       return 0;
+}
+
+static int acpi_cpufreq_resume(struct cpufreq_policy *policy)
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+
+       pr_debug("acpi_cpufreq_resume\n");
+
+       data->resume = 1;
+
+       return 0;
+}
+
+
+/* sysfs interface to change operating points voltages */
+
+static unsigned int extract_fid_from_control(unsigned int control)
+{
+       return ((control & INTEL_MSR_FID_MASK) >> INTEL_MSR_FID_SHIFT);
+}
+
+static unsigned int extract_vid_from_control(unsigned int control)
+{
+       return (control & INTEL_MSR_VID_MASK);
+}
+
+
+static bool check_cpu_control_capability(struct acpi_cpufreq_data *data) {
+ /* check if the cpu we are running on is capable of setting new control data
+  * 
+  */
+       if (unlikely(data == NULL || 
+                    data->acpi_data == NULL || 
+                    data->freq_table == NULL ||
+                    data->cpu_feature != SYSTEM_INTEL_MSR_CAPABLE)) {
+               return false;
+       } else {
+               return true;
+       };
+}
+
+
+static ssize_t check_origial_table (struct acpi_cpufreq_data *data)
+{
+
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int state_index;
+
+       acpi_data = data->acpi_data;
+       freq_table = data->freq_table;
+
+       if (data->original_controls == NULL) {
+               // Backup original control values
+               data->original_controls = kcalloc(acpi_data->state_count,
+                                                 sizeof(acpi_integer), GFP_KERNEL);
+               if (data->original_controls == NULL) {
+                       printk("failed to allocate memory for original control values\n");
+                       return -ENOMEM;
+               }
+               for (state_index = 0; state_index < acpi_data->state_count; state_index++) {
+                       data->original_controls[state_index] = acpi_data->states[state_index].control;
+               }
+       }
+       return 0;
+}
+
+static ssize_t show_freq_attr_vids(struct cpufreq_policy *policy, char *buf)
+ /* display phc's voltage id's
+  * 
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int i;
+       unsigned int vid;
+       ssize_t count = 0;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV; //check if CPU is capable of changing controls
+
+       acpi_data = data->acpi_data;
+       freq_table = data->freq_table;
+
+       for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+               vid = extract_vid_from_control(acpi_data->states[freq_table[i].index].control);
+               count += sprintf(&buf[count], "%u ", vid);
+       }
+       count += sprintf(&buf[count], "\n");
+
+       return count;
+}
+
+static ssize_t show_freq_attr_default_vids(struct cpufreq_policy *policy, char *buf)
+ /* display acpi's default voltage id's
+  * 
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int i;
+       unsigned int vid;
+       ssize_t count = 0;
+       ssize_t retval;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV; //check if CPU is capable of changing controls
+
+       retval = check_origial_table(data);
+        if (0 != retval)
+               return retval; 
+
+       freq_table = data->freq_table;
+
+       for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+               vid = extract_vid_from_control(data->original_controls[freq_table[i].index]);
+               count += sprintf(&buf[count], "%u ", vid);
+       }
+       count += sprintf(&buf[count], "\n");
+
+       return count;
+}
+
+static ssize_t show_freq_attr_fids(struct cpufreq_policy *policy, char *buf)
+ /* display phc's frequeny id's
+  * 
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int i;
+       unsigned int fid;
+       ssize_t count = 0;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV; //check if CPU is capable of changing controls
+
+       acpi_data = data->acpi_data;
+       freq_table = data->freq_table;
+
+       for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+               fid = extract_fid_from_control(acpi_data->states[freq_table[i].index].control);
+               count += sprintf(&buf[count], "%u ", fid);
+       }
+       count += sprintf(&buf[count], "\n");
+
+       return count;
+}
+
+static ssize_t show_freq_attr_controls(struct cpufreq_policy *policy, char *buf)
+ /* display phc's controls for the cpu (frequency id's and related voltage id's)
+  * 
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int i;
+       unsigned int fid;
+       unsigned int vid;
+       ssize_t count = 0;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV; //check if CPU is capable of changing controls
+
+       acpi_data = data->acpi_data;
+       freq_table = data->freq_table;
+
+       for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+               fid = extract_fid_from_control(acpi_data->states[freq_table[i].index].control);
+               vid = extract_vid_from_control(acpi_data->states[freq_table[i].index].control);
+               count += sprintf(&buf[count], "%u:%u ", fid, vid);
+       }
+       count += sprintf(&buf[count], "\n");
+
+       return count;
+}
+
+static ssize_t show_freq_attr_default_controls(struct cpufreq_policy *policy, char *buf)
+ /* display acpi's default controls for the cpu (frequency id's and related voltage id's)
+  * 
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int i;
+       unsigned int fid;
+       unsigned int vid;
+       ssize_t count = 0;
+       ssize_t retval;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV; //check if CPU is capable of changing controls
+
+       retval = check_origial_table(data);
+        if (0 != retval)
+               return retval; 
+
+       freq_table = data->freq_table;
+
+       for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+               fid = extract_fid_from_control(data->original_controls[freq_table[i].index]);
+               vid = extract_vid_from_control(data->original_controls[freq_table[i].index]);
+               count += sprintf(&buf[count], "%u:%u ", fid, vid);
+       }
+       count += sprintf(&buf[count], "\n");
+
+       return count;
+}
+
+
+static ssize_t store_freq_attr_vids(struct cpufreq_policy *policy, const char *buf, size_t count)
+ /* store the voltage id's for the related frequency
+  * We are going to do some sanity checks here to prevent users 
+  * from setting higher voltages than the default one.
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       unsigned int freq_index;
+       unsigned int state_index;
+       unsigned int new_vid;
+       unsigned int original_vid;
+       unsigned int new_control;
+       unsigned int original_control;
+       const char *curr_buf = buf;
+       char *next_buf;
+       ssize_t retval;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV; //check if CPU is capable of changing controls
+
+       retval = check_origial_table(data);
+        if (0 != retval)
+               return retval; 
+
+       acpi_data = data->acpi_data;
+       freq_table = data->freq_table;
+
+       /* for each value taken from the sysfs interfalce (phc_vids) get entrys and convert them to unsigned long integers*/
+       for (freq_index = 0; freq_table[freq_index].frequency != CPUFREQ_TABLE_END; freq_index++) {
+               new_vid = simple_strtoul(curr_buf, &next_buf, 10);
+               if (next_buf == curr_buf) {
+                       if ((curr_buf - buf == count - 1) && (*curr_buf == '\n')) {   //end of line?
+                               curr_buf++;
+                               break;
+                       }
+                       //if we didn't got end of line but there is nothing more to read something went wrong...
+                       printk("failed to parse vid value at %i (%s)\n", freq_index, curr_buf);
+                       return -EINVAL;
+               }
+
+               state_index = freq_table[freq_index].index;
+               original_control = data->original_controls[state_index];
+               original_vid = original_control & INTEL_MSR_VID_MASK;
+               
+               /* before we store the values we do some checks to prevent 
+                * users to set up values higher than the default one
+                */
+               if (new_vid <= original_vid) {
+                       new_control = (original_control & ~INTEL_MSR_VID_MASK) | new_vid;
+                       printk("setting control at %i to %x (default is %x)\n",
+                               freq_index, new_control, original_control);
+                       acpi_data->states[state_index].control = new_control;
+
+               } else {
+                       printk("skipping vid at %i, %u is greater than default %u\n",
+                              freq_index, new_vid, original_vid);
+               }
+
+               curr_buf = next_buf;
+               /* jump over value seperators (space or comma).
+                * There could be more than one space or comma character
+                * to separate two values so we better do it using a loop.
+                */
+               while ((curr_buf - buf < count) && ((*curr_buf == ' ') || (*curr_buf == ','))) {
+                       curr_buf++;
+               }
+       }
+
+       /* set new voltage for current frequency */
+       data->resume = 1;
+       acpi_cpufreq_target(policy, get_cur_freq_on_cpu(policy->cpu), CPUFREQ_RELATION_L);
+
+       return curr_buf - buf;
+}
+
+static ssize_t store_freq_attr_controls(struct cpufreq_policy *policy, const char *buf, size_t count)
+ /* store the controls (frequency id's and related voltage id's)
+  * We are going to do some sanity checks here to prevent users 
+  * from setting higher voltages than the default one.
+  */
+{
+       struct acpi_cpufreq_data *data = per_cpu(acfreq_data, policy->cpu);
+       struct acpi_processor_performance *acpi_data;
+       struct cpufreq_frequency_table *freq_table;
+       const char   *curr_buf;
+       unsigned int  op_count;
+       unsigned int  state_index;
+       int           isok;
+       char         *next_buf;
+       ssize_t       retval;
+       unsigned int  new_vid;
+       unsigned int  original_vid;
+       unsigned int  new_fid;
+       unsigned int  old_fid;
+       unsigned int  original_control;
+       unsigned int  old_control;
+       unsigned int  new_control;
+       int           found;
+
+       if (!check_cpu_control_capability(data)) return -ENODEV;
+
+       retval = check_origial_table(data);
+        if (0 != retval)
+               return retval;
+
+       acpi_data = data->acpi_data;
+       freq_table = data->freq_table;
+
+       op_count = 0;
+       curr_buf = buf;
+       next_buf = NULL;
+       isok     = 1;
+       
+       while ( (isok) && (curr_buf != NULL) )
+       {
+               op_count++;
+               // Parse fid
+               new_fid = simple_strtoul(curr_buf, &next_buf, 10);
+               if ((next_buf != curr_buf) && (next_buf != NULL))
+               {
+                       // Parse separator between frequency and voltage 
+                       curr_buf = next_buf;
+                       next_buf = NULL;
+                       if (*curr_buf==':')
+                       {
+                               curr_buf++;
+                               // Parse vid
+                               new_vid = simple_strtoul(curr_buf, &next_buf, 10);
+                               if ((next_buf != curr_buf) && (next_buf != NULL))
+                               {
+                                       found = 0;
+                                       for (state_index = 0; state_index < acpi_data->state_count; state_index++) {
+                                               old_control = acpi_data->states[state_index].control;
+                                               old_fid = extract_fid_from_control(old_control);
+                                               if (new_fid == old_fid)
+                                               {
+                                                       found = 1;
+                                                       original_control = data->original_controls[state_index];
+                                                       original_vid = extract_vid_from_control(original_control);
+                                                       if (new_vid <= original_vid)
+                                                       {
+                                                               new_control = (original_control & ~INTEL_MSR_VID_MASK) | new_vid;
+                                                               printk("setting control at %i to %x (default is %x)\n",
+                                                                       state_index, new_control, original_control);
+                                                               acpi_data->states[state_index].control = new_control;
+
+                                                       } else {
+                                                               printk("skipping vid at %i, %u is greater than default %u\n",
+                                                                      state_index, new_vid, original_vid);
+                                                       }
+                                               }
+                                       }
+
+                                       if (found == 0)
+                                       {
+                                               printk("operating point # %u not found (FID = %u)\n", op_count, new_fid);
+                                               isok = 0;
+                                       }
+
+                                       // Parse seprator before next operating point, if any
+                                       curr_buf = next_buf;
+                                       next_buf = NULL;
+                                       if ((*curr_buf == ',') || (*curr_buf == ' '))
+                                               curr_buf++;
+                                       else
+                                               curr_buf = NULL;
+                               }
+                               else
+                               {
+                                       printk("failed to parse VID of operating point # %u (%s)\n", op_count, curr_buf);
+                                       isok = 0;
+                               }
+                       }
+                       else
+                       {
+                               printk("failed to parse operating point # %u (%s)\n", op_count, curr_buf);
+                               isok = 0;
+                       }
+               }
+               else
+               {
+                       printk("failed to parse FID of operating point # %u (%s)\n", op_count, curr_buf);
+                       isok = 0;
+               }
+       }
+
+       if (isok)
+       {
+               retval = count;
+               /* set new voltage at current frequency */
+               data->resume = 1;
+               acpi_cpufreq_target(policy, get_cur_freq_on_cpu(policy->cpu), CPUFREQ_RELATION_L);
+       }
+       else
+       {
+               retval = -EINVAL;
+       }
+
+       return retval;
+}
+
+static ssize_t show_freq_attr_phc_version(struct cpufreq_policy *policy, char *buf)
+ /* print out the phc version string set at the beginning of that file
+  */
+{
+       ssize_t count = 0;
+       count += sprintf(&buf[count], "%s\n", PHC_VERSION_STRING);
+       return count;
+}
+
+
+
+static struct freq_attr cpufreq_freq_attr_phc_version =
+{
+       /*display phc's version string*/
+       .attr = { .name = "phc_version", .mode = 0444/*, .owner = THIS_MODULE */},
+       .show = show_freq_attr_phc_version,
+       .store = NULL,
+};
+
+static struct freq_attr cpufreq_freq_attr_vids =
+{
+       /*display phc's voltage id's for the cpu*/
+       .attr = { .name = "phc_vids", .mode = 0644/*, .owner = THIS_MODULE */},
+       .show = show_freq_attr_vids,
+       .store = store_freq_attr_vids,
+};
+
+static struct freq_attr cpufreq_freq_attr_default_vids =
+{
+       /*display acpi's default frequency id's for the cpu*/
+       .attr = { .name = "phc_default_vids", .mode = 0444/*, .owner = THIS_MODULE */},
+       .show = show_freq_attr_default_vids,
+       .store = NULL,
+};
+
+static struct freq_attr cpufreq_freq_attr_fids =
+{
+       /*display phc's default frequency id's for the cpu*/
+       .attr = { .name = "phc_fids", .mode = 0444/*, .owner = THIS_MODULE */},
+       .show = show_freq_attr_fids,
+       .store = NULL,
+};
+
+static struct freq_attr cpufreq_freq_attr_controls =
+{
+       /*display phc's current voltage/frequency controls for the cpu*/
+       .attr = { .name = "phc_controls", .mode = 0644/*, .owner = THIS_MODULE */},
+       .show = show_freq_attr_controls,
+       .store = store_freq_attr_controls,
+};
+
+static struct freq_attr cpufreq_freq_attr_default_controls =
+{
+       /*display acpi's default voltage/frequency controls for the cpu*/
+       .attr = { .name = "phc_default_controls", .mode = 0444/*, .owner = THIS_MODULE */},
+       .show = show_freq_attr_default_controls,
+       .store = NULL,
+};
+
+static struct freq_attr *acpi_cpufreq_attr[] = {
+       &cpufreq_freq_attr_scaling_available_freqs,
+       &cpufreq_freq_attr_phc_version,
+       &cpufreq_freq_attr_vids,
+       &cpufreq_freq_attr_default_vids,
+       &cpufreq_freq_attr_fids,
+       &cpufreq_freq_attr_controls,
+       &cpufreq_freq_attr_default_controls,
+       NULL,
+};
+
+static struct cpufreq_driver acpi_cpufreq_driver = {
+       .verify         = acpi_cpufreq_verify,
+       .target         = acpi_cpufreq_target,
+       .bios_limit     = acpi_processor_get_bios_limit,
+       .init           = acpi_cpufreq_cpu_init,
+       .exit           = acpi_cpufreq_cpu_exit,
+       .resume         = acpi_cpufreq_resume,
+       .name           = "acpi-cpufreq",
+       .owner          = THIS_MODULE,
+       .attr           = acpi_cpufreq_attr,
+};
+
+static int __init acpi_cpufreq_init(void)
+{
+       int ret;
+
+       if (acpi_disabled)
+               return 0;
+
+       pr_debug("acpi_cpufreq_init\n");
+
+       ret = acpi_cpufreq_early_init();
+       if (ret)
+               return ret;
+
+       ret = cpufreq_register_driver(&acpi_cpufreq_driver);
+       if (ret)
+               free_acpi_perf_data();
+
+       return ret;
+}
+
+static void __exit acpi_cpufreq_exit(void)
+{
+       pr_debug("acpi_cpufreq_exit\n");
+
+       cpufreq_unregister_driver(&acpi_cpufreq_driver);
+
+       free_acpi_perf_data();
+}
+
+module_param(acpi_pstate_strict, uint, 0644);
+MODULE_PARM_DESC(acpi_pstate_strict,
+       "value 0 or non-zero. non-zero -> strict ACPI checks are "
+       "performed during frequency changes.");
+
+static int param_set_phc_controls(const char *controls, struct kernel_param *kp)
+{
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               store_freq_attr_controls(cpufreq_cpu_get(cpu), controls, 0);
+       }
+       return 0;
+}
+
+module_param_call(phc_controls, param_set_phc_controls, NULL, NULL, 0644);
+__MODULE_PARM_TYPE(phc_controls, "string");
+MODULE_PARM_DESC(phc_controls, "Set initial phc_controls");
+
+late_initcall(acpi_cpufreq_init);
+module_exit(acpi_cpufreq_exit);
+
+MODULE_ALIAS("acpi");
index 6fdeb20..1ad080b 100644 (file)
@@ -1,10 +1,10 @@
-# system.prop for x41t
+# system.prop for ibm thinkpad
 ro.sf.lcd_density=160
 ro.sf.install_non_market_apps=1
 keyguard.no_require_sim=1
 wifi.supplicant_scan_interval=120
 backlight.brightness_file=/sys/class/backlight/thinkpad_screen/brightness
 backlight.max_brightness_file=/sys/class/backlight/thinkpad_screen/max_brightness
-battery.poll_interval=30
+#battery.poll_interval=30
 persist.sys.ui.hw=true
-# end system.prop for x41t
+# end system.prop for ibm thinkpad
diff --git a/thinkpad.mk b/thinkpad.mk
new file mode 100644 (file)
index 0000000..9515bd8
--- /dev/null
@@ -0,0 +1,23 @@
+PRODUCT_PACKAGES := $(THIRD_PARTY_APPS)
+PRODUCT_PACKAGES += lights.default
+PRODUCT_PACKAGES += sensors.$(TARGET_PRODUCT)
+PRODUCT_PACKAGES += wacom-input
+PRODUCT_PACKAGES += tablet-mode
+PRODUCT_PACKAGES += su Superuser FileManager alsa_amixer radiooptions rild libreference-ril libjni_pinyinime PinyinIME libskiagpu
+
+$(call inherit-product,$(SRC_TARGET_DIR)/product/generic_x86.mk)
+
+PRODUCT_NAME := thinkpad
+PRODUCT_DEVICE := thinkpad
+PRODUCT_MANUFACTURER := ibm
+
+DEVICE_PACKAGE_OVERLAYS := $(LOCAL_PATH)/overlays
+
+PRODUCT_COPY_FILES += \
+    $(LOCAL_PATH)/ueventd.$(TARGET_PRODUCT).rc:root/ueventd.$(TARGET_PRODUCT).rc \
+    $(LOCAL_PATH)/wacom-input.idc:system/usr/idc/wacom-input.idc \
+    $(LOCAL_PATH)/AT_Translated_Set_2_keyboard.idc:system/usr/idc/AT_Translated_Set_2_keyboard.idc \
+
+-include $(LOCAL_PATH)/system/Makefile.copy-files
+
+include $(call all-subdir-makefiles)
similarity index 98%
rename from x41t_defconfig
rename to thinkpad_defconfig
index cb4f1ee..3bb3eb1 100644 (file)
@@ -477,8 +477,8 @@ CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
 #
 # x86 CPU frequency scaling drivers
 #
-# CONFIG_X86_PCC_CPUFREQ is not set
-CONFIG_X86_ACPI_CPUFREQ=y
+CONFIG_X86_PCC_CPUFREQ=m
+CONFIG_X86_ACPI_CPUFREQ=m
 CONFIG_X86_POWERNOW_K6=m
 CONFIG_X86_POWERNOW_K7=m
 CONFIG_X86_POWERNOW_K7_ACPI=y
@@ -944,7 +944,6 @@ CONFIG_BLK_DEV_RAM_SIZE=4096
 CONFIG_SENSORS_LIS3LV02D=m
 CONFIG_MISC_DEVICES=y
 # CONFIG_AD525X_DPOT is not set
-# CONFIG_ANDROID_PMEM is not set
 # CONFIG_IBM_ASM is not set
 # CONFIG_PHANTOM is not set
 # CONFIG_INTEL_MID_PTI is not set
@@ -1255,7 +1254,9 @@ CONFIG_ATL1C=m
 # CONFIG_TR is not set
 CONFIG_WLAN=y
 CONFIG_PCMCIA_RAYCS=m
-# CONFIG_LIBERTAS_THINFIRM is not set
+CONFIG_LIBERTAS_THINFIRM=m
+# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set
+CONFIG_LIBERTAS_THINFIRM_USB=m
 CONFIG_AIRO=m
 CONFIG_ATMEL=m
 CONFIG_PCI_ATMEL=m
@@ -1265,11 +1266,11 @@ CONFIG_AIRO_CS=m
 CONFIG_PCMCIA_WL3501=m
 CONFIG_PRISM54=m
 CONFIG_USB_ZD1201=m
-# CONFIG_USB_NET_RNDIS_WLAN is not set
+CONFIG_USB_NET_RNDIS_WLAN=m
 CONFIG_RTL8180=m
 CONFIG_RTL8187=m
 CONFIG_RTL8187_LEDS=y
-# CONFIG_ADM8211 is not set
+CONFIG_ADM8211=m
 # CONFIG_MAC80211_HWSIM is not set
 CONFIG_MWL8K=m
 # CONFIG_WIFI_CONTROL_FUNC is not set
@@ -1288,14 +1289,35 @@ CONFIG_ATH9K_HTC=m
 CONFIG_CARL9170=m
 CONFIG_CARL9170_LEDS=y
 CONFIG_CARL9170_WPC=y
-# CONFIG_B43 is not set
-# CONFIG_B43LEGACY is not set
+CONFIG_B43=m
+CONFIG_B43_PCI_AUTOSELECT=y
+CONFIG_B43_PCICORE_AUTOSELECT=y
+CONFIG_B43_PCMCIA=y
+CONFIG_B43_SDIO=y
+CONFIG_B43_PIO=y
+CONFIG_B43_PHY_N=y
+CONFIG_B43_PHY_LP=y
+CONFIG_B43_LEDS=y
+CONFIG_B43_HWRNG=y
+# CONFIG_B43_DEBUG is not set
+CONFIG_B43LEGACY=m
+CONFIG_B43LEGACY_PCI_AUTOSELECT=y
+CONFIG_B43LEGACY_PCICORE_AUTOSELECT=y
+CONFIG_B43LEGACY_LEDS=y
+CONFIG_B43LEGACY_HWRNG=y
+CONFIG_B43LEGACY_DEBUG=y
+CONFIG_B43LEGACY_DMA=y
+CONFIG_B43LEGACY_PIO=y
+CONFIG_B43LEGACY_DMA_AND_PIO_MODE=y
+# CONFIG_B43LEGACY_DMA_MODE is not set
+# CONFIG_B43LEGACY_PIO_MODE is not set
 CONFIG_BCM4329=m
 CONFIG_BCM4329_FW_PATH="/system/etc/firmware/fw_bcm4329.bin"
 CONFIG_BCM4329_NVRAM_PATH="/proc/calibration"
 CONFIG_BCMDHD=m
 CONFIG_BCMDHD_FW_PATH="/system/etc/firmware/fw_bcmdhd.bin"
 CONFIG_BCMDHD_NVRAM_PATH="/system/etc/wifi/bcmdhd.cal"
+# CONFIG_DHD_USE_STATIC_BUF is not set
 # CONFIG_HOSTAP is not set
 CONFIG_IPW2100=m
 # CONFIG_IPW2100_MONITOR is not set
@@ -1323,7 +1345,12 @@ CONFIG_IWLWIFI_LEGACY=m
 CONFIG_IWL4965=m
 CONFIG_IWL3945=m
 CONFIG_IWM=m
-# CONFIG_LIBERTAS is not set
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_CS=m
+CONFIG_LIBERTAS_SDIO=m
+# CONFIG_LIBERTAS_DEBUG is not set
+# CONFIG_LIBERTAS_MESH is not set
 CONFIG_HERMES=m
 CONFIG_HERMES_PRISM=y
 CONFIG_HERMES_CACHE_FW_ON_INIT=y
@@ -1334,7 +1361,10 @@ CONFIG_PCI_HERMES=m
 CONFIG_PCMCIA_HERMES=m
 CONFIG_PCMCIA_SPECTRUM=m
 CONFIG_ORINOCO_USB=m
-# CONFIG_P54_COMMON is not set
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_P54_PCI=m
+CONFIG_P54_LEDS=y
 CONFIG_RT2X00=m
 # CONFIG_RT2400PCI is not set
 # CONFIG_RT2500PCI is not set
@@ -1364,8 +1394,14 @@ CONFIG_RTL8192CU=m
 CONFIG_RTLWIFI=m
 CONFIG_RTL8192C_COMMON=m
 CONFIG_WL=m
-# CONFIG_WL1251 is not set
-# CONFIG_WL12XX_MENU is not set
+CONFIG_WL1251=m
+CONFIG_WL1251_SDIO=m
+CONFIG_WL12XX_MENU=m
+CONFIG_WL12XX=m
+CONFIG_WL12XX_HT=y
+CONFIG_WL12XX_SDIO=m
+# CONFIG_WL12XX_SDIO_TEST is not set
+CONFIG_WL12XX_PLATFORM_DATA=y
 CONFIG_ZD1211RW=m
 # CONFIG_ZD1211RW_DEBUG is not set
 CONFIG_MWIFIEX=m
@@ -1555,6 +1591,7 @@ CONFIG_TOUCHSCREEN_WM9712=y
 CONFIG_TOUCHSCREEN_WM9713=y
 CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
 CONFIG_TOUCHSCREEN_USB_EGALAX=y
+# CONFIG_TOUCHSCREEN_USB_EGALAX_REVERSE is not set
 CONFIG_TOUCHSCREEN_USB_PANJIT=y
 CONFIG_TOUCHSCREEN_USB_3M=y
 CONFIG_TOUCHSCREEN_USB_ITM=y
@@ -1901,13 +1938,14 @@ CONFIG_SSB_POSSIBLE=y
 #
 CONFIG_SSB=m
 CONFIG_SSB_SPROM=y
+CONFIG_SSB_BLOCKIO=y
 CONFIG_SSB_PCIHOST_POSSIBLE=y
 CONFIG_SSB_PCIHOST=y
-# CONFIG_SSB_B43_PCI_BRIDGE is not set
+CONFIG_SSB_B43_PCI_BRIDGE=y
 CONFIG_SSB_PCMCIAHOST_POSSIBLE=y
-# CONFIG_SSB_PCMCIAHOST is not set
+CONFIG_SSB_PCMCIAHOST=y
 CONFIG_SSB_SDIOHOST_POSSIBLE=y
-# CONFIG_SSB_SDIOHOST is not set
+CONFIG_SSB_SDIOHOST=y
 # CONFIG_SSB_SILENT is not set
 # CONFIG_SSB_DEBUG is not set
 CONFIG_SSB_DRIVER_PCICORE_POSSIBLE=y
@@ -2358,7 +2396,7 @@ CONFIG_USB_HID=y
 #
 # Special HID drivers
 #
-# CONFIG_HID_A4TECH is not set
+CONFIG_HID_A4TECH=m
 # CONFIG_HID_ACRUX is not set
 # CONFIG_HID_APPLE is not set
 # CONFIG_HID_BELKIN is not set
@@ -2372,20 +2410,21 @@ CONFIG_USB_HID=y
 # CONFIG_HID_EZKEY is not set
 # CONFIG_HID_KEYTOUCH is not set
 # CONFIG_HID_KYE is not set
-# CONFIG_HID_UCLOGIC is not set
-# CONFIG_HID_WALTOP is not set
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
 # CONFIG_HID_GYRATION is not set
 # CONFIG_HID_TWINHAN is not set
 # CONFIG_HID_KENSINGTON is not set
 # CONFIG_HID_LCPOWER is not set
 # CONFIG_HID_LOGITECH is not set
-# CONFIG_HID_MAGICMOUSE is not set
-# CONFIG_HID_MICROSOFT is not set
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
 # CONFIG_HID_MONTEREY is not set
 CONFIG_HID_MULTITOUCH=m
-# CONFIG_HID_NTRIG is not set
-# CONFIG_HID_ORTEK is not set
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
 # CONFIG_HID_PANTHERLORD is not set
+CONFIG_HID_PENMOUNT=m
 # CONFIG_HID_PETALYNX is not set
 # CONFIG_HID_PICOLCD is not set
 # CONFIG_HID_ROCCAT is not set
@@ -2401,7 +2440,8 @@ CONFIG_HID_MULTITOUCH=m
 # CONFIG_HID_SMARTJOYPLUS is not set
 # CONFIG_HID_TOPSEED is not set
 # CONFIG_HID_THRUSTMASTER is not set
-# CONFIG_HID_WACOM is not set
+CONFIG_HID_WACOM=m
+CONFIG_HID_WACOM_POWER_SUPPLY=y
 # CONFIG_HID_ZEROPLUS is not set
 # CONFIG_HID_ZYDACRON is not set
 CONFIG_USB_SUPPORT=y
@@ -2820,7 +2860,7 @@ CONFIG_THINKPAD_ACPI_ALSA_SUPPORT=y
 CONFIG_THINKPAD_ACPI_UNSAFE_LEDS=y
 CONFIG_THINKPAD_ACPI_VIDEO=y
 CONFIG_THINKPAD_ACPI_HOTKEY_POLL=y
-CONFIG_SENSORS_HDAPS=m
+# CONFIG_SENSORS_HDAPS is not set
 CONFIG_INTEL_MENLOW=m
 CONFIG_EEEPC_LAPTOP=m
 CONFIG_ASUS_WMI=m
similarity index 58%
rename from x41t_info
rename to thinkpad_info
index c109f08..a8af23a 100644 (file)
--- a/x41t_info
@@ -7,5 +7,8 @@ detect_hardware()
        modprobe lib80211_crypt_wep || true
        modprobe lib80211_crypt_ccmp || true
        modprobe lib80211_crypt_tkip || true
+       modprobe wakeup_button || true
+       modprobe wacom || true
+       { grep -q phc_controls /proc/cmdline && modprobe phc_intel || modprobe acpi_cpufreq ; }
        return 1
 }
diff --git a/tp_smapi/Android.mk b/tp_smapi/Android.mk
new file mode 100644 (file)
index 0000000..698dc22
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# Copyright (C) 2009-2011 The Android-x86 Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := tp_smapi
+EXTRA_KERNEL_MODULE_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH)
diff --git a/tp_smapi/Makefile b/tp_smapi/Makefile
new file mode 100644 (file)
index 0000000..8747819
--- /dev/null
@@ -0,0 +1 @@
+obj-m += thinkpad_ec.o tp_smapi.o hdaps.o
diff --git a/tp_smapi/hdaps.c b/tp_smapi/hdaps.c
new file mode 100644 (file)
index 0000000..f278564
--- /dev/null
@@ -0,0 +1,891 @@
+/*
+ * drivers/platform/x86/hdaps.c - driver for IBM's Hard Drive Active Protection System
+ *
+ * Copyright (C) 2005 Robert Love <rml@novell.com>
+ * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
+ *
+ * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
+ * starting with the R40, T41, and X40.  It provides a basic two-axis
+ * accelerometer and other data, such as the device's temperature.
+ *
+ * This driver is based on the document by Mark A. Smith available at
+ * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
+ * and error.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License v2 as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include "thinkpad_ec.h"
+#include <linux/pci_ids.h>
+#include <linux/version.h>
+
+/* Embedded controller accelerometer read command and its result: */
+static const struct thinkpad_ec_row ec_accel_args =
+       { .mask = 0x0001, .val = {0x11} };
+#define EC_ACCEL_IDX_READOUTS  0x1     /* readouts included in this read */
+                                       /* First readout, if READOUTS>=1: */
+#define EC_ACCEL_IDX_YPOS1     0x2     /*   y-axis position word */
+#define EC_ACCEL_IDX_XPOS1     0x4     /*   x-axis position word */
+#define EC_ACCEL_IDX_TEMP1     0x6     /*   device temperature in Celsius */
+                                       /* Second readout, if READOUTS>=2: */
+#define EC_ACCEL_IDX_XPOS2     0x7     /*   y-axis position word */
+#define EC_ACCEL_IDX_YPOS2     0x9     /*   x-axis position word */
+#define EC_ACCEL_IDX_TEMP2     0xb     /*   device temperature in Celsius */
+#define EC_ACCEL_IDX_QUEUED    0xc     /* Number of queued readouts left */
+#define EC_ACCEL_IDX_KMACT     0xd     /* keyboard or mouse activity */
+#define EC_ACCEL_IDX_RETVAL    0xf     /* command return value, good=0x00 */
+
+#define KEYBD_MASK             0x20    /* set if keyboard activity */
+#define MOUSE_MASK             0x40    /* set if mouse activity */
+
+#define READ_TIMEOUT_MSECS     100     /* wait this long for device read */
+#define RETRY_MSECS            3       /* retry delay */
+
+#define HDAPS_INPUT_FUZZ       4       /* input event threshold */
+#define HDAPS_INPUT_FLAT       4
+#define KMACT_REMEMBER_PERIOD   (HZ/10) /* keyboard/mouse persistance */
+
+/* Input IDs */
+#define HDAPS_INPUT_VENDOR     PCI_VENDOR_ID_IBM
+#define HDAPS_INPUT_PRODUCT    0x5054 /* "TP", shared with thinkpad_acpi */
+#define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */
+#define HDAPS_INPUT_RAW_VERSION        0x4801 /* Raw accelerometer input device */
+
+/* Axis orientation. */
+/* The unnatural bit-representation of inversions is for backward
+ * compatibility with the"invert=1" module parameter.             */
+#define HDAPS_ORIENT_INVERT_XY  0x01   /* Invert both X and Y axes.       */
+#define HDAPS_ORIENT_INVERT_X   0x02   /* Invert the X axis (uninvert if
+                                       * already inverted by INVERT_XY). */
+#define HDAPS_ORIENT_SWAP       0x04   /* Swap the axes. The swap occurs
+                                       * before inverting X or Y.        */
+#define HDAPS_ORIENT_MAX        0x07
+#define HDAPS_ORIENT_UNDEFINED  0xFF   /* Placeholder during initialization */
+#define HDAPS_ORIENT_INVERT_Y   (HDAPS_ORIENT_INVERT_XY | HDAPS_ORIENT_INVERT_X)
+
+static struct timer_list hdaps_timer;
+static struct platform_device *pdev;
+static struct input_dev *hdaps_idev;     /* joystick-like device with fuzz */
+static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */
+static unsigned int hdaps_invert = HDAPS_ORIENT_UNDEFINED;
+static int needs_calibration;
+
+/* Configuration: */
+static int sampling_rate = 50;       /* Sampling rate  */
+static int oversampling_ratio = 5;   /* Ratio between our sampling rate and
+                                     * EC accelerometer sampling rate      */
+static int running_avg_filter_order = 2; /* EC running average filter order */
+
+/* Latest state readout: */
+static int pos_x, pos_y;      /* position */
+static int temperature;       /* temperature */
+static int stale_readout = 1; /* last read invalid */
+static int rest_x, rest_y;    /* calibrated rest position */
+
+/* Last time we saw keyboard and mouse activity: */
+static u64 last_keyboard_jiffies = INITIAL_JIFFIES;
+static u64 last_mouse_jiffies = INITIAL_JIFFIES;
+static u64 last_update_jiffies = INITIAL_JIFFIES;
+
+/* input device use count */
+static int hdaps_users;
+static DEFINE_MUTEX(hdaps_users_mtx);
+
+/* Some models require an axis transformation to the standard representation */
+static void transform_axes(int *x, int *y)
+{
+       if (hdaps_invert & HDAPS_ORIENT_SWAP) {
+               int z;
+               z = *x;
+               *x = *y;
+               *y = z;
+       }
+       if (hdaps_invert & HDAPS_ORIENT_INVERT_XY) {
+               *x = -*x;
+               *y = -*y;
+       }
+       if (hdaps_invert & HDAPS_ORIENT_INVERT_X)
+               *x = -*x;
+}
+
+/**
+ * __hdaps_update - query current state, with locks already acquired
+ * @fast: if nonzero, do one quick attempt without retries.
+ *
+ * Query current accelerometer state and update global state variables.
+ * Also prefetches the next query. Caller must hold controller lock.
+ */
+static int __hdaps_update(int fast)
+{
+       /* Read data: */
+       struct thinkpad_ec_row data;
+       int ret;
+
+       data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) |
+                   (3 << EC_ACCEL_IDX_YPOS1)    | (3 << EC_ACCEL_IDX_XPOS1) |
+                   (1 << EC_ACCEL_IDX_TEMP1)    | (1 << EC_ACCEL_IDX_RETVAL);
+       if (fast)
+               ret = thinkpad_ec_try_read_row(&ec_accel_args, &data);
+       else
+               ret = thinkpad_ec_read_row(&ec_accel_args, &data);
+       thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */
+       if (ret)
+               return ret;
+
+       /* Check status: */
+       if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) {
+               printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n",
+                      data.val[EC_ACCEL_IDX_RETVAL]);
+               return -EIO;
+       }
+
+       if (data.val[EC_ACCEL_IDX_READOUTS] < 1)
+               return -EBUSY; /* no pending readout, try again later */
+
+       /* Parse position data: */
+       pos_x = *(s16 *)(data.val+EC_ACCEL_IDX_XPOS1);
+       pos_y = *(s16 *)(data.val+EC_ACCEL_IDX_YPOS1);
+       transform_axes(&pos_x, &pos_y);
+
+       /* Keyboard and mouse activity status is cleared as soon as it's read,
+        * so applications will eat each other's events. Thus we remember any
+        * event for KMACT_REMEMBER_PERIOD jiffies.
+        */
+       if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK)
+               last_keyboard_jiffies = get_jiffies_64();
+       if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK)
+               last_mouse_jiffies = get_jiffies_64();
+
+       temperature = data.val[EC_ACCEL_IDX_TEMP1];
+
+       last_update_jiffies = get_jiffies_64();
+       stale_readout = 0;
+       if (needs_calibration) {
+               rest_x = pos_x;
+               rest_y = pos_y;
+               needs_calibration = 0;
+       }
+
+       return 0;
+}
+
+/**
+ * hdaps_update - acquire locks and query current state
+ *
+ * Query current accelerometer state and update global state variables.
+ * Also prefetches the next query.
+ * Retries until timeout if the accelerometer is not in ready status (common).
+ * Does its own locking.
+ */
+static int hdaps_update(void)
+{
+       u64 age = get_jiffies_64() - last_update_jiffies;
+       int total, ret;
+
+       if (!stale_readout && age < (9*HZ)/(10*sampling_rate))
+               return 0; /* already updated recently */
+       for (total = 0; total < READ_TIMEOUT_MSECS; total += RETRY_MSECS) {
+               ret = thinkpad_ec_lock();
+               if (ret)
+                       return ret;
+               ret = __hdaps_update(0);
+               thinkpad_ec_unlock();
+
+               if (!ret)
+                       return 0;
+               if (ret != -EBUSY)
+                       break;
+               msleep(RETRY_MSECS);
+       }
+       return ret;
+}
+
+/**
+ * hdaps_set_power - enable or disable power to the accelerometer.
+ * Returns zero on success and negative error code on failure.  Can sleep.
+ */
+static int hdaps_set_power(int on)
+{
+       struct thinkpad_ec_row args =
+               { .mask = 0x0003, .val = {0x14, on?0x01:0x00} };
+       struct thinkpad_ec_row data = { .mask = 0x8000 };
+       int ret = thinkpad_ec_read_row(&args, &data);
+       if (ret)
+               return ret;
+       if (data.val[0xF] != 0x00)
+               return -EIO;
+       return 0;
+}
+
+/**
+ * hdaps_set_ec_config - set accelerometer parameters.
+ * @ec_rate: embedded controller sampling rate
+ * @order: embedded controller running average filter order
+ * (Normally we have @ec_rate = sampling_rate * oversampling_ratio.)
+ * Returns zero on success and negative error code on failure.  Can sleep.
+ */
+static int hdaps_set_ec_config(int ec_rate, int order)
+{
+       struct thinkpad_ec_row args = { .mask = 0x000F,
+               .val = {0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} };
+       struct thinkpad_ec_row data = { .mask = 0x8000 };
+       int ret = thinkpad_ec_read_row(&args, &data);
+       printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n",
+              ec_rate, order);
+       if (ret)
+               return ret;
+       if (data.val[0xF] == 0x03) {
+               printk(KERN_WARNING "hdaps: config param out of range\n");
+               return -EINVAL;
+       }
+       if (data.val[0xF] == 0x06) {
+               printk(KERN_WARNING "hdaps: config change already pending\n");
+               return -EBUSY;
+       }
+       if (data.val[0xF] != 0x00) {
+               printk(KERN_WARNING "hdaps: config change error, ret=%d\n",
+                     data.val[0xF]);
+               return -EIO;
+       }
+       return 0;
+}
+
+/**
+ * hdaps_get_ec_config - get accelerometer parameters.
+ * @ec_rate: embedded controller sampling rate
+ * @order: embedded controller running average filter order
+ * Returns zero on success and negative error code on failure.  Can sleep.
+ */
+static int hdaps_get_ec_config(int *ec_rate, int *order)
+{
+       const struct thinkpad_ec_row args =
+               { .mask = 0x0003, .val = {0x17, 0x82} };
+       struct thinkpad_ec_row data = { .mask = 0x801F };
+       int ret = thinkpad_ec_read_row(&args, &data);
+       if (ret)
+               return ret;
+       if (data.val[0xF] != 0x00)
+               return -EIO;
+       if (!(data.val[0x1] & 0x01))
+               return -ENXIO; /* accelerometer polling not enabled */
+       if (data.val[0x1] & 0x02)
+               return -EBUSY; /* config change in progress, retry later */
+       *ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8);
+       *order = data.val[0x4];
+       return 0;
+}
+
+/**
+ * hdaps_get_ec_mode - get EC accelerometer mode
+ * Returns zero on success and negative error code on failure.  Can sleep.
+ */
+static int hdaps_get_ec_mode(u8 *mode)
+{
+       const struct thinkpad_ec_row args =
+               { .mask = 0x0001, .val = {0x13} };
+       struct thinkpad_ec_row data = { .mask = 0x8002 };
+       int ret = thinkpad_ec_read_row(&args, &data);
+       if (ret)
+               return ret;
+       if (data.val[0xF] != 0x00) {
+               printk(KERN_WARNING
+                      "accelerometer not implemented (0x%02x)\n",
+                      data.val[0xF]);
+               return -EIO;
+       }
+       *mode = data.val[0x1];
+       return 0;
+}
+
+/**
+ * hdaps_check_ec - checks something about the EC.
+ * Follows the clean-room spec for HDAPS; we don't know what it means.
+ * Returns zero on success and negative error code on failure.  Can sleep.
+ */
+static int hdaps_check_ec(void)
+{
+       const struct thinkpad_ec_row args =
+               { .mask = 0x0003, .val = {0x17, 0x81} };
+       struct thinkpad_ec_row data = { .mask = 0x800E };
+       int ret = thinkpad_ec_read_row(&args, &data);
+       if (ret)
+               return  ret;
+       if (!((data.val[0x1] == 0x00 && data.val[0x2] == 0x60) || /* cleanroom spec */
+             (data.val[0x1] == 0x01 && data.val[0x2] == 0x00)) || /* seen on T61 */
+           data.val[0x3] != 0x00 || data.val[0xF] != 0x00) {
+               printk(KERN_WARNING
+                      "hdaps_check_ec: bad response (0x%x,0x%x,0x%x,0x%x)\n",
+                      data.val[0x1], data.val[0x2],
+                      data.val[0x3], data.val[0xF]);
+               return -EIO;
+       }
+       return 0;
+}
+
+/**
+ * hdaps_device_init - initialize the accelerometer.
+ *
+ * Call several embedded controller functions to test and initialize the
+ * accelerometer.
+ * Returns zero on success and negative error code on failure. Can sleep.
+ */
+#define FAILED_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg)
+static int hdaps_device_init(void)
+{
+       int ret;
+       u8 mode;
+
+       ret = thinkpad_ec_lock();
+       if (ret)
+               return ret;
+
+       if (hdaps_get_ec_mode(&mode))
+               { FAILED_INIT("hdaps_get_ec_mode failed"); goto bad; }
+
+       printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode);
+       if (mode == 0x00)
+               { FAILED_INIT("accelerometer not available"); goto bad; }
+
+       if (hdaps_check_ec())
+               { FAILED_INIT("hdaps_check_ec failed"); goto bad; }
+
+       if (hdaps_set_power(1))
+               { FAILED_INIT("hdaps_set_power failed"); goto bad; }
+
+       if (hdaps_set_ec_config(sampling_rate*oversampling_ratio,
+                               running_avg_filter_order))
+               { FAILED_INIT("hdaps_set_ec_config failed"); goto bad; }
+
+       thinkpad_ec_invalidate();
+       udelay(200);
+
+       /* Just prefetch instead of reading, to avoid ~1sec delay on load */
+       ret = thinkpad_ec_prefetch_row(&ec_accel_args);
+       if (ret)
+               { FAILED_INIT("initial prefetch failed"); goto bad; }
+       goto good;
+bad:
+       thinkpad_ec_invalidate();
+       ret = -ENXIO;
+good:
+       stale_readout = 1;
+       thinkpad_ec_unlock();
+       return ret;
+}
+
+/**
+ * hdaps_device_shutdown - power off the accelerometer
+ * Returns nonzero on failure. Can sleep.
+ */
+static int hdaps_device_shutdown(void)
+{
+       int ret;
+       ret = hdaps_set_power(0);
+       if (ret) {
+               printk(KERN_WARNING "hdaps: cannot power off\n");
+               return ret;
+       }
+       ret = hdaps_set_ec_config(0, 1);
+       if (ret)
+               printk(KERN_WARNING "hdaps: cannot stop EC sampling\n");
+       return ret;
+}
+
+/* Device model stuff */
+
+static int hdaps_probe(struct platform_device *dev)
+{
+       int ret;
+
+       ret = hdaps_device_init();
+       if (ret)
+               return ret;
+
+       printk(KERN_INFO "hdaps: device successfully initialized.\n");
+       return 0;
+}
+
+static int hdaps_suspend(struct platform_device *dev, pm_message_t state)
+{
+       /* Don't do hdaps polls until resume re-initializes the sensor. */
+       del_timer_sync(&hdaps_timer);
+       hdaps_device_shutdown(); /* ignore errors, effect is negligible */
+       return 0;
+}
+
+static int hdaps_resume(struct platform_device *dev)
+{
+       int ret = hdaps_device_init();
+       if (ret)
+               return ret;
+
+       mutex_lock(&hdaps_users_mtx);
+       if (hdaps_users)
+               mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
+       mutex_unlock(&hdaps_users_mtx);
+       return 0;
+}
+
+static struct platform_driver hdaps_driver = {
+       .probe = hdaps_probe,
+       .suspend = hdaps_suspend,
+       .resume = hdaps_resume,
+       .driver = {
+               .name = "hdaps",
+               .owner = THIS_MODULE,
+       },
+};
+
+/**
+ * hdaps_calibrate - set our "resting" values.
+ * Does its own locking.
+ */
+static void hdaps_calibrate(void)
+{
+       needs_calibration = 1;
+       hdaps_update();
+       /* If that fails, the mousedev poll will take care of things later. */
+}
+
+/* Timer handler for updating the input device. Runs in softirq context,
+ * so avoid lenghty or blocking operations.
+ */
+static void hdaps_mousedev_poll(unsigned long unused)
+{
+       int ret;
+
+       stale_readout = 1;
+
+       /* Cannot sleep.  Try nonblockingly.  If we fail, try again later. */
+       if (thinkpad_ec_try_lock())
+               goto keep_active;
+
+       ret = __hdaps_update(1); /* fast update, we're in softirq context */
+       thinkpad_ec_unlock();
+       /* Any of "successful", "not yet ready" and "not prefetched"? */
+       if (ret != 0 && ret != -EBUSY && ret != -ENODATA) {
+               printk(KERN_ERR
+                      "hdaps: poll failed, disabling updates\n");
+               return;
+       }
+
+keep_active:
+       /* Even if we failed now, pos_x,y may have been updated earlier: */
+       input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x);
+       input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y);
+       input_sync(hdaps_idev);
+       input_report_abs(hdaps_idev_raw, ABS_X, pos_x);
+       input_report_abs(hdaps_idev_raw, ABS_Y, pos_y);
+       input_sync(hdaps_idev_raw);
+       mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
+}
+
+
+/* Sysfs Files */
+
+static ssize_t hdaps_position_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       int ret = hdaps_update();
+       if (ret)
+               return ret;
+       return sprintf(buf, "(%d,%d)\n", pos_x, pos_y);
+}
+
+static ssize_t hdaps_temp1_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       int ret = hdaps_update();
+       if (ret)
+               return ret;
+       return sprintf(buf, "%d\n", temperature);
+}
+
+static ssize_t hdaps_keyboard_activity_show(struct device *dev,
+                                           struct device_attribute *attr,
+                                           char *buf)
+{
+       int ret = hdaps_update();
+       if (ret)
+               return ret;
+       return sprintf(buf, "%u\n",
+          get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD);
+}
+
+static ssize_t hdaps_mouse_activity_show(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       int ret = hdaps_update();
+       if (ret)
+               return ret;
+       return sprintf(buf, "%u\n",
+          get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD);
+}
+
+static ssize_t hdaps_calibrate_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
+}
+
+static ssize_t hdaps_calibrate_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       hdaps_calibrate();
+       return count;
+}
+
+static ssize_t hdaps_invert_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%u\n", hdaps_invert);
+}
+
+static ssize_t hdaps_invert_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t count)
+{
+       int invert;
+
+       if (sscanf(buf, "%d", &invert) != 1 ||
+           invert < 0 || invert > HDAPS_ORIENT_MAX)
+               return -EINVAL;
+
+       hdaps_invert = invert;
+       hdaps_calibrate();
+
+       return count;
+}
+
+static ssize_t hdaps_sampling_rate_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", sampling_rate);
+}
+
+static ssize_t hdaps_sampling_rate_store(
+       struct device *dev, struct device_attribute *attr,
+       const char *buf, size_t count)
+{
+       int rate, ret;
+       if (sscanf(buf, "%d", &rate) != 1 || rate > HZ || rate <= 0) {
+               printk(KERN_WARNING
+                      "must have 0<input_sampling_rate<=HZ=%d\n", HZ);
+               return -EINVAL;
+       }
+       ret = hdaps_set_ec_config(rate*oversampling_ratio,
+                                 running_avg_filter_order);
+       if (ret)
+               return ret;
+       sampling_rate = rate;
+       return count;
+}
+
+static ssize_t hdaps_oversampling_ratio_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       int ec_rate, order;
+       int ret = hdaps_get_ec_config(&ec_rate, &order);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%u\n", ec_rate / sampling_rate);
+}
+
+static ssize_t hdaps_oversampling_ratio_store(
+       struct device *dev, struct device_attribute *attr,
+       const char *buf, size_t count)
+{
+       int ratio, ret;
+       if (sscanf(buf, "%d", &ratio) != 1 || ratio < 1)
+               return -EINVAL;
+       ret = hdaps_set_ec_config(sampling_rate*ratio,
+                                 running_avg_filter_order);
+       if (ret)
+               return ret;
+       oversampling_ratio = ratio;
+       return count;
+}
+
+static ssize_t hdaps_running_avg_filter_order_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       int rate, order;
+       int ret = hdaps_get_ec_config(&rate, &order);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%u\n", order);
+}
+
+static ssize_t hdaps_running_avg_filter_order_store(
+       struct device *dev, struct device_attribute *attr,
+       const char *buf, size_t count)
+{
+       int order, ret;
+       if (sscanf(buf, "%d", &order) != 1)
+               return -EINVAL;
+       ret = hdaps_set_ec_config(sampling_rate*oversampling_ratio, order);
+       if (ret)
+               return ret;
+       running_avg_filter_order = order;
+       return count;
+}
+
+static int hdaps_mousedev_open(struct input_dev *dev)
+{
+       if (!try_module_get(THIS_MODULE))
+               return -ENODEV;
+
+       mutex_lock(&hdaps_users_mtx);
+       if (hdaps_users++ == 0) /* first input user */
+               mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
+       mutex_unlock(&hdaps_users_mtx);
+       return 0;
+}
+
+static void hdaps_mousedev_close(struct input_dev *dev)
+{
+       mutex_lock(&hdaps_users_mtx);
+       if (--hdaps_users == 0) /* no input users left */
+               del_timer_sync(&hdaps_timer);
+       mutex_unlock(&hdaps_users_mtx);
+
+       module_put(THIS_MODULE);
+}
+
+static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
+static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
+  /* "temp1" instead of "temperature" is hwmon convention */
+static DEVICE_ATTR(keyboard_activity, 0444,
+                  hdaps_keyboard_activity_show, NULL);
+static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
+static DEVICE_ATTR(calibrate, 0644,
+                  hdaps_calibrate_show, hdaps_calibrate_store);
+static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
+static DEVICE_ATTR(sampling_rate, 0644,
+                  hdaps_sampling_rate_show, hdaps_sampling_rate_store);
+static DEVICE_ATTR(oversampling_ratio, 0644,
+                  hdaps_oversampling_ratio_show,
+                  hdaps_oversampling_ratio_store);
+static DEVICE_ATTR(running_avg_filter_order, 0644,
+                  hdaps_running_avg_filter_order_show,
+                  hdaps_running_avg_filter_order_store);
+
+static struct attribute *hdaps_attributes[] = {
+       &dev_attr_position.attr,
+       &dev_attr_temp1.attr,
+       &dev_attr_keyboard_activity.attr,
+       &dev_attr_mouse_activity.attr,
+       &dev_attr_calibrate.attr,
+       &dev_attr_invert.attr,
+       &dev_attr_sampling_rate.attr,
+       &dev_attr_oversampling_ratio.attr,
+       &dev_attr_running_avg_filter_order.attr,
+       NULL,
+};
+
+static struct attribute_group hdaps_attribute_group = {
+       .attrs = hdaps_attributes,
+};
+
+
+/* Module stuff */
+
+/* hdaps_dmi_match_invert - found an inverted match. */
+static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
+{
+       unsigned int orient = (kernel_ulong_t) id->driver_data;
+       hdaps_invert = orient;
+       printk(KERN_INFO "hdaps: %s detected, setting orientation %u\n",
+              id->ident, orient);
+       return 1; /* stop enumeration */
+}
+
+#define HDAPS_DMI_MATCH_INVERT(vendor, model, orient) { \
+       .ident = vendor " " model,                      \
+       .callback = hdaps_dmi_match_invert,             \
+       .driver_data = (void *)(orient),                \
+       .matches = {                                    \
+               DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
+               DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
+       }                                               \
+}
+
+/* List of models with abnormal axis configuration.
+   Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
+   "ThinkPad T42p", and enumeration stops after first match,
+   so the order of the entries matters. */
+struct dmi_system_id __initdata hdaps_whitelist[] = {
+       HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X40", HDAPS_ORIENT_INVERT_Y),
+       HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_ORIENT_INVERT_Y),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60 Tablet", HDAPS_ORIENT_INVERT_Y),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60s", HDAPS_ORIENT_INVERT_Y),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400s", HDAPS_ORIENT_INVERT_X),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410s", HDAPS_ORIENT_SWAP),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T500", HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W51O", HDAPS_ORIENT_MAX),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201 Tablet", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
+       HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X220", HDAPS_ORIENT_SWAP),
+       { .ident = NULL }
+};
+
+static int __init hdaps_init(void)
+{
+       int ret;
+
+       /* Determine axis orientation orientation */
+       if (hdaps_invert == HDAPS_ORIENT_UNDEFINED) /* set by module param? */
+               if (dmi_check_system(hdaps_whitelist) < 1) /* in whitelist? */
+                       hdaps_invert = 0; /* default */
+
+       /* Init timer before platform_driver_register, in case of suspend */
+       init_timer(&hdaps_timer);
+       hdaps_timer.function = hdaps_mousedev_poll;
+       ret = platform_driver_register(&hdaps_driver);
+       if (ret)
+               goto out;
+
+       pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
+       if (IS_ERR(pdev)) {
+               ret = PTR_ERR(pdev);
+               goto out_driver;
+       }
+
+       ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
+       if (ret)
+               goto out_device;
+
+       hdaps_idev = input_allocate_device();
+       if (!hdaps_idev) {
+               ret = -ENOMEM;
+               goto out_group;
+       }
+
+       hdaps_idev_raw = input_allocate_device();
+       if (!hdaps_idev_raw) {
+               ret = -ENOMEM;
+               goto out_idev_first;
+       }
+
+       /* calibration for the input device (deferred to avoid delay) */
+       needs_calibration = 1;
+
+       /* initialize the joystick-like fuzzed input device */
+       hdaps_idev->name = "hdaps";
+       hdaps_idev->phys = "hdaps/input0";
+       hdaps_idev->id.bustype = BUS_HOST;
+       hdaps_idev->id.vendor  = HDAPS_INPUT_VENDOR;
+       hdaps_idev->id.product = HDAPS_INPUT_PRODUCT;
+       hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+       hdaps_idev->cdev.dev = &pdev->dev;
+#endif
+       hdaps_idev->evbit[0] = BIT(EV_ABS);
+       hdaps_idev->open = hdaps_mousedev_open;
+       hdaps_idev->close = hdaps_mousedev_close;
+       input_set_abs_params(hdaps_idev, ABS_X,
+                       -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
+       input_set_abs_params(hdaps_idev, ABS_Y,
+                       -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
+
+       ret = input_register_device(hdaps_idev);
+       if (ret)
+               goto out_idev;
+
+       /* initialize the raw data input device */
+       hdaps_idev_raw->name = "raw_hdaps_data";
+       hdaps_idev_raw->phys = "hdaps/input1";
+       hdaps_idev_raw->id.bustype = BUS_HOST;
+       hdaps_idev_raw->id.vendor  = HDAPS_INPUT_VENDOR;
+       hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT;
+       hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+       hdaps_idev_raw->cdev.dev = &pdev->dev;
+#endif
+       hdaps_idev_raw->evbit[0] = BIT(EV_ABS);
+       hdaps_idev_raw->open = hdaps_mousedev_open;
+       hdaps_idev_raw->close = hdaps_mousedev_close;
+       input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0);
+       input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0);
+
+       ret = input_register_device(hdaps_idev_raw);
+       if (ret)
+               goto out_idev_reg_first;
+
+       printk(KERN_INFO "hdaps: driver successfully loaded.\n");
+       return 0;
+
+out_idev_reg_first:
+       input_unregister_device(hdaps_idev);
+out_idev:
+       input_free_device(hdaps_idev_raw);
+out_idev_first:
+       input_free_device(hdaps_idev);
+out_group:
+       sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
+out_device:
+       platform_device_unregister(pdev);
+out_driver:
+       platform_driver_unregister(&hdaps_driver);
+       hdaps_device_shutdown();
+out:
+       printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
+       return ret;
+}
+
+static void __exit hdaps_exit(void)
+{
+       input_unregister_device(hdaps_idev_raw);
+       input_unregister_device(hdaps_idev);
+       hdaps_device_shutdown(); /* ignore errors, effect is negligible */
+       sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
+       platform_device_unregister(pdev);
+       platform_driver_unregister(&hdaps_driver);
+
+       printk(KERN_INFO "hdaps: driver unloaded.\n");
+}
+
+module_init(hdaps_init);
+module_exit(hdaps_exit);
+
+module_param_named(invert, hdaps_invert, uint, 0);
+MODULE_PARM_DESC(invert, "axis orientation code");
+
+MODULE_AUTHOR("Robert Love");
+MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/tp_smapi/thinkpad_ec.c b/tp_smapi/thinkpad_ec.c
new file mode 100644 (file)
index 0000000..a1c94d9
--- /dev/null
@@ -0,0 +1,513 @@
+/*
+ *  thinkpad_ec.c - ThinkPad embedded controller LPC3 functions
+ *
+ *  The embedded controller on ThinkPad laptops has a non-standard interface,
+ *  where LPC channel 3 of the H8S EC chip is hooked up to IO ports
+ *  0x1600-0x161F and implements (a special case of) the H8S LPC protocol.
+ *  The EC LPC interface provides various system management services (currently
+ *  known: battery information and accelerometer readouts). This driver
+ *  provides access and mutual exclusion for the EC interface.
+*
+ *  The LPC protocol and terminology are documented here:
+ *  "H8S/2104B Group Hardware Manual",
+ *  http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
+ *
+ *  Copyright (C) 2006-2007 Shem Multinymous <multinymous@gmail.com>
+ *
+ *  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
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include "thinkpad_ec.h"
+#include <linux/jiffies.h>
+#include <asm/io.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+       #include <asm/semaphore.h>
+#else
+       #include <linux/semaphore.h>
+#endif
+
+#define TP_VERSION "0.41"
+
+MODULE_AUTHOR("Shem Multinymous");
+MODULE_DESCRIPTION("ThinkPad embedded controller hardware access");
+MODULE_VERSION(TP_VERSION);
+MODULE_LICENSE("GPL");
+
+/* IO ports used by embedded controller LPC channel 3: */
+#define TPC_BASE_PORT 0x1600
+#define TPC_NUM_PORTS 0x20
+#define TPC_STR3_PORT 0x1604  /* Reads H8S EC register STR3 */
+#define TPC_TWR0_PORT  0x1610 /* Mapped to H8S EC register TWR0MW/SW  */
+#define TPC_TWR15_PORT 0x161F /* Mapped to H8S EC register TWR15. */
+  /* (and port TPC_TWR0_PORT+i is mapped to H8S reg TWRi for 0<i<16) */
+
+/* H8S STR3 status flags (see "H8S/2104B Group Hardware Manual" p.549) */
+#define H8S_STR3_IBF3B 0x80  /* Bidi. Data Register Input Buffer Full */
+#define H8S_STR3_OBF3B 0x40  /* Bidi. Data Register Output Buffer Full */
+#define H8S_STR3_MWMF  0x20  /* Master Write Mode Flag */
+#define H8S_STR3_SWMF  0x10  /* Slave Write Mode Flag */
+#define H8S_STR3_MASK  0xF0  /* All bits we care about in STR3 */
+
+/* Timeouts and retries */
+#define TPC_READ_RETRIES     150
+#define TPC_READ_NDELAY      500
+#define TPC_REQUEST_RETRIES 1000
+#define TPC_REQUEST_NDELAY    10
+#define TPC_PREFETCH_TIMEOUT   (HZ/10)  /* invalidate prefetch after 0.1sec */
+
+/* A few macros for printk()ing: */
+#define MSG_FMT(fmt, args...) \
+  "thinkpad_ec: %s: " fmt "\n", __func__, ## args
+#define REQ_FMT(msg, code) \
+  MSG_FMT("%s: (0x%02x:0x%02x)->0x%02x", \
+         msg, args->val[0x0], args->val[0xF], code)
+
+/* State of request prefetching: */
+static u8 prefetch_arg0, prefetch_argF;           /* Args of last prefetch */
+static u64 prefetch_jiffies;                      /* time of prefetch, or: */
+#define TPC_PREFETCH_NONE   INITIAL_JIFFIES       /*   No prefetch */
+#define TPC_PREFETCH_JUNK   (INITIAL_JIFFIES+1)   /*   Ignore prefetch */
+
+/* Locking: */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
+static DECLARE_MUTEX(thinkpad_ec_mutex);
+#else
+static DEFINE_SEMAPHORE(thinkpad_ec_mutex);
+#endif
+
+/* Kludge in case the ACPI DSDT reserves the ports we need. */
+static int force_io;    /* Willing to do IO to ports we couldn't reserve? */
+static int reserved_io; /* Successfully reserved the ports? */
+module_param_named(force_io, force_io, bool, 0600);
+MODULE_PARM_DESC(force_io, "Force IO even if region already reserved (0=off, 1=on)");
+
+/**
+ * thinkpad_ec_lock - get lock on the ThinkPad EC
+ *
+ * Get exclusive lock for accesing the ThinkPad embedded controller LPC3
+ * interface. Returns 0 iff lock acquired.
+ */
+int thinkpad_ec_lock(void)
+{
+       int ret;
+       ret = down_interruptible(&thinkpad_ec_mutex);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_lock);
+
+/**
+ * thinkpad_ec_try_lock - try getting lock on the ThinkPad EC
+ *
+ * Try getting an exclusive lock for accesing the ThinkPad embedded
+ * controller LPC3. Returns immediately if lock is not available; neither
+ * blocks nor sleeps. Returns 0 iff lock acquired .
+ */
+int thinkpad_ec_try_lock(void)
+{
+       return down_trylock(&thinkpad_ec_mutex);
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_try_lock);
+
+/**
+ * thinkpad_ec_unlock - release lock on ThinkPad EC
+ *
+ * Release a previously acquired exclusive lock on the ThinkPad ebmedded
+ * controller LPC3 interface.
+ */
+void thinkpad_ec_unlock(void)
+{
+       up(&thinkpad_ec_mutex);
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_unlock);
+
+/**
+ * thinkpad_ec_request_row - tell embedded controller to prepare a row
+ * @args Input register arguments
+ *
+ * Requests a data row by writing to H8S LPC registers TRW0 through TWR15 (or
+ * a subset thereof) following the protocol prescribed by the "H8S/2104B Group
+ * Hardware Manual". Does sanity checks via status register STR3.
+ */
+static int thinkpad_ec_request_row(const struct thinkpad_ec_row *args)
+{
+       u8 str3;
+       int i;
+
+       /* EC protocol requires write to TWR0 (function code): */
+       if (!(args->mask & 0x0001)) {
+               printk(KERN_ERR MSG_FMT("bad args->mask=0x%02x", args->mask));
+               return -EINVAL;
+       }
+
+       /* Check initial STR3 status: */
+       str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
+       if (str3 & H8S_STR3_OBF3B) { /* data already pending */
+               inb(TPC_TWR15_PORT); /* marks end of previous transaction */
+               if (prefetch_jiffies == TPC_PREFETCH_NONE)
+                       printk(KERN_WARNING REQ_FMT(
+                              "EC has result from unrequested transaction",
+                              str3));
+               return -EBUSY; /* EC will be ready in a few usecs */
+       } else if (str3 == H8S_STR3_SWMF) { /* busy with previous request */
+               if (prefetch_jiffies == TPC_PREFETCH_NONE)
+                       printk(KERN_WARNING REQ_FMT(
+                              "EC is busy with unrequested transaction",
+                              str3));
+               return -EBUSY; /* data will be pending in a few usecs */
+       } else if (str3 != 0x00) { /* unexpected status? */
+               printk(KERN_WARNING REQ_FMT("unexpected initial STR3", str3));
+               return -EIO;
+       }
+
+       /* Send TWR0MW: */
+       outb(args->val[0], TPC_TWR0_PORT);
+       str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
+       if (str3 != H8S_STR3_MWMF) { /* not accepted? */
+               printk(KERN_WARNING REQ_FMT("arg0 rejected", str3));
+               return -EIO;
+       }
+
+       /* Send TWR1 through TWR14: */
+       for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++)
+               if ((args->mask>>i)&1)
+                       outb(args->val[i], TPC_TWR0_PORT+i);
+
+       /* Send TWR15 (default to 0x01). This marks end of command. */
+       outb((args->mask & 0x8000) ? args->val[0xF] : 0x01, TPC_TWR15_PORT);
+
+       /* Wait until EC starts writing its reply (~60ns on average).
+        * Releasing locks before this happens may cause an EC hang
+        * due to firmware bug!
+        */
+       for (i = 0; i < TPC_REQUEST_RETRIES; i++) {
+               str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
+               if (str3 & H8S_STR3_SWMF) /* EC started replying */
+                       return 0;
+               else if (!(str3 & ~(H8S_STR3_IBF3B|H8S_STR3_MWMF)))
+                       /* Normal progress (the EC hasn't seen the request
+                        * yet, or is processing it). Wait it out. */
+                       ndelay(TPC_REQUEST_NDELAY);
+               else { /* weird EC status */
+                       printk(KERN_WARNING
+                              REQ_FMT("bad end STR3", str3));
+                       return -EIO;
+               }
+       }
+       printk(KERN_WARNING REQ_FMT("EC is mysteriously silent", str3));
+       return -EIO;
+}
+
+/**
+ * thinkpad_ec_read_data - read pre-requested row-data from EC
+ * @args Input register arguments of pre-requested rows
+ * @data Output register values
+ *
+ * Reads current row data from the controller, assuming it's already
+ * requested. Follows the H8S spec for register access and status checks.
+ */
+static int thinkpad_ec_read_data(const struct thinkpad_ec_row *args,
+                                struct thinkpad_ec_row *data)
+{
+       int i;
+       u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
+       /* Once we make a request, STR3 assumes the sequence of values listed
+        * in the following 'if' as it reads the request and writes its data.
+        * It takes about a few dozen nanosecs total, with very high variance.
+        */
+       if (str3 == (H8S_STR3_IBF3B|H8S_STR3_MWMF) ||
+           str3 == 0x00 ||  /* the 0x00 is indistinguishable from idle EC! */
+           str3 == H8S_STR3_SWMF)
+               return -EBUSY; /* not ready yet */
+       /* Finally, the EC signals output buffer full: */
+       if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) {
+               printk(KERN_WARNING
+                      REQ_FMT("bad initial STR3", str3));
+               return -EIO;
+       }
+
+       /* Read first byte (signals start of read transactions): */
+       data->val[0] = inb(TPC_TWR0_PORT);
+       /* Optionally read 14 more bytes: */
+       for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++)
+               if ((data->mask >> i)&1)
+                       data->val[i] = inb(TPC_TWR0_PORT+i);
+       /* Read last byte from 0x161F (signals end of read transaction): */
+       data->val[0xF] = inb(TPC_TWR15_PORT);
+
+       /* Readout still pending? */
+       str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
+       if (str3 & H8S_STR3_OBF3B)
+               printk(KERN_WARNING
+                      REQ_FMT("OBF3B=1 after read", str3));
+       /* If port 0x161F returns 0x80 too often, the EC may lock up. Warn: */
+       if (data->val[0xF] == 0x80)
+               printk(KERN_WARNING
+                      REQ_FMT("0x161F reports error", data->val[0xF]));
+       return 0;
+}
+
+/**
+ * thinkpad_ec_is_row_fetched - is the given row currently prefetched?
+ *
+ * To keep things simple we compare only the first and last args;
+ * this suffices for all known cases.
+ */
+static int thinkpad_ec_is_row_fetched(const struct thinkpad_ec_row *args)
+{
+       return (prefetch_jiffies != TPC_PREFETCH_NONE) &&
+              (prefetch_jiffies != TPC_PREFETCH_JUNK) &&
+              (prefetch_arg0 == args->val[0]) &&
+              (prefetch_argF == args->val[0xF]) &&
+              (get_jiffies_64() < prefetch_jiffies + TPC_PREFETCH_TIMEOUT);
+}
+
+/**
+ * thinkpad_ec_read_row - request and read data from ThinkPad EC
+ * @args Input register arguments
+ * @data Output register values
+ *
+ * Read a data row from the ThinkPad embedded controller LPC3 interface.
+ * Does fetching and retrying if needed. The row is specified by an
+ * array of 16 bytes, some of which may be undefined (but the first is
+ * mandatory). These bytes are given in @args->val[], where @args->val[i] is
+ * used iff (@args->mask>>i)&1). The resulting row data is stored in
+ * @data->val[], but is only guaranteed to be valid for indices corresponding
+ * to set bit in @data->mask. That is, if @data->mask&(1<<i)==0 then
+ * @data->val[i] is undefined.
+ *
+ * Returns -EBUSY on transient error and -EIO on abnormal condition.
+ * Caller must hold controller lock.
+ */
+int thinkpad_ec_read_row(const struct thinkpad_ec_row *args,
+                        struct thinkpad_ec_row *data)
+{
+       int retries, ret;
+
+       if (thinkpad_ec_is_row_fetched(args))
+               goto read_row; /* already requested */
+
+       /* Request the row */
+       for (retries = 0; retries < TPC_READ_RETRIES; ++retries) {
+               ret = thinkpad_ec_request_row(args);
+               if (!ret)
+                       goto read_row;
+               if (ret != -EBUSY)
+                       break;
+               ndelay(TPC_READ_NDELAY);
+       }
+       printk(KERN_ERR REQ_FMT("failed requesting row", ret));
+       goto out;
+
+read_row:
+       /* Read the row's data */
+       for (retries = 0; retries < TPC_READ_RETRIES; ++retries) {
+               ret = thinkpad_ec_read_data(args, data);
+               if (!ret)
+                       goto out;
+               if (ret != -EBUSY)
+                       break;
+               ndelay(TPC_READ_NDELAY);
+       }
+
+       printk(KERN_ERR REQ_FMT("failed waiting for data", ret));
+
+out:
+       prefetch_jiffies = TPC_PREFETCH_JUNK;
+       return ret;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_read_row);
+
+/**
+ * thinkpad_ec_try_read_row - try reading prefetched data from ThinkPad EC
+ * @args Input register arguments
+ * @data Output register values
+ *
+ * Try reading a data row from the ThinkPad embedded controller LPC3
+ * interface, if this raw was recently prefetched using
+ * thinkpad_ec_prefetch_row(). Does not fetch, retry or block.
+ * The parameters have the same meaning as in thinkpad_ec_read_row().
+ *
+ * Returns -EBUSY is data not ready and -ENODATA if row not prefetched.
+ * Caller must hold controller lock.
+ */
+int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args,
+                            struct thinkpad_ec_row *data)
+{
+       int ret;
+       if (!thinkpad_ec_is_row_fetched(args)) {
+               ret = -ENODATA;
+       } else {
+               ret = thinkpad_ec_read_data(args, data);
+               if (!ret)
+                       prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */
+       }
+       return ret;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_try_read_row);
+
+/**
+ * thinkpad_ec_prefetch_row - prefetch data from ThinkPad EC
+ * @args Input register arguments
+ *
+ * Prefetch a data row from the ThinkPad embedded controller LCP3
+ * interface. A subsequent call to thinkpad_ec_read_row() with the
+ * same arguments will be faster, and a subsequent call to
+ * thinkpad_ec_try_read_row() stands a good chance of succeeding if
+ * done neither too soon nor too late. See
+ * thinkpad_ec_read_row() for the meaning of @args.
+ *
+ * Returns -EBUSY on transient error and -EIO on abnormal condition.
+ * Caller must hold controller lock.
+ */
+int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args)
+{
+       int ret;
+       ret = thinkpad_ec_request_row(args);
+       if (ret) {
+               prefetch_jiffies = TPC_PREFETCH_JUNK;
+       } else {
+               prefetch_jiffies = get_jiffies_64();
+               prefetch_arg0 = args->val[0x0];
+               prefetch_argF = args->val[0xF];
+       }
+       return ret;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_prefetch_row);
+
+/**
+ * thinkpad_ec_invalidate - invalidate prefetched ThinkPad EC data
+ *
+ * Invalidate the data prefetched via thinkpad_ec_prefetch_row() from the
+ * ThinkPad embedded controller LPC3 interface.
+ * Must be called before unlocking by any code that accesses the controller
+ * ports directly.
+ */
+void thinkpad_ec_invalidate(void)
+{
+       prefetch_jiffies = TPC_PREFETCH_JUNK;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_invalidate);
+
+
+/*** Checking for EC hardware ***/
+
+/**
+ * thinkpad_ec_test - verify the EC is present and follows protocol
+ *
+ * Ensure the EC LPC3 channel really works on this machine by making
+ * an EC request and seeing if the EC follows the documented H8S protocol.
+ * The requested row just reads battery status, so it should be harmless to
+ * access it (on a correct EC).
+ * This test writes to IO ports, so execute only after checking DMI.
+ */
+static int __init thinkpad_ec_test(void)
+{
+       int ret;
+       const struct thinkpad_ec_row args = /* battery 0 basic status */
+         { .mask = 0x8001, .val = {0x01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x00} };
+       struct thinkpad_ec_row data = { .mask = 0x0000 };
+       ret = thinkpad_ec_lock();
+       if (ret)
+               return ret;
+       ret = thinkpad_ec_read_row(&args, &data);
+       thinkpad_ec_unlock();
+       return ret;
+}
+
+/* Search all DMI device names of a given type for a substring */
+static int __init dmi_find_substring(int type, const char *substr)
+{
+       const struct dmi_device *dev = NULL;
+       while ((dev = dmi_find_device(type, NULL, dev))) {
+               if (strstr(dev->name, substr))
+                       return 1;
+       }
+       return 0;
+}
+
+#define TP_DMI_MATCH(vendor,model)     {               \
+       .ident = vendor " " model,                      \
+       .matches = {                                    \
+               DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
+               DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
+       }                                               \
+}
+
+/* Check DMI for existence of ThinkPad embedded controller */
+static int __init check_dmi_for_ec(void)
+{
+       /* A few old models that have a good EC but don't report it in DMI */
+       struct dmi_system_id tp_whitelist[] = {
+               TP_DMI_MATCH("IBM", "ThinkPad A30"),
+               TP_DMI_MATCH("IBM", "ThinkPad T23"),
+               TP_DMI_MATCH("IBM", "ThinkPad X24"),
+               TP_DMI_MATCH("LENOVO", "ThinkPad"),
+               { .ident = NULL }
+       };
+       return dmi_find_substring(DMI_DEV_TYPE_OEM_STRING,
+                                 "IBM ThinkPad Embedded Controller") ||
+              dmi_check_system(tp_whitelist);
+}
+
+/*** Init and cleanup ***/
+
+static int __init thinkpad_ec_init(void)
+{
+       if (!check_dmi_for_ec()) {
+               printk(KERN_WARNING
+                      "thinkpad_ec: no ThinkPad embedded controller!\n");
+               return -ENODEV;
+       }
+
+       if (request_region(TPC_BASE_PORT, TPC_NUM_PORTS, "thinkpad_ec")) {
+               reserved_io = 1;
+       } else {
+               printk(KERN_ERR "thinkpad_ec: cannot claim IO ports %#x-%#x... ",
+                      TPC_BASE_PORT,
+                      TPC_BASE_PORT + TPC_NUM_PORTS - 1);
+               if (force_io) {
+                       printk("forcing use of unreserved IO ports.\n");
+               } else {
+                       printk("consider using force_io=1.\n");
+                       return -ENXIO;
+               }
+       }
+       prefetch_jiffies = TPC_PREFETCH_JUNK;
+       if (thinkpad_ec_test()) {
+               printk(KERN_ERR "thinkpad_ec: initial ec test failed\n");
+               if (reserved_io)
+                       release_region(TPC_BASE_PORT, TPC_NUM_PORTS);
+               return -ENXIO;
+       }
+       printk(KERN_INFO "thinkpad_ec: thinkpad_ec " TP_VERSION " loaded.\n");
+       return 0;
+}
+
+static void __exit thinkpad_ec_exit(void)
+{
+       if (reserved_io)
+               release_region(TPC_BASE_PORT, TPC_NUM_PORTS);
+       printk(KERN_INFO "thinkpad_ec: unloaded.\n");
+}
+
+module_init(thinkpad_ec_init);
+module_exit(thinkpad_ec_exit);
diff --git a/tp_smapi/thinkpad_ec.h b/tp_smapi/thinkpad_ec.h
new file mode 100644 (file)
index 0000000..1b80d7e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *  thinkpad_ec.h - interface to ThinkPad embedded controller LPC3 functions
+ *
+ *  Copyright (C) 2005 Shem Multinymous <multinymous@gmail.com>
+ *
+ *  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
+ */
+
+#ifndef _THINKPAD_EC_H
+#define _THINKPAD_EC_H
+
+#ifdef __KERNEL__
+
+#define TP_CONTROLLER_ROW_LEN 16
+
+/* EC transactions input and output (possibly partial) vectors of 16 bytes. */
+struct thinkpad_ec_row {
+       u16 mask; /* bitmap of which entries of val[] are meaningful */
+       u8 val[TP_CONTROLLER_ROW_LEN];
+};
+
+extern int __must_check thinkpad_ec_lock(void);
+extern int __must_check thinkpad_ec_try_lock(void);
+extern void thinkpad_ec_unlock(void);
+
+extern int thinkpad_ec_read_row(const struct thinkpad_ec_row *args,
+                               struct thinkpad_ec_row *data);
+extern int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args,
+                                   struct thinkpad_ec_row *mask);
+extern int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args);
+extern void thinkpad_ec_invalidate(void);
+
+
+#endif /* __KERNEL */
+#endif /* _THINKPAD_EC_H */
diff --git a/tp_smapi/tp_smapi.c b/tp_smapi/tp_smapi.c
new file mode 100644 (file)
index 0000000..e1526c9
--- /dev/null
@@ -0,0 +1,1493 @@
+/*
+ *  tp_smapi.c - ThinkPad SMAPI support
+ *
+ *  This driver exposes some features of the System Management Application
+ *  Program Interface (SMAPI) BIOS found on ThinkPad laptops. It works on
+ *  models in which the SMAPI BIOS runs in SMM and is invoked by writing
+ *  to the APM control port 0xB2.
+ *  It also exposes battery status information, obtained from the ThinkPad
+ *  embedded controller (via the thinkpad_ec module).
+ *  Ancient ThinkPad models use a different interface, supported by the
+ *  "thinkpad" module from "tpctl".
+ *
+ *  Many of the battery status values obtained from the EC simply mirror
+ *  values provided by the battery's Smart Battery System (SBS) interface, so
+ *  their meaning is defined by the Smart Battery Data Specification (see
+ *  http://sbs-forum.org/specs/sbdat110.pdf). References to this SBS spec
+ *  are given in the code where relevant.
+ *
+ *  Copyright (C) 2006 Shem Multinymous <multinymous@gmail.com>.
+ *  SMAPI access code based on the mwave driver by Mike Sullivan.
+ *
+ *  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
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/mc146818rtc.h> /* CMOS defines */
+#include <linux/delay.h>
+#include <linux/version.h>
+#include "thinkpad_ec.h"
+#include <linux/platform_device.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define TP_VERSION "0.41"
+#define TP_DESC "ThinkPad SMAPI Support"
+#define TP_DIR "smapi"
+
+MODULE_AUTHOR("Shem Multinymous");
+MODULE_DESCRIPTION(TP_DESC);
+MODULE_VERSION(TP_VERSION);
+MODULE_LICENSE("GPL");
+
+static struct platform_device *pdev;
+
+static int tp_debug;
+module_param_named(debug, tp_debug, int, 0600);
+MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)");
+
+/* A few macros for printk()ing: */
+#define TPRINTK(level, fmt, args...) \
+  dev_printk(level, &(pdev->dev), "%s: " fmt "\n", __func__, ## args)
+#define DPRINTK(fmt, args...) \
+  do { if (tp_debug) TPRINTK(KERN_DEBUG, fmt, ## args); } while (0)
+
+/*********************************************************************
+ * SMAPI interface
+ */
+
+/* SMAPI functions (register BX when making the SMM call). */
+#define SMAPI_GET_INHIBIT_CHARGE                0x2114
+#define SMAPI_SET_INHIBIT_CHARGE                0x2115
+#define SMAPI_GET_THRESH_START                  0x2116
+#define SMAPI_SET_THRESH_START                  0x2117
+#define SMAPI_GET_FORCE_DISCHARGE               0x2118
+#define SMAPI_SET_FORCE_DISCHARGE               0x2119
+#define SMAPI_GET_THRESH_STOP                   0x211a
+#define SMAPI_SET_THRESH_STOP                   0x211b
+
+/* SMAPI error codes (see ThinkPad 770 Technical Reference Manual p.83 at
+ http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD */
+#define SMAPI_RETCODE_EOF 0xff
+static struct { u8 rc; char *msg; int ret; } smapi_retcode[] =
+{
+       {0x00, "OK", 0},
+       {0x53, "SMAPI function is not available", -ENXIO},
+       {0x81, "Invalid parameter", -EINVAL},
+       {0x86, "Function is not supported by SMAPI BIOS", -EOPNOTSUPP},
+       {0x90, "System error", -EIO},
+       {0x91, "System is invalid", -EIO},
+       {0x92, "System is busy, -EBUSY"},
+       {0xa0, "Device error (disk read error)", -EIO},
+       {0xa1, "Device is busy", -EBUSY},
+       {0xa2, "Device is not attached", -ENXIO},
+       {0xa3, "Device is disbled", -EIO},
+       {0xa4, "Request parameter is out of range", -EINVAL},
+       {0xa5, "Request parameter is not accepted", -EINVAL},
+       {0xa6, "Transient error", -EBUSY}, /* ? */
+       {SMAPI_RETCODE_EOF, "Unknown error code", -EIO}
+};
+
+
+#define SMAPI_MAX_RETRIES 10
+#define SMAPI_PORT2 0x4F           /* fixed port, meaning unclear */
+static unsigned short smapi_port;  /* APM control port, normally 0xB2 */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
+static DECLARE_MUTEX(smapi_mutex);
+#else
+static DEFINE_SEMAPHORE(smapi_mutex);
+#endif
+
+/**
+ * find_smapi_port - read SMAPI port from NVRAM
+ */
+static int __init find_smapi_port(void)
+{
+       u16 smapi_id = 0;
+       unsigned short port = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&rtc_lock, flags);
+       smapi_id = CMOS_READ(0x7C);
+       smapi_id |= (CMOS_READ(0x7D) << 8);
+       spin_unlock_irqrestore(&rtc_lock, flags);
+
+       if (smapi_id != 0x5349) {
+               printk(KERN_ERR "SMAPI not supported (ID=0x%x)\n", smapi_id);
+               return -ENXIO;
+       }
+       spin_lock_irqsave(&rtc_lock, flags);
+       port = CMOS_READ(0x7E);
+       port |= (CMOS_READ(0x7F) << 8);
+       spin_unlock_irqrestore(&rtc_lock, flags);
+       if (port == 0) {
+               printk(KERN_ERR "unable to read SMAPI port number\n");
+               return -ENXIO;
+       }
+       return port;
+}
+
+/**
+ * smapi_request - make a SMAPI call
+ * @inEBX, @inECX, @inEDI, @inESI: input registers
+ * @outEBX, @outECX, @outEDX, @outEDI, @outESI: outputs registers
+ * @msg: textual error message
+ * Invokes the SMAPI SMBIOS with the given input and outpu args.
+ * All outputs are optional (can be %NULL).
+ * Returns 0 when successful, and a negative errno constant
+ * (see smapi_retcode above) upon failure.
+ */
+static int smapi_request(u32 inEBX, u32 inECX,
+                        u32 inEDI, u32 inESI,
+                        u32 *outEBX, u32 *outECX, u32 *outEDX,
+                        u32 *outEDI, u32 *outESI, const char **msg)
+{
+       int ret = 0;
+       int i;
+       int retries;
+       u8 rc;
+       /* Must use local vars for output regs, due to reg pressure. */
+       u32 tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI;
+
+       for (retries = 0; retries < SMAPI_MAX_RETRIES; ++retries) {
+               DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x",
+                       inEBX, inECX, inEDI, inESI);
+
+               /* SMAPI's SMBIOS call and thinkpad_ec end up using use
+                * different interfaces to the same chip, so play it safe. */
+               ret = thinkpad_ec_lock();
+               if (ret)
+                       return ret;
+
+               __asm__ __volatile__(
+                       "movl  $0x00005380,%%eax\n\t"
+                       "movl  %6,%%ebx\n\t"
+                       "movl  %7,%%ecx\n\t"
+                       "movl  %8,%%edi\n\t"
+                       "movl  %9,%%esi\n\t"
+                       "xorl  %%edx,%%edx\n\t"
+                       "movw  %10,%%dx\n\t"
+                       "out   %%al,%%dx\n\t"  /* trigger SMI to SMBIOS */
+                       "out   %%al,$0x4F\n\t"
+                       "movl  %%eax,%0\n\t"
+                       "movl  %%ebx,%1\n\t"
+                       "movl  %%ecx,%2\n\t"
+                       "movl  %%edx,%3\n\t"
+                       "movl  %%edi,%4\n\t"
+                       "movl  %%esi,%5\n\t"
+                       :"=m"(tmpEAX),
+                        "=m"(tmpEBX),
+                        "=m"(tmpECX),
+                        "=m"(tmpEDX),
+                        "=m"(tmpEDI),
+                        "=m"(tmpESI)
+                       :"m"(inEBX), "m"(inECX), "m"(inEDI), "m"(inESI),
+                        "m"((u16)smapi_port)
+                       :"%eax", "%ebx", "%ecx", "%edx", "%edi",
+                        "%esi");
+
+               thinkpad_ec_invalidate();
+               thinkpad_ec_unlock();
+
+               /* Don't let the next SMAPI access happen too quickly,
+                * may case problems. (We're hold smapi_mutex).       */
+               msleep(50);
+
+               if (outEBX) *outEBX = tmpEBX;
+               if (outECX) *outECX = tmpECX;
+               if (outEDX) *outEDX = tmpEDX;
+               if (outESI) *outESI = tmpESI;
+               if (outEDI) *outEDI = tmpEDI;
+
+               /* Look up error code */
+               rc = (tmpEAX>>8)&0xFF;
+               for (i = 0; smapi_retcode[i].rc != SMAPI_RETCODE_EOF &&
+                           smapi_retcode[i].rc != rc; ++i) {}
+               ret = smapi_retcode[i].ret;
+               if (msg)
+                       *msg = smapi_retcode[i].msg;
+
+               DPRINTK("req_out: AX=%x BX=%x CX=%x DX=%x DI=%x SI=%x r=%d",
+                        tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI, ret);
+               if (ret)
+                       TPRINTK(KERN_NOTICE, "SMAPI error: %s (func=%x)",
+                               smapi_retcode[i].msg, inEBX);
+
+               if (ret != -EBUSY)
+                       return ret;
+       }
+       return ret;
+}
+
+/* Convenience wrapper: discard output arguments */
+static int smapi_write(u32 inEBX, u32 inECX,
+                      u32 inEDI, u32 inESI, const char **msg)
+{
+       return smapi_request(inEBX, inECX, inEDI, inESI,
+                            NULL, NULL, NULL, NULL, NULL, msg);
+}
+
+
+/*********************************************************************
+ * Specific SMAPI services
+ * All of these functions return 0 upon success, and a negative errno
+ * constant (see smapi_retcode) on failure.
+ */
+
+enum thresh_type {
+       THRESH_STOP  = 0, /* the code assumes this is 0 for brevity */
+       THRESH_START
+};
+#define THRESH_NAME(which) ((which == THRESH_START) ? "start" : "stop")
+
+/**
+ * __get_real_thresh - read battery charge start/stop threshold from SMAPI
+ * @bat:    battery number (0 or 1)
+ * @which:  THRESH_START or THRESH_STOP
+ * @thresh: 1..99, 0=default 1..99, 0=default (pass this as-is to SMAPI)
+ * @outEDI: some additional state that needs to be preserved, meaning unknown
+ * @outESI: some additional state that needs to be preserved, meaning unknown
+ */
+static int __get_real_thresh(int bat, enum thresh_type which, int *thresh,
+                            u32 *outEDI, u32 *outESI)
+{
+       u32 ebx = (which == THRESH_START) ? SMAPI_GET_THRESH_START
+                                         : SMAPI_GET_THRESH_STOP;
+       u32 ecx = (bat+1)<<8;
+       const char *msg;
+       int ret = smapi_request(ebx, ecx, 0, 0, NULL,
+                               &ecx, NULL, outEDI, outESI, &msg);
+       if (ret) {
+               TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: %s",
+                       THRESH_NAME(which), bat, msg);
+               return ret;
+       }
+       if (!(ecx&0x00000100)) {
+               TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: ecx=0%x",
+                       THRESH_NAME(which), bat, ecx);
+               return -EIO;
+       }
+       if (thresh)
+               *thresh = ecx&0xFF;
+       return 0;
+}
+
+/**
+ * get_real_thresh - read battery charge start/stop threshold from SMAPI
+ * @bat:    battery number (0 or 1)
+ * @which:  THRESH_START or THRESH_STOP
+ * @thresh: 1..99, 0=default (passes as-is to SMAPI)
+ */
+static int get_real_thresh(int bat, enum thresh_type which, int *thresh)
+{
+       return __get_real_thresh(bat, which, thresh, NULL, NULL);
+}
+
+/**
+ * set_real_thresh - write battery start/top charge threshold to SMAPI
+ * @bat:    battery number (0 or 1)
+ * @which:  THRESH_START or THRESH_STOP
+ * @thresh: 1..99, 0=default (passes as-is to SMAPI)
+ */
+static int set_real_thresh(int bat, enum thresh_type which, int thresh)
+{
+       u32 ebx = (which == THRESH_START) ? SMAPI_SET_THRESH_START
+                                         : SMAPI_SET_THRESH_STOP;
+       u32 ecx = ((bat+1)<<8) + thresh;
+       u32 getDI, getSI;
+       const char *msg;
+       int ret;
+
+       /* verify read before writing */
+       ret = __get_real_thresh(bat, which, NULL, &getDI, &getSI);
+       if (ret)
+               return ret;
+
+       ret = smapi_write(ebx, ecx, getDI, getSI, &msg);
+       if (ret)
+               TPRINTK(KERN_NOTICE, "set %s to %d for bat=%d failed: %s",
+                       THRESH_NAME(which), thresh, bat, msg);
+       else
+               TPRINTK(KERN_INFO, "set %s to %d for bat=%d",
+                       THRESH_NAME(which), thresh, bat);
+       return ret;
+}
+
+/**
+ * __get_inhibit_charge_minutes - get inhibit charge period from SMAPI
+ * @bat:     battery number (0 or 1)
+ * @minutes: period in minutes (1..65535 minutes, 0=disabled)
+ * @outECX: some additional state that needs to be preserved, meaning unknown
+ * Note that @minutes is the originally set value, it does not count down.
+ */
+static int __get_inhibit_charge_minutes(int bat, int *minutes, u32 *outECX)
+{
+       u32 ecx = (bat+1)<<8;
+       u32 esi;
+       const char *msg;
+       int ret = smapi_request(SMAPI_GET_INHIBIT_CHARGE, ecx, 0, 0,
+                               NULL, &ecx, NULL, NULL, &esi, &msg);
+       if (ret) {
+               TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg);
+               return ret;
+       }
+       if (!(ecx&0x0100)) {
+               TPRINTK(KERN_NOTICE, "bad ecx=0x%x for bat=%d", ecx, bat);
+               return -EIO;
+       }
+       if (minutes)
+               *minutes = (ecx&0x0001)?esi:0;
+       if (outECX)
+               *outECX = ecx;
+       return 0;
+}
+
+/**
+ * get_inhibit_charge_minutes - get inhibit charge period from SMAPI
+ * @bat:     battery number (0 or 1)
+ * @minutes: period in minutes (1..65535 minutes, 0=disabled)
+ * Note that @minutes is the originally set value, it does not count down.
+ */
+static int get_inhibit_charge_minutes(int bat, int *minutes)
+{
+       return __get_inhibit_charge_minutes(bat, minutes, NULL);
+}
+
+/**
+ * set_inhibit_charge_minutes - write inhibit charge period to SMAPI
+ * @bat:     battery number (0 or 1)
+ * @minutes: period in minutes (1..65535 minutes, 0=disabled)
+ */
+static int set_inhibit_charge_minutes(int bat, int minutes)
+{
+       u32 ecx;
+       const char *msg;
+       int ret;
+
+       /* verify read before writing */
+       ret = __get_inhibit_charge_minutes(bat, NULL, &ecx);
+       if (ret)
+               return ret;
+
+       ecx = ((bat+1)<<8) | (ecx&0x00FE) | (minutes > 0 ? 0x0001 : 0x0000);
+       if (minutes > 0xFFFF)
+               minutes = 0xFFFF;
+       ret = smapi_write(SMAPI_SET_INHIBIT_CHARGE, ecx, 0, minutes, &msg);
+       if (ret)
+               TPRINTK(KERN_NOTICE,
+                       "set to %d failed for bat=%d: %s", minutes, bat, msg);
+       else
+               TPRINTK(KERN_INFO, "set to %d for bat=%d\n", minutes, bat);
+       return ret;
+}
+
+
+/**
+ * get_force_discharge - get status of forced discharging from SMAPI
+ * @bat:     battery number (0 or 1)
+ * @enabled: 1 if forced discharged is enabled, 0 if not
+ */
+static int get_force_discharge(int bat, int *enabled)
+{
+       u32 ecx = (bat+1)<<8;
+       const char *msg;
+       int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0,
+                               NULL, &ecx, NULL, NULL, NULL, &msg);
+       if (ret) {
+               TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg);
+               return ret;
+       }
+       *enabled = (!(ecx&0x00000100) && (ecx&0x00000001))?1:0;
+       return 0;
+}
+
+/**
+ * set_force_discharge - write status of forced discharging to SMAPI
+ * @bat:     battery number (0 or 1)
+ * @enabled: 1 if forced discharged is enabled, 0 if not
+ */
+static int set_force_discharge(int bat, int enabled)
+{
+       u32 ecx = (bat+1)<<8;
+       const char *msg;
+       int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0,
+                               NULL, &ecx, NULL, NULL, NULL, &msg);
+       if (ret) {
+               TPRINTK(KERN_NOTICE, "get failed for bat=%d: %s", bat, msg);
+               return ret;
+       }
+       if (ecx&0x00000100) {
+               TPRINTK(KERN_NOTICE, "cannot force discharge bat=%d", bat);
+               return -EIO;
+       }
+
+       ecx = ((bat+1)<<8) | (ecx&0x000000FA) | (enabled?0x00000001:0);
+       ret = smapi_write(SMAPI_SET_FORCE_DISCHARGE, ecx, 0, 0, &msg);
+       if (ret)
+               TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s",
+                       enabled, bat, msg);
+       else
+               TPRINTK(KERN_INFO, "set to %d for bat=%d", enabled, bat);
+       return ret;
+}
+
+
+/*********************************************************************
+ * Wrappers to threshold-related SMAPI functions, which handle default
+ * thresholds and related quirks.
+ */
+
+/* Minimum, default and minimum difference for battery charging thresholds: */
+#define MIN_THRESH_DELTA      4  /* Min delta between start and stop thresh */
+#define MIN_THRESH_START      2
+#define MAX_THRESH_START      (100-MIN_THRESH_DELTA)
+#define MIN_THRESH_STOP       (MIN_THRESH_START + MIN_THRESH_DELTA)
+#define MAX_THRESH_STOP       100
+#define DEFAULT_THRESH_START  MAX_THRESH_START
+#define DEFAULT_THRESH_STOP   MAX_THRESH_STOP
+
+/* The GUI of IBM's Battery Maximizer seems to show a start threshold that
+ * is 1 more than the value we set/get via SMAPI. Since the threshold is
+ * maintained across reboot, this can be confusing. So we kludge our
+ * interface for interoperability: */
+#define BATMAX_FIX   1
+
+/* Get charge start/stop threshold (1..100),
+ * substituting default values if needed and applying BATMAT_FIX. */
+static int get_thresh(int bat, enum thresh_type which, int *thresh)
+{
+       int ret = get_real_thresh(bat, which, thresh);
+       if (ret)
+               return ret;
+       if (*thresh == 0)
+               *thresh = (which == THRESH_START) ? DEFAULT_THRESH_START
+                                                 : DEFAULT_THRESH_STOP;
+       else if (which == THRESH_START)
+               *thresh += BATMAX_FIX;
+       return 0;
+}
+
+
+/* Set charge start/stop threshold (1..100),
+ * substituting default values if needed and applying BATMAT_FIX. */
+static int set_thresh(int bat, enum thresh_type which, int thresh)
+{
+       if (which == THRESH_STOP && thresh == DEFAULT_THRESH_STOP)
+               thresh = 0; /* 100 is out of range, but default means 100 */
+       if (which == THRESH_START)
+               thresh -= BATMAX_FIX;
+       return set_real_thresh(bat, which, thresh);
+}
+
+/*********************************************************************
+ * ThinkPad embedded controller readout and basic functions
+ */
+
+/**
+ * read_tp_ec_row - read data row from the ThinkPad embedded controller
+ * @arg0: EC command code
+ * @bat: battery number, 0 or 1
+ * @j: the byte value to be used for "junk" (unused) input/outputs
+ * @dataval: result vector
+ */
+static int read_tp_ec_row(u8 arg0, int bat, u8 j, u8 *dataval)
+{
+       int ret;
+       const struct thinkpad_ec_row args = { .mask = 0xFFFF,
+               .val = {arg0, j,j,j,j,j,j,j,j,j,j,j,j,j,j, (u8)bat} };
+       struct thinkpad_ec_row data = { .mask = 0xFFFF };
+
+       ret = thinkpad_ec_lock();
+       if (ret)
+               return ret;
+       ret = thinkpad_ec_read_row(&args, &data);
+       thinkpad_ec_unlock();
+       memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN);
+       return ret;
+}
+
+/**
+ * power_device_present - check for presence of battery or AC power
+ * @bat: 0 for battery 0, 1 for battery 1, otherwise AC power
+ * Returns 1 if present, 0 if not present, negative if error.
+ */
+static int power_device_present(int bat)
+{
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       u8 test;
+       int ret = read_tp_ec_row(1, bat, 0, row);
+       if (ret)
+               return ret;
+       switch (bat) {
+       case 0:  test = 0x40; break; /* battery 0 */
+       case 1:  test = 0x20; break; /* battery 1 */
+       default: test = 0x80;        /* AC power */
+       }
+       return (row[0] & test) ? 1 : 0;
+}
+
+/**
+ * bat_has_status - check if battery can report detailed status
+ * @bat: 0 for battery 0, 1 for battery 1
+ * Returns 1 if yes, 0 if no, negative if error.
+ */
+static int bat_has_status(int bat)
+{
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       int ret = read_tp_ec_row(1, bat, 0, row);
+       if (ret)
+               return ret;
+       if ((row[0] & (bat?0x20:0x40)) == 0) /* no battery */
+               return 0;
+       if ((row[1] & (0x60)) == 0) /* no status */
+               return 0;
+       return 1;
+}
+
+/**
+ * get_tp_ec_bat_16 - read a 16-bit value from EC battery status data
+ * @arg0: first argument to EC
+ * @off: offset in row returned from EC
+ * @bat: battery (0 or 1)
+ * @val: the 16-bit value obtained
+ * Returns nonzero on error.
+ */
+static int get_tp_ec_bat_16(u8 arg0, int offset, int bat, u16 *val)
+{
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       int ret;
+       if (bat_has_status(bat) != 1)
+               return -ENXIO;
+       ret = read_tp_ec_row(arg0, bat, 0, row);
+       if (ret)
+               return ret;
+       *val = *(u16 *)(row+offset);
+       return 0;
+}
+
+/*********************************************************************
+ * sysfs attributes for batteries -
+ * definitions and helper functions
+ */
+
+/* A custom device attribute struct which holds a battery number */
+struct bat_device_attribute {
+       struct device_attribute dev_attr;
+       int bat;
+};
+
+/**
+ * attr_get_bat - get the battery to which the attribute belongs
+ */
+static int attr_get_bat(struct device_attribute *attr)
+{
+       return container_of(attr, struct bat_device_attribute, dev_attr)->bat;
+}
+
+/**
+ * show_tp_ec_bat_u16 - show an unsigned 16-bit battery attribute
+ * @arg0: specified 1st argument of EC raw to read
+ * @offset: byte offset in EC raw data
+ * @mul: correction factor to multiply by
+ * @na_msg: string to output is value not available (0xFFFFFFFF)
+ * @attr: battery attribute
+ * @buf: output buffer
+ * The 16-bit value is read from the EC, treated as unsigned,
+ * transformed as x->mul*x, and printed to the buffer.
+ * If the value is 0xFFFFFFFF and na_msg!=%NULL, na_msg is printed instead.
+ */
+static ssize_t show_tp_ec_bat_u16(u8 arg0, int offset, int mul,
+                             const char *na_msg,
+                             struct device_attribute *attr, char *buf)
+{
+       u16 val;
+       int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val);
+       if (ret)
+               return ret;
+       if (na_msg && val == 0xFFFF)
+               return sprintf(buf, "%s\n", na_msg);
+       else
+               return sprintf(buf, "%u\n", mul*(unsigned int)val);
+}
+
+/**
+ * show_tp_ec_bat_s16 - show an signed 16-bit battery attribute
+ * @arg0: specified 1st argument of EC raw to read
+ * @offset: byte offset in EC raw data
+ * @mul: correction factor to multiply by
+ * @add: correction term to add after multiplication
+ * @attr: battery attribute
+ * @buf: output buffer
+ * The 16-bit value is read from the EC, treated as signed,
+ * transformed as x->mul*x+add, and printed to the buffer.
+ */
+static ssize_t show_tp_ec_bat_s16(u8 arg0, int offset, int mul, int add,
+                             struct device_attribute *attr, char *buf)
+{
+       u16 val;
+       int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%d\n", mul*(s16)val+add);
+}
+
+/**
+ * show_tp_ec_bat_str - show a string from EC battery status data
+ * @arg0: specified 1st argument of EC raw to read
+ * @offset: byte offset in EC raw data
+ * @maxlen: maximum string length
+ * @attr: battery attribute
+ * @buf: output buffer
+ */
+static ssize_t show_tp_ec_bat_str(u8 arg0, int offset, int maxlen,
+                             struct device_attribute *attr, char *buf)
+{
+       int bat = attr_get_bat(attr);
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       int ret;
+       if (bat_has_status(bat) != 1)
+               return -ENXIO;
+       ret = read_tp_ec_row(arg0, bat, 0, row);
+       if (ret)
+               return ret;
+       strncpy(buf, (char *)row+offset, maxlen);
+       buf[maxlen] = 0;
+       strcat(buf, "\n");
+       return strlen(buf);
+}
+
+/**
+ * show_tp_ec_bat_power - show a power readout from EC battery status data
+ * @arg0: specified 1st argument of EC raw to read
+ * @offV: byte offset of voltage in EC raw data
+ * @offI: byte offset of current in EC raw data
+ * @attr: battery attribute
+ * @buf: output buffer
+ * Computes the power as current*voltage from the two given readout offsets.
+ */
+static ssize_t show_tp_ec_bat_power(u8 arg0, int offV, int offI,
+                               struct device_attribute *attr, char *buf)
+{
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       int milliamp, millivolt, ret;
+       int bat = attr_get_bat(attr);
+       if (bat_has_status(bat) != 1)
+               return -ENXIO;
+       ret = read_tp_ec_row(1, bat, 0, row);
+       if (ret)
+               return ret;
+       millivolt = *(u16 *)(row+offV);
+       milliamp = *(s16 *)(row+offI);
+       return sprintf(buf, "%d\n", milliamp*millivolt/1000); /* units: mW */
+}
+
+/**
+ * show_tp_ec_bat_date - decode and show a date from EC battery status data
+ * @arg0: specified 1st argument of EC raw to read
+ * @offset: byte offset in EC raw data
+ * @attr: battery attribute
+ * @buf: output buffer
+ */
+static ssize_t show_tp_ec_bat_date(u8 arg0, int offset,
+                              struct device_attribute *attr, char *buf)
+{
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       u16 v;
+       int ret;
+       int day, month, year;
+       int bat = attr_get_bat(attr);
+       if (bat_has_status(bat) != 1)
+               return -ENXIO;
+       ret = read_tp_ec_row(arg0, bat, 0, row);
+       if (ret)
+               return ret;
+
+       /* Decode bit-packed: v = day | (month<<5) | ((year-1980)<<9) */
+       v = *(u16 *)(row+offset);
+       day = v & 0x1F;
+       month = (v >> 5) & 0xF;
+       year = (v >> 9) + 1980;
+
+       return sprintf(buf, "%04d-%02d-%02d\n", year, month, day);
+}
+
+
+/*********************************************************************
+ * sysfs attribute I/O for batteries -
+ * the actual attribute show/store functions
+ */
+
+static ssize_t show_battery_start_charge_thresh(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       int thresh;
+       int bat = attr_get_bat(attr);
+       int ret = get_thresh(bat, THRESH_START, &thresh);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%d\n", thresh);  /* units: percent */
+}
+
+static ssize_t show_battery_stop_charge_thresh(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       int thresh;
+       int bat = attr_get_bat(attr);
+       int ret = get_thresh(bat, THRESH_STOP, &thresh);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%d\n", thresh);  /* units: percent */
+}
+
+/**
+ * store_battery_start_charge_thresh - store battery_start_charge_thresh attr
+ * Since this is a kernel<->user interface, we ensure a valid state for
+ * the hardware. We do this by clamping the requested threshold to the
+ * valid range and, if necessary, moving the other threshold so that
+ * it's MIN_THRESH_DELTA away from this one.
+ */
+static ssize_t store_battery_start_charge_thresh(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+       int thresh, other_thresh, ret;
+       int bat = attr_get_bat(attr);
+
+       if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100)
+               return -EINVAL;
+
+       if (thresh < MIN_THRESH_START) /* clamp up to MIN_THRESH_START */
+               thresh = MIN_THRESH_START;
+       if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */
+               thresh = MAX_THRESH_START;
+
+       down(&smapi_mutex);
+       ret = get_thresh(bat, THRESH_STOP, &other_thresh);
+       if (ret != -EOPNOTSUPP && ret != -ENXIO) {
+               if (ret) /* other threshold is set? */
+                       goto out;
+               ret = get_real_thresh(bat, THRESH_START, NULL);
+               if (ret) /* this threshold is set? */
+                       goto out;
+               if (other_thresh < thresh+MIN_THRESH_DELTA) {
+                       /* move other thresh to keep it above this one */
+                       ret = set_thresh(bat, THRESH_STOP,
+                                        thresh+MIN_THRESH_DELTA);
+                       if (ret)
+                               goto out;
+               }
+       }
+       ret = set_thresh(bat, THRESH_START, thresh);
+out:
+       up(&smapi_mutex);
+       return count;
+
+}
+
+/**
+ * store_battery_stop_charge_thresh - store battery_stop_charge_thresh attr
+ * Since this is a kernel<->user interface, we ensure a valid state for
+ * the hardware. We do this by clamping the requested threshold to the
+ * valid range and, if necessary, moving the other threshold so that
+ * it's MIN_THRESH_DELTA away from this one.
+ */
+static ssize_t store_battery_stop_charge_thresh(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+       int thresh, other_thresh, ret;
+       int bat = attr_get_bat(attr);
+
+       if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100)
+               return -EINVAL;
+
+       if (thresh < MIN_THRESH_STOP) /* clamp up to MIN_THRESH_STOP */
+               thresh = MIN_THRESH_STOP;
+
+       down(&smapi_mutex);
+       ret = get_thresh(bat, THRESH_START, &other_thresh);
+       if (ret != -EOPNOTSUPP && ret != -ENXIO) { /* other threshold exists? */
+               if (ret)
+                       goto out;
+               /* this threshold exists? */
+               ret = get_real_thresh(bat, THRESH_STOP, NULL);
+               if (ret)
+                       goto out;
+               if (other_thresh >= thresh-MIN_THRESH_DELTA) {
+                        /* move other thresh to be below this one */
+                       ret = set_thresh(bat, THRESH_START,
+                                        thresh-MIN_THRESH_DELTA);
+                       if (ret)
+                               goto out;
+               }
+       }
+       ret = set_thresh(bat, THRESH_STOP, thresh);
+out:
+       up(&smapi_mutex);
+       return count;
+}
+
+static ssize_t show_battery_inhibit_charge_minutes(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       int minutes;
+       int bat = attr_get_bat(attr);
+       int ret = get_inhibit_charge_minutes(bat, &minutes);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%d\n", minutes);  /* units: minutes */
+}
+
+static ssize_t store_battery_inhibit_charge_minutes(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       int ret;
+       int minutes;
+       int bat = attr_get_bat(attr);
+       if (sscanf(buf, "%d", &minutes) != 1 || minutes < 0) {
+               TPRINTK(KERN_ERR, "inhibit_charge_minutes: "
+                             "must be a non-negative integer");
+               return -EINVAL;
+       }
+       ret = set_inhibit_charge_minutes(bat, minutes);
+       if (ret)
+               return ret;
+       return count;
+}
+
+static ssize_t show_battery_force_discharge(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       int enabled;
+       int bat = attr_get_bat(attr);
+       int ret = get_force_discharge(bat, &enabled);
+       if (ret)
+               return ret;
+       return sprintf(buf, "%d\n", enabled);  /* type: boolean */
+}
+
+static ssize_t store_battery_force_discharge(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+       int ret;
+       int enabled;
+       int bat = attr_get_bat(attr);
+       if (sscanf(buf, "%d", &enabled) != 1 || enabled < 0 || enabled > 1)
+               return -EINVAL;
+       ret = set_force_discharge(bat, enabled);
+       if (ret)
+               return ret;
+       return count;
+}
+
+static ssize_t show_battery_installed(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       int bat = attr_get_bat(attr);
+       int ret = power_device_present(bat);
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%d\n", ret); /* type: boolean */
+}
+
+static ssize_t show_battery_state(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       u8 row[TP_CONTROLLER_ROW_LEN];
+       const char *txt;
+       int ret;
+       int bat = attr_get_bat(attr);
+       if (bat_has_status(bat) != 1)
+               return sprintf(buf, "none\n");
+       ret = read_tp_ec_row(1, bat, 0, row);
+       if (ret)
+               return ret;
+       switch (row[1] & 0xf0) {
+       case 0xc0: txt = "idle"; break;
+       case 0xd0: txt = "discharging"; break;
+       case 0xe0: txt = "charging"; break;
+       default:   return sprintf(buf, "unknown (0x%x)\n", row[1]);
+       }
+       return sprintf(buf, "%s\n", txt);  /* type: string from fixed set */
+}
+
+static ssize_t show_battery_manufacturer(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: string. SBS spec v1.1 p34: ManufacturerName() */
+       return show_tp_ec_bat_str(4, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf);
+}
+
+static ssize_t show_battery_model(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: string. SBS spec v1.1 p34: DeviceName() */
+       return show_tp_ec_bat_str(5, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf);
+}
+
+static ssize_t show_battery_barcoding(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: string */
+       return show_tp_ec_bat_str(7, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf);
+}
+
+static ssize_t show_battery_chemistry(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: string. SBS spec v1.1 p34-35: DeviceChemistry() */
+       return show_tp_ec_bat_str(6, 2, 5, attr, buf);
+}
+
+static ssize_t show_battery_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV. SBS spec v1.1 p24: Voltage() */
+       return show_tp_ec_bat_u16(1, 6, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_design_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV. SBS spec v1.1 p32: DesignVoltage() */
+       return show_tp_ec_bat_u16(3, 4, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_charging_max_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV. SBS spec v1.1 p37,39: ChargingVoltage() */
+       return show_tp_ec_bat_u16(9, 8, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_group0_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV */
+       return show_tp_ec_bat_u16(0xA, 12, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_group1_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV */
+       return show_tp_ec_bat_u16(0xA, 10, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_group2_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV */
+       return show_tp_ec_bat_u16(0xA, 8, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_group3_voltage(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mV */
+       return show_tp_ec_bat_u16(0xA, 6, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_current_now(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mA. SBS spec v1.1 p24: Current() */
+       return show_tp_ec_bat_s16(1, 8, 1, 0, attr, buf);
+}
+
+static ssize_t show_battery_current_avg(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mA. SBS spec v1.1 p24: AverageCurrent() */
+       return show_tp_ec_bat_s16(1, 10, 1, 0, attr, buf);
+}
+
+static ssize_t show_battery_charging_max_current(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mA. SBS spec v1.1 p36,38: ChargingCurrent() */
+       return show_tp_ec_bat_s16(9, 6, 1, 0, attr, buf);
+}
+
+static ssize_t show_battery_power_now(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mW. SBS spec v1.1: Voltage()*Current() */
+       return show_tp_ec_bat_power(1, 6, 8, attr, buf);
+}
+
+static ssize_t show_battery_power_avg(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mW. SBS spec v1.1: Voltage()*AverageCurrent() */
+       return show_tp_ec_bat_power(1, 6, 10, attr, buf);
+}
+
+static ssize_t show_battery_remaining_percent(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: percent. SBS spec v1.1 p25: RelativeStateOfCharge() */
+       return show_tp_ec_bat_u16(1, 12, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_remaining_percent_error(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: percent. SBS spec v1.1 p25: MaxError() */
+       return show_tp_ec_bat_u16(9, 4, 1, NULL, attr, buf);
+}
+
+static ssize_t show_battery_remaining_charging_time(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: minutes. SBS spec v1.1 p27: AverageTimeToFull() */
+       return show_tp_ec_bat_u16(2, 8, 1, "not_charging", attr, buf);
+}
+
+static ssize_t show_battery_remaining_running_time(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */
+       return show_tp_ec_bat_u16(2, 6, 1, "not_discharging", attr, buf);
+}
+
+static ssize_t show_battery_remaining_running_time_now(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */
+       return show_tp_ec_bat_u16(2, 4, 1, "not_discharging", attr, buf);
+}
+
+static ssize_t show_battery_remaining_capacity(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mWh. SBS spec v1.1 p26. */
+       return show_tp_ec_bat_u16(1, 14, 10, "", attr, buf);
+}
+
+static ssize_t show_battery_last_full_capacity(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mWh. SBS spec v1.1 p26: FullChargeCapacity() */
+       return show_tp_ec_bat_u16(2, 2, 10, "", attr, buf);
+}
+
+static ssize_t show_battery_design_capacity(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: mWh. SBS spec v1.1 p32: DesignCapacity() */
+       return show_tp_ec_bat_u16(3, 2, 10, "", attr, buf);
+}
+
+static ssize_t show_battery_cycle_count(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: ordinal. SBS spec v1.1 p32: CycleCount() */
+       return show_tp_ec_bat_u16(2, 12, 1, "", attr, buf); 
+}
+
+static ssize_t show_battery_temperature(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* units: millicelsius. SBS spec v1.1: Temperature()*10 */
+       return show_tp_ec_bat_s16(1, 4, 100, -273100, attr, buf);
+}
+
+static ssize_t show_battery_serial(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: int. SBS spec v1.1 p34: SerialNumber() */
+       return show_tp_ec_bat_u16(3, 10, 1, "", attr, buf);
+}
+
+static ssize_t show_battery_manufacture_date(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: YYYY-MM-DD. SBS spec v1.1 p34: ManufactureDate() */
+       return show_tp_ec_bat_date(3, 8, attr, buf);
+}
+
+static ssize_t show_battery_first_use_date(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       /* type: YYYY-MM-DD */
+       return show_tp_ec_bat_date(8, 2, attr, buf);
+}
+
+/**
+ * show_battery_dump - show the battery's dump attribute
+ * The dump attribute gives a hex dump of all EC readouts related to a
+ * battery. Some of the enumerated values don't really exist (i.e., the
+ * EC function just leaves them untouched); we use a kludge to detect and
+ * denote these.
+ */
+#define MIN_DUMP_ARG0 0x00
+#define MAX_DUMP_ARG0 0x0a /* 0x0b is useful too but hangs old EC firmware */
+static ssize_t show_battery_dump(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       int i;
+       char *p = buf;
+       int bat = attr_get_bat(attr);
+       u8 arg0; /* first argument to EC */
+       u8 rowa[TP_CONTROLLER_ROW_LEN],
+          rowb[TP_CONTROLLER_ROW_LEN];
+       const u8 junka = 0xAA,
+                junkb = 0x55; /* junk values for testing changes */
+       int ret;
+
+       for (arg0 = MIN_DUMP_ARG0; arg0 <= MAX_DUMP_ARG0; ++arg0) {
+               if ((p-buf) > PAGE_SIZE-TP_CONTROLLER_ROW_LEN*5)
+                       return -ENOMEM; /* don't overflow sysfs buf */
+               /* Read raw twice with different junk values,
+                * to detect unused output bytes which are left unchaged: */
+               ret = read_tp_ec_row(arg0, bat, junka, rowa);
+               if (ret)
+                       return ret;
+               ret = read_tp_ec_row(arg0, bat, junkb, rowb);
+               if (ret)
+                       return ret;
+               for (i = 0; i < TP_CONTROLLER_ROW_LEN; i++) {
+                       if (rowa[i] == junka && rowb[i] == junkb)
+                               p += sprintf(p, "-- "); /* unused by EC */
+                       else
+                               p += sprintf(p, "%02x ", rowa[i]);
+               }
+               p += sprintf(p, "\n");
+       }
+       return p-buf;
+}
+
+
+/*********************************************************************
+ * sysfs attribute I/O, other than batteries
+ */
+
+static ssize_t show_ac_connected(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       int ret = power_device_present(0xFF);
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%d\n", ret);  /* type: boolean */
+}
+
+/*********************************************************************
+ * The the "smapi_request" sysfs attribute executes a raw SMAPI call.
+ * You write to make a request and read to get the result. The state
+ * is saved globally rather than per fd (sysfs limitation), so
+ * simultaenous requests may get each other's results! So this is for
+ * development and debugging only.
+ */
+#define MAX_SMAPI_ATTR_ANSWER_LEN   128
+static char smapi_attr_answer[MAX_SMAPI_ATTR_ANSWER_LEN] = "";
+
+static ssize_t show_smapi_request(struct device *dev,
+                                 struct device_attribute *attr, char *buf)
+{
+       int ret = snprintf(buf, PAGE_SIZE, "%s", smapi_attr_answer);
+       smapi_attr_answer[0] = '\0';
+       return ret;
+}
+
+static ssize_t store_smapi_request(struct device *dev,
+                                  struct device_attribute *attr,
+                                  const char *buf, size_t count)
+{
+       unsigned int inEBX, inECX, inEDI, inESI;
+       u32 outEBX, outECX, outEDX, outEDI, outESI;
+       const char *msg;
+       int ret;
+       if (sscanf(buf, "%x %x %x %x", &inEBX, &inECX, &inEDI, &inESI) != 4) {
+               smapi_attr_answer[0] = '\0';
+               return -EINVAL;
+       }
+       ret = smapi_request(
+                  inEBX, inECX, inEDI, inESI,
+                  &outEBX, &outECX, &outEDX, &outEDI, &outESI, &msg);
+       snprintf(smapi_attr_answer, MAX_SMAPI_ATTR_ANSWER_LEN,
+                "%x %x %x %x %x %d '%s'\n",
+                (unsigned int)outEBX, (unsigned int)outECX,
+                (unsigned int)outEDX, (unsigned int)outEDI,
+                (unsigned int)outESI, ret, msg);
+       if (ret)
+               return ret;
+       else
+               return count;
+}
+
+/*********************************************************************
+ * Power management: the embedded controller forgets the battery
+ * thresholds when the system is suspended to disk and unplugged from
+ * AC and battery, so we restore it upon resume.
+ */
+
+static int saved_threshs[4] = {-1, -1, -1, -1};  /* -1 = don't know */
+
+static int tp_suspend(struct platform_device *dev, pm_message_t state)
+{
+       int restore = (state.event == PM_EVENT_HIBERNATE ||
+                      state.event == PM_EVENT_FREEZE);
+       if (!restore || get_real_thresh(0, THRESH_STOP , &saved_threshs[0]))
+               saved_threshs[0] = -1;
+       if (!restore || get_real_thresh(0, THRESH_START, &saved_threshs[1]))
+               saved_threshs[1] = -1;
+       if (!restore || get_real_thresh(1, THRESH_STOP , &saved_threshs[2]))
+               saved_threshs[2] = -1;
+       if (!restore || get_real_thresh(1, THRESH_START, &saved_threshs[3]))
+               saved_threshs[3] = -1;
+       DPRINTK("suspend saved: %d %d %d %d", saved_threshs[0],
+               saved_threshs[1], saved_threshs[2], saved_threshs[3]);
+       return 0;
+}
+
+static int tp_resume(struct platform_device *dev)
+{
+       DPRINTK("resume restoring: %d %d %d %d", saved_threshs[0],
+               saved_threshs[1], saved_threshs[2], saved_threshs[3]);
+       if (saved_threshs[0] >= 0)
+               set_real_thresh(0, THRESH_STOP , saved_threshs[0]);
+       if (saved_threshs[1] >= 0)
+               set_real_thresh(0, THRESH_START, saved_threshs[1]);
+       if (saved_threshs[2] >= 0)
+               set_real_thresh(1, THRESH_STOP , saved_threshs[2]);
+       if (saved_threshs[3] >= 0)
+               set_real_thresh(1, THRESH_START, saved_threshs[3]);
+       return 0;
+}
+
+
+/*********************************************************************
+ * Driver model
+ */
+
+static struct platform_driver tp_driver = {
+       .suspend = tp_suspend,
+       .resume = tp_resume,
+       .driver = {
+               .name = "smapi",
+               .owner = THIS_MODULE
+       },
+};
+
+
+/*********************************************************************
+ * Sysfs device model
+ */
+
+/* Attributes in /sys/devices/platform/smapi/ */
+
+static DEVICE_ATTR(ac_connected, 0444, show_ac_connected, NULL);
+static DEVICE_ATTR(smapi_request, 0600, show_smapi_request,
+                                       store_smapi_request);
+
+static struct attribute *tp_root_attributes[] = {
+       &dev_attr_ac_connected.attr,
+       &dev_attr_smapi_request.attr,
+       NULL
+};
+static struct attribute_group tp_root_attribute_group = {
+       .attrs = tp_root_attributes
+};
+
+/* Attributes under /sys/devices/platform/smapi/BAT{0,1}/ :
+ * Every attribute needs to be defined (i.e., statically allocated) for
+ * each battery, and then referenced in the attribute list of each battery.
+ * We use preprocessor voodoo to avoid duplicating the list of attributes 4
+ * times. The preprocessor output is just normal sysfs attributes code.
+ */
+
+/**
+ * FOREACH_BAT_ATTR - invoke the given macros on all our battery attributes
+ * @_BAT:     battery number (0 or 1)
+ * @_ATTR_RW: macro to invoke for each read/write attribute
+ * @_ATTR_R:  macro to invoke for each read-only  attribute
+ */
+#define FOREACH_BAT_ATTR(_BAT, _ATTR_RW, _ATTR_R) \
+       _ATTR_RW(_BAT, start_charge_thresh) \
+       _ATTR_RW(_BAT, stop_charge_thresh) \
+       _ATTR_RW(_BAT, inhibit_charge_minutes) \
+       _ATTR_RW(_BAT, force_discharge) \
+       _ATTR_R(_BAT, installed) \
+       _ATTR_R(_BAT, state) \
+       _ATTR_R(_BAT, manufacturer) \
+       _ATTR_R(_BAT, model) \
+       _ATTR_R(_BAT, barcoding) \
+       _ATTR_R(_BAT, chemistry) \
+       _ATTR_R(_BAT, voltage) \
+       _ATTR_R(_BAT, group0_voltage) \
+       _ATTR_R(_BAT, group1_voltage) \
+       _ATTR_R(_BAT, group2_voltage) \
+       _ATTR_R(_BAT, group3_voltage) \
+       _ATTR_R(_BAT, current_now) \
+       _ATTR_R(_BAT, current_avg) \
+       _ATTR_R(_BAT, charging_max_current) \
+       _ATTR_R(_BAT, power_now) \
+       _ATTR_R(_BAT, power_avg) \
+       _ATTR_R(_BAT, remaining_percent) \
+       _ATTR_R(_BAT, remaining_percent_error) \
+       _ATTR_R(_BAT, remaining_charging_time) \
+       _ATTR_R(_BAT, remaining_running_time) \
+       _ATTR_R(_BAT, remaining_running_time_now) \
+       _ATTR_R(_BAT, remaining_capacity) \
+       _ATTR_R(_BAT, last_full_capacity) \
+       _ATTR_R(_BAT, design_voltage) \
+       _ATTR_R(_BAT, charging_max_voltage) \
+       _ATTR_R(_BAT, design_capacity) \
+       _ATTR_R(_BAT, cycle_count) \
+       _ATTR_R(_BAT, temperature) \
+       _ATTR_R(_BAT, serial) \
+       _ATTR_R(_BAT, manufacture_date) \
+       _ATTR_R(_BAT, first_use_date) \
+       _ATTR_R(_BAT, dump)
+
+/* Define several macros we will feed into FOREACH_BAT_ATTR: */
+
+#define DEFINE_BAT_ATTR_RW(_BAT,_NAME) \
+       static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = {  \
+               .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME,   \
+                                               store_battery_##_NAME), \
+               .bat = _BAT \
+       };
+
+#define DEFINE_BAT_ATTR_R(_BAT,_NAME) \
+       static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = {    \
+               .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, 0), \
+               .bat = _BAT \
+       };
+
+#define REF_BAT_ATTR(_BAT,_NAME) \
+       &dev_attr_##_NAME##_##_BAT.dev_attr.attr,
+
+/* This provide all attributes for one battery: */
+
+#define PROVIDE_BAT_ATTRS(_BAT) \
+       FOREACH_BAT_ATTR(_BAT, DEFINE_BAT_ATTR_RW, DEFINE_BAT_ATTR_R) \
+       static struct attribute *tp_bat##_BAT##_attributes[] = { \
+               FOREACH_BAT_ATTR(_BAT, REF_BAT_ATTR, REF_BAT_ATTR) \
+               NULL \
+       }; \
+       static struct attribute_group tp_bat##_BAT##_attribute_group = { \
+               .name  = "BAT" #_BAT, \
+               .attrs = tp_bat##_BAT##_attributes \
+       };
+
+/* Finally genereate the attributes: */
+
+PROVIDE_BAT_ATTRS(0)
+PROVIDE_BAT_ATTRS(1)
+
+/* List of attribute groups */
+
+static struct attribute_group *attr_groups[] = {
+       &tp_root_attribute_group,
+       &tp_bat0_attribute_group,
+       &tp_bat1_attribute_group,
+       NULL
+};
+
+
+/*********************************************************************
+ * Init and cleanup
+ */
+
+static struct attribute_group **next_attr_group; /* next to register */
+
+static int __init tp_init(void)
+{
+       int ret;
+       printk(KERN_INFO "tp_smapi " TP_VERSION " loading...\n");
+
+       ret = find_smapi_port();
+       if (ret < 0)
+               goto err;
+       else
+               smapi_port = ret;
+
+       if (!request_region(smapi_port, 1, "smapi")) {
+               printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n",
+                      smapi_port);
+               ret = -ENXIO;
+               goto err;
+       }
+
+       if (!request_region(SMAPI_PORT2, 1, "smapi")) {
+               printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n",
+                      SMAPI_PORT2);
+               ret = -ENXIO;
+               goto err_port1;
+       }
+
+       ret = platform_driver_register(&tp_driver);
+       if (ret)
+               goto err_port2;
+
+       pdev = platform_device_alloc("smapi", -1);
+       if (!pdev) {
+               ret = -ENOMEM;
+               goto err_driver;
+       }
+
+       ret = platform_device_add(pdev);
+       if (ret)
+               goto err_device_free;
+
+       for (next_attr_group = attr_groups; *next_attr_group;
+            ++next_attr_group) {
+               ret = sysfs_create_group(&pdev->dev.kobj, *next_attr_group);
+               if (ret)
+                       goto err_attr;
+       }
+
+       printk(KERN_INFO "tp_smapi successfully loaded (smapi_port=0x%x).\n",
+              smapi_port);
+       return 0;
+
+err_attr:
+       while (--next_attr_group >= attr_groups)
+               sysfs_remove_group(&pdev->dev.kobj, *next_attr_group);
+       platform_device_unregister(pdev);
+err_device_free:
+       platform_device_put(pdev);
+err_driver:
+       platform_driver_unregister(&tp_driver);
+err_port2:
+       release_region(SMAPI_PORT2, 1);
+err_port1:
+       release_region(smapi_port, 1);
+err:
+       printk(KERN_ERR "tp_smapi init failed (ret=%d)!\n", ret);
+       return ret;
+}
+
+static void __exit tp_exit(void)
+{
+       while (next_attr_group && --next_attr_group >= attr_groups)
+               sysfs_remove_group(&pdev->dev.kobj, *next_attr_group);
+       platform_device_unregister(pdev);
+       platform_driver_unregister(&tp_driver);
+       release_region(SMAPI_PORT2, 1);
+       if (smapi_port)
+               release_region(smapi_port, 1);
+
+       printk(KERN_INFO "tp_smapi unloaded.\n");
+}
+
+module_init(tp_init);
+module_exit(tp_exit);
similarity index 100%
rename from ueventd.thinkpad_x41t.rc
rename to ueventd.thinkpad.rc
index 4e2ada3..cad7663 100644 (file)
@@ -1,2 +1,2 @@
-add_lunch_combo thinkpad_x41t-eng
-add_lunch_combo thinkpad_x41t-user
+add_lunch_combo thinkpad-eng
+add_lunch_combo thinkpad-user
diff --git a/wakeup_button/Android.mk b/wakeup_button/Android.mk
new file mode 100644 (file)
index 0000000..16d0f46
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# Copyright (C) 2009-2011 The Android-x86 Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := wakeup_button
+EXTRA_KERNEL_MODULE_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH)
diff --git a/wakeup_button/Makefile b/wakeup_button/Makefile
new file mode 100644 (file)
index 0000000..194c654
--- /dev/null
@@ -0,0 +1 @@
+obj-m += wakeup_button.o
diff --git a/wakeup_button/wakeup_button.c b/wakeup_button/wakeup_button.c
new file mode 100644 (file)
index 0000000..dcc3158
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * wakeup_button.c - Power Button which is pushed on resume from standby.
+ *
+ * Copyright (c) 2011 Stefan Seidel
+ *
+ * This file is released under the GPLv2 or later.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/earlysuspend.h>
+#include <linux/input.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Stefan Seidel <android@stefanseidel.info>");
+MODULE_DESCRIPTION("Sets up a virtual input device and sends a power key event during early resume. Needed for some to make Android on x86 wake up properly.");
+
+static struct input_dev *input;
+
+static void wakeup_button_early_suspend(struct early_suspend *h)
+{
+       return;
+}
+
+static void wakeup_button_early_resume(struct early_suspend *h)
+{
+       printk("Early resume, push virtual power button!\n");
+       input_report_key(input, KEY_POWER, 1);
+       input_sync(input);
+       input_report_key(input, KEY_POWER, 0);
+       input_sync(input);
+}
+
+static struct early_suspend wakeup_button_early_suspend_handlers = {
+       .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN - 1, // very late resume
+       .suspend = wakeup_button_early_suspend,
+       .resume = wakeup_button_early_resume
+};
+
+static int __init wakeup_button_init(void)
+{
+       int error;
+       printk("Registering Android Wakeup Button.\n");
+       input = input_allocate_device();
+       input->name = "Wakeup Button";
+       input->id.bustype = BUS_USB; // use BUS_USB here so that Android registers this as an external key
+       input->evbit[0] = BIT_MASK(EV_KEY);
+       set_bit(KEY_POWER, input->keybit);
+       error = input_register_device(input);
+       if (error) {
+               input_free_device(input);
+       } else {
+               register_early_suspend(&wakeup_button_early_suspend_handlers);
+       }
+       return error;
+}
+
+static void __exit wakeup_button_exit(void)
+{
+       printk("Unregistering Android Wakeup Button.\n");
+       unregister_early_suspend(&wakeup_button_early_suspend_handlers);
+       input_unregister_device(input);
+       return;
+}
+
+module_init(wakeup_button_init);
+module_exit(wakeup_button_exit);
diff --git a/x41t.mk b/x41t.mk
deleted file mode 100644 (file)
index 45738ac..0000000
--- a/x41t.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-PRODUCT_PACKAGES := $(THIRD_PARTY_APPS)
-PRODUCT_PACKAGES += lights.default
-PRODUCT_PACKAGES += sensors.$(TARGET_PRODUCT)
-PRODUCT_PACKAGES += wacom-input
-PRODUCT_PACKAGES += tablet-mode
-PRODUCT_PACKAGES += su Superuser FileManager libchromium_net alsa_amixer radiooptions rild libreference-ril
-
-$(call inherit-product,$(SRC_TARGET_DIR)/product/generic_x86.mk)
-
-PRODUCT_NAME := thinkpad_x41t
-PRODUCT_DEVICE := x41t
-PRODUCT_MANUFACTURER := ibm
-
-DEVICE_PACKAGE_OVERLAYS := $(LOCAL_PATH)/overlays
-
-PRODUCT_COPY_FILES += \
-    $(LOCAL_PATH)/ueventd.$(TARGET_PRODUCT).rc:root/ueventd.$(TARGET_PRODUCT).rc \
-    $(LOCAL_PATH)/wacom-input.idc:system/usr/idc/wacom-input.idc \
-    $(LOCAL_PATH)/AT_Translated_Set_2_keyboard.idc:system/usr/idc/AT_Translated_Set_2_keyboard.idc \
-
-#PRODUCT_COPY_FILES += \
-#    $(LOCAL_PATH)/system/etc/permissions/features.xml:system/etc/permissions/features.xml \
-#    $(LOCAL_PATH)/system/etc/permissions/com.google.android.media.effects.xml:system/etc/permissions/com.google.android.media.effects.xml \
-#    $(LOCAL_PATH)/system/etc/permissions/com.google.android.maps.xml:system/etc/permissions/com.google.android.maps.xml \
-#    $(LOCAL_PATH)/system/framework/com.google.android.maps.jar:system/framework/com.google.android.maps.jar \
-#    $(LOCAL_PATH)/system/framework/com.google.android.media.effects.jar:system/framework/com.google.android.media.effects.jar
-
-include $(call all-subdir-makefiles)