- patches.suse/acpi-hotkeys-extra.diff: Extra ACPI-hotkey drivers
authorAndreas Gruenbacher <agruen@suse.de>
Tue, 31 Jan 2006 20:22:56 +0000 (20:22 +0000)
committerAndreas Gruenbacher <agruen@suse.de>
Tue, 31 Jan 2006 20:22:56 +0000 (20:22 +0000)
  for SONY and PANASONIC Notebooks (135579).

suse-commit: d6148e22ea54d0230b0a782206a1099149cb080a

Documentation/acpi/sony_acpi.txt [new file with mode: 0644]
drivers/acpi/Kconfig
drivers/acpi/Kconfig.orig [new file with mode: 0644]
drivers/acpi/Makefile
drivers/acpi/pcc_acpi.c [new file with mode: 0644]
drivers/acpi/sony_acpi.c [new file with mode: 0644]

diff --git a/Documentation/acpi/sony_acpi.txt b/Documentation/acpi/sony_acpi.txt
new file mode 100644 (file)
index 0000000..35a04be
--- /dev/null
@@ -0,0 +1,87 @@
+ACPI Sony Notebook Control Driver (SNC) Readme
+----------------------------------------------
+       Copyright (C) 2004- 2005 Stelian Pop <stelian@popies.net>
+
+This mini-driver drives the ACPI SNC device present in the
+ACPI BIOS of the Sony Vaio laptops.
+
+It gives access to some extra laptop functionalities. In
+its current form, this driver is mainly useful for controlling the
+screen brightness, but it may do more in the future.
+
+You should probably start by trying the sonypi driver, and try
+sony_acpi only if sonypi doesn't work for you.
+
+Usage:
+------
+
+Loading the sony_acpi module will create a /proc/acpi/sony/
+directory populated with a couple of files.
+
+You then read/write integer values from/to those files by using
+standard UNIX tools.
+
+The files are:
+       brightness              current screen brightness
+       brightness_default      screen brightness which will be set
+                               when the laptop will be rebooted
+       cdpower                 power on/off the internal CD drive
+
+Note that some files may be missing if they are not supported
+by your particular laptop model.
+
+Example usage:
+       # echo "1" > /proc/acpi/sony/brightness
+sets the lowest screen brightness,
+       # echo "8" > /proc/acpi/sony/brightness
+sets the highest screen brightness,
+       # cat /proc/acpi/sony/brightness
+retrieves the current screen brightness.
+
+Development:
+------------
+
+If you want to help with the development of this driver (and
+you are not afraid of any side effects doing strange things with
+your ACPI BIOS could have on your laptop), load the driver and
+pass the option 'debug=1'.
+
+REPEAT: DON'T DO THIS IF YOU DON'T LIKE RISKY BUSINESS.
+
+In your kernel logs you will find the list of all ACPI methods
+the SNC device has on your laptop. You can see the GBRT/SBRT methods
+used to get/set the brightness, but there are others.
+
+I HAVE NO IDEA WHAT THOSE METHODS DO.
+
+The sony_acpi driver creates, for some of those methods (the most
+current ones found on several Vaio models), an entry under
+/proc/acpi/sony/, just like the 'brightness' one. You can create
+other entries corresponding to your own laptop methods by further
+editing the source (see the 'sony_acpi_values' table, and add a new
+structure to this table with your get/set method names).
+
+Your mission, should you accept it, is to try finding out what
+those entries are for, by reading/writing random values from/to those
+files and find out what is the impact on your laptop.
+
+Should you find anything interesting, please report it back to me,
+I will not disavow all knowledge of your actions :)
+
+Bugs/Limitations:
+-----------------
+
+* This driver is not based on official documentation from Sony
+  (because there is none), so there is no guarantee this driver
+  will work at all, or do the right thing. Although this hasn't
+  happened to me, this driver could do very bad things to your
+  laptop, including permanent damage.
+
+* The sony_acpi and sonypi drivers do not interact at all. In the
+  future, sonypi could use sony_acpi to do (part of) its business.
+
+* spicctrl, which is the userspace tool used to communicate with the
+  sonypi driver (through /dev/sonypi) does not try to use the
+  sony_acpi driver. In the future, spicctrl could try sonypi first,
+  and if it isn't present, try sony_acpi instead.
+
index 2d26133..de5f533 100644 (file)
@@ -231,6 +231,35 @@ config ACPI_TOSHIBA
          If you have a legacy free Toshiba laptop (such as the Libretto L1
          series), say Y.
 
+config ACPI_SONY
+       tristate "Sony Laptop Extras"
+       depends on X86
+       default m
+         ---help---
+         This mini-driver drives the ACPI SNC device present in the
+         ACPI BIOS of the Sony Vaio laptops.
+
+         It gives access to some extra laptop functionalities. In
+         its current form, the only thing this driver does is letting
+         the user set or query the screen brightness.
+
+         Read <file:Documentation/acpi/sony_acpi.txt> for more information.
+
+config ACPI_PCC
+        tristate "Panasonic Laptop Extras"
+        depends on X86
+        default m
+           ---help---
+           This driver implements hotkey functionality and access to
+          various hardware (e.g. LCD brightness) for the
+           Panasonic R1 (N variant), R2, R3, T2, W2, and Y2 laptops.
+
+           Further details and user space tools are available at
+           <http://www.da-cha.org/letsnote/>.
+
+          If you have one of the above listed Panasonic laptops
+          say Y or M here.
+
 config ACPI_CUSTOM_DSDT
        bool "Include Custom DSDT"
        depends on !STANDALONE
diff --git a/drivers/acpi/Kconfig.orig b/drivers/acpi/Kconfig.orig
new file mode 100644 (file)
index 0000000..0c21a11
--- /dev/null
@@ -0,0 +1,366 @@
+#
+# ACPI Configuration
+#
+
+menu "ACPI (Advanced Configuration and Power Interface) Support"
+       depends on !X86_VISWS
+       depends on !IA64_HP_SIM
+       depends on IA64 || X86
+
+config ACPI
+       bool "ACPI Support"
+       depends on IA64 || X86
+       select PM
+       select PCI
+
+       default y
+       ---help---
+         Advanced Configuration and Power Interface (ACPI) support for
+         Linux requires an ACPI compliant platform (hardware/firmware),
+         and assumes the presence of OS-directed configuration and power
+         management (OSPM) software.  This option will enlarge your
+         kernel by about 70K.
+
+         Linux ACPI provides a robust functional replacement for several
+         legacy configuration and power management interfaces, including
+         the Plug-and-Play BIOS specification (PnP BIOS), the
+         MultiProcessor Specification (MPS), and the Advanced Power
+         Management (APM) specification.  If both ACPI and APM support
+         are configured, whichever is loaded first shall be used.
+
+         The ACPI SourceForge project contains the latest source code,
+         documentation, tools, mailing list subscription, and other
+         information.  This project is available at:
+         <http://sourceforge.net/projects/acpi>
+
+         Linux support for ACPI is based on Intel Corporation's ACPI
+         Component Architecture (ACPI CA).  For more information see:
+         <http://developer.intel.com/technology/iapc/acpi>
+
+         ACPI is an open industry specification co-developed by Compaq,
+         Intel, Microsoft, Phoenix, and Toshiba.  The specification is
+         available at:
+         <http://www.acpi.info>
+
+if ACPI
+
+config ACPI_SLEEP
+       bool "Sleep States"
+       depends on X86 && (!SMP || SUSPEND_SMP) && !XEN
+       depends on PM
+       default y
+       ---help---
+         This option adds support for ACPI suspend states.
+
+         With this option, you will be able to put the system "to sleep".
+         Sleep states are low power states for the system and devices. All
+         of the system operating state is saved to either memory or disk
+         (depending on the state), to allow the system to resume operation
+         quickly at your request.
+
+         Although this option sounds really nifty, barely any of the device
+         drivers have been converted to the new driver model and hence few
+         have proper power management support.
+
+         This option is not recommended for anyone except those doing driver
+         power management development.
+
+config ACPI_SLEEP_PROC_FS
+       bool
+       depends on ACPI_SLEEP && PROC_FS
+       default y
+
+config ACPI_SLEEP_PROC_SLEEP
+       bool "/proc/acpi/sleep (deprecated)"
+       depends on ACPI_SLEEP_PROC_FS
+       default n
+       ---help---
+         Create /proc/acpi/sleep
+         Deprecated by /sys/power/state
+
+config ACPI_AC
+       tristate "AC Adapter"
+       depends on X86
+       default y
+       help
+         This driver adds support for the AC Adapter object, which indicates
+         whether a system is on AC, or not. If you have a system that can
+         switch between A/C and battery, say Y.
+
+config ACPI_BATTERY
+       tristate "Battery"
+       depends on X86
+       default y
+       help
+         This driver adds support for battery information through
+         /proc/acpi/battery. If you have a mobile system with a battery,
+         say Y.
+
+config ACPI_BUTTON
+       tristate "Button"
+       default y
+       help
+         This driver handles events on the power, sleep and lid buttons.
+         A daemon reads /proc/acpi/event and perform user-defined actions
+         such as shutting down the system.  This is necessary for
+         software controlled poweroff.
+
+config ACPI_VIDEO
+       tristate "Video"
+       depends on X86
+       default y
+       help
+         This driver implement the ACPI Extensions For Display Adapters
+         for integrated graphics devices on motherboard, as specified in
+         ACPI 2.0 Specification, Appendix B, allowing to perform some basic
+         control like defining the video POST device, retrieving EDID information
+         or to setup a video output, etc.
+         Note that this is an ref. implementation only.  It may or may not work
+         for your integrated video device.
+
+config ACPI_HOTKEY
+       tristate "Generic Hotkey (EXPERIMENTAL)"
+       depends on EXPERIMENTAL
+       depends on X86
+       default n
+       help
+         Experimental consolidated hotkey driver.
+         If you are unsure, say N.
+
+config ACPI_FAN
+       tristate "Fan"
+       default y
+       help
+         This driver adds support for ACPI fan devices, allowing user-mode
+         applications to perform basic fan control (on, off, status).
+
+config ACPI_PROCESSOR
+       tristate "Processor"
+       default y
+       help
+         This driver installs ACPI as the idle handler for Linux, and uses
+         ACPI C2 and C3 processor states to save power, on systems that
+         support it.  It is required by several flavors of cpufreq
+         Performance-state drivers.
+
+config ACPI_HOTPLUG_CPU
+       bool
+       depends on ACPI_PROCESSOR && HOTPLUG_CPU
+       select ACPI_CONTAINER
+       default y
+
+config ACPI_THERMAL
+       tristate "Thermal Zone"
+       depends on ACPI_PROCESSOR
+       default y
+       help
+         This driver adds support for ACPI thermal zones.  Most mobile and
+         some desktop systems support ACPI thermal zones.  It is HIGHLY
+         recommended that this option be enabled, as your processor(s)
+         may be damaged without it.
+
+config ACPI_NUMA
+       bool "NUMA support"
+       depends on NUMA
+       depends on (IA64 || X86_64)
+       default y if IA64_GENERIC || IA64_SGI_SN2
+
+config ACPI_ASUS
+        tristate "ASUS/Medion Laptop Extras"
+       depends on X86
+        ---help---
+          This driver provides support for extra features of ACPI-compatible
+          ASUS laptops. As some of Medion laptops are made by ASUS, it may also
+          support some Medion laptops (such as 9675 for example).  It makes all
+          the extra buttons generate standard ACPI events that go through
+          /proc/acpi/events, and (on some models) adds support for changing the
+          display brightness and output, switching the LCD backlight on and off,
+          and most importantly, allows you to blink those fancy LEDs intended
+          for reporting mail and wireless status.
+
+         Note: display switching code is currently considered EXPERIMENTAL,
+         toying with these values may even lock your machine.
+
+          All settings are changed via /proc/acpi/asus directory entries. Owner
+          and group for these entries can be set with asus_uid and asus_gid
+          parameters.
+
+          More information and a userspace daemon for handling the extra buttons
+          at <http://sourceforge.net/projects/acpi4asus/>.
+
+          If you have an ACPI-compatible ASUS laptop, say Y or M here. This
+          driver is still under development, so if your laptop is unsupported or
+          something works not quite as expected, please use the mailing list
+          available on the above page (acpi4asus-user@lists.sourceforge.net)
+
+config ACPI_IBM
+       tristate "IBM ThinkPad Laptop Extras"
+       depends on X86
+       ---help---
+         This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds
+         support for Fn-Fx key combinations, Bluetooth control, video
+         output switching, ThinkLight control, UltraBay eject and more.
+         For more information about this driver see <file:Documentation/ibm-acpi.txt>
+         and <http://ibm-acpi.sf.net/> .
+
+         If you have an IBM ThinkPad laptop, say Y or M here.
+
+config ACPI_TOSHIBA
+       tristate "Toshiba Laptop Extras"
+       depends on X86
+       ---help---
+         This driver adds support for access to certain system settings
+         on "legacy free" Toshiba laptops.  These laptops can be recognized by
+         their lack of a BIOS setup menu and APM support.
+
+         On these machines, all system configuration is handled through the
+         ACPI.  This driver is required for access to controls not covered
+         by the general ACPI drivers, such as LCD brightness, video output,
+         etc.
+
+         This driver differs from the non-ACPI Toshiba laptop driver (located
+         under "Processor type and features") in several aspects.
+         Configuration is accessed by reading and writing text files in the
+         /proc tree instead of by program interface to /dev.  Furthermore, no
+         power management functions are exposed, as those are handled by the
+         general ACPI drivers.
+
+         More information about this driver is available at
+         <http://memebeam.org/toys/ToshibaAcpiDriver>.
+
+         If you have a legacy free Toshiba laptop (such as the Libretto L1
+         series), say Y.
+
+config ACPI_CUSTOM_DSDT
+       bool "Include Custom DSDT"
+       depends on !STANDALONE
+       default n
+       help
+         Thist option is to load a custom ACPI DSDT
+         If you don't know what that is, say N.
+
+config ACPI_CUSTOM_DSDT_FILE
+       string "Custom DSDT Table file to include"
+       depends on ACPI_CUSTOM_DSDT
+       default ""
+       help
+         Enter the full path name to the file wich includes the AmlCode declaration.
+
+config ACPI_BLACKLIST_YEAR
+       int "Disable ACPI for systems before Jan 1st this year" if X86
+       default 0
+       help
+         enter a 4-digit year, eg. 2001 to disable ACPI by default
+         on platforms with DMI BIOS date before January 1st that year.
+         "acpi=force" can be used to override this mechanism.
+
+         Enter 0 to disable this mechanism and allow ACPI to
+         run by default no matter what the year.  (default)
+
+config ACPI_DEBUG
+       bool "Debug Statements"
+       default n
+       help
+         The ACPI driver can optionally report errors with a great deal
+         of verbosity. Saying Y enables these statements. This will increase
+         your kernel size by around 50K.
+
+config ACPI_EC
+       bool
+       depends on X86
+       default y
+       help
+         This driver is required on some systems for the proper operation of
+         the battery and thermal drivers.  If you are compiling for a
+         mobile system, say Y.
+
+config ACPI_POWER
+       bool
+       default y
+
+config ACPI_SYSTEM
+       bool
+       default y
+       help
+         This driver will enable your system to shut down using ACPI, and
+         dump your ACPI DSDT table using /proc/acpi/dsdt.
+
+config X86_PM_TIMER
+       bool "Power Management Timer Support"
+       depends on X86
+       depends on !X86_64
+       default y
+       help
+         The Power Management Timer is available on all ACPI-capable,
+         in most cases even if ACPI is unusable or blacklisted.
+
+         This timing source is not affected by powermanagement features
+         like aggressive processor idling, throttling, frequency and/or
+         voltage scaling, unlike the commonly used Time Stamp Counter
+         (TSC) timing source.
+
+         So, if you see messages like 'Losing too many ticks!' in the
+         kernel logs, and/or you are using this on a notebook which
+         does not yet have an HPET, you should say "Y" here.
+
+config ACPI_CONTAINER
+       tristate "ACPI0004,PNP0A05 and PNP0A06 Container Driver (EXPERIMENTAL)"
+       depends on EXPERIMENTAL
+       default (ACPI_HOTPLUG_MEMORY || ACPI_HOTPLUG_CPU || ACPI_HOTPLUG_IO)
+        ---help---
+         This allows _physical_ insertion and removal of CPUs and memory.
+         This can be useful, for example, on NUMA machines that support
+         ACPI based physical hotplug of nodes, or non-NUMA machines that
+         support physical cpu/memory hot-plug.
+
+         If one selects "m", this driver can be loaded with
+         "modprobe acpi_container".
+
+config ACPI_HOTPLUG_MEMORY
+       tristate "Memory Hotplug"
+       depends on ACPI
+       depends on MEMORY_HOTPLUG || X86_64
+       default n
+       help
+         This driver adds supports for ACPI Memory Hotplug.  This driver
+         provides support for fielding notifications on ACPI memory
+         devices (PNP0C80) which represent memory ranges that may be
+         onlined or offlined during runtime.
+
+         Enabling this driver assumes that your platform hardware
+         and firmware have support for hot-plugging physical memory. If
+         your system does not support physically adding or ripping out
+         memory DIMMs at some platfrom defined granularity (individually
+         or as a bank) at runtime, then you need not enable this driver.
+
+         If one selects "m," this driver can be loaded using the following
+         command:
+               $>modprobe acpi_memhotplug
+
+config ACPI_INITRD
+       bool "Read DSDT from initrd or initramfs"
+       depends on ACPI && BLK_DEV_INITRD && !ACPI_CUSTOM_DSDT
+       default n
+       help
+         The DSDT (Differentiated System Description Table) often needs to be
+         overridden because of broken BIOS implementations. If you want to use
+         a customized DSDT, please use the mkinitrd tool (mkinitrd package) to
+         attach the DSDT to the initrd or initramfs
+         (see http://gaugusch.at/kernel.shtml for details)
+         If there is no DSDT found in the initrd, the DSDT from the BIOS is
+         used. It is safe to say yes here.
+
+config ACPI_DEBUG_LITE
+        bool "Print ACPI errors and warnings"
+        depends on ACPI
+        default n
+        help
+          Full ACPI_DEBUG output slows down the system significantly,
+         even if not enabled during runtime.
+         This variable takes care that errors and warnings will still be printed,
+         but higher debug levels can not be enabled any more during runtime.
+
+
+endif  # ACPI
+
+endmenu
index 8faeb03..7363413 100644 (file)
@@ -59,5 +59,7 @@ obj-$(CONFIG_ACPI_NUMA)               += numa.o
 obj-$(CONFIG_ACPI_ASUS)                += asus_acpi.o
 obj-$(CONFIG_ACPI_IBM)         += ibm_acpi.o
 obj-$(CONFIG_ACPI_TOSHIBA)     += toshiba_acpi.o
+obj-$(CONFIG_ACPI_SONY)                += sony_acpi.o
+obj-$(CONFIG_ACPI_PCC)         += pcc_acpi.o
 obj-y                          += scan.o motherboard.o
 obj-$(CONFIG_ACPI_HOTPLUG_MEMORY)      += acpi_memhotplug.o
diff --git a/drivers/acpi/pcc_acpi.c b/drivers/acpi/pcc_acpi.c
new file mode 100644 (file)
index 0000000..e542cc0
--- /dev/null
@@ -0,0 +1,940 @@
+/*
+ *  Panasonic HotKey and lcd brightness control Extra driver
+ *  (C) 2004 Hiroshi Miura <miura@da-cha.org>
+ *  (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/
+ *  (C) 2005 Timo Hoenig <thoenig@nouse.net>
+ *  (C) 2006 Stefan Seyfried <seife@suse.de>
+ *
+ *  derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  publicshed 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#define ACPI_PCC_VERSION       "0.8.3"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/ctype.h>
+#include <linux/seq_file.h>
+#include <linux/input.h>
+
+#include <asm/uaccess.h>
+
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Hiroshi Miura");
+MODULE_DESCRIPTION("ACPI driver for Panasonic Lets Note laptops");
+MODULE_LICENSE("GPL");
+
+/* Defines */
+#define ACPI_HOTKEY_COMPONENT  0x10000000
+#define _COMPONENT             ACPI_HOTKEY_COMPONENT
+#define HKEY_NOTIFY            0x80
+#define PROC_PCC               "panasonic"
+
+#define PCC_LOG    "pcc_acpi: "
+#define PCC_ERR    KERN_ERR    PCC_LOG
+#define PCC_INFO   KERN_INFO   PCC_LOG
+
+/* This is transitional definition */
+#ifndef KEY_BATT
+# define KEY_BATT 227
+#endif
+
+#define PROC_STR_MAX_LEN  8
+
+/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
+ * ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
+ *
+ */
+enum SINF_BITS { SINF_NUM_BATTERIES = 0,
+                 SINF_LCD_TYPE, SINF_AC_MAX_BRIGHT,
+                SINF_AC_MIN_BRIGHT, SINF_AC_CUR_BRIGHT, SINF_DC_MAX_BRIGHT,
+                SINF_DC_MIN_BRIGHT, SINF_DC_CUR_BRIGHT, SINF_MUTE,
+                SINF_RESERVED,      SINF_ENV_STATE,
+                SINF_STICKY_KEY = 0x80,
+};
+
+
+static int __devinit acpi_pcc_hotkey_add (struct acpi_device *device);
+static int __devexit acpi_pcc_hotkey_remove (struct acpi_device *device,
+                                               int type);
+static int acpi_pcc_hotkey_resume(struct acpi_device *device, int state);
+
+static struct acpi_driver acpi_pcc_driver = {
+       .name =         "Panasonic PCC extra driver",
+       .class =        "pcc",
+       .ids =          "MAT0012,MAT0013,MAT0018,MAT0019",
+       .ops =          {
+                               .add =    acpi_pcc_hotkey_add,
+                               .remove = __devexit_p(acpi_pcc_hotkey_remove),
+                               .resume = acpi_pcc_hotkey_resume,
+                       },
+};
+
+struct acpi_hotkey {
+       acpi_handle             handle;
+       struct acpi_device      *device;
+       struct proc_dir_entry   *proc_dir_entry;
+       unsigned long           num_sifr;
+       unsigned long           status;
+       struct input_dev        *input_dev;
+       int                     sticky_mode;
+};
+
+struct pcc_keyinput {
+       struct acpi_hotkey *hotkey;
+       int key_mode;
+};
+
+/* method access functions */
+static int acpi_pcc_write_sset(struct acpi_hotkey *hotkey, int func, int val)
+{
+       acpi_status status;
+       union acpi_object in_objs[] = {
+               { .integer.type  = ACPI_TYPE_INTEGER,
+                 .integer.value = func, },
+               { .integer.type  = ACPI_TYPE_INTEGER,
+                 .integer.value = val, },
+       };
+       struct acpi_object_list params = {
+               .count   = ARRAY_SIZE(in_objs),
+               .pointer = in_objs,
+       };
+
+       status = acpi_evaluate_object(hotkey->handle, "SSET", &params, NULL);
+
+       if (status != AE_OK) {
+               return -1;
+       }
+
+       return 0;
+}
+
+static inline int acpi_pcc_get_sqty(struct acpi_device *device)
+{
+       acpi_status status;
+       unsigned long s;
+
+       status = acpi_evaluate_integer(device->handle, "SQTY", NULL, &s);
+
+       if (ACPI_SUCCESS(status)) {
+               return(s);
+       }
+       else {
+               printk(PCC_ERR "acpi_pcc_get_sqty() evaluation error "
+                              "HKEY.SQTY\n");
+               return(-EINVAL);
+       }
+}
+
+static int acpi_pcc_retrieve_biosdata(struct acpi_hotkey *hotkey, u32* sinf)
+{
+       acpi_status status;
+       struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+       union acpi_object *hkey = NULL;
+       int i;
+
+       status = acpi_evaluate_object(hotkey->handle, "SINF", 0 , &buffer);
+       if (ACPI_FAILURE(status)) {
+               printk(PCC_ERR "acpi_pcc_retrieve_biosdata() evaluation error "
+                              "HKEY.SINF\n");
+               return 0;
+       }
+
+       hkey = buffer.pointer;
+       if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
+               printk(PCC_ERR "acpi_pcc_retrieve_biosdata() invalid "
+                              "HKEY.SINF\n");
+               goto end;
+       }
+
+       if (hotkey->num_sifr < hkey->package.count) {
+               printk(PCC_ERR "acpi_pcc_retrieve_biosdata() SQTY reports bad "
+                              "SINF length\n");
+               status = AE_ERROR;
+               goto end;
+       }
+
+       for (i = 0; i < hkey->package.count; i++) {
+               union acpi_object *element = &(hkey->package.elements[i]);
+               if (likely(element->type == ACPI_TYPE_INTEGER)) {
+                       sinf[i] = element->integer.value;
+               }
+               else {
+                       printk(PCC_ERR "acpi_pcc_retrieve_biosdata() invalid "
+                                      "HKEY.SINF data");
+               }
+       }
+       sinf[hkey->package.count] = -1;
+
+end:
+       acpi_os_free(buffer.pointer);
+
+       if (status != AE_OK) {
+               return 0;
+       }
+
+       return 1;
+}
+
+static int acpi_pcc_read_sinf_field(struct seq_file *seq, int field)
+{
+       struct acpi_hotkey *hotkey = (struct acpi_hotkey *) seq->private;
+       u32* sinf = kmalloc(sizeof(u32) * (hotkey->num_sifr + 1), GFP_KERNEL);
+
+       if (!sinf) {
+               printk(PCC_ERR "acpi_pcc_read_sinf_field() could not allocate "
+                              "%li bytes\n", sizeof(u32) * hotkey->num_sifr);
+               return 0;
+       }
+
+       if (acpi_pcc_retrieve_biosdata(hotkey, sinf)) {
+               seq_printf(seq, "%u\n", sinf[field]);
+       }
+       else {
+               seq_printf(seq, "error");
+               printk(PCC_ERR "acpi_pcc_read_sinf_field() could not retrieve "
+                              "BIOS data\n");
+       }
+
+       kfree(sinf);
+       return 0;
+}
+
+/* user interface functions
+ *   - read methods
+ *   - SINF read methods
+ *
+ */
+
+#define PCC_SINF_READ_F(_name_, FUNC) \
+static int _name_ (struct seq_file *seq, void *offset) \
+{ \
+       return acpi_pcc_read_sinf_field(seq, (FUNC)); \
+}
+
+PCC_SINF_READ_F(acpi_pcc_numbatteries_show,     SINF_NUM_BATTERIES);
+PCC_SINF_READ_F(acpi_pcc_lcdtype_show,          SINF_LCD_TYPE);
+PCC_SINF_READ_F(acpi_pcc_ac_brightness_max_show, SINF_AC_MAX_BRIGHT);
+PCC_SINF_READ_F(acpi_pcc_ac_brightness_min_show, SINF_AC_MIN_BRIGHT);
+PCC_SINF_READ_F(acpi_pcc_ac_brightness_show,    SINF_AC_CUR_BRIGHT);
+PCC_SINF_READ_F(acpi_pcc_dc_brightness_max_show, SINF_DC_MAX_BRIGHT);
+PCC_SINF_READ_F(acpi_pcc_dc_brightness_min_show, SINF_DC_MIN_BRIGHT);
+PCC_SINF_READ_F(acpi_pcc_dc_brightness_show,    SINF_DC_CUR_BRIGHT);
+PCC_SINF_READ_F(acpi_pcc_mute_show,             SINF_MUTE);
+
+static int acpi_pcc_sticky_key_show(struct seq_file *seq, void *offset)
+{
+       struct acpi_hotkey *hotkey = seq->private;
+
+       if (!hotkey || !hotkey->device) {
+               return 0;
+       }
+
+       seq_printf(seq, "%d\n", hotkey->sticky_mode);
+
+       return 0;
+}
+
+static int acpi_pcc_keyinput_show(struct seq_file *seq, void *offset)
+{
+       struct acpi_hotkey *hotkey = (struct acpi_hotkey *) seq->private;
+       struct input_dev *hotk_input_dev = hotkey->input_dev;
+       struct pcc_keyinput *keyinput = hotk_input_dev->private;
+
+       seq_printf(seq, "%d\n", keyinput->key_mode);
+
+       return 0;
+}
+
+static int acpi_pcc_version_show(struct seq_file *seq, void *offset)
+{
+       struct acpi_hotkey *hotkey = (struct acpi_hotkey *) seq->private;
+
+       if (!hotkey || !hotkey->device) {
+               return 0;
+       }
+
+       seq_printf(seq, "%s version %s\n", "Panasonic PCC extra driver",
+                       ACPI_PCC_VERSION);
+       seq_printf(seq, "%li functions\n", hotkey->num_sifr);
+
+       return 0;
+}
+
+/* write methods */
+static ssize_t acpi_pcc_write_single_flag (struct file *file,
+                                           const char __user *buffer,
+                                           size_t count,
+                                           int sinf_func)
+{
+       struct seq_file *seq = file->private_data;
+       struct acpi_hotkey *hotkey = seq->private;
+       char write_string[PROC_STR_MAX_LEN];
+       u32 val;
+
+       if (!hotkey || (count > sizeof(write_string) - 1)) {
+               return -EINVAL;
+        }
+
+       if (copy_from_user(write_string, buffer, count)) {
+               return -EFAULT;
+        }
+       write_string[count] = '\0';
+
+       if (sscanf(write_string, "%i", &val) == 1 && (val == 0 || val == 1)) {
+               acpi_pcc_write_sset(hotkey, sinf_func, val);
+       }
+
+       return count;
+}
+
+static unsigned long acpi_pcc_write_brightness(struct file *file,
+                                              const char __user *buffer,
+                                              size_t count,
+                                              int min_index, int max_index,
+                                              int cur_index)
+{
+       struct seq_file *seq = (struct seq_file *)file->private_data;
+       struct acpi_hotkey *hotkey = (struct acpi_hotkey *)seq->private;
+       char write_string[PROC_STR_MAX_LEN];
+       u32 bright;
+       u32* sinf = kmalloc(sizeof(u32) * (hotkey->num_sifr + 1), GFP_KERNEL);
+
+       if (!hotkey || (count > sizeof(write_string) - 1)) {
+               return -EINVAL;
+       }
+
+       if (!sinf) {
+               printk(PCC_ERR "acpi_pcc_write_brightness() could not "
+                              "allocate %li bytes\n",
+                               sizeof(u32) * hotkey->num_sifr);
+               return -EFAULT;
+       }
+
+       if (copy_from_user(write_string, buffer, count)) {
+               return -EFAULT;
+       }
+
+       write_string[count] = '\0';
+
+       if (!acpi_pcc_retrieve_biosdata(hotkey, sinf)) {
+               printk(PCC_ERR "acpi_pcc_write_brightness() could not "
+                              "retrieve BIOS data\n");
+               goto end;
+       }
+
+       if ((sscanf(write_string, "%i", &bright) == 1) &&
+                       (bright >= sinf[min_index]) &&
+                       (bright <= sinf[max_index])) {
+               acpi_pcc_write_sset(hotkey, cur_index, bright);
+       }
+
+end:
+       kfree(sinf);
+       return count;
+}
+
+static ssize_t acpi_pcc_write_ac_brightness(struct file *file,
+                                           const char __user *buffer,
+                                           size_t count, loff_t *ppos)
+{
+       return acpi_pcc_write_brightness(file, buffer, count,
+                                        SINF_AC_MIN_BRIGHT,
+                                        SINF_AC_MAX_BRIGHT,
+                                        SINF_AC_CUR_BRIGHT);
+}
+
+static ssize_t acpi_pcc_write_dc_brightness(struct file *file,
+                                           const char __user *buffer,
+                                           size_t count, loff_t *ppos)
+{
+       return acpi_pcc_write_brightness(file, buffer, count,
+                                        SINF_DC_MIN_BRIGHT,
+                                        SINF_DC_MAX_BRIGHT,
+                                        SINF_DC_CUR_BRIGHT);
+}
+
+static ssize_t acpi_pcc_write_mute (struct file *file,
+                                   const char __user *buffer,
+                                   size_t count, loff_t *ppos)
+{
+       return acpi_pcc_write_single_flag(file, buffer, count, SINF_MUTE);
+}
+
+static ssize_t acpi_pcc_write_sticky_key (struct file *file,
+                                         const char __user *buffer,
+                                         size_t count, loff_t *ppos)
+{
+       return acpi_pcc_write_single_flag(file, buffer, count,
+                                         SINF_STICKY_KEY);
+}
+
+static ssize_t acpi_pcc_write_keyinput(struct file *file,
+                                      const char __user *buffer,
+                                      size_t count, loff_t *ppos)
+{
+       struct seq_file *seq = (struct seq_file *)file->private_data;
+       struct acpi_hotkey *hotkey = (struct acpi_hotkey *)seq->private;
+       struct pcc_keyinput *keyinput;
+       char write_string[PROC_STR_MAX_LEN];
+       int key_mode;
+
+       if (!hotkey || (count > sizeof(write_string) - 1)) {
+               return -EINVAL;
+       }
+
+       if (copy_from_user(write_string, buffer, count)) {
+               return -EFAULT;
+       }
+
+       write_string[count] = '\0';
+
+       if ((sscanf(write_string, "%i", &key_mode) == 1) &&
+           (key_mode == 0 || key_mode == 1)) {
+               keyinput = (struct pcc_keyinput *) hotkey->input_dev->private;
+               keyinput->key_mode = key_mode;
+       }
+
+       return count;
+}
+
+/* hotkey driver */
+static void acpi_pcc_generete_keyinput(struct acpi_hotkey *hotkey)
+{
+       struct input_dev *hotk_input_dev = hotkey->input_dev;
+       struct pcc_keyinput *keyinput = hotk_input_dev->private;
+       int hinf = hotkey->status;
+       int key_code, hkey_num;
+       const int key_map[] = {
+               /*  0 */ -1,
+               /*  1 */ KEY_BRIGHTNESSDOWN,
+               /*  2 */ KEY_BRIGHTNESSUP,
+               /*  3 */ -1, /* vga/lcd switch event does not occur on
+                             * hotkey driver.
+                             */
+               /*  4 */ KEY_MUTE,
+               /*  5 */ KEY_VOLUMEDOWN,
+               /*  6 */ KEY_VOLUMEUP,
+               /*  7 */ KEY_SLEEP,
+               /*  8 */ -1, /* Change CPU boost: do nothing */
+               /*  9 */ KEY_BATT,
+               /* 10 */ KEY_SUSPEND,
+       };
+
+       if (keyinput->key_mode == 0) {
+               return;
+       }
+
+       hkey_num = hinf & 0xf;
+
+       if ((0 > hkey_num) || (hkey_num > ARRAY_SIZE(key_map))) {
+               printk(PCC_ERR "acpi_pcc_generete_keyinput() hotkey "
+                              "number (%d) out of range\n", hkey_num);
+               return;
+       }
+
+       key_code = key_map[hkey_num];
+
+       if (key_code != -1) {
+               int pushed = (hinf & 0x80) ? TRUE : FALSE;
+
+               input_report_key(hotk_input_dev, key_code, pushed);
+               input_sync(hotk_input_dev);
+       }
+
+       return;
+}
+
+static int acpi_pcc_hotkey_get_key(struct acpi_hotkey *hotkey)
+{
+       acpi_status status;
+       unsigned long result;
+
+       status = acpi_evaluate_integer(hotkey->handle, "HINF", NULL, &result);
+       if (likely(ACPI_SUCCESS(status))) {
+               hotkey->status = result;
+       }
+       else {
+               printk(PCC_ERR "acpi_pcc_hotkey_get_key() error getting "
+                              "hotkey status\n");
+       }
+
+       if (status != AE_OK) {
+               return -1;
+       }
+
+       return 0;
+}
+
+void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data)
+{
+       struct acpi_hotkey *hotkey = (struct acpi_hotkey *) data;
+
+       switch(event) {
+       case HKEY_NOTIFY:
+               if (acpi_pcc_hotkey_get_key(hotkey)) {
+                       /* generate event
+                        *   e.g. '"pcc HKEY 00000080 00000084"' when Fn+F4 is
+                        *   pressed
+                        *
+                        */
+
+                       acpi_bus_generate_event(hotkey->device, event,
+                                       hotkey->status);
+               }
+
+               acpi_pcc_generete_keyinput(hotkey);
+               break;
+
+       default:
+               /* nothing to do */
+               break;
+
+       }
+
+       return;
+}
+
+/* proc interface  */
+#define SEQ_OPEN_FS(_open_func_name_, _show_func_name_) \
+    static int _open_func_name_(struct inode *inode, struct file *file) \
+{ \
+            return single_open(file, _show_func_name_, PDE(inode)->data); \
+}
+
+SEQ_OPEN_FS(acpi_pcc_dc_brightness_open_fs,
+           acpi_pcc_dc_brightness_show);
+SEQ_OPEN_FS(acpi_pcc_numbatteries_open_fs,
+           acpi_pcc_numbatteries_show);
+SEQ_OPEN_FS(acpi_pcc_lcdtype_open_fs,
+           acpi_pcc_lcdtype_show);
+SEQ_OPEN_FS(acpi_pcc_ac_brightness_max_open_fs,
+           acpi_pcc_ac_brightness_max_show);
+SEQ_OPEN_FS(acpi_pcc_ac_brightness_min_open_fs,
+           acpi_pcc_ac_brightness_min_show);
+SEQ_OPEN_FS(acpi_pcc_ac_brightness_open_fs,
+           acpi_pcc_ac_brightness_show);
+SEQ_OPEN_FS(acpi_pcc_dc_brightness_max_open_fs,
+           acpi_pcc_dc_brightness_max_show);
+SEQ_OPEN_FS(acpi_pcc_dc_brightness_min_open_fs,
+           acpi_pcc_dc_brightness_min_show);
+SEQ_OPEN_FS(acpi_pcc_mute_open_fs,
+           acpi_pcc_mute_show);
+SEQ_OPEN_FS(acpi_pcc_version_open_fs,
+           acpi_pcc_version_show);
+SEQ_OPEN_FS(acpi_pcc_keyinput_open_fs,
+           acpi_pcc_keyinput_show);
+SEQ_OPEN_FS(acpi_pcc_sticky_key_open_fs,
+            acpi_pcc_sticky_key_show);
+
+#define SEQ_FILEOPS_R(_open_func_name_) \
+{ \
+            .open    = _open_func_name_, \
+            .read    = seq_read,         \
+            .llseek  = seq_lseek,        \
+            .release = single_release,   \
+}
+
+#define SEQ_FILEOPS_RW(_open_func_name_, _write_func_name_) \
+{ \
+            .open    = _open_func_name_ , \
+            .read    = seq_read,          \
+            .write   = _write_func_name_, \
+            .llseek  = seq_lseek,         \
+            .release = single_release,    \
+}
+
+typedef struct file_operations fops_t;
+
+/* number of batteries */
+static fops_t acpi_pcc_numbatteries_fops = \
+       SEQ_FILEOPS_R (acpi_pcc_numbatteries_open_fs);
+
+/* type of lcd */
+static fops_t acpi_pcc_lcdtype_fops = \
+       SEQ_FILEOPS_R (acpi_pcc_lcdtype_open_fs);
+
+/* mute */
+static fops_t acpi_pcc_mute_fops = \
+       SEQ_FILEOPS_RW(acpi_pcc_mute_open_fs,
+                      acpi_pcc_write_mute);
+
+/* brightness */
+static fops_t acpi_pcc_ac_brightness_fops = \
+       SEQ_FILEOPS_RW(acpi_pcc_ac_brightness_open_fs,
+                      acpi_pcc_write_ac_brightness);
+static fops_t acpi_pcc_ac_brightness_max_fops = \
+       SEQ_FILEOPS_R(acpi_pcc_ac_brightness_max_open_fs);
+static fops_t acpi_pcc_ac_brightness_min_fops = \
+       SEQ_FILEOPS_R(acpi_pcc_ac_brightness_min_open_fs);
+static fops_t acpi_pcc_dc_brightness_fops = \
+       SEQ_FILEOPS_RW(acpi_pcc_dc_brightness_open_fs,
+                      acpi_pcc_write_dc_brightness);
+static fops_t acpi_pcc_dc_brightness_max_fops = \
+       SEQ_FILEOPS_R(acpi_pcc_dc_brightness_max_open_fs);
+static fops_t acpi_pcc_dc_brightness_min_fops = \
+       SEQ_FILEOPS_R(acpi_pcc_dc_brightness_min_open_fs);
+
+/* sticky key */
+static fops_t acpi_pcc_sticky_key_fops = \
+       SEQ_FILEOPS_RW(acpi_pcc_sticky_key_open_fs,
+                      acpi_pcc_write_sticky_key);
+
+/* keyinput */
+static fops_t acpi_pcc_keyinput_fops = \
+       SEQ_FILEOPS_RW(acpi_pcc_keyinput_open_fs,
+                      acpi_pcc_write_keyinput);
+
+/* version */
+static fops_t acpi_pcc_version_fops = \
+       SEQ_FILEOPS_R (acpi_pcc_version_open_fs);
+
+typedef struct _ProcItem
+{
+       const char* name;
+       struct file_operations *fops;
+       mode_t flag;
+} ProcItem;
+
+/* Note: These functions map *exactly* to the SINF/SSET functions */
+ProcItem pcc_proc_items_sifr[] =
+{
+       { "num_batteries",      &acpi_pcc_numbatteries_fops,     S_IRUGO },
+       { "lcd_type",           &acpi_pcc_lcdtype_fops,          S_IRUGO },
+       { "ac_brightness_max" , &acpi_pcc_ac_brightness_max_fops,S_IRUGO },
+       { "ac_brightness_min" , &acpi_pcc_ac_brightness_min_fops,S_IRUGO },
+       { "ac_brightness" ,     &acpi_pcc_ac_brightness_fops,    S_IFREG |
+                                                                S_IRUGO |
+                                                                S_IWUSR },
+       { "dc_brightness_max" , &acpi_pcc_dc_brightness_max_fops,S_IRUGO },
+       { "dc_brightness_min" , &acpi_pcc_dc_brightness_min_fops,S_IRUGO },
+       { "dc_brightness" ,     &acpi_pcc_dc_brightness_fops,    S_IFREG |
+                                                                S_IRUGO |
+                                                                S_IWUSR },
+       { "mute",               &acpi_pcc_mute_fops,             S_IFREG |
+                                                                S_IRUGO |
+                                                                S_IWUSR },
+       { NULL, NULL, 0 },
+};
+
+ProcItem pcc_proc_items[] =
+{
+       { "sticky_key",         &acpi_pcc_sticky_key_fops,       S_IFREG |
+                                                                S_IRUGO |
+                                                                S_IWUSR },
+       { "keyinput",           &acpi_pcc_keyinput_fops,         S_IFREG |
+                                                                S_IRUGO |
+                                                                S_IWUSR },
+       { "version",            &acpi_pcc_version_fops,          S_IRUGO },
+       { NULL, NULL, 0 },
+};
+
+static int __init acpi_pcc_add_device(struct acpi_device *device,
+                                      ProcItem *proc_items,
+                                      int num)
+{
+       struct acpi_hotkey *hotkey = \
+               (struct acpi_hotkey*)acpi_driver_data(device);
+       struct proc_dir_entry* proc;
+       ProcItem* item;
+       int i;
+
+
+       for (item = proc_items, i = 0; item->name && i < num; ++item, ++i) {
+               proc = create_proc_entry(item->name, item->flag,
+                                        hotkey->proc_dir_entry);
+               if (likely(proc)) {
+                       proc->proc_fops = item->fops;
+                       proc->data = hotkey;
+                       proc->owner = THIS_MODULE;
+               }
+               else {
+                       while (i-- > 0) {
+                               item--;
+                               remove_proc_entry(item->name,
+                                                 hotkey->proc_dir_entry);
+                       }
+                       return -ENODEV;
+               }
+       }
+
+       return 0;
+}
+
+static int __init acpi_pcc_proc_init(struct acpi_device *device)
+{
+       acpi_status status;
+       struct acpi_hotkey *hotkey = \
+               (struct acpi_hotkey*)acpi_driver_data(device);
+       struct proc_dir_entry* acpi_pcc_dir;
+
+       acpi_pcc_dir = proc_mkdir(PROC_PCC, acpi_root_dir);
+
+       if (unlikely(!acpi_pcc_dir)) {
+               printk(PCC_ERR "acpi_pcc_proc_init() could not create proc "
+                              "entry\n");
+               return -ENODEV;
+       }
+
+       acpi_pcc_dir->owner = THIS_MODULE;
+       hotkey->proc_dir_entry = acpi_pcc_dir;
+
+       status = acpi_pcc_add_device(device, pcc_proc_items_sifr,
+                                    hotkey->num_sifr);
+       status |= acpi_pcc_add_device(device, pcc_proc_items,
+                                     sizeof(pcc_proc_items)/sizeof(ProcItem));
+
+       if (unlikely(status)) {
+               remove_proc_entry(PROC_PCC, acpi_root_dir);
+               hotkey->proc_dir_entry = NULL;
+               return -ENODEV;
+       }
+
+       return status;
+}
+
+static void __exit acpi_pcc_remove_device(struct acpi_device *device,
+                                          ProcItem *proc_items,
+                                          int num)
+{
+       struct acpi_hotkey *hotkey =
+               (struct acpi_hotkey*)acpi_driver_data(device);
+       ProcItem* item;
+       int i;
+
+       for (item = proc_items, i = 0; item->name != NULL &&
+                                       i < num; ++item, ++i) {
+               remove_proc_entry(item->name, hotkey->proc_dir_entry);
+       }
+
+       return;
+}
+
+/* input init */
+static int hotk_input_open(struct input_dev *dev)
+{
+       return 0;
+}
+
+static void hotk_input_close(struct input_dev *dev)
+{
+       return;
+}
+
+static int acpi_pcc_init_input(struct acpi_hotkey *hotkey)
+{
+       struct input_dev *hotk_input_dev;
+       struct pcc_keyinput *pcc_keyinput;
+
+       hotk_input_dev = input_allocate_device();
+
+       if (!hotk_input_dev) {
+               printk(PCC_ERR "acpi_pcc_init_input() could not allocate "
+                              "memory\n");
+               return -ENOMEM;
+       }
+
+       pcc_keyinput = kmalloc(sizeof(struct pcc_keyinput),GFP_KERNEL);
+
+       if (!pcc_keyinput) {
+               printk(PCC_ERR "acpi_pcc_init_input() could not allocate "
+                              "memory\n");
+               return -ENOMEM;
+       }
+
+       hotk_input_dev->open = hotk_input_open;
+       hotk_input_dev->close = hotk_input_close;
+
+       hotk_input_dev->evbit[0] = BIT(EV_KEY);
+
+       set_bit(KEY_BRIGHTNESSDOWN, hotk_input_dev->keybit);
+       set_bit(KEY_BRIGHTNESSUP, hotk_input_dev->keybit);
+       set_bit(KEY_MUTE, hotk_input_dev->keybit);
+       set_bit(KEY_VOLUMEDOWN, hotk_input_dev->keybit);
+       set_bit(KEY_VOLUMEUP, hotk_input_dev->keybit);
+       set_bit(KEY_SLEEP, hotk_input_dev->keybit);
+       set_bit(KEY_BATT, hotk_input_dev->keybit);
+       set_bit(KEY_SUSPEND, hotk_input_dev->keybit);
+
+       hotk_input_dev->name = "Panasonic PCC extra driver";
+       hotk_input_dev->phys = "panasonic/hkey0";
+       hotk_input_dev->id.bustype = 0x1a; /* XXX FIXME: BUS_I8042? */
+       hotk_input_dev->id.vendor = 0x0001;
+       hotk_input_dev->id.product = 0x0001;
+       hotk_input_dev->id.version = 0x0100;
+
+       pcc_keyinput->key_mode = 1; /* default on */
+       pcc_keyinput->hotkey = hotkey;
+
+       hotk_input_dev->private = pcc_keyinput;
+
+       hotkey->input_dev = hotk_input_dev;
+
+       input_register_device(hotk_input_dev);
+
+       return 0;
+}
+
+/* module init */
+static int acpi_pcc_hotkey_add (struct acpi_device *device)
+{
+       acpi_status status;
+       struct acpi_hotkey *hotkey = NULL;
+       int num_sifr, result;
+
+       if (!device) {
+               return -EINVAL;
+       }
+
+       num_sifr = acpi_pcc_get_sqty(device);
+
+       if (num_sifr > 255) {
+               printk(PCC_ERR "acpi_pcc_hotkey_add() num_sifr too large "
+                              "(%i)\n", num_sifr);
+               return -ENODEV;
+       }
+
+       hotkey = kmalloc(sizeof(struct acpi_hotkey), GFP_KERNEL);
+
+       if (!hotkey) {
+               printk(PCC_ERR "acpi_pcc_hotkey_add() could not allocate "
+                              "memory\n");
+               return -ENOMEM;
+       }
+
+       memset(hotkey, 0, sizeof(struct acpi_hotkey));
+
+       hotkey->device = device;
+       hotkey->handle = device->handle;
+       hotkey->num_sifr = num_sifr;
+       acpi_driver_data(device) = hotkey;
+       strcpy(acpi_device_name(device), "Panasonic PCC");
+       strcpy(acpi_device_class(device), "pcc");
+
+       status = acpi_install_notify_handler (
+                       hotkey->handle,
+                       ACPI_DEVICE_NOTIFY,
+                       acpi_pcc_hotkey_notify,
+                       hotkey);
+
+       if (ACPI_FAILURE(status)) {
+               printk(PCC_ERR "acpi_pcc_hotkey_add() error installing notify "
+                              "handler\n");
+               kfree(hotkey);
+               return -ENODEV;
+       }
+
+       result = acpi_pcc_init_input(hotkey);
+
+       if (result) {
+               printk(PCC_ERR "acpi_pcc_hotkey_add() error installing input "
+                              "handler\n");
+               kfree(hotkey);
+               return result;
+       }
+
+       return acpi_pcc_proc_init(device);
+}
+
+static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type)
+{
+       acpi_status status;
+       struct acpi_hotkey *hotkey = acpi_driver_data(device);
+
+       if (!device || !hotkey) {
+               return -EINVAL;
+       }
+
+       if (hotkey->proc_dir_entry) {
+               acpi_pcc_remove_device(device, pcc_proc_items_sifr,
+                                      hotkey->num_sifr);
+               acpi_pcc_remove_device(device, pcc_proc_items,
+                                      sizeof(pcc_proc_items)/sizeof(ProcItem));
+               remove_proc_entry(PROC_PCC, acpi_root_dir);
+       }
+
+       status = acpi_remove_notify_handler(hotkey->handle,
+                   ACPI_DEVICE_NOTIFY, acpi_pcc_hotkey_notify);
+
+       if (ACPI_FAILURE(status)) {
+               printk(PCC_ERR "acpi_pcc_hotkey_remove() error removing "
+                              "notify handler\n");
+       }
+
+       input_unregister_device(hotkey->input_dev);
+
+       kfree(hotkey);
+
+       if(status != AE_OK) {
+               return -1;
+       }
+
+       return 0;
+}
+
+static int acpi_pcc_hotkey_resume(struct acpi_device *device, int state)
+{
+       acpi_status status;
+       struct acpi_hotkey *hotkey = acpi_driver_data(device);
+
+       if (device == NULL || hotkey == NULL) {
+               return -EINVAL;
+       }
+
+       printk(PCC_INFO "acpi_pcc_hotkey_resume() sticky mode restore: %d\n",
+                       hotkey->sticky_mode);
+
+       status = acpi_pcc_write_sset(hotkey, SINF_STICKY_KEY,
+                                    hotkey->sticky_mode);
+
+       if (status != AE_OK) {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int __init acpi_pcc_init(void)
+{
+       int result = 0;
+
+       if (acpi_disabled) {
+               return -ENODEV;
+       }
+
+       result = acpi_bus_register_driver(&acpi_pcc_driver);
+       if (result < 0) {
+               printk(PCC_ERR "error registering hotkey driver\n");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static void __exit acpi_pcc_exit(void)
+{
+       acpi_bus_unregister_driver(&acpi_pcc_driver);
+
+       return;
+}
+
+module_init(acpi_pcc_init);
+module_exit(acpi_pcc_exit);
diff --git a/drivers/acpi/sony_acpi.c b/drivers/acpi/sony_acpi.c
new file mode 100644 (file)
index 0000000..7636bba
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * ACPI Sony Notebook Control Driver (SNC)
+ *
+ * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
+ *
+ * Parts of this driver inspired from asus_acpi.c and ibm_acpi.c
+ * which are copyrighted by their respective authors.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+#include <asm/uaccess.h>
+
+#define ACPI_SNC_CLASS         "sony"
+#define ACPI_SNC_HID           "SNY5001"
+#define ACPI_SNC_DRIVER_NAME   "ACPI Sony Notebook Control Driver v0.2"
+
+#define LOG_PFX                        KERN_WARNING "sony_acpi: "
+
+MODULE_AUTHOR("Stelian Pop");
+MODULE_DESCRIPTION(ACPI_SNC_DRIVER_NAME);
+MODULE_LICENSE("GPL");
+
+static int debug;
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug, "set this to 1 (and RTFM) if you want to help "
+                       "the development of this driver");
+
+static int sony_acpi_add (struct acpi_device *device);
+static int sony_acpi_remove (struct acpi_device *device, int type);
+
+static struct acpi_driver sony_acpi_driver = {
+       .name   = ACPI_SNC_DRIVER_NAME,
+       .class  = ACPI_SNC_CLASS,
+       .ids    = ACPI_SNC_HID,
+       .ops    = {
+                       .add    = sony_acpi_add,
+                       .remove = sony_acpi_remove,
+                 },
+};
+
+static acpi_handle sony_acpi_handle;
+static struct proc_dir_entry *sony_acpi_dir;
+
+static struct sony_acpi_value {
+       char                    *name;   /* name of the entry */
+       struct proc_dir_entry   *proc;   /* /proc entry */
+       char                    *acpiget;/* name of the ACPI get function */
+       char                    *acpiset;/* name of the ACPI get function */
+       int                     min;     /* minimum allowed value or -1 */
+       int                     max;     /* maximum allowed value or -1 */
+       int                     debug;   /* active only in debug mode ? */
+} sony_acpi_values[] = {
+       {
+               .name           = "brightness",
+               .acpiget        = "GBRT",
+               .acpiset        = "SBRT",
+               .min            = 1,
+               .max            = 8,
+               .debug          = 0,
+       },
+       {
+               .name           = "brightness_default",
+               .acpiget        = "GPBR",
+               .acpiset        = "SPBR",
+               .min            = 1,
+               .max            = 8,
+               .debug          = 0,
+       },
+       {
+               .name           = "cdpower",
+               .acpiget        = "GCDP",
+               .acpiset        = "SCDP",
+               .min            = -1,
+               .max            = -1,
+               .debug          = 0,
+       },
+       {
+               .name           = "PID",
+               .acpiget        = "GPID",
+               .debug          = 1,
+       },
+       {
+               .name           = "CTR",
+               .acpiget        = "GCTR",
+               .acpiset        = "SCTR",
+               .min            = -1,
+               .max            = -1,
+               .debug          = 1,
+       },
+       {
+               .name           = "PCR",
+               .acpiget        = "GPCR",
+               .acpiset        = "SPCR",
+               .min            = -1,
+               .max            = -1,
+               .debug          = 1,
+       },
+       {
+               .name           = "CMI",
+               .acpiget        = "GCMI",
+               .acpiset        = "SCMI",
+               .min            = -1,
+               .max            = -1,
+               .debug          = 1,
+       },
+       {
+               .name           = NULL,
+       }
+};
+
+static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
+{
+       struct acpi_buffer output;
+       union acpi_object out_obj;
+       acpi_status status;
+
+       output.length = sizeof(out_obj);
+       output.pointer = &out_obj;
+
+       status = acpi_evaluate_object(handle, name, NULL, &output);
+       if ((status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER)) {
+               *result = out_obj.integer.value;
+               return 0;
+       }
+
+       printk(LOG_PFX "acpi_callreadfunc failed\n");
+
+       return -1;
+}
+
+static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
+                           int *result)
+{
+       struct acpi_object_list params;
+       union acpi_object in_obj;
+       struct acpi_buffer output;
+       union acpi_object out_obj;
+       acpi_status status;
+
+       params.count = 1;
+       params.pointer = &in_obj;
+       in_obj.type = ACPI_TYPE_INTEGER;
+       in_obj.integer.value = value;
+
+       output.length = sizeof(out_obj);
+       output.pointer = &out_obj;
+
+       status = acpi_evaluate_object(handle, name, &params, &output);
+       if (status == AE_OK) {
+               if (result != NULL) {
+                       if (out_obj.type != ACPI_TYPE_INTEGER) {
+                               printk(LOG_PFX "acpi_evaluate_object bad "
+                                      "return type\n");
+                               return -1;
+                       }
+                       *result = out_obj.integer.value;
+               }
+               return 0;
+       }
+
+       printk(LOG_PFX "acpi_evaluate_object failed\n");
+
+       return -1;
+}
+
+static int parse_buffer(const char __user *buffer, unsigned long count,
+                       int *val) {
+       char s[32];
+       int ret;
+
+       if (count > 31)
+               return -EINVAL;
+       if (copy_from_user(s, buffer, count))
+               return -EFAULT;
+       s[count] = '\0';
+       ret = simple_strtoul(s, NULL, 10);
+       *val = ret;
+       return 0;
+}
+
+static int sony_acpi_read(char* page, char** start, off_t off, int count,
+                         int* eof, void *data)
+{
+       struct sony_acpi_value *item = data;
+       int value;
+
+       if (!item->acpiget)
+               return -EIO;
+
+       if (acpi_callgetfunc(sony_acpi_handle, item->acpiget, &value) < 0)
+               return -EIO;
+
+       return sprintf(page, "%d\n", value);
+}
+
+static int sony_acpi_write(struct file *file, const char __user *buffer,
+                          unsigned long count, void *data)
+{
+       struct sony_acpi_value *item = data;
+       int result;
+       int value;
+
+       if (!item->acpiset)
+               return -EIO;
+
+       if ((result = parse_buffer(buffer, count, &value)) < 0)
+               return result;
+
+       if (item->min != -1 && value < item->min)
+               return -EINVAL;
+       if (item->max != -1 && value > item->max)
+               return -EINVAL;
+
+       if (acpi_callsetfunc(sony_acpi_handle, item->acpiset, value, NULL) < 0)
+               return -EIO;
+
+       return count;
+}
+
+static void sony_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+       printk(LOG_PFX "sony_acpi_notify\n");
+}
+
+static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
+                                     void *context, void **return_value)
+{
+       struct acpi_namespace_node *node;
+       union acpi_operand_object *operand;
+
+       node = (struct acpi_namespace_node *) handle;
+       operand = (union acpi_operand_object *) node->object;
+
+       printk(LOG_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
+              (u32) operand->method.param_count);
+
+       return AE_OK;
+}
+
+static int __init sony_acpi_add(struct acpi_device *device)
+{
+       acpi_status status;
+       int result;
+       struct sony_acpi_value *item;
+
+       sony_acpi_handle = device->handle;
+
+       acpi_driver_data(device) = NULL;
+       acpi_device_dir(device) = sony_acpi_dir;
+
+       if (debug) {
+               status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_acpi_handle,
+                                            1, sony_walk_callback, NULL, NULL);
+               if (ACPI_FAILURE(status)) {
+                       printk(LOG_PFX "unable to walk acpi resources\n");
+                       result = -ENODEV;
+                       goto outwalk;
+               }
+
+               status = acpi_install_notify_handler(sony_acpi_handle,
+                                                    ACPI_DEVICE_NOTIFY,
+                                                    sony_acpi_notify,
+                                                    NULL);
+               if (ACPI_FAILURE(status)) {
+                       printk(LOG_PFX "unable to install notify handler\n");
+                       result = -ENODEV;
+                       goto outnotify;
+               }
+       }
+
+       for (item = sony_acpi_values; item->name; ++item) {
+               acpi_handle handle;
+
+               if (!debug && item->debug)
+                       continue;
+
+               if (item->acpiget &&
+                   ACPI_FAILURE(acpi_get_handle(sony_acpi_handle,
+                                item->acpiget, &handle)))
+                       continue;
+
+               if (item->acpiset &&
+                   ACPI_FAILURE(acpi_get_handle(sony_acpi_handle,
+                                item->acpiset, &handle)))
+                       continue;
+
+               item->proc = create_proc_entry(item->name, 0600,
+                                              acpi_device_dir(device));
+               if (!item->proc) {
+                       printk(LOG_PFX "unable to create proc entry\n");
+                       result = -EIO;
+                       goto outproc;
+               }
+
+               item->proc->read_proc = sony_acpi_read;
+               item->proc->write_proc = sony_acpi_write;
+               item->proc->data = item;
+               item->proc->owner = THIS_MODULE;
+       }
+
+       printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully installed\n");
+
+       return 0;
+
+outproc:
+       if (debug) {
+               status = acpi_remove_notify_handler(sony_acpi_handle,
+                                                   ACPI_DEVICE_NOTIFY,
+                                                   sony_acpi_notify);
+               if (ACPI_FAILURE(status))
+                       printk(LOG_PFX "unable to remove notify handler\n");
+       }
+outnotify:
+       for (item = sony_acpi_values; item->name; ++item)
+               if (item->proc)
+                       remove_proc_entry(item->name, acpi_device_dir(device));
+outwalk:
+       return result;
+}
+
+
+static int __exit sony_acpi_remove(struct acpi_device *device, int type)
+{
+       acpi_status status;
+       struct sony_acpi_value *item;
+
+       if (debug) {
+               status = acpi_remove_notify_handler(sony_acpi_handle,
+                                                   ACPI_DEVICE_NOTIFY,
+                                                   sony_acpi_notify);
+               if (ACPI_FAILURE(status))
+                       printk(LOG_PFX "unable to remove notify handler\n");
+       }
+
+       for (item = sony_acpi_values; item->name; ++item)
+               if (item->proc)
+                       remove_proc_entry(item->name, acpi_device_dir(device));
+
+       printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully removed\n");
+
+       return 0;
+}
+
+static int __init sony_acpi_init(void)
+{
+       int result;
+
+       sony_acpi_dir = proc_mkdir("sony", acpi_root_dir);
+       if (!sony_acpi_dir) {
+               printk(LOG_PFX "unable to create /proc entry\n");
+               return -ENODEV;
+       }
+       sony_acpi_dir->owner = THIS_MODULE;
+
+       result = acpi_bus_register_driver(&sony_acpi_driver);
+       if (result < 0) {
+               remove_proc_entry("sony", acpi_root_dir);
+               return -ENODEV;
+       }
+       return 0;
+}
+
+
+static void __exit sony_acpi_exit(void)
+{
+       acpi_bus_unregister_driver(&sony_acpi_driver);
+       remove_proc_entry("sony", acpi_root_dir);
+}
+
+module_init(sony_acpi_init);
+module_exit(sony_acpi_exit);