bk://kernel.bkbits.net/gregkh/linux/i2c-2.6
mgreer@mvista.com[gregkh]|ChangeSet|20050317065837|58583 mgreer

# This is a BitKeeper generated diff -Nru style patch.
#
# ChangeSet
#   2005/03/16 22:58:37-08:00 mgreer@mvista.com 
#   [PATCH] I2C: Fix breakage in m41t00 i2c rtc driver
#   
#   Remove setting of deleted i2c_client structure member.
#   
#   The latest include/linux/i2c.h:i2c_client structure no longer has an
#   'id' member.  This patch removes the setting of that no longer existing
#   member.
#   
#   Signed-off-by: Mark A. Greer <mgreer@mvista.com>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/m41t00.c
#   2005/03/08 16:18:24-08:00 mgreer@mvista.com +0 -1
#   I2C: Fix breakage in m41t00 i2c rtc driver
# 
# ChangeSet
#   2005/03/16 22:58:09-08:00 frank.beesley@aeroflex.com 
#   [PATCH] I2C: Clean up of i2c-elektor.c build
#   
#   This patch changes the flags variable type from long to unsigned long in
#   one function. This removes a couple of warnings from the compile
#   messages for elektor i2c bus driver.
#   
#   Signed-off-by: Frank Beesley <frank.beesley@aeroflex.com>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/busses/i2c-elektor.c
#   2005/03/11 12:03:44-08:00 frank.beesley@aeroflex.com +1 -1
#   I2C: Clean up of i2c-elektor.c build
# 
# ChangeSet
#   2005/03/16 22:54:03-08:00 jchapman@katalix.com 
#   [PATCH] i2c: add adt7461 chip support to lm90 driver
#   
#   i2c: add adt7461 chip support
#   
#   The Analog Devices ADT7461 temperature sensor chip is compatible with
#   the lm90 device provided its extended temperature range is not
#   enabled.  The chip will be ignored if the boot firmware enables
#   extended temperature range.
#   
#   Also, since the adt7461 treats temp values <0 as 0 and >127 as 127,
#   the driver prevents temperature values outside the supported range
#   from being set.
#   
#   Signed-off-by: James Chapman <jchapman@katalix.com>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/lm90.c
#   2005/03/16 22:51:41-08:00 jchapman@katalix.com +41 -3
#   i2c: add adt7461 chip support to lm90 driver
# 
# ChangeSet
#   2005/03/16 22:49:47-08:00 jchapman@katalix.com 
#   [PATCH] i2c: new driver for ds1337 RTC
#   
#   Signed-off-by: James Chapman <jchapman@katalix.com>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/ds1337.c
#   2005/03/07 02:33:52-08:00 jchapman@katalix.com +402 -0
#   i2c: new driver for ds1337 RTC
# 
# drivers/i2c/chips/Makefile
#   2005/03/04 08:59:53-08:00 jchapman@katalix.com +1 -0
#   i2c: new driver for ds1337 RTC
# 
# drivers/i2c/chips/Kconfig
#   2005/03/04 08:59:53-08:00 jchapman@katalix.com +11 -0
#   i2c: new driver for ds1337 RTC
# 
# drivers/i2c/chips/ds1337.c
#   2005/03/07 02:33:52-08:00 jchapman@katalix.com +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/i2c/chips/ds1337.c
# 
# ChangeSet
#   2005/03/16 22:39:26-08:00 grant_nospam@dodo.com.au 
#   [PATCH] I2C: group Intel on I2C Hardware Bus support
#   
#    From an end-user perspective it is easy to miss the third Intel PIIX
#   entry on the menuconfig "I2C Hardware Bus support" screen.
#   Also the Intel 801 menu item does not mention ICH.
#   
#   This trivial patch groups three Intel entries together, adds ICH to
#   menu item, and ICH5/ICH5R to the help section.  Includes suggestions
#   from Jean Delvare.
#   
#   Signed-off-by: Grant Coady <gcoady@gmail.com>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/busses/Kconfig
#   2005/03/16 14:45:14-08:00 grant_nospam@dodo.com.au +19 -19
#   I2C: group Intel on I2C Hardware Bus support
# 
# ChangeSet
#   2005/03/16 22:39:00-08:00 khali@linux-fr.org 
#   [PATCH] I2C: Skip broken detection step in it87
#   
#   One of the detection steps in the it87 chip driver was reported to be
#   broken for some revisions of the IT8712F chip [1] [2]. This detection
#   step is a legacy from the lm78 driver and the documentation available
#   for the IT8705F and IT8712F chips does not mention it at all. For this
#   reason, I propose to skip this detection step for Super-I/O chips.
#   Super-I/O chips have already been identified when we reach this step, so
#   it is redundant (additionally do being broken). This closes bug #4335.
#   
#   [1] http://bugzilla.kernel.org/show_bug.cgi?id=4335
#   [2] http://archives.andrew.net.au/lm-sensors/msg29962.html
#   
#   Signed-off-by: Jean Delvare <khali@linux-fr.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/it87.c
#   2005/03/15 11:51:14-08:00 khali@linux-fr.org +2 -3
#   I2C: Skip broken detection step in it87
# 
# ChangeSet
#   2005/03/16 22:38:32-08:00 khali@linux-fr.org 
#   [PATCH] I2C: Make master_xfer debug messages more useful
#   
#   While working on the recent saa7110 mess, I found that the debug message
#   displayed when calling master_xfer wasn't as useful as it could be. Here
#   is a patch improving this.
#   
#   Signed-off-by: Jean Delvare <khali@linux-fr.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/i2c-core.c
#   2005/03/08 10:06:36-08:00 khali@linux-fr.org +7 -1
#   I2C: Make master_xfer debug messages more useful
# 
# ChangeSet
#   2005/03/16 22:38:06-08:00 khali@linux-fr.org 
#   [PATCH] I2C: Kill unused struct members in w83627hf driver
#   
#   I just noticed that the pwmenable struct members in the w83627hf driver
#   are not used anywhere (and quite rightly so, as PWM cannot be disabled
#   in these chips as far as I know). Let's just get rid of them and save
#   some bytes of memory.
#   
#   Signed-off-by: Jean Delvare <khali@linux-fr.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/w83627hf.c
#   2005/03/07 02:56:22-08:00 khali@linux-fr.org +0 -5
#   I2C: Kill unused struct members in w83627hf driver
# 
# ChangeSet
#   2005/03/16 22:37:40-08:00 khali@linux-fr.org 
#   [PATCH] I2C: Fix adm1021 alarms mask
#   
#   This patch fixes an incorrect bitmasking on the status register in the
#   adm1021 driver, which was causing high alarm on remote temperature to be
#   hidden.
#   This bug was found and reported by Jayakrishnan:
#     http://bugzilla.kernel.org/show_bug.cgi?id=4285
#   
#   
#   Signed-off-by: Jean Delvare <khali@linux-fr.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/adm1021.c
#   2005/03/04 05:03:33-08:00 khali@linux-fr.org +1 -1
#   I2C: Fix adm1021 alarms mask
# 
# ChangeSet
#   2005/03/16 22:37:17-08:00 khali@linux-fr.org 
#   [PATCH] I2C: Cleanup adm1021 unused defines
#   
#   While working on the adm1021 driver, I found that this driver has a
#   number of unused (and useless) defines we could get rid of.
#   
#   Signed-off-by: Jean Delvare <khali@linux-fr.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/adm1021.c
#   2005/03/04 05:24:16-08:00 khali@linux-fr.org +0 -12
#   I2C: Cleanup adm1021 unused defines
# 
# ChangeSet
#   2005/03/16 22:36:58-08:00 khali@linux-fr.org 
#   [PATCH] I2C: New lm92 chip driver
#   
#   This is a new i2c chip driver named lm92. It supports the National
#   Semiconductor LM92 and Maxim MAX6635 chips. This time I did not port the
#   driver from the lm_sensors project but instead rewrote it. The reason is
#   that the original driver has a different structure from the other i2c
#   chip drivers, which would have made maintenance harder.
#   
#   I don't have a compatible chip myself but could test my code thanks to
#   Mark Hoffman's i2c-stub fake bus driver. Later on, James Chapman tested
#   it on a real chip, successfully.
#   
#   Signed-off-by: Jean Delvare <khali@linux-fr.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/chips/lm92.c
#   2005/03/06 00:19:47-08:00 khali@linux-fr.org +423 -0
#   I2C: New lm92 chip driver
# 
# drivers/i2c/chips/lm92.c
#   2005/03/06 00:19:47-08:00 khali@linux-fr.org +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/i2c/chips/lm92.c
# 
# drivers/i2c/chips/Makefile
#   2005/03/06 00:19:47-08:00 khali@linux-fr.org +1 -0
#   I2C: New lm92 chip driver
# 
# drivers/i2c/chips/Kconfig
#   2005/03/06 00:19:47-08:00 khali@linux-fr.org +11 -0
#   I2C: New lm92 chip driver
# 
# ChangeSet
#   2005/03/16 22:36:33-08:00 domen@coderock.org 
#   [PATCH] i2c/i2c-elektor: remove interruptible_sleep_on_timeout() usage
#   
#   Replace deprecated interruptible_sleep_on_timeout() with direct
#   wait-queue usage. Patch is compile-tested.
#   
#   Signed-off-by: Nishanth Aravamudan <nacc@us.ibm.com>
#   Signed-off-by: Domen Puncer <domen@coderock.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/busses/i2c-elektor.c
#   2005/03/05 07:12:06-08:00 domen@coderock.org +4 -3
#   i2c/i2c-elektor: remove interruptible_sleep_on_timeout() usage
# 
# ChangeSet
#   2005/03/16 22:36:08-08:00 domen@coderock.org 
#   [PATCH] i2c/i2c-ite: remove interruptible_sleep_on_timeout() usage
#   
#   Replace deprecated interruptible_sleep_on_timeout() with direct
#   wait-queue usage. Patch is compile-tested, sort of; the driver does not build in
#   vanilla kernel either, but I don't seem to add any warnings..
#   
#   Signed-off-by: Nishanth Aravamudan <nacc@us.ibm.com>
#   Signed-off-by: Domen Puncer <domen@coderock.org>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/i2c/busses/i2c-ite.c
#   2005/03/05 07:12:07-08:00 domen@coderock.org +5 -2
#   i2c/i2c-ite: remove interruptible_sleep_on_timeout() usage
# 
# ChangeSet
#   2005/03/14 21:31:27-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] superio: get rid of the potential problems with atomic operations.
#   
#   Get rid of the potential problems with atomic operations.
#   
#   According to upcoming atomic_ops.txt by David Miller and Anton Blanchard
#   some archs may reoder atomic operations with nonatomic, since
#   the former are always visible but the latter are not, this can lead
#   to unpredicted behaviour.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/superio/sc.c
#   2005/03/02 09:56:38-08:00 johnpol@2ka.mipt.ru +24 -2
#   superio: get rid of the potential problems with atomic operations.
# 
# ChangeSet
#   2005/03/14 21:31:01-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] kobject_uevent: prefill allocated buffer with 0 before use.
#   
#   Prefill allocated buffer with 0 before use.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# lib/kobject_uevent.c
#   2005/03/02 09:56:56-08:00 johnpol@2ka.mipt.ru +2 -0
#   kobject_uevent: prefill allocated buffer with 0 before use.
# 
# ChangeSet
#   2005/03/14 21:30:28-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] connector: fix skb leak on big messages.
#   
#   If message size exceeds CONNECTOR_MAX_MSG_SIZE skb can be leaked.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/connector/connector.c
#   2005/03/02 09:57:02-08:00 johnpol@2ka.mipt.ru +7 -2
#   connector: fix skb leak on big messages.
# 
# ChangeSet
#   2005/03/14 21:29:59-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] connector: allow sent to self messages.
#   
#   Allow sending messages to itself.
#   Since netlink sending routing do not allow(will not allow)
#   shared skb, we will clone original skb, if it fails
#   skb will be delivered only to remote groups.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/connector/connector.c
#   2005/03/02 09:57:09-08:00 johnpol@2ka.mipt.ru +9 -2
#   connector: allow sent to self messages.
# 
# ChangeSet
#   2005/03/14 21:29:24-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] connector: get rid of the potential problems with atomic operations.
#   
#   Get rid of the potential problems with atomic operations.
#   
#   According to upcoming atomic_ops.txt by David Miller and Anton Blanchard
#   some archs may reoder atomic operations with nonatomic, since
#   the former are always visible but the latter are not, this can lead
#   to unpredicted behaviour.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/connector/cn_queue.c
#   2005/03/02 09:57:18-08:00 johnpol@2ka.mipt.ru +14 -2
#   connector: get rid of the potential problems with atomic operations.
# 
# ChangeSet
#   2005/03/14 21:28:54-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] connector: Use DEFINE_SPINLOCK.
#   
#   Use DEFINE_SPINLOCK for private connector's notify lock.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/connector/connector.c
#   2005/03/02 09:58:14-08:00 johnpol@2ka.mipt.ru +1 -1
#   connector: Use DEFINE_SPINLOCK.
# 
# ChangeSet
#   2005/03/14 21:28:26-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] superio: Use msleep* calls instead of direct schedule* calls.
#   
#   Use msleep* calls instead of direct schedule* calls.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/superio/scx.c
#   2005/03/02 09:59:36-08:00 johnpol@2ka.mipt.ru +5 -5
#   superio: Use msleep* calls instead of direct schedule* calls.
# 
# drivers/superio/sc.c
#   2005/03/02 09:59:36-08:00 johnpol@2ka.mipt.ru +10 -10
#   superio: Use msleep* calls instead of direct schedule* calls.
# 
# drivers/superio/pc8736x.c
#   2005/03/02 09:59:36-08:00 johnpol@2ka.mipt.ru +5 -5
#   superio: Use msleep* calls instead of direct schedule* calls.
# 
# ChangeSet
#   2005/03/14 21:27:55-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] superio: change scx200 module name to scx.
#   
#   Change scx200 module name to scx.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/superio/scx.h
#   2005/03/02 09:59:42-08:00 johnpol@2ka.mipt.ru +1 -1
#   superio: change scx200 module name to scx.
# 
# drivers/superio/scx.c
#   2005/03/02 09:59:42-08:00 johnpol@2ka.mipt.ru +2 -2
#   superio: change scx200 module name to scx.
# 
# drivers/superio/Makefile
#   2005/03/02 09:59:42-08:00 johnpol@2ka.mipt.ru +1 -1
#   superio: change scx200 module name to scx.
# 
# ChangeSet
#   2005/03/14 21:27:16-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] superio: remove unneded exports and make some functions static.
#   
#   Remove unneded exports and make some functions static.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/superio/sc_gpio.h
#   2005/03/02 09:59:50-08:00 johnpol@2ka.mipt.ru +0 -7
#   superio: remove unneded exports and make some functions static.
# 
# drivers/superio/sc_gpio.c
#   2005/03/02 09:59:50-08:00 johnpol@2ka.mipt.ru +16 -14
#   superio: remove unneded exports and make some functions static.
# 
# drivers/superio/sc_acb.c
#   2005/03/02 09:59:50-08:00 johnpol@2ka.mipt.ru +10 -15
#   superio: remove unneded exports and make some functions static.
# 
# ChangeSet
#   2005/03/14 21:26:44-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] Re: kobject_uevent.c moved to kernel connector.
#   
#   kobject_uevent.c change which allows to use new kernel connector
#   interface.  More details at
#   http://marc.theaimsgroup.com/?l=linux-kernel&m=110370721906005&w=2
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# lib/kobject_uevent.c
#   2005/03/02 10:00:13-08:00 johnpol@2ka.mipt.ru +66 -0
#   Re: kobject_uevent.c moved to kernel connector.
# 
# ChangeSet
#   2005/03/14 21:26:15-08:00 greg@kroah.com 
#   [PATCH] superio: fix up some sparse warnings in the code
#   
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# drivers/superio/scx200.c
#   2005/03/02 10:00:20-08:00 greg@kroah.com +5 -5
#   superio: fix up some sparse warnings in the code
# 
# drivers/superio/sc_gpio.c
#   2005/03/02 10:00:20-08:00 greg@kroah.com +1 -1
#   superio: fix up some sparse warnings in the code
# 
# drivers/superio/pc8736x.c
#   2005/03/02 10:00:20-08:00 greg@kroah.com +6 -6
#   superio: fix up some sparse warnings in the code
# 
# ChangeSet
#   2005/03/14 21:25:41-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] superio: add SuperIO subsystem.
#   
#   SuperIO subsystem is new interface for different chips
#   that implement various logical devices inside.
#   Many embedded boards use such chips like SC1100 Geode processor
#   and PC87366. The latter for example contains
#   GPIO, Access Bus, Watchdog timer, Game port, MIDI port,
#   Voltage level monitor and Temperature sensor.
#   
#   SuperIO subsystem currently supports GPIO and
#   Access Bus logical devices in PC8736x and SCx200/SC1100 chips.
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# include/linux/sc_conn.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +50 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/scx200.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +28 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/scx200.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +413 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc_w1.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +107 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc_gpio.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +57 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc_gpio.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +310 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc_conn.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +124 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc_acb.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +45 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc_acb.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +163 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +134 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/sc.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +780 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/pin_test.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +93 -0
#   superio: add SuperIO subsystem.
# 
# include/linux/sc_conn.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/include/linux/sc_conn.h
# 
# include/linux/connector.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +4 -1
#   superio: add SuperIO subsystem.
# 
# drivers/superio/scx200.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/scx200.h
# 
# drivers/superio/scx200.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/scx200.c
# 
# drivers/superio/sc_w1.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc_w1.c
# 
# drivers/superio/sc_gpio.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc_gpio.h
# 
# drivers/superio/sc_gpio.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc_gpio.c
# 
# drivers/superio/sc_conn.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc_conn.c
# 
# drivers/superio/sc_acb.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc_acb.h
# 
# drivers/superio/sc_acb.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc_acb.c
# 
# drivers/superio/sc.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc.h
# 
# drivers/superio/sc.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/sc.c
# 
# drivers/superio/pin_test.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/pin_test.c
# 
# drivers/superio/pc8736x.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +39 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/pc8736x.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +209 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/chain.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +37 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/chain.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +52 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/Makefile
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +11 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/Kconfig
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +56 -0
#   superio: add SuperIO subsystem.
# 
# drivers/Makefile
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +1 -0
#   superio: add SuperIO subsystem.
# 
# drivers/Kconfig
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +2 -0
#   superio: add SuperIO subsystem.
# 
# drivers/superio/pc8736x.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/pc8736x.h
# 
# drivers/superio/pc8736x.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/pc8736x.c
# 
# drivers/superio/chain.h
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/chain.h
# 
# drivers/superio/chain.c
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/chain.c
# 
# drivers/superio/Makefile
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/Makefile
# 
# drivers/superio/Kconfig
#   2005/03/02 10:00:28-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/superio/Kconfig
# 
# ChangeSet
#   2005/03/14 21:25:11-08:00 johnpol@2ka.mipt.ru 
#   [PATCH] Add Kernel conector subsystem
#   
#   Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
#   Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
# 
# include/linux/connector.h
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +147 -0
#   Add Kernel conector subsystem
# 
# drivers/connector/connector.c
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +509 -0
#   Add Kernel conector subsystem
# 
# drivers/connector/cn_queue.c
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +219 -0
#   Add Kernel conector subsystem
# 
# drivers/connector/Makefile
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +2 -0
#   Add Kernel conector subsystem
# 
# include/linux/connector.h
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/include/linux/connector.h
# 
# drivers/connector/connector.c
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/connector/connector.c
# 
# drivers/connector/cn_queue.c
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/connector/cn_queue.c
# 
# drivers/connector/Makefile
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/connector/Makefile
# 
# drivers/connector/Kconfig
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +13 -0
#   Add Kernel conector subsystem
# 
# drivers/Makefile
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +2 -0
#   Add Kernel conector subsystem
# 
# drivers/Kconfig
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +2 -0
#   Add Kernel conector subsystem
# 
# drivers/connector/Kconfig
#   2005/03/02 10:01:44-08:00 johnpol@2ka.mipt.ru +0 -0
#   BitKeeper file /home/greg/linux/BK/i2c-2.6/drivers/connector/Kconfig
# 
diff -Nru a/drivers/Kconfig b/drivers/Kconfig
--- a/drivers/Kconfig	2005-03-20 16:43:51 -08:00
+++ b/drivers/Kconfig	2005-03-20 16:43:51 -08:00
@@ -4,6 +4,8 @@
 
 source "drivers/base/Kconfig"
 
+source "drivers/connector/Kconfig"
+
 source "drivers/mtd/Kconfig"
 
 source "drivers/parport/Kconfig"
@@ -43,6 +45,8 @@
 source "drivers/i2c/Kconfig"
 
 source "drivers/w1/Kconfig"
+
+source "drivers/superio/Kconfig"
 
 source "drivers/misc/Kconfig"
 
diff -Nru a/drivers/Makefile b/drivers/Makefile
--- a/drivers/Makefile	2005-03-20 16:43:51 -08:00
+++ b/drivers/Makefile	2005-03-20 16:43:51 -08:00
@@ -17,6 +17,8 @@
 # default.
 obj-y				+= char/
 
+obj-$(CONFIG_CONNECTOR)	+= connector/
+
 # i810fb and intelfb depend on char/agp/
 obj-$(CONFIG_FB_I810)           += video/i810/
 obj-$(CONFIG_FB_INTEL)          += video/intelfb/
@@ -52,6 +54,7 @@
 obj-$(CONFIG_I2O)		+= message/
 obj-$(CONFIG_I2C)		+= i2c/
 obj-$(CONFIG_W1)		+= w1/
+obj-$(CONFIG_SC_SUPERIO)	+= superio/
 obj-$(CONFIG_PHONE)		+= telephony/
 obj-$(CONFIG_MD)		+= md/
 obj-$(CONFIG_BT)		+= bluetooth/
diff -Nru a/drivers/connector/Kconfig b/drivers/connector/Kconfig
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/connector/Kconfig	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,13 @@
+menu "Connector - unified userspace <-> kernelspace linker"
+
+config CONNECTOR
+	tristate "Connector - unified userspace <-> kernelspace linker"
+	depends on NET
+	---help---
+	  This is unified userspace <-> kernelspace connector working on top
+	  of the netlink socket protocol.
+
+	  Connector support can also be built as a module.  If so, the module
+	  will be called cn.ko.
+
+endmenu
diff -Nru a/drivers/connector/Makefile b/drivers/connector/Makefile
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/connector/Makefile	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,2 @@
+obj-$(CONFIG_CONNECTOR)		+= cn.o
+cn-objs		:= cn_queue.o connector.o
diff -Nru a/drivers/connector/cn_queue.c b/drivers/connector/cn_queue.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/connector/cn_queue.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,231 @@
+/*
+ * 	cn_queue.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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/list.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/suspend.h>
+#include <linux/connector.h>
+#include <linux/delay.h>
+
+static void cn_queue_wrapper(void *data)
+{
+	struct cn_callback_entry *cbq = (struct cn_callback_entry *)data;
+
+	smp_mb__before_atomic_inc();
+	atomic_inc(&cbq->cb->refcnt);
+	smp_mb__after_atomic_inc();
+	cbq->cb->callback(cbq->cb->priv);
+	smp_mb__before_atomic_inc();
+	atomic_dec(&cbq->cb->refcnt);
+	smp_mb__after_atomic_inc();
+
+	cbq->destruct_data(cbq->ddata);
+}
+
+static struct cn_callback_entry *cn_queue_alloc_callback_entry(struct cn_callback *cb)
+{
+	struct cn_callback_entry *cbq;
+
+	cbq = kmalloc(sizeof(*cbq), GFP_KERNEL);
+	if (!cbq) {
+		printk(KERN_ERR "Failed to create new callback queue.\n");
+		return NULL;
+	}
+
+	memset(cbq, 0, sizeof(*cbq));
+
+	cbq->cb = cb;
+
+	INIT_WORK(&cbq->work, &cn_queue_wrapper, cbq);
+
+	return cbq;
+}
+
+static void cn_queue_free_callback(struct cn_callback_entry *cbq)
+{
+	cancel_delayed_work(&cbq->work);
+
+	while (atomic_read(&cbq->cb->refcnt)) {
+		printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n",
+		       cbq->pdev->name, atomic_read(&cbq->cb->refcnt));
+
+		msleep_interruptible(1000);
+
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
+		if (signal_pending(current))
+			flush_signals(current);
+	}
+
+	kfree(cbq);
+}
+
+int cn_cb_equal(struct cb_id *i1, struct cb_id *i2)
+{
+#if 0
+	printk(KERN_INFO "%s: comparing %04x.%04x and %04x.%04x\n",
+			__func__,
+			i1->idx, i1->val,
+			i2->idx, i2->val);
+#endif
+	return ((i1->idx == i2->idx) && (i1->val == i2->val));
+}
+
+int cn_queue_add_callback(struct cn_queue_dev *dev, struct cn_callback *cb)
+{
+	struct cn_callback_entry *cbq, *n, *__cbq;
+	int found = 0;
+
+	cbq = cn_queue_alloc_callback_entry(cb);
+	if (!cbq)
+		return -ENOMEM;
+
+	atomic_inc(&dev->refcnt);
+	smp_mb__after_atomic_inc();
+	cbq->pdev = dev;
+
+	spin_lock_bh(&dev->queue_lock);
+	list_for_each_entry_safe(__cbq, n, &dev->queue_list, callback_entry) {
+		if (cn_cb_equal(&__cbq->cb->id, &cb->id)) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		atomic_set(&cbq->cb->refcnt, 1);
+		list_add_tail(&cbq->callback_entry, &dev->queue_list);
+	}
+	spin_unlock_bh(&dev->queue_lock);
+
+	if (found) {
+		smp_mb__before_atomic_inc();
+		atomic_dec(&dev->refcnt);
+		smp_mb__after_atomic_inc();
+		atomic_set(&cbq->cb->refcnt, 0);
+		cn_queue_free_callback(cbq);
+		return -EINVAL;
+	}
+
+	cbq->nls = dev->nls;
+	cbq->seq = 0;
+	cbq->group = cbq->cb->id.idx;
+
+	return 0;
+}
+
+void cn_queue_del_callback(struct cn_queue_dev *dev, struct cn_callback *cb)
+{
+	struct cn_callback_entry *cbq = NULL, *n;
+	int found = 0;
+
+	spin_lock_bh(&dev->queue_lock);
+	list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) {
+		if (cn_cb_equal(&cbq->cb->id, &cb->id)) {
+			list_del(&cbq->callback_entry);
+			found = 1;
+			break;
+		}
+	}
+	spin_unlock_bh(&dev->queue_lock);
+
+	if (found) {
+		smp_mb__before_atomic_inc();
+		atomic_dec(&cbq->cb->refcnt);
+		smp_mb__after_atomic_inc();
+		cn_queue_free_callback(cbq);
+		smp_mb__before_atomic_inc();
+		atomic_dec(&dev->refcnt);
+		smp_mb__after_atomic_inc();
+	}
+}
+
+struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls)
+{
+	struct cn_queue_dev *dev;
+
+	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		printk(KERN_ERR "%s: Failed to allocte new struct cn_queue_dev.\n",
+		       name);
+		return NULL;
+	}
+
+	memset(dev, 0, sizeof(*dev));
+
+	snprintf(dev->name, sizeof(dev->name), "%s", name);
+
+	atomic_set(&dev->refcnt, 0);
+	INIT_LIST_HEAD(&dev->queue_list);
+	spin_lock_init(&dev->queue_lock);
+
+	dev->nls = nls;
+	dev->netlink_groups = 0;
+
+	dev->cn_queue = create_workqueue(dev->name);
+	if (!dev->cn_queue) {
+		printk(KERN_ERR "Failed to create %s queue.\n", dev->name);
+		kfree(dev);
+		return NULL;
+	}
+
+	return dev;
+}
+
+void cn_queue_free_dev(struct cn_queue_dev *dev)
+{
+	struct cn_callback_entry *cbq, *n;
+
+	flush_workqueue(dev->cn_queue);
+	destroy_workqueue(dev->cn_queue);
+
+	spin_lock_bh(&dev->queue_lock);
+	list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) {
+		list_del(&cbq->callback_entry);
+		smp_mb__before_atomic_inc();
+		atomic_dec(&cbq->cb->refcnt);
+		smp_mb__after_atomic_inc();
+	}
+	spin_unlock_bh(&dev->queue_lock);
+
+	while (atomic_read(&dev->refcnt)) {
+		printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n",
+		       dev->name, atomic_read(&dev->refcnt));
+
+		msleep_interruptible(1000);
+
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
+		if (signal_pending(current))
+			flush_signals(current);
+	}
+
+	memset(dev, 0, sizeof(*dev));
+	kfree(dev);
+	dev = NULL;
+}
diff -Nru a/drivers/connector/connector.c b/drivers/connector/connector.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/connector/connector.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,521 @@
+/*
+ * 	connector.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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/list.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/moduleparam.h>
+#include <linux/connector.h>
+
+#include <net/sock.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
+
+static int unit = NETLINK_NFLOG;
+static u32 cn_idx = -1;
+static u32 cn_val = -1;
+
+module_param(unit, int, 0);
+module_param(cn_idx, uint, 0);
+module_param(cn_val, uint, 0);
+
+static DEFINE_SPINLOCK(notify_lock);
+static LIST_HEAD(notify_list);
+
+static struct cn_dev cdev;
+
+int cn_already_initialized = 0;
+
+/*
+ * msg->seq and msg->ack are used to determine message genealogy.
+ * When someone sends message it puts there locally unique sequence 
+ * and random acknowledge numbers.
+ * Sequence number may be copied into nlmsghdr->nlmsg_seq too.
+ *
+ * Sequence number is incremented with each message to be sent.
+ *
+ * If we expect reply to our message, 
+ * then sequence number in received message MUST be the same as in original message,
+ * and acknowledge number MUST be the same + 1.
+ *
+ * If we receive message and it's sequence number is not equal to one we are expecting, 
+ * then it is new message.
+ * If we receive message and it's sequence number is the same as one we are expecting,
+ * but it's acknowledge is not equal acknowledge number in original message + 1,
+ * then it is new message.
+ *
+ */
+void cn_netlink_send(struct cn_msg *msg, u32 __groups)
+{
+	struct cn_callback_entry *n, *__cbq;
+	unsigned int size;
+	struct sk_buff *skb, *uskb;
+	struct nlmsghdr *nlh;
+	struct cn_msg *data;
+	struct cn_dev *dev = &cdev;
+	u32 groups = 0;
+	int found = 0;
+
+	if (!__groups)
+	{
+		spin_lock_bh(&dev->cbdev->queue_lock);
+		list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry) {
+			if (cn_cb_equal(&__cbq->cb->id, &msg->id)) {
+				found = 1;
+				groups = __cbq->group;
+			}
+		}
+		spin_unlock_bh(&dev->cbdev->queue_lock);
+
+		if (!found) {
+			printk(KERN_ERR "Failed to find multicast netlink group for callback[0x%x.0x%x]. seq=%u\n",
+			       msg->id.idx, msg->id.val, msg->seq);
+			return;
+		}
+	}
+	else
+		groups = __groups;
+
+	size = NLMSG_SPACE(sizeof(*msg) + msg->len);
+
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (!skb) {
+		printk(KERN_ERR "Failed to allocate new skb with size=%u.\n", size);
+		return;
+	}
+
+	nlh = NLMSG_PUT(skb, 0, msg->seq, NLMSG_DONE, size - sizeof(*nlh));
+
+	data = (struct cn_msg *)NLMSG_DATA(nlh);
+
+	memcpy(data, msg, sizeof(*data) + msg->len);
+#if 0
+	printk("%s: len=%u, seq=%u, ack=%u, group=%u.\n",
+	       __func__, msg->len, msg->seq, msg->ack, groups);
+#endif
+	
+	NETLINK_CB(skb).dst_groups = groups;
+
+	uskb = skb_clone(skb, GFP_ATOMIC);
+	if (uskb) {
+		netlink_unicast(dev->nls, uskb, 0, 0);
+	}
+	
+	netlink_broadcast(dev->nls, skb, 0, groups, GFP_ATOMIC);
+
+	return;
+
+nlmsg_failure:
+	printk(KERN_ERR "Failed to send %u.%u\n", msg->seq, msg->ack);
+	kfree_skb(skb);
+	return;
+}
+
+static int cn_call_callback(struct cn_msg *msg, void (*destruct_data) (void *), void *data)
+{
+	struct cn_callback_entry *n, *__cbq;
+	struct cn_dev *dev = &cdev;
+	int found = 0;
+
+	spin_lock_bh(&dev->cbdev->queue_lock);
+	list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry) {
+		if (cn_cb_equal(&__cbq->cb->id, &msg->id)) {
+			__cbq->cb->priv = msg;
+
+			__cbq->ddata = data;
+			__cbq->destruct_data = destruct_data;
+
+			queue_work(dev->cbdev->cn_queue, &__cbq->work);
+			found = 1;
+			break;
+		}
+	}
+	spin_unlock_bh(&dev->cbdev->queue_lock);
+
+	return found;
+}
+
+static int __cn_rx_skb(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	u32 pid, uid, seq, group;
+	struct cn_msg *msg;
+
+	pid = NETLINK_CREDS(skb)->pid;
+	uid = NETLINK_CREDS(skb)->uid;
+	seq = nlh->nlmsg_seq;
+	group = NETLINK_CB((skb)).groups;
+	msg = (struct cn_msg *)NLMSG_DATA(nlh);
+
+	if (msg->len != nlh->nlmsg_len - sizeof(*msg) - sizeof(*nlh)) {
+		printk(KERN_ERR "skb does not have enough length: "
+				"requested msg->len=%u[%u], nlh->nlmsg_len=%u[%u], skb->len=%u[must be %u].\n", 
+				msg->len, NLMSG_SPACE(msg->len), 
+				nlh->nlmsg_len, nlh->nlmsg_len - sizeof(*nlh),
+				skb->len, msg->len + sizeof(*msg));
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+#if 0
+	printk(KERN_INFO "pid=%u, uid=%u, seq=%u, group=%u.\n",
+	       pid, uid, seq, group);
+#endif
+	return cn_call_callback(msg, (void (*)(void *))kfree_skb, skb);
+}
+
+static void cn_rx_skb(struct sk_buff *__skb)
+{
+	struct nlmsghdr *nlh;
+	u32 len;
+	int err;
+	struct sk_buff *skb;
+
+	skb = skb_get(__skb);
+	if (!skb) {
+		printk(KERN_ERR "Failed to reference an skb.\n");
+		kfree_skb(__skb);
+		return;
+	}
+#if 0
+	printk(KERN_INFO
+	       "skb: len=%u, data_len=%u, truesize=%u, proto=%u, cloned=%d, shared=%d.\n",
+	       skb->len, skb->data_len, skb->truesize, skb->protocol,
+	       skb_cloned(skb), skb_shared(skb));
+#endif
+	while (skb->len >= NLMSG_SPACE(0)) {
+		nlh = (struct nlmsghdr *)skb->data;
+		if (nlh->nlmsg_len < sizeof(struct cn_msg) ||
+		    skb->len < nlh->nlmsg_len ||
+		    nlh->nlmsg_len > CONNECTOR_MAX_MSG_SIZE) {
+#if 0
+			printk(KERN_INFO "nlmsg_len=%u, sizeof(*nlh)=%u\n",
+			       nlh->nlmsg_len, sizeof(*nlh));
+#endif
+			kfree_skb(skb);
+			break;
+		}
+
+		len = NLMSG_ALIGN(nlh->nlmsg_len);
+		if (len > skb->len)
+			len = skb->len;
+
+		err = __cn_rx_skb(skb, nlh);
+		if (err) {
+#if 0
+			if (err < 0 && (nlh->nlmsg_flags & NLM_F_ACK))
+				netlink_ack(skb, nlh, -err);
+#endif
+			break;
+		} else {
+#if 0
+			if (nlh->nlmsg_flags & NLM_F_ACK)
+				netlink_ack(skb, nlh, 0);
+#endif
+			break;
+		}
+		skb_pull(skb, len);
+	}
+			
+	kfree_skb(__skb);
+}
+
+static void cn_input(struct sock *sk, int len)
+{
+	struct sk_buff *skb;
+
+	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL)
+		cn_rx_skb(skb);
+}
+
+static void cn_notify(struct cb_id *id, u32 notify_event)
+{
+	struct cn_ctl_entry *ent;
+
+	spin_lock_bh(&notify_lock);
+	list_for_each_entry(ent, &notify_list, notify_entry) {
+		int i;
+		struct cn_notify_req *req;
+		struct cn_ctl_msg *ctl = ent->msg;
+		int a, b;
+
+		a = b = 0;
+		
+		req = (struct cn_notify_req *)ctl->data;
+		for (i=0; i<ctl->idx_notify_num; ++i, ++req) {
+			if (id->idx >= req->first && id->idx < req->first + req->range) {
+				a = 1;
+				break;
+			}
+		}
+		
+		for (i=0; i<ctl->val_notify_num; ++i, ++req) {
+			if (id->val >= req->first && id->val < req->first + req->range) {
+				b = 1;
+				break;
+			}
+		}
+
+		if (a && b) {
+			struct cn_msg m;
+			
+			printk(KERN_INFO "Notifying group %x with event %u about %x.%x.\n", 
+					ctl->group, notify_event, 
+					id->idx, id->val);
+
+			memset(&m, 0, sizeof(m));
+			m.ack = notify_event;
+
+			memcpy(&m.id, id, sizeof(m.id));
+			cn_netlink_send(&m, ctl->group);
+		}
+	}
+	spin_unlock_bh(&notify_lock);
+}
+
+int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void *))
+{
+	int err;
+	struct cn_dev *dev = &cdev;
+	struct cn_callback *cb;
+
+	cb = kmalloc(sizeof(*cb), GFP_KERNEL);
+	if (!cb) {
+		printk(KERN_INFO "%s: Failed to allocate new struct cn_callback.\n",
+		       dev->cbdev->name);
+		return -ENOMEM;
+	}
+
+	memset(cb, 0, sizeof(*cb));
+
+	snprintf(cb->name, sizeof(cb->name), "%s", name);
+
+	memcpy(&cb->id, id, sizeof(cb->id));
+	cb->callback = callback;
+
+	atomic_set(&cb->refcnt, 0);
+
+	err = cn_queue_add_callback(dev->cbdev, cb);
+	if (err) {
+		kfree(cb);
+		return err;
+	}
+			
+	cn_notify(id, 0);
+
+	return 0;
+}
+
+void cn_del_callback(struct cb_id *id)
+{
+	struct cn_dev *dev = &cdev;
+	struct cn_callback_entry *n, *__cbq;
+
+	list_for_each_entry_safe(__cbq, n, &dev->cbdev->queue_list, callback_entry) {
+		if (cn_cb_equal(&__cbq->cb->id, id)) {
+			cn_queue_del_callback(dev->cbdev, __cbq->cb);
+			cn_notify(id, 1);
+			break;
+		}
+	}
+}
+
+static int cn_ctl_msg_equals(struct cn_ctl_msg *m1, struct cn_ctl_msg *m2)
+{
+	int i;
+	struct cn_notify_req *req1, *req2;
+
+	if (m1->idx_notify_num != m2->idx_notify_num)
+		return 0;
+	
+	if (m1->val_notify_num != m2->val_notify_num)
+		return 0;
+	
+	if (m1->len != m2->len)
+		return 0;
+
+	if ((m1->idx_notify_num + m1->val_notify_num)*sizeof(*req1) != m1->len) {
+		printk(KERN_ERR "Notify entry[idx_num=%x, val_num=%x, len=%u] contains garbage. Removing.\n", 
+				m1->idx_notify_num, m1->val_notify_num, m1->len);
+		return 1;
+	}
+
+	req1 = (struct cn_notify_req *)m1->data;
+	req2 = (struct cn_notify_req *)m2->data;
+	
+	for (i=0; i<m1->idx_notify_num; ++i) {
+		if (memcmp(req1, req2, sizeof(*req1)))
+			return 0;
+
+		req1++;
+		req2++;
+	}
+
+	for (i=0; i<m1->val_notify_num; ++i) {
+		if (memcmp(req1, req2, sizeof(*req1)))
+			return 0;
+
+		req1++;
+		req2++;
+	}
+
+	return 1;
+}
+
+static void cn_callback(void * data)
+{
+	struct cn_msg *msg = (struct cn_msg *)data;
+	struct cn_ctl_msg *ctl;
+	struct cn_ctl_entry *ent;
+	u32 size;
+ 
+	if (msg->len < sizeof(*ctl)) {
+		printk(KERN_ERR "Wrong connector request size %u, must be >= %u.\n", 
+				msg->len, sizeof(*ctl));
+		return;
+	}
+	
+	ctl = (struct cn_ctl_msg *)msg->data;
+
+	size = sizeof(*ctl) + (ctl->idx_notify_num + ctl->val_notify_num)*sizeof(struct cn_notify_req);
+
+	if (msg->len != size) {
+		printk(KERN_ERR "Wrong connector request size %u, must be == %u.\n", 
+				msg->len, size);
+		return;
+	}
+
+	if (ctl->len + sizeof(*ctl) != msg->len) {
+		printk(KERN_ERR "Wrong message: msg->len=%u must be equal to inner_len=%u [+%u].\n", 
+				msg->len, ctl->len, sizeof(*ctl));
+		return;
+	}
+
+	/*
+	 * Remove notification.
+	 */
+	if (ctl->group == 0) {
+		struct cn_ctl_entry *n;
+		
+		spin_lock_bh(&notify_lock);
+		list_for_each_entry_safe(ent, n, &notify_list, notify_entry) {
+			if (cn_ctl_msg_equals(ent->msg, ctl)) {
+				list_del(&ent->notify_entry);
+				kfree(ent);
+			}
+		}
+		spin_unlock_bh(&notify_lock);
+
+		return;
+	}
+
+	size += sizeof(*ent);
+
+	ent = kmalloc(size, GFP_ATOMIC);
+	if (!ent) {
+		printk(KERN_ERR "Failed to allocate %d bytes for new notify entry.\n", size);
+		return;
+	}
+
+	memset(ent, 0, size);
+
+	ent->msg = (struct cn_ctl_msg *)(ent + 1);
+
+	memcpy(ent->msg, ctl, size - sizeof(*ent));
+
+	spin_lock_bh(&notify_lock);
+	list_add(&ent->notify_entry, &notify_list);
+	spin_unlock_bh(&notify_lock);
+
+	{
+		int i;
+		struct cn_notify_req *req;
+	
+		printk("Notify group %x for idx: ", ctl->group);
+
+		req = (struct cn_notify_req *)ctl->data;
+		for (i=0; i<ctl->idx_notify_num; ++i, ++req) {
+			printk("%u-%u ", req->first, req->first+req->range-1);
+		}
+		
+		printk("\nNotify group %x for val: ", ctl->group);
+
+		for (i=0; i<ctl->val_notify_num; ++i, ++req) {
+			printk("%u-%u ", req->first, req->first+req->range-1);
+		}
+		printk("\n");
+	}
+}
+
+static int cn_init(void)
+{
+	struct cn_dev *dev = &cdev;
+	int err;
+
+	dev->input = cn_input;
+	dev->id.idx = cn_idx;
+	dev->id.val = cn_val;
+
+	dev->nls = netlink_kernel_create(unit, dev->input);
+	if (!dev->nls) {
+		printk(KERN_ERR "Failed to create new netlink socket(%u).\n",
+		       unit);
+		return -EIO;
+	}
+
+	dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls);
+	if (!dev->cbdev) {
+		if (dev->nls->sk_socket)
+			sock_release(dev->nls->sk_socket);
+		return -EINVAL;
+	}
+
+	err = cn_add_callback(&dev->id, "connector", &cn_callback);
+	if (err) {
+		cn_queue_free_dev(dev->cbdev);
+		if (dev->nls->sk_socket)
+			sock_release(dev->nls->sk_socket);
+		return -EINVAL;
+	}
+
+	cn_already_initialized = 1;
+
+	return 0;
+}
+
+static void cn_fini(void)
+{
+	struct cn_dev *dev = &cdev;
+
+	cn_del_callback(&dev->id);
+	cn_queue_free_dev(dev->cbdev);
+	if (dev->nls->sk_socket)
+		sock_release(dev->nls->sk_socket);
+}
+
+module_init(cn_init);
+module_exit(cn_fini);
+
+EXPORT_SYMBOL_GPL(cn_add_callback);
+EXPORT_SYMBOL_GPL(cn_del_callback);
+EXPORT_SYMBOL_GPL(cn_netlink_send);
diff -Nru a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
--- a/drivers/i2c/busses/Kconfig	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/busses/Kconfig	2005-03-20 16:43:51 -08:00
@@ -108,7 +108,7 @@
 	  will be called i2c-hydra.
 
 config I2C_I801
-	tristate "Intel 801"
+	tristate "Intel 82801 (ICH)"
 	depends on I2C && PCI && EXPERIMENTAL
 	help
 	  If you say yes to this option, support will be included for the Intel
@@ -119,7 +119,7 @@
 	    82801BA
 	    82801CA/CAM
 	    82801DB
-	    82801EB
+	    82801EB/ER (ICH5/ICH5R)
 	    6300ESB
 	    ICH6
 	    ICH7
@@ -143,6 +143,23 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-i810.
 
+config I2C_PIIX4
+	tristate "Intel PIIX4"
+	depends on I2C && PCI
+	help
+	  If you say yes to this option, support will be included for the Intel
+	  PIIX4 family of mainboard I2C interfaces.  Specifically, the following
+	  versions of the chipset are supported:
+	    Intel PIIX4
+	    Intel 440MX
+	    Serverworks OSB4
+	    Serverworks CSB5
+	    Serverworks CSB6
+	    SMSC Victory66
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-piix4.
+
 config I2C_IBM_IIC
 	tristate "IBM PPC 4xx on-chip I2C interface"
 	depends on IBM_OCP && I2C
@@ -284,23 +301,6 @@
 
 	  This support is also available as a module.  If so, the module 
 	  will be called i2c-parport-light.
-
-config I2C_PIIX4
-	tristate "Intel PIIX4"
-	depends on I2C && PCI && EXPERIMENTAL
-	help
-	  If you say yes to this option, support will be included for the Intel
-	  PIIX4 family of mainboard I2C interfaces.  Specifically, the following
-	  versions of the chipset are supported:
-	    Intel PIIX4
-	    Intel 440MX
-	    Serverworks OSB4
-	    Serverworks CSB5
-	    Serverworks CSB6
-	    SMSC Victory66
-
-	  This driver can also be built as a module.  If so, the module
-	  will be called i2c-piix4.
 
 config I2C_PROSAVAGE
 	tristate "S3/VIA (Pro)Savage"
diff -Nru a/drivers/i2c/busses/i2c-elektor.c b/drivers/i2c/busses/i2c-elektor.c
--- a/drivers/i2c/busses/i2c-elektor.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/busses/i2c-elektor.c	2005-03-20 16:43:51 -08:00
@@ -110,22 +110,23 @@
 }
 
 static void pcf_isa_waitforpin(void) {
-
+	DEFINE_WAIT(wait);
 	int timeout = 2;
-	long flags;
+	unsigned long flags;
 
 	if (irq > 0) {
 		spin_lock_irqsave(&lock, flags);
 		if (pcf_pending == 0) {
 			spin_unlock_irqrestore(&lock, flags);
-			if (interruptible_sleep_on_timeout(&pcf_wait,
-								timeout*HZ)) {
+			prepare_to_wait(&pcf_wait, &wait, TASK_INTERRUPTIBLE);
+			if (schedule_timeout(timeout*HZ)) {
 				spin_lock_irqsave(&lock, flags);
 				if (pcf_pending == 1) {
 					pcf_pending = 0;
 				}
 				spin_unlock_irqrestore(&lock, flags);
 			}
+			finish_wait(&pcf_wait, &wait);
 		} else {
 			pcf_pending = 0;
 			spin_unlock_irqrestore(&lock, flags);
diff -Nru a/drivers/i2c/busses/i2c-ite.c b/drivers/i2c/busses/i2c-ite.c
--- a/drivers/i2c/busses/i2c-ite.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/busses/i2c-ite.c	2005-03-20 16:43:51 -08:00
@@ -40,6 +40,7 @@
 #include <linux/delay.h>
 #include <linux/slab.h>
 #include <linux/init.h>
+#include <linux/wait.h>
 #include <asm/irq.h>
 #include <asm/io.h>
 
@@ -107,7 +108,7 @@
  * IIC controller interrupts.
  */
 static void iic_ite_waitforpin(void) {
-
+   DEFINE_WAIT(wait);
    int timeout = 2;
    long flags;
 
@@ -121,13 +122,15 @@
 	spin_lock_irqsave(&lock, flags);
 	if (iic_pending == 0) {
 		spin_unlock_irqrestore(&lock, flags);
-		if (interruptible_sleep_on_timeout(&iic_wait, timeout*HZ)) {
+		prepare_to_wait(&iic_wait, &wait, TASK_INTERRUPTIBLE);
+		if (schedule_timeout(timeout*HZ)) {
 			spin_lock_irqsave(&lock, flags);
 			if (iic_pending == 1) {
 				iic_pending = 0;
 			}
 			spin_unlock_irqrestore(&lock, flags);
 		}
+		finish_wait(&iic_wait, &wait);
 	} else {
 		iic_pending = 0;
 		spin_unlock_irqrestore(&lock, flags);
diff -Nru a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
--- a/drivers/i2c/chips/Kconfig	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/Kconfig	2005-03-20 16:43:51 -08:00
@@ -236,6 +236,17 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called lm90.
 
+config SENSORS_LM92
+	tristate "National Semiconductor LM92 and compatibles"
+	depends on I2C && EXPERIMENTAL
+	select I2C_SENSOR
+	help
+	  If you say yes here you get support for National Semiconductor LM92
+	  and Maxim MAX6635 sensor chips.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called lm92.
+
 config SENSORS_MAX1619
 	tristate "Maxim MAX1619 sensor chip"
 	depends on I2C && EXPERIMENTAL
@@ -350,6 +361,17 @@
 
 menu "Other I2C Chip support"
 	depends on I2C
+
+config SENSORS_DS1337
+	tristate "Dallas Semiconductor DS1337 Real Time Clock"
+	depends on I2C && EXPERIMENTAL
+	select I2C_SENSOR
+	help
+	  If you say yes here you get support for Dallas Semiconductor
+	  DS1337 real-time clock chips. 
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called ds1337.
 
 config SENSORS_EEPROM
 	tristate "EEPROM reader"
diff -Nru a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
--- a/drivers/i2c/chips/Makefile	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/Makefile	2005-03-20 16:43:51 -08:00
@@ -11,6 +11,7 @@
 obj-$(CONFIG_SENSORS_ADM1025)	+= adm1025.o
 obj-$(CONFIG_SENSORS_ADM1026)	+= adm1026.o
 obj-$(CONFIG_SENSORS_ADM1031)	+= adm1031.o
+obj-$(CONFIG_SENSORS_DS1337)	+= ds1337.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EEPROM)	+= eeprom.o
 obj-$(CONFIG_SENSORS_FSCHER)	+= fscher.o
@@ -27,6 +28,7 @@
 obj-$(CONFIG_SENSORS_LM85)	+= lm85.o
 obj-$(CONFIG_SENSORS_LM87)	+= lm87.o
 obj-$(CONFIG_SENSORS_LM90)	+= lm90.o
+obj-$(CONFIG_SENSORS_LM92)	+= lm92.o
 obj-$(CONFIG_SENSORS_MAX1619)	+= max1619.o
 obj-$(CONFIG_SENSORS_M41T00)	+= m41t00.o
 obj-$(CONFIG_SENSORS_PC87360)	+= pc87360.o
diff -Nru a/drivers/i2c/chips/adm1021.c b/drivers/i2c/chips/adm1021.c
--- a/drivers/i2c/chips/adm1021.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/adm1021.c	2005-03-20 16:43:51 -08:00
@@ -28,18 +28,6 @@
 #include <linux/i2c-sensor.h>
 
 
-/* Registers */
-#define ADM1021_SYSCTL_TEMP		1200
-#define ADM1021_SYSCTL_REMOTE_TEMP	1201
-#define ADM1021_SYSCTL_DIE_CODE		1202
-#define ADM1021_SYSCTL_ALARMS		1203
-
-#define ADM1021_ALARM_TEMP_HIGH		0x40
-#define ADM1021_ALARM_TEMP_LOW		0x20
-#define ADM1021_ALARM_RTEMP_HIGH	0x10
-#define ADM1021_ALARM_RTEMP_LOW		0x08
-#define ADM1021_ALARM_RTEMP_NA		0x04
-
 /* Addresses to scan */
 static unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a,
 					0x29, 0x2a, 0x2b,
@@ -380,7 +368,7 @@
 		data->remote_temp_input = adm1021_read_value(client, ADM1021_REG_REMOTE_TEMP);
 		data->remote_temp_max = adm1021_read_value(client, ADM1021_REG_REMOTE_TOS_R);
 		data->remote_temp_hyst = adm1021_read_value(client, ADM1021_REG_REMOTE_THYST_R);
-		data->alarms = adm1021_read_value(client, ADM1021_REG_STATUS) & 0xec;
+		data->alarms = adm1021_read_value(client, ADM1021_REG_STATUS) & 0x7c;
 		if (data->type == adm1021)
 			data->die_code = adm1021_read_value(client, ADM1021_REG_DIE_CODE);
 		if (data->type == adm1023) {
diff -Nru a/drivers/i2c/chips/ds1337.c b/drivers/i2c/chips/ds1337.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/i2c/chips/ds1337.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,402 @@
+/*
+ *  linux/drivers/i2c/chips/ds1337.c
+ *
+ *  Copyright (C) 2005 James Chapman <jchapman@katalix.com>
+ *
+ *	based on linux/drivers/acron/char/pcf8583.c
+ *  Copyright (C) 2000 Russell King
+ *
+ * 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
+ * published by the Free Software Foundation.
+ *
+ * Driver for Dallas Semiconductor DS1337 real time clock chip
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/i2c-sensor.h>
+#include <linux/string.h>
+#include <linux/rtc.h>		/* get the user-level API */
+#include <linux/bcd.h>
+#include <linux/list.h>
+
+/* Device registers */
+#define DS1337_REG_HOUR		2
+#define DS1337_REG_DAY		3
+#define DS1337_REG_DATE		4
+#define DS1337_REG_MONTH	5
+#define DS1337_REG_CONTROL	14
+#define DS1337_REG_STATUS	15
+
+/* FIXME - how do we export these interface constants? */
+#define DS1337_GET_DATE		0
+#define DS1337_SET_DATE		1
+
+/*
+ * Functions declaration
+ */
+static unsigned short normal_i2c[] = { 0x68, I2C_CLIENT_END };
+static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
+
+SENSORS_INSMOD_1(ds1337);
+
+static int ds1337_attach_adapter(struct i2c_adapter *adapter);
+static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind);
+static void ds1337_init_client(struct i2c_client *client);
+static int ds1337_detach_client(struct i2c_client *client);
+static int ds1337_command(struct i2c_client *client, unsigned int cmd,
+			  void *arg);
+
+/*
+ * Driver data (common to all clients)
+ */
+static struct i2c_driver ds1337_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "ds1337",
+	.flags		= I2C_DF_NOTIFY,
+	.attach_adapter	= ds1337_attach_adapter,
+	.detach_client	= ds1337_detach_client,
+	.command	= ds1337_command,
+};
+
+/*
+ * Client data (each client gets its own)
+ */
+struct ds1337_data {
+	struct i2c_client client;
+	struct list_head list;
+	int id;
+};
+
+/*
+ * Internal variables
+ */
+static int ds1337_id;
+static LIST_HEAD(ds1337_clients);
+
+static inline int ds1337_read(struct i2c_client *client, u8 reg, u8 *value)
+{
+	s32 tmp = i2c_smbus_read_byte_data(client, reg);
+
+	if (tmp < 0)
+		return -EIO;
+
+	*value = tmp;
+
+	return 0;
+}
+
+/*
+ * Chip access functions
+ */
+static int ds1337_get_datetime(struct i2c_client *client, struct rtc_time *dt)
+{
+	struct ds1337_data *data = i2c_get_clientdata(client);
+	int result;
+	u8 buf[7];
+	u8 val;
+	struct i2c_msg msg[2];
+	u8 offs = 0;
+
+	if (!dt) {
+		dev_dbg(&client->adapter->dev, "%s: EINVAL: dt=NULL\n",
+			__FUNCTION__);
+
+		return -EINVAL;
+	}
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].len = 1;
+	msg[0].buf = &offs;
+
+	msg[1].addr = client->addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].len = sizeof(buf);
+	msg[1].buf = &buf[0];
+
+	result = client->adapter->algo->master_xfer(client->adapter,
+						    &msg[0], 2);
+
+	dev_dbg(&client->adapter->dev,
+		"%s: [%d] %02x %02x %02x %02x %02x %02x %02x\n",
+		__FUNCTION__, result, buf[0], buf[1], buf[2], buf[3],
+		buf[4], buf[5], buf[6]);
+
+	if (result >= 0) {
+		dt->tm_sec = BCD_TO_BIN(buf[0]);
+		dt->tm_min = BCD_TO_BIN(buf[1]);
+		val = buf[2] & 0x3f;
+		dt->tm_hour = BCD_TO_BIN(val);
+		dt->tm_wday = BCD_TO_BIN(buf[3]) - 1;
+		dt->tm_mday = BCD_TO_BIN(buf[4]);
+		val = buf[5] & 0x7f;
+		dt->tm_mon = BCD_TO_BIN(val);
+		dt->tm_year = 1900 + BCD_TO_BIN(buf[6]);
+		if (buf[5] & 0x80)
+			dt->tm_year += 100;
+
+		dev_dbg(&client->adapter->dev, "%s: secs=%d, mins=%d, "
+			"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
+			__FUNCTION__, dt->tm_sec, dt->tm_min,
+			dt->tm_hour, dt->tm_mday,
+			dt->tm_mon, dt->tm_year, dt->tm_wday);
+	} else {
+		dev_err(&client->adapter->dev, "ds1337[%d]: error reading "
+			"data! %d\n", data->id, result);
+		result = -EIO;
+	}
+
+	return result;
+}
+
+static int ds1337_set_datetime(struct i2c_client *client, struct rtc_time *dt)
+{
+	struct ds1337_data *data = i2c_get_clientdata(client);
+	int result;
+	u8 buf[8];
+	u8 val;
+	struct i2c_msg msg[1];
+
+	if (!dt) {
+		dev_dbg(&client->adapter->dev, "%s: EINVAL: dt=NULL\n",
+			__FUNCTION__);
+
+		return -EINVAL;
+	}
+
+	dev_dbg(&client->adapter->dev, "%s: secs=%d, mins=%d, hours=%d, "
+		"mday=%d, mon=%d, year=%d, wday=%d\n", __FUNCTION__,
+		dt->tm_sec, dt->tm_min, dt->tm_hour,
+		dt->tm_mday, dt->tm_mon, dt->tm_year, dt->tm_wday);
+
+	buf[0] = 0;		/* reg offset */
+	buf[1] = BIN_TO_BCD(dt->tm_sec);
+	buf[2] = BIN_TO_BCD(dt->tm_min);
+	buf[3] = BIN_TO_BCD(dt->tm_hour) | (1 << 6);
+	buf[4] = BIN_TO_BCD(dt->tm_wday) + 1;
+	buf[5] = BIN_TO_BCD(dt->tm_mday);
+	buf[6] = BIN_TO_BCD(dt->tm_mon);
+	if (dt->tm_year >= 2000) {
+		val = dt->tm_year - 2000;
+		buf[6] |= (1 << 7);
+	} else {
+		val = dt->tm_year - 1900;
+	}
+	buf[7] = BIN_TO_BCD(val);
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].len = sizeof(buf);
+	msg[0].buf = &buf[0];
+
+	result = client->adapter->algo->master_xfer(client->adapter,
+						    &msg[0], 1);
+	if (result < 0) {
+		dev_err(&client->adapter->dev, "ds1337[%d]: error "
+			"writing data! %d\n", data->id, result);
+		result = -EIO;
+	} else {
+		result = 0;
+	}
+
+	return result;
+}
+
+static int ds1337_command(struct i2c_client *client, unsigned int cmd,
+			  void *arg)
+{
+	dev_dbg(&client->adapter->dev, "%s: cmd=%d\n", __FUNCTION__, cmd);
+
+	switch (cmd) {
+	case DS1337_GET_DATE:
+		return ds1337_get_datetime(client, arg);
+
+	case DS1337_SET_DATE:
+		return ds1337_set_datetime(client, arg);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * Public API for access to specific device. Useful for low-level
+ * RTC access from kernel code.
+ */
+int ds1337_do_command(int id, int cmd, void *arg)
+{
+	struct list_head *walk;
+	struct list_head *tmp;
+	struct ds1337_data *data;
+
+	list_for_each_safe(walk, tmp, &ds1337_clients) {
+		data = list_entry(walk, struct ds1337_data, list);
+		if (data->id == id)
+			return ds1337_command(&data->client, cmd, arg);
+	}
+
+	return -ENODEV;
+}
+
+static int ds1337_attach_adapter(struct i2c_adapter *adapter)
+{
+	return i2c_detect(adapter, &addr_data, ds1337_detect);
+}
+
+/*
+ * The following function does more than just detection. If detection
+ * succeeds, it also registers the new chip.
+ */
+static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *new_client;
+	struct ds1337_data *data;
+	int err = 0;
+	const char *name = "";
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+				     I2C_FUNC_I2C))
+		goto exit;
+
+	if (!(data = kmalloc(sizeof(struct ds1337_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+	memset(data, 0, sizeof(struct ds1337_data));
+	INIT_LIST_HEAD(&data->list);
+
+	/* The common I2C client data is placed right before the
+	 * DS1337-specific data. 
+	 */
+	new_client = &data->client;
+	i2c_set_clientdata(new_client, data);
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &ds1337_driver;
+	new_client->flags = 0;
+
+	/*
+	 * Now we do the remaining detection. A negative kind means that
+	 * the driver was loaded with no force parameter (default), so we
+	 * must both detect and identify the chip. A zero kind means that
+	 * the driver was loaded with the force parameter, the detection
+	 * step shall be skipped. A positive kind means that the driver
+	 * was loaded with the force parameter and a given kind of chip is
+	 * requested, so both the detection and the identification steps
+	 * are skipped.
+	 *
+	 * For detection, we read registers that are most likely to cause
+	 * detection failure, i.e. those that have more bits with fixed
+	 * or reserved values.
+	 */
+
+	/* Default to an DS1337 if forced */
+	if (kind == 0)
+		kind = ds1337;
+
+	if (kind < 0) {		/* detection and identification */
+		u8 data;
+
+		/* Check that status register bits 6-2 are zero */
+		if ((ds1337_read(new_client, DS1337_REG_STATUS, &data) < 0) ||
+		    (data & 0x7c))
+			goto exit_free;
+
+		/* Check for a valid day register value */
+		if ((ds1337_read(new_client, DS1337_REG_DAY, &data) < 0) ||
+		    (data == 0) || (data & 0xf8))
+			goto exit_free;
+
+		/* Check for a valid date register value */
+		if ((ds1337_read(new_client, DS1337_REG_DATE, &data) < 0) ||
+		    (data == 0) || (data & 0xc0) || ((data & 0x0f) > 9) ||
+		    (data >= 0x32))
+			goto exit_free;
+
+		/* Check for a valid month register value */
+		if ((ds1337_read(new_client, DS1337_REG_MONTH, &data) < 0) ||
+		    (data == 0) || (data & 0x60) || ((data & 0x0f) > 9) ||
+		    ((data >= 0x13) && (data <= 0x19)))
+			goto exit_free;
+
+		/* Check that control register bits 6-5 are zero */
+		if ((ds1337_read(new_client, DS1337_REG_CONTROL, &data) < 0) ||
+		    (data & 0x60))
+			goto exit_free;
+
+		kind = ds1337;
+	}
+
+	if (kind == ds1337)
+		name = "ds1337";
+
+	/* We can fill in the remaining client fields */
+	strlcpy(new_client->name, name, I2C_NAME_SIZE);
+
+	/* Tell the I2C layer a new client has arrived */
+	if ((err = i2c_attach_client(new_client)))
+		goto exit_free;
+
+	/* Initialize the DS1337 chip */
+	ds1337_init_client(new_client);
+
+	/* Add client to local list */
+	data->id = ds1337_id++;
+	list_add(&data->list, &ds1337_clients);
+
+	return 0;
+
+exit_free:
+	kfree(data);
+exit:
+	return err;
+}
+
+static void ds1337_init_client(struct i2c_client *client)
+{
+	s32 val;
+
+	/* Ensure that device is set in 24-hour mode */
+	val = i2c_smbus_read_byte_data(client, DS1337_REG_HOUR);
+	if ((val >= 0) && (val & (1 << 6)) == 0)
+		i2c_smbus_write_byte_data(client, DS1337_REG_HOUR,
+					  val | (1 << 6));
+}
+
+static int ds1337_detach_client(struct i2c_client *client)
+{
+	int err;
+	struct ds1337_data *data = i2c_get_clientdata(client);
+
+	if ((err = i2c_detach_client(client))) {
+		dev_err(&client->dev, "Client deregistration failed, "
+			"client not detached.\n");
+		return err;
+	}
+
+	list_del(&data->list);
+	kfree(data);
+	return 0;
+}
+
+static int __init ds1337_init(void)
+{
+	return i2c_add_driver(&ds1337_driver);
+}
+
+static void __exit ds1337_exit(void)
+{
+	i2c_del_driver(&ds1337_driver);
+}
+
+MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
+MODULE_DESCRIPTION("DS1337 RTC driver");
+MODULE_LICENSE("GPL");
+
+module_init(ds1337_init);
+module_exit(ds1337_exit);
diff -Nru a/drivers/i2c/chips/it87.c b/drivers/i2c/chips/it87.c
--- a/drivers/i2c/chips/it87.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/it87.c	2005-03-20 16:43:51 -08:00
@@ -734,10 +734,9 @@
 			goto ERROR0;
 
 	/* Probe whether there is anything available on this address. Already
-	   done for SMBus clients */
+	   done for SMBus and Super-I/O clients */
 	if (kind < 0) {
-		if (is_isa) {
-
+		if (is_isa && !chip_type) {
 #define REALLY_SLOW_IO
 			/* We need the timeouts for at least some IT87-like chips. But only
 			   if we read 'undefined' registers. */
diff -Nru a/drivers/i2c/chips/lm90.c b/drivers/i2c/chips/lm90.c
--- a/drivers/i2c/chips/lm90.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/lm90.c	2005-03-20 16:43:51 -08:00
@@ -43,6 +43,14 @@
  * variants. The extra address and features of the MAX6659 are not
  * supported by this driver.
  *
+ * This driver also supports the ADT7461 chip from Analog Devices but
+ * only in its "compatability mode". If an ADT7461 chip is found but
+ * is configured in non-compatible mode (where its temperature
+ * register values are decoded differently) it is ignored by this
+ * driver. Complete datasheet can be obtained from Analog's website
+ * at:
+ *   http://products.analog.com/products/info.asp?product=ADT7461
+ *
  * Since the LM90 was the first chipset supported by this driver, most
  * comments will refer to this chipset, but are actually general and
  * concern all supported chipsets, unless mentioned otherwise.
@@ -77,6 +85,7 @@
  * LM86, LM89, LM90, LM99, ADM1032, MAX6657 and MAX6658 have address 0x4c.
  * LM89-1, and LM99-1 have address 0x4d.
  * MAX6659 can have address 0x4c, 0x4d or 0x4e (unsupported).
+ * ADT7461 always has address 0x4c.
  */
 
 static unsigned short normal_i2c[] = { 0x4c, 0x4d, I2C_CLIENT_END };
@@ -86,7 +95,7 @@
  * Insmod parameters
  */
 
-SENSORS_INSMOD_5(lm90, adm1032, lm99, lm86, max6657);
+SENSORS_INSMOD_6(lm90, adm1032, lm99, lm86, max6657, adt7461);
 
 /*
  * The LM90 registers
@@ -148,6 +157,19 @@
 #define HYST_TO_REG(val)	((val) <= 0 ? 0 : (val) >= 30500 ? 31 : \
 				 ((val) + 500) / 1000)
 
+/* 
+ * ADT7461 is almost identical to LM90 except that attempts to write
+ * values that are outside the range 0 < temp < 127 are treated as
+ * the boundary value. 
+ */
+
+#define TEMP1_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \
+				 (val) >= 127000 ? 127 : \
+				 ((val) + 500) / 1000)
+#define TEMP2_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \
+				 (val) >= 127750 ? 0x7FC0 : \
+				 ((val) + 125) / 250 * 64)
+
 /*
  * Functions declaration
  */
@@ -181,6 +203,7 @@
 	struct semaphore update_lock;
 	char valid; /* zero until following fields are valid */
 	unsigned long last_updated; /* in jiffies */
+	int kind;
 
 	/* registers values */
 	s8 temp_input1, temp_low1, temp_high1; /* local */
@@ -216,7 +239,10 @@
 	struct i2c_client *client = to_i2c_client(dev); \
 	struct lm90_data *data = i2c_get_clientdata(client); \
 	long val = simple_strtol(buf, NULL, 10); \
-	data->value = TEMP1_TO_REG(val); \
+	if (data->kind == adt7461) \
+		data->value = TEMP1_TO_REG_ADT7461(val); \
+	else \
+		data->value = TEMP1_TO_REG(val); \
 	i2c_smbus_write_byte_data(client, reg, data->value); \
 	return count; \
 }
@@ -227,7 +253,10 @@
 	struct i2c_client *client = to_i2c_client(dev); \
 	struct lm90_data *data = i2c_get_clientdata(client); \
 	long val = simple_strtol(buf, NULL, 10); \
-	data->value = TEMP2_TO_REG(val); \
+	if (data->kind == adt7461) \
+		data->value = TEMP2_TO_REG_ADT7461(val); \
+	else \
+		data->value = TEMP2_TO_REG(val); \
 	i2c_smbus_write_byte_data(client, regh, data->value >> 8); \
 	i2c_smbus_write_byte_data(client, regl, data->value & 0xff); \
 	return count; \
@@ -381,6 +410,12 @@
 			 && (reg_config1 & 0x3F) == 0x00
 			 && reg_convrate <= 0x0A) {
 				kind = adm1032;
+			} else
+			if (address == 0x4c
+			 && chip_id == 0x51 /* ADT7461 */
+			 && (reg_config1 & 0x1F) == 0x00 /* check compat mode */
+			 && reg_convrate <= 0x0A) {
+				kind = adt7461;
 			}
 		} else
 		if (man_id == 0x4D) { /* Maxim */
@@ -418,11 +453,14 @@
 		name = "lm86";
 	} else if (kind == max6657) {
 		name = "max6657";
+	} else if (kind == adt7461) {
+		name = "adt7461";
 	}
 
 	/* We can fill in the remaining client fields */
 	strlcpy(new_client->name, name, I2C_NAME_SIZE);
 	data->valid = 0;
+	data->kind = kind;
 	init_MUTEX(&data->update_lock);
 
 	/* Tell the I2C layer a new client has arrived */
diff -Nru a/drivers/i2c/chips/lm92.c b/drivers/i2c/chips/lm92.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/i2c/chips/lm92.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,423 @@
+/*
+ * lm92 - Hardware monitoring driver
+ * Copyright (C) 2005  Jean Delvare <khali@linux-fr.org>
+ *
+ * Based on the lm90 driver, with some ideas taken from the lm_sensors
+ * lm92 driver as well.
+ *
+ * The LM92 is a sensor chip made by National Semiconductor. It reports
+ * its own temperature with a 0.0625 deg resolution and a 0.33 deg
+ * accuracy. Complete datasheet can be obtained from National's website
+ * at:
+ *   http://www.national.com/pf/LM/LM92.html
+ *
+ * This driver also supports the MAX6635 sensor chip made by Maxim.
+ * This chip is compatible with the LM92, but has a lesser accuracy
+ * (1.0 deg). Complete datasheet can be obtained from Maxim's website
+ * at:
+ *   http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3074
+ *
+ * Since the LM92 was the first chipset supported by this driver, most
+ * comments will refer to this chipset, but are actually general and
+ * concern all supported chipsets, unless mentioned otherwise.
+ *
+ * Support could easily be added for the National Semiconductor LM76
+ * and Maxim MAX6633 and MAX6634 chips, which are mostly compatible
+ * with the LM92.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/i2c-sensor.h>
+
+
+/* The LM92 and MAX6635 have 2 two-state pins for address selection,
+   resulting in 4 possible addresses. */
+static unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b,
+				       I2C_CLIENT_END };
+static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
+
+/* Insmod parameters */
+SENSORS_INSMOD_1(lm92);
+
+/* The LM92 registers */
+#define LM92_REG_CONFIG			0x01 /* 8-bit, RW */
+#define LM92_REG_TEMP			0x00 /* 16-bit, RO */
+#define LM92_REG_TEMP_HYST		0x02 /* 16-bit, RW */
+#define LM92_REG_TEMP_CRIT		0x03 /* 16-bit, RW */
+#define LM92_REG_TEMP_LOW		0x04 /* 16-bit, RW */
+#define LM92_REG_TEMP_HIGH		0x05 /* 16-bit, RW */
+#define LM92_REG_MAN_ID			0x07 /* 16-bit, RO, LM92 only */
+
+/* The LM92 uses signed 13-bit values with LSB = 0.0625 degree Celsius,
+   left-justified in 16-bit registers. No rounding is done, with such
+   a resolution it's just not worth it. Note that the MAX6635 doesn't
+   make use of the 4 lower bits for limits (i.e. effective resolution
+   for limits is 1 degree Celsius). */
+static inline int TEMP_FROM_REG(s16 reg)
+{
+	return reg / 8 * 625 / 10;
+}
+
+static inline s16 TEMP_TO_REG(int val)
+{
+	if (val <= -60000)
+		return -60000 * 10 / 625 * 8;
+	if (val >= 160000)
+		return 160000 * 10 / 625 * 8;
+	return val * 10 / 625 * 8;
+}
+
+/* Alarm flags are stored in the 3 LSB of the temperature register */
+static inline u8 ALARMS_FROM_REG(s16 reg)
+{
+	return reg & 0x0007;
+}
+
+/* Driver data (common to all clients) */
+static struct i2c_driver lm92_driver;
+
+/* Client data (each client gets its own) */
+struct lm92_data {
+	struct i2c_client client;
+	struct semaphore update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* registers values */
+	s16 temp1_input, temp1_crit, temp1_min, temp1_max, temp1_hyst;
+};
+
+
+/*
+ * Sysfs attributes and callback functions
+ */
+
+static struct lm92_data *lm92_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct lm92_data *data = i2c_get_clientdata(client);
+
+	down(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + HZ)
+	 || !data->valid) {
+		dev_dbg(&client->dev, "Updating lm92 data\n");
+		data->temp1_input = swab16(i2c_smbus_read_word_data(client,
+				    LM92_REG_TEMP));
+		data->temp1_hyst = swab16(i2c_smbus_read_word_data(client,
+				    LM92_REG_TEMP_HYST));
+		data->temp1_crit = swab16(i2c_smbus_read_word_data(client,
+				    LM92_REG_TEMP_CRIT));
+		data->temp1_min = swab16(i2c_smbus_read_word_data(client,
+				    LM92_REG_TEMP_LOW));
+		data->temp1_max = swab16(i2c_smbus_read_word_data(client,
+				    LM92_REG_TEMP_HIGH));
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+
+	up(&data->update_lock);
+
+	return data;
+}
+
+#define show_temp(value) \
+static ssize_t show_##value(struct device *dev, char *buf) \
+{ \
+	struct lm92_data *data = lm92_update_device(dev); \
+	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->value)); \
+}
+show_temp(temp1_input);
+show_temp(temp1_crit);
+show_temp(temp1_min);
+show_temp(temp1_max);
+
+#define set_temp(value, reg) \
+static ssize_t set_##value(struct device *dev, const char *buf, \
+	size_t count) \
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct lm92_data *data = i2c_get_clientdata(client); \
+	long val = simple_strtol(buf, NULL, 10); \
+	data->value = TEMP_TO_REG(val); \
+	i2c_smbus_write_word_data(client, reg, swab16(data->value)); \
+	return count; \
+}
+set_temp(temp1_crit, LM92_REG_TEMP_CRIT);
+set_temp(temp1_min, LM92_REG_TEMP_LOW);
+set_temp(temp1_max, LM92_REG_TEMP_HIGH);
+
+static ssize_t show_temp1_crit_hyst(struct device *dev, char *buf)
+{
+	struct lm92_data *data = lm92_update_device(dev);
+	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp1_crit)
+		       - TEMP_FROM_REG(data->temp1_hyst));
+}
+static ssize_t show_temp1_max_hyst(struct device *dev, char *buf)
+{
+	struct lm92_data *data = lm92_update_device(dev);
+	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp1_max)
+		       - TEMP_FROM_REG(data->temp1_hyst));
+}
+static ssize_t show_temp1_min_hyst(struct device *dev, char *buf)
+{
+	struct lm92_data *data = lm92_update_device(dev);
+	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp1_min)
+		       + TEMP_FROM_REG(data->temp1_hyst));
+}
+
+static ssize_t set_temp1_crit_hyst(struct device *dev, const char *buf,
+	size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct lm92_data *data = i2c_get_clientdata(client);
+	data->temp1_hyst = TEMP_FROM_REG(data->temp1_crit) -
+			   simple_strtol(buf, NULL, 10);
+	i2c_smbus_write_word_data(client, LM92_REG_TEMP_HYST,
+				  swab16(TEMP_TO_REG(data->temp1_hyst)));
+	return count;
+}
+
+static ssize_t show_alarms(struct device *dev, char *buf)
+{
+	struct lm92_data *data = lm92_update_device(dev);
+	return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->temp1_input));
+}
+
+static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp1_input, NULL);
+static DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp1_crit,
+	set_temp1_crit);
+static DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp1_crit_hyst,
+	set_temp1_crit_hyst);
+static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp1_min,
+	set_temp1_min);
+static DEVICE_ATTR(temp1_min_hyst, S_IRUGO, show_temp1_min_hyst, NULL);
+static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp1_max,
+	set_temp1_max);
+static DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_temp1_max_hyst, NULL);
+static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+
+/*
+ * Detection and registration
+ */
+
+static void lm92_init_client(struct i2c_client *client)
+{
+	u8 config;
+
+	/* Start the conversions if needed */
+	config = i2c_smbus_read_byte_data(client, LM92_REG_CONFIG);
+	if (config & 0x01)
+		i2c_smbus_write_byte_data(client, LM92_REG_CONFIG,
+					  config & 0xFE);
+}
+
+/* The MAX6635 has no identification register, so we have to use tricks
+   to identify it reliably. This is somewhat slow.
+   Note that we do NOT rely on the 2 MSB of the configuration register
+   always reading 0, as suggested by the datasheet, because it was once
+   reported not to be true. */
+static int max6635_check(struct i2c_client *client)
+{
+	u16 temp_low, temp_high, temp_hyst, temp_crit;
+	u8 conf;
+	int i;
+
+	/* No manufacturer ID register, so a read from this address will
+	   always return the last read value. */
+	temp_low = i2c_smbus_read_word_data(client, LM92_REG_TEMP_LOW);
+	if (i2c_smbus_read_word_data(client, LM92_REG_MAN_ID) != temp_low)
+		return 0;
+	temp_high = i2c_smbus_read_word_data(client, LM92_REG_TEMP_HIGH);
+	if (i2c_smbus_read_word_data(client, LM92_REG_MAN_ID) != temp_high)
+		return 0;
+	
+	/* Limits are stored as integer values (signed, 9-bit). */
+	if ((temp_low & 0x7f00) || (temp_high & 0x7f00))
+		return 0;
+	temp_hyst = i2c_smbus_read_word_data(client, LM92_REG_TEMP_HYST);
+	temp_crit = i2c_smbus_read_word_data(client, LM92_REG_TEMP_CRIT);
+	if ((temp_hyst & 0x7f00) || (temp_crit & 0x7f00))
+		return 0;
+
+	/* Registers addresses were found to cycle over 16-byte boundaries.
+	   We don't test all registers with all offsets so as to save some
+	   reads and time, but this should still be sufficient to dismiss
+	   non-MAX6635 chips. */
+	conf = i2c_smbus_read_byte_data(client, LM92_REG_CONFIG);
+	for (i=16; i<96; i*=2) {
+		if (temp_hyst != i2c_smbus_read_word_data(client,
+		 		 LM92_REG_TEMP_HYST + i - 16)
+		 || temp_crit != i2c_smbus_read_word_data(client,
+		 		 LM92_REG_TEMP_CRIT + i)
+		 || temp_low != i2c_smbus_read_word_data(client,
+				LM92_REG_TEMP_LOW + i + 16)
+		 || temp_high != i2c_smbus_read_word_data(client,
+		 		 LM92_REG_TEMP_HIGH + i + 32)
+		 || conf != i2c_smbus_read_byte_data(client,
+		 	    LM92_REG_CONFIG + i))
+			return 0;
+	}
+
+	return 1;
+}
+
+/* The following function does more than just detection. If detection
+   succeeds, it also registers the new chip. */
+static int lm92_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *new_client;
+	struct lm92_data *data;
+	int err = 0;
+	char *name;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
+					    | I2C_FUNC_SMBUS_WORD_DATA))
+		goto exit;
+
+	if (!(data = kmalloc(sizeof(struct lm92_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+	memset(data, 0, sizeof(struct lm92_data));
+
+	/* Fill in enough client fields so that we can read from the chip,
+	   which is required for identication */
+	new_client = &data->client;
+	i2c_set_clientdata(new_client, data);
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &lm92_driver;
+	new_client->flags = 0;
+
+	/* A negative kind means that the driver was loaded with no force
+	   parameter (default), so we must identify the chip. */
+	if (kind < 0) {
+		u8 config = i2c_smbus_read_byte_data(new_client,
+			     LM92_REG_CONFIG);
+		u16 man_id = i2c_smbus_read_word_data(new_client,
+			     LM92_REG_MAN_ID);
+
+		if ((config & 0xe0) == 0x00
+		 && man_id == 0x0180) {
+			pr_info("lm92: Found National Semiconductor LM92 chip\n");
+	 		kind = lm92;
+		} else
+		if (max6635_check(new_client)) {
+			pr_info("lm92: Found Maxim MAX6635 chip\n");
+			kind = lm92; /* No separate prefix */
+		}
+		else
+			goto exit_free;
+	} else
+	if (kind == 0) /* Default to an LM92 if forced */
+		kind = lm92;
+
+	/* Give it the proper name */
+	if (kind == lm92) {
+		name = "lm92";
+	} else { /* Supposedly cannot happen */
+		dev_dbg(&new_client->dev, "Kind out of range?\n");
+		goto exit_free;
+	}
+
+	/* Fill in the remaining client fields */
+	strlcpy(new_client->name, name, I2C_NAME_SIZE);
+	data->valid = 0;
+	init_MUTEX(&data->update_lock);
+
+	/* Tell the i2c subsystem a new client has arrived */
+	if ((err = i2c_attach_client(new_client)))
+		goto exit_free;
+
+	/* Initialize the chipset */
+	lm92_init_client(new_client);
+
+	/* Register sysfs hooks */
+	device_create_file(&new_client->dev, &dev_attr_temp1_input);
+	device_create_file(&new_client->dev, &dev_attr_temp1_crit);
+	device_create_file(&new_client->dev, &dev_attr_temp1_crit_hyst);
+	device_create_file(&new_client->dev, &dev_attr_temp1_min);
+	device_create_file(&new_client->dev, &dev_attr_temp1_min_hyst);
+	device_create_file(&new_client->dev, &dev_attr_temp1_max);
+	device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
+	device_create_file(&new_client->dev, &dev_attr_alarms);
+
+	return 0;
+
+exit_free:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int lm92_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+		return 0;
+	return i2c_detect(adapter, &addr_data, lm92_detect);
+}
+
+static int lm92_detach_client(struct i2c_client *client)
+{
+	int err;
+
+	if ((err = i2c_detach_client(client))) {
+		dev_err(&client->dev, "Client deregistration failed, "
+			"client not detached.\n");
+		return err;
+	}
+
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+
+/*
+ * Module and driver stuff
+ */
+
+static struct i2c_driver lm92_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "lm92",
+	.id		= I2C_DRIVERID_LM92,
+	.flags		= I2C_DF_NOTIFY,
+	.attach_adapter	= lm92_attach_adapter,
+	.detach_client	= lm92_detach_client,
+};
+
+static int __init sensors_lm92_init(void)
+{
+	return i2c_add_driver(&lm92_driver);
+}
+
+static void __exit sensors_lm92_exit(void)
+{
+	i2c_del_driver(&lm92_driver);
+}
+
+MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
+MODULE_DESCRIPTION("LM92/MAX6635 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_lm92_init);
+module_exit(sensors_lm92_exit);
diff -Nru a/drivers/i2c/chips/m41t00.c b/drivers/i2c/chips/m41t00.c
--- a/drivers/i2c/chips/m41t00.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/m41t00.c	2005-03-20 16:43:51 -08:00
@@ -184,7 +184,6 @@
 
 	memset(client, 0, sizeof(struct i2c_client));
 	strncpy(client->name, M41T00_DRV_NAME, I2C_NAME_SIZE);
-	client->id = m41t00_driver.id;
 	client->flags = I2C_DF_NOTIFY;
 	client->addr = addr;
 	client->adapter = adap;
diff -Nru a/drivers/i2c/chips/w83627hf.c b/drivers/i2c/chips/w83627hf.c
--- a/drivers/i2c/chips/w83627hf.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/chips/w83627hf.c	2005-03-20 16:43:51 -08:00
@@ -304,7 +304,6 @@
 	u32 beep_mask;		/* Register encoding, combined */
 	u8 beep_enable;		/* Boolean */
 	u8 pwm[3];		/* Register value */
-	u8 pwmenable[3];	/* bool */
 	u16 sens[3];		/* 782D/783S only.
 				   1 = pentium diode; 2 = 3904 diode;
 				   3000-5000 = thermistor beta.
@@ -1316,10 +1315,6 @@
 		if ((type == w83697hf) && (i == 2))
 			break;
 	}
-
-	data->pwmenable[0] = 1;
-	data->pwmenable[1] = 1;
-	data->pwmenable[2] = 1;
 
 	if(init) {
 		/* Enable temp2 */
diff -Nru a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
--- a/drivers/i2c/i2c-core.c	2005-03-20 16:43:51 -08:00
+++ b/drivers/i2c/i2c-core.c	2005-03-20 16:43:51 -08:00
@@ -587,7 +587,13 @@
 	int ret;
 
 	if (adap->algo->master_xfer) {
- 	 	dev_dbg(&adap->dev, "master_xfer: with %d msgs.\n", num);
+#ifdef DEBUG
+		for (ret = 0; ret < num; ret++) {
+			dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
+				"len=%d\n", ret, msgs[ret].flags & I2C_M_RD ?
+				'R' : 'W', msgs[ret].addr, msgs[ret].len);
+		}
+#endif
 
 		down(&adap->bus_lock);
 		ret = adap->algo->master_xfer(adap,msgs,num);
diff -Nru a/drivers/superio/Kconfig b/drivers/superio/Kconfig
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/Kconfig	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,56 @@
+menu "SuperIO subsystem support"
+
+config SC_SUPERIO
+	tristate "SuperIO subsystem support"
+	depends on CONNECTOR
+	help
+	  SuperIO subsystem support.
+	
+	  This support is also available as a module.  If so, the module
+          will be called superio.ko.
+
+config SC_PC8736X
+	tristate "PC8736x SuperIO"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use PC8736x controller.
+	  It is LPC SuperIO with hardware monitoring chip from National Semiconductor.
+	
+	  This support is also available as a module.  If so, the module
+          will be called pc8736x.ko.
+
+config SC_SCX200
+	tristate "SCx200/SC1100 SuperIO"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use SCx200/SC1100 controller.
+	  It is Geode system-on-chip processor from AMD(formerly National Semiconductor).
+	
+	  This support is also available as a module.  If so, the module
+          will be called scx200.ko.
+
+
+config SC_GPIO
+	tristate "SuperIO - GPIO"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use General-Purpose Input/Output (GPIO) pins.
+	
+	  This support is also available as a module.  If so, the module
+          will be called sc_gpio.ko.
+
+config SC_ACB
+	tristate "SuperIO - Access Bus"
+	depends on SC_SUPERIO
+	help
+	  Say Y here if you want to use Access Bus.
+	  The ACB is a two-wire synchronous serial interface compatible with the ACCESS.bus physical layer. 
+	  The ACB is also compatible with Intel's SMBus and Philips' I2C. 
+	  The ACB allows easy interfacing to a wide range of low-cost memories and I/O devices, 
+	  including EEPROMs, SRAMs, timers, ADC, DAC, clock chips and peripheral drivers.
+	
+	  This support is also available as a module.  If so, the module
+          will be called sc_acb.ko.
+
+
+endmenu
diff -Nru a/drivers/superio/Makefile b/drivers/superio/Makefile
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/Makefile	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,11 @@
+#
+# Makefile for the SuperIO subsystem.
+#
+
+obj-$(CONFIG_SC_SUPERIO)	+= superio.o
+obj-$(CONFIG_SC_GPIO)		+= sc_gpio.o
+obj-$(CONFIG_SC_ACB)		+= sc_acb.o
+obj-$(CONFIG_SC_PC8736X)	+= pc8736x.o
+obj-$(CONFIG_SC_SCX200)		+= scx.o
+
+superio-objs		:= sc.o chain.o sc_conn.o
diff -Nru a/drivers/superio/chain.c b/drivers/superio/chain.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/chain.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,52 @@
+/*
+ * 	chain.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "chain.h"
+
+struct dev_chain *chain_alloc(void *ptr)
+{
+	struct dev_chain *ch;
+
+	ch = kmalloc(sizeof(struct dev_chain), GFP_ATOMIC);
+	if (!ch) {
+		printk(KERN_ERR "Failed to allocate new chain for %p.\n", ptr);
+		return NULL;
+	}
+
+	memset(ch, 0, sizeof(struct dev_chain));
+
+	ch->ptr = ptr;
+
+	return ch;
+}
+
+void chain_free(struct dev_chain *ch)
+{
+	memset(ch, 0, sizeof(struct dev_chain));
+	kfree(ch);
+}
diff -Nru a/drivers/superio/chain.h b/drivers/superio/chain.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/chain.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,37 @@
+/*
+ * 	chain.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __CHAIN_H
+#define __CHAIN_H
+
+#include <linux/list.h>
+
+struct dev_chain
+{
+	struct list_head	chain_entry;
+	void			*ptr;
+};
+
+struct dev_chain *chain_alloc(void *ptr);
+void chain_free(struct dev_chain *ch);
+
+#endif /* __CHAIN_H */
diff -Nru a/drivers/superio/pc8736x.c b/drivers/superio/pc8736x.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/pc8736x.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,209 @@
+/*
+ * 	pc8736x.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/suspend.h>
+
+#include "sc.h"
+#include "pc8736x.h"
+#include "sc_gpio.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for PC87366 SuperIO chip.");
+
+static int pc8736x_probe(void *, unsigned long base);
+static int pc8736x_activate_one_logical(struct logical_dev *ldev);
+static int pc8736x_deactivate_one_logical(struct logical_dev *ldev);
+
+static struct sc_dev pc8736x_dev = {
+	.name = 		"PC8736X",
+	.probe =		pc8736x_probe,
+	.activate_one =		pc8736x_activate_one_logical,
+	.deactivate_one = 	pc8736x_deactivate_one_logical,
+	//.read = 		pc8736x_read,
+	//.write = 		pc8736x_write,
+};
+
+static struct sc_chip_id pc8736x_sio_ids[] = {
+	{"PC87360", 0xe1},
+	{"PC87363", 0xe8},
+	{"PC87364", 0xe4},
+	{"PC87365", 0xe5},
+	{"PC87366", 0xe9},
+};
+
+void pc8736x_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+u8 pc8736x_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+static int pc8736x_chip_index(u8 id)
+{
+	int i;
+
+	for (i = 0; i < sizeof(pc8736x_sio_ids) / sizeof(pc8736x_sio_ids[0]); ++i)
+		if (pc8736x_sio_ids[i].id == id)
+			return i;
+
+	return -ENODEV;
+}
+
+static int pc8736x_probe(void *data, unsigned long base)
+{
+	unsigned long size = 2;
+	u8 id;
+	int chip_num;
+	struct sc_dev *dev = (struct sc_dev *)data;
+
+	/*
+	 * Special address to handle.
+	 */
+	if (base == 0)
+		return -ENODEV;
+
+	dev->base_index = base;
+	dev->base_data = base + 1;
+
+	id = pc8736x_read_reg(dev, SIO_REG_SID);
+	chip_num = pc8736x_chip_index(id);
+
+	if (chip_num >= 0) {
+		printk(KERN_INFO "Found %s [0x%x] at 0x%04lx-0x%04lx.\n",
+		       pc8736x_sio_ids[chip_num].name,
+		       pc8736x_sio_ids[chip_num].id, 
+		       base, base + size - 1);
+		return 0;
+	}
+
+	printk(KERN_INFO "Found nothing at 0x%04lx-0x%04lx.\n", 
+			base, base + size - 1);
+
+	return -ENODEV;
+}
+
+static int pc8736x_deactivate_one_logical(struct logical_dev *ldev)
+{
+	return 0;
+}
+
+static int pc8736x_activate_one_logical(struct logical_dev *ldev)
+{
+	int err;
+	struct sc_dev *dev = ldev->pdev;
+	u8 active;
+
+	pc8736x_write_reg(dev, SIO_REG_LDN, ldev->index);
+	active = pc8736x_read_reg(dev, SIO_REG_ACTIVE);
+	if ((active & SIO_ACTIVE_EN) == 0) {
+		printk(KERN_INFO "\t%16s - not activated at %x: activating... ",
+		       ldev->name, ldev->index);
+
+		pc8736x_write_reg(dev, SIO_REG_ACTIVE, active | SIO_ACTIVE_EN);
+		active = pc8736x_read_reg(dev, SIO_REG_ACTIVE);
+		if ((active & SIO_ACTIVE_EN) == 0) {
+			printk("failed.\n");
+			return -ENODEV;
+		}
+		printk("done\n");
+	}
+
+	pc8736x_write_reg(dev, SIO_REG_IRQ, ldev->irq);
+	ldev->irq = pc8736x_read_reg(dev, SIO_REG_IRQ);
+
+	ldev->irq_type = pc8736x_read_reg(dev, SIO_REG_IRQ_TYPE);
+	ldev->base_addr = pc8736x_read_reg(dev, SIO_REG_IO_LSB);
+	ldev->base_addr |= (pc8736x_read_reg(dev, SIO_REG_IO_MSB) << 8);
+
+	err = ldev->activate(ldev);
+	if (err < 0) {
+		printk(KERN_INFO "\t%16s - not activated: ->activate() failed with error code %d.\n",
+		       ldev->name, err);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "\t%16s - activated: 0x%04lx-0x%04lx, irq=%02x [type=%02x]\n",
+	       ldev->name, ldev->base_addr, ldev->base_addr + ldev->range,
+	       ldev->irq, ldev->irq_type);
+
+	return 0;
+}
+
+static int pc8736x_init(void)
+{
+	int err;
+
+	err = sc_add_sc_dev(&pc8736x_dev);
+	if (err)
+		return err;
+
+	printk(KERN_INFO "Driver for %s SuperIO chip.\n", pc8736x_dev.name);
+	return 0;
+}
+
+static void pc8736x_fini(void)
+{
+	sc_del_sc_dev(&pc8736x_dev);
+
+	while (atomic_read(&pc8736x_dev.refcnt)) {
+		printk(KERN_INFO "Waiting for %s to became free: refcnt=%d.\n",
+				pc8736x_dev.name, atomic_read(&pc8736x_dev.refcnt));
+
+		msleep_interruptible(1000);
+
+		if (signal_pending(current))
+			flush_signals(current);
+			
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
+	}
+}
+
+module_init(pc8736x_init);
+module_exit(pc8736x_fini);
+
+EXPORT_SYMBOL(pc8736x_write_reg);
+EXPORT_SYMBOL(pc8736x_read_reg);
diff -Nru a/drivers/superio/pc8736x.h b/drivers/superio/pc8736x.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/pc8736x.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,39 @@
+/*
+ * 	pc8736x.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __PC8736X_H
+#define __PC8736X_H
+
+#define SIO_GPDO0		0x00
+#define SIO_GPDI0 		0x01
+#define SIO_GPEVEN0		0x02 
+#define SIO_GPEVST0		0x03 
+#define SIO_GPDO1 		0x04
+#define SIO_GPDI1 		0x05
+#define SIO_GPEVEN1		0x06 
+#define SIO_GPEVST1		0x07 
+#define SIO_GPDO2 		0x08
+#define SIO_GPDI2 		0x09
+#define SIO_GPDO3 		0x0A
+#define SIO_GPDI3 		0x0B
+
+#endif /* __PC8736X_H */
diff -Nru a/drivers/superio/pin_test.c b/drivers/superio/pin_test.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/pin_test.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,93 @@
+/*
+ * 	pin_test.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * 
+ *
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "sc.h"
+#include "sc_gpio.h"
+
+MODULE_LICENSE ("GPL");
+
+static int test_pin = 21;
+module_param(test_pin, int, 0);
+
+static struct timer_list tm;
+static struct logical_dev *ldev;
+
+static void tm_func(unsigned long data)
+{
+	int i;
+	int val;
+
+	for (i=0; i<SIO_GPIO_NPINS; ++i)
+	{
+		val = ldev->read(ldev, i);
+		printk("%02d.%d ", i, val);
+		if (i % 8 == 7)
+			printk("\n");
+
+		if (i == test_pin)
+			ldev->write(ldev, i, (val)?0:1);
+	}
+	printk("\n");
+
+	mod_timer(&tm, jiffies + HZ);
+}
+
+int __devinit tm_init (void)
+{
+	int i;
+	
+	ldev = sc_get_ldev("GPIO");
+	if (!ldev)
+	{
+		printk(KERN_ERR "Logical device GPIO is not registered.\n");
+		return -ENODEV;
+	}
+	for (i=0; i<SIO_GPIO_NPINS; ++i)
+		ldev->control(ldev, i, ~0, SIO_GPIO_CONF_PUSHPULL);
+
+	init_timer(&tm);
+	tm.expires = jiffies + HZ;
+	tm.function = tm_func;
+	tm.data = 0;
+	add_timer(&tm);
+
+	return 0;
+}
+
+void __devexit tm_fini(void)
+{
+	del_timer_sync(&tm);
+	sc_put_ldev(ldev);
+}
+
+module_init(tm_init);
+module_exit(tm_fini);
diff -Nru a/drivers/superio/sc.c b/drivers/superio/sc.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,802 @@
+/*
+ * 	sc.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+
+#include "sc.h"
+#include <linux/sc_conn.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Generic SuperIO driver.");
+
+static unsigned long base_addr[] = { 0x2e, 0x4e };
+
+static spinlock_t sdev_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(sdev_list);
+
+static spinlock_t ldev_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(ldev_list);
+
+static int sc_activate_logical(struct sc_dev *, struct logical_dev *);
+static void sc_deactivate_logical(struct sc_dev *, struct logical_dev *);
+
+static int __devinit sc_init(void);
+static void __devexit sc_fini(void);
+
+static inline int sc_ldev_equal(struct logical_dev *l1, struct logical_dev *l2)
+{
+	int a, b;
+
+	a = b = 1;
+
+	a = (!strncmp(l1->name, l2->name, SC_NAME_LEN) && l1->index == l2->index);
+
+	if (sc_ldev_is_clone(l1) && sc_ldev_is_clone(l2))
+		b = (l1->base_addr == l2->base_addr);
+
+	return (a && b);
+}
+
+static inline int sc_ldev_equal_name(struct logical_dev *l1,
+				     struct logical_dev *l2)
+{
+	return (!strncmp(l1->name, l2->name, SC_NAME_LEN));
+}
+
+static inline int sc_sdev_equal(struct sc_dev *s1, struct sc_dev *s2)
+{
+	return (!strncmp(s1->name, s2->name, SC_NAME_LEN));
+}
+
+static void sc_del_sdev_from_ldev(struct sc_dev *sdev, struct logical_dev *ldev)
+{
+	struct sc_dev *__sdev;
+	struct dev_chain *ch, *n;
+
+	spin_lock(&ldev->chain_lock);
+
+	list_for_each_entry_safe(ch, n, &ldev->chain_list, chain_entry) {
+		__sdev = ch->ptr;
+
+		if (sc_sdev_equal(__sdev, sdev)) {
+			list_del(&ch->chain_entry);
+			chain_free(ch);
+			smp_mb__before_atomic_dec();
+			atomic_dec(&__sdev->refcnt);
+			smp_mb__after_atomic_dec();
+			break;
+		}
+	}
+
+	spin_unlock(&ldev->chain_lock);
+}
+
+static int __sc_add_logical_dev(struct sc_dev *dev, struct logical_dev *__ldev)
+{
+	int err;
+	struct logical_dev *ldev;
+	struct dev_chain *ch, *lch, *_ch;
+	int __found = 0;
+
+	err = 0;
+
+	list_for_each_entry(_ch, &dev->chain_list, chain_entry) {
+		ldev = _ch->ptr;
+
+		if (sc_ldev_equal(ldev, __ldev)) {
+			printk(KERN_INFO "Logical device %s already registered in SuperIO chip %s.\n",
+			       ldev->name, dev->name);
+			err++;
+			break;
+		}
+	}
+
+	if (err) {
+		err = -ENODEV;
+		goto err_out;
+	}
+
+	if (!sc_ldev_is_clone(__ldev)) {
+		struct sc_chip_id *cid;
+
+		/*
+		 * SuperIO core is registering logical device. 
+		 * SuperIO chip knows where it must live.
+		 * If logical device being registered lives at the different location
+		 * (for example when it was registered for all devices, 
+		 * but has address(index) corresponding to only one SuperIO chip) 
+		 * then we will register new logical device with the same name 
+		 * but with the different location(index).
+		 *
+		 * It is called clone.
+		 */
+
+		for (cid = dev->ldevs; cid && strlen(cid->name); ++cid) {
+			if (!strncmp(cid->name, __ldev->name, SC_NAME_LEN)
+			    && cid->id != __ldev->index) {
+				struct logical_dev *clone;
+
+				__found = 1;
+
+				printk(KERN_INFO "Logical device %s in chip %s lives at %x, but provided address %x.\n"
+				       "Registering new logical device %s in chip %s with address %x.\n",
+				       __ldev->name, dev->name, cid->id,
+				       __ldev->index, __ldev->name, dev->name,
+				       cid->id);
+
+				clone = sc_ldev_clone(__ldev);
+				if (!clone) {
+					err = -ENOMEM;
+					continue;
+				}
+
+				/*
+				 * If logical device provided 0xFF index, than it is mean that
+				 * SuperIO chip driver must handle this situation.
+				 * It is similar to the zero base address in SuperIO ->probe() function.
+				 */
+
+				clone->index = cid->id;
+
+				err = __sc_add_logical_dev(dev, clone);
+				if (err)
+					sc_ldev_unclone(clone);
+			}
+		}
+
+		if (__found)
+			return 0;
+	}
+
+	__ldev->pdev = dev;
+	err = sc_activate_logical(dev, __ldev);
+	if (err) {
+		printk(KERN_INFO "Logical device %s is not found in SuperIO chip %s.\n",
+		       __ldev->name, dev->name);
+		err = -EINVAL;
+		goto err_out;
+	}
+
+	ch = chain_alloc(dev);
+	if (!ch) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	lch = chain_alloc(__ldev);
+	if (!lch) {
+		err = -ENOMEM;
+		goto err_out_free_chain;
+	}
+
+	ch->ptr = dev;
+
+	spin_lock(&__ldev->chain_lock);
+	smp_mb__before_atomic_inc();
+	atomic_inc(&__ldev->refcnt);
+	smp_mb__after_atomic_inc();
+	list_add_tail(&ch->chain_entry, &__ldev->chain_list);
+	spin_unlock(&__ldev->chain_lock);
+
+	smp_mb__before_atomic_inc();
+	atomic_inc(&dev->refcnt);
+	smp_mb__after_atomic_inc();
+	list_add_tail(&lch->chain_entry, &dev->chain_list);
+
+	__found = 0;
+	spin_lock(&ldev_lock);
+	list_for_each_entry(ldev, &ldev_list, ldev_entry) {
+		if (sc_ldev_equal(ldev, __ldev)) {
+			__found = 1;
+			break;
+		}
+	}
+
+	if (!__found) {
+		list_add(&__ldev->ldev_entry, &ldev_list);
+	}
+
+	spin_unlock(&ldev_lock);
+
+	return 0;
+
+	chain_free(lch);
+err_out_free_chain:
+	chain_free(ch);
+err_out:
+
+	return err;
+}
+
+int sc_add_logical_dev(struct sc_dev *sdev, struct logical_dev *__ldev)
+{
+	struct sc_dev *dev;
+	int err, found = 0;
+
+	printk(KERN_INFO "Adding logical device %s [%x] [%s].\n",
+	       __ldev->name, __ldev->index,
+	       (sc_ldev_is_clone(__ldev)) ? "clone" : "not clone");
+
+	spin_lock_init(&__ldev->chain_lock);
+	INIT_LIST_HEAD(&__ldev->chain_list);
+
+	spin_lock_init(&__ldev->lock);
+
+	atomic_set(&__ldev->refcnt, 0);
+
+	if (sdev) {
+		spin_lock(&sdev->lock);
+		spin_lock(&sdev->chain_lock);
+		err = __sc_add_logical_dev(sdev, __ldev);
+		spin_unlock(&sdev->chain_lock);
+		spin_unlock(&sdev->lock);
+
+		if (!err)
+			found = 1;
+
+		goto finish;
+	}
+
+	spin_lock(&sdev_lock);
+	list_for_each_entry(dev, &sdev_list, sdev_entry) {
+		spin_lock(&dev->lock);
+		spin_lock(&dev->chain_lock);
+		err = __sc_add_logical_dev(dev, __ldev);
+		spin_unlock(&dev->chain_lock);
+		spin_unlock(&dev->lock);
+		if (!err)
+			found = 1;
+	}
+	spin_unlock(&sdev_lock);
+
+finish:
+
+	return (found) ? 0 : -ENODEV;
+}
+
+/*
+ * Must be called under ldev->chain_lock and ldev_lock held.
+ */
+static void __sc_del_logical_dev(struct sc_dev *dev, struct logical_dev *__ldev)
+{
+	struct dev_chain *ch, *n;
+	struct logical_dev *ldev;
+
+	spin_lock(&dev->chain_lock);
+	list_for_each_entry_safe(ch, n, &dev->chain_list, chain_entry) {
+		ldev = ch->ptr;
+
+		if (sc_ldev_equal(ldev, __ldev)) {
+			list_del(&ch->chain_entry);
+			smp_mb__before_atomic_dec();
+			atomic_dec(&ldev->refcnt);
+			smp_mb__after_atomic_dec();
+			chain_free(ch);
+
+			sc_deactivate_logical(dev, ldev);
+
+			break;
+		}
+	}
+	spin_unlock(&dev->chain_lock);
+}
+
+void sc_del_logical_dev(struct logical_dev *ldev)
+{
+	struct sc_dev *dev;
+	struct dev_chain *ch, *n;
+	struct logical_dev *ld, *ln;
+
+	spin_lock(&ldev->lock);
+
+	spin_lock(&ldev->chain_lock);
+	list_for_each_entry_safe(ch, n, &ldev->chain_list, chain_entry) {
+		dev = ch->ptr;
+
+		spin_lock(&dev->lock);
+		printk(KERN_INFO "Deactivating %s [%s/%s] from %s\n",
+		       ldev->name,
+		       (sc_ldev_is_clone(ldev)) ? "clone" : "not clone",
+		       (sc_ldev_cloned(ldev)) ? "cloned" : "not cloned",
+		       dev->name);
+		__sc_del_logical_dev(dev, ldev);
+
+		list_del(&ch->chain_entry);
+		smp_mb__before_atomic_dec();
+		atomic_dec(&dev->refcnt);
+		smp_mb__after_atomic_dec();
+		chain_free(ch);
+		spin_unlock(&dev->lock);
+	}
+	spin_unlock(&ldev->chain_lock);
+
+	if (sc_ldev_is_clone(ldev)) {
+		spin_unlock(&ldev->lock);
+		return;
+	}
+
+	spin_lock(&ldev_lock);
+	list_for_each_entry_safe(ld, ln, &ldev_list, ldev_entry) {
+		printk(KERN_INFO "Processing ldev %s [%s/%s] [%x]\n",
+		       ld->name,
+		       (sc_ldev_is_clone(ld)) ? "clone" : "not clone",
+		       (sc_ldev_cloned(ld)) ? "cloned" : "not cloned",
+		       ld->index);
+		if (sc_ldev_equal(ld, ldev)) {
+			list_del(&ld->ldev_entry);
+		} else if (sc_ldev_cloned(ldev)) {
+			/*
+			 * When logical device is clonned 
+			 * clone's chunks can point to the diferent device 
+			 * than origianl logical device's chunks.
+			 * Since we do not have backlink from the original device
+			 * to it's clones we must run through the whole ldev_list.
+			 */
+
+			if (sc_ldev_is_clone(ld) && sc_ldev_equal_name(ld, ldev)) {
+				list_del(&ld->ldev_entry);
+				sc_del_logical_dev(ld);
+				sc_ldev_unclone(ld);
+			}
+		}
+	}
+	spin_unlock(&ldev_lock);
+
+	spin_unlock(&ldev->lock);
+
+	while (atomic_read(&ldev->refcnt)) {
+		printk(KERN_INFO "Waiting logical device %s [%x] [%s] to become free: refcnt=%d.\n",
+		       ldev->name, ldev->index,
+		       (sc_ldev_is_clone(ldev)) ? "clone" : "not clone",
+		       atomic_read(&ldev->refcnt));
+
+		msleep_interruptible(1000);
+
+		if (signal_pending(current))
+			flush_signals(current);
+			
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+	}
+
+}
+
+static int sc_check_sc_dev(struct sc_dev *dev)
+{
+	if (!dev->activate_one) {
+		printk(KERN_ERR "SuperIO device %s does not have ->activate_one() method.\n",
+		       dev->name);
+		return -EINVAL;
+	}
+
+	if (!dev->probe) {
+		printk(KERN_ERR "SuperIO device %s does not have ->probe() method.\n",
+		       dev->name);
+		return -EINVAL;
+	}
+
+	if (!dev->ldevs)
+		printk(KERN_INFO "SuperIO device %s does not have logical device table.\n",
+		       dev->name);
+
+	return 0;
+}
+
+int sc_add_sc_dev(struct sc_dev *__sdev)
+{
+	int i, err;
+	struct sc_dev *sdev;
+
+	if (sc_check_sc_dev(__sdev))
+		return -EINVAL;
+
+	spin_lock_init(&__sdev->chain_lock);
+	INIT_LIST_HEAD(&__sdev->chain_list);
+
+	spin_lock_init(&__sdev->lock);
+
+	spin_lock(&sdev_lock);
+	list_for_each_entry(sdev, &sdev_list, sdev_entry) {
+		if (sc_sdev_equal(sdev, __sdev)) {
+			printk(KERN_INFO "Super IO chip %s already registered.\n",
+			       sdev->name);
+			spin_unlock(&sdev_lock);
+			return -EINVAL;
+		}
+	}
+
+	err = -ENODEV;
+	for (i = 0; i < sizeof(base_addr) / sizeof(base_addr[0]); ++i) {
+		err = __sdev->probe(__sdev, base_addr[i]);
+		if (!err)
+			break;
+	}
+
+	/*
+	 * Special case for non standard base location.
+	 */
+	if (i == sizeof(base_addr) / sizeof(base_addr[0]))
+		err = __sdev->probe(__sdev, 0);
+
+	if (!err) {
+		atomic_set(&__sdev->refcnt, 0);
+		list_add_tail(&__sdev->sdev_entry, &sdev_list);
+	}
+
+	spin_unlock(&sdev_lock);
+
+	return err;
+}
+
+void sc_del_sc_dev(struct sc_dev *__sdev)
+{
+	struct dev_chain *ch, *n;
+	struct logical_dev *ldev;
+	struct sc_dev *sdev, *sn;
+
+	spin_lock(&__sdev->lock);
+	spin_lock(&sdev_lock);
+	list_for_each_entry_safe(sdev, sn, &sdev_list, sdev_entry) {
+		if (sc_sdev_equal(sdev, __sdev)) {
+			list_del(&sdev->sdev_entry);
+			break;
+		}
+	}
+	spin_unlock(&sdev_lock);
+
+	spin_lock(&__sdev->chain_lock);
+	list_for_each_entry_safe(ch, n, &__sdev->chain_list, chain_entry) {
+		ldev = ch->ptr;
+
+		list_del(&ch->chain_entry);
+		smp_mb__before_atomic_dec();
+		atomic_dec(&ldev->refcnt);
+		smp_mb__after_atomic_dec();
+		chain_free(ch);
+
+		sc_deactivate_logical(__sdev, ldev);
+		sc_del_sdev_from_ldev(__sdev, ldev);
+	}
+	spin_unlock(&__sdev->chain_lock);
+	spin_unlock(&__sdev->lock);
+
+	while (atomic_read(&__sdev->refcnt)) {
+		printk(KERN_INFO "Waiting SuperIO chip %s to become free: refcnt=%d.\n",
+		       __sdev->name, atomic_read(&__sdev->refcnt));
+
+		msleep_interruptible(1000);
+
+		if (signal_pending(current))
+			flush_signals(current);
+			
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+	}
+}
+
+static void sc_deactivate_logical(struct sc_dev *dev, struct logical_dev *ldev)
+{
+	printk(KERN_INFO "Deactivating logical device %s in SuperIO chip %s... ",
+	       ldev->name, dev->name);
+	
+	if (ldev->irq)
+	{
+		free_irq(ldev->irq, ldev);
+		ldev->irq = 0;
+	}
+
+
+	if (dev->deactivate_one)
+		dev->deactivate_one(ldev);
+
+	printk("done.\n");
+}
+
+/*
+ * Must be called under sdev_lock held.
+ */
+static int sc_activate_logical(struct sc_dev *dev, struct logical_dev *ldev)
+{
+	int err;
+
+	printk(KERN_INFO "Activating logical device %s [%x].\n", ldev->name,
+	       ldev->index);
+
+	if (ldev->irq && !ldev->irq_handler)
+		ldev->irq = 0;
+
+	ldev->pdev = dev;
+	err = dev->activate_one(ldev);
+	if (err)
+		return err;
+	
+	if (ldev->irq)
+	{
+		err = request_irq(ldev->irq, ldev->irq_handler, SA_SHIRQ | SA_INTERRUPT, ldev->name, ldev);
+		if (err)
+		{
+			printk(KERN_ERR "Failed to request irq %d: err=%d. Disabling interrupt.\n", 
+					ldev->irq, err);
+			ldev->irq = 0;
+		}
+	}
+		
+
+	
+	return err;
+}
+
+struct sc_dev *sc_get_sdev(char *name)
+{
+	struct sc_dev *sdev;
+
+	spin_lock(&sdev_lock);
+	list_for_each_entry(sdev, &sdev_list, sdev_entry) {
+		if (!strcmp(name, sdev->name)) {
+			atomic_inc(&sdev->refcnt);
+			smp_mb__after_atomic_inc();
+			spin_unlock(&sdev_lock);
+			return sdev;
+		}
+	}
+	spin_unlock(&sdev_lock);
+
+	return NULL;
+}
+
+void sc_put_sdev(struct sc_dev *sdev)
+{
+	smp_mb__before_atomic_dec();
+	atomic_dec(&sdev->refcnt);
+	smp_mb__after_atomic_dec();
+}
+
+/*
+ * Get logical device which has given name and index.
+ */
+struct logical_dev *sc_get_ldev_index(char *name, u8 index)
+{
+	struct logical_dev *ldev;
+
+	spin_lock(&ldev_lock);
+	list_for_each_entry(ldev, &ldev_list, ldev_entry) {
+		if (!strcmp(name, ldev->name) && ldev->index == index) {
+			atomic_inc(&ldev->refcnt);
+			smp_mb__after_atomic_inc();
+			spin_unlock(&ldev_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&ldev_lock);
+
+	return NULL;
+}
+
+/*
+ * Get the first logical device with the given name.
+ */
+struct logical_dev *sc_get_ldev(char *name)
+{
+	struct logical_dev *ldev;
+
+	spin_lock(&ldev_lock);
+	list_for_each_entry(ldev, &ldev_list, ldev_entry) {
+		if (!strcmp(name, ldev->name)) {
+			atomic_inc(&ldev->refcnt);
+			smp_mb__after_atomic_inc();
+			spin_unlock(&ldev_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&ldev_lock);
+
+	return NULL;
+}
+
+/*
+ * Get the first logical device with the given name connected to given SuperIO chip.
+ */
+struct logical_dev *sc_get_ldev_in_sdev(char *name, struct sc_dev *sdev)
+{
+	struct dev_chain *ch;
+	struct logical_dev *ldev;
+
+	spin_lock(&sdev->chain_lock);
+	list_for_each_entry(ch, &sdev->chain_list, chain_entry) {
+		ldev = ch->ptr;
+
+		if (!strcmp(name, ldev->name)) {
+			atomic_inc(&ldev->refcnt);
+			smp_mb__after_atomic_inc();
+			spin_unlock(&sdev->chain_lock);
+			return ldev;
+		}
+	}
+	spin_unlock(&sdev->chain_lock);
+
+	return NULL;
+}
+
+void sc_put_ldev(struct logical_dev *ldev)
+{
+	smp_mb__before_atomic_dec();
+	atomic_dec(&ldev->refcnt);
+	smp_mb__after_atomic_dec();
+}
+
+/*
+ * Cloned logical device has the same structure as original device.
+ * Although cloned and original devices do not cross, they both point
+ * to the same block of the memory(they have pointers to the same functions), 
+ * so we will increment reference counter for original device 
+ * like cloned device has a reference to it.
+ */
+struct logical_dev *sc_ldev_clone(struct logical_dev *ldev)
+{
+	struct logical_dev *__ldev;
+
+	__ldev = sc_ldev_alloc(ldev->name, ldev->index);
+	if (!__ldev)
+		return NULL;
+
+	memcpy(__ldev, ldev, sizeof(*__ldev));
+
+	spin_lock_init(&__ldev->chain_lock);
+	INIT_LIST_HEAD(&__ldev->chain_list);
+	spin_lock_init(&__ldev->lock);
+
+	atomic_inc(&ldev->refcnt);
+	smp_mb__after_atomic_inc();
+	set_bit(LDEV_CLONED, (long *)&ldev->flags);
+
+	atomic_set(&__ldev->refcnt, 0);
+	__ldev->orig_ldev = ldev;
+
+	return __ldev;
+}
+
+int sc_ldev_is_clone(struct logical_dev *ldev)
+{
+	return (ldev->orig_ldev) ? 1 : 0;
+}
+
+int sc_ldev_cloned(struct logical_dev *ldev)
+{
+	return (test_bit(LDEV_CLONED, (long *)&ldev->flags)
+		&& (atomic_read(&ldev->refcnt) >= 1));
+}
+
+void sc_ldev_unclone(struct logical_dev *clone)
+{
+	struct logical_dev *orig = clone->orig_ldev;
+
+	if (!sc_ldev_is_clone(clone)) {
+		printk(KERN_INFO "Logical device %s is not clone.\n",
+		       clone->name);
+		return;
+	}
+
+	if (atomic_dec_and_test(&orig->refcnt))
+		clear_bit(LDEV_CLONED, (long *)&orig->flags);
+
+	memset(clone, 0, sizeof(*clone));
+	kfree(clone);
+	clone = NULL;
+}
+
+struct logical_dev *sc_ldev_alloc(char *name, u8 index)
+{
+	struct logical_dev *ldev;
+
+	ldev = kmalloc(sizeof(*ldev), GFP_ATOMIC);
+	if (!ldev) {
+		printk(KERN_ERR "Failed to allocate new logical device %s at address %x.\n",
+		       name, index);
+		return NULL;
+	}
+
+	memset(ldev, 0, sizeof(*ldev));
+
+	snprintf(ldev->name, sizeof(ldev->name), "%s", name);
+	ldev->index = index;
+
+	return ldev;
+}
+
+void sc_ldev_free(struct logical_dev *ldev)
+{
+	if (ldev->orig_ldev) {
+		struct logical_dev *orig = ldev->orig_ldev;
+		/*
+		 * It is clone.
+		 */
+		if (!atomic_dec_and_test(&ldev->refcnt)) {
+			/*
+			 * It is impossible, clone can not have clones.
+			 */
+			printk(KERN_INFO "Logical device clone %s has refcnt=%d and flags=%x.\n",
+			       ldev->name, atomic_read(&ldev->refcnt),
+			       ldev->flags);
+			BUG();
+		}
+
+		spin_lock(&orig->lock);
+
+		clear_bit(LDEV_CLONED, (long *)&orig->flags);
+		smp_mb__before_atomic_dec();
+		atomic_dec(&orig->refcnt);
+		smp_mb__after_atomic_dec();
+
+		memset(ldev, 0, sizeof(*ldev));
+		kfree(ldev);
+
+		spin_unlock(&orig->lock);
+	} else if (sc_ldev_cloned(ldev)) {
+		/*
+		 * It is cloned.
+		 */
+	}
+}
+
+static int __devinit sc_init(void)
+{
+	printk(KERN_INFO "SuperIO driver is starting...\n");
+
+	return sc_register_callback();
+}
+
+static void __devexit sc_fini(void)
+{
+	sc_unregister_callback();
+	printk(KERN_INFO "SuperIO driver finished.\n");
+}
+
+module_init(sc_init);
+module_exit(sc_fini);
+
+EXPORT_SYMBOL(sc_add_logical_dev);
+EXPORT_SYMBOL(sc_del_logical_dev);
+EXPORT_SYMBOL(sc_get_ldev);
+EXPORT_SYMBOL(sc_get_ldev_in_sdev);
+EXPORT_SYMBOL(sc_put_ldev);
+EXPORT_SYMBOL(sc_add_sc_dev);
+EXPORT_SYMBOL(sc_del_sc_dev);
+EXPORT_SYMBOL(sc_get_sdev);
+EXPORT_SYMBOL(sc_put_sdev);
+EXPORT_SYMBOL(sc_ldev_alloc);
+EXPORT_SYMBOL(sc_ldev_free);
+EXPORT_SYMBOL(sc_ldev_clone);
+EXPORT_SYMBOL(sc_ldev_unclone);
+EXPORT_SYMBOL(sc_ldev_cloned);
+EXPORT_SYMBOL(sc_ldev_is_clone);
diff -Nru a/drivers/superio/sc.h b/drivers/superio/sc.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,134 @@
+/*
+ * 	sc.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __SC_H
+#define __SC_H
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+
+#include "chain.h"
+
+#define SC_NAME_LEN		16
+
+#define SIO_REG_SID		0x20	/* Super I/O ID */
+
+#define SIO_REG_SRID		0x27	/* Super I/O Revision */
+#define SIO_REG_IRQ		0x70	/* IRQ number */
+#define SIO_REG_IRQ_TYPE	0x71	/* IRQ type */
+
+#define SIO_REG_LDN		0x07	/* Logical Device Number */
+#define SIO_LDN_GPIO		0x07	/* General-Purpose I/O (GPIO) Ports */
+#define SIO_LDN_ACB		0x08	/* Access bus */
+
+#define SIO_REG_ACTIVE		0x30	/* Logical Device Activate Register */
+#define SIO_ACTIVE_EN		0x01	/* enabled */
+#define SIO_RESET		0x02
+
+#define SIO_REG_IO_MSB		0x60	/* I/O Port Base, bits 15-8 */
+#define SIO_REG_IO_LSB		0x61	/* I/O Port Base, bits 7-0 */
+
+#define LDEV_PRIVATE		0xff	/* Logical device has non standard dynamic address (like PCI space) */
+
+#define LDEV_CLONED		(1<<0)
+
+struct logical_dev
+{
+	struct list_head	ldev_entry;
+
+	atomic_t		refcnt;
+	spinlock_t		lock;
+
+	struct list_head	chain_list;
+	spinlock_t		chain_lock;
+
+	unsigned char		name[SC_NAME_LEN];
+	u8			index;
+	
+	unsigned long		base_addr;
+	unsigned long		range;
+
+	u32			flags;
+
+	void			*pdev;
+	void			*orig_ldev;
+
+	u8			irq;
+	u8			irq_type;
+
+	int			(*activate)(void *);
+	u8			(*read)(void *, int);
+	void			(*write)(void *, int, u8);
+	void			(*control)(void *, int, u8, u8);
+	irqreturn_t		(*irq_handler)(int, void *, struct pt_regs *);
+};
+
+struct sc_dev
+{
+	struct list_head	sdev_entry;
+
+	atomic_t		refcnt;
+	spinlock_t		lock;
+	
+	struct list_head	chain_list;
+	spinlock_t		chain_lock;
+
+	unsigned char		name[SC_NAME_LEN];
+
+	void			*pdev;
+	unsigned long		base_index, base_data;
+
+	struct sc_chip_id	*ldevs;
+
+	int			(*probe)(void *, unsigned long);
+	int			(*activate_one)(struct logical_dev *);
+	int			(*deactivate_one)(struct logical_dev *);
+	u8			(*read)(struct logical_dev *, unsigned long);
+	void			(*write)(struct logical_dev *, unsigned long, u8);
+};
+
+struct sc_chip_id
+{
+	unsigned char		name[SC_NAME_LEN];
+	u8			id;
+};
+
+int sc_add_logical_dev(struct sc_dev *, struct logical_dev *);
+void sc_del_logical_dev(struct logical_dev *);
+struct logical_dev *sc_get_ldev(char *);
+struct logical_dev *sc_get_ldev_in_sdev(char *, struct sc_dev *);
+void sc_put_ldev(struct logical_dev *);
+
+int sc_add_sc_dev(struct sc_dev *);
+void sc_del_sc_dev(struct sc_dev *);
+struct sc_dev *sc_get_sdev(char *);
+void sc_put_sdev(struct sc_dev *);
+
+struct logical_dev *sc_ldev_clone(struct logical_dev *ldev);
+void sc_ldev_unclone(struct logical_dev *ldev);
+int sc_ldev_cloned(struct logical_dev *ldev);
+int sc_ldev_is_clone(struct logical_dev *ldev);
+
+struct logical_dev *sc_ldev_alloc(char *name, u8 index);
+void sc_ldev_free(struct logical_dev *ldev);
+
+#endif /* __SC_H */
diff -Nru a/drivers/superio/sc_acb.c b/drivers/superio/sc_acb.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc_acb.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,158 @@
+/*
+ * 	sc_acb.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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/module.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+
+#include "sc.h"
+#include "sc_acb.h"
+
+static int sc_acb_activate(void *data);
+static u8 sc_acb_read(void *data, int reg);
+static void sc_acb_write(void *data, int reg, u8 byte);
+static void sc_acb_control(void *data, int pin, u8 mask, u8 ctl);
+
+static struct logical_dev ldev_acb = {
+	.name = "ACB",
+	.index = 0x08,
+	.range = 16,
+
+	.activate = sc_acb_activate,
+	.read = sc_acb_read,
+	.write = sc_acb_write,
+	.control = sc_acb_control,
+
+	.flags = 0,
+	.orig_ldev = NULL,
+};
+
+static void sc_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+static u8 sc_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+static int sc_acb_activate(void *data)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	u8 val;
+
+	sc_write_reg(ldev->pdev, ACBCTL2, 1);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL2);
+	if ((val & 1) != 1) {
+		printk(KERN_ERR "Can not enable %s at %x: ctl2=%x.\n",
+		       ldev->name, ldev->index, val);
+		return -ENODEV;
+	}
+
+	sc_write_reg(ldev->pdev, ACBCTL2, 0x71);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL2);
+	if (val != 0x71) {
+		printk(KERN_ERR "ACBCTL2 readback failed: val=%x.\n", val);
+		return -ENXIO;
+	}
+
+	sc_write_reg(ldev->pdev, ACBCTL1,
+		     sc_read_reg(ldev->pdev, ACBCTL1) | ACBCTL1_NMINTE);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL1);
+	if (val) {
+		printk(KERN_ERR "Disabled, but ACBCTL1=0x%02x\n", val);
+		return -ENXIO;
+	}
+
+	sc_write_reg(ldev->pdev, ACBCTL2,
+		     sc_read_reg(ldev->pdev, ACBCTL2) | ACBCTL2_ENABLE);
+
+	sc_write_reg(ldev->pdev, ACBCTL1,
+		     sc_read_reg(ldev->pdev, ACBCTL1) | ACBCTL1_NMINTE);
+
+	val = sc_read_reg(ldev->pdev, ACBCTL1);
+	if ((val & ACBCTL1_NMINTE) != ACBCTL1_NMINTE) {
+		printk(KERN_ERR "Enabled, but NMINTE won't be set, ACBCTL1=0x%02x\n",
+		       val);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static u8 sc_acb_read(void *data, int reg)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	u8 val;
+
+	val = inb(ldev->base_addr + reg);
+
+	//printk("R: %02x\n", val);
+
+	return val;
+}
+
+static void sc_acb_write(void *data, int reg, u8 byte)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	//printk("W: %02x\n", val);
+
+	outb(byte, ldev->base_addr + reg);
+}
+
+static void sc_acb_control(void *data, int pin, u8 mask, u8 ctl)
+{
+}
+
+static int __devinit sc_acb_init(void)
+{
+	printk(KERN_INFO "Access Bus logical device driver is activating now.\n");
+	INIT_LIST_HEAD(&ldev_acb.ldev_entry);
+	spin_lock_init(&ldev_acb.lock);
+	return sc_add_logical_dev(NULL, &ldev_acb);
+}
+
+static void __devexit sc_acb_fini(void)
+{
+	sc_del_logical_dev(&ldev_acb);
+	printk(KERN_INFO "Access Bus logical device driver finished.\n");
+}
+
+module_init(sc_acb_init);
+module_exit(sc_acb_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for Access Bus logical device.");
diff -Nru a/drivers/superio/sc_acb.h b/drivers/superio/sc_acb.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc_acb.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,45 @@
+/*
+ * 	sc_acb.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __SC_ACB_H
+#define __SC_ACB_H
+
+#define ACBSDA			(ldev->base_addr + 0)
+#define ACBST			(ldev->base_addr + 1)
+#define ACBST_SDAST		0x40 /* SDA Status */
+#define ACBST_BER		0x20 
+#define ACBST_NEGACK		0x10 /* Negative Acknowledge */
+#define ACBST_STASTR		0x08 /* Stall After Start */
+#define ACBST_MASTER		0x02
+#define ACBCST			(ldev->base_addr + 2)
+#define ACBCST_BB		0x02
+#define ACBCTL1		(ldev->base_addr + 3)
+#define ACBCTL1_STASTRE	0x80
+#define ACBCTL1_NMINTE		0x40
+#define ACBCTL1_ACK		0x10
+#define ACBCTL1_STOP		0x02
+#define ACBCTL1_START		0x01
+#define ACBADDR		(ldev->base_addr + 4)
+#define ACBCTL2		(ldev->base_addr + 5)
+#define ACBCTL2_ENABLE		0x01
+
+#endif /* __SC_ACB_H */
diff -Nru a/drivers/superio/sc_conn.c b/drivers/superio/sc_conn.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc_conn.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,124 @@
+/*
+ * 	sc_conn.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/connector.h>
+
+#include "sc.h"
+#include <linux/sc_conn.h>
+
+static struct cb_id sc_conn_id = { CN_IDX_SUPERIO, CN_VAL_SUPERIO };
+
+static void sc_conn_callback(void *data)
+{
+	struct cn_msg *reply, *msg = (struct cn_msg *)data;
+	struct sc_conn_data *rcmd, *cmd = (struct sc_conn_data *)(msg + 1);
+	struct logical_dev *ldev;
+	struct sc_dev *sdev;
+	u8 ret;
+
+	if (msg->len != sizeof(*cmd)) {
+		printk(KERN_ERR "Wrong additional data size %u, must be %u.\n",
+		       msg->len, sizeof(*cmd));
+		return;
+	}
+#if 0
+	printk
+	    ("%s: len=%u, seq=%u, ack=%u, sname=%s, lname=%s, idx=0x%x, cmd=%02x [%02x.%02x.%02x].\n",
+	     __func__, msg->len, msg->seq, msg->ack, cmd->sname, cmd->lname,
+	     cmd->idx, cmd->cmd, cmd->p0, cmd->p1, cmd->p2);
+#endif
+	sdev = sc_get_sdev(cmd->sname);
+	if (!sdev) {
+		printk(KERN_ERR "%s: sdev %s does not exist.\n", 
+			__func__, cmd->sname);
+		return;
+	}
+
+	ldev = sc_get_ldev_in_sdev(cmd->lname, sdev);
+	if (!ldev) {
+		printk(KERN_ERR "%s: ldev %s does not exist in chip %s.\n",
+		       __func__, cmd->lname, cmd->sname);
+		sc_put_sdev(sdev);
+		return;
+	}
+
+	ret = 0;
+	switch (cmd->cmd) {
+	case SC_CMD_LDEV_READ:
+		ret = ldev->read(ldev, cmd->p0);
+		reply = kmalloc(sizeof(*msg) + sizeof(*cmd), GFP_ATOMIC);
+		if (reply) {
+			memcpy(reply, msg, sizeof(*reply));
+
+			/*
+			 * See protocol description in connector.c
+			 */
+			reply->ack++;
+
+			rcmd = (struct sc_conn_data *)(reply + 1);
+			memcpy(rcmd, cmd, sizeof(*rcmd));
+
+			rcmd->cmd = SC_CMD_LDEV_READ;
+			rcmd->p0 = cmd->p0;
+			rcmd->p1 = ret;
+
+			cn_netlink_send(reply, 0);
+
+			kfree(reply);
+		} else
+			printk(KERN_ERR "Failed to allocate %d bytes in reply to comamnd 0x%x.\n",
+			       sizeof(*msg) + sizeof(*cmd), cmd->cmd);
+		break;
+	case SC_CMD_LDEV_WRITE:
+		ldev->write(ldev, cmd->p0, cmd->p1);
+		break;
+	case SC_CMD_LDEV_CONTROL:
+		ldev->control(ldev, cmd->p0, cmd->p1, cmd->p2);
+		break;
+	case SC_CMD_LDEV_ACTIVATE:
+		ldev->activate(ldev);
+		break;
+	default:
+		printk(KERN_ERR "Unsupported command 0x%x for %s in chip %s.\n",
+		       cmd->cmd, ldev->name, sdev->name);
+		break;
+	}
+
+	sc_put_ldev(ldev);
+	sc_put_sdev(sdev);
+}
+
+int sc_register_callback(void)
+{
+	return cn_add_callback(&sc_conn_id, "sc_callback",
+			       (void (*)(void *))&sc_conn_callback);
+}
+
+void sc_unregister_callback(void)
+{
+	return cn_del_callback(&sc_conn_id);
+}
+
+EXPORT_SYMBOL(sc_register_callback);
diff -Nru a/drivers/superio/sc_gpio.c b/drivers/superio/sc_gpio.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc_gpio.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,312 @@
+/*
+ * 	sc_gpio.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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/module.h>
+#include <linux/string.h>
+
+#include <asm/io.h>
+
+#include "sc.h"
+#include "sc_gpio.h"
+#include "pc8736x.h"
+
+static struct gpio_pin gpin[SIO_GPIO_NPINS];
+
+static int sc_gpio_activate(void *);
+static u8 sc_gpio_read(void *, int);
+static void sc_gpio_write(void *, int, u8);
+static void sc_gpio_control(void *, int, u8, u8);
+static void sc_gpio_pin_select(void *, int);
+static irqreturn_t sc_gpio_interrupt(int, void *, struct pt_regs *);
+
+
+static struct logical_dev ldev_gpio = {
+	.name = "GPIO",
+	.index = SIO_LDN_GPIO,
+	.range = 16,
+
+	.activate 	= sc_gpio_activate,
+	.read 		= sc_gpio_read,
+	.write 		= sc_gpio_write,
+	.control 	= sc_gpio_control,
+	.irq_handler 	= sc_gpio_interrupt,
+
+	.irq		= 3,
+	
+	.flags = 0,
+	.orig_ldev = NULL,
+};
+
+static void sc_gpio_write_event(void *data, int pin_number, u8 byte);
+
+static void sc_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+static u8 sc_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+static irqreturn_t sc_gpio_interrupt(int irq, void *data, struct pt_regs * regs)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	static u8 r[4], e[2], s[2];
+	u8 cr[4], ce[2], cs[2];
+	int i;
+
+	for (i=0; i<2; ++i)
+	{	
+		ce[i] = inb(ldev->base_addr + i*4 + 2);
+		cs[i] = inb(ldev->base_addr + i*4 + 3);
+	}
+	
+	for (i=0; i<4; ++i)
+		cr[i] = inb(ldev->base_addr + i*4 + 1);
+
+	for (i=0; i<4; ++i)
+	{
+		u8 p = cr[i] ^ r[i];
+		u8 f;
+		int pin, val;
+		
+		if (!p)
+			continue;
+		
+		while((f = ffs(p)))
+		{
+			f = ffs(p);
+			
+			pin = f + i*8 - 1;
+			val = ((cr[i] >> (f-1)) & 1);
+			printk("pin=%2d, val=%1d, jiffies=%lu\n", 
+					pin, val, jiffies);
+
+			p &= ~(1<<(f-1));
+		}
+
+
+		/*
+		 * Clear status byte.
+		 * Spec does not say that each IRQ shuld be ACKed, 
+		 * but it should.
+		 *
+		 * This is probably those ACK.
+		 */
+		outb(0xff, ldev->base_addr + i*4 + 3);
+	}
+		
+	memcpy(r, cr, sizeof(r));
+	memcpy(s, cs, sizeof(s));
+	memcpy(e, ce, sizeof(e));
+	
+	return IRQ_HANDLED;
+}
+
+
+static void sc_gpio_pin_select(void *data, int pin_number)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val;
+
+	port = pin_number >> 3;
+	pin = pin_number - (pin_number & (~7));
+	val = (port << 4) | pin;
+
+	sc_write_reg(ldev->pdev, SIO_REG_LDN, SIO_LDN_GPIO);
+	sc_write_reg(ldev->pdev, SIO_GPIO_PINSEL, val);
+}
+
+static int sc_gpio_activate(void *data)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int i;
+
+	memset(gpin, 0, sizeof(gpin));
+
+	for (i = 0; i < SIO_GPIO_NPINS; ++i) {
+		gpin[i].flags = SIO_GPIO_CONF_PULLUP | SIO_GPIO_CONF_EVENT_LEVEL;
+		gpin[i].mask &= ~(SIO_GPIO_CONF_DEBOUNCE);
+
+		sc_gpio_control(ldev, i, gpin[i].mask, gpin[i].flags);
+
+		gpin[i].state = GPIO_PIN_HIGH;
+		sc_gpio_write(ldev, i, gpin[i].state);
+
+		sc_gpio_write_event(ldev, i, 1);
+	}
+	
+	outb(0xff, ldev->base_addr + SIO_GPEVEN0);
+	outb(0xff, ldev->base_addr + SIO_GPEVEN1);
+
+	return 0;
+}
+
+static u8 sc_gpio_read(void *data, int pin_number)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val, reg = SIO_GPDI0;
+
+	port = pin_number >> 3;
+	pin = pin_number - (pin_number & (~7));
+
+	switch (port) {
+	case 0:
+		reg = SIO_GPDI0;
+		break;
+	case 1:
+		reg = SIO_GPDI1;
+		break;
+	case 2:
+		reg = SIO_GPDI2;
+		break;
+	case 3:
+		reg = SIO_GPDI3;
+		break;
+	}
+
+	val = inb(ldev->base_addr + reg);
+
+	return ((val >> pin) & 0x01);
+}
+
+static void sc_gpio_write_event(void *data, int pin_number, u8 byte)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val, reg = SIO_GPEVEN0;
+
+	port = pin_number >> 3;
+	pin = pin_number - (pin_number & (~7));
+
+	switch (port) {
+		case 0:
+			reg = SIO_GPEVEN0;
+			break;
+		case 1:
+			reg = SIO_GPEVEN1;
+			break;
+		default:
+			return;
+	}
+
+	val = inb(ldev->base_addr + reg);
+
+	if (byte)
+		val |= (1 << pin);
+	else
+		val &= ~(1 << pin);
+
+	outb(val, ldev->base_addr + reg);
+	
+	outb(1<<pin, ldev->base_addr + reg+1);
+}
+
+static void sc_gpio_write(void *data, int pin_number, u8 byte)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	int port, pin;
+	u8 val, reg = SIO_GPDO0, rreg = SIO_GPDI0;
+
+	port = pin_number >> 3;
+	pin = pin_number - (pin_number & (~7));
+
+	switch (port) {
+	case 0:
+		reg = SIO_GPDO0;
+		rreg = SIO_GPDI0;
+		break;
+	case 1:
+		reg = SIO_GPDO1;
+		rreg = SIO_GPDI1;
+		break;
+	case 2:
+		reg = SIO_GPDO2;
+		rreg = SIO_GPDI2;
+		break;
+	case 3:
+		reg = SIO_GPDO3;
+		rreg = SIO_GPDI3;
+		break;
+	}
+
+	//val = inb(ldev->base_addr + reg);
+	val = inb(ldev->base_addr + rreg);
+
+	if (byte)
+		val |= (1 << pin);
+	else
+		val &= ~(1 << pin);
+
+	//printk("W: %02x [%d]\n", val, ((val>>pin)&1));
+
+	outb(val, ldev->base_addr + reg);
+}
+
+static void sc_gpio_control(void *data, int pin, u8 mask, u8 ctl)
+{
+	struct logical_dev *ldev = (struct logical_dev *)data;
+	u8 cfg, ev;
+
+	sc_gpio_pin_select(ldev, pin);
+
+	cfg = sc_read_reg(ldev->pdev, SIO_GPIO_PINCFG);
+	ev = sc_read_reg(ldev->pdev, SIO_GPIO_PINEV);
+
+	cfg &= mask;
+	cfg |= ctl;
+
+	printk(KERN_INFO "pin=%2d cfg=%02x, mask=%02x, ctl=%02x, event=%02x\n", 
+			pin, cfg, mask, ctl, ev);
+	
+	sc_write_reg(ldev->pdev, SIO_GPIO_PINCFG, cfg);
+}
+
+static int __devinit sc_gpio_init(void)
+{
+	printk(KERN_INFO "GPIO logical device driver is activating now.\n");
+	INIT_LIST_HEAD(&ldev_gpio.ldev_entry);
+	spin_lock_init(&ldev_gpio.lock);
+	return sc_add_logical_dev(NULL, &ldev_gpio);
+}
+
+static void __devexit sc_gpio_fini(void)
+{
+	sc_del_logical_dev(&ldev_gpio);
+	printk(KERN_INFO "GPIO logical device driver finished.\n");
+}
+
+module_init(sc_gpio_init);
+module_exit(sc_gpio_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for GPIO logical device.");
diff -Nru a/drivers/superio/sc_gpio.h b/drivers/superio/sc_gpio.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc_gpio.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,50 @@
+/*
+ * 	sc_gpio.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __SC_GPIO_H
+#define __SC_GPIO_H
+
+#define SIO_GPIO_PINSEL		0xf0
+#define SIO_GPIO_PINCFG		0xf1
+#define SIO_GPIO_PINEV		0xf2
+
+#define GPIO_PIN_LOW		0x00	/* low level (logical 0) */
+#define GPIO_PIN_HIGH		0x01	/* high level (logical 1) */
+
+#define SIO_GPIO_NPINS		29
+
+#define SIO_GPIO_CONF_OUTPUTEN		(1 << 0)
+#define SIO_GPIO_CONF_PUSHPULL		(1 << 1)
+#define SIO_GPIO_CONF_PULLUP		(1 << 2)
+#define SIO_GPIO_CONF_LOCK		(1 << 3)
+#define SIO_GPIO_CONF_EVENT_LEVEL	(1 << 4)
+#define SIO_GPIO_CONF_EVENT_POLAR_RIS	(1 << 5)
+#define SIO_GPIO_CONF_DEBOUNCE		(1 << 6)
+
+struct gpio_pin
+{
+	u8			state;
+	u8			flags;
+	u8			mask;
+};
+
+#endif /* __SC_GPIO_H */
diff -Nru a/drivers/superio/sc_w1.c b/drivers/superio/sc_w1.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/sc_w1.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,107 @@
+/*
+ * 	sc_w1.c
+ *
+ * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * 
+ *
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "../w1/w1.h"
+#include "../w1/w1_int.h"
+#include "../w1/w1_log.h"
+
+#include "../superio/sc.h"
+#include "../superio/sc_gpio.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for transport(Dallas 1-wire prtocol) over SuperIO GPIO pins.");
+
+static int pin_number = 21;	/* Use pin 21 by default */
+module_param(pin_number, int, 0);
+
+struct sc_w1_device {
+	struct logical_dev *ldev;
+	struct w1_bus_master bus_master;
+} sc_w1;
+
+static u8 sc_w1_read_bit(unsigned long data)
+{
+	struct sc_w1_device *swd = (struct sc_w1_device *)data;
+
+	//swd->ldev->control(swd->ldev, pin_number, ~(SIO_GPIO_CONF_PUSHPULL | SIO_GPIO_CONF_PULLUP), 0);
+
+	return swd->ldev->read(swd->ldev, pin_number);
+}
+
+static void sc_w1_write_bit(unsigned long data, u8 bit)
+{
+	struct sc_w1_device *swd = (struct sc_w1_device *)data;
+	u8 mask = SIO_GPIO_CONF_OUTPUTEN;
+
+	swd->ldev->control(swd->ldev, pin_number, (bit)?~mask:~0, SIO_GPIO_CONF_PULLUP);
+	swd->ldev->write(swd->ldev, pin_number, bit);
+}
+
+int __devinit sc_w1_init(void)
+{
+	int err;
+
+	sc_w1.ldev = sc_get_ldev("GPIO");
+	if (!sc_w1.ldev) {
+		printk(KERN_ERR "Logical device GPIO is not registered.\n");
+		return -ENODEV;
+	}
+
+	sc_w1.bus_master.data = (unsigned long)&sc_w1;
+	sc_w1.bus_master.read_bit = sc_w1_read_bit;
+	sc_w1.bus_master.write_bit = sc_w1_write_bit;
+
+	err = w1_add_master_device(&sc_w1.bus_master);
+	if (err) {
+		printk(KERN_ERR "Failed to register sc_w1 master device: err=%d.\n",
+		       err);
+		sc_put_ldev(sc_w1.ldev);
+		return err;
+	}
+
+	printk(KERN_INFO "sc_w1 transport driver has been loaded. Pin number %d.\n",
+	       pin_number);
+
+	return 0;
+}
+
+void __devexit sc_w1_fini(void)
+{
+	w1_remove_master_device(&sc_w1.bus_master);
+
+	sc_put_ldev(sc_w1.ldev);
+
+	printk(KERN_INFO "sc_w1 transport driver has been unloaded.\n");
+}
+
+module_init(sc_w1_init);
+module_exit(sc_w1_fini);
diff -Nru a/drivers/superio/scx.c b/drivers/superio/scx.c
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/scx.c	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,413 @@
+/*
+ * 	scx.c
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 <asm/atomic.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pci.h>
+#include <linux/suspend.h>
+
+#include "sc.h"
+#include "scx.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
+MODULE_DESCRIPTION("Driver for SCx200/SC1100 SuperIO chips.");
+
+static int scx200_probe(void *, unsigned long base);
+static int scx200_activate_one_logical(struct logical_dev *ldev);
+static int scx200_deactivate_one_logical(struct logical_dev *ldev);
+
+static struct sc_chip_id scx200_logical_devs[] = {
+	{"RTC", 0x00},
+	{"SWC", 0x01},
+	{"IRCP", 0x02},
+	{"ACB", 0x05},
+	{"ACB", 0x06},
+	{"SPORT", 0x08},
+	{"GPIO", LDEV_PRIVATE},
+	{}
+};
+
+static struct sc_dev scx200_dev = {
+	.name =			"SCx200",
+	.probe =		scx200_probe,
+	.ldevs = 		scx200_logical_devs,
+	.activate_one = 	scx200_activate_one_logical,
+	.deactivate_one =	scx200_deactivate_one_logical,
+};
+
+static struct sc_chip_id scx200_sio_ids[] = {
+	{"SCx200/SC1100", 0xF5},
+};
+
+static unsigned long private_base;
+
+static struct pci_device_id scx200_tbl[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SCx200_BRIDGE)},
+	{PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SC1100_BRIDGE)},
+	{},
+};
+
+MODULE_DEVICE_TABLE(pci, scx200_tbl);
+
+static int scx200_pci_probe(struct pci_dev *, const struct pci_device_id *);
+
+static struct pci_driver scx200_pci_driver = {
+	.name = "scx200",
+	.id_table = scx200_tbl,
+	.probe = scx200_pci_probe,
+};
+
+void scx200_write_reg(struct sc_dev *dev, u8 reg, u8 val)
+{
+	outb(reg, dev->base_index);
+	outb(val, dev->base_data);
+}
+
+u8 scx200_read_reg(struct sc_dev *dev, u8 reg)
+{
+	u8 val;
+
+	outb(reg, dev->base_index);
+	val = inb(dev->base_data);
+
+	return val;
+}
+
+static u8 scx200_gpio_read(void *data, int pin_number)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return 0;
+
+	bank = 0;
+	base = ldev->base_addr;
+
+	if (pin_number > 31) {
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	val = inl(base + 0x04);
+
+	return ((val >> pin_number) & 0x01);
+}
+
+static void scx200_gpio_write(void *data, int pin_number, u8 byte)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return;
+
+	bank = 0;
+	base = ldev->base_addr;
+
+	if (pin_number > 31) {
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	val = inl(base);
+
+	if (byte)
+		val |= (1 << pin_number);
+	else
+		val &= ~(1 << pin_number);
+
+	outl(val, base);
+}
+
+void scx200_gpio_control(void *data, int pin_number, u8 mask, u8 ctl)
+{
+	u32 val;
+	int bank;
+	unsigned long base;
+	struct logical_dev *ldev = (struct logical_dev *)data;
+
+	if (pin_number > 63 || pin_number < 0)
+		return;
+
+	bank = 0;
+	base = ldev->base_addr;
+
+	if (pin_number > 31) {
+		bank = 1;
+		base = ldev->base_addr + 0x10;
+		pin_number -= 32;
+	}
+
+	/*
+	 * Pin selection.
+	 */
+
+	val = 0;
+	val = ((bank & 0x01) << 5) | pin_number;
+	outl(val, ldev->base_addr + 0x20);
+
+	val = inl(ldev->base_addr + 0x24);
+
+	val &= 0x7f;
+	val &= mask;
+	val |= ctl;
+
+	outl(val, ldev->base_addr + 0x24);
+}
+
+int scx200_gpio_activate(void *data)
+{
+	return 0;
+}
+
+static int scx200_ldev_index_by_name(char *name)
+{
+	int i;
+
+	for (i = 0;
+	     i < sizeof(scx200_logical_devs) / sizeof(scx200_logical_devs[0]); ++i)
+		if (!strncmp(scx200_logical_devs[i].name, name, SC_NAME_LEN))
+			return i;
+
+	return -ENODEV;
+}
+
+static int scx200_chip_index(u8 id)
+{
+	int i;
+
+	for (i = 0; i < sizeof(scx200_sio_ids) / sizeof(scx200_sio_ids[0]); ++i)
+		if (scx200_sio_ids[i].id == id)
+			return i;
+
+	return -ENODEV;
+}
+
+static int scx200_probe(void *data, unsigned long base)
+{
+	unsigned long size = 2;
+	u8 id;
+	int chip_num;
+	struct sc_dev *dev = (struct sc_dev *)data;
+
+	/*
+	 * Special address to handle.
+	 */
+	if (base == 0) {
+		return scx200_probe(data, 0x015C);
+	}
+
+	dev->base_index = base;
+	dev->base_data = base + 1;
+
+	id = scx200_read_reg(dev, SIO_REG_SID);
+	chip_num = scx200_chip_index(id);
+
+	if (chip_num >= 0) {
+		printk(KERN_INFO "Found %s [0x%x] at 0x%04lx-0x%04lx.\n",
+		       scx200_sio_ids[chip_num].name,
+		       scx200_sio_ids[chip_num].id, base, base + size - 1);
+		return 0;
+	}
+
+	printk(KERN_INFO "Found nothing at 0x%04lx-0x%04lx.\n", 
+			base, base + size - 1);
+
+	return -ENODEV;
+}
+
+static int scx200_pci_probe(struct pci_dev *pdev,
+			    const struct pci_device_id *ent)
+{
+	private_base = pci_resource_start(pdev, 0);
+	printk(KERN_INFO "%s: GPIO base 0x%lx.\n", pci_name(pdev), private_base);
+
+	if (!request_region
+	    (private_base, SCx200_GPIO_SIZE, "NatSemi SCx200 GPIO")) {
+		printk(KERN_ERR "%s: failed to request %d bytes I/O region for GPIOs.\n",
+		       pci_name(pdev), SCx200_GPIO_SIZE);
+		return -EBUSY;
+	}
+
+	pci_set_drvdata(pdev, &private_base);
+	pci_enable_device(pdev);
+
+	return 0;
+}
+
+static int scx200_deactivate_one_logical(struct logical_dev *ldev)
+{
+	if (ldev->index != LDEV_PRIVATE)
+		return -ENODEV;
+
+	private_base -= 0x10;
+
+	return 0;
+}
+
+static int scx200_find_private_device(struct logical_dev *ldev)
+{
+	struct sc_dev *dev = (struct sc_dev *)ldev->pdev;
+
+	/*
+	 * SCx200/SC1100 has only GPIO in it's private space.
+	 */
+
+	if (strncmp(ldev->name, "GPIO", SC_NAME_LEN)) {
+		printk(KERN_ERR "Logical device %s at private space is not supported in chip %s.\n",
+		       ldev->name, dev->name);
+		return -ENODEV;
+	}
+
+	ldev->base_addr = private_base;
+	private_base += 0x10;
+
+	ldev->read = scx200_gpio_read;
+	ldev->write = scx200_gpio_write;
+	ldev->control = scx200_gpio_control;
+	ldev->activate = scx200_gpio_activate;
+
+	return 0;
+}
+
+static int scx200_activate_one_logical(struct logical_dev *ldev)
+{
+	int err, idx;
+	struct sc_dev *dev = ldev->pdev;
+	u8 active;
+
+	idx = scx200_ldev_index_by_name(ldev->name);
+	if (idx < 0) {
+		printk(KERN_INFO "Chip %s does not have logical device %s at %x.\n",
+		       dev->name, ldev->name, ldev->index);
+		return -ENODEV;
+	}
+
+	if (scx200_logical_devs[idx].id == LDEV_PRIVATE) {
+		err = scx200_find_private_device(ldev);
+		if (err)
+			return err;
+
+		printk(KERN_INFO "\t%16s - found at 0x%lx.\n", 
+				ldev->name, ldev->base_addr);
+	} else {
+		scx200_write_reg(dev, SIO_REG_LDN, ldev->index);
+		active = scx200_read_reg(dev, SIO_REG_ACTIVE);
+		if ((active & SIO_ACTIVE_EN) == 0) {
+			printk(KERN_INFO "\t%16s - not activated at %x: activating... ",
+			       ldev->name, ldev->index);
+
+			scx200_write_reg(dev, SIO_REG_ACTIVE,
+					 active | SIO_ACTIVE_EN);
+			active = scx200_read_reg(dev, SIO_REG_ACTIVE);
+			if ((active & SIO_ACTIVE_EN) == 0) {
+				printk("failed.\n");
+				return -ENODEV;
+			}
+			printk("done.\n");
+		}
+
+		ldev->base_addr = scx200_read_reg(dev, SIO_REG_IO_LSB);
+		ldev->base_addr |= (scx200_read_reg(dev, SIO_REG_IO_MSB) << 8);
+	}
+
+	err = ldev->activate(ldev);
+	if (err < 0) {
+		printk(KERN_INFO "\t%16s - not activated: ->activate() failed with error code %d.\n",
+				ldev->name, err);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "\t%16s - activated at %x: 0x%04lx-0x%04lx\n",
+	       ldev->name, ldev->index, ldev->base_addr,
+	       ldev->base_addr + ldev->range);
+
+	return 0;
+}
+
+static int scx200_init(void)
+{
+	int err;
+
+	err = pci_module_init(&scx200_pci_driver);
+	if (err) {
+		printk(KERN_ERR "Failed to register PCI driver for device %s : err=%d.\n",
+		       scx200_pci_driver.name, err);
+		return err;
+	}
+
+	err = sc_add_sc_dev(&scx200_dev);
+	if (err)
+		return err;
+
+	printk(KERN_INFO "Driver for %s SuperIO chip.\n", scx200_dev.name);
+	return 0;
+}
+
+static void scx200_fini(void)
+{
+	sc_del_sc_dev(&scx200_dev);
+
+	while (atomic_read(&scx200_dev.refcnt))
+	{
+		printk(KERN_INFO "Waiting for %s to became free: refcnt=%d.\n",
+				scx200_dev.name, atomic_read(&scx200_dev.refcnt));
+		
+		msleep_interruptible(1000);
+
+		if (signal_pending(current))
+			flush_signals(current);
+		
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+	}
+
+	pci_unregister_driver(&scx200_pci_driver);
+	if (private_base)
+		release_region(private_base, SCx200_GPIO_SIZE);
+}
+
+module_init(scx200_init);
+module_exit(scx200_fini);
+
+EXPORT_SYMBOL(scx200_write_reg);
+EXPORT_SYMBOL(scx200_read_reg);
diff -Nru a/drivers/superio/scx.h b/drivers/superio/scx.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/drivers/superio/scx.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,28 @@
+/*
+ * 	scx.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __SCX200_H
+#define __SCX200_H
+
+#define SCx200_GPIO_SIZE 	0x2c
+
+#endif /* __SCX200_H */
diff -Nru a/include/linux/connector.h b/include/linux/connector.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/include/linux/connector.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,150 @@
+/*
+ * 	connector.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __CONNECTOR_H
+#define __CONNECTOR_H
+
+#include <asm/types.h>
+
+#define CN_IDX_KOBJECT_UEVENT		0xabcd  /* Kobject's userspace events*/
+#define CN_VAL_KOBJECT_UEVENT		0x0000
+#define CN_IDX_SUPERIO			0xaabb  /* SuperIO subsystem */
+#define CN_VAL_SUPERIO			0xccdd
+
+
+#define CONNECTOR_MAX_MSG_SIZE 	1024
+
+struct cb_id
+{
+	__u32			idx;
+	__u32			val;
+};
+
+struct cn_msg
+{
+	struct cb_id 		id;
+
+	__u32			seq;
+	__u32			ack;
+
+	__u32			len;		/* Length of the following data */
+	__u8			data[0];
+};
+
+struct cn_notify_req
+{
+	__u32			first;
+	__u32			range;
+};
+
+struct cn_ctl_msg
+{
+	__u32			idx_notify_num;
+	__u32			val_notify_num;
+	__u32			group;
+	__u32			len;
+	__u8			data[0];
+};
+
+
+#ifdef __KERNEL__
+
+#include <asm/atomic.h>
+
+#include <linux/list.h>
+#include <linux/workqueue.h>
+
+#include <net/sock.h>
+
+#define CN_CBQ_NAMELEN		32
+
+struct cn_queue_dev
+{
+	atomic_t		refcnt;
+	unsigned char		name[CN_CBQ_NAMELEN];
+
+	struct workqueue_struct	*cn_queue;
+	
+	struct list_head 	queue_list;
+	spinlock_t 		queue_lock;
+
+	int			netlink_groups;
+	struct sock		*nls;
+};
+
+struct cn_callback
+{
+	unsigned char		name[CN_CBQ_NAMELEN];
+	
+	struct cb_id		id;
+	void			(* callback)(void *);
+	void			*priv;
+	
+	atomic_t		refcnt;
+};
+
+struct cn_callback_entry
+{
+	struct list_head	callback_entry;
+	struct cn_callback	*cb;
+	struct work_struct	work;
+	struct cn_queue_dev	*pdev;
+	
+	void			(* destruct_data)(void *);
+	void			*ddata;
+
+	int			seq, group;
+	struct sock		*nls;
+};
+
+struct cn_ctl_entry
+{
+	struct list_head	notify_entry;
+	struct cn_ctl_msg	*msg;
+};
+
+struct cn_dev
+{
+	struct cb_id 		id;
+
+	u32			seq, groups;
+	struct sock 		*nls;
+	void 			(*input)(struct sock *sk, int len);
+
+	struct cn_queue_dev	*cbdev;
+};
+
+extern int cn_already_initialized;
+
+int cn_add_callback(struct cb_id *, char *, void (* callback)(void *));
+void cn_del_callback(struct cb_id *);
+void cn_netlink_send(struct cn_msg *, u32);
+
+int cn_queue_add_callback(struct cn_queue_dev *dev, struct cn_callback *cb);
+void cn_queue_del_callback(struct cn_queue_dev *dev, struct cn_callback *cb);
+
+struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *);
+void cn_queue_free_dev(struct cn_queue_dev *dev);
+
+int cn_cb_equal(struct cb_id *, struct cb_id *);
+
+#endif /* __KERNEL__ */
+#endif /* __CONNECTOR_H */
diff -Nru a/include/linux/sc_conn.h b/include/linux/sc_conn.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/include/linux/sc_conn.h	2005-03-20 16:43:51 -08:00
@@ -0,0 +1,50 @@
+/*
+ * 	sc_conn.h
+ * 
+ * 2004 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ * 
+ * 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 __SC_CONN_H
+#define __SC_CONN_H
+
+enum sc_conn_cmd
+{
+	SC_CMD_LDEV_READ = 0,
+	SC_CMD_LDEV_WRITE,
+	SC_CMD_LDEV_CONTROL,
+	SC_CMD_LDEV_ACTIVATE,
+
+	__SC_CMD_MAX_CMD_NUMBER
+};
+
+struct sc_conn_data
+{
+	char		sname[16];
+	char		lname[16];
+	__u32		idx;
+	
+	__u8		cmd;
+	__u8		p0, p1, p2;
+
+	__u8		data[0];
+};
+
+int sc_register_callback(void);
+void sc_unregister_callback(void);
+
+#endif /* __SC_CONN_H */
diff -Nru a/lib/kobject_uevent.c b/lib/kobject_uevent.c
--- a/lib/kobject_uevent.c	2005-03-20 16:43:51 -08:00
+++ b/lib/kobject_uevent.c	2005-03-20 16:43:51 -08:00
@@ -12,6 +12,7 @@
  *	Kay Sievers		<kay.sievers@vrfy.org>
  *	Arjan van de Ven	<arjanv@redhat.com>
  *	Greg Kroah-Hartman	<greg@kroah.com>
+ *	Evgeniy Polyakov 	<johnpol@2ka.mipt.ru>
  */
 
 #include <linux/spinlock.h>
@@ -21,6 +22,7 @@
 #include <linux/string.h>
 #include <linux/kobject_uevent.h>
 #include <linux/kobject.h>
+#include <linux/connector.h>
 #include <net/sock.h>
 
 #define BUFFER_SIZE	1024	/* buffer for the hotplug env */
@@ -53,6 +55,70 @@
 #ifdef CONFIG_KOBJECT_UEVENT
 static struct sock *uevent_sock;
 
+#ifdef CONFIG_CONNECTOR
+static struct cb_id uid = {CN_IDX_KOBJECT_UEVENT, CN_VAL_KOBJECT_UEVENT};
+static void kobject_uevent_connector_callback(void *data)
+{
+}
+
+static void kobject_uevent_send_connector(const char *signal, const char *obj, char **envp, int gfp_mask)
+{
+	if (cn_already_initialized) {
+		int size;
+		struct cn_msg *msg;
+		static int uevent_connector_initialized;
+		
+		if (!uevent_connector_initialized) {
+			cn_add_callback(&uid, "kobject_uevent", kobject_uevent_connector_callback);
+			uevent_connector_initialized = 1;
+		}
+
+
+		size = strlen(signal) + strlen(obj) + 2 + BUFFER_SIZE + sizeof(*msg);
+		msg = kmalloc(size, gfp_mask);
+		if (msg) {
+			u8 *pos;
+			int len;
+
+			memset(msg, 0, size);
+
+			msg->len = size - sizeof(*msg);
+
+			memcpy(&msg->id, &uid, sizeof(msg->id));
+			
+			size -= sizeof(*msg);
+
+			pos = (u8 *)(msg + 1);
+			
+			len = snprintf(pos, size, "%s@%s", signal, obj);
+			len++;
+			size -= len;
+			pos += len;
+			
+			if (envp) {
+				int i;
+
+				for (i = 2; envp[i]; i++) {
+					len = strlen(envp[i]) + 1;
+					snprintf(pos, size, "%s", envp[i]);
+					size -= len;
+					pos += len;
+				}
+			}
+
+			cn_netlink_send(msg, 0);
+
+			kfree(msg);
+		}
+	}
+}
+#else
+static void kobject_uevent_send_connector(const char *signal, const char *obj, char **envp, int gfp_mask)
+{
+}
+#endif
+
+
 /**
  * send_uevent - notify userspace by sending event trough netlink socket
  *
@@ -92,6 +158,8 @@
 			strcpy(pos, envp[i]);
 		}
 	}
+
+	kobject_uevent_send_connector(signal, obj, envp, gfp_mask);
 
 	return netlink_broadcast(uevent_sock, skb, 0, 1, gfp_mask);
 }
