bk://kernel.bkbits.net/gregkh/linux/i2c-2.6 johnpol@2ka.mipt.ru[gregkh]|ChangeSet|20050315053127|30618 johnpol # This is a BitKeeper generated diff -Nru style patch. # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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 # Signed-off-by: Greg Kroah-Hartman # # 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-16 00:01:15 -08:00 +++ b/drivers/Kconfig 2005-03-16 00:01:15 -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-16 00:01:15 -08:00 +++ b/drivers/Makefile 2005-03-16 00:01:15 -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-16 00:01:15 -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-16 00:01:15 -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-16 00:01:15 -08:00 @@ -0,0 +1,231 @@ +/* + * cn_queue.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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-16 00:01:15 -08:00 @@ -0,0 +1,521 @@ +/* + * connector.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include +#include +#include +#include +#include + +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +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(¬ify_lock); + list_for_each_entry(ent, ¬ify_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; iidx_notify_num; ++i, ++req) { + if (id->idx >= req->first && id->idx < req->first + req->range) { + a = 1; + break; + } + } + + for (i=0; ival_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(¬ify_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; iidx_notify_num; ++i) { + if (memcmp(req1, req2, sizeof(*req1))) + return 0; + + req1++; + req2++; + } + + for (i=0; ival_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(¬ify_lock); + list_for_each_entry_safe(ent, n, ¬ify_list, notify_entry) { + if (cn_ctl_msg_equals(ent->msg, ctl)) { + list_del(&ent->notify_entry); + kfree(ent); + } + } + spin_unlock_bh(¬ify_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(¬ify_lock); + list_add(&ent->notify_entry, ¬ify_list); + spin_unlock_bh(¬ify_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; iidx_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; ival_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/superio/Kconfig b/drivers/superio/Kconfig --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/superio/Kconfig 2005-03-16 00:01:15 -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-16 00:01:15 -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-16 00:01:15 -08:00 @@ -0,0 +1,52 @@ +/* + * chain.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include + +#include +#include + +#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-16 00:01:15 -08:00 @@ -0,0 +1,37 @@ +/* + * chain.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 + +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-16 00:01:15 -08:00 @@ -0,0 +1,209 @@ +/* + * pc8736x.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sc.h" +#include "pc8736x.h" +#include "sc_gpio.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +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-16 00:01:15 -08:00 @@ -0,0 +1,39 @@ +/* + * pc8736x.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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-16 00:01:15 -08:00 @@ -0,0 +1,93 @@ +/* + * pin_test.c + * + * Copyright (c) 2004 Evgeniy Polyakov + * + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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; iread(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; icontrol(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-16 00:01:15 -08:00 @@ -0,0 +1,802 @@ +/* + * sc.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sc.h" +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +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-16 00:01:15 -08:00 @@ -0,0 +1,134 @@ +/* + * sc.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include + +#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-16 00:01:15 -08:00 @@ -0,0 +1,158 @@ +/* + * sc_acb.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include + +#include + +#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 "); +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-16 00:01:15 -08:00 @@ -0,0 +1,45 @@ +/* + * sc_acb.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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-16 00:01:15 -08:00 @@ -0,0 +1,124 @@ +/* + * sc_conn.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include + +#include + +#include "sc.h" +#include + +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-16 00:01:15 -08:00 @@ -0,0 +1,312 @@ +/* + * sc_gpio.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include + +#include + +#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<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 "); +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-16 00:01:15 -08:00 @@ -0,0 +1,50 @@ +/* + * sc_gpio.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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-16 00:01:15 -08:00 @@ -0,0 +1,107 @@ +/* + * sc_w1.c + * + * Copyright (c) 2004 Evgeniy Polyakov + * + * + * 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 +#include + +#include +#include +#include +#include +#include +#include + +#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 "); +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-16 00:01:15 -08:00 @@ -0,0 +1,413 @@ +/* + * scx.c + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sc.h" +#include "scx.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +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-16 00:01:15 -08:00 @@ -0,0 +1,28 @@ +/* + * scx.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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-16 00:01:15 -08:00 @@ -0,0 +1,150 @@ +/* + * connector.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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 + +#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 + +#include +#include + +#include + +#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-16 00:01:15 -08:00 @@ -0,0 +1,50 @@ +/* + * sc_conn.h + * + * 2004 Copyright (c) Evgeniy Polyakov + * 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-16 00:01:15 -08:00 +++ b/lib/kobject_uevent.c 2005-03-16 00:01:15 -08:00 @@ -12,6 +12,7 @@ * Kay Sievers * Arjan van de Ven * Greg Kroah-Hartman + * Evgeniy Polyakov */ #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #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); }