diff options
author | Denys Dmytriyenko <denis@denix.org> | 2009-03-17 14:32:59 -0400 |
---|---|---|
committer | Denys Dmytriyenko <denis@denix.org> | 2009-03-17 14:32:59 -0400 |
commit | 709c4d66e0b107ca606941b988bad717c0b45d9b (patch) | |
tree | 37ee08b1eb308f3b2b6426d5793545c38396b838 /recipes/linux/linux-mtx-2-2.4.27/17-lmsensors.2.8.8.patch | |
parent | fa6cd5a3b993f16c27de4ff82b42684516d433ba (diff) |
rename packages/ to recipes/ per earlier agreement
See links below for more details:
http://thread.gmane.org/gmane.comp.handhelds.openembedded/21326
http://thread.gmane.org/gmane.comp.handhelds.openembedded/21816
Signed-off-by: Denys Dmytriyenko <denis@denix.org>
Acked-by: Mike Westerhof <mwester@dls.net>
Acked-by: Philip Balister <philip@balister.org>
Acked-by: Khem Raj <raj.khem@gmail.com>
Acked-by: Marcin Juszkiewicz <hrw@openembedded.org>
Acked-by: Koen Kooi <koen@openembedded.org>
Acked-by: Frans Meulenbroeks <fransmeulenbroeks@gmail.com>
Diffstat (limited to 'recipes/linux/linux-mtx-2-2.4.27/17-lmsensors.2.8.8.patch')
-rw-r--r-- | recipes/linux/linux-mtx-2-2.4.27/17-lmsensors.2.8.8.patch | 40940 |
1 files changed, 40940 insertions, 0 deletions
diff --git a/recipes/linux/linux-mtx-2-2.4.27/17-lmsensors.2.8.8.patch b/recipes/linux/linux-mtx-2-2.4.27/17-lmsensors.2.8.8.patch new file mode 100644 index 0000000000..3b288e6351 --- /dev/null +++ b/recipes/linux/linux-mtx-2-2.4.27/17-lmsensors.2.8.8.patch @@ -0,0 +1,40940 @@ +--- linux-old/drivers/i2c/i2c-ali1535.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-ali1535.c Mon Dec 13 20:18:40 2004 +@@ -0,0 +1,601 @@ ++/* ++ i2c-ali1535.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2000 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ Mark D. Studebaker <mdsxyz123@yahoo.com>, ++ Dan Eaton <dan.eaton@rocketlogix.com> and ++ Stephen Rousset<stephen.rousset@rocketlogix.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ This is the driver for the SMB Host controller on ++ Acer Labs Inc. (ALI) M1535 South Bridge. ++ ++ The M1535 is a South bridge for portable systems. ++ It is very similar to the M15x3 South bridges also produced ++ by Acer Labs Inc. Some of the registers within the part ++ have moved and some have been redefined slightly. Additionally, ++ the sequencing of the SMBus transactions has been modified ++ to be more consistent with the sequencing recommended by ++ the manufacturer and observed through testing. These ++ changes are reflected in this driver and can be identified ++ by comparing this driver to the i2c-ali15x3 driver. ++ For an overview of these chips see http://www.acerlabs.com ++ ++ The SMB controller is part of the 7101 device, which is an ++ ACPI-compliant Power Management Unit (PMU). ++ ++ The whole 7101 device has to be enabled for the SMB to work. ++ You can't just enable the SMB alone. ++ The SMB and the ACPI have separate I/O spaces. ++ We make sure that the SMB is enabled. We leave the ACPI alone. ++ ++ This driver controls the SMB Host only. ++ ++ This driver does not use interrupts. ++*/ ++ ++ ++/* Note: we assume there can only be one ALI1535, with one SMBus interface */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/semaphore.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++ ++/* ALI1535 SMBus address offsets */ ++#define SMBHSTSTS (0 + ali1535_smba) ++#define SMBHSTTYP (1 + ali1535_smba) ++#define SMBHSTPORT (2 + ali1535_smba) ++#define SMBHSTCMD (7 + ali1535_smba) ++#define SMBHSTADD (3 + ali1535_smba) ++#define SMBHSTDAT0 (4 + ali1535_smba) ++#define SMBHSTDAT1 (5 + ali1535_smba) ++#define SMBBLKDAT (6 + ali1535_smba) ++ ++/* PCI Address Constants */ ++#define SMBCOM 0x004 ++#define SMBREV 0x008 ++#define SMBCFG 0x0D1 ++#define SMBBA 0x0E2 ++#define SMBHSTCFG 0x0F0 ++#define SMBCLK 0x0F2 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 /* times 1/100 sec */ ++#define ALI1535_SMB_IOSIZE 32 ++ ++/* ++*/ ++#define ALI1535_SMB_DEFAULTBASE 0x8040 ++ ++/* ALI1535 address lock bits */ ++#define ALI1535_LOCK 0x06 < dwe > ++ ++/* ALI1535 command constants */ ++#define ALI1535_QUICK 0x00 ++#define ALI1535_BYTE 0x10 ++#define ALI1535_BYTE_DATA 0x20 ++#define ALI1535_WORD_DATA 0x30 ++#define ALI1535_BLOCK_DATA 0x40 ++#define ALI1535_I2C_READ 0x60 ++ ++#define ALI1535_DEV10B_EN 0x80 /* Enable 10-bit addressing in */ ++ /* I2C read */ ++#define ALI1535_T_OUT 0x08 /* Time-out Command (write) */ ++#define ALI1535_A_HIGH_BIT9 0x08 /* Bit 9 of 10-bit address in */ ++ /* Alert-Response-Address */ ++ /* (read) */ ++#define ALI1535_KILL 0x04 /* Kill Command (write) */ ++#define ALI1535_A_HIGH_BIT8 0x04 /* Bit 8 of 10-bit address in */ ++ /* Alert-Response-Address */ ++ /* (read) */ ++ ++#define ALI1535_D_HI_MASK 0x03 /* Mask for isolating bits 9-8 */ ++ /* of 10-bit address in I2C */ ++ /* Read Command */ ++ ++/* ALI1535 status register bits */ ++#define ALI1535_STS_IDLE 0x04 ++#define ALI1535_STS_BUSY 0x08 /* host busy */ ++#define ALI1535_STS_DONE 0x10 /* transaction complete */ ++#define ALI1535_STS_DEV 0x20 /* device error */ ++#define ALI1535_STS_BUSERR 0x40 /* bus error */ ++#define ALI1535_STS_FAIL 0x80 /* failed bus transaction */ ++#define ALI1535_STS_ERR 0xE0 /* all the bad error bits */ ++ ++#define ALI1535_BLOCK_CLR 0x04 /* reset block data index */ ++ ++/* ALI1535 device address register bits */ ++#define ALI1535_RD_ADDR 0x01 /* Read/Write Bit in Device */ ++ /* Address field */ ++ /* -> Write = 0 */ ++ /* -> Read = 1 */ ++#define ALI1535_SMBIO_EN 0x04 /* SMB I/O Space enable */ ++ ++static int ali1535_transaction(void); ++ ++static unsigned short ali1535_smba = 0; ++DECLARE_MUTEX(i2c_ali1535_sem); ++ ++ ++/* Detect whether a ALI1535 can be found, and initialize it, where necessary. ++ Note the differences between kernels with the old PCI BIOS interface and ++ newer kernels with the real PCI interface. In compat.h some things are ++ defined to make the transition easier. */ ++int ali1535_setup(struct pci_dev *ALI1535_dev) ++{ ++ int error_return = 0; ++ unsigned char temp; ++ ++/* Check the following things: ++ - SMB I/O address is initialized ++ - Device is enabled ++ - We can use the addresses ++*/ ++ ++/* Determine the address of the SMBus area */ ++ pci_read_config_word(ALI1535_dev, SMBBA, &ali1535_smba); ++ ali1535_smba &= (0xffff & ~(ALI1535_SMB_IOSIZE - 1)); ++ if (ali1535_smba == 0) { ++ printk ++ ("i2c-ali1535.o: ALI1535_smb region uninitialized - upgrade BIOS?\n"); ++ error_return = -ENODEV; ++ } ++ ++ if (error_return == -ENODEV) ++ goto END; ++ ++ if (check_region(ali1535_smba, ALI1535_SMB_IOSIZE)) { ++ printk ++ ("i2c-ali1535.o: ALI1535_smb region 0x%x already in use!\n", ++ ali1535_smba); ++ error_return = -ENODEV; ++ } ++ ++ if (error_return == -ENODEV) ++ goto END; ++ ++ /* check if whole device is enabled */ ++ pci_read_config_byte(ALI1535_dev, SMBCFG, &temp); ++ if ((temp & ALI1535_SMBIO_EN) == 0) { ++ printk ++ ("i2c-ali1535.o: SMB device not enabled - upgrade BIOS?\n"); ++ error_return = -ENODEV; ++ goto END; ++ } ++ ++/* Is SMB Host controller enabled? */ ++ pci_read_config_byte(ALI1535_dev, SMBHSTCFG, &temp); ++ if ((temp & 1) == 0) { ++ printk ++ ("i2c-ali1535.o: SMBus controller not enabled - upgrade BIOS?\n"); ++ error_return = -ENODEV; ++ goto END; ++ } ++ ++/* set SMB clock to 74KHz as recommended in data sheet */ ++ pci_write_config_byte(ALI1535_dev, SMBCLK, 0x20); ++ ++ /* Everything is happy, let's grab the memory and set things up. */ ++ request_region(ali1535_smba, ALI1535_SMB_IOSIZE, "ali1535-smb"); ++ ++#ifdef DEBUG ++/* ++ The interrupt routing for SMB is set up in register 0x77 in the ++ 1533 ISA Bridge device, NOT in the 7101 device. ++ Don't bother with finding the 1533 device and reading the register. ++ if ((....... & 0x0F) == 1) ++ printk("i2c-ali1535.o: ALI1535 using Interrupt 9 for SMBus.\n"); ++*/ ++ pci_read_config_byte(ALI1535_dev, SMBREV, &temp); ++ printk("i2c-ali1535.o: SMBREV = 0x%X\n", temp); ++ printk("i2c-ali1535.o: ALI1535_smba = 0x%X\n", ali1535_smba); ++#endif /* DEBUG */ ++ ++ END: ++ return error_return; ++} ++ ++ ++/* Another internally used function */ ++int ali1535_transaction(void) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++#ifdef DEBUG ++ printk ++ ("i2c-ali1535.o: Transaction (pre): STS=%02x, TYP=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, " ++ "DAT1=%02x\n", inb_p(SMBHSTSTS), inb_p(SMBHSTTYP), ++ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++#endif ++ ++ /* get status */ ++ temp = inb_p(SMBHSTSTS); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ /* Check the busy bit first */ ++ if (temp & ALI1535_STS_BUSY) { ++/* ++ If the host controller is still busy, it may have timed out in the previous transaction, ++ resulting in a "SMBus Timeout" printk. ++ I've tried the following to reset a stuck busy bit. ++ 1. Reset the controller with an KILL command. ++ (this doesn't seem to clear the controller if an external device is hung) ++ 2. Reset the controller and the other SMBus devices with a T_OUT command. ++ (this clears the host busy bit if an external device is hung, ++ but it comes back upon a new access to a device) ++ 3. Disable and reenable the controller in SMBHSTCFG ++ Worst case, nothing seems to work except power reset. ++*/ ++/* Abort - reset the host controller */ ++/* ++#ifdef DEBUG ++ printk("i2c-ali1535.o: Resetting host controller to clear busy condition\n",temp); ++#endif ++ outb_p(ALI1535_KILL, SMBHSTTYP); ++ temp = inb_p(SMBHSTSTS); ++ if (temp & ALI1535_STS_BUSY) { ++*/ ++ ++/* ++ Try resetting entire SMB bus, including other devices - ++ This may not work either - it clears the BUSY bit but ++ then the BUSY bit may come back on when you try and use the chip again. ++ If that's the case you are stuck. ++*/ ++ printk ++ ("i2c-ali1535.o: Resetting entire SMB Bus to clear busy condition (%02x)\n", ++ temp); ++ outb_p(ALI1535_T_OUT, SMBHSTTYP); ++ temp = inb_p(SMBHSTSTS); ++ } ++/* ++ } ++*/ ++ ++ /* now check the error bits and the busy bit */ ++ if (temp & (ALI1535_STS_ERR | ALI1535_STS_BUSY)) { ++ /* do a clear-on-write */ ++ outb_p(0xFF, SMBHSTSTS); ++ if ((temp = inb_p(SMBHSTSTS)) & ++ (ALI1535_STS_ERR | ALI1535_STS_BUSY)) { ++ /* this is probably going to be correctable only by a power reset ++ as one of the bits now appears to be stuck */ ++ /* This may be a bus or device with electrical problems. */ ++ printk ++ ("i2c-ali1535.o: SMBus reset failed! (0x%02x) - controller or device on bus is probably hung\n", ++ temp); ++ return -1; ++ } ++ } else { ++ /* check and clear done bit */ ++ if (temp & ALI1535_STS_DONE) { ++ outb_p(temp, SMBHSTSTS); ++ } ++ } ++ ++ /* start the transaction by writing anything to the start register */ ++ outb_p(0xFF, SMBHSTPORT); ++ ++ /* We will always wait for a fraction of a second! */ ++ timeout = 0; ++ do { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } while (((temp & ALI1535_STS_BUSY) && !(temp & ALI1535_STS_IDLE)) ++ && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ result = -1; ++ printk("i2c-ali1535.o: SMBus Timeout!\n"); ++ } ++ ++ if (temp & ALI1535_STS_FAIL) { ++ result = -1; ++#ifdef DEBUG ++ printk("i2c-ali1535.o: Error: Failed bus transaction\n"); ++#endif ++ } ++ ++/* ++ Unfortunately the ALI SMB controller maps "no response" and "bus collision" ++ into a single bit. No reponse is the usual case so don't do a printk. ++ This means that bus collisions go unreported. ++*/ ++ if (temp & ALI1535_STS_BUSERR) { ++ result = -1; ++#ifdef DEBUG ++ printk ++ ("i2c-ali1535.o: Error: no response or bus collision ADD=%02x\n", ++ inb_p(SMBHSTADD)); ++#endif ++ } ++ ++/* haven't ever seen this */ ++ if (temp & ALI1535_STS_DEV) { ++ result = -1; ++ printk("i2c-ali1535.o: Error: device error\n"); ++ } ++ ++/* ++ check to see if the "command complete" indication is set ++ */ ++ if (!(temp & ALI1535_STS_DONE)) { ++ result = -1; ++ printk("i2c-ali1535.o: Error: command never completed\n"); ++ } ++#ifdef DEBUG ++ printk ++ ("i2c-ali1535.o: Transaction (post): STS=%02x, TYP=%02x, CMD=%02x, ADD=%02x, " ++ "DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTSTS), inb_p(SMBHSTTYP), ++ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++#endif ++ ++/* ++ take consequent actions for error conditions ++ */ ++ if (!(temp & ALI1535_STS_DONE)) { ++ /* issue "kill" to reset host controller */ ++ outb_p(ALI1535_KILL,SMBHSTTYP); ++ outb_p(0xFF,SMBHSTSTS); ++ } ++ else if (temp & ALI1535_STS_ERR) { ++ /* issue "timeout" to reset all devices on bus */ ++ outb_p(ALI1535_T_OUT,SMBHSTTYP); ++ outb_p(0xFF,SMBHSTSTS); ++ } ++ ++ return result; ++} ++ ++/* Return -1 on error. */ ++s32 ali1535_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, u8 command, ++ int size, union i2c_smbus_data * data) ++{ ++ int i, len; ++ int temp; ++ int timeout; ++ s32 result = 0; ++ ++ down(&i2c_ali1535_sem); ++/* make sure SMBus is idle */ ++ temp = inb_p(SMBHSTSTS); ++ for (timeout = 0; ++ (timeout < MAX_TIMEOUT) && !(temp & ALI1535_STS_IDLE); ++ timeout++) { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } ++ if (timeout >= MAX_TIMEOUT) { ++ printk("i2c-ali1535.o: Idle wait Timeout! STS=0x%02x\n", ++ temp); ++ } ++ ++/* clear status register (clear-on-write) */ ++ outb_p(0xFF, SMBHSTSTS); ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = ALI1535_QUICK; ++ outb_p(size, SMBHSTTYP); /* output command */ ++ break; ++ case I2C_SMBUS_BYTE: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = ALI1535_BYTE; ++ outb_p(size, SMBHSTTYP); /* output command */ ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMBHSTCMD); ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = ALI1535_BYTE_DATA; ++ outb_p(size, SMBHSTTYP); /* output command */ ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, SMBHSTDAT0); ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = ALI1535_WORD_DATA; ++ outb_p(size, SMBHSTTYP); /* output command */ ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT0); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); ++ } ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = ALI1535_BLOCK_DATA; ++ outb_p(size, SMBHSTTYP); /* output command */ ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 0) { ++ len = 0; ++ data->block[0] = len; ++ } ++ if (len > 32) { ++ len = 32; ++ data->block[0] = len; ++ } ++ outb_p(len, SMBHSTDAT0); ++ outb_p(inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR, SMBHSTTYP); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= len; i++) ++ outb_p(data->block[i], SMBBLKDAT); ++ } ++ break; ++ default: ++ printk ++ (KERN_WARNING "i2c-ali1535.o: Unsupported transaction %d\n", size); ++ result = -1; ++ goto EXIT; ++ } ++ ++ if (ali1535_transaction()) /* Error in transaction */ ++ { ++ result = -1; ++ goto EXIT; ++ } ++ ++ if ((read_write == I2C_SMBUS_WRITE) || (size == ALI1535_QUICK)) ++ { ++ result = 0; ++ goto EXIT; ++ } ++ ++ switch (size) { ++ case ALI1535_BYTE: /* Result put in SMBHSTDAT0 */ ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case ALI1535_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case ALI1535_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); ++ break; ++ case ALI1535_BLOCK_DATA: ++ len = inb_p(SMBHSTDAT0); ++ if (len > 32) ++ len = 32; ++ data->block[0] = len; ++ outb_p(inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR, SMBHSTTYP); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= data->block[0]; i++) { ++ data->block[i] = inb_p(SMBBLKDAT); ++#ifdef DEBUG ++ printk ++ ("i2c-ali1535.o: Blk: len=%d, i=%d, data=%02x\n", ++ len, i, data->block[i]); ++#endif /* DEBUG */ ++ } ++ break; ++ } ++EXIT: ++ up(&i2c_ali1535_sem); ++ return result; ++} ++ ++ ++u32 ali1535_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-i2c SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = ali1535_access, ++ .functionality = ali1535_func, ++}; ++ ++static struct i2c_adapter ali1535_adapter = { ++ .owner = THIS_MODULE, ++ .name = "unset", ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_ALI1535, ++ .algo = &smbus_algorithm, ++}; ++ ++ ++static struct pci_device_id ali1535_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_AL, ++ .device = PCI_DEVICE_ID_AL_M7101, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit ali1535_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ if (ali1535_setup(dev)) { ++ printk ++ ("i2c-ali1535.o: ALI1535 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ ++ sprintf(ali1535_adapter.name, "SMBus ALI1535 adapter at %04x", ++ ali1535_smba); ++ return i2c_add_adapter(&ali1535_adapter); ++} ++ ++static void __devexit ali1535_remove(struct pci_dev *dev) ++{ ++ i2c_del_adapter(&ali1535_adapter); ++ release_region(ali1535_smba, ALI1535_SMB_IOSIZE); ++} ++ ++ ++static struct pci_driver ali1535_driver = { ++ .name = "ali1535 smbus", ++ .id_table = ali1535_ids, ++ .probe = ali1535_probe, ++ .remove = __devexit_p(ali1535_remove), ++}; ++ ++static int __init i2c_ali1535_init(void) ++{ ++ printk("i2c-ali1535.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&ali1535_driver); ++} ++ ++ ++static void __exit i2c_ali1535_exit(void) ++{ ++ pci_unregister_driver(&ali1535_driver); ++} ++ ++#ifdef RLX ++EXPORT_SYMBOL(ali1535_smba); ++EXPORT_SYMBOL(ali1535_access); ++EXPORT_SYMBOL(i2c_ali1535_sem); ++#endif ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, " ++ "Mark D. Studebaker <mdsxyz123@yahoo.com> and Dan Eaton <dan.eaton@rocketlogix.com>"); ++MODULE_DESCRIPTION("ALI1535 SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_ali1535_init); ++module_exit(i2c_ali1535_exit); ++ +--- linux-old/drivers/i2c/i2c-ali15x3.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-ali15x3.c Mon Dec 13 20:18:40 2004 +@@ -0,0 +1,533 @@ ++/* ++ ali15x3.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1999 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> and ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ This is the driver for the SMB Host controller on ++ Acer Labs Inc. (ALI) M1541 and M1543C South Bridges. ++ ++ The M1543C is a South bridge for desktop systems. ++ The M1533 is a South bridge for portable systems. ++ They are part of the following ALI chipsets: ++ "Aladdin Pro 2": Includes the M1621 Slot 1 North bridge ++ with AGP and 100MHz CPU Front Side bus ++ "Aladdin V": Includes the M1541 Socket 7 North bridge ++ with AGP and 100MHz CPU Front Side bus ++ "Aladdin IV": Includes the M1541 Socket 7 North bridge ++ with host bus up to 83.3 MHz. ++ For an overview of these chips see http://www.acerlabs.com ++ ++ The M1533/M1543C devices appear as FOUR separate devices ++ on the PCI bus. An output of lspci will show something similar ++ to the following: ++ ++ 00:02.0 USB Controller: Acer Laboratories Inc. M5237 ++ 00:03.0 Bridge: Acer Laboratories Inc. M7101 ++ 00:07.0 ISA bridge: Acer Laboratories Inc. M1533 ++ 00:0f.0 IDE interface: Acer Laboratories Inc. M5229 ++ ++ The SMB controller is part of the 7101 device, which is an ++ ACPI-compliant Power Management Unit (PMU). ++ ++ The whole 7101 device has to be enabled for the SMB to work. ++ You can't just enable the SMB alone. ++ The SMB and the ACPI have separate I/O spaces. ++ We make sure that the SMB is enabled. We leave the ACPI alone. ++ ++ This driver controls the SMB Host only. ++ The SMB Slave controller on the M15X3 is not enabled. ++ ++ This driver does not use interrupts. ++*/ ++ ++/* Note: we assume there can only be one ALI15X3, with one SMBus interface */ ++ ++/* #define DEBUG 1 */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_compat.h> ++ ++/* ALI15X3 SMBus address offsets */ ++#define SMBHSTSTS (0 + ali15x3_smba) ++#define SMBHSTCNT (1 + ali15x3_smba) ++#define SMBHSTSTART (2 + ali15x3_smba) ++#define SMBHSTCMD (7 + ali15x3_smba) ++#define SMBHSTADD (3 + ali15x3_smba) ++#define SMBHSTDAT0 (4 + ali15x3_smba) ++#define SMBHSTDAT1 (5 + ali15x3_smba) ++#define SMBBLKDAT (6 + ali15x3_smba) ++ ++/* PCI Address Constants */ ++#define SMBCOM 0x004 ++#define SMBBA 0x014 ++#define SMBATPC 0x05B /* used to unlock xxxBA registers */ ++#define SMBHSTCFG 0x0E0 ++#define SMBSLVC 0x0E1 ++#define SMBCLK 0x0E2 ++#define SMBREV 0x008 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 200 /* times 1/100 sec */ ++#define ALI15X3_SMB_IOSIZE 32 ++ ++/* this is what the Award 1004 BIOS sets them to on a ASUS P5A MB. ++ We don't use these here. If the bases aren't set to some value we ++ tell user to upgrade BIOS and we fail. ++*/ ++#define ALI15X3_SMB_DEFAULTBASE 0xE800 ++ ++/* ALI15X3 address lock bits */ ++#define ALI15X3_LOCK 0x06 ++ ++/* ALI15X3 command constants */ ++#define ALI15X3_ABORT 0x02 ++#define ALI15X3_T_OUT 0x04 ++#define ALI15X3_QUICK 0x00 ++#define ALI15X3_BYTE 0x10 ++#define ALI15X3_BYTE_DATA 0x20 ++#define ALI15X3_WORD_DATA 0x30 ++#define ALI15X3_BLOCK_DATA 0x40 ++#define ALI15X3_BLOCK_CLR 0x80 ++ ++/* ALI15X3 status register bits */ ++#define ALI15X3_STS_IDLE 0x04 ++#define ALI15X3_STS_BUSY 0x08 ++#define ALI15X3_STS_DONE 0x10 ++#define ALI15X3_STS_DEV 0x20 /* device error */ ++#define ALI15X3_STS_COLL 0x40 /* collision or no response */ ++#define ALI15X3_STS_TERM 0x80 /* terminated by abort */ ++#define ALI15X3_STS_ERR 0xE0 /* all the bad error bits */ ++ ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the device at the given address. */ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the i2c controller"); ++ ++static unsigned short ali15x3_smba = 0; ++ ++static int ali15x3_setup(struct pci_dev *ALI15X3_dev) ++{ ++ u16 a; ++ unsigned char temp; ++ ++ /* Check the following things: ++ - SMB I/O address is initialized ++ - Device is enabled ++ - We can use the addresses ++ */ ++ ++ /* Unlock the register. ++ The data sheet says that the address registers are read-only ++ if the lock bits are 1, but in fact the address registers ++ are zero unless you clear the lock bits. ++ */ ++ pci_read_config_byte(ALI15X3_dev, SMBATPC, &temp); ++ if (temp & ALI15X3_LOCK) { ++ temp &= ~ALI15X3_LOCK; ++ pci_write_config_byte(ALI15X3_dev, SMBATPC, temp); ++ } ++ ++ /* Determine the address of the SMBus area */ ++ pci_read_config_word(ALI15X3_dev, SMBBA, &ali15x3_smba); ++ ali15x3_smba &= (0xffff & ~(ALI15X3_SMB_IOSIZE - 1)); ++ if (ali15x3_smba == 0 && force_addr == 0) { ++ dev_err(ALI15X3_dev, "ALI15X3_smb region uninitialized " ++ "- upgrade BIOS or use force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ ++ if(force_addr) ++ ali15x3_smba = force_addr & ~(ALI15X3_SMB_IOSIZE - 1); ++ ++ if (!request_region(ali15x3_smba, ALI15X3_SMB_IOSIZE, "ali15x3-smb")) { ++ dev_err(ALI15X3_dev, ++ "ALI15X3_smb region 0x%x already in use!\n", ++ ali15x3_smba); ++ return -ENODEV; ++ } ++ ++ if(force_addr) { ++ dev_info(ALI15X3_dev, "forcing ISA address 0x%04X\n", ++ ali15x3_smba); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(ALI15X3_dev, SMBBA, ali15x3_smba)) ++ return -ENODEV; ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(ALI15X3_dev, SMBBA, &a)) ++ return -ENODEV; ++ if ((a & ~(ALI15X3_SMB_IOSIZE - 1)) != ali15x3_smba) { ++ /* make sure it works */ ++ dev_err(ALI15X3_dev, ++ "force address failed - not supported?\n"); ++ return -ENODEV; ++ } ++ } ++ /* check if whole device is enabled */ ++ pci_read_config_byte(ALI15X3_dev, SMBCOM, &temp); ++ if ((temp & 1) == 0) { ++ dev_info(ALI15X3_dev, "enabling SMBus device\n"); ++ pci_write_config_byte(ALI15X3_dev, SMBCOM, temp | 0x01); ++ } ++ ++ /* Is SMB Host controller enabled? */ ++ pci_read_config_byte(ALI15X3_dev, SMBHSTCFG, &temp); ++ if ((temp & 1) == 0) { ++ dev_info(ALI15X3_dev, "enabling SMBus controller\n"); ++ pci_write_config_byte(ALI15X3_dev, SMBHSTCFG, temp | 0x01); ++ } ++ ++ /* set SMB clock to 74KHz as recommended in data sheet */ ++ pci_write_config_byte(ALI15X3_dev, SMBCLK, 0x20); ++ ++ /* ++ The interrupt routing for SMB is set up in register 0x77 in the ++ 1533 ISA Bridge device, NOT in the 7101 device. ++ Don't bother with finding the 1533 device and reading the register. ++ if ((....... & 0x0F) == 1) ++ dev_dbg(ALI15X3_dev, "ALI15X3 using Interrupt 9 for SMBus.\n"); ++ */ ++ pci_read_config_byte(ALI15X3_dev, SMBREV, &temp); ++ dev_dbg(ALI15X3_dev, "SMBREV = 0x%X\n", temp); ++ dev_dbg(ALI15X3_dev, "iALI15X3_smba = 0x%X\n", ali15x3_smba); ++ ++ return 0; ++} ++ ++/* Another internally used function */ ++static int ali15x3_transaction(struct i2c_adapter *adap) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++ dev_dbg(adap, "Transaction (pre): STS=%02x, CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTSTS), ++ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), ++ inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); ++ ++ /* get status */ ++ temp = inb_p(SMBHSTSTS); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ /* Check the busy bit first */ ++ if (temp & ALI15X3_STS_BUSY) { ++ /* ++ If the host controller is still busy, it may have timed out in the ++ previous transaction, resulting in a "SMBus Timeout" Dev. ++ I've tried the following to reset a stuck busy bit. ++ 1. Reset the controller with an ABORT command. ++ (this doesn't seem to clear the controller if an external ++ device is hung) ++ 2. Reset the controller and the other SMBus devices with a ++ T_OUT command. (this clears the host busy bit if an ++ external device is hung, but it comes back upon a new access ++ to a device) ++ 3. Disable and reenable the controller in SMBHSTCFG ++ Worst case, nothing seems to work except power reset. ++ */ ++ /* Abort - reset the host controller */ ++ /* ++ Try resetting entire SMB bus, including other devices - ++ This may not work either - it clears the BUSY bit but ++ then the BUSY bit may come back on when you try and use the chip again. ++ If that's the case you are stuck. ++ */ ++ dev_info(adap, "Resetting entire SMB Bus to " ++ "clear busy condition (%02x)\n", temp); ++ outb_p(ALI15X3_T_OUT, SMBHSTCNT); ++ temp = inb_p(SMBHSTSTS); ++ } ++ ++ /* now check the error bits and the busy bit */ ++ if (temp & (ALI15X3_STS_ERR | ALI15X3_STS_BUSY)) { ++ /* do a clear-on-write */ ++ outb_p(0xFF, SMBHSTSTS); ++ if ((temp = inb_p(SMBHSTSTS)) & ++ (ALI15X3_STS_ERR | ALI15X3_STS_BUSY)) { ++ /* this is probably going to be correctable only by a power reset ++ as one of the bits now appears to be stuck */ ++ /* This may be a bus or device with electrical problems. */ ++ dev_err(adap, "SMBus reset failed! (0x%02x) - " ++ "controller or device on bus is probably hung\n", ++ temp); ++ return -1; ++ } ++ } else { ++ /* check and clear done bit */ ++ if (temp & ALI15X3_STS_DONE) { ++ outb_p(temp, SMBHSTSTS); ++ } ++ } ++ ++ /* start the transaction by writing anything to the start register */ ++ outb_p(0xFF, SMBHSTSTART); ++ ++ /* We will always wait for a fraction of a second! */ ++ timeout = 0; ++ do { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } while ((!(temp & (ALI15X3_STS_ERR | ALI15X3_STS_DONE))) ++ && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ result = -1; ++ dev_err(adap, "SMBus Timeout!\n"); ++ } ++ ++ if (temp & ALI15X3_STS_TERM) { ++ result = -1; ++ dev_dbg(adap, "Error: Failed bus transaction\n"); ++ } ++ ++ /* ++ Unfortunately the ALI SMB controller maps "no response" and "bus ++ collision" into a single bit. No reponse is the usual case so don't ++ do a printk. ++ This means that bus collisions go unreported. ++ */ ++ if (temp & ALI15X3_STS_COLL) { ++ result = -1; ++ dev_dbg(adap, ++ "Error: no response or bus collision ADD=%02x\n", ++ inb_p(SMBHSTADD)); ++ } ++ ++ /* haven't ever seen this */ ++ if (temp & ALI15X3_STS_DEV) { ++ result = -1; ++ dev_err(adap, "Error: device error\n"); ++ } ++ dev_dbg(adap, "Transaction (post): STS=%02x, CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTSTS), ++ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), ++ inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); ++ return result; ++} ++ ++/* Return -1 on error. */ ++static s32 ali15x3_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, u8 command, ++ int size, union i2c_smbus_data * data) ++{ ++ int i, len; ++ int temp; ++ int timeout; ++ ++ /* clear all the bits (clear-on-write) */ ++ outb_p(0xFF, SMBHSTSTS); ++ /* make sure SMBus is idle */ ++ temp = inb_p(SMBHSTSTS); ++ for (timeout = 0; ++ (timeout < MAX_TIMEOUT) && !(temp & ALI15X3_STS_IDLE); ++ timeout++) { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } ++ if (timeout >= MAX_TIMEOUT) { ++ dev_err(adap, "Idle wait Timeout! STS=0x%02x\n", temp); ++ } ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = ALI15X3_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMBHSTCMD); ++ size = ALI15X3_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, SMBHSTDAT0); ++ size = ALI15X3_BYTE_DATA; ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT0); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); ++ } ++ size = ALI15X3_WORD_DATA; ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 0) { ++ len = 0; ++ data->block[0] = len; ++ } ++ if (len > 32) { ++ len = 32; ++ data->block[0] = len; ++ } ++ outb_p(len, SMBHSTDAT0); ++ /* Reset SMBBLKDAT */ ++ outb_p(inb_p(SMBHSTCNT) | ALI15X3_BLOCK_CLR, SMBHSTCNT); ++ for (i = 1; i <= len; i++) ++ outb_p(data->block[i], SMBBLKDAT); ++ } ++ size = ALI15X3_BLOCK_DATA; ++ break; ++ default: ++ printk ++ (KERN_WARNING "i2c-ali15x3.o: Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ outb_p(size, SMBHSTCNT); /* output command */ ++ ++ if (ali15x3_transaction(adap)) /* Error in transaction */ ++ return -1; ++ ++ if ((read_write == I2C_SMBUS_WRITE) || (size == ALI15X3_QUICK)) ++ return 0; ++ ++ ++ switch (size) { ++ case ALI15X3_BYTE: /* Result put in SMBHSTDAT0 */ ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case ALI15X3_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case ALI15X3_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); ++ break; ++ case ALI15X3_BLOCK_DATA: ++ len = inb_p(SMBHSTDAT0); ++ if (len > 32) ++ len = 32; ++ data->block[0] = len; ++ /* Reset SMBBLKDAT */ ++ outb_p(inb_p(SMBHSTCNT) | ALI15X3_BLOCK_CLR, SMBHSTCNT); ++ for (i = 1; i <= data->block[0]; i++) { ++ data->block[i] = inb_p(SMBBLKDAT); ++ dev_dbg(adap, "Blk: len=%d, i=%d, data=%02x\n", ++ len, i, data->block[i]); ++ } ++ break; ++ } ++ return 0; ++} ++ ++static u32 ali15x3_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = ali15x3_access, ++ .functionality = ali15x3_func, ++}; ++ ++static struct i2c_adapter ali15x3_adapter = { ++ .owner = THIS_MODULE, ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_ALI15X3, ++ .algo = &smbus_algorithm, ++ .name = "unset", ++}; ++ ++static struct pci_device_id ali15x3_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_AL, ++ .device = PCI_DEVICE_ID_AL_M7101, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit ali15x3_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ if (ali15x3_setup(dev)) { ++ dev_err(dev, ++ "ALI15X3 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ ++ snprintf(ali15x3_adapter.name, 32, ++ "SMBus ALI15X3 adapter at %04x", ali15x3_smba); ++ return i2c_add_adapter(&ali15x3_adapter); ++} ++ ++static void __devexit ali15x3_remove(struct pci_dev *dev) ++{ ++ i2c_del_adapter(&ali15x3_adapter); ++ release_region(ali15x3_smba, ALI15X3_SMB_IOSIZE); ++} ++ ++static struct pci_driver ali15x3_driver = { ++ .name = "ali15x3 smbus", ++ .id_table = ali15x3_ids, ++ .probe = ali15x3_probe, ++ .remove = __devexit_p(ali15x3_remove), ++}; ++ ++static int __init i2c_ali15x3_init(void) ++{ ++ printk("i2c-ali15x3.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&ali15x3_driver); ++} ++ ++static void __exit i2c_ali15x3_exit(void) ++{ ++ pci_unregister_driver(&ali15x3_driver); ++} ++ ++MODULE_AUTHOR ("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "and Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("ALI15X3 SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_ali15x3_init); ++module_exit(i2c_ali15x3_exit); +--- linux-old/drivers/i2c/i2c-amd756.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-amd756.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,425 @@ ++/* ++ amd756.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ ++ Copyright (c) 1999-2002 Merlin Hughes <merlin@merlin.org> ++ ++ Shamelessly ripped from i2c-piix4.c: ++ ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ 2002-04-08: Added nForce support. (Csaba Halasz) ++ 2002-10-03: Fixed nForce PnP I/O port. (Michael Steil) ++ 2002-12-28: Rewritten into something that resembles a Linux driver (hch) ++ 2003-11-29: Added back AMD8111 removed by the previous rewrite. ++ (Philip Pokorny) ++ 2004-02-15: Don't register driver to avoid driver conflicts. ++ (Daniel Rune Jensen) ++*/ ++ ++/* ++ Supports AMD756, AMD766, AMD768, AMD8111 and nVidia nForce ++ Note: we assume there can only be one device, with one SMBus interface. ++*/ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++#define DRV_NAME "i2c-amd756" ++ ++/* AMD756 SMBus address offsets */ ++#define SMB_ADDR_OFFSET 0xE0 ++#define SMB_IOSIZE 16 ++#define SMB_GLOBAL_STATUS (0x0 + amd756_ioport) ++#define SMB_GLOBAL_ENABLE (0x2 + amd756_ioport) ++#define SMB_HOST_ADDRESS (0x4 + amd756_ioport) ++#define SMB_HOST_DATA (0x6 + amd756_ioport) ++#define SMB_HOST_COMMAND (0x8 + amd756_ioport) ++#define SMB_HOST_BLOCK_DATA (0x9 + amd756_ioport) ++#define SMB_HAS_DATA (0xA + amd756_ioport) ++#define SMB_HAS_DEVICE_ADDRESS (0xC + amd756_ioport) ++#define SMB_HAS_HOST_ADDRESS (0xE + amd756_ioport) ++#define SMB_SNOOP_ADDRESS (0xF + amd756_ioport) ++ ++/* PCI Address Constants */ ++ ++/* address of I/O space */ ++#define SMBBA 0x058 /* mh */ ++#define SMBBANFORCE 0x014 ++ ++/* general configuration */ ++#define SMBGCFG 0x041 /* mh */ ++ ++/* silicon revision code */ ++#define SMBREV 0x008 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++ ++/* AMD756 constants */ ++#define AMD756_QUICK 0x00 ++#define AMD756_BYTE 0x01 ++#define AMD756_BYTE_DATA 0x02 ++#define AMD756_WORD_DATA 0x03 ++#define AMD756_PROCESS_CALL 0x04 ++#define AMD756_BLOCK_DATA 0x05 ++ ++ ++static unsigned short amd756_ioport = 0; ++ ++/* ++ SMBUS event = I/O 28-29 bit 11 ++ see E0 for the status bits and enabled in E2 ++ ++*/ ++ ++#define GS_ABRT_STS (1 << 0) ++#define GS_COL_STS (1 << 1) ++#define GS_PRERR_STS (1 << 2) ++#define GS_HST_STS (1 << 3) ++#define GS_HCYC_STS (1 << 4) ++#define GS_TO_STS (1 << 5) ++#define GS_SMB_STS (1 << 11) ++ ++#define GS_CLEAR_STS (GS_ABRT_STS | GS_COL_STS | GS_PRERR_STS | \ ++ GS_HCYC_STS | GS_TO_STS ) ++ ++#define GE_CYC_TYPE_MASK (7) ++#define GE_HOST_STC (1 << 3) ++#define GE_ABORT (1 << 5) ++ ++ ++static int amd756_transaction(void) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++ pr_debug(DRV_NAME ++ ": Transaction (pre): GS=%04x, GE=%04x, ADD=%04x, DAT=%04x\n", ++ inw_p(SMB_GLOBAL_STATUS), inw_p(SMB_GLOBAL_ENABLE), ++ inw_p(SMB_HOST_ADDRESS), inb_p(SMB_HOST_DATA)); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ if ((temp = inw_p(SMB_GLOBAL_STATUS)) & (GS_HST_STS | GS_SMB_STS)) { ++ pr_debug(DRV_NAME ": SMBus busy (%04x). Waiting... \n", temp); ++ do { ++ i2c_delay(1); ++ temp = inw_p(SMB_GLOBAL_STATUS); ++ } while ((temp & (GS_HST_STS | GS_SMB_STS)) && ++ (timeout++ < MAX_TIMEOUT)); ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ pr_debug(DRV_NAME ": Busy wait timeout (%04x)\n", temp); ++ goto abort; ++ } ++ timeout = 0; ++ } ++ ++ /* start the transaction by setting the start bit */ ++ outw_p(inw(SMB_GLOBAL_ENABLE) | GE_HOST_STC, SMB_GLOBAL_ENABLE); ++ ++ /* We will always wait for a fraction of a second! */ ++ do { ++ i2c_delay(1); ++ temp = inw_p(SMB_GLOBAL_STATUS); ++ } while ((temp & GS_HST_STS) && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ pr_debug(DRV_NAME ": Completion timeout!\n"); ++ goto abort; ++ } ++ ++ if (temp & GS_PRERR_STS) { ++ result = -1; ++ pr_debug(DRV_NAME ": SMBus Protocol error (no response)!\n"); ++ } ++ ++ if (temp & GS_COL_STS) { ++ result = -1; ++ printk(KERN_WARNING DRV_NAME ": SMBus collision!\n"); ++ } ++ ++ if (temp & GS_TO_STS) { ++ result = -1; ++ pr_debug(DRV_NAME ": SMBus protocol timeout!\n"); ++ } ++ ++ if (temp & GS_HCYC_STS) ++ pr_debug(DRV_NAME ": SMBus protocol success!\n"); ++ ++ outw_p(GS_CLEAR_STS, SMB_GLOBAL_STATUS); ++ ++#ifdef DEBUG ++ if (((temp = inw_p(SMB_GLOBAL_STATUS)) & GS_CLEAR_STS) != 0x00) { ++ pr_debug(DRV_NAME ++ ": Failed reset at end of transaction (%04x)\n", temp); ++ } ++ ++ pr_debug(DRV_NAME ++ ": Transaction (post): GS=%04x, GE=%04x, ADD=%04x, DAT=%04x\n", ++ inw_p(SMB_GLOBAL_STATUS), inw_p(SMB_GLOBAL_ENABLE), ++ inw_p(SMB_HOST_ADDRESS), inb_p(SMB_HOST_DATA)); ++#endif ++ ++ return result; ++ ++ abort: ++ printk(KERN_WARNING DRV_NAME ": Sending abort.\n"); ++ outw_p(inw(SMB_GLOBAL_ENABLE) | GE_ABORT, SMB_GLOBAL_ENABLE); ++ i2c_delay(100); ++ outw_p(GS_CLEAR_STS, SMB_GLOBAL_STATUS); ++ return -1; ++} ++ ++/* Return -1 on error. */ ++ ++static s32 amd756_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data * data) ++{ ++ int i, len; ++ ++ /** TODO: Should I supporte the 10-bit transfers? */ ++ switch (size) { ++ /* TODO: proc call is supported, I'm just not sure what to do here... */ ++ case I2C_SMBUS_QUICK: ++ outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMB_HOST_ADDRESS); ++ size = AMD756_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMB_HOST_ADDRESS); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMB_HOST_DATA); ++ size = AMD756_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMB_HOST_ADDRESS); ++ outb_p(command, SMB_HOST_COMMAND); ++ if (read_write == I2C_SMBUS_WRITE) ++ outw_p(data->byte, SMB_HOST_DATA); ++ size = AMD756_BYTE_DATA; ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMB_HOST_ADDRESS); ++ outb_p(command, SMB_HOST_COMMAND); ++ if (read_write == I2C_SMBUS_WRITE) ++ outw_p(data->word, SMB_HOST_DATA); /* TODO: endian???? */ ++ size = AMD756_WORD_DATA; ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outw_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMB_HOST_ADDRESS); ++ outb_p(command, SMB_HOST_COMMAND); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 0) ++ len = 0; ++ if (len > 32) ++ len = 32; ++ outw_p(len, SMB_HOST_DATA); ++ /* i = inw_p(SMBHSTCNT); Reset SMBBLKDAT */ ++ for (i = 1; i <= len; i++) ++ outb_p(data->block[i], ++ SMB_HOST_BLOCK_DATA); ++ } ++ size = AMD756_BLOCK_DATA; ++ break; ++ default: ++ printk ++ (KERN_WARNING "i2c-amd756.o: Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ /* How about enabling interrupts... */ ++ outw_p(size & GE_CYC_TYPE_MASK, SMB_GLOBAL_ENABLE); ++ ++ if (amd756_transaction()) /* Error in transaction */ ++ return -1; ++ ++ if ((read_write == I2C_SMBUS_WRITE) || (size == AMD756_QUICK)) ++ return 0; ++ ++ ++ switch (size) { ++ case AMD756_BYTE: ++ data->byte = inw_p(SMB_HOST_DATA); ++ break; ++ case AMD756_BYTE_DATA: ++ data->byte = inw_p(SMB_HOST_DATA); ++ break; ++ case AMD756_WORD_DATA: ++ data->word = inw_p(SMB_HOST_DATA); /* TODO: endian???? */ ++ break; ++ case AMD756_BLOCK_DATA: ++ data->block[0] = inw_p(SMB_HOST_DATA) & 0x3f; ++ if(data->block[0] > 32) ++ data->block[0] = 32; ++ /* i = inw_p(SMBHSTCNT); Reset SMBBLKDAT */ ++ for (i = 1; i <= data->block[0]; i++) ++ data->block[i] = inb_p(SMB_HOST_BLOCK_DATA); ++ break; ++ } ++ ++ return 0; ++} ++ ++static u32 amd756_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = amd756_access, ++ .functionality = amd756_func, ++}; ++ ++static struct i2c_adapter amd756_adapter = { ++ .owner = THIS_MODULE, ++ .name = "unset", ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_AMD756, ++ .algo = &smbus_algorithm, ++}; ++ ++enum chiptype { AMD756, AMD766, AMD768, NFORCE, AMD8111 }; ++ ++static struct pci_device_id amd756_ids[] __devinitdata = { ++ {PCI_VENDOR_ID_AMD, 0x740B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD756 }, ++ {PCI_VENDOR_ID_AMD, 0x7413, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD766 }, ++ {PCI_VENDOR_ID_AMD, 0x7443, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD768 }, ++ {PCI_VENDOR_ID_AMD, 0x746B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD8111 }, ++ {PCI_VENDOR_ID_NVIDIA, 0x01B4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, NFORCE }, ++ { 0, } ++}; ++ ++static int __devinit amd756_probe(struct pci_dev *pdev, ++ const struct pci_device_id *id) ++{ ++ int nforce = (id->driver_data == NFORCE); ++ int error; ++ u8 temp; ++ ++ if (amd756_ioport) { ++ printk(KERN_ERR DRV_NAME ": Only one device supported. " ++ "(you have a strange motherboard, btw..)\n"); ++ return -ENODEV; ++ } ++ ++ if (nforce) { ++ if (PCI_FUNC(pdev->devfn) != 1) ++ return -ENODEV; ++ ++ pci_read_config_word(pdev, SMBBANFORCE, &amd756_ioport); ++ amd756_ioport &= 0xfffc; ++ } else { /* amd */ ++ if (PCI_FUNC(pdev->devfn) != 3) ++ return -ENODEV; ++ ++ pci_read_config_byte(pdev, SMBGCFG, &temp); ++ if ((temp & 128) == 0) { ++ printk(KERN_ERR DRV_NAME ++ ": Error: SMBus controller I/O not enabled!\n"); ++ return -ENODEV; ++ } ++ ++ /* Determine the address of the SMBus areas */ ++ /* Technically it is a dword but... */ ++ pci_read_config_word(pdev, SMBBA, &amd756_ioport); ++ amd756_ioport &= 0xff00; ++ amd756_ioport += SMB_ADDR_OFFSET; ++ } ++ ++ if (!request_region(amd756_ioport, SMB_IOSIZE, "amd756-smbus")) { ++ printk(KERN_ERR DRV_NAME ++ ": SMB region 0x%x already in use!\n", amd756_ioport); ++ return -ENODEV; ++ } ++ ++#ifdef DEBUG ++ pci_read_config_byte(pdev, SMBREV, &temp); ++ printk(KERN_DEBUG DRV_NAME ": SMBREV = 0x%X\n", temp); ++ printk(KERN_DEBUG DRV_NAME ": AMD756_smba = 0x%X\n", amd756_ioport); ++#endif ++ ++ sprintf(amd756_adapter.name, ++ "SMBus AMD756 adapter at %04x", amd756_ioport); ++ ++ error = i2c_add_adapter(&amd756_adapter); ++ if (error) { ++ printk(KERN_ERR DRV_NAME ++ ": Adapter registration failed, module not inserted.\n"); ++ goto out_err; ++ } ++ ++ return 0; ++ ++ out_err: ++ release_region(amd756_ioport, SMB_IOSIZE); ++ return error; ++} ++ ++ ++static int __init i2c_amd756_init(void) ++{ ++ struct pci_dev *dev; ++ const struct pci_device_id *id; ++ ++ printk(KERN_INFO "i2c-amd756.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ pci_for_each_dev(dev) { ++ id = pci_match_device(amd756_ids, dev); ++ if (id && amd756_probe(dev, id) >= 0) ++ return 0; ++ } ++ ++ return -ENODEV; ++} ++ ++ ++static void __exit i2c_amd756_exit(void) ++{ ++ i2c_del_adapter(&amd756_adapter); ++ release_region(amd756_ioport, SMB_IOSIZE); ++} ++ ++MODULE_AUTHOR("Merlin Hughes <merlin@merlin.org>"); ++MODULE_DESCRIPTION("AMD756/766/768/8111 and nVidia nForce SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_amd756_init) ++module_exit(i2c_amd756_exit) +--- linux-old/drivers/i2c/i2c-amd8111.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-amd8111.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,421 @@ ++/* ++ * SMBus 2.0 driver for AMD-8111 IO-Hub. ++ * ++ * Copyright (c) 2002 Vojtech Pavlik ++ * ++ * 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 version 2. ++ */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/init.h> ++#include <linux/i2c.h> ++#include <linux/delay.h> ++#include <linux/slab.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++#ifndef I2C_HW_SMBUS_AMD8111 ++#error Your i2c is too old - i2c-2.7.0 or greater required! ++#endif ++ ++/* kernel 2.4.9 needs this */ ++#ifndef min_t ++#define min_t(type,x,y) min(type,x,y) ++#endif ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR ("Vojtech Pavlik <vojtech@suse.cz>"); ++MODULE_DESCRIPTION("AMD8111 SMBus 2.0 driver"); ++ ++struct amd_smbus { ++ struct pci_dev *dev; ++ struct i2c_adapter adapter; ++ int base; ++ int size; ++}; ++ ++/* ++ * AMD PCI control registers definitions. ++ */ ++ ++#define AMD_PCI_MISC 0x48 ++ ++#define AMD_PCI_MISC_SCI 0x04 /* deliver SCI */ ++#define AMD_PCI_MISC_INT 0x02 /* deliver PCI IRQ */ ++#define AMD_PCI_MISC_SPEEDUP 0x01 /* 16x clock speedup */ ++ ++/* ++ * ACPI 2.0 chapter 13 PCI interface definitions. ++ */ ++ ++#define AMD_EC_DATA 0x00 /* data register */ ++#define AMD_EC_SC 0x04 /* status of controller */ ++#define AMD_EC_CMD 0x04 /* command register */ ++#define AMD_EC_ICR 0x08 /* interrupt control register */ ++ ++#define AMD_EC_SC_SMI 0x04 /* smi event pending */ ++#define AMD_EC_SC_SCI 0x02 /* sci event pending */ ++#define AMD_EC_SC_BURST 0x01 /* burst mode enabled */ ++#define AMD_EC_SC_CMD 0x08 /* byte in data reg is command */ ++#define AMD_EC_SC_IBF 0x02 /* data ready for embedded controller */ ++#define AMD_EC_SC_OBF 0x01 /* data ready for host */ ++ ++#define AMD_EC_CMD_RD 0x80 /* read EC */ ++#define AMD_EC_CMD_WR 0x81 /* write EC */ ++#define AMD_EC_CMD_BE 0x82 /* enable burst mode */ ++#define AMD_EC_CMD_BD 0x83 /* disable burst mode */ ++#define AMD_EC_CMD_QR 0x84 /* query EC */ ++ ++/* ++ * ACPI 2.0 chapter 13 access of registers of the EC ++ */ ++ ++unsigned int amd_ec_wait_write(struct amd_smbus *smbus) ++{ ++ int timeout = 500; ++ ++ while (timeout-- && (inb(smbus->base + AMD_EC_SC) & AMD_EC_SC_IBF)) ++ udelay(1); ++ ++ if (!timeout) { ++ printk(KERN_WARNING "i2c-amd8111.c: Timeout while waiting for IBF to clear\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++unsigned int amd_ec_wait_read(struct amd_smbus *smbus) ++{ ++ int timeout = 500; ++ ++ while (timeout-- && (~inb(smbus->base + AMD_EC_SC) & AMD_EC_SC_OBF)) ++ udelay(1); ++ ++ if (!timeout) { ++ printk(KERN_WARNING "i2c-amd8111.c: Timeout while waiting for OBF to set\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++unsigned int amd_ec_read(struct amd_smbus *smbus, unsigned char address, unsigned char *data) ++{ ++ if (amd_ec_wait_write(smbus)) ++ return -1; ++ outb(AMD_EC_CMD_RD, smbus->base + AMD_EC_CMD); ++ ++ if (amd_ec_wait_write(smbus)) ++ return -1; ++ outb(address, smbus->base + AMD_EC_DATA); ++ ++ if (amd_ec_wait_read(smbus)) ++ return -1; ++ *data = inb(smbus->base + AMD_EC_DATA); ++ ++ return 0; ++} ++ ++unsigned int amd_ec_write(struct amd_smbus *smbus, unsigned char address, unsigned char data) ++{ ++ if (amd_ec_wait_write(smbus)) ++ return -1; ++ outb(AMD_EC_CMD_WR, smbus->base + AMD_EC_CMD); ++ ++ if (amd_ec_wait_write(smbus)) ++ return -1; ++ outb(address, smbus->base + AMD_EC_DATA); ++ ++ if (amd_ec_wait_write(smbus)) ++ return -1; ++ outb(data, smbus->base + AMD_EC_DATA); ++ ++ return 0; ++} ++ ++/* ++ * ACPI 2.0 chapter 13 SMBus 2.0 EC register model ++ */ ++ ++#define AMD_SMB_PRTCL 0x00 /* protocol, PEC */ ++#define AMD_SMB_STS 0x01 /* status */ ++#define AMD_SMB_ADDR 0x02 /* address */ ++#define AMD_SMB_CMD 0x03 /* command */ ++#define AMD_SMB_DATA 0x04 /* 32 data registers */ ++#define AMD_SMB_BCNT 0x24 /* number of data bytes */ ++#define AMD_SMB_ALRM_A 0x25 /* alarm address */ ++#define AMD_SMB_ALRM_D 0x26 /* 2 bytes alarm data */ ++ ++#define AMD_SMB_STS_DONE 0x80 ++#define AMD_SMB_STS_ALRM 0x40 ++#define AMD_SMB_STS_RES 0x20 ++#define AMD_SMB_STS_STATUS 0x1f ++ ++#define AMD_SMB_STATUS_OK 0x00 ++#define AMD_SMB_STATUS_FAIL 0x07 ++#define AMD_SMB_STATUS_DNAK 0x10 ++#define AMD_SMB_STATUS_DERR 0x11 ++#define AMD_SMB_STATUS_CMD_DENY 0x12 ++#define AMD_SMB_STATUS_UNKNOWN 0x13 ++#define AMD_SMB_STATUS_ACC_DENY 0x17 ++#define AMD_SMB_STATUS_TIMEOUT 0x18 ++#define AMD_SMB_STATUS_NOTSUP 0x19 ++#define AMD_SMB_STATUS_BUSY 0x1A ++#define AMD_SMB_STATUS_PEC 0x1F ++ ++#define AMD_SMB_PRTCL_WRITE 0x00 ++#define AMD_SMB_PRTCL_READ 0x01 ++#define AMD_SMB_PRTCL_QUICK 0x02 ++#define AMD_SMB_PRTCL_BYTE 0x04 ++#define AMD_SMB_PRTCL_BYTE_DATA 0x06 ++#define AMD_SMB_PRTCL_WORD_DATA 0x08 ++#define AMD_SMB_PRTCL_BLOCK_DATA 0x0a ++#define AMD_SMB_PRTCL_PROC_CALL 0x0c ++#define AMD_SMB_PRTCL_BLOCK_PROC_CALL 0x0d ++#define AMD_SMB_PRTCL_I2C_BLOCK_DATA 0x4a ++#define AMD_SMB_PRTCL_PEC 0x80 ++ ++ ++s32 amd8111_access(struct i2c_adapter * adap, u16 addr, unsigned short flags, ++ char read_write, u8 command, int size, union i2c_smbus_data * data) ++{ ++ struct amd_smbus *smbus = adap->algo_data; ++ unsigned char protocol, len, pec, temp[2]; ++ int i; ++ ++ protocol = (read_write == I2C_SMBUS_READ) ? AMD_SMB_PRTCL_READ : AMD_SMB_PRTCL_WRITE; ++ pec = (flags & I2C_CLIENT_PEC) ? AMD_SMB_PRTCL_PEC : 0; ++ ++ switch (size) { ++ ++ case I2C_SMBUS_QUICK: ++ protocol |= AMD_SMB_PRTCL_QUICK; ++ read_write = I2C_SMBUS_WRITE; ++ break; ++ ++ case I2C_SMBUS_BYTE: ++ if (read_write == I2C_SMBUS_WRITE) ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ protocol |= AMD_SMB_PRTCL_BYTE; ++ break; ++ ++ case I2C_SMBUS_BYTE_DATA: ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) ++ amd_ec_write(smbus, AMD_SMB_DATA, data->byte); ++ protocol |= AMD_SMB_PRTCL_BYTE_DATA; ++ break; ++ ++ case I2C_SMBUS_WORD_DATA: ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) { ++ amd_ec_write(smbus, AMD_SMB_DATA, data->word); ++ amd_ec_write(smbus, AMD_SMB_DATA + 1, data->word >> 8); ++ } ++ protocol |= AMD_SMB_PRTCL_WORD_DATA | pec; ++ break; ++ ++ case I2C_SMBUS_BLOCK_DATA: ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = min_t(u8, data->block[0], 32); ++ amd_ec_write(smbus, AMD_SMB_BCNT, len); ++ for (i = 0; i < len; i++) ++ amd_ec_write(smbus, AMD_SMB_DATA + i, data->block[i + 1]); ++ } ++ protocol |= AMD_SMB_PRTCL_BLOCK_DATA | pec; ++ break; ++ ++ case I2C_SMBUS_I2C_BLOCK_DATA: ++ len = min_t(u8, data->block[0], 32); ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ amd_ec_write(smbus, AMD_SMB_BCNT, len); ++ if (read_write == I2C_SMBUS_WRITE) ++ for (i = 0; i < len; i++) ++ amd_ec_write(smbus, AMD_SMB_DATA + i, data->block[i + 1]); ++ protocol |= AMD_SMB_PRTCL_I2C_BLOCK_DATA; ++ break; ++ ++ case I2C_SMBUS_PROC_CALL: ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ amd_ec_write(smbus, AMD_SMB_DATA, data->word); ++ amd_ec_write(smbus, AMD_SMB_DATA + 1, data->word >> 8); ++ protocol = AMD_SMB_PRTCL_PROC_CALL | pec; ++ read_write = I2C_SMBUS_READ; ++ break; ++ ++ case I2C_SMBUS_BLOCK_PROC_CALL: ++ protocol |= pec; ++ len = min_t(u8, data->block[0], 31); ++ amd_ec_write(smbus, AMD_SMB_CMD, command); ++ amd_ec_write(smbus, AMD_SMB_BCNT, len); ++ for (i = 0; i < len; i++) ++ amd_ec_write(smbus, AMD_SMB_DATA + i, data->block[i + 1]); ++ protocol = AMD_SMB_PRTCL_BLOCK_PROC_CALL | pec; ++ read_write = I2C_SMBUS_READ; ++ break; ++ ++ case I2C_SMBUS_WORD_DATA_PEC: ++ case I2C_SMBUS_BLOCK_DATA_PEC: ++ case I2C_SMBUS_PROC_CALL_PEC: ++ case I2C_SMBUS_BLOCK_PROC_CALL_PEC: ++ printk(KERN_WARNING "i2c-amd8111.c: Unexpected software PEC transaction %d\n.", size); ++ return -1; ++ ++ default: ++ printk(KERN_WARNING "i2c-amd8111.c: Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ amd_ec_write(smbus, AMD_SMB_ADDR, addr << 1); ++ amd_ec_write(smbus, AMD_SMB_PRTCL, protocol); ++ ++ amd_ec_read(smbus, AMD_SMB_STS, temp + 0); ++ ++ if (~temp[0] & AMD_SMB_STS_DONE) { ++ udelay(500); ++ amd_ec_read(smbus, AMD_SMB_STS, temp + 0); ++ } ++ ++ if (~temp[0] & AMD_SMB_STS_DONE) { ++ i2c_delay(HZ/100); ++ amd_ec_read(smbus, AMD_SMB_STS, temp + 0); ++ } ++ ++ if ((~temp[0] & AMD_SMB_STS_DONE) || (temp[0] & AMD_SMB_STS_STATUS)) ++ return -1; ++ ++ if (read_write == I2C_SMBUS_WRITE) ++ return 0; ++ ++ switch (size) { ++ ++ case I2C_SMBUS_BYTE: ++ case I2C_SMBUS_BYTE_DATA: ++ amd_ec_read(smbus, AMD_SMB_DATA, &data->byte); ++ break; ++ ++ case I2C_SMBUS_WORD_DATA: ++ case I2C_SMBUS_PROC_CALL: ++ amd_ec_read(smbus, AMD_SMB_DATA, temp + 0); ++ amd_ec_read(smbus, AMD_SMB_DATA + 1, temp + 1); ++ data->word = (temp[1] << 8) | temp[0]; ++ break; ++ ++ case I2C_SMBUS_BLOCK_DATA: ++ case I2C_SMBUS_BLOCK_PROC_CALL: ++ amd_ec_read(smbus, AMD_SMB_BCNT, &len); ++ len = min_t(u8, len, 32); ++ case I2C_SMBUS_I2C_BLOCK_DATA: ++ for (i = 0; i < len; i++) ++ amd_ec_read(smbus, AMD_SMB_DATA + i, data->block + i + 1); ++ data->block[0] = len; ++ break; ++ } ++ ++ return 0; ++} ++ ++ ++u32 amd8111_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA | ++ I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_BLOCK_PROC_CALL | ++ I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_HWPEC_CALC; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus 2.0 adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = amd8111_access, ++ .functionality = amd8111_func, ++}; ++ ++ ++static struct pci_device_id amd8111_ids[] __devinitdata = { ++ { 0x1022, 0x746a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, ++ { 0, } ++}; ++ ++static int __devinit amd8111_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ struct amd_smbus *smbus; ++ int error; ++ ++ if (~pci_resource_flags(dev, 0) & IORESOURCE_IO) ++ return -1; ++ ++ if (!(smbus = (void*)kmalloc(sizeof(struct amd_smbus), GFP_KERNEL))) ++ return -1; ++ memset(smbus, 0, sizeof(struct amd_smbus)); ++ ++ pci_set_drvdata(dev, smbus); ++ smbus->dev = dev; ++ smbus->base = pci_resource_start(dev, 0); ++ smbus->size = pci_resource_len(dev, 0); ++ ++ if (!request_region(smbus->base, smbus->size, "amd8111 SMBus 2.0")) { ++ kfree(smbus); ++ return -1; ++ } ++ ++ smbus->adapter.owner = THIS_MODULE; ++ sprintf(smbus->adapter.name, "SMBus2 AMD8111 adapter at %04x", smbus->base); ++ smbus->adapter.id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_AMD8111; ++ smbus->adapter.algo = &smbus_algorithm; ++ smbus->adapter.algo_data = smbus; ++ ++ error = i2c_add_adapter(&smbus->adapter); ++ if (error) { ++ printk(KERN_WARNING "i2c-amd8111.c: Failed to register adapter.\n"); ++ release_region(smbus->base, smbus->size); ++ kfree(smbus); ++ return -1; ++ } ++ ++ pci_write_config_dword(smbus->dev, AMD_PCI_MISC, 0); ++ ++ printk(KERN_INFO "i2c-amd8111.c: AMD8111 SMBus 2.0 adapter at %#x\n", smbus->base); ++ return 0; ++} ++ ++ ++static void __devexit amd8111_remove(struct pci_dev *dev) ++{ ++ struct amd_smbus *smbus = (void*) pci_get_drvdata(dev); ++ i2c_del_adapter(&smbus->adapter); ++ release_region(smbus->base, smbus->size); ++ kfree(smbus); ++} ++ ++static struct pci_driver amd8111_driver = { ++ .name = "amd8111 smbus 2.0", ++ .id_table = amd8111_ids, ++ .probe = amd8111_probe, ++ .remove = __devexit_p(amd8111_remove), ++}; ++ ++static int __init i2c_amd8111_init(void) ++{ ++ printk(KERN_INFO "i2c-amd8111.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&amd8111_driver); ++} ++ ++ ++static void __exit i2c_amd8111_exit(void) ++{ ++ pci_unregister_driver(&amd8111_driver); ++} ++ ++module_init(i2c_amd8111_init); ++module_exit(i2c_amd8111_exit); +--- linux-old/drivers/i2c/i2c-hydra.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-hydra.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,175 @@ ++/* ++ i2c-hydra.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ i2c Support for the Apple `Hydra' Mac I/O ++ ++ Copyright (c) 1999 Geert Uytterhoeven <geert@linux-m68k.org> ++ ++ Based on i2c Support for Via Technologies 82C586B South Bridge ++ Copyright (c) 1998, 1999 Kyösti Mälkki <kmalkki@cc.hut.fi> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/types.h> ++#include <linux/i2c.h> ++#include <linux/i2c-algo-bit.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/system.h> ++#include <asm/param.h> /* for HZ */ ++ ++MODULE_LICENSE("GPL"); ++ ++ ++#define HYDRA_CACHE_PD 0x00000030 ++ ++#define HYDRA_CPD_PD0 0x00000001 /* CachePD lines */ ++#define HYDRA_CPD_PD1 0x00000002 ++#define HYDRA_CPD_PD2 0x00000004 ++#define HYDRA_CPD_PD3 0x00000008 ++ ++#define HYDRA_SCLK HYDRA_CPD_PD0 ++#define HYDRA_SDAT HYDRA_CPD_PD1 ++#define HYDRA_SCLK_OE 0x00000010 ++#define HYDRA_SDAT_OE 0x00000020 ++ ++static unsigned long hydra_base; ++ ++static inline void pdregw(u32 val) ++{ ++ writel(val, hydra_base + HYDRA_CACHE_PD); ++} ++ ++static inline u32 pdregr(void) ++{ ++ u32 val = readl(hydra_base + HYDRA_CACHE_PD); ++ return val; ++} ++ ++static void bit_hydra_setscl(void *data, int state) ++{ ++ u32 val = pdregr(); ++ if (state) ++ val &= ~HYDRA_SCLK_OE; ++ else { ++ val &= ~HYDRA_SCLK; ++ val |= HYDRA_SCLK_OE; ++ } ++ pdregw(val); ++ pdregr(); /* flush posted write */ ++} ++ ++static void bit_hydra_setsda(void *data, int state) ++{ ++ u32 val = pdregr(); ++ if (state) ++ val &= ~HYDRA_SDAT_OE; ++ else { ++ val &= ~HYDRA_SDAT; ++ val |= HYDRA_SDAT_OE; ++ } ++ pdregw(val); ++ pdregr(); /* flush posted write */ ++} ++ ++static int bit_hydra_getscl(void *data) ++{ ++ return (pdregr() & HYDRA_SCLK) != 0; ++} ++ ++static int bit_hydra_getsda(void *data) ++{ ++ return (pdregr() & HYDRA_SDAT) != 0; ++} ++ ++/* ------------------------------------------------------------------------ */ ++ ++static struct i2c_algo_bit_data bit_hydra_data = { ++ .setsda = bit_hydra_setsda, ++ .setscl = bit_hydra_setscl, ++ .getsda = bit_hydra_getsda, ++ .getscl = bit_hydra_getscl, ++ .udelay = 5, ++ .mdelay = 5, ++ .timeout = HZ ++}; ++ ++static struct i2c_adapter bit_hydra_ops = { ++ .owner = THIS_MODULE, ++ .name = "Hydra i2c", ++ .id = I2C_HW_B_HYDRA, ++ .algo_data = &bit_hydra_data, ++}; ++ ++static struct pci_device_id hydra_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_APPLE, ++ .device = PCI_DEVICE_ID_APPLE_HYDRA, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit hydra_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ unsigned int base_addr; ++ ++ base_addr = dev->resource[0].start; ++ hydra_base = (unsigned long) ioremap(base_addr, 0x100); ++ ++ pdregw(0); /* clear SCLK_OE and SDAT_OE */ ++ return i2c_bit_add_bus(&bit_hydra_ops); ++} ++ ++static void __devexit hydra_remove(struct pci_dev *dev) ++{ ++ pdregw(0); /* clear SCLK_OE and SDAT_OE */ ++ i2c_bit_del_bus(&bit_hydra_ops); ++ iounmap((void *) hydra_base); ++} ++ ++ ++static struct pci_driver hydra_driver = { ++ .name = "hydra smbus", ++ .id_table = hydra_ids, ++ .probe = hydra_probe, ++ .remove = __devexit_p(hydra_remove), ++}; ++ ++static int __init i2c_hydra_init(void) ++{ ++ return pci_module_init(&hydra_driver); ++} ++ ++ ++static void __exit i2c_hydra_exit(void) ++{ ++ pci_unregister_driver(&hydra_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>"); ++MODULE_DESCRIPTION("i2c for Apple Hydra Mac I/O"); ++ ++module_init(i2c_hydra_init); ++module_exit(i2c_hydra_exit); ++ +--- linux-old/drivers/i2c/i2c-i801.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-i801.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,656 @@ ++/* ++ i801.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, and Mark D. Studebaker ++ <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ SUPPORTED DEVICES PCI ID ++ 82801AA 2413 ++ 82801AB 2423 ++ 82801BA 2443 ++ 82801CA/CAM 2483 ++ 82801DB 24C3 (HW PEC supported, 32 byte buffer not supported) ++ 82801EB 24D3 (HW PEC supported, 32 byte buffer not supported) ++ 6300ESB 25A4 ("") ++ ICH6 266A ++ This driver supports several versions of Intel's I/O Controller Hubs (ICH). ++ For SMBus support, they are similar to the PIIX4 and are part ++ of Intel's '810' and other chipsets. ++ See the doc/busses/i2c-i801 file for details. ++ I2C Block Read and Process Call are not supported. ++*/ ++ ++/* Note: we assume there can only be one I801, with one SMBus interface */ ++ ++/* #define DEBUG 1 */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/init.h> ++#include <linux/i2c.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_compat.h> ++ ++/* 82801DB is undefined before kernel 2.4.19 */ ++#ifndef PCI_DEVICE_ID_INTEL_82801DB_3 ++#define PCI_DEVICE_ID_INTEL_82801DB_3 0x24c3 ++#endif ++ ++#ifdef I2C_FUNC_SMBUS_BLOCK_DATA_PEC ++#define HAVE_PEC ++#endif ++ ++/* I801 SMBus address offsets */ ++#define SMBHSTSTS (0 + i801_smba) ++#define SMBHSTCNT (2 + i801_smba) ++#define SMBHSTCMD (3 + i801_smba) ++#define SMBHSTADD (4 + i801_smba) ++#define SMBHSTDAT0 (5 + i801_smba) ++#define SMBHSTDAT1 (6 + i801_smba) ++#define SMBBLKDAT (7 + i801_smba) ++#define SMBPEC (8 + i801_smba) /* ICH4 only */ ++#define SMBAUXSTS (12 + i801_smba) /* ICH4 only */ ++#define SMBAUXCTL (13 + i801_smba) /* ICH4 only */ ++ ++/* PCI Address Constants */ ++#define SMBBA 0x020 ++#define SMBHSTCFG 0x040 ++#define SMBREV 0x008 ++ ++/* Host configuration bits for SMBHSTCFG */ ++#define SMBHSTCFG_HST_EN 1 ++#define SMBHSTCFG_SMB_SMI_EN 2 ++#define SMBHSTCFG_I2C_EN 4 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 100 ++#define ENABLE_INT9 0 /* set to 0x01 to enable - untested */ ++ ++/* I801 command constants */ ++#define I801_QUICK 0x00 ++#define I801_BYTE 0x04 ++#define I801_BYTE_DATA 0x08 ++#define I801_WORD_DATA 0x0C ++#define I801_PROC_CALL 0x10 /* later chips only, unimplemented */ ++#define I801_BLOCK_DATA 0x14 ++#define I801_I2C_BLOCK_DATA 0x18 /* unimplemented */ ++#define I801_BLOCK_LAST 0x34 ++#define I801_I2C_BLOCK_LAST 0x38 /* unimplemented */ ++#define I801_START 0x40 ++#define I801_PEC_EN 0x80 /* ICH4 only */ ++ ++/* insmod parameters */ ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the I801 at the given address. VERY DANGEROUS! */ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Forcibly enable the I801 at the given address. " ++ "EXTREMELY DANGEROUS!"); ++ ++static int i801_transaction(void); ++static int i801_block_transaction(union i2c_smbus_data *data, ++ char read_write, int command); ++ ++static unsigned short i801_smba; ++static struct pci_dev *I801_dev; ++static int isich4; ++ ++static int i801_setup(struct pci_dev *dev) ++{ ++ int error_return = 0; ++ unsigned char temp; ++ ++ /* Note: we keep on searching until we have found 'function 3' */ ++ if(PCI_FUNC(dev->devfn) != 3) ++ return -ENODEV; ++ ++ I801_dev = dev; ++ if (dev->device == PCI_DEVICE_ID_INTEL_82801DB_3 || ++ dev->device == 0x24d3 || ++ dev->device == 0x25a4) ++ isich4 = 1; ++ else ++ isich4 = 0; ++ ++ /* Determine the address of the SMBus areas */ ++ if (force_addr) { ++ i801_smba = force_addr & 0xfff0; ++ } else { ++ pci_read_config_word(I801_dev, SMBBA, &i801_smba); ++ i801_smba &= 0xfff0; ++ if(i801_smba == 0) { ++ dev_err(dev, "SMB base address uninitialized" ++ "- upgrade BIOS or use force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ } ++ ++ if (!request_region(i801_smba, (isich4 ? 16 : 8), "i801-smbus")) { ++ dev_err(dev, "I801_smb region 0x%x already in use!\n", ++ i801_smba); ++ error_return = -EBUSY; ++ goto END; ++ } ++ ++ pci_read_config_byte(I801_dev, SMBHSTCFG, &temp); ++ temp &= ~SMBHSTCFG_I2C_EN; /* SMBus timing */ ++ pci_write_config_byte(I801_dev, SMBHSTCFG, temp); ++ ++ /* If force_addr is set, we program the new address here. Just to make ++ sure, we disable the device first. */ ++ if (force_addr) { ++ pci_write_config_byte(I801_dev, SMBHSTCFG, temp & 0xfe); ++ pci_write_config_word(I801_dev, SMBBA, i801_smba); ++ pci_write_config_byte(I801_dev, SMBHSTCFG, temp | 0x01); ++ dev_warn(dev, "WARNING: I801 SMBus interface set to " ++ "new address %04x!\n", i801_smba); ++ } else if ((temp & 1) == 0) { ++ pci_write_config_byte(I801_dev, SMBHSTCFG, temp | 1); ++ dev_warn(dev, "enabling SMBus device\n"); ++ } ++ ++ if (temp & 0x02) ++ dev_dbg(dev, "I801 using Interrupt SMI# for SMBus.\n"); ++ else ++ dev_dbg(dev, "I801 using PCI Interrupt for SMBus.\n"); ++ ++ pci_read_config_byte(I801_dev, SMBREV, &temp); ++ dev_dbg(dev, "SMBREV = 0x%X\n", temp); ++ dev_dbg(dev, "I801_smba = 0x%X\n", i801_smba); ++ ++END: ++ return error_return; ++} ++ ++ ++static int i801_transaction(void) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++ dev_dbg(I801_dev, "Transaction (pre): CNT=%02x, CMD=%02x," ++ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), ++ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ /* 0x1f = Failed, Bus_Err, Dev_Err, Intr, Host_Busy */ ++ if ((temp = (0x1f & inb_p(SMBHSTSTS))) != 0x00) { ++ dev_dbg(I801_dev, "SMBus busy (%02x). Resetting... \n", ++ temp); ++ outb_p(temp, SMBHSTSTS); ++ if ((temp = (0x1f & inb_p(SMBHSTSTS))) != 0x00) { ++ dev_dbg(I801_dev, "Failed! (%02x)\n", temp); ++ return -1; ++ } else { ++ dev_dbg(I801_dev, "Successfull!\n"); ++ } ++ } ++ ++ outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT); ++ ++ /* We will always wait for a fraction of a second! */ ++ do { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ dev_dbg(I801_dev, "SMBus Timeout!\n"); ++ result = -1; ++ } ++ ++ if (temp & 0x10) { ++ result = -1; ++ dev_dbg(I801_dev, "Error: Failed bus transaction\n"); ++ } ++ ++ if (temp & 0x08) { ++ result = -1; ++ dev_err(I801_dev, "Bus collision! SMBus may be locked " ++ "until next hard reset. (sorry!)\n"); ++ /* Clock stops and slave is stuck in mid-transmission */ ++ } ++ ++ if (temp & 0x04) { ++ result = -1; ++ dev_dbg(I801_dev, "Error: no response!\n"); ++ } ++ ++ if ((inb_p(SMBHSTSTS) & 0x1f) != 0x00) ++ outb_p(inb(SMBHSTSTS), SMBHSTSTS); ++ ++ if ((temp = (0x1f & inb_p(SMBHSTSTS))) != 0x00) { ++ dev_dbg(I801_dev, "Failed reset at end of transaction" ++ "(%02x)\n", temp); ++ } ++ dev_dbg(I801_dev, "Transaction (post): CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), ++ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++ return result; ++} ++ ++/* All-inclusive block transaction function */ ++static int i801_block_transaction(union i2c_smbus_data *data, char read_write, ++ int command) ++{ ++ int i, len; ++ int smbcmd; ++ int temp; ++ int result = 0; ++ int timeout; ++ unsigned char hostc, errmask; ++ ++ if (command == I2C_SMBUS_I2C_BLOCK_DATA) { ++ if (read_write == I2C_SMBUS_WRITE) { ++ /* set I2C_EN bit in configuration register */ ++ pci_read_config_byte(I801_dev, SMBHSTCFG, &hostc); ++ pci_write_config_byte(I801_dev, SMBHSTCFG, ++ hostc | SMBHSTCFG_I2C_EN); ++ } else { ++ dev_err(I801_dev, ++ "I2C_SMBUS_I2C_BLOCK_READ not DB!\n"); ++ return -1; ++ } ++ } ++ ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 1) ++ len = 1; ++ if (len > 32) ++ len = 32; ++ outb_p(len, SMBHSTDAT0); ++ outb_p(data->block[1], SMBBLKDAT); ++ } else { ++ len = 32; /* max for reads */ ++ } ++ ++ if(isich4 && command != I2C_SMBUS_I2C_BLOCK_DATA) { ++ /* set 32 byte buffer */ ++ } ++ ++ for (i = 1; i <= len; i++) { ++ if (i == len && read_write == I2C_SMBUS_READ) ++ smbcmd = I801_BLOCK_LAST; ++ else ++ smbcmd = I801_BLOCK_DATA; ++ outb_p(smbcmd | ENABLE_INT9, SMBHSTCNT); ++ ++ dev_dbg(I801_dev, "Block (pre %d): CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, BLKDAT=%02x\n", i, ++ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), ++ inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT)); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ temp = inb_p(SMBHSTSTS); ++ if (i == 1) { ++ /* Erronenous conditions before transaction: ++ * Byte_Done, Failed, Bus_Err, Dev_Err, Intr, Host_Busy */ ++ errmask=0x9f; ++ } else { ++ /* Erronenous conditions during transaction: ++ * Failed, Bus_Err, Dev_Err, Intr */ ++ errmask=0x1e; ++ } ++ if (temp & errmask) { ++ dev_dbg(I801_dev, "SMBus busy (%02x). " ++ "Resetting... \n", temp); ++ outb_p(temp, SMBHSTSTS); ++ if (((temp = inb_p(SMBHSTSTS)) & errmask) != 0x00) { ++ dev_err(I801_dev, ++ "Reset failed! (%02x)\n", temp); ++ result = -1; ++ goto END; ++ } ++ if (i != 1) { ++ /* if die in middle of block transaction, fail */ ++ result = -1; ++ goto END; ++ } ++ } ++ ++ if (i == 1) ++ outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT); ++ ++ /* We will always wait for a fraction of a second! */ ++ timeout = 0; ++ do { ++ temp = inb_p(SMBHSTSTS); ++ i2c_delay(1); ++ } ++ while ((!(temp & 0x80)) ++ && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ result = -1; ++ dev_dbg(I801_dev, "SMBus Timeout!\n"); ++ } ++ ++ if (temp & 0x10) { ++ result = -1; ++ dev_dbg(I801_dev, ++ "Error: Failed bus transaction\n"); ++ } else if (temp & 0x08) { ++ result = -1; ++ dev_err(I801_dev, "Bus collision!\n"); ++ } else if (temp & 0x04) { ++ result = -1; ++ dev_dbg(I801_dev, "Error: no response!\n"); ++ } ++ ++ if (i == 1 && read_write == I2C_SMBUS_READ) { ++ len = inb_p(SMBHSTDAT0); ++ if (len < 1) ++ len = 1; ++ if (len > 32) ++ len = 32; ++ data->block[0] = len; ++ } ++ ++ /* Retrieve/store value in SMBBLKDAT */ ++ if (read_write == I2C_SMBUS_READ) ++ data->block[i] = inb_p(SMBBLKDAT); ++ if (read_write == I2C_SMBUS_WRITE && i+1 <= len) ++ outb_p(data->block[i+1], SMBBLKDAT); ++ if ((temp & 0x9e) != 0x00) ++ outb_p(temp, SMBHSTSTS); /* signals SMBBLKDAT ready */ ++ ++ if ((temp = (0x1e & inb_p(SMBHSTSTS))) != 0x00) { ++ dev_dbg(I801_dev, ++ "Bad status (%02x) at end of transaction\n", ++ temp); ++ } ++ dev_dbg(I801_dev, "Block (post %d): CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, BLKDAT=%02x\n", i, ++ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), ++ inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT)); ++ ++ if (result < 0) ++ goto END; ++ } ++ ++#ifdef HAVE_PEC ++ if(isich4 && command == I2C_SMBUS_BLOCK_DATA_PEC) { ++ /* wait for INTR bit as advised by Intel */ ++ timeout = 0; ++ do { ++ temp = inb_p(SMBHSTSTS); ++ i2c_delay(1); ++ } while ((!(temp & 0x02)) ++ && (timeout++ < MAX_TIMEOUT)); ++ ++ if (timeout >= MAX_TIMEOUT) { ++ dev_dbg(I801_dev, "PEC Timeout!\n"); ++ } ++ outb_p(temp, SMBHSTSTS); ++ } ++#endif ++ result = 0; ++END: ++ if (command == I2C_SMBUS_I2C_BLOCK_DATA) { ++ /* restore saved configuration register value */ ++ pci_write_config_byte(I801_dev, SMBHSTCFG, hostc); ++ } ++ return result; ++} ++ ++/* Return -1 on error. */ ++static s32 i801_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, u8 command, ++ int size, union i2c_smbus_data * data) ++{ ++ int hwpec = 0; ++ int block = 0; ++ int ret, xact = 0; ++ ++#ifdef HAVE_PEC ++ if(isich4) ++ hwpec = (flags & I2C_CLIENT_PEC) != 0; ++#endif ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ xact = I801_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMBHSTCMD); ++ xact = I801_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, SMBHSTDAT0); ++ xact = I801_BYTE_DATA; ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT0); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); ++ } ++ xact = I801_WORD_DATA; ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ case I2C_SMBUS_I2C_BLOCK_DATA: ++#ifdef HAVE_PEC ++ case I2C_SMBUS_BLOCK_DATA_PEC: ++ if(hwpec && size == I2C_SMBUS_BLOCK_DATA) ++ size = I2C_SMBUS_BLOCK_DATA_PEC; ++#endif ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ block = 1; ++ break; ++ case I2C_SMBUS_PROC_CALL: ++ default: ++ dev_err(I801_dev, "Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++#ifdef HAVE_PEC ++ if(isich4 && hwpec) { ++ if(size != I2C_SMBUS_QUICK && ++ size != I2C_SMBUS_I2C_BLOCK_DATA) ++ outb_p(1, SMBAUXCTL); /* enable HW PEC */ ++ } ++#endif ++ if(block) ++ ret = i801_block_transaction(data, read_write, size); ++ else { ++ outb_p(xact | ENABLE_INT9, SMBHSTCNT); ++ ret = i801_transaction(); ++ } ++ ++#ifdef HAVE_PEC ++ if(isich4 && hwpec) { ++ if(size != I2C_SMBUS_QUICK && ++ size != I2C_SMBUS_I2C_BLOCK_DATA) ++ outb_p(0, SMBAUXCTL); ++ } ++#endif ++ ++ if(block) ++ return ret; ++ if(ret) ++ return -1; ++ if ((read_write == I2C_SMBUS_WRITE) || (xact == I801_QUICK)) ++ return 0; ++ ++ switch (xact & 0x7f) { ++ case I801_BYTE: /* Result put in SMBHSTDAT0 */ ++ case I801_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case I801_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); ++ break; ++ } ++ return 0; ++} ++ ++ ++static u32 i801_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK ++#ifdef HAVE_PEC ++ | (isich4 ? I2C_FUNC_SMBUS_BLOCK_DATA_PEC | ++ I2C_FUNC_SMBUS_HWPEC_CALC ++ : 0) ++#endif ++ ; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = i801_access, ++ .functionality = i801_func, ++}; ++ ++static struct i2c_adapter i801_adapter = { ++ .owner = THIS_MODULE, ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_I801, ++ .algo = &smbus_algorithm, ++ .name = "unset", ++}; ++ ++static struct pci_device_id i801_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82801AA_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82801AB_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82801BA_2, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82801CA_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82801DB_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = 0x24d3, /* 82801EB ICH5 */ ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = 0x25a4, /* PCI_DEVICE_ID_INTEL_ESB_4 */ ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = 0x266a, /* PCI_DEVICE_ID_INTEL_ICH6_16 */ ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ ++ if (i801_setup(dev)) { ++ dev_warn(dev, ++ "I801 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ ++ snprintf(i801_adapter.name, 32, ++ "SMBus I801 adapter at %04x", i801_smba); ++ return i2c_add_adapter(&i801_adapter); ++} ++ ++static void __devexit i801_remove(struct pci_dev *dev) ++{ ++ i2c_del_adapter(&i801_adapter); ++ release_region(i801_smba, (isich4 ? 16 : 8)); ++} ++ ++static struct pci_driver i801_driver = { ++ .name = "i801 smbus", ++ .id_table = i801_ids, ++ .probe = i801_probe, ++ .remove = __devexit_p(i801_remove), ++}; ++ ++static int __init i2c_i801_init(void) ++{ ++ printk(KERN_INFO "i2c-i801 version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&i801_driver); ++} ++ ++static void __exit i2c_i801_exit(void) ++{ ++ pci_unregister_driver(&i801_driver); ++} ++ ++MODULE_AUTHOR ("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "and Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("I801 SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_i801_init); ++module_exit(i2c_i801_exit); +--- linux-old/drivers/i2c/i2c-i810.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-i810.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,310 @@ ++/* ++ i2c-i810.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999, 2000 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ Ralph Metzler <rjkm@thp.uni-koeln.de>, and ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ Based on code written by Ralph Metzler <rjkm@thp.uni-koeln.de> and ++ Simon Vogl ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++/* ++ This interfaces to the I810/I815 to provide access to ++ the DDC Bus and the I2C Bus. ++ ++ SUPPORTED DEVICES PCI ID ++ i810AA 7121 ++ i810AB 7123 ++ i810E 7125 ++ i815 1132 ++*/ ++ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/i2c.h> ++#include <linux/i2c-algo-bit.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/param.h> /* for HZ */ ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++#ifndef PCI_DEVICE_ID_INTEL_82815_2 ++#define PCI_DEVICE_ID_INTEL_82815_2 0x1132 ++#endif ++ ++/* GPIO register locations */ ++#define I810_IOCONTROL_OFFSET 0x5000 ++#define I810_HVSYNC 0x00 /* not used */ ++#define I810_GPIOA 0x10 ++#define I810_GPIOB 0x14 ++ ++/* bit locations in the registers */ ++#define SCL_DIR_MASK 0x0001 ++#define SCL_DIR 0x0002 ++#define SCL_VAL_MASK 0x0004 ++#define SCL_VAL_OUT 0x0008 ++#define SCL_VAL_IN 0x0010 ++#define SDA_DIR_MASK 0x0100 ++#define SDA_DIR 0x0200 ++#define SDA_VAL_MASK 0x0400 ++#define SDA_VAL_OUT 0x0800 ++#define SDA_VAL_IN 0x1000 ++ ++/* initialization states */ ++#define INIT1 0x1 ++#define INIT2 0x2 ++#define INIT3 0x4 ++ ++/* delays */ ++#define CYCLE_DELAY 10 ++#define TIMEOUT (HZ / 2) ++ ++ ++static void config_i810(struct pci_dev *dev); ++ ++ ++static unsigned long ioaddr; ++ ++/* The i810 GPIO registers have individual masks for each bit ++ so we never have to read before writing. Nice. */ ++ ++static void bit_i810i2c_setscl(void *data, int val) ++{ ++ writel((val ? SCL_VAL_OUT : 0) | SCL_DIR | SCL_DIR_MASK | SCL_VAL_MASK, ++ ioaddr + I810_GPIOB); ++ readl(ioaddr + I810_GPIOB); /* flush posted write */ ++} ++ ++static void bit_i810i2c_setsda(void *data, int val) ++{ ++ writel((val ? SDA_VAL_OUT : 0) | SDA_DIR | SDA_DIR_MASK | SDA_VAL_MASK, ++ ioaddr + I810_GPIOB); ++ readl(ioaddr + I810_GPIOB); /* flush posted write */ ++} ++ ++/* The GPIO pins are open drain, so the pins could always remain outputs. ++ However, some chip versions don't latch the inputs unless they ++ are set as inputs. ++ We rely on the i2c-algo-bit routines to set the pins high before ++ reading the input from other chips. Following guidance in the 815 ++ prog. ref. guide, we do a "dummy write" of 0 to the register before ++ reading which forces the input value to be latched. We presume this ++ applies to the 810 as well; shouldn't hurt anyway. This is necessary to get ++ i2c_algo_bit bit_test=1 to pass. */ ++ ++static int bit_i810i2c_getscl(void *data) ++{ ++ writel(SCL_DIR_MASK, ioaddr + I810_GPIOB); ++ writel(0, ioaddr + I810_GPIOB); ++ return (0 != (readl(ioaddr + I810_GPIOB) & SCL_VAL_IN)); ++} ++ ++static int bit_i810i2c_getsda(void *data) ++{ ++ writel(SDA_DIR_MASK, ioaddr + I810_GPIOB); ++ writel(0, ioaddr + I810_GPIOB); ++ return (0 != (readl(ioaddr + I810_GPIOB) & SDA_VAL_IN)); ++} ++ ++static void bit_i810ddc_setscl(void *data, int val) ++{ ++ writel((val ? SCL_VAL_OUT : 0) | SCL_DIR | SCL_DIR_MASK | SCL_VAL_MASK, ++ ioaddr + I810_GPIOA); ++ readl(ioaddr + I810_GPIOA); /* flush posted write */ ++} ++ ++static void bit_i810ddc_setsda(void *data, int val) ++{ ++ writel((val ? SDA_VAL_OUT : 0) | SDA_DIR | SDA_DIR_MASK | SDA_VAL_MASK, ++ ioaddr + I810_GPIOA); ++ readl(ioaddr + I810_GPIOA); /* flush posted write */ ++} ++ ++static int bit_i810ddc_getscl(void *data) ++{ ++ writel(SCL_DIR_MASK, ioaddr + I810_GPIOA); ++ writel(0, ioaddr + I810_GPIOA); ++ return (0 != (readl(ioaddr + I810_GPIOA) & SCL_VAL_IN)); ++} ++ ++static int bit_i810ddc_getsda(void *data) ++{ ++ writel(SDA_DIR_MASK, ioaddr + I810_GPIOA); ++ writel(0, ioaddr + I810_GPIOA); ++ return (0 != (readl(ioaddr + I810_GPIOA) & SDA_VAL_IN)); ++} ++ ++ ++/* Configures the chip */ ++void config_i810(struct pci_dev *dev) ++{ ++ unsigned long cadr; ++ ++ /* map I810 memory */ ++ cadr = dev->resource[1].start; ++ cadr += I810_IOCONTROL_OFFSET; ++ cadr &= PCI_BASE_ADDRESS_MEM_MASK; ++ ioaddr = (unsigned long)ioremap_nocache(cadr, 0x1000); ++ if(ioaddr) { ++ bit_i810i2c_setscl(NULL, 1); ++ bit_i810i2c_setsda(NULL, 1); ++ bit_i810ddc_setscl(NULL, 1); ++ bit_i810ddc_setsda(NULL, 1); ++ } ++} ++ ++ ++static struct i2c_algo_bit_data i810_i2c_bit_data = { ++ .setsda = bit_i810i2c_setsda, ++ .setscl = bit_i810i2c_setscl, ++ .getsda = bit_i810i2c_getsda, ++ .getscl = bit_i810i2c_getscl, ++ .udelay = CYCLE_DELAY, ++ .mdelay = CYCLE_DELAY, ++ .timeout = TIMEOUT, ++}; ++ ++static struct i2c_adapter i810_i2c_adapter = { ++ .owner = THIS_MODULE, ++ .name = "I810/I815 I2C Adapter", ++ .id = I2C_HW_B_I810, ++ .algo_data = &i810_i2c_bit_data, ++}; ++ ++static struct i2c_algo_bit_data i810_ddc_bit_data = { ++ .setsda = bit_i810ddc_setsda, ++ .setscl = bit_i810ddc_setscl, ++ .getsda = bit_i810ddc_getsda, ++ .getscl = bit_i810ddc_getscl, ++ .udelay = CYCLE_DELAY, ++ .mdelay = CYCLE_DELAY, ++ .timeout = TIMEOUT, ++}; ++ ++static struct i2c_adapter i810_ddc_adapter = { ++ .owner = THIS_MODULE, ++ .name = "I810/I815 DDC Adapter", ++ .id = I2C_HW_B_I810, ++ .algo_data = &i810_ddc_bit_data, ++}; ++ ++ ++static struct pci_device_id i810_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82810_IG1, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82810_IG3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = 0x7125, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82815_2, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = 0x2562, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit i810_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ int retval; ++ ++ config_i810(dev); ++ printk("i2c-i810.o: i810/i815 found.\n"); ++ ++ retval = i2c_bit_add_bus(&i810_i2c_adapter); ++ if(retval) ++ return retval; ++ retval = i2c_bit_add_bus(&i810_ddc_adapter); ++ if(retval) ++ i2c_bit_del_bus(&i810_i2c_adapter); ++ return retval; ++} ++ ++static void __devexit i810_remove(struct pci_dev *dev) ++{ ++ i2c_bit_del_bus(&i810_ddc_adapter); ++ i2c_bit_del_bus(&i810_i2c_adapter); ++} ++ ++ ++/* Don't register driver to avoid driver conflicts */ ++/* ++static struct pci_driver i810_driver = { ++ .name = "i810 smbus", ++ .id_table = i810_ids, ++ .probe = i810_probe, ++ .remove = __devexit_p(i810_remove), ++}; ++*/ ++ ++static int __init i2c_i810_init(void) ++{ ++ struct pci_dev *dev; ++ const struct pci_device_id *id; ++ ++ printk("i2c-i810.o version %s (%s)\n", LM_VERSION, LM_DATE); ++/* ++ return pci_module_init(&i810_driver); ++*/ ++ pci_for_each_dev(dev) { ++ id = pci_match_device(i810_ids, dev); ++ if(id) ++ if(i810_probe(dev, id) >= 0) ++ return 0; ++ } ++ return -ENODEV; ++} ++ ++static void __exit i2c_i810_exit(void) ++{ ++/* ++ pci_unregister_driver(&i810_driver); ++*/ ++ i810_remove(NULL); ++ iounmap((void *)ioaddr); ++} ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, Ralph Metzler <rjkm@thp.uni-koeln.de>, and Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("I810/I815 I2C/DDC driver"); ++ ++module_init(i2c_i810_init); ++module_exit(i2c_i810_exit); +--- linux-old/drivers/i2c/i2c-isa.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-isa.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,74 @@ ++/* ++ i2c-isa.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* This implements an i2c algorithm/adapter for ISA bus. Not that this is ++ on first sight very useful; almost no functionality is preserved. ++ Except that it makes writing drivers for chips which can be on both ++ the SMBus and the ISA bus very much easier. See lm78.c for an example ++ of this. */ ++ ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/kernel.h> ++#include <linux/errno.h> ++#include <linux/i2c.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++static u32 isa_func(struct i2c_adapter *adapter); ++ ++/* This is the actual algorithm we define */ ++static struct i2c_algorithm isa_algorithm = { ++ .name = "ISA bus algorithm", ++ .id = I2C_ALGO_ISA, ++ .functionality = isa_func, ++}; ++ ++/* There can only be one... */ ++static struct i2c_adapter isa_adapter = { ++ .owner = THIS_MODULE, ++ .name = "ISA main adapter", ++ .id = I2C_ALGO_ISA | I2C_HW_ISA, ++ .algo = &isa_algorithm, ++}; ++ ++/* We can't do a thing... */ ++static u32 isa_func(struct i2c_adapter *adapter) ++{ ++ return 0; ++} ++ ++static int __init i2c_isa_init(void) ++{ ++ printk("i2c-isa.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_adapter(&isa_adapter); ++} ++ ++static void __exit i2c_isa_exit(void) ++{ ++ i2c_del_adapter(&isa_adapter); ++} ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"); ++MODULE_DESCRIPTION("ISA bus access through i2c"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_isa_init); ++module_exit(i2c_isa_exit); +--- linux-old/drivers/i2c/i2c-nforce2.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-nforce2.c Mon Dec 13 20:18:41 2004 +@@ -0,0 +1,409 @@ ++/* ++ SMBus driver for nVidia nForce2 MCP ++ ++ Copyright (c) 2003 Hans-Frieder Vogt <hfvogt@arcor.de>, ++ Based on ++ SMBus 2.0 driver for AMD-8111 IO-Hub ++ Copyright (c) 2002 Vojtech Pavlik ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ SUPPORTED DEVICES PCI ID ++ nForce2 MCP 0064 ++ ++ This driver supports the 2 SMBuses that are included in the MCP2 of the ++ nForce2 chipset. ++*/ ++ ++/* Note: we assume there can only be one nForce2, with two SMBus interfaces */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/init.h> ++#include <linux/i2c.h> ++#include <linux/delay.h> ++#include <linux/slab.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* kernel 2.4.9 needs this */ ++#ifndef min_t ++#define min_t(type,x,y) min(type,x,y) ++#endif ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR ("Hans-Frieder Vogt <hfvogt@arcor.de>"); ++MODULE_DESCRIPTION("nForce2 SMBus driver"); ++ ++#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS ++#define PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS 0x0064 ++#endif ++ ++ ++struct nforce2_smbus { ++ struct pci_dev *dev; ++ struct i2c_adapter adapter; ++ int base; ++ int size; ++}; ++ ++ ++/* ++ * nVidia nForce2 SMBus control register definitions ++ */ ++#define NFORCE_PCI_SMB1 0x50 ++#define NFORCE_PCI_SMB2 0x54 ++ ++ ++/* ++ * ACPI 2.0 chapter 13 SMBus 2.0 EC register model ++ */ ++#define NVIDIA_SMB_PRTCL (smbus->base + 0x00) /* protocol, PEC */ ++#define NVIDIA_SMB_STS (smbus->base + 0x01) /* status */ ++#define NVIDIA_SMB_ADDR (smbus->base + 0x02) /* address */ ++#define NVIDIA_SMB_CMD (smbus->base + 0x03) /* command */ ++#define NVIDIA_SMB_DATA (smbus->base + 0x04) /* 32 data registers */ ++#define NVIDIA_SMB_BCNT (smbus->base + 0x24) /* number of data bytes */ ++#define NVIDIA_SMB_ALRM_A (smbus->base + 0x25) /* alarm address */ ++#define NVIDIA_SMB_ALRM_D (smbus->base + 0x26) /* 2 bytes alarm data */ ++ ++#define NVIDIA_SMB_STS_DONE 0x80 ++#define NVIDIA_SMB_STS_ALRM 0x40 ++#define NVIDIA_SMB_STS_RES 0x20 ++#define NVIDIA_SMB_STS_STATUS 0x1f ++ ++#define NVIDIA_SMB_PRTCL_WRITE 0x00 ++#define NVIDIA_SMB_PRTCL_READ 0x01 ++#define NVIDIA_SMB_PRTCL_QUICK 0x02 ++#define NVIDIA_SMB_PRTCL_BYTE 0x04 ++#define NVIDIA_SMB_PRTCL_BYTE_DATA 0x06 ++#define NVIDIA_SMB_PRTCL_WORD_DATA 0x08 ++#define NVIDIA_SMB_PRTCL_BLOCK_DATA 0x0a ++#define NVIDIA_SMB_PRTCL_PROC_CALL 0x0c ++#define NVIDIA_SMB_PRTCL_BLOCK_PROC_CALL 0x0d ++#define NVIDIA_SMB_PRTCL_I2C_BLOCK_DATA 0x4a ++#define NVIDIA_SMB_PRTCL_PEC 0x80 ++ ++ ++/* Other settings */ ++#define MAX_TIMEOUT 256 ++ ++ ++ ++static s32 nforce2_access(struct i2c_adapter *adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data *data); ++/* ++static int nforce2_block_transaction(union i2c_smbus_data *data, ++ char read_write, int i2c_enable); ++ */ ++static u32 nforce2_func(struct i2c_adapter *adapter); ++ ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = nforce2_access, ++ .functionality = nforce2_func, ++}; ++ ++/* Return -1 on error. See smbus.h for more information */ ++s32 nforce2_access(struct i2c_adapter * adap, u16 addr, unsigned short flags, ++ char read_write, u8 command, int size, ++ union i2c_smbus_data * data) ++{ ++ struct nforce2_smbus *smbus = adap->algo_data; ++ unsigned char protocol, pec, temp; ++ unsigned char len = 0; /* to keep the compiler quiet */ ++ int timeout = 0; ++ int i; ++ ++ protocol = (read_write == I2C_SMBUS_READ) ? NVIDIA_SMB_PRTCL_READ : NVIDIA_SMB_PRTCL_WRITE; ++ pec = (flags & I2C_CLIENT_PEC) ? NVIDIA_SMB_PRTCL_PEC : 0; ++ ++ switch (size) { ++ ++ case I2C_SMBUS_QUICK: ++ protocol |= NVIDIA_SMB_PRTCL_QUICK; ++ read_write = I2C_SMBUS_WRITE; ++ break; ++ ++ case I2C_SMBUS_BYTE: ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, NVIDIA_SMB_CMD); ++ protocol |= NVIDIA_SMB_PRTCL_BYTE; ++ break; ++ ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p(command, NVIDIA_SMB_CMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, NVIDIA_SMB_DATA); ++ protocol |= NVIDIA_SMB_PRTCL_BYTE_DATA; ++ break; ++ ++ case I2C_SMBUS_WORD_DATA: ++ outb_p(command, NVIDIA_SMB_CMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word, NVIDIA_SMB_DATA); ++ outb_p(data->word >> 8, NVIDIA_SMB_DATA+1); ++ } ++ protocol |= NVIDIA_SMB_PRTCL_WORD_DATA | pec; ++ break; ++ ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p(command, NVIDIA_SMB_CMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = min_t(u8, data->block[0], 32); ++ outb_p(len, NVIDIA_SMB_BCNT); ++ for (i = 0; i < len; i++) ++ outb_p(data->block[i + 1], NVIDIA_SMB_DATA+i); ++ } ++ protocol |= NVIDIA_SMB_PRTCL_BLOCK_DATA | pec; ++ break; ++ ++ case I2C_SMBUS_I2C_BLOCK_DATA: ++ len = min_t(u8, data->block[0], 32); ++ outb_p(command, NVIDIA_SMB_CMD); ++ outb_p(len, NVIDIA_SMB_BCNT); ++ if (read_write == I2C_SMBUS_WRITE) ++ for (i = 0; i < len; i++) ++ outb_p(data->block[i + 1], NVIDIA_SMB_DATA+i); ++ protocol |= NVIDIA_SMB_PRTCL_I2C_BLOCK_DATA; ++ break; ++ ++ case I2C_SMBUS_PROC_CALL: ++ printk(KERN_WARNING "i2c-nforce2.o: I2C_SMBUS_PROC_CALL not supported!\n"); ++ return -1; ++ /* ++ outb_p(command, NVIDIA_SMB_CMD); ++ outb_p(data->word, NVIDIA_SMB_DATA); ++ outb_p(data->word >> 8, NVIDIA_SMB_DATA + 1); ++ protocol = NVIDIA_SMB_PRTCL_PROC_CALL | pec; ++ read_write = I2C_SMBUS_READ; ++ break; ++ */ ++ ++ case I2C_SMBUS_BLOCK_PROC_CALL: ++ printk(KERN_WARNING "i2c-nforce2.o: I2C_SMBUS_BLOCK_PROC_CALL not supported!\n"); ++ return -1; ++ /* ++ protocol |= pec; ++ len = min_t(u8, data->block[0], 31); ++ outb_p(command, NVIDIA_SMB_CMD); ++ outb_p(len, NVIDIA_SMB_BCNT); ++ for (i = 0; i < len; i++) ++ outb_p(data->block[i + 1], NVIDIA_SMB_DATA + i); ++ protocol = NVIDIA_SMB_PRTCL_BLOCK_PROC_CALL | pec; ++ read_write = I2C_SMBUS_READ; ++ break; ++ */ ++ ++ case I2C_SMBUS_WORD_DATA_PEC: ++ case I2C_SMBUS_BLOCK_DATA_PEC: ++ case I2C_SMBUS_PROC_CALL_PEC: ++ case I2C_SMBUS_BLOCK_PROC_CALL_PEC: ++ printk(KERN_WARNING "i2c-nforce2.c: Unexpected software PEC transaction %d\n.", size); ++ return -1; ++ ++ default: ++ printk(KERN_WARNING "i2c-nforce2.c: Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ outb_p((addr & 0x7f) << 1, NVIDIA_SMB_ADDR); ++ outb_p(protocol, NVIDIA_SMB_PRTCL); ++ ++ temp = inb_p(NVIDIA_SMB_STS); ++ ++#if 0 ++ do { ++ i2c_delay(1); ++ temp = inb_p(NVIDIA_SMB_STS); ++ } while (((temp & NVIDIA_SMB_STS_DONE) == 0) && (timeout++ < MAX_TIMEOUT)); ++#endif ++ if (~temp & NVIDIA_SMB_STS_DONE) { ++ udelay(500); ++ temp = inb_p(NVIDIA_SMB_STS); ++ } ++ if (~temp & NVIDIA_SMB_STS_DONE) { ++ i2c_delay(HZ/100); ++ temp = inb_p(NVIDIA_SMB_STS); ++ } ++ ++ if ((timeout >= MAX_TIMEOUT) || (~temp & NVIDIA_SMB_STS_DONE) || (temp & NVIDIA_SMB_STS_STATUS)) ++ return -1; ++ ++ if (read_write == I2C_SMBUS_WRITE) ++ return 0; ++ ++ switch (size) { ++ ++ case I2C_SMBUS_BYTE: ++ case I2C_SMBUS_BYTE_DATA: ++ data->byte = inb_p(NVIDIA_SMB_DATA); ++ break; ++ ++ case I2C_SMBUS_WORD_DATA: ++ /* case I2C_SMBUS_PROC_CALL: not supported */ ++ data->word = inb_p(NVIDIA_SMB_DATA) | (inb_p(NVIDIA_SMB_DATA+1) << 8); ++ break; ++ ++ case I2C_SMBUS_BLOCK_DATA: ++ /* case I2C_SMBUS_BLOCK_PROC_CALL: not supported */ ++ len = inb_p(NVIDIA_SMB_BCNT); ++ len = min_t(u8, len, 32); ++ case I2C_SMBUS_I2C_BLOCK_DATA: ++ for (i = 0; i < len; i++) ++ data->block[i+1] = inb_p(NVIDIA_SMB_DATA + i); ++ data->block[0] = len; ++ break; ++ } ++ ++ return 0; ++} ++ ++ ++u32 nforce2_func(struct i2c_adapter *adapter) ++{ ++ /* other functionality might be possible, but is not tested */ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA /* | ++ I2C_FUNC_SMBUS_BLOCK_DATA */; ++} ++ ++ ++static struct pci_device_id nforce2_ids[] = { ++ { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS, ++ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, ++ { 0 } ++}; ++ ++ ++static int __devinit nforce2_probe_smb (struct pci_dev *dev, int reg, struct nforce2_smbus *smbus, char *name) ++{ ++ u16 iobase; ++ int error; ++ ++ if (pci_read_config_word(dev, reg, &iobase) != PCIBIOS_SUCCESSFUL) { ++ printk (KERN_ERR "i2c-nforce2.o: Error reading PCI config for %s\n", name); ++ return -1; ++ } ++ smbus->dev = dev; ++ smbus->base = iobase & 0xfffc; ++ smbus->size = 8; ++ ++ if (!request_region(smbus->base, smbus->size, "nForce2 SMBus")) { ++ printk (KERN_ERR "i2c-nforce2.o: Error requesting region %02x .. %02X for %s\n", smbus->base, smbus->base+smbus->size-1, name); ++ return -1; ++ } ++ ++ /* TODO: find a better way to find out whether this file is compiled ++ * with i2c 2.7.0 of earlier ++ */ ++#ifdef I2C_HW_SMBUS_AMD8111 ++ smbus->adapter.owner = THIS_MODULE; ++#endif ++ sprintf(smbus->adapter.name, "SMBus nForce2 adapter at %04x", smbus->base); ++ smbus->adapter.id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_NFORCE2; ++ smbus->adapter.algo = &smbus_algorithm; ++ smbus->adapter.algo_data = smbus; ++ ++ error = i2c_add_adapter(&smbus->adapter); ++ if (error) { ++ printk(KERN_WARNING "i2c-nforce2.o: Failed to register adapter.\n"); ++ release_region(smbus->base, smbus->size); ++ return -1; ++ } ++ printk(KERN_INFO "i2c-nforce2.o: nForce2 SMBus adapter at %#x\n", smbus->base); ++ return 0; ++} ++ ++ ++static int __devinit nforce2_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ struct nforce2_smbus *smbuses; ++ int res1, res2; ++ ++ /* we support 2 SMBus adapters */ ++ if (!(smbuses = (void *)kmalloc(2*sizeof(struct nforce2_smbus), ++ GFP_KERNEL))) ++ return -ENOMEM; ++ memset (smbuses, 0, 2*sizeof(struct nforce2_smbus)); ++ pci_set_drvdata(dev, smbuses); ++ ++ /* SMBus adapter 1 */ ++ res1 = nforce2_probe_smb (dev, NFORCE_PCI_SMB1, &smbuses[0], "SMB1"); ++ if (res1 < 0) { ++ printk (KERN_ERR "i2c-nforce2.o: Error probing SMB1.\n"); ++ smbuses[0].base = 0; /* to have a check value */ ++ } ++ res2 = nforce2_probe_smb (dev, NFORCE_PCI_SMB2, &smbuses[1], "SMB2"); ++ if (res2 < 0) { ++ printk (KERN_ERR "i2c-nforce2.o: Error probing SMB2.\n"); ++ smbuses[1].base = 0; /* to have a check value */ ++ } ++ if ((res1 < 0) && (res2 < 0)) { ++ /* we did not find even one of the SMBuses, so we give up */ ++ kfree(smbuses); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++ ++static void __devexit nforce2_remove(struct pci_dev *dev) ++{ ++ struct nforce2_smbus *smbuses = (void*) pci_get_drvdata(dev); ++ ++ if (smbuses[0].base) { ++ i2c_del_adapter(&smbuses[0].adapter); ++ release_region(smbuses[0].base, smbuses[0].size); ++ } ++ if (smbuses[1].base) { ++ i2c_del_adapter(&smbuses[1].adapter); ++ release_region(smbuses[1].base, smbuses[1].size); ++ } ++ kfree(smbuses); ++} ++ ++static struct pci_driver nforce2_driver = { ++ .name = "nForce2 SMBus", ++ .id_table = nforce2_ids, ++ .probe = nforce2_probe, ++ .remove = __devexit_p(nforce2_remove), ++}; ++ ++int __init nforce2_init(void) ++{ ++ printk(KERN_INFO "i2c-nforce2.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&nforce2_driver); ++} ++ ++void __exit nforce2_exit(void) ++{ ++ pci_unregister_driver(&nforce2_driver); ++} ++ ++module_init(nforce2_init); ++module_exit(nforce2_exit); ++ +--- linux-old/drivers/i2c/i2c-piix4.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-piix4.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,549 @@ ++/* ++ piix4.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Supports: ++ Intel PIIX4, 440MX ++ Serverworks OSB4, CSB5, CSB6 ++ SMSC Victory66 ++ ++ Note: we assume there can only be one device, with one SMBus interface. ++*/ ++ ++#include <linux/version.h> ++#include <linux/module.h> ++#include <linux/config.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/apm_bios.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++ ++struct sd { ++ const unsigned short mfr; ++ const unsigned short dev; ++ const unsigned char fn; ++ const char *name; ++}; ++ ++/* PIIX4 SMBus address offsets */ ++#define SMBHSTSTS (0 + piix4_smba) ++#define SMBHSLVSTS (1 + piix4_smba) ++#define SMBHSTCNT (2 + piix4_smba) ++#define SMBHSTCMD (3 + piix4_smba) ++#define SMBHSTADD (4 + piix4_smba) ++#define SMBHSTDAT0 (5 + piix4_smba) ++#define SMBHSTDAT1 (6 + piix4_smba) ++#define SMBBLKDAT (7 + piix4_smba) ++#define SMBSLVCNT (8 + piix4_smba) ++#define SMBSHDWCMD (9 + piix4_smba) ++#define SMBSLVEVT (0xA + piix4_smba) ++#define SMBSLVDAT (0xC + piix4_smba) ++ ++/* count for request_region */ ++#define SMBIOSIZE 8 ++ ++/* PCI Address Constants */ ++#define SMBBA 0x090 ++#define SMBHSTCFG 0x0D2 ++#define SMBSLVC 0x0D3 ++#define SMBSHDW1 0x0D4 ++#define SMBSHDW2 0x0D5 ++#define SMBREV 0x0D6 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++#define ENABLE_INT9 0 ++ ++/* PIIX4 constants */ ++#define PIIX4_QUICK 0x00 ++#define PIIX4_BYTE 0x04 ++#define PIIX4_BYTE_DATA 0x08 ++#define PIIX4_WORD_DATA 0x0C ++#define PIIX4_BLOCK_DATA 0x14 ++ ++/* insmod parameters */ ++ ++/* If force is set to anything different from 0, we forcibly enable the ++ PIIX4. DANGEROUS! */ ++static int force = 0; ++MODULE_PARM(force, "i"); ++MODULE_PARM_DESC(force, "Forcibly enable the PIIX4. DANGEROUS!"); ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the PIIX4 at the given address. VERY DANGEROUS! */ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Forcibly enable the PIIX4 at the given address. " ++ "EXTREMELY DANGEROUS!"); ++ ++static int fix_hstcfg = 0; ++MODULE_PARM(fix_hstcfg, "i"); ++MODULE_PARM_DESC(fix_hstcfg, ++ "Fix config register. Needed on some boards (Force CPCI735)."); ++ ++static int piix4_transaction(void); ++ ++static unsigned short piix4_smba = 0; ++ ++#ifdef CONFIG_X86 ++/* ++ * Get DMI information. ++ */ ++ ++static int __devinit ibm_dmi_probe(void) ++{ ++ extern int is_unsafe_smbus; ++ return is_unsafe_smbus; ++} ++#endif ++ ++/* Detect whether a PIIX4 can be found, and initialize it, where necessary. ++ Note the differences between kernels with the old PCI BIOS interface and ++ newer kernels with the real PCI interface. In compat.h some things are ++ defined to make the transition easier. */ ++static int __devinit piix4_setup(struct pci_dev *PIIX4_dev, ++ const struct pci_device_id *id) ++{ ++ unsigned char temp; ++ ++ /* match up the function */ ++ if (PCI_FUNC(PIIX4_dev->devfn) != id->driver_data) ++ return -ENODEV; ++ ++ printk(KERN_INFO "Found %s device\n", PIIX4_dev->name); ++ ++#ifdef CONFIG_X86 ++ if(ibm_dmi_probe() && PIIX4_dev->vendor == PCI_VENDOR_ID_INTEL) { ++ printk(KERN_ERR "i2c-piix4.o: IBM Laptop detected; this module " ++ "may corrupt your serial eeprom! Refusing to load " ++ "module!\n"); ++ return -EPERM; ++ } ++#endif ++ ++ /* Determine the address of the SMBus areas */ ++ if (force_addr) { ++ piix4_smba = force_addr & 0xfff0; ++ force = 0; ++ } else { ++ pci_read_config_word(PIIX4_dev, SMBBA, &piix4_smba); ++ piix4_smba &= 0xfff0; ++ if(piix4_smba == 0) { ++ printk(KERN_ERR "i2c-piix4.o: SMB base address " ++ "uninitialized - upgrade BIOS or use " ++ "force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ } ++ ++ if (!request_region(piix4_smba, SMBIOSIZE, "piix4-smbus")) { ++ printk(KERN_ERR "i2c-piix4.o: SMB region 0x%x already in " ++ "use!\n", piix4_smba); ++ return -ENODEV; ++ } ++ ++ pci_read_config_byte(PIIX4_dev, SMBHSTCFG, &temp); ++ ++ /* Some BIOS will set up the chipset incorrectly and leave a register ++ in an undefined state (causing I2C to act very strangely). */ ++ if (temp & 0x02) { ++ if (fix_hstcfg) { ++ printk(KERN_INFO "i2c-piix4.o: Working around buggy " ++ "BIOS (I2C)\n"); ++ temp &= 0xfd; ++ pci_write_config_byte(PIIX4_dev, SMBHSTCFG, temp); ++ } else { ++ printk(KERN_INFO "i2c-piix4.o: Unusual config register " ++ "value\n"); ++ printk(KERN_INFO "i2c-piix4.o: Try using fix_hstcfg=1 " ++ "if you experience problems\n"); ++ } ++ } ++ ++ /* If force_addr is set, we program the new address here. Just to make ++ sure, we disable the PIIX4 first. */ ++ if (force_addr) { ++ pci_write_config_byte(PIIX4_dev, SMBHSTCFG, temp & 0xfe); ++ pci_write_config_word(PIIX4_dev, SMBBA, piix4_smba); ++ pci_write_config_byte(PIIX4_dev, SMBHSTCFG, temp | 0x01); ++ printk(KERN_INFO "i2c-piix4.o: WARNING: SMBus interface set to " ++ "new address %04x!\n", piix4_smba); ++ } else if ((temp & 1) == 0) { ++ if (force) { ++ /* This should never need to be done, but has been ++ * noted that many Dell machines have the SMBus ++ * interface on the PIIX4 disabled!? NOTE: This assumes ++ * I/O space and other allocations WERE done by the ++ * Bios! Don't complain if your hardware does weird ++ * things after enabling this. :') Check for Bios ++ * updates before resorting to this. ++ */ ++ pci_write_config_byte(PIIX4_dev, SMBHSTCFG, ++ temp | 1); ++ printk(KERN_NOTICE "i2c-piix4.o: WARNING: SMBus " ++ "interface has been FORCEFULLY ENABLED!\n"); ++ } else { ++ printk(KERN_ERR "i2c-piix4.o: Host SMBus controller " ++ "not enabled!\n"); ++ release_region(piix4_smba, SMBIOSIZE); ++ piix4_smba = 0; ++ return -ENODEV; ++ } ++ } ++ ++#ifdef DEBUG ++ if ((temp & 0x0E) == 8) ++ printk(KERN_DEBUG "i2c-piix4.o: Using Interrupt 9 for " ++ "SMBus.\n"); ++ else if ((temp & 0x0E) == 0) ++ printk(KERN_DEBUG "i2c-piix4.o: Using Interrupt SMI# " ++ "for SMBus.\n"); ++ else ++ printk(KERN_ERR "i2c-piix4.o: Illegal Interrupt configuration " ++ "(or code out of date)!\n"); ++ ++ pci_read_config_byte(PIIX4_dev, SMBREV, &temp); ++ printk(KERN_DEBUG "i2c-piix4.o: SMBREV = 0x%X\n", temp); ++ printk(KERN_DEBUG "i2c-piix4.o: SMBA = 0x%X\n", piix4_smba); ++#endif /* DEBUG */ ++ ++ return 0; ++} ++ ++ ++/* Another internally used function */ ++int piix4_transaction(void) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++#ifdef DEBUG ++ printk ++ (KERN_DEBUG "i2c-piix4.o: Transaction (pre): CNT=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, " ++ "DAT1=%02x\n", inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), ++ inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); ++#endif ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "i2c-piix4.o: SMBus busy (%02x). Resetting... \n", ++ temp); ++#endif ++ outb_p(temp, SMBHSTSTS); ++ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { ++#ifdef DEBUG ++ printk(KERN_ERR "i2c-piix4.o: Failed! (%02x)\n", temp); ++#endif ++ return -1; ++ } else { ++#ifdef DEBUG ++ printk(KERN_DEBUG "i2c-piix4.o: Successfull!\n"); ++#endif ++ } ++ } ++ ++ /* start the transaction by setting bit 6 */ ++ outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT); ++ ++ /* We will always wait for a fraction of a second! (See PIIX4 docs errata) */ ++ do { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT)); ++ ++#ifdef DEBUG ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ printk(KERN_ERR "i2c-piix4.o: SMBus Timeout!\n"); ++ result = -1; ++ } ++#endif ++ ++ if (temp & 0x10) { ++ result = -1; ++#ifdef DEBUG ++ printk(KERN_ERR "i2c-piix4.o: Error: Failed bus transaction\n"); ++#endif ++ } ++ ++ if (temp & 0x08) { ++ result = -1; ++ printk ++ (KERN_ERR "i2c-piix4.o: Bus collision! SMBus may be locked until next hard\n" ++ "reset. (sorry!)\n"); ++ /* Clock stops and slave is stuck in mid-transmission */ ++ } ++ ++ if (temp & 0x04) { ++ result = -1; ++#ifdef DEBUG ++ printk(KERN_ERR "i2c-piix4.o: Error: no response!\n"); ++#endif ++ } ++ ++ if (inb_p(SMBHSTSTS) != 0x00) ++ outb_p(inb(SMBHSTSTS), SMBHSTSTS); ++ ++#ifdef DEBUG ++ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { ++ printk ++ (KERN_ERR "i2c-piix4.o: Failed reset at end of transaction (%02x)\n", ++ temp); ++ } ++ printk ++ (KERN_DEBUG "i2c-piix4.o: Transaction (post): CNT=%02x, CMD=%02x, ADD=%02x, " ++ "DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), ++ inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); ++#endif ++ return result; ++} ++ ++/* Return -1 on error. */ ++s32 piix4_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data * data) ++{ ++ int i, len; ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = PIIX4_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMBHSTCMD); ++ size = PIIX4_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, SMBHSTDAT0); ++ size = PIIX4_BYTE_DATA; ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT0); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); ++ } ++ size = PIIX4_WORD_DATA; ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 0) ++ len = 0; ++ if (len > 32) ++ len = 32; ++ outb_p(len, SMBHSTDAT0); ++ i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= len; i++) ++ outb_p(data->block[i], SMBBLKDAT); ++ } ++ size = PIIX4_BLOCK_DATA; ++ break; ++ default: ++ printk ++ (KERN_WARNING "i2c-piix4.o: Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ outb_p((size & 0x1C) + (ENABLE_INT9 & 1), SMBHSTCNT); ++ ++ if (piix4_transaction()) /* Error in transaction */ ++ return -1; ++ ++ if ((read_write == I2C_SMBUS_WRITE) || (size == PIIX4_QUICK)) ++ return 0; ++ ++ ++ switch (size) { ++ case PIIX4_BYTE: /* Where is the result put? I assume here it is in ++ SMBHSTDAT0 but it might just as well be in the ++ SMBHSTCMD. No clue in the docs */ ++ ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case PIIX4_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case PIIX4_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); ++ break; ++ case PIIX4_BLOCK_DATA: ++ data->block[0] = inb_p(SMBHSTDAT0); ++ i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= data->block[0]; i++) ++ data->block[i] = inb_p(SMBBLKDAT); ++ break; ++ } ++ return 0; ++} ++ ++ ++u32 piix4_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = piix4_access, ++ .functionality = piix4_func, ++}; ++ ++static struct i2c_adapter piix4_adapter = { ++ .owner = THIS_MODULE, ++ .name = "unset", ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_PIIX4, ++ .algo = &smbus_algorithm, ++}; ++ ++#ifndef PCI_DEVICE_ID_SERVERWORKS_CSB6 ++#define PCI_DEVICE_ID_SERVERWORKS_CSB6 0x0203 ++#endif ++ ++static struct pci_device_id piix4_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82371AB_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = 3 ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_SERVERWORKS, ++ .device = PCI_DEVICE_ID_SERVERWORKS_OSB4, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = 0, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_SERVERWORKS, ++ .device = PCI_DEVICE_ID_SERVERWORKS_CSB5, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = 0, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_SERVERWORKS, ++ .device = PCI_DEVICE_ID_SERVERWORKS_CSB6, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = 0, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_DEVICE_ID_INTEL_82443MX_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = 3, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_EFAR, ++ .device = PCI_DEVICE_ID_EFAR_SLC90E66_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = 0, ++ }, ++ { 0, } ++}; ++ ++static int __devinit piix4_probe(struct pci_dev *dev, ++ const struct pci_device_id *id) ++{ ++ int retval; ++ ++ retval = piix4_setup(dev, id); ++ if (retval) ++ return retval; ++ ++ sprintf(piix4_adapter.name, "SMBus PIIX4 adapter at %04x", ++ piix4_smba); ++ ++ if ((retval = i2c_add_adapter(&piix4_adapter))) { ++ printk(KERN_ERR "i2c-piix4.o: Couldn't register adapter!\n"); ++ release_region(piix4_smba, SMBIOSIZE); ++ piix4_smba = 0; ++ } ++ ++ return retval; ++} ++ ++static void __devexit piix4_remove(struct pci_dev *dev) ++{ ++ if (piix4_smba) { ++ i2c_del_adapter(&piix4_adapter); ++ release_region(piix4_smba, SMBIOSIZE); ++ piix4_smba = 0; ++ } ++} ++ ++static struct pci_driver piix4_driver = { ++ .name = "piix4 smbus", ++ .id_table = piix4_ids, ++ .probe = piix4_probe, ++ .remove = __devexit_p(piix4_remove), ++}; ++ ++static int __init i2c_piix4_init(void) ++{ ++ printk("i2c-piix4.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&piix4_driver); ++} ++ ++static void __exit i2c_piix4_exit(void) ++{ ++ pci_unregister_driver(&piix4_driver); ++} ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and " ++ "Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("PIIX4 SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_piix4_init); ++module_exit(i2c_piix4_exit); +--- linux-old/drivers/i2c/i2c-savage4.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-savage4.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,229 @@ ++/* ++ i2c-savage4.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (C) 1998-2003 The LM Sensors Team ++ Alexander Wold <awold@bigfoot.com> ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ Based on i2c-voodoo3.c. ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* This interfaces to the I2C bus of the Savage4 to gain access to ++ the BT869 and possibly other I2C devices. The DDC bus is not ++ yet supported because its register is not memory-mapped. ++ However we leave the DDC code here, commented out, to make ++ it easier to add later. ++*/ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/i2c.h> ++#include <linux/i2c-algo-bit.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/param.h> /* for HZ */ ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* 3DFX defines */ ++/* #define PCI_VENDOR_ID_S3 0x5333 */ ++#define PCI_CHIP_SAVAGE3D 0x8A20 ++#define PCI_CHIP_SAVAGE3D_MV 0x8A21 ++#define PCI_CHIP_SAVAGE4 0x8A22 ++#define PCI_CHIP_SAVAGE2000 0x9102 ++#define PCI_CHIP_PROSAVAGE_PM 0x8A25 ++#define PCI_CHIP_PROSAVAGE_KM 0x8A26 ++#define PCI_CHIP_SAVAGE_MX_MV 0x8c10 ++#define PCI_CHIP_SAVAGE_MX 0x8c11 ++#define PCI_CHIP_SAVAGE_IX_MV 0x8c12 ++#define PCI_CHIP_SAVAGE_IX 0x8c13 ++ ++#define REG 0xff20 /* Serial Port 1 Register */ ++ ++/* bit locations in the register */ ++//#define DDC_ENAB 0x00040000 ++//#define DDC_SCL_OUT 0x00080000 ++//#define DDC_SDA_OUT 0x00100000 ++//#define DDC_SCL_IN 0x00200000 ++//#define DDC_SDA_IN 0x00400000 ++#define I2C_ENAB 0x00000020 ++#define I2C_SCL_OUT 0x00000001 ++#define I2C_SDA_OUT 0x00000002 ++#define I2C_SCL_IN 0x00000008 ++#define I2C_SDA_IN 0x00000010 ++ ++/* initialization states */ ++#define INIT2 0x20 ++/* #define INIT3 0x4 */ ++ ++/* delays */ ++#define CYCLE_DELAY 10 ++#define TIMEOUT (HZ / 2) ++ ++ ++static void config_s4(struct pci_dev *dev); ++ ++static unsigned long ioaddr; ++ ++/* The sav GPIO registers don't have individual masks for each bit ++ so we always have to read before writing. */ ++ ++static void bit_savi2c_setscl(void *data, int val) ++{ ++ unsigned int r; ++ r = readl(ioaddr + REG); ++ if(val) ++ r |= I2C_SCL_OUT; ++ else ++ r &= ~I2C_SCL_OUT; ++ writel(r, ioaddr + REG); ++ readl(ioaddr + REG); /* flush posted write */ ++} ++ ++static void bit_savi2c_setsda(void *data, int val) ++{ ++ unsigned int r; ++ r = readl(ioaddr + REG); ++ if(val) ++ r |= I2C_SDA_OUT; ++ else ++ r &= ~I2C_SDA_OUT; ++ writel(r, ioaddr + REG); ++ readl(ioaddr + REG); /* flush posted write */ ++} ++ ++/* The GPIO pins are open drain, so the pins always remain outputs. ++ We rely on the i2c-algo-bit routines to set the pins high before ++ reading the input from other chips. */ ++ ++static int bit_savi2c_getscl(void *data) ++{ ++ return (0 != (readl(ioaddr + REG) & I2C_SCL_IN)); ++} ++ ++static int bit_savi2c_getsda(void *data) ++{ ++ return (0 != (readl(ioaddr + REG) & I2C_SDA_IN)); ++} ++ ++/* Configures the chip */ ++ ++void config_s4(struct pci_dev *dev) ++{ ++ unsigned int cadr; ++ ++ /* map memory */ ++ cadr = dev->resource[0].start; ++ cadr &= PCI_BASE_ADDRESS_MEM_MASK; ++ ioaddr = (unsigned long)ioremap_nocache(cadr, 0x0080000); ++ if(ioaddr) { ++// writel(0x8160, ioaddr + REG2); ++ writel(0x00000020, ioaddr + REG); ++ printk("i2c-savage4: Using Savage4 at 0x%lx\n", ioaddr); ++ } ++} ++ ++ ++static struct i2c_algo_bit_data sav_i2c_bit_data = { ++ .setsda = bit_savi2c_setsda, ++ .setscl = bit_savi2c_setscl, ++ .getsda = bit_savi2c_getsda, ++ .getscl = bit_savi2c_getscl, ++ .udelay = CYCLE_DELAY, ++ .mdelay = CYCLE_DELAY, ++ .timeout = TIMEOUT ++}; ++ ++static struct i2c_adapter savage4_i2c_adapter = { ++ .owner = THIS_MODULE, ++ .name = "I2C Savage4 adapter", ++ .id = I2C_HW_B_SAVG, ++ .algo_data = &sav_i2c_bit_data, ++}; ++ ++static struct pci_device_id savage4_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_S3, ++ .device = PCI_CHIP_SAVAGE4, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_S3, ++ .device = PCI_CHIP_SAVAGE2000, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit savage4_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ config_s4(dev); ++ return i2c_bit_add_bus(&savage4_i2c_adapter); ++} ++ ++static void __devexit savage4_remove(struct pci_dev *dev) ++{ ++ i2c_bit_del_bus(&savage4_i2c_adapter); ++} ++ ++ ++/* Don't register driver to avoid driver conflicts */ ++/* ++static struct pci_driver savage4_driver = { ++ .name = "savage4 smbus", ++ .id_table = savage4_ids, ++ .probe = savage4_probe, ++ .remove = __devexit_p(savage4_remove), ++}; ++*/ ++ ++static int __init i2c_savage4_init(void) ++{ ++ struct pci_dev *dev; ++ const struct pci_device_id *id; ++ ++ printk("i2c-savage4.o version %s (%s)\n", LM_VERSION, LM_DATE); ++/* ++ return pci_module_init(&savage4_driver); ++*/ ++ pci_for_each_dev(dev) { ++ id = pci_match_device(savage4_ids, dev); ++ if(id) ++ if(savage4_probe(dev, id) >= 0) ++ return 0; ++ } ++ return -ENODEV; ++} ++ ++static void __exit i2c_savage4_exit(void) ++{ ++/* ++ pci_unregister_driver(&savage4_driver); ++*/ ++ savage4_remove(NULL); ++ iounmap((void *)ioaddr); ++} ++ ++MODULE_AUTHOR("Alexander Wold <awold@bigfoot.com> " ++ "and Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("Savage4 I2C/SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_savage4_init); ++module_exit(i2c_savage4_exit); +--- linux-old/drivers/i2c/i2c-sis5595.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-sis5595.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,481 @@ ++/* ++ sis5595.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Note: we assume there can only be one SIS5595 with one SMBus interface */ ++ ++/* ++ Note: all have mfr. ID 0x1039. ++ SUPPORTED PCI ID ++ 5595 0008 ++ ++ Note: these chips contain a 0008 device which is incompatible with the ++ 5595. We recognize these by the presence of the listed ++ "blacklist" PCI ID and refuse to load. ++ ++ NOT SUPPORTED PCI ID BLACKLIST PCI ID ++ 540 0008 0540 ++ 550 0008 0550 ++ 5513 0008 5511 ++ 5581 0008 5597 ++ 5582 0008 5597 ++ 5597 0008 5597 ++ 5598 0008 5597/5598 ++ 630 0008 0630 ++ 645 0008 0645 ++ 646 0008 0646 ++ 648 0008 0648 ++ 650 0008 0650 ++ 651 0008 0651 ++ 730 0008 0730 ++ 735 0008 0735 ++ 745 0008 0745 ++ 746 0008 0746 ++*/ ++ ++/* TO DO: ++ * Add Block Transfers (ugly, but supported by the adapter) ++ * Add adapter resets ++ */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++static int blacklist[] = { ++ PCI_DEVICE_ID_SI_540, ++ PCI_DEVICE_ID_SI_550, ++ PCI_DEVICE_ID_SI_630, ++ PCI_DEVICE_ID_SI_730, ++ PCI_DEVICE_ID_SI_5511, /* 5513 chip has the 0008 device but ++ that ID shows up in other chips so we ++ use the 5511 ID for recognition */ ++ PCI_DEVICE_ID_SI_5597, ++ PCI_DEVICE_ID_SI_5598, ++ 0x645, ++ 0x646, ++ 0x648, ++ 0x650, ++ 0x651, ++ 0x735, ++ 0x745, ++ 0x746, ++ 0 }; ++ ++/* Length of ISA address segment */ ++#define SIS5595_EXTENT 8 ++/* SIS5595 SMBus registers */ ++#define SMB_STS_LO 0x00 ++#define SMB_STS_HI 0x01 ++#define SMB_CTL_LO 0x02 ++#define SMB_CTL_HI 0x03 ++#define SMB_ADDR 0x04 ++#define SMB_CMD 0x05 ++#define SMB_PCNT 0x06 ++#define SMB_CNT 0x07 ++#define SMB_BYTE 0x08 ++#define SMB_DEV 0x10 ++#define SMB_DB0 0x11 ++#define SMB_DB1 0x12 ++#define SMB_HAA 0x13 ++ ++/* PCI Address Constants */ ++#define SMB_INDEX 0x38 ++#define SMB_DAT 0x39 ++#define SIS5595_ENABLE_REG 0x40 ++#define ACPI_BASE 0x90 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++ ++/* SIS5595 constants */ ++#define SIS5595_QUICK 0x00 ++#define SIS5595_BYTE 0x02 ++#define SIS5595_BYTE_DATA 0x04 ++#define SIS5595_WORD_DATA 0x06 ++#define SIS5595_PROC_CALL 0x08 ++#define SIS5595_BLOCK_DATA 0x0A ++ ++/* insmod parameters */ ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the device at the given address. */ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the i2c controller"); ++ ++static int sis5595_transaction(void); ++ ++static unsigned short sis5595_base = 0; ++ ++static u8 sis5595_read(u8 reg) ++{ ++ outb(reg, sis5595_base + SMB_INDEX); ++ return inb(sis5595_base + SMB_DAT); ++} ++ ++static void sis5595_write(u8 reg, u8 data) ++{ ++ outb(reg, sis5595_base + SMB_INDEX); ++ outb(data, sis5595_base + SMB_DAT); ++} ++ ++ ++/* Detect whether a SIS5595 can be found, and initialize it, where necessary. ++ Note the differences between kernels with the old PCI BIOS interface and ++ newer kernels with the real PCI interface. In compat.h some things are ++ defined to make the transition easier. */ ++int sis5595_setup(struct pci_dev *SIS5595_dev) ++{ ++ u16 a; ++ u8 val; ++ int *i; ++ ++ /* Look for imposters */ ++ for(i = blacklist; *i != 0; i++) { ++ if (pci_find_device(PCI_VENDOR_ID_SI, *i, NULL)) { ++ printk("i2c-sis5595.o: Error: Looked for SIS5595 but found unsupported device %.4X\n", *i); ++ return -ENODEV; ++ } ++ } ++ ++/* Determine the address of the SMBus areas */ ++ pci_read_config_word(SIS5595_dev, ACPI_BASE, &sis5595_base); ++ if(sis5595_base == 0 && force_addr == 0) { ++ printk("i2c-sis5595.o: ACPI base address uninitialized - upgrade BIOS or use force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ ++ if(force_addr) ++ sis5595_base = force_addr & ~(SIS5595_EXTENT - 1); ++#ifdef DEBUG ++ printk("ACPI Base address: %04x\n", sis5595_base); ++#endif ++ /* NB: We grab just the two SMBus registers here, but this may still ++ * interfere with ACPI :-( */ ++ if (check_region(sis5595_base + SMB_INDEX, 2)) { ++ printk ++ ("i2c-sis5595.o: SMBus registers 0x%04x-0x%04x already in use!\n", ++ sis5595_base + SMB_INDEX, ++ sis5595_base + SMB_INDEX + 1); ++ return -ENODEV; ++ } ++ ++ if(force_addr) { ++ printk("i2c-sis5595.o: forcing ISA address 0x%04X\n", sis5595_base); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(SIS5595_dev, ACPI_BASE, sis5595_base)) ++ return -ENODEV; ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(SIS5595_dev, ACPI_BASE, &a)) ++ return -ENODEV; ++ if ((a & ~(SIS5595_EXTENT - 1)) != sis5595_base) { ++ /* doesn't work for some chips! */ ++ printk("i2c-sis5595.o: force address failed - not supported?\n"); ++ return -ENODEV; ++ } ++ } ++ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, &val)) ++ return -ENODEV; ++ if((val & 0x80) == 0) { ++ printk("sis5595.o: enabling ACPI\n"); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, ++ val | 0x80)) ++ return -ENODEV; ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, &val)) ++ return -ENODEV; ++ if((val & 0x80) == 0) { /* doesn't work for some chips? */ ++ printk("sis5595.o: ACPI enable failed - not supported?\n"); ++ return -ENODEV; ++ } ++ } ++ ++ /* Everything is happy, let's grab the memory and set things up. */ ++ request_region(sis5595_base + SMB_INDEX, 2, "sis5595-smbus"); ++ return(0); ++} ++ ++ ++/* Another internally used function */ ++int sis5595_transaction(void) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ if ( ++ (temp = ++ sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8)) != ++ 0x00) { ++#ifdef DEBUG ++ printk("i2c-sis5595.o: SMBus busy (%04x). Resetting... \n", ++ temp); ++#endif ++ sis5595_write(SMB_STS_LO, temp & 0xff); ++ sis5595_write(SMB_STS_HI, temp >> 8); ++ if ( ++ (temp = ++ sis5595_read(SMB_STS_LO) + ++ (sis5595_read(SMB_STS_HI) << 8)) != 0x00) { ++#ifdef DEBUG ++ printk("i2c-sis5595.o: Failed! (%02x)\n", temp); ++#endif ++ return -1; ++ } else { ++#ifdef DEBUG ++ printk("i2c-sis5595.o: Successfull!\n"); ++#endif ++ } ++ } ++ ++ /* start the transaction by setting bit 4 */ ++ sis5595_write(SMB_CTL_LO, sis5595_read(SMB_CTL_LO) | 0x10); ++ ++ /* We will always wait for a fraction of a second! */ ++ do { ++ i2c_delay(1); ++ temp = sis5595_read(SMB_STS_LO); ++ } while (!(temp & 0x40) && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++#ifdef DEBUG ++ printk("i2c-sis5595.o: SMBus Timeout!\n"); ++#endif ++ result = -1; ++ } ++ ++ if (temp & 0x10) { ++ result = -1; ++#ifdef DEBUG ++ printk("i2c-sis5595.o: Error: Failed bus transaction\n"); ++#endif ++ } ++ ++ if (temp & 0x20) { ++ result = -1; ++ printk ++ ("i2c-sis5595.o: Bus collision! SMBus may be locked until next hard\n" ++ "reset (or not...)\n"); ++ /* Clock stops and slave is stuck in mid-transmission */ ++ } ++ ++ if ( ++ (temp = ++ sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8)) != ++ 0x00) { ++ sis5595_write(SMB_STS_LO, temp & 0xff); ++ sis5595_write(SMB_STS_HI, temp >> 8); ++ } ++ ++ if ( ++ (temp = ++ sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8)) != ++ 0x00) { ++ ++#ifdef DEBUG ++ printk ++ ("i2c-sis5595.o: Failed reset at end of transaction (%02x)\n", ++ temp); ++#endif ++ } ++ return result; ++} ++ ++/* Return -1 on error. */ ++s32 sis5595_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data * data) ++{ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ sis5595_write(SMB_ADDR, ++ ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ size = SIS5595_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ sis5595_write(SMB_ADDR, ++ ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ if (read_write == I2C_SMBUS_WRITE) ++ sis5595_write(SMB_CMD, command); ++ size = SIS5595_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ sis5595_write(SMB_ADDR, ++ ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis5595_write(SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) ++ sis5595_write(SMB_BYTE, data->byte); ++ size = SIS5595_BYTE_DATA; ++ break; ++ case I2C_SMBUS_PROC_CALL: ++ case I2C_SMBUS_WORD_DATA: ++ sis5595_write(SMB_ADDR, ++ ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis5595_write(SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) { ++ sis5595_write(SMB_BYTE, data->word & 0xff); ++ sis5595_write(SMB_BYTE + 1, ++ (data->word & 0xff00) >> 8); ++ } ++ size = ++ (size == ++ I2C_SMBUS_PROC_CALL) ? SIS5595_PROC_CALL : ++ SIS5595_WORD_DATA; ++ break; ++/* ++ case I2C_SMBUS_BLOCK_DATA: ++ printk("sis5595.o: Block data not yet implemented!\n"); ++ return -1; ++ break; ++*/ ++ default: ++ printk ++ (KERN_WARNING "sis5595.o: Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ sis5595_write(SMB_CTL_LO, ((size & 0x0E))); ++ ++ if (sis5595_transaction()) /* Error in transaction */ ++ return -1; ++ ++ if ((size != SIS5595_PROC_CALL) && ++ ((read_write == I2C_SMBUS_WRITE) || (size == SIS5595_QUICK))) ++ return 0; ++ ++ ++ switch (size) { ++ case SIS5595_BYTE: /* Where is the result put? I assume here it is in ++ SMB_DATA but it might just as well be in the ++ SMB_CMD. No clue in the docs */ ++ case SIS5595_BYTE_DATA: ++ data->byte = sis5595_read(SMB_BYTE); ++ break; ++ case SIS5595_WORD_DATA: ++ case SIS5595_PROC_CALL: ++ data->word = ++ sis5595_read(SMB_BYTE) + ++ (sis5595_read(SMB_BYTE + 1) << 8); ++ break; ++ } ++ return 0; ++} ++ ++ ++u32 sis5595_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_PROC_CALL; ++} ++ ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = sis5595_access, ++ .functionality = sis5595_func, ++}; ++ ++static struct i2c_adapter sis5595_adapter = { ++ .owner = THIS_MODULE, ++ .name = "unset", ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS5595, ++ .algo = &smbus_algorithm, ++}; ++ ++ ++static struct pci_device_id sis5595_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_SI, ++ .device = PCI_DEVICE_ID_SI_503, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit sis5595_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ ++ if (sis5595_setup(dev)) { ++ printk ++ ("i2c-sis5595.o: SIS5595 not detected, module not inserted.\n"); ++ ++ return -ENODEV; ++ } ++ ++ sprintf(sis5595_adapter.name, "SMBus SIS5595 adapter at %04x", ++ sis5595_base + SMB_INDEX); ++ i2c_add_adapter(&sis5595_adapter); ++ ++ return 0; ++} ++ ++static void __devexit sis5595_remove(struct pci_dev *dev) ++{ ++ i2c_del_adapter(&sis5595_adapter); ++ release_region(sis5595_base + SMB_INDEX, 2); ++} ++ ++ ++static struct pci_driver sis5595_driver = { ++ .name = "sis5595 smbus", ++ .id_table = sis5595_ids, ++ .probe = sis5595_probe, ++ .remove = __devexit_p(sis5595_remove), ++}; ++ ++static int __init i2c_sis5595_init(void) ++{ ++ printk("i2c-sis5595.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&sis5595_driver); ++} ++ ++ ++static void __exit i2c_sis5595_exit(void) ++{ ++ pci_unregister_driver(&sis5595_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"); ++MODULE_DESCRIPTION("SIS5595 SMBus driver"); ++ ++module_init(i2c_sis5595_init); ++module_exit(i2c_sis5595_exit); +--- linux-old/drivers/i2c/i2c-sis630.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-sis630.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,527 @@ ++/* ++ i2c-sis630.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ ++ Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Changes: ++ 24.08.2002 ++ Fixed the typo in sis630_access (Thanks to Mark M. Hoffman) ++ Changed sis630_transaction.(Thanks to Mark M. Hoffman) ++ 18.09.2002 ++ Added SIS730 as supported. ++ 21.09.2002 ++ Added high_clock module option.If this option is set ++ used Host Master Clock 56KHz (default 14KHz).For now we save old Host ++ Master Clock and after transaction completed restore (otherwise ++ it's confuse BIOS and hung Machine). ++ 24.09.2002 ++ Fixed typo in sis630_access ++ Fixed logical error by restoring of Host Master Clock ++ 31.07.2003 ++ Added block data read/write support. ++*/ ++ ++/* ++ Status: beta ++ ++ Supports: ++ SIS 630 ++ SIS 730 ++ ++ Note: we assume there can only be one device, with one SMBus interface. ++*/ ++ ++#include <linux/module.h> ++#include <linux/kernel.h> ++#include <linux/pci.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++ ++#ifdef DEBUG ++#define DBG(x...) printk(KERN_DEBUG "i2c-sis630.o: " x) ++#else ++#define DBG(x...) ++#endif ++ ++/* SIS630 SMBus registers */ ++#define SMB_STS 0x80 /* status */ ++#define SMB_EN 0x81 /* status enable */ ++#define SMB_CNT 0x82 ++#define SMBHOST_CNT 0x83 ++#define SMB_ADDR 0x84 ++#define SMB_CMD 0x85 ++#define SMB_PCOUNT 0x86 /* processed count */ ++#define SMB_COUNT 0x87 ++#define SMB_BYTE 0x88 /* ~0x8F data byte field */ ++#define SMBDEV_ADDR 0x90 ++#define SMB_DB0 0x91 ++#define SMB_DB1 0x92 ++#define SMB_SAA 0x93 ++ ++/* register count for request_region */ ++#define SIS630_SMB_IOREGION 20 ++ ++/* PCI address constants */ ++/* acpi base address register */ ++#define SIS630_ACPI_BASE_REG 0x74 ++/* bios control register */ ++#define SIS630_BIOS_CTL_REG 0x40 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++ ++/* SIS630 constants */ ++#define SIS630_QUICK 0x00 ++#define SIS630_BYTE 0x01 ++#define SIS630_BYTE_DATA 0x02 ++#define SIS630_WORD_DATA 0x03 ++#define SIS630_PCALL 0x04 ++#define SIS630_BLOCK_DATA 0x05 ++ ++/* insmod parameters */ ++static int high_clock = 0; ++static int force = 0; ++MODULE_PARM(high_clock, "i"); ++MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz)."); ++MODULE_PARM(force, "i"); ++MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!"); ++ ++/* acpi base address */ ++static unsigned short acpi_base = 0; ++ ++/* supported chips */ ++static int supported[] = { ++ PCI_DEVICE_ID_SI_630, ++ PCI_DEVICE_ID_SI_730, ++ 0 /* terminates the list */ ++}; ++ ++static inline u8 sis630_read(u8 reg) { ++ return inb(acpi_base + reg); ++} ++ ++static inline void sis630_write(u8 reg, u8 data) { ++ outb(data, acpi_base + reg); ++} ++ ++static int sis630_transaction_start(int size, u8 *oldclock) { ++ int temp; ++ ++ /* ++ Make sure the SMBus host is ready to start transmitting. ++ */ ++ if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { ++ DBG("SMBus busy (%02x).Resetting...\n",temp); ++ /* kill smbus transaction */ ++ sis630_write(SMBHOST_CNT, 0x20); ++ ++ if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { ++ DBG("Failed! (%02x)\n", temp); ++ return -1; ++ } else { ++ DBG("Successfull!\n"); ++ } ++ } ++ ++ /* save old clock, so we can prevent machine for hung */ ++ *oldclock = sis630_read(SMB_CNT); ++ ++ DBG("saved clock 0x%02x\n", *oldclock); ++ ++ /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */ ++ if (high_clock > 0) ++ sis630_write(SMB_CNT, 0x20); ++ else ++ sis630_write(SMB_CNT, (*oldclock & ~0x40)); ++ ++ /* clear all sticky bits */ ++ temp = sis630_read(SMB_STS); ++ sis630_write(SMB_STS, temp & 0x1e); ++ ++ /* start the transaction by setting bit 4 and size */ ++ sis630_write(SMBHOST_CNT,0x10 | (size & 0x07)); ++ ++ return 0; ++} ++ ++static int sis630_transaction_wait(int size) { ++ int temp, result = 0, timeout = 0; ++ ++ /* We will always wait for a fraction of a second! */ ++ do { ++ i2c_delay(1); ++ temp = sis630_read(SMB_STS); ++ /* check if block transmitted */ ++ if (size == SIS630_BLOCK_DATA && (temp & 0x10)) ++ break; ++ } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ DBG("SMBus Timeout!\n"); ++ result = -1; ++ } ++ ++ if (temp & 0x02) { ++ result = -1; ++ DBG("Error: Failed bus transaction\n"); ++ } ++ ++ if (temp & 0x04) { ++ result = -1; ++ printk(KERN_ERR "i2c-sis630.o: Bus collision!\n"); ++ /* ++ TBD: Datasheet say: ++ the software should clear this bit and restart SMBUS operation. ++ Should we do it or user start request again? ++ */ ++ } ++ ++ return result; ++} ++ ++static void sis630_transaction_end(u8 oldclock) { ++ int temp = 0; ++ ++ /* clear all status "sticky" bits */ ++ sis630_write(SMB_STS, temp); ++ ++ DBG("SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT)); ++ ++ /* ++ * restore old Host Master Clock if high_clock is set ++ * and oldclock was not 56KHz ++ */ ++ if (high_clock > 0 && !(oldclock & 0x20)) ++ sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20)); ++ ++ DBG("SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT)); ++} ++ ++static int sis630_transaction(int size) { ++ int result = 0; ++ u8 oldclock = 0; ++ ++ if (!(result = sis630_transaction_start(size, &oldclock))) { ++ result = sis630_transaction_wait(size); ++ sis630_transaction_end(oldclock); ++ } ++ ++ return result; ++} ++ ++static int sis630_block_data(union i2c_smbus_data * data, int read_write) { ++ int i, len = 0, rc = 0; ++ u8 oldclock = 0; ++ ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 0) ++ len = 0; ++ else if (len > 32) ++ len = 32; ++ sis630_write(SMB_COUNT, len); ++ for (i=1; i <= len; i++) { ++ DBG("set data 0x%02x\n", data->block[i]); ++ /* set data */ ++ sis630_write(SMB_BYTE+(i-1)%8, data->block[i]); ++ if (i==8 || (len<8 && i==len)) { ++ DBG("start trans len=%d i=%d\n",len ,i); ++ /* first transaction */ ++ if (sis630_transaction_start(SIS630_BLOCK_DATA, &oldclock)) ++ return -1; ++ } ++ else if ((i-1)%8 == 7 || i==len) { ++ DBG("trans_wait len=%d i=%d\n",len,i); ++ if (i>8) { ++ DBG("clear smbary_sts len=%d i=%d\n",len,i); ++ /* ++ If this is not first transaction, ++ we must clear sticky bit. ++ clear SMBARY_STS ++ */ ++ sis630_write(SMB_STS,0x10); ++ } ++ if (sis630_transaction_wait(SIS630_BLOCK_DATA)) { ++ DBG("trans_wait failed\n"); ++ rc = -1; ++ break; ++ } ++ ++ } ++ } ++ } ++ else { /* read request */ ++ data->block[0] = len = 0; ++ if (sis630_transaction_start(SIS630_BLOCK_DATA, &oldclock)) { ++ return -1; ++ } ++ do { ++ if (sis630_transaction_wait(SIS630_BLOCK_DATA)) { ++ DBG("trans_wait failed\n"); ++ rc = -1; ++ break; ++ } ++ /* if this first transaction then read byte count */ ++ if (len == 0) ++ data->block[0] = sis630_read(SMB_COUNT); ++ ++ /* just to be sure */ ++ if (data->block[0] > 32) ++ data->block[0] = 32; ++ ++ DBG("block data read len=0x%x\n", data->block[0]); ++ ++ for (i=0; i < 8 && len < data->block[0]; i++,len++) { ++ DBG("read i=%d len=%d\n", i, len); ++ data->block[len+1] = sis630_read(SMB_BYTE+i); ++ } ++ ++ DBG("clear smbary_sts len=%d i=%d\n",len,i); ++ ++ /* clear SMBARY_STS */ ++ sis630_write(SMB_STS,0x10); ++ } while(len < data->block[0]); ++ } ++ ++ sis630_transaction_end(oldclock); ++ ++ return rc; ++} ++ ++/* Return -1 on error. */ ++static s32 sis630_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data * data) ++{ ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ size = SIS630_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ if (read_write == I2C_SMBUS_WRITE) ++ sis630_write(SMB_CMD, command); ++ size = SIS630_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis630_write(SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) ++ sis630_write(SMB_BYTE, data->byte); ++ size = SIS630_BYTE_DATA; ++ break; ++ case I2C_SMBUS_PROC_CALL: ++ case I2C_SMBUS_WORD_DATA: ++ sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis630_write(SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) { ++ sis630_write(SMB_BYTE, data->word & 0xff); ++ sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8); ++ } ++ size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA); ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis630_write(SMB_CMD, command); ++ size = SIS630_BLOCK_DATA; ++ return sis630_block_data(data, read_write); ++ default: ++ printk("Unsupported I2C size\n"); ++ return -1; ++ break; ++ } ++ ++ ++ if (sis630_transaction(size)) ++ return -1; ++ ++ if ((size != SIS630_PCALL) && ++ ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) { ++ return 0; ++ } ++ ++ switch(size) { ++ case SIS630_BYTE: ++ case SIS630_BYTE_DATA: ++ data->byte = sis630_read(SMB_BYTE); ++ break; ++ case SIS630_PCALL: ++ case SIS630_WORD_DATA: ++ data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8); ++ break; ++ default: ++ return -1; ++ break; ++ } ++ ++ return 0; ++} ++ ++ ++static u32 sis630_func(struct i2c_adapter *adapter) { ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL | ++ I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static int __devinit sis630_setup(struct pci_dev *sis630_dev) { ++ unsigned char b; ++ struct pci_dev *dummy = NULL; ++ int i; ++ ++ /* check for supported SiS devices */ ++ for (i=0; supported[i] > 0; i++) { ++ if ((dummy = pci_find_device(PCI_VENDOR_ID_SI, supported[i], dummy))) ++ break; /* found */ ++ } ++ ++ if (!dummy && force > 0) { ++ printk(KERN_ERR "i2c-sis630.o: WARNING: Can't detect SIS630 compatible device, but " ++ "loading because of force option enabled\n"); ++ } ++ else if (!dummy) { ++ return -ENODEV; ++ } ++ ++ /* ++ Enable ACPI first , so we can accsess reg 74-75 ++ in acpi io space and read acpi base addr ++ */ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) { ++ printk(KERN_ERR "i2c-sis630.o: Error: Can't read bios ctl reg\n"); ++ return -ENODEV; ++ } ++ ++ /* if ACPI already enabled , do nothing */ ++ if (!(b & 0x80) && ++ PCIBIOS_SUCCESSFUL != ++ pci_write_config_byte(sis630_dev,SIS630_BIOS_CTL_REG,b|0x80)) { ++ printk(KERN_ERR "i2c-sis630.o: Error: Can't enable ACPI\n"); ++ return -ENODEV; ++ } ++ /* Determine the ACPI base address */ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) { ++ printk(KERN_ERR "i2c-sis630.o: Error: Can't determine ACPI base address\n"); ++ return -ENODEV; ++ } ++ ++ DBG("ACPI base at 0x%04x\n", acpi_base); ++ ++ /* Everything is happy, let's grab the memory and set things up. */ ++ if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, "sis630-smbus")){ ++ printk(KERN_ERR "i2c-sis630.o: SMBus registers 0x%04x-0x%04x " ++ "already in use!\n",acpi_base + SMB_STS, acpi_base + SMB_SAA); ++ acpi_base = 0; /* reset acpi_base */ ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = sis630_access, ++ .functionality = sis630_func, ++}; ++ ++static struct i2c_adapter sis630_adapter = { ++ .owner = THIS_MODULE, ++ .name = "unset", ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS630, ++ .algo = &smbus_algorithm, ++}; ++ ++ ++static struct pci_device_id sis630_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_SI, ++ .device = PCI_DEVICE_ID_SI_503, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ if (sis630_setup(dev)) { ++ printk(KERN_ERR "i2c-sis630.o: SIS630 comp. bus not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ ++ sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x", ++ acpi_base + SMB_STS); ++ ++ return i2c_add_adapter(&sis630_adapter); ++} ++ ++static void __devexit sis630_remove(struct pci_dev *dev) ++{ ++ if (acpi_base) { ++ i2c_del_adapter(&sis630_adapter); ++ release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION); ++ acpi_base = 0; ++ } ++} ++ ++ ++static struct pci_driver sis630_driver = { ++ .name = "sis630 smbus", ++ .id_table = sis630_ids, ++ .probe = sis630_probe, ++ .remove = __devexit_p(sis630_remove), ++}; ++ ++static int __init i2c_sis630_init(void) ++{ ++ printk("i2c-sis630.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&sis630_driver); ++} ++ ++ ++static void __exit i2c_sis630_exit(void) ++{ ++ pci_unregister_driver(&sis630_driver); ++} ++ ++ ++ ++ ++MODULE_LICENSE("GPL"); ++ ++MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>"); ++MODULE_DESCRIPTION("SIS630 SMBus driver"); ++ ++module_init(i2c_sis630_init); ++module_exit(i2c_sis630_exit); +--- linux-old/drivers/i2c/i2c-sis645.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-sis645.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,590 @@ ++/* ++ sis645.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ ++ Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ This module must be considered BETA unless and until ++ the chipset manufacturer releases a datasheet. ++ ++ The register definitions are based on the SiS630. ++ The method for *finding* the registers is based on trial and error. ++ ++ A history of changes to this file is available by anonymous CVS: ++ http://www2.lm-sensors.nu/~lm78/download.html ++*/ ++ ++/* 25th March 2004 ++ Support for Sis655 chipsets added by Ken Healy ++*/ ++ ++/* ++ Note: we assume there can only be one SiS645 with one SMBus interface ++*/ ++ ++/* #define DEBUG 1 */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/mm.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_compat.h> ++ ++#define DRV_NAME "i2c-sis645" ++ ++/* SiS645DX north bridge (defined in 2.4.21) */ ++#ifndef PCI_DEVICE_ID_SI_646 ++#define PCI_DEVICE_ID_SI_646 0x0646 ++#endif ++ ++/* SiS648 north bridge (defined in 2.4.21) */ ++#ifndef PCI_DEVICE_ID_SI_648 ++#define PCI_DEVICE_ID_SI_648 0x0648 ++#endif ++ ++/* SiS650 north bridge (defined in 2.4.19) */ ++#ifndef PCI_DEVICE_ID_SI_650 ++#define PCI_DEVICE_ID_SI_650 0x0650 ++#endif ++ ++/* SiS651 north bridge (defined in 2.4.21)*/ ++#ifndef PCI_DEVICE_ID_SI_651 ++#define PCI_DEVICE_ID_SI_651 0x0651 ++#endif ++ ++/* SiS655 north bridge (defined in 2.4.22)*/ ++#ifndef PCI_DEVICE_ID_SI_655 ++#define PCI_DEVICE_ID_SI_655 0x0655 ++#endif ++ ++/* SiS746 north bridge (defined in 2.4.21) */ ++#ifndef PCI_DEVICE_ID_SI_746 ++#define PCI_DEVICE_ID_SI_746 0x0746 ++#endif ++ ++/* SiS85C503/5513 (LPC Bridge) */ ++#ifndef PCI_DEVICE_ID_SI_LPC ++#define PCI_DEVICE_ID_SI_LPC 0x0018 ++#endif ++ ++/* SiS961 south bridge */ ++#ifndef PCI_DEVICE_ID_SI_961 ++#define PCI_DEVICE_ID_SI_961 0x0961 ++#endif ++ ++/* SiS962 south bridge */ ++#ifndef PCI_DEVICE_ID_SI_962 ++#define PCI_DEVICE_ID_SI_962 0x0962 ++#endif ++ ++/* SiS963 south bridge */ ++#ifndef PCI_DEVICE_ID_SI_963 ++#define PCI_DEVICE_ID_SI_963 0x0963 ++#endif ++ ++/* SMBus ID */ ++#ifndef PCI_DEVICE_ID_SI_SMBUS ++#define PCI_DEVICE_ID_SI_SMBUS 0x16 ++#endif ++ ++/* base address register in PCI config space */ ++#define SIS645_BAR 0x04 ++ ++/* SiS645 SMBus registers */ ++#define SMB_STS 0x00 ++#define SMB_EN 0x01 ++#define SMB_CNT 0x02 ++#define SMB_HOST_CNT 0x03 ++#define SMB_ADDR 0x04 ++#define SMB_CMD 0x05 ++#define SMB_PCOUNT 0x06 ++#define SMB_COUNT 0x07 ++#define SMB_BYTE 0x08 ++#define SMB_DEV_ADDR 0x10 ++#define SMB_DB0 0x11 ++#define SMB_DB1 0x12 ++#define SMB_SAA 0x13 ++ ++/* register count for request_region */ ++#define SMB_IOSIZE 0x20 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++ ++/* SiS645 SMBus constants */ ++#define SIS645_QUICK 0x00 ++#define SIS645_BYTE 0x01 ++#define SIS645_BYTE_DATA 0x02 ++#define SIS645_WORD_DATA 0x03 ++#define SIS645_PROC_CALL 0x04 ++#define SIS645_BLOCK_DATA 0x05 ++ ++static struct i2c_adapter sis645_adapter; ++static u16 sis645_smbus_base = 0; ++ ++static inline u8 sis645_read(u8 reg) ++{ ++ return inb(sis645_smbus_base + reg) ; ++} ++ ++static inline void sis645_write(u8 reg, u8 data) ++{ ++ outb(data, sis645_smbus_base + reg) ; ++} ++ ++#ifdef CONFIG_HOTPLUG ++ ++/* Turns on SMBus device if it is not; return 0 iff successful ++ */ ++static int __devinit sis645_enable_smbus(struct pci_dev *dev) ++{ ++ u8 val = 0; ++ ++ pci_read_config_byte(dev, 0x77, &val); ++ ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Config byte was 0x%02x.\n", val); ++#endif ++ ++ pci_write_config_byte(dev, 0x77, val & ~0x10); ++ ++ pci_read_config_byte(dev, 0x77, &val); ++ ++ if (val & 0x10) { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Config byte stuck!\n"); ++#endif ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* Builds the basic pci_dev for SiS645 SMBus ++ */ ++static int __devinit sis645_build_dev(struct pci_dev **smbus_dev, ++ struct pci_dev *bridge_dev) ++{ ++ struct pci_dev temp_dev; ++ u16 vid = 0, did = 0; ++ int ret; ++ ++ /* fill in the device structure for search */ ++ memset(&temp_dev, 0, sizeof(temp_dev)); ++ temp_dev.bus = bridge_dev->bus; ++ temp_dev.sysdata = bridge_dev->bus->sysdata; ++ temp_dev.hdr_type = PCI_HEADER_TYPE_NORMAL; ++ ++ /* the SMBus device is function 1 on the same unit as the ISA bridge */ ++ temp_dev.devfn = bridge_dev->devfn + 1; ++ ++ /* query to make sure */ ++ ret = pci_read_config_word(&temp_dev, PCI_VENDOR_ID, &vid); ++ if (ret || PCI_VENDOR_ID_SI != vid) { ++ printk(KERN_ERR DRV_NAME ": Couldn't find SMBus device!\n"); ++ return ret; ++ } ++ temp_dev.vendor = vid; ++ ++ ret = pci_read_config_word(&temp_dev, PCI_DEVICE_ID, &did); ++ if (ret || PCI_DEVICE_ID_SI_SMBUS != did) { ++ printk(KERN_ERR DRV_NAME ": Couldn't find SMBus device!\n"); ++ return ret; ++ } ++ temp_dev.device = did; ++ ++ /* ok, we've got it... request some memory and finish it off */ ++ *smbus_dev = kmalloc(sizeof(**smbus_dev), GFP_ATOMIC); ++ if (NULL == *smbus_dev) { ++ printk(KERN_ERR DRV_NAME ": Out of memory!\n"); ++ return -ENOMEM; ++ } ++ ++ **smbus_dev = temp_dev; ++ ++ ret = pci_setup_device(*smbus_dev); ++ if (ret) { ++ printk(KERN_ERR DRV_NAME ": pci_setup_device failed (0x%08x)\n",ret); ++ } ++ return ret; ++} ++ ++/* See if a SMBus can be found, and enable it if possible. ++ */ ++static int __devinit sis645_hotplug_smbus(void) ++{ ++ int ret; ++ struct pci_dev *smbus_dev, *bridge_dev ; ++ ++ if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_961, NULL))) ++ printk(KERN_INFO DRV_NAME ": Found SiS961 south bridge.\n"); ++ ++ else if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_962, NULL))) ++ printk(KERN_INFO DRV_NAME ": Found SiS962 [MuTIOL Media IO].\n"); ++ ++ else if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_963, NULL))) ++ printk(KERN_INFO DRV_NAME ": Found SiS963 [MuTIOL Media IO].\n"); ++ ++ else if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_503, NULL)) || ++ (bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_LPC, NULL))) { ++ ++ printk(KERN_INFO DRV_NAME ": Found SiS south bridge in compatability mode(?)\n"); ++ ++ /* look for known compatible north bridges */ ++ if ((NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_645, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_646, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_648, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_650, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_651, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_655, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_735, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_745, NULL)) ++ && (NULL == pci_find_device(PCI_VENDOR_ID_SI, ++ PCI_DEVICE_ID_SI_746, NULL))) { ++ printk(KERN_ERR DRV_NAME ": Can't find suitable host bridge!\n"); ++ return -ENODEV; ++ } ++ } else { ++ printk(KERN_ERR DRV_NAME ": Can't find suitable south bridge!\n"); ++ return -ENODEV; ++ } ++ ++ /* if we get this far, we think the smbus device is present */ ++ ++ if ((ret = sis645_enable_smbus(bridge_dev))) ++ return ret; ++ ++ if ((ret = sis645_build_dev(&smbus_dev, bridge_dev))) ++ return ret; ++ ++ if ((ret = pci_enable_device(smbus_dev))) { ++ printk(KERN_ERR DRV_NAME ": Can't pci_enable SMBus device!" ++ " (0x%08x)\n", ret); ++ return ret; ++ } ++ ++ pci_insert_device(smbus_dev, smbus_dev->bus); ++ ++ return 0; ++} ++#endif /* CONFIG_HOTPLUG */ ++ ++/* Execute a SMBus transaction. ++ int size is from SIS645_QUICK to SIS645_BLOCK_DATA ++ */ ++static int sis645_transaction(int size) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ if (((temp = sis645_read(SMB_CNT)) & 0x03) != 0x00) { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": SMBus busy (0x%02x). Resetting...\n", ++ temp); ++#endif ++ ++ /* kill the transaction */ ++ sis645_write(SMB_HOST_CNT, 0x20); ++ ++ /* check it again */ ++ if (((temp = sis645_read(SMB_CNT)) & 0x03) != 0x00) { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Failed! (0x%02x)\n", temp); ++#endif ++ return -1; ++ } else { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Successful!\n"); ++#endif ++ } ++ } ++ ++ /* Turn off timeout interrupts, set fast host clock */ ++ sis645_write(SMB_CNT, 0x20); ++ ++ /* clear all (sticky) status flags */ ++ temp = sis645_read(SMB_STS); ++ sis645_write(SMB_STS, temp & 0x1e); ++ ++ /* start the transaction by setting bit 4 and size bits */ ++ sis645_write(SMB_HOST_CNT, 0x10 | (size & 0x07)); ++ ++ /* We will always wait for a fraction of a second! */ ++ do { ++ i2c_delay(1); ++ temp = sis645_read(SMB_STS); ++ } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ printk(KERN_DEBUG DRV_NAME ": SMBus Timeout! (0x%02x)\n",temp); ++ result = -1; ++ } ++ ++ /* device error - probably missing ACK */ ++ if (temp & 0x02) { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Failed bus transaction!\n"); ++#endif ++ result = -1; ++ } ++ ++ /* bus collision */ ++ if (temp & 0x04) { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Bus collision!\n"); ++#endif ++ result = -1; ++ } ++ ++ /* Finish up by resetting the bus */ ++ sis645_write(SMB_STS, temp); ++ if ((temp = sis645_read(SMB_STS))) { ++#ifdef DEBUG ++ printk(KERN_DEBUG DRV_NAME ": Failed reset at end of transaction!" ++ " (0x%02x)\n", temp); ++#endif ++ } ++ ++ return result; ++} ++ ++/* Return -1 on error. */ ++static s32 sis645_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data * data) ++{ ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ size = SIS645_QUICK; ++ break; ++ ++ case I2C_SMBUS_BYTE: ++ sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ if (read_write == I2C_SMBUS_WRITE) ++ sis645_write(SMB_CMD, command); ++ size = SIS645_BYTE; ++ break; ++ ++ case I2C_SMBUS_BYTE_DATA: ++ sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis645_write(SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) ++ sis645_write(SMB_BYTE, data->byte); ++ size = SIS645_BYTE_DATA; ++ break; ++ ++ case I2C_SMBUS_PROC_CALL: ++ case I2C_SMBUS_WORD_DATA: ++ sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); ++ sis645_write(SMB_CMD, command); ++ if (read_write == I2C_SMBUS_WRITE) { ++ sis645_write(SMB_BYTE, data->word & 0xff); ++ sis645_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8); ++ } ++ size = (size == I2C_SMBUS_PROC_CALL ? SIS645_PROC_CALL : SIS645_WORD_DATA); ++ break; ++ ++ case I2C_SMBUS_BLOCK_DATA: ++ /* TO DO: */ ++ printk(KERN_INFO DRV_NAME ": SMBus block not implemented!\n"); ++ return -1; ++ break; ++ ++ default: ++ printk(KERN_INFO DRV_NAME ": Unsupported I2C size\n"); ++ return -1; ++ break; ++ } ++ ++ if (sis645_transaction(size)) ++ return -1; ++ ++ if ((size != SIS645_PROC_CALL) && ++ ((read_write == I2C_SMBUS_WRITE) || (size == SIS645_QUICK))) ++ return 0; ++ ++ switch (size) { ++ case SIS645_BYTE: ++ case SIS645_BYTE_DATA: ++ data->byte = sis645_read(SMB_BYTE); ++ break; ++ ++ case SIS645_WORD_DATA: ++ case SIS645_PROC_CALL: ++ data->word = sis645_read(SMB_BYTE) + ++ (sis645_read(SMB_BYTE + 1) << 8); ++ break; ++ } ++ return 0; ++} ++ ++static u32 sis645_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_PROC_CALL; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = sis645_access, ++ .functionality = sis645_func, ++}; ++ ++static struct i2c_adapter sis645_adapter = { ++ .owner = THIS_MODULE, ++ .name = "unset", ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS645, ++ .algo = &smbus_algorithm, ++}; ++ ++static struct pci_device_id sis645_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_SI, ++ .device = PCI_DEVICE_ID_SI_SMBUS, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit sis645_probe(struct pci_dev *dev, ++ const struct pci_device_id *id) ++{ ++ u16 ww = 0; ++ int retval; ++ ++ if (sis645_smbus_base) { ++ dev_err(dev, "Only one device supported.\n"); ++ return -EBUSY; ++ } ++ ++ pci_read_config_word(dev, PCI_CLASS_DEVICE, &ww); ++ if (PCI_CLASS_SERIAL_SMBUS != ww) { ++ dev_err(dev, "Unsupported device class 0x%04x!\n", ww); ++ return -ENODEV; ++ } ++ ++ sis645_smbus_base = pci_resource_start(dev, SIS645_BAR); ++ if (!sis645_smbus_base) { ++ dev_err(dev, "SiS645 SMBus base address " ++ "not initialized!\n"); ++ return -EINVAL; ++ } ++ dev_info(dev, "SiS645 SMBus base address: 0x%04x\n", ++ sis645_smbus_base); ++ ++ /* Everything is happy, let's grab the memory and set things up. */ ++ if (!request_region(sis645_smbus_base, SMB_IOSIZE, "sis645-smbus")) { ++ dev_err(dev, "SMBus registers 0x%04x-0x%04x " ++ "already in use!\n", sis645_smbus_base, ++ sis645_smbus_base + SMB_IOSIZE - 1); ++ ++ sis645_smbus_base = 0; ++ return -EINVAL; ++ } ++ ++ sprintf(sis645_adapter.name, "SiS645 SMBus adapter at 0x%04x", ++ sis645_smbus_base); ++ ++ if ((retval = i2c_add_adapter(&sis645_adapter))) { ++ dev_err(dev, "Couldn't register adapter!\n"); ++ release_region(sis645_smbus_base, SMB_IOSIZE); ++ sis645_smbus_base = 0; ++ } ++ ++ return retval; ++} ++ ++static void __devexit sis645_remove(struct pci_dev *dev) ++{ ++ if (sis645_smbus_base) { ++ i2c_del_adapter(&sis645_adapter); ++ release_region(sis645_smbus_base, SMB_IOSIZE); ++ sis645_smbus_base = 0; ++ } ++} ++ ++static struct pci_driver sis645_driver = { ++ .name = "sis645 smbus", ++ .id_table = sis645_ids, ++ .probe = sis645_probe, ++ .remove = __devexit_p(sis645_remove), ++}; ++ ++static int __init i2c_sis645_init(void) ++{ ++ printk(KERN_INFO DRV_NAME ".o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ /* if the required device id is not present, try to HOTPLUG it first */ ++ if (!pci_find_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_SMBUS, NULL)) { ++ ++ printk(KERN_INFO DRV_NAME ": " ++ "Attempting to enable SiS645 SMBus device\n"); ++ ++#ifdef CONFIG_HOTPLUG ++ sis645_hotplug_smbus(); ++#else ++ printk(KERN_INFO DRV_NAME ": " ++ "Requires kernel with CONFIG_HOTPLUG, sorry!\n"); ++#endif ++ } ++ ++ return pci_module_init(&sis645_driver); ++} ++ ++static void __exit i2c_sis645_exit(void) ++{ ++ pci_unregister_driver(&sis645_driver); ++} ++ ++MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>"); ++MODULE_DESCRIPTION("SiS645 SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++/* Register initialization functions using helper macros */ ++module_init(i2c_sis645_init); ++module_exit(i2c_sis645_exit); +--- linux-old/drivers/i2c/i2c-tsunami.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-tsunami.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,155 @@ ++/* ++ i2c-tsunami.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2001 Oleg Vdovikin <vdovikin@jscc.ru> ++ ++ Based on code written by Ralph Metzler <rjkm@thp.uni-koeln.de> and ++ Simon Vogl ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* This interfaces to the I2C bus of the Tsunami/Typhoon 21272 chipsets ++ to gain access to the on-board I2C devices. ++ ++ For more information refer to Compaq's ++ "Tsunami/Typhoon 21272 Chipset Hardware Reference Manual" ++ Order Number: DS-0025-TE ++*/ ++ ++#include <linux/module.h> ++#include <linux/i2c.h> ++#include <linux/i2c-algo-bit.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/hwrpb.h> ++#include <asm/core_tsunami.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Memory Presence Detect Register (MPD-RW) bits ++ with except of reserved RAZ bits */ ++ ++#define MPD_DR 0x8 /* Data receive - RO */ ++#define MPD_CKR 0x4 /* Clock receive - RO */ ++#define MPD_DS 0x2 /* Data send - Must be a 1 to receive - WO */ ++#define MPD_CKS 0x1 /* Clock send - WO */ ++ ++static inline void writempd(unsigned long value) ++{ ++ TSUNAMI_cchip->mpd.csr = value; ++ mb(); ++} ++ ++static inline unsigned long readmpd(void) ++{ ++ return TSUNAMI_cchip->mpd.csr; ++} ++ ++static void bit_tsunami_setscl(void *data, int val) ++{ ++ /* read currently setted bits to modify them */ ++ unsigned long bits = readmpd() >> 2; /* assume output == input */ ++ ++ if (val) ++ bits |= MPD_CKS; ++ else ++ bits &= ~MPD_CKS; ++ ++ writempd(bits); ++} ++ ++static void bit_tsunami_setsda(void *data, int val) ++{ ++ /* read currently setted bits to modify them */ ++ unsigned long bits = readmpd() >> 2; /* assume output == input */ ++ ++ if (val) ++ bits |= MPD_DS; ++ else ++ bits &= ~MPD_DS; ++ ++ writempd(bits); ++} ++ ++/* The MPD pins are open drain, so the pins always remain outputs. ++ We rely on the i2c-algo-bit routines to set the pins high before ++ reading the input from other chips. */ ++ ++static int bit_tsunami_getscl(void *data) ++{ ++ return (0 != (readmpd() & MPD_CKR)); ++} ++ ++static int bit_tsunami_getsda(void *data) ++{ ++ return (0 != (readmpd() & MPD_DR)); ++} ++ ++static struct i2c_algo_bit_data tsunami_i2c_bit_data = { ++ .setsda = bit_tsunami_setsda, ++ .setscl = bit_tsunami_setscl, ++ .getsda = bit_tsunami_getsda, ++ .getscl = bit_tsunami_getscl, ++ .udelay = 10, ++ .mdelay = 10, ++ .timeout = HZ/2 ++}; ++ ++static struct i2c_adapter tsunami_i2c_adapter = { ++ .owner = THIS_MODULE, ++ .name = "I2C Tsunami/Typhoon adapter", ++ .id = I2C_HW_B_TSUNA, ++ .algo_data = &tsunami_i2c_bit_data, ++}; ++ ++ ++#if 0 ++static struct pci_driver tsunami_driver = { ++ .name = "tsunami smbus", ++ .id_table = tsunami_ids, ++ .probe = tsunami_probe, ++ .remove = __devexit_p(tsunami_remove), ++}; ++#endif ++ ++static int __init i2c_tsunami_init(void) ++{ ++ printk("i2c-tsunami.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ if (hwrpb->sys_type != ST_DEC_TSUNAMI) { ++ printk("i2c-tsunami.o: not Tsunami based system (%ld), module not inserted.\n", hwrpb->sys_type); ++ return -ENXIO; ++ } else { ++ printk("i2c-tsunami.o: using Cchip MPD at 0x%lx.\n", (long) &TSUNAMI_cchip->mpd); ++ } ++ return i2c_bit_add_bus(&tsunami_i2c_adapter); ++} ++ ++ ++static void __exit i2c_tsunami_exit(void) ++{ ++ i2c_bit_del_bus(&tsunami_i2c_adapter); ++} ++ ++ ++ ++MODULE_AUTHOR("Oleg I. Vdovikin <vdovikin@jscc.ru>"); ++MODULE_DESCRIPTION("Tsunami I2C/SMBus driver"); ++ ++module_init(i2c_tsunami_init); ++module_exit(i2c_tsunami_exit); +--- linux-old/drivers/i2c/i2c-via.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-via.c Mon Dec 13 20:18:42 2004 +@@ -0,0 +1,207 @@ ++/* ++ i2c-via.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ i2c Support for Via Technologies 82C586B South Bridge ++ ++ Copyright (c) 1998, 1999 Kyösti Mälkki <kmalkki@cc.hut.fi> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/kernel.h> ++#include <linux/ioport.h> ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/types.h> ++#include <linux/i2c.h> ++#include <linux/i2c-algo-bit.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/param.h> /* for HZ */ ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* Power management registers */ ++ ++#define PM_CFG_REVID 0x08 /* silicon revision code */ ++#define PM_CFG_IOBASE0 0x20 ++#define PM_CFG_IOBASE1 0x48 ++ ++#define I2C_DIR (pm_io_base+0x40) ++#define I2C_OUT (pm_io_base+0x42) ++#define I2C_IN (pm_io_base+0x44) ++#define I2C_SCL 0x02 /* clock bit in DIR/OUT/IN register */ ++#define I2C_SDA 0x04 ++ ++/* io-region reservation */ ++#define IOSPACE 0x06 ++#define IOTEXT "via-i2c" ++ ++static u16 pm_io_base = 0; ++ ++/* ++ It does not appear from the datasheet that the GPIO pins are ++ open drain. So a we set a low value by setting the direction to ++ output and a high value by setting the direction to input and ++ relying on the required I2C pullup. The data value is initialized ++ to 0 in via_init() and never changed. ++*/ ++ ++static void bit_via_setscl(void *data, int state) ++{ ++ outb(state ? inb(I2C_DIR) & ~I2C_SCL : inb(I2C_DIR) | I2C_SCL, ++ I2C_DIR); ++} ++ ++static void bit_via_setsda(void *data, int state) ++{ ++ outb(state ? inb(I2C_DIR) & ~I2C_SDA : inb(I2C_DIR) | I2C_SDA, ++ I2C_DIR); ++} ++ ++static int bit_via_getscl(void *data) ++{ ++ return (0 != (inb(I2C_IN) & I2C_SCL)); ++} ++ ++static int bit_via_getsda(void *data) ++{ ++ return (0 != (inb(I2C_IN) & I2C_SDA)); ++} ++ ++ ++static struct i2c_algo_bit_data bit_data = { ++ .setsda = bit_via_setsda, ++ .setscl = bit_via_setscl, ++ .getsda = bit_via_getsda, ++ .getscl = bit_via_getscl, ++ .udelay = 5, ++ .mdelay = 5, ++ .timeout = HZ ++}; ++ ++static struct i2c_adapter vt586b_adapter = { ++ .owner = THIS_MODULE, ++ .name = "VIA i2c", ++ .id = I2C_HW_B_VIA, ++ .algo_data = &bit_data, ++}; ++ ++ ++static struct pci_device_id vt586b_ids[] __devinitdata = { ++ { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, ++ { 0, } ++}; ++ ++static int __devinit vt586b_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ u16 base; ++ u8 rev; ++ int res; ++ ++ if (pm_io_base) { ++ printk(KERN_ERR "i2c-via.o: Will only support one host\n"); ++ return -EBUSY; ++ } ++ ++ pci_read_config_byte(dev, PM_CFG_REVID, &rev); ++ ++ switch (rev) { ++ case 0x00: ++ base = PM_CFG_IOBASE0; ++ break; ++ case 0x01: ++ case 0x10: ++ base = PM_CFG_IOBASE1; ++ break; ++ ++ default: ++ base = PM_CFG_IOBASE1; ++ /* later revision */ ++ } ++ ++ pci_read_config_word(dev, base, &pm_io_base); ++ pm_io_base &= (0xff << 8); ++ ++ if (! request_region(I2C_DIR, IOSPACE, IOTEXT)) { ++ printk("i2c-via.o: IO 0x%x-0x%x already in use\n", ++ I2C_DIR, I2C_DIR + IOSPACE); ++ return -EBUSY; ++ } ++ outb(inb(I2C_DIR) & ~(I2C_SDA | I2C_SCL), I2C_DIR); ++ outb(inb(I2C_OUT) & ~(I2C_SDA | I2C_SCL), I2C_OUT); ++ ++ res = i2c_bit_add_bus(&vt586b_adapter); ++ if ( res < 0 ) { ++ release_region(I2C_DIR, IOSPACE); ++ pm_io_base = 0; ++ return res; ++ } ++ return 0; ++} ++ ++static void __devexit vt586b_remove(struct pci_dev *dev) ++{ ++ i2c_bit_del_bus(&vt586b_adapter); ++ release_region(I2C_DIR, IOSPACE); ++ pm_io_base = 0; ++} ++ ++ ++/* Don't register driver to avoid driver conflicts */ ++/* ++static struct pci_driver vt586b_driver = { ++ .name = "vt586b smbus", ++ .id_table = vt586b_ids, ++ .probe = vt586b_probe, ++ .remove = __devexit_p(vt586b_remove), ++}; ++*/ ++ ++static int __init i2c_vt586b_init(void) ++{ ++ struct pci_dev *dev; ++ const struct pci_device_id *id; ++ ++ printk("i2c-via.o version %s (%s)\n", LM_VERSION, LM_DATE); ++/* ++ return pci_module_init(&vt586b_driver); ++*/ ++ pci_for_each_dev(dev) { ++ id = pci_match_device(vt586b_ids, dev); ++ if(id) ++ if(vt586b_probe(dev, id) >= 0) ++ return 0; ++ } ++ return -ENODEV; ++} ++ ++ ++static void __exit i2c_vt586b_exit(void) ++{ ++/* ++ pci_unregister_driver(&vt586b_driver); ++*/ ++ vt586b_remove(NULL); ++} ++ ++ ++MODULE_AUTHOR("Kyösti Mälkki <kmalkki@cc.hut.fi>"); ++MODULE_DESCRIPTION("i2c for Via vt82c586b southbridge"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_vt586b_init); ++module_exit(i2c_vt586b_exit); +--- linux-old/drivers/i2c/i2c-viapro.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-viapro.c Mon Dec 13 20:18:43 2004 +@@ -0,0 +1,514 @@ ++/* ++ i2c-viapro.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, Kyösti Mälkki <kmalkki@cc.hut.fi>, ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Supports Via devices: ++ 82C596A/B (0x3050) ++ 82C596B (0x3051) ++ 82C686A/B ++ 8231 ++ 8233 ++ 8233A (0x3147 and 0x3177) ++ 8235 ++ 8237 ++ Note: we assume there can only be one device, with one SMBus interface. ++*/ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/kernel.h> ++#include <linux/stddef.h> ++#include <linux/sched.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_compat.h> ++ ++#define SMBBA1 0x90 ++#define SMBBA2 0x80 ++#define SMBBA3 0xD0 ++ ++/* SMBus address offsets */ ++static unsigned short vt596_smba; ++#define SMBHSTSTS (vt596_smba + 0) ++#define SMBHSLVSTS (vt596_smba + 1) ++#define SMBHSTCNT (vt596_smba + 2) ++#define SMBHSTCMD (vt596_smba + 3) ++#define SMBHSTADD (vt596_smba + 4) ++#define SMBHSTDAT0 (vt596_smba + 5) ++#define SMBHSTDAT1 (vt596_smba + 6) ++#define SMBBLKDAT (vt596_smba + 7) ++#define SMBSLVCNT (vt596_smba + 8) ++#define SMBSHDWCMD (vt596_smba + 9) ++#define SMBSLVEVT (vt596_smba + 0xA) ++#define SMBSLVDAT (vt596_smba + 0xC) ++ ++/* PCI Address Constants */ ++ ++/* SMBus data in configuration space can be found in two places, ++ We try to select the better one*/ ++ ++static unsigned short smb_cf_hstcfg = 0xD2; ++ ++#define SMBHSTCFG (smb_cf_hstcfg) ++#define SMBSLVC (smb_cf_hstcfg + 1) ++#define SMBSHDW1 (smb_cf_hstcfg + 2) ++#define SMBSHDW2 (smb_cf_hstcfg + 3) ++#define SMBREV (smb_cf_hstcfg + 4) ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++#define ENABLE_INT9 0 ++ ++/* VT82C596 constants */ ++#define VT596_QUICK 0x00 ++#define VT596_BYTE 0x04 ++#define VT596_BYTE_DATA 0x08 ++#define VT596_WORD_DATA 0x0C ++#define VT596_BLOCK_DATA 0x14 ++ ++ ++/* If force is set to anything different from 0, we forcibly enable the ++ VT596. DANGEROUS! */ ++static int force; ++MODULE_PARM(force, "i"); ++MODULE_PARM_DESC(force, "Forcibly enable the SMBus. DANGEROUS!"); ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the VT596 at the given address. VERY DANGEROUS! */ ++static int force_addr; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Forcibly enable the SMBus at the given address. " ++ "EXTREMELY DANGEROUS!"); ++ ++ ++static struct i2c_adapter vt596_adapter; ++ ++/* Another internally used function */ ++static int vt596_transaction(void) ++{ ++ int temp; ++ int result = 0; ++ int timeout = 0; ++ ++ dev_dbg(&vt596_adapter, "Transaction (pre): CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), ++ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { ++ dev_dbg(&vt596_adapter, "SMBus busy (0x%02x). " ++ "Resetting...\n", temp); ++ ++ outb_p(temp, SMBHSTSTS); ++ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { ++ dev_dbg(&vt596_adapter, "Failed! (0x%02x)\n", temp); ++ ++ return -1; ++ } else { ++ dev_dbg(&vt596_adapter, "Successfull!\n"); ++ } ++ } ++ ++ /* start the transaction by setting bit 6 */ ++ outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT); ++ ++ /* We will always wait for a fraction of a second! ++ I don't know if VIA needs this, Intel did */ ++ do { ++ i2c_delay(1); ++ temp = inb_p(SMBHSTSTS); ++ } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT)); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout >= MAX_TIMEOUT) { ++ result = -1; ++ dev_dbg(&vt596_adapter, "SMBus Timeout!\n"); ++ } ++ ++ if (temp & 0x10) { ++ result = -1; ++ dev_dbg(&vt596_adapter, "Error: Failed bus transaction\n"); ++ } ++ ++ if (temp & 0x08) { ++ result = -1; ++ dev_info(&vt596_adapter, "Bus collision! SMBus may be " ++ "locked until next hard\nreset. (sorry!)\n"); ++ /* Clock stops and slave is stuck in mid-transmission */ ++ } ++ ++ if (temp & 0x04) { ++ result = -1; ++ dev_dbg(&vt596_adapter, "Error: no response!\n"); ++ } ++ ++ if (inb_p(SMBHSTSTS) != 0x00) ++ outb_p(inb(SMBHSTSTS), SMBHSTSTS); ++ ++ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { ++ dev_dbg(&vt596_adapter, "Failed reset at end of " ++ "transaction (%02x)\n", temp); ++ } ++ dev_dbg(&vt596_adapter, "Transaction (post): CNT=%02x, CMD=%02x, " ++ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), ++ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++ ++ return result; ++} ++ ++/* Return -1 on error. */ ++static s32 vt596_access(struct i2c_adapter *adap, u16 addr, ++ unsigned short flags, char read_write, u8 command, ++ int size, union i2c_smbus_data *data) ++{ ++ int i, len; ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ size = VT596_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMBHSTCMD); ++ size = VT596_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, SMBHSTDAT0); ++ size = VT596_BYTE_DATA; ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT0); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); ++ } ++ size = VT596_WORD_DATA; ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len < 0) ++ len = 0; ++ if (len > 32) ++ len = 32; ++ outb_p(len, SMBHSTDAT0); ++ i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= len; i++) ++ outb_p(data->block[i], SMBBLKDAT); ++ } ++ size = VT596_BLOCK_DATA; ++ break; ++ default: ++ dev_warn(&vt596_adapter, "Unsupported transaction %d\n", size); ++ return -1; ++ } ++ ++ outb_p((size & 0x1C) + (ENABLE_INT9 & 1), SMBHSTCNT); ++ ++ if (vt596_transaction()) /* Error in transaction */ ++ return -1; ++ ++ if ((read_write == I2C_SMBUS_WRITE) || (size == VT596_QUICK)) ++ return 0; ++ ++ switch (size) { ++ case VT596_BYTE: ++ /* Where is the result put? I assume here it is in ++ * SMBHSTDAT0 but it might just as well be in the ++ * SMBHSTCMD. No clue in the docs ++ */ ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case VT596_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case VT596_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); ++ break; ++ case VT596_BLOCK_DATA: ++ data->block[0] = inb_p(SMBHSTDAT0); ++ if (data->block[0] > 32) ++ data->block[0] = 32; ++ i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= data->block[0]; i++) ++ data->block[i] = inb_p(SMBBLKDAT); ++ break; ++ } ++ return 0; ++} ++ ++static u32 vt596_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static struct i2c_algorithm smbus_algorithm = { ++ .name = "Non-I2C SMBus adapter", ++ .id = I2C_ALGO_SMBUS, ++ .smbus_xfer = vt596_access, ++ .functionality = vt596_func, ++}; ++ ++static struct i2c_adapter vt596_adapter = { ++ .owner = THIS_MODULE, ++ .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_VIA2, ++ .algo = &smbus_algorithm, ++ .name = "unset", ++}; ++ ++static int __devinit vt596_probe(struct pci_dev *pdev, ++ const struct pci_device_id *id) ++{ ++ unsigned char temp; ++ int error = -ENODEV; ++ ++ /* Determine the address of the SMBus areas */ ++ if (force_addr) { ++ vt596_smba = force_addr & 0xfff0; ++ force = 0; ++ goto found; ++ } ++ ++ if ((pci_read_config_word(pdev, id->driver_data, &vt596_smba)) || ++ !(vt596_smba & 0x1)) { ++ /* try 2nd address and config reg. for 596 */ ++ if (id->device == PCI_DEVICE_ID_VIA_82C596_3 && ++ !pci_read_config_word(pdev, SMBBA2, &vt596_smba) && ++ (vt596_smba & 0x1)) { ++ smb_cf_hstcfg = 0x84; ++ } else { ++ /* no matches at all */ ++ dev_err(pdev, "Cannot configure " ++ "SMBus I/O Base address\n"); ++ return -ENODEV; ++ } ++ } ++ ++ vt596_smba &= 0xfff0; ++ if (vt596_smba == 0) { ++ dev_err(pdev, "SMBus base address " ++ "uninitialized - upgrade BIOS or use " ++ "force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ ++ found: ++ if (!request_region(vt596_smba, 8, "viapro-smbus")) { ++ dev_err(pdev, "SMBus region 0x%x already in use!\n", ++ vt596_smba); ++ return -ENODEV; ++ } ++ ++ pci_read_config_byte(pdev, SMBHSTCFG, &temp); ++ /* If force_addr is set, we program the new address here. Just to make ++ sure, we disable the VT596 first. */ ++ if (force_addr) { ++ pci_write_config_byte(pdev, SMBHSTCFG, temp & 0xfe); ++ pci_write_config_word(pdev, id->driver_data, vt596_smba); ++ pci_write_config_byte(pdev, SMBHSTCFG, temp | 0x01); ++ dev_warn(pdev, "WARNING: SMBus interface set to new " ++ "address 0x%04x!\n", vt596_smba); ++ } else if ((temp & 1) == 0) { ++ if (force) { ++ /* NOTE: This assumes I/O space and other allocations ++ * WERE done by the Bios! Don't complain if your ++ * hardware does weird things after enabling this. ++ * :') Check for Bios updates before resorting to ++ * this. ++ */ ++ pci_write_config_byte(pdev, SMBHSTCFG, temp | 1); ++ dev_info(pdev, "Enabling SMBus device\n"); ++ } else { ++ dev_err(pdev, "SMBUS: Error: Host SMBus " ++ "controller not enabled! - upgrade BIOS or " ++ "use force=1\n"); ++ goto release_region; ++ } ++ } ++ ++ if ((temp & 0x0E) == 8) ++ dev_dbg(pdev, "using Interrupt 9 for SMBus.\n"); ++ else if ((temp & 0x0E) == 0) ++ dev_dbg(pdev, "using Interrupt SMI# for SMBus.\n"); ++ else ++ dev_dbg(pdev, "Illegal Interrupt configuration " ++ "(or code out of date)!\n"); ++ ++ pci_read_config_byte(pdev, SMBREV, &temp); ++ dev_dbg(pdev, "SMBREV = 0x%X\n", temp); ++ dev_dbg(pdev, "VT596_smba = 0x%X\n", vt596_smba); ++ ++ snprintf(vt596_adapter.name, 32, ++ "SMBus Via Pro adapter at %04x", vt596_smba); ++ ++ return i2c_add_adapter(&vt596_adapter); ++ ++ release_region: ++ release_region(vt596_smba, 8); ++ return error; ++} ++ ++static void __devexit vt596_remove(struct pci_dev *pdev) ++{ ++ i2c_del_adapter(&vt596_adapter); ++ release_region(vt596_smba, 8); ++} ++ ++/* 8233A is undefined before kernel 2.4.19 */ ++#ifndef PCI_DEVICE_ID_VIA_8233A ++#define PCI_DEVICE_ID_VIA_8233A 0x3147 ++#endif ++/* 8235 is undefined before kernel 2.4.20 */ ++#ifndef PCI_DEVICE_ID_VIA_8235 ++#define PCI_DEVICE_ID_VIA_8235 0x3177 ++#endif ++/* 8237 is undefined before kernel 2.4.21 */ ++#ifndef PCI_DEVICE_ID_VIA_8237 ++#define PCI_DEVICE_ID_VIA_8237 0x3227 ++#endif ++static struct pci_device_id vt596_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_82C596_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA1, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_82C596B_3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA1, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_82C686_4, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA1, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_8233_0, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA3 ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_8233A, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA3, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_8235, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA3 ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_8237, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA3 ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_VIA, ++ .device = PCI_DEVICE_ID_VIA_8231_4, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .driver_data = SMBBA1, ++ }, ++ { 0, } ++}; ++ ++/* Don't register driver to avoid driver conflicts */ ++/* ++static struct pci_driver vt596_driver = { ++ .name = "vt596 smbus", ++ .id_table = vt596_ids, ++ .probe = vt596_probe, ++ .remove = __devexit_p(vt596_remove), ++}; ++*/ ++ ++static int __init i2c_vt596_init(void) ++{ ++ struct pci_dev *dev; ++ const struct pci_device_id *id; ++ ++ printk("i2c-viapro.o version %s (%s)\n", LM_VERSION, LM_DATE); ++/* ++ return pci_module_init(&vt596_driver); ++*/ ++ pci_for_each_dev(dev) { ++ id = pci_match_device(vt596_ids, dev); ++ if(id) ++ if(vt596_probe(dev, id) >= 0) ++ return 0; ++ } ++ return -ENODEV; ++} ++ ++ ++static void __exit i2c_vt596_exit(void) ++{ ++/* ++ pci_unregister_driver(&vt596_driver); ++*/ ++ vt596_remove(NULL); ++} ++ ++MODULE_AUTHOR( ++ "Frodo Looijaard <frodol@dds.nl> and " ++ "Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("vt82c596 SMBus driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_vt596_init); ++module_exit(i2c_vt596_exit); +--- linux-old/drivers/i2c/i2c-voodoo3.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/i2c/i2c-voodoo3.c Mon Dec 13 20:18:43 2004 +@@ -0,0 +1,281 @@ ++/* ++ voodoo3.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ Ralph Metzler <rjkm@thp.uni-koeln.de>, and ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ Based on code written by Ralph Metzler <rjkm@thp.uni-koeln.de> and ++ Simon Vogl ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* This interfaces to the I2C bus of the Voodoo3 to gain access to ++ the BT869 and possibly other I2C devices. */ ++ ++#include <linux/module.h> ++#include <linux/pci.h> ++#include <linux/i2c.h> ++#include <linux/i2c-algo-bit.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#include <asm/param.h> /* for HZ */ ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* the only registers we use */ ++#define REG 0x78 ++#define REG2 0x70 ++ ++/* bit locations in the register */ ++#define DDC_ENAB 0x00040000 ++#define DDC_SCL_OUT 0x00080000 ++#define DDC_SDA_OUT 0x00100000 ++#define DDC_SCL_IN 0x00200000 ++#define DDC_SDA_IN 0x00400000 ++#define I2C_ENAB 0x00800000 ++#define I2C_SCL_OUT 0x01000000 ++#define I2C_SDA_OUT 0x02000000 ++#define I2C_SCL_IN 0x04000000 ++#define I2C_SDA_IN 0x08000000 ++ ++/* initialization states */ ++#define INIT2 0x2 ++#define INIT3 0x4 ++ ++/* delays */ ++#define CYCLE_DELAY 10 ++#define TIMEOUT (HZ / 2) ++ ++ ++static void config_v3(struct pci_dev *dev); ++ ++static unsigned long ioaddr; ++ ++/* The voo GPIO registers don't have individual masks for each bit ++ so we always have to read before writing. */ ++ ++static void bit_vooi2c_setscl(void *data, int val) ++{ ++ unsigned int r; ++ r = readl(ioaddr + REG); ++ if(val) ++ r |= I2C_SCL_OUT; ++ else ++ r &= ~I2C_SCL_OUT; ++ writel(r, ioaddr + REG); ++ readl(ioaddr + REG); /* flush posted write */ ++} ++ ++static void bit_vooi2c_setsda(void *data, int val) ++{ ++ unsigned int r; ++ r = readl(ioaddr + REG); ++ if(val) ++ r |= I2C_SDA_OUT; ++ else ++ r &= ~I2C_SDA_OUT; ++ writel(r, ioaddr + REG); ++ readl(ioaddr + REG); /* flush posted write */ ++} ++ ++/* The GPIO pins are open drain, so the pins always remain outputs. ++ We rely on the i2c-algo-bit routines to set the pins high before ++ reading the input from other chips. */ ++ ++static int bit_vooi2c_getscl(void *data) ++{ ++ return (0 != (readl(ioaddr + REG) & I2C_SCL_IN)); ++} ++ ++static int bit_vooi2c_getsda(void *data) ++{ ++ return (0 != (readl(ioaddr + REG) & I2C_SDA_IN)); ++} ++ ++static void bit_vooddc_setscl(void *data, int val) ++{ ++ unsigned int r; ++ r = readl(ioaddr + REG); ++ if(val) ++ r |= DDC_SCL_OUT; ++ else ++ r &= ~DDC_SCL_OUT; ++ writel(r, ioaddr + REG); ++ readl(ioaddr + REG); /* flush posted write */ ++} ++ ++static void bit_vooddc_setsda(void *data, int val) ++{ ++ unsigned int r; ++ r = readl(ioaddr + REG); ++ if(val) ++ r |= DDC_SDA_OUT; ++ else ++ r &= ~DDC_SDA_OUT; ++ writel(r, ioaddr + REG); ++ readl(ioaddr + REG); /* flush posted write */ ++} ++ ++static int bit_vooddc_getscl(void *data) ++{ ++ return (0 != (readl(ioaddr + REG) & DDC_SCL_IN)); ++} ++ ++static int bit_vooddc_getsda(void *data) ++{ ++ return (0 != (readl(ioaddr + REG) & DDC_SDA_IN)); ++} ++ ++ ++/* Configures the chip */ ++ ++void config_v3(struct pci_dev *dev) ++{ ++ unsigned int cadr; ++ ++ /* map Voodoo3 memory */ ++ cadr = dev->resource[0].start; ++ cadr &= PCI_BASE_ADDRESS_MEM_MASK; ++ ioaddr = (unsigned long)ioremap_nocache(cadr, 0x1000); ++ if(ioaddr) { ++ writel(0x8160, ioaddr + REG2); ++ writel(0xcffc0020, ioaddr + REG); ++ printk("i2c-voodoo3: Using Banshee/Voodoo3 at 0x%lx\n", ioaddr); ++ } ++} ++ ++static struct i2c_algo_bit_data voo_i2c_bit_data = { ++ .setsda = bit_vooi2c_setsda, ++ .setscl = bit_vooi2c_setscl, ++ .getsda = bit_vooi2c_getsda, ++ .getscl = bit_vooi2c_getscl, ++ .udelay = CYCLE_DELAY, ++ .mdelay = CYCLE_DELAY, ++ .timeout = TIMEOUT ++}; ++ ++static struct i2c_adapter voodoo3_i2c_adapter = { ++ .owner = THIS_MODULE, ++ .name = "I2C Voodoo3/Banshee adapter", ++ .id = I2C_HW_B_VOO, ++ .algo_data = &voo_i2c_bit_data, ++}; ++ ++static struct i2c_algo_bit_data voo_ddc_bit_data = { ++ .setsda = bit_vooddc_setsda, ++ .setscl = bit_vooddc_setscl, ++ .getsda = bit_vooddc_getsda, ++ .getscl = bit_vooddc_getscl, ++ .udelay = CYCLE_DELAY, ++ .mdelay = CYCLE_DELAY, ++ .timeout = TIMEOUT ++}; ++ ++static struct i2c_adapter voodoo3_ddc_adapter = { ++ .owner = THIS_MODULE, ++ .name = "DDC Voodoo3/Banshee adapter", ++ .id = I2C_HW_B_VOO, ++ .algo_data = &voo_ddc_bit_data, ++}; ++ ++ ++static struct pci_device_id voodoo3_ids[] __devinitdata = { ++ { ++ .vendor = PCI_VENDOR_ID_3DFX, ++ .device = PCI_DEVICE_ID_3DFX_VOODOO3, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { ++ .vendor = PCI_VENDOR_ID_3DFX, ++ .device = PCI_DEVICE_ID_3DFX_BANSHEE, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ }, ++ { 0, } ++}; ++ ++static int __devinit voodoo3_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ int retval; ++ ++ printk("voodoo3: in probe\n"); ++ config_v3(dev); ++ retval = i2c_bit_add_bus(&voodoo3_i2c_adapter); ++ if(retval) ++ return retval; ++ retval = i2c_bit_add_bus(&voodoo3_ddc_adapter); ++ if(retval) ++ i2c_bit_del_bus(&voodoo3_i2c_adapter); ++ return retval; ++} ++ ++static void __devexit voodoo3_remove(struct pci_dev *dev) ++{ ++ i2c_bit_del_bus(&voodoo3_i2c_adapter); ++ i2c_bit_del_bus(&voodoo3_ddc_adapter); ++} ++ ++ ++/* Don't register driver to avoid driver conflicts */ ++/* ++static struct pci_driver voodoo3_driver = { ++ .name = "voodoo3 smbus", ++ .id_table = voodoo3_ids, ++ .probe = voodoo3_probe, ++ .remove = __devexit_p(voodoo3_remove), ++}; ++*/ ++ ++static int __init i2c_voodoo3_init(void) ++{ ++ struct pci_dev *dev; ++ const struct pci_device_id *id; ++ ++ printk("i2c-voodoo3.o version %s (%s)\n", LM_VERSION, LM_DATE); ++/* ++ return pci_module_init(&voodoo3_driver); ++*/ ++ pci_for_each_dev(dev) { ++ id = pci_match_device(voodoo3_ids, dev); ++ if(id) ++ if(voodoo3_probe(dev, id) >= 0) ++ return 0; ++ } ++ return -ENODEV; ++} ++ ++ ++static void __exit i2c_voodoo3_exit(void) ++{ ++/* ++ pci_unregister_driver(&voodoo3_driver); ++*/ ++ voodoo3_remove(NULL); ++ iounmap((void *)ioaddr); ++} ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, Ralph Metzler <rjkm@thp.uni-koeln.de>, and Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("Voodoo3 I2C/SMBus driver"); ++ ++module_init(i2c_voodoo3_init); ++module_exit(i2c_voodoo3_exit); +--- linux-old/drivers/sensors/adm1021.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/adm1021.c Mon Dec 13 20:18:43 2004 +@@ -0,0 +1,594 @@ ++/* ++ adm1021.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x18, 0x1a, 0x29, 0x2b, ++ 0x4c, 0x4e, SENSORS_I2C_END ++}; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_8(adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066); ++ ++/* adm1021 constants specified below */ ++ ++/* The adm1021 registers */ ++/* Read-only */ ++#define ADM1021_REG_TEMP 0x00 ++#define ADM1021_REG_REMOTE_TEMP 0x01 ++#define ADM1021_REG_STATUS 0x02 ++#define ADM1021_REG_MAN_ID 0x0FE /* 0x41 = Analog Devices, 0x49 = TI, ++ 0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi*/ ++#define ADM1021_REG_DEV_ID 0x0FF /* ADM1021 = 0x0X, ADM1021A/ADM1023 = 0x3X */ ++#define ADM1021_REG_DIE_CODE 0x0FF /* MAX1617A */ ++/* These use different addresses for reading/writing */ ++#define ADM1021_REG_CONFIG_R 0x03 ++#define ADM1021_REG_CONFIG_W 0x09 ++#define ADM1021_REG_CONV_RATE_R 0x04 ++#define ADM1021_REG_CONV_RATE_W 0x0A ++/* These are for the ADM1023's additional precision on the remote temp sensor */ ++#define ADM1021_REG_REM_TEMP_PREC 0x010 ++#define ADM1021_REG_REM_OFFSET 0x011 ++#define ADM1021_REG_REM_OFFSET_PREC 0x012 ++#define ADM1021_REG_REM_TOS_PREC 0x013 ++#define ADM1021_REG_REM_THYST_PREC 0x014 ++/* limits */ ++#define ADM1021_REG_TOS_R 0x05 ++#define ADM1021_REG_TOS_W 0x0B ++#define ADM1021_REG_REMOTE_TOS_R 0x07 ++#define ADM1021_REG_REMOTE_TOS_W 0x0D ++#define ADM1021_REG_THYST_R 0x06 ++#define ADM1021_REG_THYST_W 0x0C ++#define ADM1021_REG_REMOTE_THYST_R 0x08 ++#define ADM1021_REG_REMOTE_THYST_W 0x0E ++/* write-only */ ++#define ADM1021_REG_ONESHOT 0x0F ++ ++#define ADM1021_ALARM_TEMP (ADM1021_ALARM_TEMP_HIGH | ADM1021_ALARM_TEMP_LOW) ++#define ADM1021_ALARM_RTEMP (ADM1021_ALARM_RTEMP_HIGH | ADM1021_ALARM_RTEMP_LOW\ ++ | ADM1021_ALARM_RTEMP_NA) ++#define ADM1021_ALARM_ALL (ADM1021_ALARM_TEMP | ADM1021_ALARM_RTEMP) ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++/* Conversions note: 1021 uses normal integer signed-byte format*/ ++#define TEMP_FROM_REG(val) (val > 127 ? val-256 : val) ++#define TEMP_TO_REG(val) (SENSORS_LIMIT((val < 0 ? val+256 : val),0,255)) ++ ++/* Each client has this additional data */ ++struct adm1021_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 temp, temp_os, temp_hyst; /* Register values */ ++ u8 remote_temp, remote_temp_os, remote_temp_hyst, alarms, die_code; ++ u8 fail; ++ /* Special values for ADM1023 only */ ++ u8 remote_temp_prec, remote_temp_os_prec, remote_temp_hyst_prec, ++ remote_temp_offset, remote_temp_offset_prec; ++}; ++ ++static int adm1021_attach_adapter(struct i2c_adapter *adapter); ++static int adm1021_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void adm1021_init_client(struct i2c_client *client); ++static int adm1021_detach_client(struct i2c_client *client); ++static int adm1021_read_value(struct i2c_client *client, u8 reg); ++static int adm1021_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask); ++static int adm1021_write_value(struct i2c_client *client, u8 reg, ++ u16 value); ++static void adm1021_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1021_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, ++ long *results); ++static void adm1021_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1021_die_code(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1021_update_client(struct i2c_client *client); ++ ++/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */ ++static int read_only = 0; ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver adm1021_driver = { ++ .owner = THIS_MODULE, ++ .name = "ADM1021, MAX1617 sensor driver", ++ .id = I2C_DRIVERID_ADM1021, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = adm1021_attach_adapter, ++ .detach_client = adm1021_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define ADM1021_SYSCTL_TEMP 1200 ++#define ADM1021_SYSCTL_REMOTE_TEMP 1201 ++#define ADM1021_SYSCTL_DIE_CODE 1202 ++#define ADM1021_SYSCTL_ALARMS 1203 ++ ++#define ADM1021_ALARM_TEMP_HIGH 0x40 ++#define ADM1021_ALARM_TEMP_LOW 0x20 ++#define ADM1021_ALARM_RTEMP_HIGH 0x10 ++#define ADM1021_ALARM_RTEMP_LOW 0x08 ++#define ADM1021_ALARM_RTEMP_NA 0x04 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected adm1021. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table adm1021_dir_table_template[] = { ++ {ADM1021_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_temp}, ++ {ADM1021_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_remote_temp}, ++ {ADM1021_SYSCTL_DIE_CODE, "die_code", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_die_code}, ++ {ADM1021_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_alarms}, ++ {0} ++}; ++ ++static ctl_table adm1021_max_dir_table_template[] = { ++ {ADM1021_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_temp}, ++ {ADM1021_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_remote_temp}, ++ {ADM1021_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1021_alarms}, ++ {0} ++}; ++ ++static int adm1021_id = 0; ++ ++static int adm1021_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, adm1021_detect); ++} ++ ++static int adm1021_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct adm1021_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("adm1021.o: adm1021_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto error0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access adm1021_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct adm1021_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto error0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &adm1021_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if ((adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0x03) != 0x00 ++ || (adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x3F) != 0x00 ++ || (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) & 0xF8) != 0x00) { ++ err = -ENODEV; ++ goto error1; ++ } ++ } ++ ++ /* Determine the chip type. */ ++ ++ if (kind <= 0) { ++ i = adm1021_read_value(new_client, ADM1021_REG_MAN_ID); ++ if (i == 0x41) ++ if ((adm1021_read_value (new_client, ADM1021_REG_DEV_ID) & 0xF0) == 0x30) ++ kind = adm1023; ++ else ++ kind = adm1021; ++ else if (i == 0x49) ++ kind = thmc10; ++ else if (i == 0x23) ++ kind = gl523sm; ++ else if ((i == 0x4d) && ++ (adm1021_read_value ++ (new_client, ADM1021_REG_DEV_ID) == 0x01)) ++ kind = max1617a; ++ else if (i == 0x54) ++ kind = mc1066; ++ /* LM84 Mfr ID in a different place, and it has more unused bits */ ++ else if (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) == 0x00 ++ && (kind == 0 /* skip extra detection */ ++ || ((adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x7F) == 0x00 ++ && (adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0xAB) == 0x00))) ++ kind = lm84; ++ else ++ kind = max1617; ++ } ++ ++ if (kind == max1617) { ++ type_name = "max1617"; ++ client_name = "MAX1617 chip"; ++ } else if (kind == max1617a) { ++ type_name = "max1617a"; ++ client_name = "MAX1617A chip"; ++ } else if (kind == adm1021) { ++ type_name = "adm1021"; ++ client_name = "ADM1021 chip"; ++ } else if (kind == adm1023) { ++ type_name = "adm1023"; ++ client_name = "ADM1023 chip"; ++ } else if (kind == thmc10) { ++ type_name = "thmc10"; ++ client_name = "THMC10 chip"; ++ } else if (kind == lm84) { ++ type_name = "lm84"; ++ client_name = "LM84 chip"; ++ } else if (kind == gl523sm) { ++ type_name = "gl523sm"; ++ client_name = "GL523SM chip"; ++ } else if (kind == mc1066) { ++ type_name = "mc1066"; ++ client_name = "MC1066 chip"; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = adm1021_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto error3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ data->type == adm1021 ? adm1021_dir_table_template : ++ adm1021_max_dir_table_template)) < 0) { ++ err = i; ++ goto error4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the ADM1021 chip */ ++ if (kind != lm84) ++ adm1021_init_client(new_client); ++ return 0; ++ ++ error4: ++ i2c_detach_client(new_client); ++ error3: ++ error1: ++ kfree(data); ++ error0: ++ return err; ++} ++ ++static void adm1021_init_client(struct i2c_client *client) ++{ ++ /* Enable ADC and disable suspend mode */ ++ adm1021_write_value(client, ADM1021_REG_CONFIG_W, ++ adm1021_read_value(client, ADM1021_REG_CONFIG_R) & 0xBF); ++ /* Set Conversion rate to 1/sec (this can be tinkered with) */ ++ adm1021_write_value(client, ADM1021_REG_CONV_RATE_W, 0x04); ++} ++ ++static int adm1021_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct adm1021_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("adm1021.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* All registers are byte-sized */ ++static int adm1021_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* only update value if read succeeded; set fail bit if failed */ ++static int adm1021_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask) ++{ ++ int i; ++ struct adm1021_data *data = client->data; ++ ++ i = i2c_smbus_read_byte_data(client, reg); ++ if (i < 0) { ++ data->fail |= mask; ++ return i; ++ } ++ *val = i; ++ return 0; ++} ++ ++static int adm1021_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if (read_only > 0) ++ return 0; ++ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void adm1021_update_client(struct i2c_client *client) ++{ ++ struct adm1021_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting adm1021 update\n"); ++#endif ++ ++ data->fail = 0; ++ adm1021_rd_good(&(data->temp), client, ADM1021_REG_TEMP, ++ ADM1021_ALARM_TEMP); ++ adm1021_rd_good(&(data->temp_os), client, ADM1021_REG_TOS_R, ++ ADM1021_ALARM_TEMP); ++ adm1021_rd_good(&(data->temp_hyst), client, ++ ADM1021_REG_THYST_R, ADM1021_ALARM_TEMP); ++ adm1021_rd_good(&(data->remote_temp), client, ++ ADM1021_REG_REMOTE_TEMP, ADM1021_ALARM_RTEMP); ++ adm1021_rd_good(&(data->remote_temp_os), client, ++ ADM1021_REG_REMOTE_TOS_R, ADM1021_ALARM_RTEMP); ++ adm1021_rd_good(&(data->remote_temp_hyst), client, ++ ADM1021_REG_REMOTE_THYST_R, ++ ADM1021_ALARM_RTEMP); ++ data->alarms = ADM1021_ALARM_ALL; ++ if (!adm1021_rd_good(&(data->alarms), client, ++ ADM1021_REG_STATUS, 0)) ++ data->alarms &= ADM1021_ALARM_ALL; ++ if (data->type == adm1021) ++ adm1021_rd_good(&(data->die_code), client, ++ ADM1021_REG_DIE_CODE, 0); ++ if (data->type == adm1023) { ++ adm1021_rd_good(&(data->remote_temp_prec), client, ++ ADM1021_REG_REM_TEMP_PREC, ++ ADM1021_ALARM_TEMP); ++ adm1021_rd_good(&(data->remote_temp_os_prec), client, ++ ADM1021_REG_REM_TOS_PREC, ++ ADM1021_ALARM_RTEMP); ++ adm1021_rd_good(&(data->remote_temp_hyst_prec), client, ++ ADM1021_REG_REM_THYST_PREC, ++ ADM1021_ALARM_RTEMP); ++ adm1021_rd_good(&(data->remote_temp_offset), client, ++ ADM1021_REG_REM_OFFSET, ++ ADM1021_ALARM_RTEMP); ++ adm1021_rd_good(&(data->remote_temp_offset_prec), ++ client, ADM1021_REG_REM_OFFSET_PREC, ++ ADM1021_ALARM_RTEMP); ++ } ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void adm1021_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1021_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1021_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_os); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_os = TEMP_TO_REG(results[0]); ++ adm1021_write_value(client, ADM1021_REG_TOS_W, ++ data->temp_os); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ adm1021_write_value(client, ADM1021_REG_THYST_W, ++ data->temp_hyst); ++ } ++ } ++} ++ ++void adm1021_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm1021_data *data = client->data; ++ int prec = 0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ if (data->type == adm1023) { *nrels_mag = 3; } ++ else { *nrels_mag = 0; } ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1021_update_client(client); ++ results[0] = TEMP_FROM_REG(data->remote_temp_os); ++ results[1] = TEMP_FROM_REG(data->remote_temp_hyst); ++ results[2] = TEMP_FROM_REG(data->remote_temp); ++ if (data->type == adm1023) { ++ results[0]=results[0]*1000 + ++ ((data->remote_temp_os_prec >> 5) * 125); ++ results[1]=results[1]*1000 + ++ ((data->remote_temp_hyst_prec >> 5) * 125); ++ results[2]=(TEMP_FROM_REG(data->remote_temp_offset)*1000) + ++ ((data->remote_temp_offset_prec >> 5) * 125); ++ results[3]=TEMP_FROM_REG(data->remote_temp)*1000 + ++ ((data->remote_temp_prec >> 5) * 125); ++ *nrels_mag = 4; ++ } else { ++ *nrels_mag = 3; ++ } ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (data->type == adm1023) { ++ prec=((results[0]-((results[0]/1000)*1000))/125)<<5; ++ adm1021_write_value(client, ++ ADM1021_REG_REM_TOS_PREC, ++ prec); ++ results[0]=results[0]/1000; ++ data->remote_temp_os_prec=prec; ++ } ++ data->remote_temp_os = TEMP_TO_REG(results[0]); ++ adm1021_write_value(client, ++ ADM1021_REG_REMOTE_TOS_W, ++ data->remote_temp_os); ++ } ++ if (*nrels_mag >= 2) { ++ if (data->type == adm1023) { ++ prec=((results[1]-((results[1]/1000)*1000))/125)<<5; ++ adm1021_write_value(client, ++ ADM1021_REG_REM_THYST_PREC, ++ prec); ++ results[1]=results[1]/1000; ++ data->remote_temp_hyst_prec=prec; ++ } ++ data->remote_temp_hyst = TEMP_TO_REG(results[1]); ++ adm1021_write_value(client, ++ ADM1021_REG_REMOTE_THYST_W, ++ data->remote_temp_hyst); ++ } ++ if (*nrels_mag >= 3) { ++ if (data->type == adm1023) { ++ prec=((results[2]-((results[2]/1000)*1000))/125)<<5; ++ adm1021_write_value(client, ++ ADM1021_REG_REM_OFFSET_PREC, ++ prec); ++ results[2]=results[2]/1000; ++ data->remote_temp_offset_prec=prec; ++ data->remote_temp_offset=results[2]; ++ adm1021_write_value(client, ++ ADM1021_REG_REM_OFFSET, ++ data->remote_temp_offset); ++ } ++ } ++ } ++} ++ ++void adm1021_die_code(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm1021_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1021_update_client(client); ++ results[0] = data->die_code; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ /* Can't write to it */ ++ } ++} ++ ++void adm1021_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1021_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1021_update_client(client); ++ results[0] = data->alarms | data->fail; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ /* Can't write to it */ ++ } ++} ++ ++static int __init sm_adm1021_init(void) ++{ ++ printk(KERN_INFO "adm1021.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&adm1021_driver); ++} ++ ++static void __exit sm_adm1021_exit(void) ++{ ++ i2c_del_driver(&adm1021_driver); ++} ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("adm1021 driver"); ++MODULE_LICENSE("GPL"); ++ ++MODULE_PARM(read_only, "i"); ++MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); ++ ++module_init(sm_adm1021_init) ++module_exit(sm_adm1021_exit) +--- linux-old/drivers/sensors/adm1024.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/adm1024.c Mon Dec 13 20:18:43 2004 +@@ -0,0 +1,782 @@ ++/* ++ adm1024.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Add by Ken Bowley <ken@opnix.com> from the adm1025.c written by ++ Gordon Wu <gwu@esoft.com> and from adm9240.c written by ++ Copyright (c) 1999 Frodo Looijaard <frodol@dds.nl> ++ and Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Supports the Analog Devices ADM1024. See doc/chips/adm1024 for details */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(adm1024); ++ ++/* Many ADM1024 constants specified below */ ++ ++#define ADM1024_REG_IN_MAX(nr) (0x2b + (nr) * 2) ++#define ADM1024_REG_IN_MIN(nr) (0x2c + (nr) * 2) ++#define ADM1024_REG_IN(nr) (0x20 + (nr)) ++ ++/* The ADM1024 registers */ ++#define ADM1024_REG_INT_TEMP_TRIP_SET 0x13 ++#define ADM1024_REG_EXT_TEMP_TRIP_SET 0x14 ++#define ADM1024_REG_TEST 0x15 ++#define ADM1024_REG_CHANNEL_MODE 0x16 ++#define ADM1024_REG_INT_TEMP_TRIP 0x17 /* read only */ ++#define ADM1024_REG_EXT_TEMP_TRIP 0x18 /* read only */ ++#define ADM1024_REG_ANALOG_OUT 0x19 ++#define ADM1024_REG_AIN1_LOW_LIMIT 0x1A ++#define ADM1024_REG_AIN2_LOW_LIMIT 0x1B ++/* These are all read-only */ ++#define ADM1024_REG_2_5V 0x20 /* 2.5V Measured Value/EXT Temp 2 */ ++#define ADM1024_REG_VCCP1 0x21 ++#define ADM1024_REG_3_3V 0x22 /* VCC Measured Value */ ++#define ADM1024_REG_5V 0x23 ++#define ADM1024_REG_12V 0x24 ++#define ADM1024_REG_VCCP2 0x25 ++#define ADM1024_REG_EXT_TEMP1 0x26 ++#define ADM1024_REG_TEMP 0x27 ++#define ADM1024_REG_FAN1 0x28 /* FAN1/AIN1 Value */ ++#define ADM1024_REG_FAN2 0x29 /* FAN2/AIN2 Value */ ++#define ADM1024_REG_COMPANY_ID 0x3E /* 0x41 for ADM1024 */ ++#define ADM1024_REG_DIE_REV 0x3F ++/* These are read/write */ ++#define ADM1024_REG_2_5V_HIGH 0x2B /* 2.5V/Ext Temp2 High Limit */ ++#define ADM1024_REG_2_5V_LOW 0x2C /* 2.5V/Ext Temp2 Low Limit */ ++#define ADM1024_REG_VCCP1_HIGH 0x2D ++#define ADM1024_REG_VCCP1_LOW 0x2E ++#define ADM1024_REG_3_3V_HIGH 0x2F /* VCC High Limit */ ++#define ADM1024_REG_3_3V_LOW 0x30 /* VCC Low Limit */ ++#define ADM1024_REG_5V_HIGH 0x31 ++#define ADM1024_REG_5V_LOW 0x32 ++#define ADM1024_REG_12V_HIGH 0x33 ++#define ADM1024_REG_12V_LOW 0x34 ++#define ADM1024_REG_VCCP2_HIGH 0x35 ++#define ADM1024_REG_VCCP2_LOW 0x36 ++#define ADM1024_REG_EXT_TEMP1_HIGH 0x37 ++#define ADM1024_REG_EXT_TEMP1_LOW 0x38 ++#define ADM1024_REG_TOS 0x39 ++#define ADM1024_REG_THYST 0x3A ++#define ADM1024_REG_FAN1_MIN 0x3B ++#define ADM1024_REG_FAN2_MIN 0x3C ++ ++#define ADM1024_REG_CONFIG 0x40 ++#define ADM1024_REG_INT1_STAT 0x41 ++#define ADM1024_REG_INT2_STAT 0x42 ++#define ADM1024_REG_INT1_MASK 0x43 ++#define ADM1024_REG_INT2_MASK 0x44 ++ ++#define ADM1024_REG_CHASSIS_CLEAR 0x46 ++#define ADM1024_REG_VID_FAN_DIV 0x47 ++#define ADM1024_REG_I2C_ADDR 0x48 ++#define ADM1024_REG_VID4 0x49 ++#define ADM1024_REG_CONFIG2 0x4A ++#define ADM1024_REG_TEMP_CONFIG 0x4B ++#define ADM1024_REG_EXTMODE1 0x4C /* Interupt Status Register Mirror No. 1 */ ++#define ADM1024_REG_EXTMODE2 0x4D /* Interupt Status Register Mirror No. 2 */ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val,nr) (SENSORS_LIMIT(((val) & 0xff),0,255)) ++#define IN_FROM_REG(val,nr) (val) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:\ ++ (val)==255?0:1350000/((div)*(val))) ++ ++#define TEMP_FROM_REG(temp) \ ++ ((temp)<256?((((temp)&0x1fe) >> 1) * 10) + ((temp) & 1) * 5: \ ++ ((((temp)&0x1fe) >> 1) -255) * 10 - ((temp) & 1) * 5) \ ++ ++#define EXT_TEMP_FROM_REG(temp) (((temp)>0x80?(temp)-0x100:(temp))*10) ++ ++ ++#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) ++ ++#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10), \ ++ 0,255) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1))) ++ ++#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ ++ 205-(val)*5) ++ ++/* For each registered ADM1024, we need to keep some data in memory. That ++ data is pointed to by adm1024_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new adm1024 client is ++ allocated. */ ++struct adm1024_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[6]; /* Register value */ ++ u8 in_max[6]; /* Register value */ ++ u8 in_min[6]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ int temp; /* Temp, shifted right */ ++ u8 temp_os_max; /* Register value */ ++ u8 temp_os_hyst; /* Register value */ ++ int temp1; /* Ext Temp 1 */ ++ u8 temp1_os_max; ++ u8 temp1_os_hyst; ++ int temp2; /* Ext Temp 2 */ ++ u8 temp2_os_max; ++ u8 temp2_os_hyst; ++ u16 alarms; /* Register encoding, combined */ ++ u8 analog_out; /* Register value */ ++ u8 vid; /* Register value combined */ ++}; ++ ++ ++ ++static int adm1024_attach_adapter(struct i2c_adapter *adapter); ++static int adm1024_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int adm1024_detach_client(struct i2c_client *client); ++ ++static int adm1024_read_value(struct i2c_client *client, u8 register); ++static int adm1024_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void adm1024_update_client(struct i2c_client *client); ++static void adm1024_init_client(struct i2c_client *client); ++ ++ ++static void adm1024_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_temp1(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_temp2(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1024_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, ++ long *results); ++static void adm1024_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int adm1024_id = 0; ++ ++static struct i2c_driver adm1024_driver = { ++ .owner = THIS_MODULE, ++ .name = "ADM1024 sensor driver", ++ .id = I2C_DRIVERID_ADM1024, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = adm1024_attach_adapter, ++ .detach_client = adm1024_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define ADM1024_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define ADM1024_SYSCTL_IN1 1001 ++#define ADM1024_SYSCTL_IN2 1002 ++#define ADM1024_SYSCTL_IN3 1003 ++#define ADM1024_SYSCTL_IN4 1004 ++#define ADM1024_SYSCTL_IN5 1005 ++#define ADM1024_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define ADM1024_SYSCTL_FAN2 1102 ++#define ADM1024_SYSCTL_TEMP 1250 /* Degrees Celcius * 100 */ ++#define ADM1024_SYSCTL_TEMP1 1290 /* Degrees Celcius */ ++#define ADM1024_SYSCTL_TEMP2 1295 /* Degrees Celcius */ ++#define ADM1024_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define ADM1024_SYSCTL_ALARMS 2001 /* bitvector */ ++#define ADM1024_SYSCTL_ANALOG_OUT 2002 ++#define ADM1024_SYSCTL_VID 2003 ++ ++#define ADM1024_ALARM_IN0 0x0001 ++#define ADM1024_ALARM_IN1 0x0002 ++#define ADM1024_ALARM_IN2 0x0004 ++#define ADM1024_ALARM_IN3 0x0008 ++#define ADM1024_ALARM_IN4 0x0100 ++#define ADM1024_ALARM_IN5 0x0200 ++#define ADM1024_ALARM_FAN1 0x0040 ++#define ADM1024_ALARM_FAN2 0x0080 ++#define ADM1024_ALARM_TEMP 0x0010 ++#define ADM1024_ALARM_TEMP1 0x0020 ++#define ADM1024_ALARM_TEMP2 0x0001 ++#define ADM1024_ALARM_CHAS 0x1000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected ADM1024. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table adm1024_dir_table_template[] = { ++ {ADM1024_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_in}, ++ {ADM1024_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_in}, ++ {ADM1024_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_in}, ++ {ADM1024_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_in}, ++ {ADM1024_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_in}, ++ {ADM1024_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_in}, ++ {ADM1024_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_fan}, ++ {ADM1024_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_fan}, ++ {ADM1024_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_temp}, ++ {ADM1024_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_temp1}, ++ {ADM1024_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_temp2}, ++ {ADM1024_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_fan_div}, ++ {ADM1024_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_alarms}, ++ {ADM1024_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_analog_out}, ++ {ADM1024_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1024_vid}, ++ {0} ++}; ++ ++static int adm1024_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, adm1024_detect); ++} ++ ++static int adm1024_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct adm1024_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("adm1024.o: adm1024_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access adm1024_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct adm1024_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &adm1024_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if((adm1024_read_value(new_client, ADM1024_REG_CONFIG) & 0x80) != 0x00) ++ goto ERROR1; ++ } ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ i = adm1024_read_value(new_client, ADM1024_REG_COMPANY_ID); ++ if (i == 0x41) ++ kind = adm1024; ++ else { ++ if (kind == 0) ++ printk ++ ("adm1024.o: Ignoring 'force' parameter for unknown chip at " ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ goto ERROR1; ++ } ++ } ++ ++ if (kind == adm1024) { ++ type_name = "adm1024"; ++ client_name = "ADM1024 chip"; ++ } else { ++#ifdef DEBUG ++ printk("adm1024.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = adm1024_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ adm1024_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the ADM1024 chip */ ++ adm1024_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int adm1024_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct adm1024_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("adm1024.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static int adm1024_read_value(struct i2c_client *client, u8 reg) ++{ ++ return 0xFF & i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int adm1024_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void adm1024_init_client(struct i2c_client *client) ++{ ++ /* Enable temperature channel 2 */ ++ adm1024_write_value(client, ADM1024_REG_CHANNEL_MODE, adm1024_read_value(client, ADM1024_REG_CHANNEL_MODE) | 0x04); ++ ++ /* Start monitoring */ ++ adm1024_write_value(client, ADM1024_REG_CONFIG, 0x07); ++} ++ ++static void adm1024_update_client(struct i2c_client *client) ++{ ++ struct adm1024_data *data = client->data; ++ u8 i; ++ ++ down(&data->update_lock); ++ ++ if ( ++ (jiffies - data->last_updated > ++ (data->type == adm1024 ? HZ / 2 : HZ * 2)) ++ || (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting adm1024 update\n"); ++#endif ++ for (i = 0; i <= 5; i++) { ++ data->in[i] = ++ adm1024_read_value(client, ADM1024_REG_IN(i)); ++ data->in_min[i] = ++ adm1024_read_value(client, ++ ADM1024_REG_IN_MIN(i)); ++ data->in_max[i] = ++ adm1024_read_value(client, ++ ADM1024_REG_IN_MAX(i)); ++ } ++ data->fan[0] = ++ adm1024_read_value(client, ADM1024_REG_FAN1); ++ data->fan_min[0] = ++ adm1024_read_value(client, ADM1024_REG_FAN1_MIN); ++ data->fan[1] = ++ adm1024_read_value(client, ADM1024_REG_FAN2); ++ data->fan_min[1] = ++ adm1024_read_value(client, ADM1024_REG_FAN2_MIN); ++ data->temp = ++ (adm1024_read_value(client, ADM1024_REG_TEMP) << 1) + ++ ((adm1024_read_value ++ (client, ADM1024_REG_TEMP_CONFIG) & 0x80) >> 7); ++ data->temp_os_max = ++ adm1024_read_value(client, ADM1024_REG_TOS); ++ data->temp_os_hyst = ++ adm1024_read_value(client, ADM1024_REG_THYST); ++ data->temp1 = ++ adm1024_read_value(client, ADM1024_REG_EXT_TEMP1); ++ data->temp1_os_max = ++ adm1024_read_value(client, ADM1024_REG_EXT_TEMP1_HIGH); ++ data->temp1_os_hyst = ++ adm1024_read_value(client, ADM1024_REG_EXT_TEMP1_LOW); ++ data->temp2 = ++ adm1024_read_value(client, ADM1024_REG_2_5V); ++ data->temp2_os_max = ++ adm1024_read_value(client, ADM1024_REG_2_5V_HIGH); ++ data->temp2_os_hyst = ++ adm1024_read_value(client, ADM1024_REG_2_5V_LOW); ++ ++ i = adm1024_read_value(client, ADM1024_REG_VID_FAN_DIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = (i >> 6) & 0x03; ++ data->vid = i & 0x0f; ++ data->vid |= ++ (adm1024_read_value(client, ADM1024_REG_VID4) & 0x01) ++ << 4; ++ ++ data->alarms = ++ adm1024_read_value(client, ++ ADM1024_REG_INT1_STAT) + ++ (adm1024_read_value(client, ADM1024_REG_INT2_STAT) << ++ 8); ++ data->analog_out = ++ adm1024_read_value(client, ADM1024_REG_ANALOG_OUT); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void adm1024_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ ++ int scales[6] = { 250, 225, 330, 500, 1200, 270 }; ++ ++ struct adm1024_data *data = client->data; ++ int nr = ctl_name - ADM1024_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = ++ IN_FROM_REG(data->in_min[nr], nr) * scales[nr] / 192; ++ results[1] = ++ IN_FROM_REG(data->in_max[nr], nr) * scales[nr] / 192; ++ results[2] = ++ IN_FROM_REG(data->in[nr], nr) * scales[nr] / 192; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = ++ IN_TO_REG((results[0] * 192) / scales[nr], nr); ++ adm1024_write_value(client, ADM1024_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = ++ IN_TO_REG((results[1] * 192) / scales[nr], nr); ++ adm1024_write_value(client, ADM1024_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void adm1024_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ int nr = ctl_name - ADM1024_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data-> ++ fan_div[nr - 1])); ++ results[1] = ++ FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr - ++ 1])); ++ adm1024_write_value(client, ++ nr == ++ 1 ? ADM1024_REG_FAN1_MIN : ++ ADM1024_REG_FAN2_MIN, ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void adm1024_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = TEMP_LIMIT_FROM_REG(data->temp_os_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_os_max = TEMP_LIMIT_TO_REG(results[0]); ++ adm1024_write_value(client, ADM1024_REG_TOS, ++ data->temp_os_max); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[1]); ++ adm1024_write_value(client, ADM1024_REG_THYST, ++ data->temp_os_hyst); ++ } ++ } ++} ++ ++void adm1024_temp1(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = TEMP_LIMIT_FROM_REG(data->temp1_os_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->temp1_os_hyst); ++ results[2] = EXT_TEMP_FROM_REG(data->temp1); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp1_os_max = TEMP_LIMIT_TO_REG(results[0]); ++ adm1024_write_value(client, ADM1024_REG_EXT_TEMP1_HIGH, ++ data->temp1_os_max); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp1_os_hyst = TEMP_LIMIT_TO_REG(results[1]); ++ adm1024_write_value(client, ADM1024_REG_EXT_TEMP1_LOW, ++ data->temp1_os_hyst); ++ } ++ } ++} ++ ++void adm1024_temp2(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = TEMP_LIMIT_FROM_REG(data->temp2_os_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->temp2_os_hyst); ++ results[2] = EXT_TEMP_FROM_REG(data->temp2); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp2_os_max = TEMP_LIMIT_TO_REG(results[0]); ++ adm1024_write_value(client, ADM1024_REG_2_5V_HIGH, ++ data->temp2_os_max); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp2_os_hyst = TEMP_LIMIT_TO_REG(results[1]); ++ adm1024_write_value(client, ADM1024_REG_2_5V_LOW, ++ data->temp2_os_hyst); ++ } ++ } ++} ++ ++void adm1024_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void adm1024_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = adm1024_read_value(client, ADM1024_REG_VID_FAN_DIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ adm1024_write_value(client, ++ ADM1024_REG_VID_FAN_DIV, old); ++ } ++ } ++} ++ ++void adm1024_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = data->analog_out; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->analog_out = results[0]; ++ adm1024_write_value(client, ADM1024_REG_ANALOG_OUT, ++ data->analog_out); ++ } ++ } ++} ++ ++void adm1024_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1024_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1024_update_client(client); ++ results[0] = VID_FROM_REG(data->vid); ++ *nrels_mag = 1; ++ } ++} ++ ++static int __init sm_adm1024_init(void) ++{ ++ printk("adm1024.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&adm1024_driver); ++} ++ ++static void __exit sm_adm1024_exit(void) ++{ ++ i2c_del_driver(&adm1024_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("ADM1024 driver"); ++ ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_adm1024_init); ++module_exit(sm_adm1024_exit); +--- linux-old/drivers/sensors/adm1025.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/adm1025.c Mon Dec 13 20:18:43 2004 +@@ -0,0 +1,594 @@ ++/* ++ adm1025.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2000 Chen-Yuan Wu <gwu@esoft.com> ++ Copyright (c) 2003-2004 Jean Delvare <khali@linux-fr.org> ++ ++ Based on the adm9240 driver. ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Supports the Analog Devices ADM1025 and the Philips NE1619. ++ See doc/chips/adm1025 for details */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_2(adm1025, ne1619); ++ ++/* Many ADM1025 constants specified below */ ++ ++ ++/* The ADM1025 registers */ ++ ++/* These are all read-only */ ++#define ADM1025_REG_2_5V 0x20 /* not used directly, see */ ++#define ADM1025_REG_VCCP1 0x21 /* ADM1025_REG_IN(nr) below */ ++#define ADM1025_REG_3_3V 0x22 ++#define ADM1025_REG_5V 0x23 ++#define ADM1025_REG_12V 0x24 ++#define ADM1025_REG_VCC 0x25 ++ ++#define ADM1025_REG_RTEMP 0x26 /* not used directly, see */ ++#define ADM1025_REG_LTEMP 0x27 /* ADM1025_REG_TEMP(nr) below */ ++ ++#define ADM1025_REG_COMPANY_ID 0x3E /* 0x41 for Analog Devices, ++ 0xA1 for Philips */ ++#define ADM1025_REG_DIE_REV 0x3F /* 0x20-0x2F for ADM1025 and compatible */ ++ ++#define ADM1025_REG_STATUS1 0x41 ++#define ADM1025_REG_STATUS2 0x42 ++ ++#define ADM1025_REG_VID 0x47 ++#define ADM1025_REG_VID4 0x49 /* actually R/W ++ but we don't write to it */ ++ ++/* These are read/write */ ++#define ADM1025_REG_2_5V_HIGH 0x2B /* not used directly, see */ ++#define ADM1025_REG_2_5V_LOW 0x2C /* ADM1025_REG_IN_MAX(nr) and */ ++#define ADM1025_REG_VCCP1_HIGH 0x2D /* ADM1025_REG_IN_MIN(nr) below */ ++#define ADM1025_REG_VCCP1_LOW 0x2E ++#define ADM1025_REG_3_3V_HIGH 0x2F ++#define ADM1025_REG_3_3V_LOW 0x30 ++#define ADM1025_REG_5V_HIGH 0x31 ++#define ADM1025_REG_5V_LOW 0x32 ++#define ADM1025_REG_12V_HIGH 0x33 ++#define ADM1025_REG_12V_LOW 0x34 ++#define ADM1025_REG_VCC_HIGH 0x35 ++#define ADM1025_REG_VCC_LOW 0x36 ++ ++#define ADM1025_REG_RTEMP_HIGH 0x37 /* not used directly, see */ ++#define ADM1025_REG_RTEMP_LOW 0x38 /* ADM1025_REG_TEMP_MAX(nr) and */ ++#define ADM1025_REG_LTEMP_HIGH 0x39 /* ADM1025_REG_TEMP_MIN(nr) below */ ++#define ADM1025_REG_LTEMP_LOW 0x3A ++ ++#define ADM1025_REG_CONFIG 0x40 ++ ++/* Useful macros */ ++#define ADM1025_REG_IN(nr) (ADM1025_REG_2_5V + (nr)) ++#define ADM1025_REG_IN_MAX(nr) (ADM1025_REG_2_5V_HIGH + (nr) * 2) ++#define ADM1025_REG_IN_MIN(nr) (ADM1025_REG_2_5V_LOW + (nr) * 2) ++#define ADM1025_REG_TEMP(nr) (ADM1025_REG_RTEMP + (nr)) ++#define ADM1025_REG_TEMP_HIGH(nr) (ADM1025_REG_RTEMP_HIGH + (nr) * 2) ++#define ADM1025_REG_TEMP_LOW(nr) (ADM1025_REG_RTEMP_LOW + (nr) * 2) ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val) SENSORS_LIMIT(val, 0, 255) ++#define IN_FROM_REG(val) (val) ++ ++#define TEMP_FROM_REG(val) (((val)>=0x80?(val)-0x100:(val))*10) ++#define TEMP_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),-128,127) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++/* For each registered ADM1025, we need to keep some data in memory. That ++ data is pointed to by adm1025_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new adm1025 client is ++ allocated. */ ++struct adm1025_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[6]; /* Register value */ ++ u8 in_max[6]; /* Register value */ ++ u8 in_min[6]; /* Register value */ ++ u8 temp[2]; /* Register value */ ++ u8 temp_high[2]; /* Register value */ ++ u8 temp_low[2]; /* Register value */ ++ u16 alarms; /* Register encoding, combined */ ++ u8 vid; /* Register value combined */ ++ u8 vrm; ++}; ++ ++ ++static int adm1025_attach_adapter(struct i2c_adapter *adapter); ++static int adm1025_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int adm1025_detach_client(struct i2c_client *client); ++static void adm1025_update_client(struct i2c_client *client); ++static void adm1025_init_client(struct i2c_client *client); ++ ++ ++static void adm1025_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1025_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1025_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1025_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1025_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int adm1025_id = 0; ++ ++static struct i2c_driver adm1025_driver = { ++ .owner = THIS_MODULE, ++ .name = "ADM1025 sensor driver", ++ .id = I2C_DRIVERID_ADM1025, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = adm1025_attach_adapter, ++ .detach_client = adm1025_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define ADM1025_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define ADM1025_SYSCTL_IN1 1001 ++#define ADM1025_SYSCTL_IN2 1002 ++#define ADM1025_SYSCTL_IN3 1003 ++#define ADM1025_SYSCTL_IN4 1004 ++#define ADM1025_SYSCTL_IN5 1005 ++ ++#define ADM1025_SYSCTL_RTEMP 1250 /* Degrees Celcius * 10 */ ++#define ADM1025_SYSCTL_TEMP 1251 ++ ++#define ADM1025_SYSCTL_ALARMS 2001 /* bitvector */ ++#define ADM1025_SYSCTL_VID 2003 /* Volts * 1000 */ ++#define ADM1025_SYSCTL_VRM 2004 ++ ++#define ADM1025_ALARM_IN0 0x0001 ++#define ADM1025_ALARM_IN1 0x0002 ++#define ADM1025_ALARM_IN2 0x0004 ++#define ADM1025_ALARM_IN3 0x0008 ++#define ADM1025_ALARM_IN4 0x0100 ++#define ADM1025_ALARM_IN5 0x0200 ++#define ADM1025_ALARM_RTEMP 0x0020 ++#define ADM1025_ALARM_TEMP 0x0010 ++#define ADM1025_ALARM_RFAULT 0x4000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected ADM1025. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table adm1025_dir_table_template[] = { ++ {ADM1025_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_in}, ++ {ADM1025_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_in}, ++ {ADM1025_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_in}, ++ {ADM1025_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_in}, ++ {ADM1025_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_in}, ++ {ADM1025_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_in}, ++ {ADM1025_SYSCTL_RTEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_temp}, ++ {ADM1025_SYSCTL_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_temp}, ++ {ADM1025_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_alarms}, ++ {ADM1025_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_vid}, ++ {ADM1025_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1025_vrm}, ++ {0} ++}; ++ ++static int adm1025_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, adm1025_detect); ++} ++ ++static int adm1025_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct adm1025_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("adm1025.o: adm1025_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access adm1025_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct adm1025_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &adm1025_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if ((i2c_smbus_read_byte_data(new_client, ++ ADM1025_REG_CONFIG) & 0x80) != 0x00 ++ || (i2c_smbus_read_byte_data(new_client, ++ ADM1025_REG_STATUS1) & 0xC0) != 0x00 ++ || (i2c_smbus_read_byte_data(new_client, ++ ADM1025_REG_STATUS2) & 0xBC) != 0x00) ++ goto ERROR1; ++ } ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ u8 man_id, chip_id; ++ ++ man_id = i2c_smbus_read_byte_data(new_client, ++ ADM1025_REG_COMPANY_ID); ++ chip_id = i2c_smbus_read_byte_data(new_client, ++ ADM1025_REG_DIE_REV); ++ ++ if (man_id == 0x41) { /* Analog Devices */ ++ if ((chip_id & 0xF0) == 0x20) /* ADM1025 */ ++ kind = adm1025; ++ } else if (man_id == 0xA1) { /* Philips */ ++ if (address != 0x2E ++ && (chip_id & 0xF0) == 0x20) /* NE1619 */ ++ kind = ne1619; ++ } ++ } ++ ++ if (kind <= 0) { /* Identification failed */ ++ printk("adm1025.o: Unsupported chip.\n"); ++ goto ERROR1; ++ } ++ ++ if (kind == adm1025) { ++ type_name = "adm1025"; ++ client_name = "ADM1025 chip"; ++ } else if (kind == ne1619) { ++ type_name = "ne1619"; ++ client_name = "NE1619 chip"; ++ } else { ++#ifdef DEBUG ++ printk("adm1025.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = adm1025_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ adm1025_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the ADM1025 chip */ ++ adm1025_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int adm1025_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct adm1025_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("adm1025.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* Called when we have found a new ADM1025. */ ++static void adm1025_init_client(struct i2c_client *client) ++{ ++ struct adm1025_data *data = client->data; ++ u8 reg; ++ int i; ++ ++ data->vrm = 82; ++ ++ /* Set high limits ++ Usually we avoid setting limits on driver init, but it happens ++ that the ADM1025 comes with stupid default limits (all registers ++ set to 0). In case the chip has not gone through any limit ++ setting yet, we better set the high limits to the max so that ++ no alarm triggers. */ ++ for (i=0; i<6; i++) { ++ reg = i2c_smbus_read_byte_data(client, ++ ADM1025_REG_IN_MAX(i)); ++ if (reg == 0) ++ i2c_smbus_write_byte_data(client, ++ ADM1025_REG_IN_MAX(i), ++ 0xFF); ++ } ++ for (i=0; i<2; i++) { ++ reg = i2c_smbus_read_byte_data(client, ++ ADM1025_REG_TEMP_HIGH(i)); ++ if (reg == 0) ++ i2c_smbus_write_byte_data(client, ++ ADM1025_REG_TEMP_HIGH(i), ++ 0x7F); ++ } ++ ++ /* Start monitoring */ ++ reg = i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG); ++ i2c_smbus_write_byte_data(client, ADM1025_REG_CONFIG, (reg|0x01)&0x7F); ++} ++ ++static void adm1025_update_client(struct i2c_client *client) ++{ ++ struct adm1025_data *data = client->data; ++ u8 nr; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > 2 * HZ) ++ || (jiffies < data->last_updated) || !data->valid) { ++#ifdef DEBUG ++ printk("Starting adm1025 update\n"); ++#endif ++ ++ /* Voltages */ ++ for (nr = 0; nr < 6; nr++) { ++ data->in[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN(nr)); ++ data->in_min[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN_MIN(nr)); ++ data->in_max[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN_MAX(nr)); ++ } ++ ++ /* Temperatures */ ++ for (nr = 0; nr < 2; nr++) { ++ data->temp[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP(nr)); ++ data->temp_high[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP_HIGH(nr)); ++ data->temp_low[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP_LOW(nr)); ++ } ++ ++ /* VID */ ++ data->vid = (i2c_smbus_read_byte_data(client, ADM1025_REG_VID) & 0x0f) ++ + ((i2c_smbus_read_byte_data(client, ADM1025_REG_VID4) & 0x01) << 4); ++ ++ /* Alarms */ ++ data->alarms = (i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS1) & 0x3f) ++ + ((i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS2) & 0x43) << 8); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the data ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void adm1025_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int scales[6] = { 250, 225, 330, 500, 1200, 330 }; ++ ++ struct adm1025_data *data = client->data; ++ int nr = ctl_name - ADM1025_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1025_update_client(client); ++ results[0] = (IN_FROM_REG(data->in_min[nr]) * scales[nr] + 96) / 192; ++ results[1] = (IN_FROM_REG(data->in_max[nr]) * scales[nr] + 96) / 192; ++ results[2] = (IN_FROM_REG(data->in[nr]) * scales[nr] + 96) / 192; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG((results[0] * 192 + scales[nr] / 2) ++ / scales[nr]); ++ i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG((results[1] * 192 + scales[nr] / 2) ++ / scales[nr]); ++ i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void adm1025_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1025_data *data = client->data; ++ int nr = ctl_name - ADM1025_SYSCTL_RTEMP; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1025_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_high[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_low[nr]); ++ results[2] = TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_high[nr] = TEMP_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_HIGH(nr), ++ data->temp_high[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_low[nr] = TEMP_TO_REG(results[1]); ++ i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_LOW(nr), ++ data->temp_low[nr]); ++ } ++ } ++} ++ ++void adm1025_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1025_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1025_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void adm1025_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1025_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1025_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void adm1025_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1025_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++static int __init sm_adm1025_init(void) ++{ ++ printk("adm1025.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&adm1025_driver); ++} ++ ++static void __exit sm_adm1025_exit(void) ++{ ++ i2c_del_driver(&adm1025_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Chen-Yuan Wu <gwu@esoft.com>" ++ " and Jean Delvare <khali@linux-fr.org>"); ++MODULE_DESCRIPTION("ADM1025 driver"); ++ ++module_init(sm_adm1025_init); ++module_exit(sm_adm1025_exit); +--- linux-old/drivers/sensors/adm1026.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/adm1026.c Mon Dec 13 20:18:44 2004 +@@ -0,0 +1,1745 @@ ++/* ++ adm1026.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2002, 2003 Philip Pokorny <ppokorny@penguincomputing.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++ CHANGELOG ++ ++ 2003-03-13 Initial development ++ 2003-05-07 First Release. Includes GPIO fixup and full ++ functionality. ++ 2003-05-18 Minor fixups and tweaks. ++ Print GPIO config after fixup. ++ Adjust fan MIN if DIV changes. ++ 2003-05-21 Fix printing of FAN/GPIO config ++ Fix silly bug in fan_div logic ++ Fix fan_min handling so that 0xff is 0 is 0xff ++ 2003-05-25 Fix more silly typos... ++ 2003-06-11 Change FAN_xx_REG macros to use different scaling ++ Most (all?) drivers assume two pulses per rev fans ++ and the old scaling was producing double the RPM's ++ Thanks to Jerome Hsiao @ Arima for pointing this out. ++ 2004-01-27 Remove use of temporary ID. ++ Define addresses as a range. ++*/ ++ ++#include <linux/version.h> ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++#ifndef I2C_DRIVERID_ADM1026 ++#define I2C_DRIVERID_ADM1026 1048 ++#endif ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(adm1026); ++ ++static int gpio_input[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ -1, -1, -1, -1, -1, -1, -1, -1 }; ++static int gpio_output[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ -1, -1, -1, -1, -1, -1, -1, -1 }; ++static int gpio_inverted[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ -1, -1, -1, -1, -1, -1, -1, -1 }; ++static int gpio_normal[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ -1, -1, -1, -1, -1, -1, -1, -1 }; ++static int gpio_fan[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; ++MODULE_PARM(gpio_input,"1-17i"); ++MODULE_PARM_DESC(gpio_input,"List of GPIO pins (0-16) to program as inputs"); ++MODULE_PARM(gpio_output,"1-17i"); ++MODULE_PARM_DESC(gpio_output,"List of GPIO pins (0-16) to program as outputs"); ++MODULE_PARM(gpio_inverted,"1-17i"); ++MODULE_PARM_DESC(gpio_inverted,"List of GPIO pins (0-16) to program as inverted"); ++MODULE_PARM(gpio_normal,"1-17i"); ++MODULE_PARM_DESC(gpio_normal,"List of GPIO pins (0-16) to program as normal/non-inverted"); ++MODULE_PARM(gpio_fan,"1-8i"); ++MODULE_PARM_DESC(gpio_fan,"List of GPIO pins (0-7) to program as fan tachs"); ++ ++/* Many ADM1026 constants specified below */ ++ ++/* The ADM1026 registers */ ++#define ADM1026_REG_CONFIG1 (0x00) ++#define CFG1_MONITOR (0x01) ++#define CFG1_INT_ENABLE (0x02) ++#define CFG1_INT_CLEAR (0x04) ++#define CFG1_AIN8_9 (0x08) ++#define CFG1_THERM_HOT (0x10) ++#define CFG1_DAC_AFC (0x20) ++#define CFG1_PWM_AFC (0x40) ++#define CFG1_RESET (0x80) ++#define ADM1026_REG_CONFIG2 (0x01) ++/* CONFIG2 controls FAN0/GPIO0 through FAN7/GPIO7 */ ++#define ADM1026_REG_CONFIG3 (0x07) ++#define CFG3_GPIO16_ENABLE (0x01) ++#define CFG3_CI_CLEAR (0x02) ++#define CFG3_VREF_250 (0x04) ++#define CFG3_GPIO16_DIR (0x40) ++#define CFG3_GPIO16_POL (0x80) ++#define ADM1026_REG_E2CONFIG (0x13) ++#define E2CFG_READ (0x01) ++#define E2CFG_WRITE (0x02) ++#define E2CFG_ERASE (0x04) ++#define E2CFG_ROM (0x08) ++#define E2CFG_CLK_EXT (0x80) ++ ++/* There are 10 general analog inputs and 7 dedicated inputs ++ * They are: ++ * 0 - 9 = AIN0 - AIN9 ++ * 10 = Vbat ++ * 11 = 3.3V Standby ++ * 12 = 3.3V Main ++ * 13 = +5V ++ * 14 = Vccp (CPU core voltage) ++ * 15 = +12V ++ * 16 = -12V ++ */ ++static u16 REG_IN[] = { ++ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, ++ 0x36, 0x37, 0x27, 0x29, 0x26, 0x2a, ++ 0x2b, 0x2c, 0x2d, 0x2e, 0x2f ++ }; ++static u16 REG_IN_MIN[] = { ++ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, ++ 0x5e, 0x5f, 0x6d, 0x49, 0x6b, 0x4a, ++ 0x4b, 0x4c, 0x4d, 0x4e, 0x4f ++ }; ++static u16 REG_IN_MAX[] = { ++ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, ++ 0x56, 0x57, 0x6c, 0x41, 0x6a, 0x42, ++ 0x43, 0x44, 0x45, 0x46, 0x47 ++ }; ++#define ADM1026_REG_IN(nr) (REG_IN[(nr)]) ++#define ADM1026_REG_IN_MIN(nr) (REG_IN_MIN[(nr)]) ++#define ADM1026_REG_IN_MAX(nr) (REG_IN_MAX[(nr)]) ++ ++/* Temperatures are: ++ * 0 - Internal ++ * 1 - External 1 ++ * 2 - External 2 ++ */ ++static u16 REG_TEMP[] = { 0x1f, 0x28, 0x29 }; ++static u16 REG_TEMP_MIN[] = { 0x69, 0x48, 0x49 }; ++static u16 REG_TEMP_MAX[] = { 0x68, 0x40, 0x41 }; ++static u16 REG_TEMP_TMIN[] = { 0x10, 0x11, 0x12 }; ++static u16 REG_TEMP_THERM[] = { 0x0d, 0x0e, 0x0f }; ++static u16 REG_TEMP_OFFSET[] = { 0x1e, 0x6e, 0x6f }; ++#define ADM1026_REG_TEMP(nr) (REG_TEMP[(nr)]) ++#define ADM1026_REG_TEMP_MIN(nr) (REG_TEMP_MIN[(nr)]) ++#define ADM1026_REG_TEMP_MAX(nr) (REG_TEMP_MAX[(nr)]) ++#define ADM1026_REG_TEMP_TMIN(nr) (REG_TEMP_TMIN[(nr)]) ++#define ADM1026_REG_TEMP_THERM(nr) (REG_TEMP_THERM[(nr)]) ++#define ADM1026_REG_TEMP_OFFSET(nr) (REG_TEMP_OFFSET[(nr)]) ++ ++#define ADM1026_REG_FAN(nr) (0x38 + (nr)) ++#define ADM1026_REG_FAN_MIN(nr) (0x60 + (nr)) ++#define ADM1026_REG_FAN_DIV_0_3 (0x02) ++#define ADM1026_REG_FAN_DIV_4_7 (0x03) ++ ++#define ADM1026_REG_DAC (0x04) ++#define ADM1026_REG_PWM (0x05) ++ ++#define ADM1026_REG_GPIO_CFG_0_3 (0x08) ++#define ADM1026_REG_GPIO_CFG_4_7 (0x09) ++#define ADM1026_REG_GPIO_CFG_8_11 (0x0a) ++#define ADM1026_REG_GPIO_CFG_12_15 (0x0b) ++/* CFG_16 in REG_CFG3 */ ++#define ADM1026_REG_GPIO_STATUS_0_7 (0x24) ++#define ADM1026_REG_GPIO_STATUS_8_15 (0x25) ++/* STATUS_16 in REG_STATUS4 */ ++#define ADM1026_REG_GPIO_MASK_0_7 (0x1c) ++#define ADM1026_REG_GPIO_MASK_8_15 (0x1d) ++/* MASK_16 in REG_MASK4 */ ++ ++#define ADM1026_REG_COMPANY 0x16 ++#define ADM1026_REG_VERSTEP 0x17 ++/* These are the recognized values for the above regs */ ++#define ADM1026_COMPANY_ANALOG_DEV 0x41 ++#define ADM1026_VERSTEP_GENERIC 0x40 ++#define ADM1026_VERSTEP_ADM1026 0x44 ++ ++#define ADM1026_REG_MASK1 0x18 ++#define ADM1026_REG_MASK2 0x19 ++#define ADM1026_REG_MASK3 0x1a ++#define ADM1026_REG_MASK4 0x1b ++ ++#define ADM1026_REG_STATUS1 0x20 ++#define ADM1026_REG_STATUS2 0x21 ++#define ADM1026_REG_STATUS3 0x22 ++#define ADM1026_REG_STATUS4 0x23 ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ */ ++ ++/* IN are scaled acording to built-in resistors. These are the ++ * voltages corresponding to 3/4 of full scale (192 or 0xc0) ++ * NOTE: The -12V input needs an additional factor to account ++ * for the Vref pullup resistor. ++ * NEG12_OFFSET = SCALE * Vref / V-192 - Vref ++ * = 13875 * 2.50 / 1.875 - 2500 ++ * = 16000 ++ */ ++#if 1 ++/* The values in this table are based on Table II, page 15 of the ++ * datasheet. ++ */ ++static int adm1026_scaling[] = { /* .001 Volts */ ++ 2250, 2250, 2250, 2250, 2250, 2250, ++ 1875, 1875, 1875, 1875, 3000, 3330, ++ 3330, 4995, 2250, 12000, 13875 ++ }; ++#define NEG12_OFFSET 16000 ++#else ++/* The values in this table are based on the resistors in ++ * Figure 5 on page 16. But the 3.3V inputs are not in ++ * the figure and the values for the 5V input are wrong. ++ * For 5V, I'm guessing that R2 at 55.2k is right, but ++ * the total resistance should be 1400 or 1449 like the ++ * other inputs. Using 1449, gives 4.922V at 192. ++ */ ++static int adm1026_scaling[] = { /* .001 Volts */ ++ 2249, 2249, 2249, 2249, 2249, 2249, ++ 1875, 1875, 1875, 1875, 3329, 3329, ++ 3329, 4922, 2249, 11969, 13889 ++ }; ++#define NEG12_OFFSET 16019 ++#endif ++ ++#define SCALE(val,from,to) (((val)*(to) + ((from)/2))/(from)) ++#define INS_TO_REG(n,val) (SENSORS_LIMIT(SCALE(val,adm1026_scaling[n],192),0,255)) ++#if 0 /* If we have extended A/D bits */ ++#define INSEXT_FROM_REG(n,val,ext) (SCALE((val)*4 + (ext),192*4,adm1026_scaling[n])) ++#define INS_FROM_REG(n,val) (INSEXT_FROM_REG(n,val,0)) ++#else ++#define INS_FROM_REG(n,val) (SCALE(val,192,adm1026_scaling[n])) ++#endif ++ ++/* FAN speed is measured using 22.5kHz clock and counts for 2 pulses ++ * and we assume a 2 pulse-per-rev fan tach signal ++ * 22500 kHz * 60 (sec/min) * 2 (pulse) / 2 (pulse/rev) == 1350000 ++ */ ++#define FAN_TO_REG(val,div) ((val)<=0 ? 0xff : SENSORS_LIMIT(1350000/((val)*(div)),1,254)) ++#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==0xff ? 0 : 1350000/((val)*(div))) ++#define DIV_FROM_REG(val) (1<<(val)) ++#define DIV_TO_REG(val) ((val)>=8 ? 3 : (val)>=4 ? 2 : (val)>=2 ? 1 : 0) ++ ++/* Temperature is reported in 1 degC increments */ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(val,-127,127)) ++#define TEMP_FROM_REG(val) (val) ++#define OFFSET_TO_REG(val) (SENSORS_LIMIT(val,-127,127)) ++#define OFFSET_FROM_REG(val) (val) ++ ++#define PWM_TO_REG(val) (SENSORS_LIMIT(val,0,255)) ++#define PWM_FROM_REG(val) (val) ++ ++/* Analog output is a voltage, but it's used like a PWM ++ * Seems like this should be scaled, but to be consistent ++ * with other drivers, we do it this way. ++ */ ++#define DAC_TO_REG(val) (SENSORS_LIMIT(val,0,255)) ++#define DAC_FROM_REG(val) (val) ++ ++/* sensors_vid.h defines vid_from_reg() */ ++#define VID_FROM_REG(val,vrm) (vid_from_reg(val,vrm)) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++/* Unlike some other drivers we DO NOT set initial limits. Use ++ * the config file to set limits. ++ */ ++ ++/* Typically used with systems using a v9.1 VRM spec ? */ ++#define ADM1026_INIT_VRM 91 ++#define ADM1026_INIT_VID -1 ++ ++/* Chip sampling rates ++ * ++ * Some sensors are not updated more frequently than once per second ++ * so it doesn't make sense to read them more often than that. ++ * We cache the results and return the saved data if the driver ++ * is called again before a second has elapsed. ++ * ++ * Also, there is significant configuration data for this chip ++ * So, we keep the config data up to date in the cache ++ * when it is written and only sample it once every 5 *minutes* ++ */ ++#define ADM1026_DATA_INTERVAL (1 * HZ) ++#define ADM1026_CONFIG_INTERVAL (5 * 60 * HZ) ++ ++/* We allow for multiple chips in a single system. ++ * ++ * For each registered ADM1026, we need to keep state information ++ * at client->data. The adm1026_data structure is dynamically ++ * allocated, when a new client structure is allocated. */ ++ ++struct adm1026_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ int valid; /* !=0 if following fields are valid */ ++ unsigned long last_reading; /* In jiffies */ ++ unsigned long last_config; /* In jiffies */ ++ ++ u8 in[17]; /* Register value */ ++ u8 in_max[17]; /* Register value */ ++ u8 in_min[17]; /* Register value */ ++ s8 temp[3]; /* Register value */ ++ s8 temp_min[3]; /* Register value */ ++ s8 temp_max[3]; /* Register value */ ++ s8 temp_tmin[3]; /* Register value */ ++ s8 temp_therm[3]; /* Register value */ ++ s8 temp_offset[3]; /* Register value */ ++ u8 fan[8]; /* Register value */ ++ u8 fan_min[8]; /* Register value */ ++ u8 fan_div[8]; /* Decoded value */ ++ u8 pwm; /* Register value */ ++ u8 analog_out; /* Register value */ ++ int vid; /* Decoded value */ ++ u8 vrm; /* VRM version */ ++ long alarms; /* Register encoding, combined */ ++ long alarm_mask; /* Register encoding, combined */ ++ long gpio; /* Register encoding, combined */ ++ long gpio_mask; /* Register encoding, combined */ ++ u8 gpio_config[17]; /* Decoded value */ ++ u8 config1; /* Register value */ ++ u8 config2; /* Register value */ ++ u8 config3; /* Register value */ ++}; ++ ++static int adm1026_attach_adapter(struct i2c_adapter *adapter); ++static int adm1026_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int adm1026_detach_client(struct i2c_client *client); ++ ++static int adm1026_read_value(struct i2c_client *client, u8 register); ++static int adm1026_write_value(struct i2c_client *client, u8 register, int value); ++static void adm1026_print_gpio(struct i2c_client *client); ++static void adm1026_fixup_gpio(struct i2c_client *client); ++static void adm1026_update_client(struct i2c_client *client); ++static void adm1026_init_client(struct i2c_client *client); ++ ++ ++static void adm1026_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void adm1026_in16(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void adm1026_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_fixup_fan_min(struct i2c_client *client, ++ int fan, int old_div); ++static void adm1026_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_temp_offset(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_temp_tmin(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_temp_therm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_alarm_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_gpio(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_gpio_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1026_afc(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static struct i2c_driver adm1026_driver = { ++ .owner = THIS_MODULE, ++ .name = "ADM1026 compatible sensor driver", ++ .id = I2C_DRIVERID_ADM1026, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = &adm1026_attach_adapter, ++ .detach_client = &adm1026_detach_client, ++}; ++ ++/* Unique ID assigned to each ADM1026 detected */ ++static int adm1026_id = 0; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define ADM1026_SYSCTL_FAN0 1000 ++#define ADM1026_SYSCTL_FAN1 1001 ++#define ADM1026_SYSCTL_FAN2 1002 ++#define ADM1026_SYSCTL_FAN3 1003 ++#define ADM1026_SYSCTL_FAN4 1004 ++#define ADM1026_SYSCTL_FAN5 1005 ++#define ADM1026_SYSCTL_FAN6 1006 ++#define ADM1026_SYSCTL_FAN7 1007 ++#define ADM1026_SYSCTL_FAN_DIV 1008 ++#define ADM1026_SYSCTL_GPIO 1009 ++#define ADM1026_SYSCTL_GPIO_MASK 1010 ++#define ADM1026_SYSCTL_ALARMS 1011 ++#define ADM1026_SYSCTL_ALARM_MASK 1012 ++#define ADM1026_SYSCTL_IN0 1013 ++#define ADM1026_SYSCTL_IN1 1014 ++#define ADM1026_SYSCTL_IN2 1015 ++#define ADM1026_SYSCTL_IN3 1016 ++#define ADM1026_SYSCTL_IN4 1017 ++#define ADM1026_SYSCTL_IN5 1018 ++#define ADM1026_SYSCTL_IN6 1019 ++#define ADM1026_SYSCTL_IN7 1020 ++#define ADM1026_SYSCTL_IN8 1021 ++#define ADM1026_SYSCTL_IN9 1022 ++#define ADM1026_SYSCTL_IN10 1023 ++#define ADM1026_SYSCTL_IN11 1024 ++#define ADM1026_SYSCTL_IN12 1025 ++#define ADM1026_SYSCTL_IN13 1026 ++#define ADM1026_SYSCTL_IN14 1027 ++#define ADM1026_SYSCTL_IN15 1028 ++#define ADM1026_SYSCTL_IN16 1029 ++#define ADM1026_SYSCTL_PWM 1030 ++#define ADM1026_SYSCTL_ANALOG_OUT 1031 ++#define ADM1026_SYSCTL_AFC 1032 ++#define ADM1026_SYSCTL_TEMP1 1033 ++#define ADM1026_SYSCTL_TEMP2 1034 ++#define ADM1026_SYSCTL_TEMP3 1035 ++#define ADM1026_SYSCTL_TEMP_OFFSET1 1036 ++#define ADM1026_SYSCTL_TEMP_OFFSET2 1037 ++#define ADM1026_SYSCTL_TEMP_OFFSET3 1038 ++#define ADM1026_SYSCTL_TEMP_THERM1 1039 ++#define ADM1026_SYSCTL_TEMP_THERM2 1040 ++#define ADM1026_SYSCTL_TEMP_THERM3 1041 ++#define ADM1026_SYSCTL_TEMP_TMIN1 1042 ++#define ADM1026_SYSCTL_TEMP_TMIN2 1043 ++#define ADM1026_SYSCTL_TEMP_TMIN3 1044 ++#define ADM1026_SYSCTL_VID 1045 ++#define ADM1026_SYSCTL_VRM 1046 ++ ++#define ADM1026_ALARM_TEMP2 (1L << 0) ++#define ADM1026_ALARM_TEMP3 (1L << 1) ++#define ADM1026_ALARM_IN9 (1L << 1) ++#define ADM1026_ALARM_IN11 (1L << 2) ++#define ADM1026_ALARM_IN12 (1L << 3) ++#define ADM1026_ALARM_IN13 (1L << 4) ++#define ADM1026_ALARM_IN14 (1L << 5) ++#define ADM1026_ALARM_IN15 (1L << 6) ++#define ADM1026_ALARM_IN16 (1L << 7) ++#define ADM1026_ALARM_IN0 (1L << 8) ++#define ADM1026_ALARM_IN1 (1L << 9) ++#define ADM1026_ALARM_IN2 (1L << 10) ++#define ADM1026_ALARM_IN3 (1L << 11) ++#define ADM1026_ALARM_IN4 (1L << 12) ++#define ADM1026_ALARM_IN5 (1L << 13) ++#define ADM1026_ALARM_IN6 (1L << 14) ++#define ADM1026_ALARM_IN7 (1L << 15) ++#define ADM1026_ALARM_FAN0 (1L << 16) ++#define ADM1026_ALARM_FAN1 (1L << 17) ++#define ADM1026_ALARM_FAN2 (1L << 18) ++#define ADM1026_ALARM_FAN3 (1L << 19) ++#define ADM1026_ALARM_FAN4 (1L << 20) ++#define ADM1026_ALARM_FAN5 (1L << 21) ++#define ADM1026_ALARM_FAN6 (1L << 22) ++#define ADM1026_ALARM_FAN7 (1L << 23) ++#define ADM1026_ALARM_TEMP1 (1L << 24) ++#define ADM1026_ALARM_IN10 (1L << 25) ++#define ADM1026_ALARM_IN8 (1L << 26) ++#define ADM1026_ALARM_THERM (1L << 27) ++#define ADM1026_ALARM_AFC_FAN (1L << 28) ++#define ADM1026_ALARM_UNUSED (1L << 29) ++#define ADM1026_ALARM_CI (1L << 30) ++/* -- SENSORS SYSCTL END -- */ ++ ++/* The /proc/sys entries */ ++/* These files are created for each detected ADM1026. This is just a template; ++ * The actual list is built from this and additional per-chip ++ * custom lists below. Note the XXX_LEN macros. These must be ++ * compile time constants because they will be used to allocate ++ * space for the final template passed to i2c_register_entry. ++ * We depend on the ability of GCC to evaluate expressions at ++ * compile time to turn these expressions into compile time ++ * constants, but this can generate a warning. ++ */ ++static ctl_table adm1026_common[] = { ++ {ADM1026_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN9, "in9", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN10, "in10", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN11, "in11", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN12, "in12", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN13, "in13", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN14, "in14", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN15, "in15", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in}, ++ {ADM1026_SYSCTL_IN16, "in16", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_in16}, ++ ++ {ADM1026_SYSCTL_FAN0, "fan0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN5, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN6, "fan6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN7, "fan7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_fan}, ++ {ADM1026_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_fan_div}, ++ ++ {ADM1026_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_temp}, ++ {ADM1026_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_temp}, ++ {ADM1026_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_temp}, ++ {ADM1026_SYSCTL_TEMP_OFFSET1, "temp1_offset", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_offset}, ++ {ADM1026_SYSCTL_TEMP_OFFSET2, "temp2_offset", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_offset}, ++ {ADM1026_SYSCTL_TEMP_OFFSET3, "temp3_offset", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_offset}, ++ {ADM1026_SYSCTL_TEMP_TMIN1, "temp1_tmin", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_tmin}, ++ {ADM1026_SYSCTL_TEMP_TMIN2, "temp2_tmin", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_tmin}, ++ {ADM1026_SYSCTL_TEMP_TMIN3, "temp3_tmin", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_tmin}, ++ {ADM1026_SYSCTL_TEMP_THERM1, "temp1_therm", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_therm}, ++ {ADM1026_SYSCTL_TEMP_THERM2, "temp2_therm", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_therm}, ++ {ADM1026_SYSCTL_TEMP_THERM3, "temp3_therm", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_temp_therm}, ++ ++ {ADM1026_SYSCTL_VID, "vid", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_vid}, ++ {ADM1026_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_vrm}, ++ ++ {ADM1026_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_alarms}, ++ {ADM1026_SYSCTL_ALARM_MASK, "alarm_mask", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_alarm_mask}, ++ ++ {ADM1026_SYSCTL_GPIO, "gpio", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_gpio}, ++ {ADM1026_SYSCTL_GPIO_MASK, "gpio_mask", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_gpio_mask}, ++ ++ {ADM1026_SYSCTL_PWM, "pwm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_pwm}, ++ {ADM1026_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1026_analog_out}, ++ {ADM1026_SYSCTL_AFC, "afc", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm1026_afc}, ++ ++ {0} ++}; ++#define CTLTBL_COMMON (sizeof(adm1026_common)/sizeof(adm1026_common[0])) ++ ++#define MAX2(a,b) ((a)>(b)?(a):(b)) ++#define MAX3(a,b,c) ((a)>(b)?MAX2((a),(c)):MAX2((b),(c))) ++#define MAX4(a,b,c,d) ((a)>(b)?MAX3((a),(c),(d)):MAX3((b),(c),(d))) ++ ++#define CTLTBL_MAX (CTLTBL_COMMON) ++ ++/* This function is called when: ++ * the module is loaded ++ * a new adapter is loaded ++ */ ++int adm1026_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, adm1026_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int adm1026_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ int company, verstep ; ++ struct i2c_client *new_client; ++ struct adm1026_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ struct ctl_table template[CTLTBL_MAX] ; ++ struct ctl_table * template_next = template ; ++ ++ if (i2c_is_isa_adapter(adapter)) { ++ /* This chip has no ISA interface */ ++ goto ERROR0 ; ++ } ++ ++ if (!i2c_check_functionality(adapter, ++ I2C_FUNC_SMBUS_BYTE_DATA)) { ++ /* We need to be able to do byte I/O */ ++ goto ERROR0 ; ++ } ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access adm1026_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct adm1026_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &adm1026_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ company = adm1026_read_value(new_client, ADM1026_REG_COMPANY); ++ verstep = adm1026_read_value(new_client, ADM1026_REG_VERSTEP); ++ ++#ifdef DEBUG ++ printk("adm1026: Detecting device at %d,0x%02x with" ++ " COMPANY: 0x%02x and VERSTEP: 0x%02x\n", ++ i2c_adapter_id(new_client->adapter), new_client->addr, ++ company, verstep ++ ); ++#endif ++ ++ /* If auto-detecting, Determine the chip type. */ ++ if (kind <= 0) { ++#ifdef DEBUG ++ printk("adm1026: Autodetecting device at %d,0x%02x ...\n", ++ i2c_adapter_id(adapter), address ); ++#endif ++ if( company == ADM1026_COMPANY_ANALOG_DEV ++ && verstep == ADM1026_VERSTEP_ADM1026 ) { ++ kind = adm1026 ; ++ } else if( company == ADM1026_COMPANY_ANALOG_DEV ++ && (verstep & 0xf0) == ADM1026_VERSTEP_GENERIC ) { ++ printk("adm1026: Unrecgonized stepping 0x%02x" ++ " Defaulting to ADM1026.\n", verstep ); ++ kind = adm1026 ; ++ } else if( (verstep & 0xf0) == ADM1026_VERSTEP_GENERIC ) { ++ printk("adm1026: Found version/stepping 0x%02x" ++ " Assuming generic ADM1026.\n", verstep ); ++ kind = any_chip ; ++ } else { ++#ifdef DEBUG ++ printk("adm1026: Autodetection failed\n"); ++#endif ++ /* Not an ADM1026 ... */ ++ if( kind == 0 ) { /* User used force=x,y */ ++ printk("adm1026: Generic ADM1026 Version 6 not" ++ " found at %d,0x%02x. Try force_adm1026.\n", ++ i2c_adapter_id(adapter), address ); ++ } ++ err = 0 ; ++ goto ERROR1; ++ } ++ } ++ ++ /* Fill in the chip specific driver values */ ++ switch (kind) { ++ case any_chip : ++ type_name = "adm1026"; ++ strcpy(new_client->name, "Generic ADM1026"); ++ template_next = template ; /* None used */ ++ break ; ++ case adm1026 : ++ type_name = "adm1026"; ++ strcpy(new_client->name, "Analog Devices ADM1026"); ++ template_next = template ; ++ break ; ++#if 0 ++ /* Example of another adm1026 "compatible" device */ ++ case adx1000 : ++ type_name = "adx1000"; ++ strcpy(new_client->name, "Compatible ADX1000"); ++ memcpy( template, adx_specific, sizeof(adx_specific) ); ++ template_next = template + CTLTBL_ADX1000 ; ++ break ; ++#endif ++ default : ++ printk("adm1026: Internal error, invalid kind (%d)!", kind); ++ err = -EFAULT ; ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields */ ++ new_client->id = adm1026_id++; ++ printk("adm1026(%d): Assigning ID %d to %s at %d,0x%02x\n", ++ new_client->id, new_client->id, new_client->name, ++ i2c_adapter_id(new_client->adapter), ++ new_client->addr ++ ); ++ ++ /* Housekeeping values */ ++ data->type = kind; ++ data->valid = 0; ++ ++ /* Set the VRM version */ ++ data->vrm = ADM1026_INIT_VRM ; ++ data->vid = ADM1026_INIT_VID ; ++ ++ init_MUTEX(&data->update_lock); ++ ++ /* Initialize the ADM1026 chip */ ++ adm1026_init_client(new_client); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR1; ++ ++ /* Finish out the template */ ++ memcpy(template_next, adm1026_common, sizeof(adm1026_common)); ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ template)) < 0) { ++ err = i; ++ goto ERROR2; ++ } ++ data->sysctl_id = i; ++ ++ return 0; ++ ++ /* Error out and cleanup code */ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++int adm1026_detach_client(struct i2c_client *client) ++{ ++ int err; ++ int id ; ++ ++ id = client->id; ++ i2c_deregister_entry(((struct adm1026_data *)(client->data))->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk("adm1026(%d): Client deregistration failed," ++ " client not detached.\n", id ); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++int adm1026_read_value(struct i2c_client *client, u8 reg) ++{ ++ int res; ++ ++ if( reg < 0x80 ) { ++ /* "RAM" locations */ ++ res = i2c_smbus_read_byte_data(client, reg) & 0xff ; ++ } else { ++ /* EEPROM, do nothing */ ++ res = 0 ; ++ } ++ ++ return res ; ++} ++ ++int adm1026_write_value(struct i2c_client *client, u8 reg, int value) ++{ ++ int res ; ++ ++ if( reg < 0x80 ) { ++ /* "RAM" locations */ ++ res = i2c_smbus_write_byte_data(client, reg, value); ++ } else { ++ /* EEPROM, do nothing */ ++ res = 0 ; ++ } ++ ++ return res ; ++} ++ ++/* Called when we have found a new ADM1026. */ ++void adm1026_init_client(struct i2c_client *client) ++{ ++ int value ; ++ int i; ++ struct adm1026_data *data = client->data; ++ ++#ifdef DEBUG ++ printk("adm1026(%d): Initializing device\n", client->id); ++#endif ++ ++ /* Read chip config */ ++ data->config1 = adm1026_read_value(client, ADM1026_REG_CONFIG1); ++ data->config2 = adm1026_read_value(client, ADM1026_REG_CONFIG2); ++ data->config3 = adm1026_read_value(client, ADM1026_REG_CONFIG3); ++ ++ /* Inform user of chip config */ ++#ifdef DEBUG ++ printk("adm1026(%d): ADM1026_REG_CONFIG1 is: 0x%02x\n", ++ client->id, data->config1 ); ++#endif ++ if( (data->config1 & CFG1_MONITOR) == 0 ) { ++ printk("adm1026(%d): Monitoring not currently enabled.\n", ++ client->id ); ++ } ++ if( data->config1 & CFG1_INT_ENABLE ) { ++ printk("adm1026(%d): SMBALERT interrupts are enabled.\n", ++ client->id ); ++ } ++ if( data->config1 & CFG1_AIN8_9 ) { ++ printk("adm1026(%d): in8 and in9 enabled. temp3 disabled.\n", ++ client->id ); ++ } else { ++ printk("adm1026(%d): temp3 enabled. in8 and in9 disabled.\n", ++ client->id ); ++ } ++ if( data->config1 & CFG1_THERM_HOT ) { ++ printk("adm1026(%d): Automatic THERM, PWM, and temp limits enabled.\n", ++ client->id ); ++ } ++ ++ value = data->config3 ; ++ if( data->config3 & CFG3_GPIO16_ENABLE ) { ++ printk("adm1026(%d): GPIO16 enabled. THERM pin disabled.\n", ++ client->id ); ++ } else { ++ printk("adm1026(%d): THERM pin enabled. GPIO16 disabled.\n", ++ client->id ); ++ } ++ if( data->config3 & CFG3_VREF_250 ) { ++ printk("adm1026(%d): Vref is 2.50 Volts.\n", client->id ); ++ } else { ++ printk("adm1026(%d): Vref is 1.82 Volts.\n", client->id ); ++ } ++ ++ /* Read and pick apart the existing GPIO configuration */ ++ value = 0 ; ++ for( i = 0 ; i <= 15 ; ++i ) { ++ if( (i & 0x03) == 0 ) { ++ value = adm1026_read_value(client, ++ ADM1026_REG_GPIO_CFG_0_3 + i/4 ); ++ } ++ data->gpio_config[i] = value & 0x03 ; ++ value >>= 2 ; ++ } ++ data->gpio_config[16] = (data->config3 >> 6) & 0x03 ; ++ ++ /* ... and then print it */ ++ adm1026_print_gpio(client); ++ ++ /* If the user asks us to reprogram the GPIO config, then ++ * do it now. But only if this is the first ADM1026. ++ */ ++ if( client->id == 0 ++ && (gpio_input[0] != -1 || gpio_output[0] != -1 ++ || gpio_inverted[0] != -1 || gpio_normal[0] != -1 ++ || gpio_fan[0] != -1 ) ) { ++ adm1026_fixup_gpio(client); ++ } ++ ++ /* WE INTENTIONALLY make no changes to the limits, ++ * offsets, pwms and fans. If they were ++ * configured, we don't want to mess with them. ++ * If they weren't, the default is generally safe ++ * and will suffice until 'sensors -s' can be run. ++ */ ++ ++ /* Start monitoring */ ++ value = adm1026_read_value(client, ADM1026_REG_CONFIG1); ++ ++ /* Set MONITOR, clear interrupt acknowledge and s/w reset */ ++ value = (value | CFG1_MONITOR) & (~CFG1_INT_CLEAR & ~CFG1_RESET) ; ++#ifdef DEBUG ++ printk("adm1026(%d): Setting CONFIG to: 0x%02x\n", client->id, value ); ++#endif ++ data->config1 = value ; ++ adm1026_write_value(client, ADM1026_REG_CONFIG1, value); ++ ++} ++ ++void adm1026_print_gpio(struct i2c_client *client) ++{ ++ struct adm1026_data *data = client->data; ++ int i ; ++ ++ printk("adm1026(%d): GPIO config is:\nadm1026(%d):", ++ client->id, client->id ); ++ for( i = 0 ; i <= 7 ; ++i ) { ++ if( data->config2 & (1 << i) ) { ++ printk( " %sGP%s%d", ++ data->gpio_config[i] & 0x02 ? "" : "!", ++ data->gpio_config[i] & 0x01 ? "OUT" : "IN", ++ i ); ++ } else { ++ printk( " FAN%d", i ); ++ } ++ } ++ printk( "\nadm1026(%d):", client->id ); ++ for( i = 8 ; i <= 15 ; ++i ) { ++ printk( " %sGP%s%d", ++ data->gpio_config[i] & 0x02 ? "" : "!", ++ data->gpio_config[i] & 0x01 ? "OUT" : "IN", ++ i ); ++ } ++ if( data->config3 & CFG3_GPIO16_ENABLE ) { ++ printk( " %sGP%s16\n", ++ data->gpio_config[16] & 0x02 ? "" : "!", ++ data->gpio_config[16] & 0x01 ? "OUT" : "IN" ); ++ } else { ++ /* GPIO16 is THERM */ ++ printk( " THERM\n" ); ++ } ++} ++ ++void adm1026_fixup_gpio(struct i2c_client *client) ++{ ++ struct adm1026_data *data = client->data; ++ int i ; ++ int value ; ++ ++ /* Make the changes requested. */ ++ /* We may need to unlock/stop monitoring or soft-reset the ++ * chip before we can make changes. This hasn't been ++ * tested much. FIXME ++ */ ++ ++ /* Make outputs */ ++ for( i = 0 ; i <= 16 ; ++i ) { ++ if( gpio_output[i] >= 0 && gpio_output[i] <= 16 ) { ++ data->gpio_config[gpio_output[i]] |= 0x01 ; ++ } ++ /* if GPIO0-7 is output, it isn't a FAN tach */ ++ if( gpio_output[i] >= 0 && gpio_output[i] <= 7 ) { ++ data->config2 |= 1 << gpio_output[i] ; ++ } ++ } ++ ++ /* Input overrides output */ ++ for( i = 0 ; i <= 16 ; ++i ) { ++ if( gpio_input[i] >= 0 && gpio_input[i] <= 16 ) { ++ data->gpio_config[gpio_input[i]] &= ~ 0x01 ; ++ } ++ /* if GPIO0-7 is input, it isn't a FAN tach */ ++ if( gpio_input[i] >= 0 && gpio_input[i] <= 7 ) { ++ data->config2 |= 1 << gpio_input[i] ; ++ } ++ } ++ ++ /* Inverted */ ++ for( i = 0 ; i <= 16 ; ++i ) { ++ if( gpio_inverted[i] >= 0 && gpio_inverted[i] <= 16 ) { ++ data->gpio_config[gpio_inverted[i]] &= ~ 0x02 ; ++ } ++ } ++ ++ /* Normal overrides inverted */ ++ for( i = 0 ; i <= 16 ; ++i ) { ++ if( gpio_normal[i] >= 0 && gpio_normal[i] <= 16 ) { ++ data->gpio_config[gpio_normal[i]] |= 0x02 ; ++ } ++ } ++ ++ /* Fan overrides input and output */ ++ for( i = 0 ; i <= 7 ; ++i ) { ++ if( gpio_fan[i] >= 0 && gpio_fan[i] <= 7 ) { ++ data->config2 &= ~( 1 << gpio_fan[i] ); ++ } ++ } ++ ++ /* Write new configs to registers */ ++ adm1026_write_value(client, ADM1026_REG_CONFIG2, data->config2); ++ data->config3 = (data->config3 & 0x3f) ++ | ((data->gpio_config[16] & 0x03) << 6) ; ++ adm1026_write_value(client, ADM1026_REG_CONFIG3, data->config3); ++ for( i = 15, value = 0 ; i >= 0 ; --i ) { ++ value <<= 2 ; ++ value |= data->gpio_config[i] & 0x03 ; ++ if( (i & 0x03) == 0 ) { ++ adm1026_write_value(client, ++ ADM1026_REG_GPIO_CFG_0_3 + i/4, ++ value ); ++ value = 0 ; ++ } ++ } ++ ++ /* Print the new config */ ++ adm1026_print_gpio(client); ++} ++ ++void adm1026_update_client(struct i2c_client *client) ++{ ++ struct adm1026_data *data = client->data; ++ int i; ++ long value, alarms, gpio ; ++ ++ down(&data->update_lock); ++ ++ if (!data->valid ++ || (jiffies - data->last_reading > ADM1026_DATA_INTERVAL )) { ++ /* Things that change quickly */ ++ ++#ifdef DEBUG ++ printk("adm1026(%d): Reading sensor values\n", client->id); ++#endif ++ for (i = 0 ; i <= 16 ; ++i) { ++ data->in[i] = ++ adm1026_read_value(client, ADM1026_REG_IN(i)); ++ } ++ ++ for (i = 0 ; i <= 7 ; ++i) { ++ data->fan[i] = ++ adm1026_read_value(client, ADM1026_REG_FAN(i)); ++ } ++ ++ for (i = 0 ; i <= 2 ; ++i) { ++ /* NOTE: temp[] is s8 and we assume 2's complement ++ * "conversion" in the assignment */ ++ data->temp[i] = ++ adm1026_read_value(client, ADM1026_REG_TEMP(i)); ++ } ++ ++ data->pwm = adm1026_read_value(client, ADM1026_REG_PWM); ++ data->analog_out = adm1026_read_value(client, ADM1026_REG_DAC); ++ ++ /* GPIO16 is MSbit of alarms, move it to gpio */ ++ alarms = adm1026_read_value(client, ADM1026_REG_STATUS4); ++ gpio = alarms & 0x80 ? 0x0100 : 0 ; /* GPIO16 */ ++ alarms &= 0x7f ; ++ alarms <<= 8 ; ++ alarms |= adm1026_read_value(client, ADM1026_REG_STATUS3); ++ alarms <<= 8 ; ++ alarms |= adm1026_read_value(client, ADM1026_REG_STATUS2); ++ alarms <<= 8 ; ++ alarms |= adm1026_read_value(client, ADM1026_REG_STATUS1); ++ data->alarms = alarms ; ++ ++ /* Read the GPIO values */ ++ gpio |= adm1026_read_value(client, ADM1026_REG_GPIO_STATUS_8_15); ++ gpio <<= 8 ; ++ gpio |= adm1026_read_value(client, ADM1026_REG_GPIO_STATUS_0_7); ++ data->gpio = gpio ; ++ ++ data->last_reading = jiffies ; ++ }; /* last_reading */ ++ ++ if (!data->valid ++ || (jiffies - data->last_config > ADM1026_CONFIG_INTERVAL) ) { ++ /* Things that don't change often */ ++ ++#ifdef DEBUG ++ printk("adm1026(%d): Reading config values\n", client->id); ++#endif ++ for (i = 0 ; i <= 16 ; ++i) { ++ data->in_min[i] = ++ adm1026_read_value(client, ADM1026_REG_IN_MIN(i)); ++ data->in_max[i] = ++ adm1026_read_value(client, ADM1026_REG_IN_MAX(i)); ++ } ++ ++ value = adm1026_read_value(client, ADM1026_REG_FAN_DIV_0_3) ++ | (adm1026_read_value(client, ADM1026_REG_FAN_DIV_4_7) << 8); ++ for (i = 0 ; i <= 7 ; ++i) { ++ data->fan_min[i] = ++ adm1026_read_value(client, ADM1026_REG_FAN_MIN(i)); ++ data->fan_div[i] = DIV_FROM_REG(value & 0x03); ++ value >>= 2 ; ++ } ++ ++ for (i = 0; i <= 2; ++i) { ++ /* NOTE: temp_xxx[] are s8 and we assume 2's complement ++ * "conversion" in the assignment */ ++ data->temp_min[i] = ++ adm1026_read_value(client, ADM1026_REG_TEMP_MIN(i)); ++ data->temp_max[i] = ++ adm1026_read_value(client, ADM1026_REG_TEMP_MAX(i)); ++ data->temp_tmin[i] = ++ adm1026_read_value(client, ADM1026_REG_TEMP_TMIN(i)); ++ data->temp_therm[i] = ++ adm1026_read_value(client, ADM1026_REG_TEMP_THERM(i)); ++ data->temp_offset[i] = ++ adm1026_read_value(client, ADM1026_REG_TEMP_OFFSET(i)); ++ } ++ ++ /* Read the STATUS/alarm masks */ ++ alarms = adm1026_read_value(client, ADM1026_REG_MASK4); ++ gpio = alarms & 0x80 ? 0x0100 : 0 ; /* GPIO16 */ ++ alarms = (alarms & 0x7f) << 8 ; ++ alarms |= adm1026_read_value(client, ADM1026_REG_MASK3); ++ alarms <<= 8 ; ++ alarms |= adm1026_read_value(client, ADM1026_REG_MASK2); ++ alarms <<= 8 ; ++ alarms |= adm1026_read_value(client, ADM1026_REG_MASK1); ++ data->alarm_mask = alarms ; ++ ++ /* Read the GPIO values */ ++ gpio |= adm1026_read_value(client, ADM1026_REG_GPIO_MASK_8_15); ++ gpio <<= 8 ; ++ gpio |= adm1026_read_value(client, ADM1026_REG_GPIO_MASK_0_7); ++ data->gpio_mask = gpio ; ++ ++ /* Read the GPIO config */ ++ data->config2 = adm1026_read_value(client, ADM1026_REG_CONFIG2); ++ data->config3 = adm1026_read_value(client, ADM1026_REG_CONFIG3); ++ data->gpio_config[16] = (data->config3 >> 6) & 0x03 ; ++ ++ value = 0 ; ++ for( i = 0 ; i <= 15 ; ++i ) { ++ if( (i & 0x03) == 0 ) { ++ value = adm1026_read_value(client, ++ ADM1026_REG_GPIO_CFG_0_3 + i/4 ); ++ } ++ data->gpio_config[i] = value & 0x03 ; ++ value >>= 2 ; ++ } ++ ++ data->last_config = jiffies; ++ }; /* last_config */ ++ ++ /* We don't know where or even _if_ the VID might be on the GPIO ++ * pins. But the datasheet gives an example config showing ++ * GPIO11-15 being used to monitor VID0-4, so we go with that ++ * but make the vid WRITEABLE so if it's wrong, the user can ++ * set it in /etc/sensors.conf perhaps using an expression or ++ * 0 to trigger a re-read from the GPIO pins. ++ */ ++ if( data->vid == ADM1026_INIT_VID ) { ++ /* Hasn't been set yet, make a bold assumption */ ++ printk("adm1026(%d): Setting VID from GPIO11-15.\n", ++ client->id ); ++ data->vid = (data->gpio >> 11) & 0x1f ; ++ } ++ ++ data->valid = 1; ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The following functions are the call-back functions of the /proc/sys and ++ sysctl files. The appropriate function is referenced in the ctl_table ++ extra1 field. ++ ++ Each function must return the magnitude (power of 10 to divide the ++ data with) if it is called with operation set to SENSORS_PROC_REAL_INFO. ++ It must put a maximum of *nrels elements in results reflecting the ++ data of this file, and set *nrels to the number it actually put in ++ it, if operation is SENSORS_PROC_REAL_READ. Finally, it must get ++ up to *nrels elements from results and write them to the chip, if ++ operations is SENSORS_PROC_REAL_WRITE. ++ */ ++void adm1026_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_IN0; ++ ++ /* We handle in0 - in15 here. in16 (-12V) is handled below */ ++ if (nr < 0 || nr > 15) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; /* 1.000 */ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = INS_FROM_REG(nr,data->in_min[nr]); ++ results[1] = INS_FROM_REG(nr,data->in_max[nr]); ++ results[2] = INS_FROM_REG(nr,data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->in_max[nr] = INS_TO_REG(nr,results[1]); ++ adm1026_write_value(client, ADM1026_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ if (*nrels_mag > 0) { ++ data->in_min[nr] = INS_TO_REG(nr,results[0]); ++ adm1026_write_value(client, ADM1026_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_in16(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_IN0; ++ ++ /* We handle in16 (-12V) here */ ++ if (nr != 16) ++ return ; /* ERROR */ ++ ++ /* Apply offset and swap min/max so that min is 90% of ++ * target and max is 110% of target. ++ */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; /* 1.000 */ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = INS_FROM_REG(nr,data->in_max[nr])-NEG12_OFFSET ; ++ results[1] = INS_FROM_REG(nr,data->in_min[nr])-NEG12_OFFSET ; ++ results[2] = INS_FROM_REG(nr,data->in[nr])-NEG12_OFFSET ; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->in_min[nr] = INS_TO_REG(nr,results[1]+NEG12_OFFSET); ++ adm1026_write_value(client, ADM1026_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag > 0) { ++ data->in_max[nr] = INS_TO_REG(nr,results[0]+NEG12_OFFSET); ++ adm1026_write_value(client, ADM1026_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_FAN0 ; ++ ++ if (nr < 0 || nr > 7) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr], data->fan_div[nr]); ++ results[1] = FAN_FROM_REG(data->fan[nr], data->fan_div[nr]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->fan_min[nr] = FAN_TO_REG(results[0], ++ data->fan_div[nr]); ++ adm1026_write_value(client, ADM1026_REG_FAN_MIN(nr), ++ data->fan_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++/* Adjust fan_min to account for new fan divisor */ ++void adm1026_fixup_fan_min(struct i2c_client *client, int fan, int old_div) ++{ ++ struct adm1026_data *data = client->data; ++ int new_div = data->fan_div[fan] ; ++ int new_min; ++ ++ /* 0 and 0xff are special. Don't adjust them */ ++ if( data->fan_min[fan] == 0 || data->fan_min[fan] == 0xff ) { ++ return ; ++ } ++ ++ new_min = data->fan_min[fan] * old_div / new_div ; ++ new_min = SENSORS_LIMIT(new_min, 1, 254); ++ data->fan_min[fan] = new_min ; ++ adm1026_write_value(client, ADM1026_REG_FAN_MIN(fan), new_min); ++} ++ ++void adm1026_fan_div(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int i ; ++ int value, div, old ; ++ ++ if (ctl_name != ADM1026_SYSCTL_FAN_DIV) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ for( i = 0 ; i <= 7 ; ++i ) { ++ results[i] = data->fan_div[i] ; ++ } ++ *nrels_mag = 8; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ value = 0 ; ++ for( i = 7 ; i >= 0 ; --i ) { ++ value <<= 2 ; ++ if (*nrels_mag > i) { ++ old = data->fan_div[i] ; ++ div = DIV_TO_REG(results[i]) ; ++ data->fan_div[i] = DIV_FROM_REG(div) ; ++ if( data->fan_div[i] != old ) { ++ adm1026_fixup_fan_min(client,i,old); ++ } ++ } else { ++ div = DIV_TO_REG(data->fan_div[i]) ; ++ } ++ value |= div ; ++ } ++ adm1026_write_value(client, ADM1026_REG_FAN_DIV_0_3, ++ value & 0xff); ++ adm1026_write_value(client, ADM1026_REG_FAN_DIV_4_7, ++ (value >> 8) & 0xff); ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_TEMP1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_min[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_max[nr]); ++ results[2] = TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->temp_max[nr] = TEMP_TO_REG(results[1]); ++ adm1026_write_value(client, ADM1026_REG_TEMP_MAX(nr), ++ data->temp_max[nr]); ++ } ++ if (*nrels_mag > 0) { ++ data->temp_min[nr] = TEMP_TO_REG(results[0]); ++ adm1026_write_value(client, ADM1026_REG_TEMP_MIN(nr), ++ data->temp_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_temp_offset(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_TEMP_OFFSET1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_offset[nr]); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->temp_offset[nr] = TEMP_TO_REG(results[0]); ++ adm1026_write_value(client, ADM1026_REG_TEMP_OFFSET(nr), ++ data->temp_offset[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_temp_tmin(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_TEMP_TMIN1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_tmin[nr]); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->temp_tmin[nr] = TEMP_TO_REG(results[0]); ++ adm1026_write_value(client, ADM1026_REG_TEMP_TMIN(nr), ++ data->temp_tmin[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_temp_therm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ int nr = ctl_name - ADM1026_SYSCTL_TEMP_THERM1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_therm[nr]); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->temp_therm[nr] = TEMP_TO_REG(results[0]); ++ adm1026_write_value(client, ADM1026_REG_TEMP_THERM(nr), ++ data->temp_therm[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ ++ if (ctl_name != ADM1026_SYSCTL_PWM) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm); ++ results[1] = 1 ; /* Always enabled */ ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ /* PWM enable is read-only */ ++ if (*nrels_mag > 0) { ++ data->pwm = PWM_TO_REG(results[0]); ++ adm1026_write_value(client, ADM1026_REG_PWM, ++ data->pwm); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_analog_out(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ ++ if (ctl_name != ADM1026_SYSCTL_ANALOG_OUT) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* 0 - 255 */ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = DAC_FROM_REG(data->analog_out); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->analog_out = DAC_TO_REG(results[0]); ++ adm1026_write_value(client, ADM1026_REG_DAC, ++ data->analog_out); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_afc(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ ++ if (ctl_name != ADM1026_SYSCTL_AFC) ++ return ; /* ERROR */ ++ ++ /* PWM auto fan control, DAC auto fan control */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = (data->config1 & CFG1_PWM_AFC) != 0 ; ++ results[1] = (data->config1 & CFG1_DAC_AFC) != 0 ; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->config1 = (data->config1 & ~CFG1_DAC_AFC) ++ | (results[1] ? CFG1_DAC_AFC : 0) ; ++ } ++ if (*nrels_mag > 0) { ++ data->config1 = (data->config1 & ~CFG1_PWM_AFC) ++ | (results[0] ? CFG1_PWM_AFC : 0) ; ++ adm1026_write_value(client, ADM1026_REG_CONFIG1, ++ data->config1); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ ++ if( ctl_name != ADM1026_SYSCTL_VID ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = VID_FROM_REG((data->vid)&0x3f,data->vrm); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ /* Hmmm... There isn't a VID_TO_REG mapping */ ++ if (*nrels_mag > 0) { ++ if( results[0] >= 0 ) { ++ data->vid = results[0] & 0x3f ; ++ } else { ++ data->vid = ADM1026_INIT_VID ; ++ } ++ } ++ up(&data->update_lock); ++ } ++ ++} ++ ++void adm1026_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ ++ if( ctl_name != ADM1026_SYSCTL_VRM ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm ; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag > 0) { ++ data->vrm = results[0] ; ++ } ++ } ++} ++ ++void adm1026_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ ++ if( ctl_name != ADM1026_SYSCTL_ALARMS ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = data->alarms ; ++ *nrels_mag = 1; ++ } ++ /* FIXME: Perhaps we should implement a write function ++ * to clear an alarm? ++ */ ++} ++ ++void adm1026_alarm_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ unsigned long mask ; ++ ++ if( ctl_name != ADM1026_SYSCTL_ALARM_MASK ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = data->alarm_mask ; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->alarm_mask = results[0] & 0x7fffffff ; ++ mask = data->alarm_mask ++ | (data->gpio_mask & 0x10000 ? 0x80000000 : 0) ; ++ adm1026_write_value(client, ADM1026_REG_MASK1, ++ mask & 0xff); ++ mask >>= 8 ; ++ adm1026_write_value(client, ADM1026_REG_MASK2, ++ mask & 0xff); ++ mask >>= 8 ; ++ adm1026_write_value(client, ADM1026_REG_MASK3, ++ mask & 0xff); ++ mask >>= 8 ; ++ adm1026_write_value(client, ADM1026_REG_MASK4, ++ mask & 0xff); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_gpio(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ long gpio ; ++ ++ if( ctl_name != ADM1026_SYSCTL_GPIO ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = data->gpio ; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->gpio = results[0] & 0x1ffff ; ++ gpio = data->gpio ; ++ adm1026_write_value(client, ++ ADM1026_REG_GPIO_STATUS_0_7, ++ gpio & 0xff ); ++ gpio >>= 8 ; ++ adm1026_write_value(client, ++ ADM1026_REG_GPIO_STATUS_8_15, ++ gpio & 0xff ); ++ gpio = ((gpio >> 1) & 0x80) ++ | (data->alarms >> 24 & 0x7f); ++ adm1026_write_value(client, ++ ADM1026_REG_STATUS4, ++ gpio & 0xff ); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1026_gpio_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm1026_data *data = client->data; ++ long mask ; ++ ++ if( ctl_name != ADM1026_SYSCTL_GPIO_MASK ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm1026_update_client(client); ++ results[0] = data->gpio_mask ; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->gpio_mask = results[0] & 0x1ffff ; ++ mask = data->gpio_mask ; ++ adm1026_write_value(client, ADM1026_REG_GPIO_MASK_0_7, ++ mask & 0xff); ++ mask >>= 8 ; ++ adm1026_write_value(client, ADM1026_REG_GPIO_MASK_8_15, ++ mask & 0xff); ++ mask = ((mask >> 1) & 0x80) ++ | (data->alarm_mask >> 24 & 0x7f); ++ adm1026_write_value(client, ADM1026_REG_MASK1, ++ mask & 0xff); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++static int __init sm_adm1026_init(void) ++{ ++ printk("adm1026: Version %s (%s)\n", LM_VERSION, LM_DATE); ++ printk("adm1026: See http://www.penguincomputing.com/lm_sensors for more info.\n" ); ++ return i2c_add_driver(&adm1026_driver); ++} ++ ++static void __exit sm_adm1026_exit(void) ++{ ++ i2c_del_driver(&adm1026_driver); ++} ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Philip Pokorny <ppokorny@penguincomputing.com"); ++MODULE_DESCRIPTION("ADM1026 driver"); ++ ++module_init(sm_adm1026_init); ++module_exit(sm_adm1026_exit); +--- linux-old/drivers/sensors/adm9240.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/adm9240.c Mon Dec 13 20:18:44 2004 +@@ -0,0 +1,713 @@ ++/* ++ adm9240.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (C) 1999 Frodo Looijaard <frodol@dds.nl> ++ and Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Supports ADM9240, DS1780, and LM81. See doc/chips/adm9240 for details */ ++ ++/* ++ A couple notes about the ADM9240: ++ ++* It claims to be 'LM7x' register compatible. This must be in reference ++ to only the LM78, because it is missing stuff to emulate LM75's as well. ++ (like the Winbond W83781 does) ++ ++* This driver was written from rev. 0 of the PDF, but it seems well ++ written and complete (unlike the W83781 which is horrible and has ++ supposidly gone through a few revisions.. rev 0 of that one must ++ have been in crayon on construction paper...) ++ ++* All analog inputs can range from 0 to 2.5, eventhough some inputs are ++ marked as being 5V, 12V, etc. I don't have any real voltages going ++ into my prototype, so I'm not sure that things are computed right, ++ but at least the limits seem to be working OK. ++ ++* Another curiousity is that the fan_div seems to be read-only. I.e., ++ any written value to it doesn't seem to make any difference. The ++ fan_div seems to be 'stuck' at 2 (which isn't a bad value in most cases). ++ ++ ++ --Phil ++ ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x2c, 0x2f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_3(adm9240, ds1780, lm81); ++ ++/* Many ADM9240 constants specified below */ ++ ++#define ADM9240_REG_IN_MAX(nr) (0x2b + (nr) * 2) ++#define ADM9240_REG_IN_MIN(nr) (0x2c + (nr) * 2) ++#define ADM9240_REG_IN(nr) (0x20 + (nr)) ++ ++/* The ADM9240 registers */ ++#define ADM9240_REG_TEST 0x15 ++#define ADM9240_REG_ANALOG_OUT 0x19 ++/* These are all read-only */ ++#define ADM9240_REG_2_5V 0x20 ++#define ADM9240_REG_VCCP1 0x21 ++#define ADM9240_REG_3_3V 0x22 ++#define ADM9240_REG_5V 0x23 ++#define ADM9240_REG_12V 0x24 ++#define ADM9240_REG_VCCP2 0x25 ++#define ADM9240_REG_TEMP 0x27 ++#define ADM9240_REG_FAN1 0x28 ++#define ADM9240_REG_FAN2 0x29 ++#define ADM9240_REG_COMPANY_ID 0x3E /* 0x23 for ADM9240; 0xDA for DS1780 */ ++ /* 0x01 for LM81 */ ++#define ADM9240_REG_DIE_REV 0x3F ++/* These are read/write */ ++#define ADM9240_REG_2_5V_HIGH 0x2B ++#define ADM9240_REG_2_5V_LOW 0x2C ++#define ADM9240_REG_VCCP1_HIGH 0x2D ++#define ADM9240_REG_VCCP1_LOW 0x2E ++#define ADM9240_REG_3_3V_HIGH 0x2F ++#define ADM9240_REG_3_3V_LOW 0x30 ++#define ADM9240_REG_5V_HIGH 0x31 ++#define ADM9240_REG_5V_LOW 0x32 ++#define ADM9240_REG_12V_HIGH 0x33 ++#define ADM9240_REG_12V_LOW 0x34 ++#define ADM9240_REG_VCCP2_HIGH 0x35 ++#define ADM9240_REG_VCCP2_LOW 0x36 ++#define ADM9240_REG_TCRIT_LIMIT 0x37 /* LM81 only - not supported */ ++#define ADM9240_REG_LOW_LIMIT 0x38 /* LM81 only - not supported */ ++#define ADM9240_REG_TOS 0x39 ++#define ADM9240_REG_THYST 0x3A ++#define ADM9240_REG_FAN1_MIN 0x3B ++#define ADM9240_REG_FAN2_MIN 0x3C ++ ++#define ADM9240_REG_CONFIG 0x40 ++#define ADM9240_REG_INT1_STAT 0x41 ++#define ADM9240_REG_INT2_STAT 0x42 ++#define ADM9240_REG_INT1_MASK 0x43 ++#define ADM9240_REG_INT2_MASK 0x44 ++ ++#define ADM9240_REG_COMPAT 0x45 /* dummy compat. register for other drivers? */ ++#define ADM9240_REG_CHASSIS_CLEAR 0x46 ++#define ADM9240_REG_VID_FAN_DIV 0x47 ++#define ADM9240_REG_I2C_ADDR 0x48 ++#define ADM9240_REG_VID4 0x49 ++#define ADM9240_REG_TEMP_CONFIG 0x4B ++#define ADM9240_REG_EXTMODE1 0x4C /* LM81 only - not supported */ ++#define ADM9240_REG_EXTMODE2 0x4D /* LM81 only - not supported */ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val), 0, 255)) ++#define IN_FROM_REG(val,nr) (val) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:\ ++ (val)==255?0:1350000/((div)*(val))) ++ ++#define TEMP_FROM_REG(temp) ((temp)<256 ? (temp) * 5 : \ ++ ((temp) - 512) * 5) ++ ++#define TEMP_LIMIT_FROM_REG(val) (((val)>=0x80?(val)-0x100:(val))*10) ++ ++#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10)+256:\ ++ ((val)+5)/10), \ ++ 0,255) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1))) ++ ++#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ ++ 205-(val)*5) ++ ++/* For each registered ADM9240, we need to keep some data in memory. */ ++struct adm9240_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[6]; /* Register value */ ++ u8 in_max[6]; /* Register value */ ++ u8 in_min[6]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ int temp; /* Temp, shifted right */ ++ u8 temp_os_max; /* Register value */ ++ u8 temp_os_hyst; /* Register value */ ++ u16 alarms; /* Register encoding, combined */ ++ u8 analog_out; /* Register value */ ++ u8 vid; /* Register value combined */ ++}; ++ ++ ++static int adm9240_attach_adapter(struct i2c_adapter *adapter); ++static int adm9240_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int adm9240_detach_client(struct i2c_client *client); ++ ++static int adm9240_read_value(struct i2c_client *client, u8 reg); ++static int adm9240_write_value(struct i2c_client *client, u8 reg, u8 value); ++static void adm9240_update_client(struct i2c_client *client); ++static void adm9240_init_client(struct i2c_client *client); ++ ++ ++static void adm9240_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm9240_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm9240_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm9240_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm9240_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm9240_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, ++ long *results); ++static void adm9240_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int adm9240_id = 0; ++ ++static struct i2c_driver adm9240_driver = { ++ .owner = THIS_MODULE, ++ .name = "ADM9240 sensor driver", ++ .id = I2C_DRIVERID_ADM9240, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = adm9240_attach_adapter, ++ .detach_client = adm9240_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define ADM9240_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define ADM9240_SYSCTL_IN1 1001 ++#define ADM9240_SYSCTL_IN2 1002 ++#define ADM9240_SYSCTL_IN3 1003 ++#define ADM9240_SYSCTL_IN4 1004 ++#define ADM9240_SYSCTL_IN5 1005 ++#define ADM9240_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define ADM9240_SYSCTL_FAN2 1102 ++#define ADM9240_SYSCTL_TEMP 1250 /* Degrees Celcius * 100 */ ++#define ADM9240_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define ADM9240_SYSCTL_ALARMS 2001 /* bitvector */ ++#define ADM9240_SYSCTL_ANALOG_OUT 2002 ++#define ADM9240_SYSCTL_VID 2003 ++ ++#define ADM9240_ALARM_IN0 0x0001 ++#define ADM9240_ALARM_IN1 0x0002 ++#define ADM9240_ALARM_IN2 0x0004 ++#define ADM9240_ALARM_IN3 0x0008 ++#define ADM9240_ALARM_IN4 0x0100 ++#define ADM9240_ALARM_IN5 0x0200 ++#define ADM9240_ALARM_FAN1 0x0040 ++#define ADM9240_ALARM_FAN2 0x0080 ++#define ADM9240_ALARM_TEMP 0x0010 ++#define ADM9240_ALARM_CHAS 0x1000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected ADM9240. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table adm9240_dir_table_template[] = { ++ {ADM9240_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_in}, ++ {ADM9240_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_in}, ++ {ADM9240_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_in}, ++ {ADM9240_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_in}, ++ {ADM9240_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_in}, ++ {ADM9240_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_in}, ++ {ADM9240_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_fan}, ++ {ADM9240_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_fan}, ++ {ADM9240_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_temp}, ++ {ADM9240_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_fan_div}, ++ {ADM9240_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_alarms}, ++ {ADM9240_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_analog_out}, ++ {ADM9240_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &adm9240_vid}, ++ {0} ++}; ++ ++static int adm9240_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, adm9240_detect); ++} ++ ++static int adm9240_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct adm9240_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access adm9240_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct adm9240_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &adm9240_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if ( ++ ((adm9240_read_value ++ (new_client, ADM9240_REG_CONFIG) & 0x80) != 0x00) ++ || ++ (adm9240_read_value(new_client, ADM9240_REG_I2C_ADDR) ++ != address)) ++ goto ERROR1; ++ } ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ i = adm9240_read_value(new_client, ADM9240_REG_COMPANY_ID); ++ if (i == 0x23) ++ kind = adm9240; ++ else if (i == 0xda) ++ kind = ds1780; ++ else if (i == 0x01) ++ kind = lm81; ++ else { ++ if (kind == 0) ++ printk ++ ("adm9240.o: Ignoring 'force' parameter for unknown chip at " ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ goto ERROR1; ++ } ++ } ++ ++ if (kind == adm9240) { ++ type_name = "adm9240"; ++ client_name = "ADM9240 chip"; ++ } else if (kind == ds1780) { ++ type_name = "ds1780"; ++ client_name = "DS1780 chip"; ++ } else if (kind == lm81) { ++ type_name = "lm81"; ++ client_name = "LM81 chip"; ++ } else { ++#ifdef DEBUG ++ printk("adm9240.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = adm9240_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ adm9240_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the ADM9240 chip */ ++ adm9240_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int adm9240_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct adm9240_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("adm9240.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++static int adm9240_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int adm9240_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new ADM9240. */ ++static void adm9240_init_client(struct i2c_client *client) ++{ ++ /* Start monitoring */ ++ adm9240_write_value(client, ADM9240_REG_CONFIG, 0x01); ++} ++ ++static void adm9240_update_client(struct i2c_client *client) ++{ ++ struct adm9240_data *data = client->data; ++ u8 i; ++ ++ down(&data->update_lock); ++ ++ if ( ++ (jiffies - data->last_updated > ++ (data->type == adm9240 ? HZ / 2 : HZ * 2)) ++ || (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting adm9240 update\n"); ++#endif ++ for (i = 0; i <= 5; i++) { ++ data->in[i] = ++ adm9240_read_value(client, ADM9240_REG_IN(i)); ++ data->in_min[i] = ++ adm9240_read_value(client, ++ ADM9240_REG_IN_MIN(i)); ++ data->in_max[i] = ++ adm9240_read_value(client, ++ ADM9240_REG_IN_MAX(i)); ++ } ++ data->fan[0] = ++ adm9240_read_value(client, ADM9240_REG_FAN1); ++ data->fan_min[0] = ++ adm9240_read_value(client, ADM9240_REG_FAN1_MIN); ++ data->fan[1] = ++ adm9240_read_value(client, ADM9240_REG_FAN2); ++ data->fan_min[1] = ++ adm9240_read_value(client, ADM9240_REG_FAN2_MIN); ++ data->temp = ++ (adm9240_read_value(client, ADM9240_REG_TEMP) << 1) + ++ ((adm9240_read_value ++ (client, ADM9240_REG_TEMP_CONFIG) & 0x80) >> 7); ++ data->temp_os_max = ++ adm9240_read_value(client, ADM9240_REG_TOS); ++ data->temp_os_hyst = ++ adm9240_read_value(client, ADM9240_REG_THYST); ++ ++ i = adm9240_read_value(client, ADM9240_REG_VID_FAN_DIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = (i >> 6) & 0x03; ++ data->vid = i & 0x0f; ++ data->vid |= ++ (adm9240_read_value(client, ADM9240_REG_VID4) & 0x01) ++ << 4; ++ ++ data->alarms = ++ adm9240_read_value(client, ++ ADM9240_REG_INT1_STAT) + ++ (adm9240_read_value(client, ADM9240_REG_INT2_STAT) << ++ 8); ++ data->analog_out = ++ adm9240_read_value(client, ADM9240_REG_ANALOG_OUT); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void adm9240_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ ++ int scales[6] = { 250, 270, 330, 500, 1200, 270 }; ++ ++ struct adm9240_data *data = client->data; ++ int nr = ctl_name - ADM9240_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = ++ IN_FROM_REG(data->in_min[nr], nr) * scales[nr] / 192; ++ results[1] = ++ IN_FROM_REG(data->in_max[nr], nr) * scales[nr] / 192; ++ results[2] = ++ IN_FROM_REG(data->in[nr], nr) * scales[nr] / 192; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = ++ IN_TO_REG((results[0] * 192) / scales[nr], nr); ++ adm9240_write_value(client, ADM9240_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = ++ IN_TO_REG((results[1] * 192) / scales[nr], nr); ++ adm9240_write_value(client, ADM9240_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void adm9240_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm9240_data *data = client->data; ++ int nr = ctl_name - ADM9240_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data-> ++ fan_div[nr - 1])); ++ results[1] = ++ FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr - ++ 1])); ++ adm9240_write_value(client, ++ nr == ++ 1 ? ADM9240_REG_FAN1_MIN : ++ ADM9240_REG_FAN2_MIN, ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void adm9240_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm9240_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = TEMP_LIMIT_FROM_REG(data->temp_os_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_os_max = TEMP_LIMIT_TO_REG(results[0]); ++ adm9240_write_value(client, ADM9240_REG_TOS, ++ data->temp_os_max); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[1]); ++ adm9240_write_value(client, ADM9240_REG_THYST, ++ data->temp_os_hyst); ++ } ++ } ++} ++ ++void adm9240_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm9240_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void adm9240_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm9240_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = adm9240_read_value(client, ADM9240_REG_VID_FAN_DIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ adm9240_write_value(client, ++ ADM9240_REG_VID_FAN_DIV, old); ++ } ++ } ++} ++ ++void adm9240_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct adm9240_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = data->analog_out; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->analog_out = results[0]; ++ adm9240_write_value(client, ADM9240_REG_ANALOG_OUT, ++ data->analog_out); ++ } ++ } ++} ++ ++void adm9240_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct adm9240_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ adm9240_update_client(client); ++ results[0] = VID_FROM_REG(data->vid); ++ *nrels_mag = 1; ++ } ++} ++ ++static int __init sm_adm9240_init(void) ++{ ++ printk("adm9240.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&adm9240_driver); ++} ++ ++static void __exit sm_adm9240_exit(void) ++{ ++ i2c_del_driver(&adm9240_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("ADM9240 driver"); ++ ++module_init(sm_adm9240_init); ++module_exit(sm_adm9240_exit); +--- linux-old/drivers/sensors/asb100.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/asb100.c Mon Dec 13 20:18:45 2004 +@@ -0,0 +1,1025 @@ ++/* ++ asb100.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ ++ Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> ++ ++ (derived from w83781d.c) ++ ++ Copyright (c) 1998 - 2003 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, and ++ Mark Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ This driver supports the hardware sensor chips: Asus ASB100 and ++ ASB100-A "BACH". ++ ++ ASB100-A supports pwm1, while plain ASB100 does not. There is no known ++ way for the driver to tell which one is there. ++ ++ Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA ++ asb100 7 3 1 4 0x31 0x0694 yes no ++*/ ++ ++//#define DEBUG 1 ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++#include "lm75.h" ++ ++#ifndef I2C_DRIVERID_ASB100 ++#define I2C_DRIVERID_ASB100 1043 ++#endif ++ ++/* I2C addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x28, 0x2f, SENSORS_I2C_END }; ++ ++/* ISA addresses to scan (none) */ ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* default VRM to 9.0 instead of 8.2 */ ++#define ASB100_DEFAULT_VRM 90 ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(asb100); ++SENSORS_MODULE_PARM(force_subclients, "List of subclient addresses: " \ ++ "{bus, clientaddr, subclientaddr1, subclientaddr2}"); ++ ++/* Voltage IN registers 0-6 */ ++#define ASB100_REG_IN(nr) (0x20 + (nr)) ++#define ASB100_REG_IN_MAX(nr) (0x2b + (nr * 2)) ++#define ASB100_REG_IN_MIN(nr) (0x2c + (nr * 2)) ++ ++/* FAN IN registers 1-3 */ ++#define ASB100_REG_FAN(nr) (0x27 + (nr)) ++#define ASB100_REG_FAN_MIN(nr) (0x3a + (nr)) ++ ++/* TEMPERATURE registers 1-4 */ ++static const u16 asb100_reg_temp[] = {0, 0x27, 0x150, 0x250, 0x17}; ++static const u16 asb100_reg_temp_max[] = {0, 0x39, 0x155, 0x255, 0x18}; ++static const u16 asb100_reg_temp_hyst[] = {0, 0x3a, 0x153, 0x253, 0x19}; ++ ++#define ASB100_REG_TEMP(nr) (asb100_reg_temp[nr]) ++#define ASB100_REG_TEMP_MAX(nr) (asb100_reg_temp_max[nr]) ++#define ASB100_REG_TEMP_HYST(nr) (asb100_reg_temp_hyst[nr]) ++ ++#define ASB100_REG_TEMP2_CONFIG 0x0152 ++#define ASB100_REG_TEMP3_CONFIG 0x0252 ++ ++ ++#define ASB100_REG_CONFIG 0x40 ++#define ASB100_REG_ALARM1 0x41 ++#define ASB100_REG_ALARM2 0x42 ++#define ASB100_REG_SMIM1 0x43 ++#define ASB100_REG_SMIM2 0x44 ++#define ASB100_REG_VID_FANDIV 0x47 ++#define ASB100_REG_I2C_ADDR 0x48 ++#define ASB100_REG_CHIPID 0x49 ++#define ASB100_REG_I2C_SUBADDR 0x4a ++#define ASB100_REG_PIN 0x4b ++#define ASB100_REG_IRQ 0x4c ++#define ASB100_REG_BANK 0x4e ++#define ASB100_REG_CHIPMAN 0x4f ++ ++#define ASB100_REG_WCHIPID 0x58 ++ ++/* bit 7 -> enable, bits 0-3 -> duty cycle */ ++#define ASB100_REG_PWM1 0x59 ++ ++/* CONVERSIONS ++ Rounding and limit checking is only done on the TO_REG variants. */ ++ ++/* These constants are a guess, consistent w/ w83781d */ ++#define ASB100_IN_MIN ( 0) ++#define ASB100_IN_MAX (408) ++ ++/* IN: 1/100 V (0V to 4.08V) ++ REG: 16mV/bit */ ++static u8 IN_TO_REG(unsigned val) ++{ ++ unsigned nval = SENSORS_LIMIT(val, ASB100_IN_MIN, ASB100_IN_MAX); ++ return (nval * 10 + 8) / 16; ++} ++ ++static unsigned IN_FROM_REG(u8 reg) ++{ ++ return (reg * 16 + 5) / 10; ++} ++ ++static u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, 254); ++} ++ ++static int FAN_FROM_REG(u8 val, int div) ++{ ++ return val==0 ? -1 : val==255 ? 0 : 1350000/(val*div); ++} ++ ++/* These constants are a guess, consistent w/ w83781d */ ++#define ASB100_TEMP_MIN (-1280) ++#define ASB100_TEMP_MAX ( 1270) ++ ++/* TEMP: 1/10 degrees C (-128C to +127C) ++ REG: 1C/bit, two's complement */ ++static u8 TEMP_TO_REG(int temp) ++{ ++ int ntemp = SENSORS_LIMIT(temp, ASB100_TEMP_MIN, ASB100_TEMP_MAX); ++ ntemp += (ntemp<0 ? -5 : 5); ++ return (u8)(ntemp / 10); ++} ++ ++static int TEMP_FROM_REG(u8 reg) ++{ ++ return (s8)reg * 10; ++} ++ ++/* PWM: 0 - 255 per sensors documentation ++ REG: (6.25% duty cycle per bit) */ ++static u8 ASB100_PWM_TO_REG(int pwm) ++{ ++ pwm = SENSORS_LIMIT(pwm, 0, 255); ++ return (u8)(pwm / 16); ++} ++ ++static int ASB100_PWM_FROM_REG(u8 reg) ++{ ++ return reg * 16; ++} ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++/* FAN DIV: 1, 2, 4, or 8 (defaults to 2) ++ REG: 0, 1, 2, or 3 (respectively) (defaults to 1) */ ++static u8 DIV_TO_REG(long val) ++{ ++ return val==8 ? 3 : val==4 ? 2 : val==1 ? 0 : 1; ++} ++ ++/* For each registered client, we need to keep some data in memory. That ++ data is pointed to by client->data. The structure itself is ++ dynamically allocated, at the same time the client itself is allocated. */ ++struct asb100_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ unsigned long last_updated; /* In jiffies */ ++ ++ /* array of 2 pointers to subclients */ ++ struct i2c_client *lm75[2]; ++ ++ char valid; /* !=0 if following fields are valid */ ++ u8 in[7]; /* Register value */ ++ u8 in_max[7]; /* Register value */ ++ u8 in_min[7]; /* Register value */ ++ u8 fan[3]; /* Register value */ ++ u8 fan_min[3]; /* Register value */ ++ u16 temp[4]; /* Register value (0 and 3 are u8 only) */ ++ u16 temp_max[4]; /* Register value (0 and 3 are u8 only) */ ++ u16 temp_hyst[4]; /* Register value (0 and 3 are u8 only) */ ++ u8 fan_div[3]; /* Register encoding, right justified */ ++ u8 pwm; /* Register encoding */ ++ u8 vid; /* Register encoding, combined */ ++ u32 alarms; /* Register encoding, combined */ ++ u8 vrm; ++}; ++ ++static int asb100_attach_adapter(struct i2c_adapter *adapter); ++static int asb100_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int asb100_detach_client(struct i2c_client *client); ++ ++static int asb100_read_value(struct i2c_client *client, u16 reg); ++static void asb100_write_value(struct i2c_client *client, u16 reg, u16 val); ++static void asb100_update_client(struct i2c_client *client); ++static void asb100_init_client(struct i2c_client *client); ++ ++static void asb100_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void asb100_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static struct i2c_driver asb100_driver = { ++ .owner = THIS_MODULE, ++ .name = "asb100", ++ .id = I2C_DRIVERID_ASB100, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = asb100_attach_adapter, ++ .detach_client = asb100_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define ASB100_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define ASB100_SYSCTL_IN1 1001 ++#define ASB100_SYSCTL_IN2 1002 ++#define ASB100_SYSCTL_IN3 1003 ++#define ASB100_SYSCTL_IN4 1004 ++#define ASB100_SYSCTL_IN5 1005 ++#define ASB100_SYSCTL_IN6 1006 ++ ++#define ASB100_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define ASB100_SYSCTL_FAN2 1102 ++#define ASB100_SYSCTL_FAN3 1103 ++ ++#define ASB100_SYSCTL_TEMP1 1200 /* Degrees Celcius * 10 */ ++#define ASB100_SYSCTL_TEMP2 1201 ++#define ASB100_SYSCTL_TEMP3 1202 ++#define ASB100_SYSCTL_TEMP4 1203 ++ ++#define ASB100_SYSCTL_VID 1300 /* Volts * 1000 */ ++#define ASB100_SYSCTL_VRM 1301 ++ ++#define ASB100_SYSCTL_PWM1 1401 /* 0-255 => 0-100% duty cycle */ ++ ++#define ASB100_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define ASB100_SYSCTL_ALARMS 2001 /* bitvector */ ++ ++#define ASB100_ALARM_IN0 0x0001 /* ? */ ++#define ASB100_ALARM_IN1 0x0002 /* ? */ ++#define ASB100_ALARM_IN2 0x0004 ++#define ASB100_ALARM_IN3 0x0008 ++#define ASB100_ALARM_TEMP1 0x0010 ++#define ASB100_ALARM_TEMP2 0x0020 ++#define ASB100_ALARM_FAN1 0x0040 ++#define ASB100_ALARM_FAN2 0x0080 ++#define ASB100_ALARM_IN4 0x0100 ++#define ASB100_ALARM_IN5 0x0200 /* ? */ ++#define ASB100_ALARM_IN6 0x0400 /* ? */ ++#define ASB100_ALARM_FAN3 0x0800 ++#define ASB100_ALARM_CHAS 0x1000 ++#define ASB100_ALARM_TEMP3 0x2000 ++ ++#define ASB100_ALARM_IN7 0x10000 /* ? */ ++#define ASB100_ALARM_IN8 0x20000 /* ? */ ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected chip. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++ ++/* no datasheet - but we did get some hints from someone who ++ claimed to have the datasheet */ ++#define ASB100_SYSCTL_IN(nr) {ASB100_SYSCTL_IN##nr, "in" #nr, NULL, 0, \ ++ 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &asb100_in} ++#define ASB100_SYSCTL_FAN(nr) {ASB100_SYSCTL_FAN##nr, "fan" #nr, NULL, 0, \ ++ 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &asb100_fan} ++#define ASB100_SYSCTL_TEMP(nr, func) {ASB100_SYSCTL_TEMP##nr, "temp" #nr, \ ++ NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, func} ++static ctl_table asb100_dir_table_template[] = { ++ ASB100_SYSCTL_IN(0), ++ ASB100_SYSCTL_IN(1), ++ ASB100_SYSCTL_IN(2), ++ ASB100_SYSCTL_IN(3), ++ ASB100_SYSCTL_IN(4), ++ ASB100_SYSCTL_IN(5), ++ ASB100_SYSCTL_IN(6), ++ ++ ASB100_SYSCTL_FAN(1), ++ ASB100_SYSCTL_FAN(2), ++ ASB100_SYSCTL_FAN(3), ++ ++ ASB100_SYSCTL_TEMP(1, &asb100_temp), ++ ASB100_SYSCTL_TEMP(2, &asb100_temp_add), ++ ASB100_SYSCTL_TEMP(3, &asb100_temp_add), ++ ASB100_SYSCTL_TEMP(4, &asb100_temp), ++ ++ {ASB100_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &asb100_vid}, ++ {ASB100_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &asb100_vrm}, ++ {ASB100_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &asb100_fan_div}, ++ {ASB100_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &asb100_alarms}, ++ {ASB100_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &asb100_pwm}, ++ {0} ++}; ++ ++/* This function is called when: ++ asb100_driver is inserted (when this module is loaded), for each ++ available adapter ++ when a new adapter is inserted (and asb100_driver is still present) ++ */ ++static int asb100_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, asb100_detect); ++} ++ ++static int asb100_detect_subclients(struct i2c_adapter *adapter, int address, ++ int kind, struct i2c_client *new_client) ++{ ++ int i, id, err; ++ struct asb100_data *data = new_client->data; ++ ++ data->lm75[0] = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); ++ if (!(data->lm75[0])) { ++ err = -ENOMEM; ++ goto ERROR_SC_0; ++ } ++ memset(data->lm75[0], 0x00, sizeof(struct i2c_client)); ++ ++ data->lm75[1] = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); ++ if (!(data->lm75[1])) { ++ err = -ENOMEM; ++ goto ERROR_SC_1; ++ } ++ memset(data->lm75[1], 0x00, sizeof(struct i2c_client)); ++ ++ id = i2c_adapter_id(adapter); ++ ++ if (force_subclients[0] == id && force_subclients[1] == address) { ++ for (i = 2; i <= 3; i++) { ++ if (force_subclients[i] < 0x48 || ++ force_subclients[i] > 0x4f) { ++ printk(KERN_ERR "asb100.o: invalid subclient " ++ "address %d; must be 0x48-0x4f\n", ++ force_subclients[i]); ++ err = -ENODEV; ++ goto ERROR_SC_2; ++ } ++ } ++ asb100_write_value(new_client, ASB100_REG_I2C_SUBADDR, ++ (force_subclients[2] & 0x07) | ++ ((force_subclients[3] & 0x07) <<4)); ++ data->lm75[0]->addr = force_subclients[2]; ++ data->lm75[1]->addr = force_subclients[3]; ++ } else { ++ int val = asb100_read_value(new_client, ASB100_REG_I2C_SUBADDR); ++ data->lm75[0]->addr = 0x48 + (val & 0x07); ++ data->lm75[1]->addr = 0x48 + ((val >> 4) & 0x07); ++ } ++ ++ if(data->lm75[0]->addr == data->lm75[1]->addr) { ++ printk(KERN_ERR "asb100.o: duplicate addresses 0x%x " ++ "for subclients\n", data->lm75[0]->addr); ++ err = -ENODEV; ++ goto ERROR_SC_2; ++ } ++ ++ for (i = 0; i <= 1; i++) { ++ data->lm75[i]->data = NULL; ++ data->lm75[i]->adapter = adapter; ++ data->lm75[i]->driver = &asb100_driver; ++ data->lm75[i]->flags = 0; ++ strcpy(data->lm75[i]->name, "asb100 subclient"); ++ } ++ ++ if ((err = i2c_attach_client(data->lm75[0]))) { ++ printk(KERN_ERR "asb100.o: Subclient %d registration " ++ "at address 0x%x failed.\n", i, data->lm75[0]->addr); ++ goto ERROR_SC_2; ++ } ++ ++ if ((err = i2c_attach_client(data->lm75[1]))) { ++ printk(KERN_ERR "asb100.o: Subclient %d registration " ++ "at address 0x%x failed.\n", i, data->lm75[1]->addr); ++ goto ERROR_SC_3; ++ } ++ ++ return 0; ++ ++/* Undo inits in case of errors */ ++ERROR_SC_3: ++ i2c_detach_client(data->lm75[0]); ++ERROR_SC_2: ++ kfree(data->lm75[1]); ++ERROR_SC_1: ++ kfree(data->lm75[0]); ++ERROR_SC_0: ++ return err; ++} ++ ++static int asb100_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int err; ++ struct i2c_client *new_client; ++ struct asb100_data *data; ++ ++ /* asb100 is SMBus only */ ++ if (i2c_is_isa_adapter(adapter)) { ++ pr_debug("asb100.o: detect failed, " ++ "cannot attach to legacy adapter!\n"); ++ err = -ENODEV; ++ goto ERROR0; ++ } ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { ++ pr_debug("asb100.o: detect failed, " ++ "smbus byte data not supported!\n"); ++ err = -ENODEV; ++ goto ERROR0; ++ } ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access asb100_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct asb100_data), GFP_KERNEL))) { ++ pr_debug("asb100.o: detect failed, kmalloc failed!\n"); ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &asb100_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ /* The chip may be stuck in some other bank than bank 0. This may ++ make reading other information impossible. Specify a force=... or ++ force_*=... parameter, and the chip will be reset to the right ++ bank. */ ++ if (kind < 0) { ++ ++ int val1 = asb100_read_value(new_client, ASB100_REG_BANK); ++ int val2 = asb100_read_value(new_client, ASB100_REG_CHIPMAN); ++ ++ /* If we're in bank 0 */ ++ if ( (!(val1 & 0x07)) && ++ /* Check for ASB100 ID (low byte) */ ++ ( ((!(val1 & 0x80)) && (val2 != 0x94)) || ++ /* Check for ASB100 ID (high byte ) */ ++ ((val1 & 0x80) && (val2 != 0x06)) ) ) { ++ pr_debug("asb100.o: detect failed, " ++ "bad chip id 0x%02x!\n", val2); ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ ++ } /* kind < 0 */ ++ ++ /* We have either had a force parameter, or we have already detected ++ Winbond. Put it now into bank 0 and Vendor ID High Byte */ ++ asb100_write_value(new_client, ASB100_REG_BANK, ++ (asb100_read_value(new_client, ASB100_REG_BANK) & 0x78) | 0x80); ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ int val1 = asb100_read_value(new_client, ASB100_REG_WCHIPID); ++ int val2 = asb100_read_value(new_client, ASB100_REG_CHIPMAN); ++ ++ if ((val1 == 0x31) && (val2 == 0x06)) ++ kind = asb100; ++ else { ++ if (kind == 0) ++ printk (KERN_WARNING "asb100.o: Ignoring " ++ "'force' parameter for unknown chip " ++ "at adapter %d, address 0x%02x.\n", ++ i2c_adapter_id(adapter), address); ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ } ++ ++ /* Fill in remaining client fields and put it into the global list */ ++ strcpy(new_client->name, "ASB100 chip"); ++ data->type = kind; ++ ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR1; ++ ++ /* Attach secondary lm75 clients */ ++ if ((err = asb100_detect_subclients(adapter, address, kind, ++ new_client))) ++ goto ERROR2; ++ ++ /* Initialize the chip */ ++ asb100_init_client(new_client); ++ ++ /* Register a new directory entry with module sensors */ ++ if ((data->sysctl_id = i2c_register_entry(new_client, "asb100", ++ asb100_dir_table_template)) < 0) { ++ err = data->sysctl_id; ++ goto ERROR3; ++ } ++ ++ return 0; ++ ++ERROR3: ++ i2c_detach_client(data->lm75[0]); ++ kfree(data->lm75[1]); ++ kfree(data->lm75[0]); ++ERROR2: ++ i2c_detach_client(new_client); ++ERROR1: ++ kfree(data); ++ERROR0: ++ return err; ++} ++ ++static int asb100_detach_client(struct i2c_client *client) ++{ ++ int err; ++ struct asb100_data *data = client->data; ++ ++ /* remove sysctl table (primary client only) */ ++ if ((data)) ++ i2c_deregister_entry(data->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk (KERN_ERR "asb100.o: Client deregistration failed; " ++ "client not detached.\n"); ++ return err; ++ } ++ ++ if (data) { ++ /* primary client */ ++ kfree(data); ++ } else { ++ /* subclients */ ++ kfree(client); ++ } ++ ++ return 0; ++} ++ ++/* The SMBus locks itself, usually, but nothing may access the Winbond between ++ bank switches. ISA access must always be locked explicitly! ++ We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the W83781D access and should not be necessary. ++ There are some ugly typecasts here, but the good news is - they should ++ nowhere else be necessary! */ ++static int asb100_read_value(struct i2c_client *client, u16 reg) ++{ ++ struct asb100_data *data = client->data; ++ struct i2c_client *cl; ++ int res, bank; ++ ++ down(&data->lock); ++ ++ bank = (reg >> 8) & 0x0f; ++ if (bank > 2) ++ /* switch banks */ ++ i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank); ++ ++ if (bank == 0 || bank > 2) { ++ res = i2c_smbus_read_byte_data(client, reg & 0xff); ++ } else { ++ /* switch to subclient */ ++ cl = data->lm75[bank - 1]; ++ ++ /* convert from ISA to LM75 I2C addresses */ ++ switch (reg & 0xff) { ++ case 0x50: /* TEMP */ ++ res = swab16(i2c_smbus_read_word_data (cl, 0)); ++ break; ++ case 0x52: /* CONFIG */ ++ res = i2c_smbus_read_byte_data(cl, 1); ++ break; ++ case 0x53: /* HYST */ ++ res = swab16(i2c_smbus_read_word_data (cl, 2)); ++ break; ++ case 0x55: /* MAX */ ++ default: ++ res = swab16(i2c_smbus_read_word_data (cl, 3)); ++ break; ++ } ++ } ++ ++ if (bank > 2) ++ i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0); ++ ++ up(&data->lock); ++ ++ return res; ++} ++ ++static void asb100_write_value(struct i2c_client *client, u16 reg, u16 value) ++{ ++ struct asb100_data *data = client->data; ++ struct i2c_client *cl; ++ int bank; ++ ++ down(&data->lock); ++ ++ bank = (reg >> 8) & 0x0f; ++ if (bank > 2) ++ /* switch banks */ ++ i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank); ++ ++ if (bank == 0 || bank > 2) { ++ i2c_smbus_write_byte_data(client, reg & 0xff, value & 0xff); ++ } else { ++ /* switch to subclient */ ++ cl = data->lm75[bank - 1]; ++ ++ /* convert from ISA to LM75 I2C addresses */ ++ switch (reg & 0xff) { ++ case 0x52: /* CONFIG */ ++ i2c_smbus_write_byte_data(cl, 1, value & 0xff); ++ break; ++ case 0x53: /* HYST */ ++ i2c_smbus_write_word_data(cl, 2, swab16(value)); ++ break; ++ case 0x55: /* MAX */ ++ i2c_smbus_write_word_data(cl, 3, swab16(value)); ++ break; ++ } ++ } ++ ++ if (bank > 2) ++ i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0); ++ ++ up(&data->lock); ++} ++ ++static void asb100_init_client(struct i2c_client *client) ++{ ++ struct asb100_data *data = client->data; ++ int vid = 0; ++ ++ vid = asb100_read_value(client, ASB100_REG_VID_FANDIV) & 0x0f; ++ vid |= (asb100_read_value(client, ASB100_REG_CHIPID) & 0x01) << 4; ++ data->vrm = ASB100_DEFAULT_VRM; ++ vid = vid_from_reg(vid, data->vrm); ++ ++ /* Start monitoring */ ++ asb100_write_value(client, ASB100_REG_CONFIG, ++ (asb100_read_value(client, ASB100_REG_CONFIG) & 0xf7) | 0x01); ++} ++ ++static void asb100_update_client(struct i2c_client *client) ++{ ++ struct asb100_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || ++ time_before(jiffies, data->last_updated) || !data->valid) { ++ ++ pr_debug("asb100.o: starting device update...\n"); ++ ++ /* 7 voltage inputs */ ++ for (i = 0; i < 7; i++) { ++ data->in[i] = asb100_read_value(client, ++ ASB100_REG_IN(i)); ++ data->in_min[i] = asb100_read_value(client, ++ ASB100_REG_IN_MIN(i)); ++ data->in_max[i] = asb100_read_value(client, ++ ASB100_REG_IN_MAX(i)); ++ } ++ ++ /* 3 fan inputs */ ++ for (i = 1; i <= 3; i++) { ++ data->fan[i-1] = asb100_read_value(client, ++ ASB100_REG_FAN(i)); ++ data->fan_min[i-1] = asb100_read_value(client, ++ ASB100_REG_FAN_MIN(i)); ++ } ++ ++ /* 4 temperature inputs */ ++ for (i = 1; i <= 4; i++) { ++ data->temp[i-1] = asb100_read_value(client, ++ ASB100_REG_TEMP(i)); ++ data->temp_max[i-1] = asb100_read_value(client, ++ ASB100_REG_TEMP_MAX(i)); ++ data->temp_hyst[i-1] = asb100_read_value(client, ++ ASB100_REG_TEMP_HYST(i)); ++ } ++ ++ /* VID and fan divisors */ ++ i = asb100_read_value(client, ASB100_REG_VID_FANDIV); ++ data->vid = i & 0x0f; ++ data->vid |= (asb100_read_value(client, ++ ASB100_REG_CHIPID) & 0x01) << 4; ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = (i >> 6) & 0x03; ++ data->fan_div[2] = (asb100_read_value(client, ++ ASB100_REG_PIN) >> 6) & 0x03; ++ ++ /* PWM */ ++ data->pwm = asb100_read_value(client, ASB100_REG_PWM1); ++ ++ /* alarms */ ++ data->alarms = asb100_read_value(client, ASB100_REG_ALARM1) + ++ (asb100_read_value(client, ASB100_REG_ALARM2) << 8); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ ++ pr_debug("asb100.o: ... update complete.\n"); ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++static void asb100_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ int nr = ctl_name - ASB100_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ asb100_write_value(client, ASB100_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ asb100_write_value(client, ASB100_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void asb100_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ int nr = ctl_name - ASB100_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = ++ FAN_TO_REG(results[0], ++ DIV_FROM_REG(data->fan_div[nr-1])); ++ asb100_write_value(client, ++ ASB100_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++void asb100_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ int nr = ctl_name - ASB100_SYSCTL_TEMP1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_max[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); ++ results[2] = TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 3; ++ ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_max[nr] = TEMP_TO_REG(results[0]); ++ asb100_write_value(client, ASB100_REG_TEMP_MAX(nr+1), ++ data->temp_max[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst[nr] = TEMP_TO_REG(results[1]); ++ asb100_write_value(client, ASB100_REG_TEMP_HYST(nr+1), ++ data->temp_hyst[nr]); ++ } ++ } ++} ++ ++void asb100_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ int nr = ctl_name - ASB100_SYSCTL_TEMP1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ ++ results[0] = LM75_TEMP_FROM_REG(data->temp_max[nr]); ++ results[1] = LM75_TEMP_FROM_REG(data->temp_hyst[nr]); ++ results[2] = LM75_TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 3; ++ ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_max[nr] = ++ LM75_TEMP_TO_REG(results[0]); ++ asb100_write_value(client, ASB100_REG_TEMP_MAX(nr+1), ++ data->temp_max[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst[nr] = ++ LM75_TEMP_TO_REG(results[1]); ++ asb100_write_value(client, ASB100_REG_TEMP_HYST(nr+1), ++ data->temp_hyst[nr]); ++ } ++ } ++} ++ ++void asb100_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void asb100_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++void asb100_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void asb100_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ int old, old2; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ results[2] = DIV_FROM_REG(data->fan_div[2]); ++ *nrels_mag = 3; ++ ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = asb100_read_value(client, ASB100_REG_VID_FANDIV); ++ if (*nrels_mag >= 3) { ++ data->fan_div[2] = DIV_TO_REG(results[2]); ++ old2 = asb100_read_value(client, ASB100_REG_PIN); ++ old2 = (old2 & 0x3f) | ((data->fan_div[2] & 0x03) << 6); ++ asb100_write_value(client, ASB100_REG_PIN, old2); ++ } ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | ((data->fan_div[1] & 0x03) << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | ((data->fan_div[0] & 0x03) << 4); ++ asb100_write_value(client, ASB100_REG_VID_FANDIV, old); ++ } ++ } ++} ++ ++void asb100_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct asb100_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ asb100_update_client(client); ++ results[0] = ASB100_PWM_FROM_REG(data->pwm & 0x0f); ++ results[1] = (data->pwm & 0x80) ? 1 : 0; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ u8 val = data->pwm; ++ if (*nrels_mag >= 1) { ++ val = 0x0f & ASB100_PWM_TO_REG(results[0]); ++ if (*nrels_mag >= 2) { ++ if (results[1]) ++ val |= 0x80; ++ else ++ val &= ~0x80; ++ } ++ asb100_write_value(client, ASB100_REG_PWM1, val); ++ } ++ } ++} ++ ++static int __init asb100_init(void) ++{ ++ printk(KERN_INFO "asb100.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&asb100_driver); ++} ++ ++static void __exit asb100_exit(void) ++{ ++ i2c_del_driver(&asb100_driver); ++} ++ ++MODULE_AUTHOR( "Mark M. Hoffman <mhoffman@lightlink.com>, " ++ "Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, and" ++ "Mark Studebaker <mdsxyz123@yahoo.com>"); ++ ++MODULE_DESCRIPTION("ASB100 'Bach' driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(asb100_init); ++module_exit(asb100_exit); ++ +--- linux-old/drivers/sensors/bt869.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/bt869.c Mon Dec 13 20:18:45 2004 +@@ -0,0 +1,891 @@ ++/* ++ bt869.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ Copyright (c) 2001, 2002 Stephen Davies <steve@daviesfam.org> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++ ++#define DEBUG 1 ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++ ++/* found only at 0x44 or 0x45 */ ++static unsigned short normal_i2c_range[] = { 0x44, 0x45, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(bt869); ++ ++/* Many bt869 constants specified below */ ++ ++/* The bt869 registers */ ++/* Coming soon: Many, many registers */ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++ /*none */ ++ ++/* Initial values */ ++/*none*/ ++ ++/* Each client has this additional data */ ++struct bt869_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 status[3]; /* Register values */ ++ u16 res[2]; /* Resolution XxY */ ++ u8 ntsc; /* 1=NTSC, 0=PAL */ ++ u8 half; /* go half res */ ++ u8 depth; /* screen depth */ ++ u8 colorbars; /* turn on/off colorbar calibration screen */ ++ u8 svideo; /* output format: (2=RGB) 1=SVIDEO, 0=Composite */ ++}; ++ ++static int bt869_attach_adapter(struct i2c_adapter *adapter); ++static int bt869_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void bt869_init_client(struct i2c_client *client); ++static int bt869_detach_client(struct i2c_client *client); ++static int bt869_read_value(struct i2c_client *client, u8 reg); ++static int bt869_write_value(struct i2c_client *client, u8 reg, u16 value); ++static void bt869_write_values(struct i2c_client *client, u16 *values); ++static void bt869_status(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_ntsc(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_res(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_half(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_colorbars(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_svideo(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_depth(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void bt869_update_client(struct i2c_client *client); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver bt869_driver = { ++ .owner = THIS_MODULE, ++ .name = "BT869 video-output chip driver", ++ .id = I2C_DRIVERID_BT869, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = bt869_attach_adapter, ++ .detach_client = bt869_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define BT869_SYSCTL_STATUS 1000 ++#define BT869_SYSCTL_NTSC 1001 ++#define BT869_SYSCTL_HALF 1002 ++#define BT869_SYSCTL_RES 1003 ++#define BT869_SYSCTL_COLORBARS 1004 ++#define BT869_SYSCTL_DEPTH 1005 ++#define BT869_SYSCTL_SVIDEO 1006 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected bt869. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table bt869_dir_table_template[] = { ++ {BT869_SYSCTL_STATUS, "status", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_status}, ++ {BT869_SYSCTL_NTSC, "ntsc", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_ntsc}, ++ {BT869_SYSCTL_RES, "res", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_res}, ++ {BT869_SYSCTL_HALF, "half", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_half}, ++ {BT869_SYSCTL_COLORBARS, "colorbars", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_colorbars}, ++ {BT869_SYSCTL_DEPTH, "depth", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_depth}, ++ {BT869_SYSCTL_SVIDEO, "svideo", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &bt869_svideo}, ++ {0} ++}; ++ ++/* ****************** ++ ++720x576, 27.5MHz, PAL, no overscan compensation. ++ ++This mode should be use for digital video, DVD playback etc. ++ ++NOTE: This mode for PAL, see 720x480 for an equivalent NTSC mode ++NOTE: -- Steve Davies <steve@daviesfam.org> ++ ++ ++Compatible X modeline: ++ ++ Mode "720x576-BT869" ++ DotClock 27.5 ++ HTimings 720 744 800 880 ++ VTimings 576 581 583 625 ++ EndMode ++ ++ ++625LINE=1 625 line output format ++BST_AMP[7:0]=x57 87 Burst ampl. multiplication factor (PAL std??) ++BY_PLL=0 Use the PLL ++CATTENUATE[2:0]=0 No chroma attenuation ++CCF1B1[7:0]=0 close caption stuff ++CCF1B2[7:0]=0 close caption stuff ++CCF2B1[7:0]=0 close caption stuff ++CCF2B2[7:0]=0 close caption stuff ++CCORING[2:0]=0 Bypass chroma coring ++CCR_START[8:0]=0 [CCR_START[8]=0; CCR_START[7:0]=0] Close-caption clock runin start from hsync ++CC_ADD[11:0]=xD2 210 [CC_ADD[11:8]=0; CC_ADD[7:0]=xD2] Close-caption DTO increment ++CHECK_STAT=0 Don't check monitor status ++CLPF[1:0]=0 Hoz chroma lowpass filter=Bypass ++DACDISA=1 Disable DACA ++DACDISB=0 Don't disable DACB ++DACDISC=0 Don't disable DACC ++DACOFF=0 Don't disable the DACs ++DATDLY = 0 normal ++DATSWP=0 normal ++DCHROMA=0 Don't blank chroma ++DIS_FFILT=1 Disable flickerfilter ++DIS_GMSHC=1 Disable chroma psuedo-gamma removal ++DIS_GMSHY=1 Disable luma pseudo gamma removal ++DIS_GMUSHC=1 Disable chroma anti-pseudo gamma removal ++DIS_GMUSHY=1 Disable luma anti-pseudo gamma removal ++DIS_SCRESET=0 Normal subcarrier phase resets ++DIS_YFLPF=0 Disable Luma initial hoz low pass filter ++DIV2=0 Input pixel rate not divided by 2 ++ECBAR=0 No colour bars ++ECCF1=0 Disable closed caption ++ECCF2=0 Disable closed caption ++ECCGATE=0 Normal close caption encoding ++ECLIP=0 0=disable clipping ++EN_ASYNC=0 set to 0 for normal operation ++EN_BLANKO=0 BLANK is an input ++EN_DOT=0 Disables dot clock sync on BLANK pin ++EN_OUT=1 Allows outputs to be enabled ++EN_XCLK=1 Use CLKI pin as clock source ++ESTATUS[1:0]=0 Used to select readback register ++FIELDI=0 Logical 1 on FIELD indicates even field ++F_SELC[2:0]=0 5 line chroma flickerfilter ++F_SELY[2:0]=0 5 line luma flickerfilter ++HBURST_BEGIN[7:0]=x98 152 Chroma burst start point in clocks ++HBURST_END[7:0]=x58 88 Chroma burst end point in clocks - 128 ++HSYNCI=0 Active low HSYNC ++HSYNC_WIDTH[7:0]=x80 128 Analogue sync width in clocks ++HSYNOFFSET[9:0]=0 [HSYNOFFSET[9:8]=0; HSYNOFFSET[7:0]=0] hsync in "standard position" ++HSYNWIDTH[5:0]=2 2 pixel hsync width ++H_ACTIVE[9:0]=x2D0 720 [H_ACTIVE[9:8]=2; H_ACTIVE[7:0]=xD0] Active pixels per line ++H_BLANKI[8:0]=x84 132 [H_BLANKI[8]=0; H_BLANKI[7:0]=x84] End of blanking of input video ++H_BLANKO[9:0]=x120 288 [H_BLANKO[9:8]=1; H_BLANKO[7:0]=x20] End of blanking from hoz sync leading edge ++H_CLKI[10:0]=x378 888 [H_CLKI[10:8]=3; H_CLKI[7:0]=x78] Input line length total in clocks ++H_CLKO[11:0]=x6e0 1760 [H_CLKO[11:8]=6; H_CLKO[7:0]=xe0] Output clocks per line ++H_FRACT[7:0]=0 0 fractional input clocks per line ++IN_MODE[2:0]=0 24Bit RGB muxed ++LUMADLY[1:0]=0 0 pixel delay on Y_DLY luma ++MCB[7:0]=x49 73 Mult factor for CB prior to subcarrier mod. ++MCR[7:0]=x82 130 Mult factor for CR prior to subcarrier mod. ++MODE2X=0 Don't divide clock input by 2 ++MSC[31:0]=x2945E0B4 692445365 [MSC[31:24]=x29; MSC[23:16]=x45; MSC[15:8]=xE0; MSC[7:0]=xB4] Subcarrier incr. ++MY[7:0]=x8C 140 Mult factor for Y ++NI_OUT=0 Normal interlaced output ++OUT_MODE[1:0]=0 video0-3 is CVBS, Y, C, Y_DLY ++OUT_MUXA[1:0]=0 Don't care as DACA is disabled ++OUT_MUXB[1:0]=1 Output video[1] (Y) on DACB ++OUT_MUXC[1:0]=2 Output video[2] (C) on DACC ++PAL_MD=1 Video output in PAL mode ++PHASE_OFF[7:0]=0 Subcarrier phase offset ++PLL_FRACT[15:0]=x30 48 [PLL_FRACT[15:8]=0x0; PLL_FRACT[7:0]=x30] frac portion of pll multiplier ++PLL_INT[5:0]=0x0C 12 Int portion of pll multiplier ++SETUP=0 7.5-IRE setup disabled ++SLAVER=1 ++SRESET=0 Don't do a software reset ++SYNC_AMP[7:0]=xF0 240 Sync amp mult. factor (PAL std???) ++VBLANKDLY=0 Extra line of blanking in 2nd field? ++VSYNCI=0 Active low VSYNC ++VSYNC_DUR=0 2.5line VSYNC duration on output ++VSYNCOFFSET[10:0]=0 [VSYNOFFSET[10:8]=0; VSYNOFFSET[7:0]=0] VSYNC in standard position ++VSYNWIDTH[2:0]=1 1 line of vsync width ++V_ACTIVEI[9:0]=x240 576 [V_ACTIVEI[9:0]=2; V_ACTIVEI[7:0]=x40] Active input lines ++V_ACTIVEO[8:0]=x122 290 [V_ACTIVE0[8]=1; V_ACTIVEO[7:0]=x22] ++V_BLANKI[7:0]=x2A 42 Input lines from vsync to first active line ++V_BLANKO[7:0]=x16 22 ++V_LINESI[9:0]=x271 625 [V_LINESI[9:8]=2; V_LINESI[7:0]=x71] Number of input lines ++V_SCALE[13:0]=x1000 4096 [V_SCALE[13:8]=x10; V_SCALE[7:0]=0] Vert scale coefficient="none"? ++YATTENUATE[2:0]=0 no luma attenuation ++YCORING[2:0]=0 Luma-coring bypass ++YLPF[1:0]=0 Luma hoz low pass filter=bypass ++ ++***************** */ ++ ++static u16 registers_720_576[] = ++ { ++ 0x6e, 0x00, /* HSYNOFFSET[7:0]=0 */ ++ 0x70, 0x02, /* HSYNOFFSET[9:8]=0; HSYNWIDTH[5:0]=2 */ ++ 0x72, 0x00, /* VSYNOFFSET[7:0]=0 */ ++ 0x74, 0x01, /* DATDLY = 0; DATSWP=0; VSYNOFFSET[10:8]=0; VSYNWIDTH[2:0]=1 */ ++ 0x76, 0xe0, /* H_CLKO[7:0]=xe0 */ ++ 0x78, 0xd0, /* H_ACTIVE[7:0]=xD0 */ ++ 0x7a, 0x80, /* HSYNC_WIDTH[7:0]=x80 */ ++ 0x7c, 0x98, /* HBURST_BEGIN[7:0]=x98 */ ++ 0x7e, 0x58, /* HBURST_END[7:0]=x58 */ ++ 0x80, 0x20, /* H_BLANKO[7:0]=x20 */ ++ 0x82, 0x16, /* V_BLANKO[7:0]=x16 */ ++ 0x84, 0x22, /* V_ACTIVEO[7:0]=x22 */ ++ 0x86, 0xa6, /* V_ACTIVE0[8]=1; H_ACTIVE[9:8]=2; H_CLKO[11:8]=6 */ ++ 0x88, 0x00, /* H_FRACT[7:0]=0 */ ++ 0x8a, 0x78, /* H_CLKI[7:0]=x78 */ ++ 0x8c, 0x80, /* H_BLANKI[7:0]=x84 */ ++ 0x8e, 0x03, /* VBLANKDLY=0; H_BLANKI[8]=0; H_CLKI[10:8]=3 */ ++ 0x90, 0x71, /* V_LINESI[7:0]=x71 */ ++ 0x92, 0x2a, /* V_BLANKI[7:0]=x2A */ ++ 0x94, 0x40, /* V_ACTIVEI[7:0]=x40 */ ++ 0x96, 0x0a, /* CLPF[1:0]=0; YLPF[1:0]=0; V_ACTIVEI[9:0]=2; V_LINESI[9:8]=2 */ ++ 0x98, 0x00, /* V_SCALE[7:0]=0 */ ++ 0x9a, 0x50, /* H_BLANKO[9:8]=1; V_SCALE[13:8]=x10 */ ++ 0x9c, 0x30, /* PLL_FRACT[7:0]=x30 */ ++ 0x9e, 0x0, /* PLL_FRACT[15:8]=0x0 */ ++ 0xa0, 0x8c, /* EN_XCLK=1; BY_PLL=0; PLL_INT[5:0]=0x0C */ ++ 0xa2, 0x24, /* ECLIP=0; PAL_MD=1; DIS_SCRESET=0; VSYNC_DUR=0; 625LINE=1; SETUP=0; NI_OUT=0 */ ++ 0xa4, 0xf0, /* SYNC_AMP[7:0]=xF0 */ ++ 0xa6, 0x57, /* BST_AMP[7:0]=x57 */ ++ 0xa8, 0x82, /* MCR[7:0]=x82 */ ++ 0xaa, 0x49, /* MCB[7:0]=x49 */ ++ 0xac, 0x8c, /* MY[7:0]=x8C */ ++ 0xae, 0xb4, /* MSC[7:0]=xb4 */ ++ 0xb0, 0xe0, /* MSC[15:8]=xe0 */ ++ 0xb2, 0x45, /* MSC[23:16]=x45 */ ++ 0xb4, 0x29, /* MSC[31:24]=x29 */ ++ 0xb6, 0x00, /* PHASE_OFF[7:0]=0 */ ++ //0xba, 0x21, /* SRESET=0; CHECK_STAT=0; SLAVER=1; DACOFF=0; DACDISC=0; DACDISB=0; DACDISA=1 */ ++ 0xc4, 0x01, /* ESTATUS[1:0]=0; ECCF2=0; ECCF1=0; ECCGATE=0; ECBAR=0; DCHROMA=0; EN_OUT=1 */ ++ 0xc6, 0x00, /* EN_BLANKO=0; EN_DOT=0; FIELDI=0; VSYNCI=0; HSYNCI=0; IN_MODE[2:0]=0(24bRGB) */ ++ 0xc8, 0x40, /* DIS_YFLPF=0; DIS_FFILT=1; F_SELC[2:0]=0; F_SELY[2:0]=0 */ ++ 0xca, 0xc0, /* DIS_GMUSHY=1; DIS_GMSHY=1; YCORING[2:0]=0; YATTENUATE[2:0]=0 */ ++ 0xcc, 0xc0, /* DIS_GMUSHC=1; DIS_GMSHC=1; CCORING[2:0]=0; CATTENUATE[2:0]=0 */ ++ //0xce, 0x24, /* OUT_MUXC=2 [C]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/ ++ //0xce, 0x04, /* OUT_MUXC=0 [CVBS]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/ ++ 0xd6, 0x00, /* OUT_MODE[1:0]=0; LUMADLY[1:0]=0 */ ++ 0, 0 ++ }; ++ ++ ++/* ****************** ++ ++720x480, 27.5MHz, NTSC no overscan compensation. ++ ++This mode should be use for digital video, DVD playback etc. ++ ++NOTE: This mode for NTSC, see 720x576 for an equivalent PAL mode ++NOTE: -- Steve Davies <steve@daviesfam.org> ++ ++Compatible X modeline: ++ ++ Mode "720x480-BT869" ++ DotClock 27.5 ++ HTimings 720 744 800 872 ++ VTimings 480 483 485 525 ++ EndMode ++ ++ ++625LINE=0 not 625 line output format ++BST_AMP[7:0]=x74 116 Burst ampl. multiplication factor (NTSC std??) ++BY_PLL=0 Use the PLL ++CATTENUATE[2:0]=0 No chroma attenuation ++CCF1B1[7:0]=0 close caption stuff ++CCF1B2[7:0]=0 close caption stuff ++CCF2B1[7:0]=0 close caption stuff ++CCF2B2[7:0]=0 close caption stuff ++CCORING[2:0]=0 Bypass chroma coring ++CCR_START[8:0]=0 [CCR_START[8]=0; CCR_START[7:0]=0] Close-caption clock runin start from hsync ++CC_ADD[11:0]=xD2 210 [CC_ADD[11:8]=0; CC_ADD[7:0]=xD2] Close-caption DTO increment ++CHECK_STAT=0 Don't check monitor status ++CLPF[1:0]=0 Hoz chroma lowpass filter=Bypass ++DACDISA=1 Disable DACA ++DACDISB=0 Don't disable DACB ++DACDISC=0 Don't disable DACC ++DACOFF=0 Don't disable the DACs ++DATDLY = 0 normal ++DATSWP=0 normal ++DCHROMA=0 Don't blank chroma ++DIS_FFILT=1 Disable flickerfilter ++DIS_GMSHC=1 Disable chroma psuedo-gamma removal ++DIS_GMSHY=1 Disable luma pseudo gamma removal ++DIS_GMUSHC=1 Disable chroma anti-pseudo gamma removal ++DIS_GMUSHY=1 Disable luma anti-pseudo gamma removal ++DIS_SCRESET=0 Normal subcarrier phase resets ++DIS_YFLPF=0 Disable Luma initial hoz low pass filter ++DIV2=0 Input pixel rate not divided by 2 ++ECBAR=0 No colour bars ++ECCF1=0 Disable closed caption ++ECCF2=0 Disable closed caption ++ECCGATE=0 Normal close caption encoding ++ECLIP=0 0=disable clipping ++EN_ASYNC=0 set to 0 for normal operation ++EN_BLANKO=0 BLANK is an input ++EN_DOT=0 Disables dot clock sync on BLANK pin ++EN_OUT=1 Allows outputs to be enabled ++EN_XCLK=1 Use CLKI pin as clock source ++ESTATUS[1:0]=0 Used to select readback register ++FIELDI=0 Logical 1 on FIELD indicates even field ++F_SELC[2:0]=0 5 line chroma flickerfilter ++F_SELY[2:0]=0 5 line luma flickerfilter ++HBURST_BEGIN[7:0]=x92 146 Chroma burst start point in clocks ++HBURST_END[7:0]=x57 87 Chroma burst end point in clocks - 128 ++HSYNCI=0 Active low HSYNC ++HSYNC_WIDTH[7:0]=x80 128 Analogue sync width in clocks ++HSYNOFFSET[9:0]=0 [HSYNOFFSET[9:8]=0; HSYNOFFSET[7:0]=0] hsync in "standard position" ++HSYNWIDTH[5:0]=2 2 pixel hsync width ++H_ACTIVE[9:0]=x2D0 720 [H_ACTIVE[9:8]=2; H_ACTIVE[7:0]=xD0] Active pixels per line ++H_BLANKI[8:0]=x80 128 [H_BLANKI[8]=0; H_BLANKI[7:0]=x80] End of blanking of input video ++H_BLANKO[9:0]=x102 258 [H_BLANKO[9:8]=1; H_BLANKO[7:0]=x2] End of blanking from hoz sync leading edge ++H_CLKI[10:0]=x368 872 [H_CLKI[10:8]=3; H_CLKI[7:0]=x68] Input line length total in clocks ++H_CLKO[11:0]=x6d0 1744 [H_CLKO[11:8]=6; H_CLKO[7:0]=xD0] Output clocks per line ++H_FRACT[7:0]=0 0 fractional input clocks per line ++IN_MODE[2:0]=0 24Bit RGB muxed ++LUMADLY[1:0]=0 0 pixel delay on Y_DLY luma ++MCB[7:0]=x43 67 Mult factor for CB prior to subcarrier mod. ++MCR[7:0]=x77 119 Mult factor for CR prior to subcarrier mod. ++MODE2X=0 Don't divide clock input by 2 ++MSC[31:0]=x215282E5 559055589 [MSC[31:24]=x21; MSC[23:16]=x52; MSC[15:8]=x82; MSC[7:0]=xE5] Subcarrier incr. ++MY[7:0]=x85 133 Mult factor for Y ++NI_OUT=0 Normal interlaced output ++OUT_MODE[1:0]=0 video0-3 is CVBS, Y, C, Y_DLY ++OUT_MUXA[1:0]=0 Don't care as DACA is disabled ++OUT_MUXB[1:0]=1 Output video[1] (Y) on DACB ++OUT_MUXC[1:0]=2 Output video[2] (C) on DACC ++PAL_MD=0 Video output in PAL mode? No. ++PHASE_OFF[7:0]=0 Subcarrier phase offset ++PLL_FRACT[15:0]=x30 48 [PLL_FRACT[15:8]=0x0; PLL_FRACT[7:0]=x30] frac portion of pll multiplier ++PLL_INT[5:0]=0x0C 12 Int portion of pll multiplier ++SETUP=1 7.5-IRE enabled for NTSC ++SLAVER=1 ++SRESET=0 Don't do a software reset ++SYNC_AMP[7:0]=xE5 229 Sync amp mult. factor (PAL std???) ++VBLANKDLY=0 Extra line of blanking in 2nd field? ++VSYNCI=0 Active low VSYNC ++VSYNC_DUR=1 2.5line VSYNC duration on output (Yes for NTSC) ++VSYNCOFFSET[10:0]=0 [VSYNOFFSET[10:8]=0; VSYNOFFSET[7:0]=0] VSYNC in standard position ++VSYNWIDTH[2:0]=1 1 line of vsync width ++V_ACTIVEI[9:0]=x1E0 480 [V_ACTIVEI[9:0]=1; V_ACTIVEI[7:0]=xE0] Active input lines ++V_ACTIVEO[8:0]=xF0 240 [V_ACTIVE0[8]=0; V_ACTIVEO[7:0]=xF0] ++V_BLANKI[7:0]=x2A 42 Input lines from vsync to first active line ++V_BLANKO[7:0]=x16 22 ++V_LINESI[9:0]=x20D 525 [V_LINESI[9:8]=2; V_LINESI[7:0]=x0D] Number of input lines ++V_SCALE[13:0]=x1000 4096 [V_SCALE[13:8]=x10; V_SCALE[7:0]=0] Vert scale coefficient="none"? ++YATTENUATE[2:0]=0 no luma attenuation ++YCORING[2:0]=0 Luma-coring bypass ++YLPF[1:0]=0 Luma hoz low pass filter=bypass ++ ++***************** */ ++ ++static u16 registers_720_480[] = ++ { ++ 0x6e, 0x00, /* HSYNOFFSET[7:0]=0 */ ++ 0x70, 0x02, /* HSYNOFFSET[9:8]=0; HSYNWIDTH[5:0]=2 */ ++ 0x72, 0x00, /* VSYNOFFSET[7:0]=0 */ ++ 0x74, 0x01, /* DATDLY = 0; DATSWP=0; VSYNOFFSET[10:8]=0; VSYNWIDTH[2:0]=1 */ ++ 0x76, 0xD0, /* H_CLKO[7:0]=xD0 */ ++ 0x78, 0xD0, /* H_ACTIVE[7:0]=xD0 */ ++ 0x7a, 0x80, /* HSYNC_WIDTH[7:0]=x80 */ ++ 0x7c, 0x92, /* HBURST_BEGIN[7:0]=x92 */ ++ 0x7e, 0x57, /* HBURST_END[7:0]=x57 */ ++ 0x80, 0x02, /* H_BLANKO[7:0]=x2 */ ++ 0x82, 0x16, /* V_BLANKO[7:0]=x16 */ ++ 0x84, 0xF0, /* V_ACTIVEO[7:0]=xF0 */ ++ 0x86, 0x26, /* V_ACTIVE0[8]=0; H_ACTIVE[9:8]=2; H_CLKO[11:8]=6 */ ++ 0x88, 0x00, /* H_FRACT[7:0]=0 */ ++ 0x8a, 0xD0, /* H_CLKI[7:0]=xD0 */ ++ 0x8c, 0x80, /* H_BLANKI[7:0]=x80 */ ++ 0x8e, 0x03, /* VBLANKDLY=0; H_BLANKI[8]=0; H_CLKI[10:8]=3 */ ++ 0x90, 0x0D, /* V_LINESI[7:0]=x0D */ ++ 0x92, 0x2A, /* V_BLANKI[7:0]=x2A */ ++ 0x94, 0xE0, /* V_ACTIVEI[7:0]=xE0 */ ++ 0x96, 0x06, /* CLPF[1:0]=0; YLPF[1:0]=0; V_ACTIVEI[9:8]=1; V_LINESI[9:8]=2 */ ++ 0x98, 0x00, /* V_SCALE[7:0]=0 */ ++ 0x9a, 0x50, /* H_BLANKO[9:8]=1; V_SCALE[13:8]=x10 */ ++ 0x9c, 0x30, /* PLL_FRACT[7:0]=x30 */ ++ 0x9e, 0x0, /* PLL_FRACT[15:8]=0x0 */ ++ 0xa0, 0x8c, /* EN_XCLK=1; BY_PLL=0; PLL_INT[5:0]=0x0C */ ++ 0xa2, 0x0A, /* ECLIP=0; PAL_MD=0; DIS_SCRESET=0; VSYNC_DUR=1; 625LINE=0; SETUP=1; NI_OUT=0 */ ++ 0xa4, 0xE5, /* SYNC_AMP[7:0]=xE5 */ ++ 0xa6, 0x74, /* BST_AMP[7:0]=x74 */ ++ 0xa8, 0x77, /* MCR[7:0]=x77 */ ++ 0xaa, 0x43, /* MCB[7:0]=x43 */ ++ 0xac, 0x85, /* MY[7:0]=x85 */ ++ 0xae, 0xE5, /* MSC[7:0]=xE5 */ ++ 0xb0, 0x82, /* MSC[15:8]=x82 */ ++ 0xb2, 0x52, /* MSC[23:16]=x52 */ ++ 0xb4, 0x21, /* MSC[31:24]=x21 */ ++ 0xb6, 0x00, /* PHASE_OFF[7:0]=0 */ ++ //0xba, 0x21, /* SRESET=0; CHECK_STAT=0; SLAVER=1; DACOFF=0; DACDISC=0; DACDISB=0; DACDISA=1 */ ++ 0xc4, 0x01, /* ESTATUS[1:0]=0; ECCF2=0; ECCF1=0; ECCGATE=0; ECBAR=0; DCHROMA=0; EN_OUT=1 */ ++ 0xc6, 0x00, /* EN_BLANKO=0; EN_DOT=0; FIELDI=0; VSYNCI=0; HSYNCI=0; IN_MODE[2:0]=0(24bRGB) */ ++ 0xc8, 0x40, /* DIS_YFLPF=0; DIS_FFILT=1; F_SELC[2:0]=0; F_SELY[2:0]=0 */ ++ 0xca, 0xc0, /* DIS_GMUSHY=1; DIS_GMSHY=1; YCORING[2:0]=0; YATTENUATE[2:0]=0 */ ++ 0xcc, 0xc0, /* DIS_GMUSHC=1; DIS_GMSHC=1; CCORING[2:0]=0; CATTENUATE[2:0]=0 */ ++ //0xce, 0x24, /* OUT_MUXC=2 [C]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/ ++ //0xce, 0x04, /* OUT_MUXC=0 [CVBS]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/ ++ 0xd6, 0x00, /* OUT_MODE[1:0]=0; LUMADLY[1:0]=0 */ ++ 0, 0 ++ }; ++ ++ ++int bt869_id = 0; ++ ++static int bt869_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, bt869_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int bt869_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, cur; ++ struct i2c_client *new_client; ++ struct bt869_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ ++ printk("bt869.o: probing address %d .\n", address); ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("bt869.o: bt869_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE | ++ I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access bt869_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct bt869_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &bt869_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. It is lousy. */ ++ i2c_smbus_write_byte_data(new_client, 0xC4, 0); /* set status bank 0 */ ++ cur = i2c_smbus_read_byte(new_client); ++ printk("bt869.o: address 0x%X testing-->0x%X\n", address, cur); ++ if ((cur & 0xE0) != 0x20) ++ goto ERROR1; ++ ++ /* Determine the chip type */ ++ kind = ((cur & 0x20) >> 5); ++ ++ if (kind) { ++ type_name = "bt869"; ++ client_name = "bt869 chip"; ++ printk("bt869.o: BT869 detected\n"); ++ } else { ++ type_name = "bt868"; ++ client_name = "bt868 chip"; ++ printk("bt869.o: BT868 detected\n"); ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = bt869_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ bt869_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ bt869_init_client((struct i2c_client *) new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int bt869_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct bt869_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("bt869.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* All registers are byte-sized. ++ bt869 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int bt869_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte(client); ++} ++ ++/* All registers are byte-sized. ++ bt869 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int bt869_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++#ifdef DEBUG ++ printk("bt869.o: write_value(0x%X, 0x%X)\n", reg, value); ++#endif ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void bt869_write_values(struct i2c_client *client, u16 *values) ++{ ++ /* writes set of registers from array. 0,0 marks end of table */ ++ while (*values) { ++ bt869_write_value(client, values[0], values[1]); ++ values += 2; ++ } ++} ++ ++static void bt869_init_client(struct i2c_client *client) ++{ ++ struct bt869_data *data = client->data; ++ ++ /* Initialize the bt869 chip */ ++ bt869_write_value(client, 0x0ba, 0x80); ++ // bt869_write_value(client,0x0D6, 0x00); ++ /* Be a slave to the clock on the Voodoo3 */ ++ bt869_write_value(client, 0xa0, 0x80); ++ bt869_write_value(client, 0xba, 0x20); ++ /* depth =16bpp */ ++ bt869_write_value(client, 0x0C6, 0x001); ++ bt869_write_value(client, 0xC4, 1); ++ /* Flicker free enable and config */ ++ bt869_write_value(client, 0xC8, 0); ++ data->res[0] = 640; ++ data->res[1] = 480; ++ data->ntsc = 1; ++ data->half = 0; ++ data->colorbars = 0; ++ data->svideo = 0; ++ data->depth = 16; ++ ++} ++ ++static void bt869_update_client(struct i2c_client *client) ++{ ++ struct bt869_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++#ifdef DEBUG ++ printk("Starting bt869 update\n"); ++#endif ++ if ((data->res[0] == 800) && (data->res[1] == 600)) { ++ /* 800x600 built-in mode */ ++ bt869_write_value(client, 0xB8, ++ (2 + (!data->ntsc))); ++ bt869_write_value(client, 0xa0, 0x80 + 0x11); ++ printk("bt869.o: writing into config -->0x%X\n", ++ (2 + (!data->ntsc))); ++ } ++ else if ((data->res[0] == 720) && (data->res[1] == 576)) { ++ /* 720x576 no-overscan-compensation mode suitable for PAL DVD playback */ ++ data->ntsc = 0; /* This mode always PAL */ ++ bt869_write_values(client, registers_720_576); ++ } ++ else if ((data->res[0] == 720) && (data->res[1] == 480)) { ++ /* 720x480 no-overscan-compensation mode suitable for NTSC DVD playback */ ++ data->ntsc = 1; /* This mode always NTSC */ ++ bt869_write_values(client, registers_720_480); ++ } ++ else { ++ /* 640x480 built-in mode */ ++ bt869_write_value(client, 0xB8, (!data->ntsc)); ++ bt869_write_value(client, 0xa0, 0x80 + 0x0C); ++ printk("bt869.o: writing into config -->0x%X\n", ++ (0 + (!data->ntsc))); ++ if ((data->res[0] != 640) || (data->res[1] != 480)) { ++ printk ++ ("bt869.o: Warning: arbitrary resolutions not supported yet. Using 640x480.\n"); ++ data->res[0] = 640; ++ data->res[1] = 480; ++ } ++ } ++ /* Set colour depth */ ++ if ((data->depth != 24) && (data->depth != 16)) ++ data->depth = 16; ++ if (data->depth == 16) ++ bt869_write_value(client, 0x0C6, 0x001); ++ if (data->depth == 24) ++ bt869_write_value(client, 0x0C6, 0x000); ++ /* set "half" resolution mode */ ++ bt869_write_value(client, 0xd4, data->half << 6); ++ /* Set composite/svideo mode, also enable the right dacs */ ++ switch (data->svideo) { ++ case 2: /* RGB */ ++ /* requires hardware mod on Voodoo3 to get all outputs, ++ untested in practice... Feedback to steve@daviesfam.org please */ ++ bt869_write_value(client, 0xd6, 0x0c); ++ bt869_write_value(client, 0xce, 0x24); ++ bt869_write_value(client, 0xba, 0x20); ++ break; ++ case 1: /* Svideo*/ ++ bt869_write_value(client, 0xce, 0x24); ++ bt869_write_value(client, 0xba, 0x21); ++ break; ++ default: /* Composite */ ++ bt869_write_value(client, 0xce, 0x0); ++ bt869_write_value(client, 0xba, 0x21); ++ break; ++ } ++ /* Enable outputs */ ++ bt869_write_value(client, 0xC4, 1); ++ /* Issue timing reset */ ++ bt869_write_value(client, 0x6c, 0x80); ++ ++/* Read back status registers */ ++ bt869_write_value(client, 0xC4, ++ 1 | (data->colorbars << 2)); ++ data->status[0] = bt869_read_value(client, 1); ++ bt869_write_value(client, 0xC4, ++ 0x41 | (data->colorbars << 2)); ++ data->status[1] = bt869_read_value(client, 1); ++ bt869_write_value(client, 0xC4, ++ 0x81 | (data->colorbars << 2)); ++ data->status[2] = bt869_read_value(client, 1); ++ bt869_write_value(client, 0xC4, ++ 0x0C1 | (data->colorbars << 2)); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ up(&data->update_lock); ++} ++ ++ ++void bt869_status(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->status[0]; ++ results[1] = data->status[1]; ++ results[2] = data->status[2]; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ printk ++ ("bt869.o: Warning: write was requested on read-only proc file: status\n"); ++ } ++} ++ ++ ++void bt869_ntsc(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->ntsc; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->ntsc = (results[0] > 0); ++ } ++ bt869_update_client(client); ++ } ++} ++ ++ ++void bt869_svideo(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->svideo; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->svideo = results[0]; ++ } ++ bt869_update_client(client); ++ } ++} ++ ++ ++void bt869_res(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->res[0]; ++ results[1] = data->res[1]; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->res[0] = results[0]; ++ } ++ if (*nrels_mag >= 2) { ++ data->res[1] = results[1]; ++ } ++ bt869_update_client(client); ++ } ++} ++ ++ ++void bt869_half(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->half; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->half = (results[0] > 0); ++ bt869_update_client(client); ++ } ++ } ++} ++ ++void bt869_colorbars(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->colorbars; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->colorbars = (results[0] > 0); ++ bt869_update_client(client); ++ } ++ } ++} ++ ++void bt869_depth(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct bt869_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ bt869_update_client(client); ++ results[0] = data->depth; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->depth = results[0]; ++ bt869_update_client(client); ++ } ++ } ++} ++ ++static int __init sm_bt869_init(void) ++{ ++ printk("bt869.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&bt869_driver); ++} ++ ++static void __exit sm_bt869_exit(void) ++{ ++ i2c_del_driver(&bt869_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, Stephen Davies <steve@daviesfam.org>"); ++MODULE_DESCRIPTION("bt869 driver"); ++ ++module_init(sm_bt869_init); ++module_exit(sm_bt869_exit); +--- linux-old/drivers/sensors/ddcmon.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/ddcmon.c Mon Dec 13 20:18:45 2004 +@@ -0,0 +1,591 @@ ++/* ++ ddcmon.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999, 2000 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ and Mark Studebaker <mdsxyz123@yahoo.com> ++ Copyright (c) 2003 Jean Delvare <khali@linux-fr.org> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x50, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(ddcmon); ++ ++static int checksum = 0; ++MODULE_PARM(checksum, "i"); ++MODULE_PARM_DESC(checksum, "Only accept eeproms whose checksum is correct"); ++ ++/* Many constants specified below */ ++ ++/* DDCMON registers */ ++/* vendor section */ ++#define DDCMON_REG_MAN_ID 0x08 ++#define DDCMON_REG_PROD_ID 0x0A ++#define DDCMON_REG_SERIAL 0x0C ++#define DDCMON_REG_WEEK 0x10 ++#define DDCMON_REG_YEAR 0x11 ++/* EDID version */ ++#define DDCMON_REG_EDID_VER 0x12 ++#define DDCMON_REG_EDID_REV 0x13 ++/* display information */ ++#define DDCMON_REG_HORSIZE 0x15 ++#define DDCMON_REG_VERSIZE 0x16 ++#define DDCMON_REG_GAMMA 0x17 ++#define DDCMON_REG_DPMS_FLAGS 0x18 ++/* supported timings */ ++#define DDCMON_REG_ESTABLISHED_TIMINGS 0x23 ++#define DDCMON_REG_STANDARD_TIMINGS 0x26 ++#define DDCMON_REG_TIMBASE 0x36 ++#define DDCMON_REG_TIMINCR 18 ++#define DDCMON_REG_TIMNUM 4 ++ ++#define DDCMON_REG_CHECKSUM 0x7f ++ ++/* Size of DDCMON in bytes */ ++#define DDCMON_SIZE 128 ++ ++/* Each client has this additional data */ ++struct ddcmon_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 data[DDCMON_SIZE]; /* Register values */ ++}; ++ ++ ++static int ddcmon_attach_adapter(struct i2c_adapter *adapter); ++static int ddcmon_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int ddcmon_detach_client(struct i2c_client *client); ++ ++static void ddcmon_idcall(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_size(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_sync(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_maxclock(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_timings(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_serial(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_time(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_edid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_gamma(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_dpms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_standard_timing(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ddcmon_update_client(struct i2c_client *client); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver ddcmon_driver = { ++ .owner = THIS_MODULE, ++ .name = "DDCMON READER", ++ .id = I2C_DRIVERID_DDCMON, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = ddcmon_attach_adapter, ++ .detach_client = ddcmon_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define DDCMON_SYSCTL_ID 1010 ++#define DDCMON_SYSCTL_SIZE 1011 ++#define DDCMON_SYSCTL_SYNC 1012 ++#define DDCMON_SYSCTL_TIMINGS 1013 ++#define DDCMON_SYSCTL_SERIAL 1014 ++#define DDCMON_SYSCTL_TIME 1015 ++#define DDCMON_SYSCTL_EDID 1016 ++#define DDCMON_SYSCTL_GAMMA 1017 ++#define DDCMON_SYSCTL_DPMS 1018 ++#define DDCMON_SYSCTL_TIMING1 1021 ++#define DDCMON_SYSCTL_TIMING2 1022 ++#define DDCMON_SYSCTL_TIMING3 1023 ++#define DDCMON_SYSCTL_TIMING4 1024 ++#define DDCMON_SYSCTL_TIMING5 1025 ++#define DDCMON_SYSCTL_TIMING6 1026 ++#define DDCMON_SYSCTL_TIMING7 1027 ++#define DDCMON_SYSCTL_TIMING8 1028 ++#define DDCMON_SYSCTL_MAXCLOCK 1029 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected DDCMON. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table ddcmon_dir_table_template[] = { ++ {DDCMON_SYSCTL_ID, "id", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &ddcmon_idcall}, ++ {DDCMON_SYSCTL_SIZE, "size", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &ddcmon_size}, ++ {DDCMON_SYSCTL_SYNC, "sync", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_sync}, ++ {DDCMON_SYSCTL_TIMINGS, "timings", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_timings}, ++ {DDCMON_SYSCTL_SERIAL, "serial", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_serial}, ++ {DDCMON_SYSCTL_TIME, "time", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_time}, ++ {DDCMON_SYSCTL_EDID, "edid", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_edid}, ++ {DDCMON_SYSCTL_GAMMA, "gamma", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_gamma}, ++ {DDCMON_SYSCTL_DPMS, "dpms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_dpms}, ++ {DDCMON_SYSCTL_TIMING1, "timing1", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING2, "timing2", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING3, "timing3", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING4, "timing4", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING5, "timing5", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING6, "timing6", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING7, "timing7", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_TIMING8, "timing8", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing}, ++ {DDCMON_SYSCTL_MAXCLOCK, "maxclock", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_maxclock}, ++ {0} ++}; ++ ++static int ddcmon_id = 0; ++ ++static int ddcmon_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, ddcmon_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int ddcmon_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, cs; ++ struct i2c_client *new_client; ++ struct ddcmon_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access ddcmon_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct ddcmon_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ memset(data->data, 0xff, DDCMON_SIZE); ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &ddcmon_driver; ++ new_client->flags = 0; ++ ++ /* prevent 24RF08 corruption (just in case) */ ++ i2c_smbus_write_quick(new_client, 0); ++ ++ /* Now, we do the remaining detection. */ ++ if (checksum) { ++ int cs = 0; ++ for (i = 0; i < 0x80; i++) ++ cs += i2c_smbus_read_byte_data(new_client, i); ++ if ((cs & 0xff) != 0) ++ goto ERROR1; ++ } ++ ++ /* Verify the first 8 locations 0x00FFFFFFFFFFFF00 */ ++ /* Allow force and force_ddcmon arguments */ ++ if(kind < 0) ++ { ++ for(i = 0; i < 8; i++) { ++ cs = i2c_smbus_read_byte_data(new_client, i); ++ if(i == 0 || i == 7) { ++ if(cs != 0) ++ goto ERROR1; ++ } else if(cs != 0xff) ++ goto ERROR1; ++ } ++ } ++ ++ type_name = "ddcmon"; ++ client_name = "DDC Monitor"; ++ ++ /* Fill in the remaining client fields and put it in the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = ddcmon_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ ddcmon_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ return 0; ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int ddcmon_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct ddcmon_data *) (client->data))-> ++ sysctl_id); ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("ddcmon.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ kfree(client->data); ++ return 0; ++} ++ ++static void ddcmon_update_client(struct i2c_client *client) ++{ ++ struct ddcmon_data *data = client->data; ++ int i, j; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > 300 * HZ) || ++ (jiffies < data->last_updated) || !data->valid) { ++ if (i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) ++ { ++ for (i=0; i<DDCMON_SIZE; i+=I2C_SMBUS_I2C_BLOCK_MAX) ++ if (i2c_smbus_read_i2c_block_data(client, ++ i, data->data + i) ++ != I2C_SMBUS_I2C_BLOCK_MAX) { ++ printk(KERN_WARNING "ddcmon.o: block read fail at 0x%.2x!\n", i); ++ goto DONE; ++ } ++ } else { ++ if (i2c_smbus_write_byte(client, 0)) { ++ printk(KERN_WARNING "ddcmon.o: read start fail at 0!\n"); ++ goto DONE; ++ } ++ for (i = 0; i < DDCMON_SIZE; i++) { ++ j = i2c_smbus_read_byte(client); ++ if (j < 0) { ++ printk(KERN_WARNING "eeprom.o: read fail at 0x%.2x!\n", i); ++ goto DONE; ++ } ++ data->data[i] = (u8) j; ++ } ++ } ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++DONE: ++ up(&data->update_lock); ++} ++ ++ ++void ddcmon_idcall(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_MAN_ID + 1] | ++ (data->data[DDCMON_REG_MAN_ID] << 8); ++ results[1] = data->data[DDCMON_REG_PROD_ID + 1] | ++ (data->data[DDCMON_REG_PROD_ID] << 8); ++ *nrels_mag = 2; ++ } ++} ++ ++void ddcmon_size(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_VERSIZE]; ++ results[1] = data->data[DDCMON_REG_HORSIZE]; ++ *nrels_mag = 2; ++ } ++} ++ ++void ddcmon_sync(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ int i, j; ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ *nrels_mag = 4; ++ /* look for monitor limits entry */ ++ for(i = DDCMON_REG_TIMBASE; ++ i < DDCMON_REG_TIMBASE + ++ (DDCMON_REG_TIMNUM * DDCMON_REG_TIMINCR); ++ i += DDCMON_REG_TIMINCR) { ++ if (data->data[i] == 0x00 ++ && data->data[i + 1] == 0x00 ++ && data->data[i + 2] == 0x00 ++ && data->data[i + 3] == 0xfd) { ++ for(j = 0; j < 4; j++) ++ results[j] = data->data[i + j + 5]; ++ return; ++ } ++ } ++ for(j = 0; j < 4; j++) ++ results[j] = 0; ++ } ++} ++ ++void ddcmon_maxclock(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ int i; ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ *nrels_mag = 1; ++ /* look for monitor limits entry */ ++ for(i = DDCMON_REG_TIMBASE; ++ i < DDCMON_REG_TIMBASE + ++ (DDCMON_REG_TIMNUM * DDCMON_REG_TIMINCR); ++ i += DDCMON_REG_TIMINCR) { ++ if (data->data[i] == 0x00 ++ && data->data[i + 1] == 0x00 ++ && data->data[i + 2] == 0x00 ++ && data->data[i + 3] == 0xfd) { ++ results[0] = (data->data[i + 9] == 0xff ? ++ 0 : data->data[i + 9] * 10); ++ return; ++ } ++ } ++ results[0] = 0; ++ } ++} ++ ++void ddcmon_timings(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_ESTABLISHED_TIMINGS] | ++ (data->data[DDCMON_REG_ESTABLISHED_TIMINGS + 1] << 8) | ++ (data->data[DDCMON_REG_ESTABLISHED_TIMINGS + 2] << 16); ++ *nrels_mag = 1; ++ } ++} ++ ++void ddcmon_serial(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_SERIAL] | ++ (data->data[DDCMON_REG_SERIAL + 1] << 8) | ++ (data->data[DDCMON_REG_SERIAL + 2] << 16) | ++ (data->data[DDCMON_REG_SERIAL + 3] << 24); ++ *nrels_mag = 1; ++ } ++} ++ ++void ddcmon_time(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_YEAR] + 1990; ++ results[1] = data->data[DDCMON_REG_WEEK]; ++ *nrels_mag = 2; ++ } ++} ++ ++void ddcmon_edid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_EDID_VER]; ++ results[1] = data->data[DDCMON_REG_EDID_REV]; ++ *nrels_mag = 2; ++ } ++} ++ ++void ddcmon_gamma(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = 100 + data->data[DDCMON_REG_GAMMA]; ++ *nrels_mag = 1; ++ } ++} ++ ++void ddcmon_dpms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ results[0] = data->data[DDCMON_REG_DPMS_FLAGS]; ++ *nrels_mag = 1; ++ } ++} ++ ++void ddcmon_standard_timing(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct ddcmon_data *data = client->data; ++ int nr = ctl_name - DDCMON_SYSCTL_TIMING1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ddcmon_update_client(client); ++ /* If both bytes of the timing are 0x00 or 0x01, then the timing ++ slot is unused. */ ++ if ((data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2] ++ | data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2 + 1]) & 0xfe) { ++ results[0] = (data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2] + 31) * 8; ++ switch (data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2 + 1] >> 6) { ++ /* We don't care about rounding issues there, it really ++ should be OK without it. */ ++ case 0x00: ++ results[1] = results[0]; /* unconfirmed */ ++ break; ++ case 0x01: ++ results[1] = results[0] * 3 / 4; ++ break; ++ case 0x02: ++ results[1] = results[0] * 4 / 5; ++ break; ++ case 0x03: ++ results[1] = results[0] * 9 / 16; ++ break; ++ } ++ results[2] = (data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2 + 1] & 0x3f) + 60; ++ } else { ++ results[0] = 0; ++ results[1] = 0; ++ results[2] = 0; ++ } ++ *nrels_mag = 3; ++ } ++} ++ ++static int __init sm_ddcmon_init(void) ++{ ++ printk("ddcmon.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&ddcmon_driver); ++} ++ ++static void __exit sm_ddcmon_exit(void) ++{ ++ i2c_del_driver(&ddcmon_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "Mark Studebaker <mdsxyz123@yahoo.com> " ++ "and Jean Delvare <khali@linux-fr.org>"); ++MODULE_DESCRIPTION("DDCMON driver"); ++ ++module_init(sm_ddcmon_init); ++module_exit(sm_ddcmon_exit); +--- linux-old/drivers/sensors/ds1621.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/ds1621.c Mon Dec 13 20:18:45 2004 +@@ -0,0 +1,528 @@ ++/* ++ ds1621.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Christian W. Zuckschwerdt <zany@triq.net> 2000-11-23 ++ based on lm75.c by Frodo Looijaard <frodol@dds.nl> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Supports DS1621. See doc/chips/ds1621 for details */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(ds1621); ++ ++/* Many DS1621 constants specified below */ ++ ++/* Config register used for detection */ ++/* 7 6 5 4 3 2 1 0 */ ++/* |Done|THF |TLF |NVB | 1 | 0 |POL |1SHOT| */ ++#define DS1621_REG_CONFIG_MASK 0x0C ++#define DS1621_REG_CONFIG_VAL 0x08 ++#define DS1621_REG_CONFIG_POLARITY 0x02 ++#define DS1621_REG_CONFIG_1SHOT 0x01 ++#define DS1621_REG_CONFIG_DONE 0x80 ++ ++/* Note: the done bit is always unset if continuous conversion is in progress. ++ We need to stop the continuous conversion or switch to single shot ++ before this bit becomes available! ++ */ ++ ++/* The DS1621 registers */ ++#define DS1621_REG_TEMP 0xAA /* word, RO */ ++#define DS1621_REG_TEMP_OVER 0xA1 /* word, RW */ ++#define DS1621_REG_TEMP_HYST 0xA2 /* word, RW -- it's a low temp trigger */ ++#define DS1621_REG_CONF 0xAC /* byte, RW */ ++#define DS1621_REG_TEMP_COUNTER 0xA8 /* byte, RO */ ++#define DS1621_REG_TEMP_SLOPE 0xA9 /* byte, RO */ ++#define DS1621_COM_START 0xEE /* no data */ ++#define DS1621_COM_STOP 0x22 /* no data */ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define TEMP_FROM_REG(val) ((((val & 0x7fff) >> 7) * 5) | \ ++ ((val & 0x8000)?-256:0)) ++#define TEMP_TO_REG(val) (SENSORS_LIMIT((val<0 ? (0x200+((val)/5))<<7 : \ ++ (((val) + 2) / 5) << 7),0,0xffff)) ++#define ALARMS_FROM_REG(val) ((val) & \ ++ (DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW)) ++#define ITEMP_FROM_REG(val) ((((val & 0x7fff) >> 8)) | \ ++ ((val & 0x8000)?-256:0)) ++ ++/* Each client has this additional data */ ++struct ds1621_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u16 temp, temp_over, temp_hyst; /* Register values, word */ ++ u8 conf; /* Register encoding, combined */ ++ ++ char enable; /* !=0 if we're expected to restart the conversion */ ++ u8 temp_int, temp_counter, temp_slope; /* Register values, byte */ ++}; ++ ++static int ds1621_attach_adapter(struct i2c_adapter *adapter); ++static int ds1621_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void ds1621_init_client(struct i2c_client *client); ++static int ds1621_detach_client(struct i2c_client *client); ++ ++static int ds1621_read_value(struct i2c_client *client, u8 reg); ++static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value); ++static void ds1621_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ds1621_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ds1621_enable(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ds1621_continuous(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ds1621_polarity(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void ds1621_update_client(struct i2c_client *client); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver ds1621_driver = { ++ .owner = THIS_MODULE, ++ .name = "DS1621 sensor driver", ++ .id = I2C_DRIVERID_DS1621, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = ds1621_attach_adapter, ++ .detach_client = ds1621_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define DS1621_SYSCTL_TEMP 1200 /* Degrees Celcius * 10 */ ++#define DS1621_SYSCTL_ALARMS 2001 /* bitvector */ ++#define DS1621_ALARM_TEMP_HIGH 0x40 ++#define DS1621_ALARM_TEMP_LOW 0x20 ++#define DS1621_SYSCTL_ENABLE 2002 ++#define DS1621_SYSCTL_CONTINUOUS 2003 ++#define DS1621_SYSCTL_POLARITY 2004 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected DS1621. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table ds1621_dir_table_template[] = { ++ {DS1621_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_temp}, ++ {DS1621_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_alarms}, ++ {DS1621_SYSCTL_ENABLE, "enable", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_enable}, ++ {DS1621_SYSCTL_CONTINUOUS, "continuous", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_continuous}, ++ {DS1621_SYSCTL_POLARITY, "polarity", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_polarity}, ++ {0} ++}; ++ ++static int ds1621_id = 0; ++ ++static int ds1621_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, ds1621_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int ds1621_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, conf; ++ struct i2c_client *new_client; ++ struct ds1621_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("ds1621.o: ds1621_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_WRITE_BYTE)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access ds1621_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct ds1621_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &ds1621_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. It is lousy. */ ++ if (kind < 0) { ++ conf = i2c_smbus_read_byte_data(new_client, ++ DS1621_REG_CONF); ++ if ((conf & DS1621_REG_CONFIG_MASK) ++ != DS1621_REG_CONFIG_VAL) ++ goto ERROR1; ++ } ++ ++ /* Determine the chip type - only one kind supported! */ ++ if (kind <= 0) ++ kind = ds1621; ++ ++ if (kind == ds1621) { ++ type_name = "ds1621"; ++ client_name = "DS1621 chip"; ++ } else { ++#ifdef DEBUG ++ printk("ds1621.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = ds1621_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ ds1621_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ ds1621_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int ds1621_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct ds1621_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("ds1621.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* All registers are word-sized, except for the configuration register. ++ DS1621 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int ds1621_read_value(struct i2c_client *client, u8 reg) ++{ ++ if ((reg == DS1621_REG_CONF) || (reg == DS1621_REG_TEMP_COUNTER) ++ || (reg == DS1621_REG_TEMP_SLOPE)) ++ return i2c_smbus_read_byte_data(client, reg); ++ else ++ return swab16(i2c_smbus_read_word_data(client, reg)); ++} ++ ++/* All registers are word-sized, except for the configuration register. ++ DS1621 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if ( (reg == DS1621_COM_START) || (reg == DS1621_COM_STOP) ) ++ return i2c_smbus_write_byte(client, reg); ++ else ++ if ((reg == DS1621_REG_CONF) || (reg == DS1621_REG_TEMP_COUNTER) ++ || (reg == DS1621_REG_TEMP_SLOPE)) ++ return i2c_smbus_write_byte_data(client, reg, value); ++ else ++ return i2c_smbus_write_word_data(client, reg, swab16(value)); ++} ++ ++static void ds1621_init_client(struct i2c_client *client) ++{ ++ int reg; ++ ++ reg = ds1621_read_value(client, DS1621_REG_CONF); ++ /* start the continous conversion */ ++ if(reg & 0x01) ++ ds1621_write_value(client, DS1621_REG_CONF, reg & 0xfe); ++} ++ ++static void ds1621_update_client(struct i2c_client *client) ++{ ++ struct ds1621_data *data = client->data; ++ u8 new_conf; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting ds1621 update\n"); ++#endif ++ ++ data->conf = ds1621_read_value(client, DS1621_REG_CONF); ++ ++ data->temp = ds1621_read_value(client, ++ DS1621_REG_TEMP); ++ data->temp_over = ds1621_read_value(client, ++ DS1621_REG_TEMP_OVER); ++ data->temp_hyst = ds1621_read_value(client, ++ DS1621_REG_TEMP_HYST); ++ ++ /* wait for the DONE bit before reading extended values */ ++ ++ if (data->conf & DS1621_REG_CONFIG_DONE) { ++ data->temp_counter = ds1621_read_value(client, ++ DS1621_REG_TEMP_COUNTER); ++ data->temp_slope = ds1621_read_value(client, ++ DS1621_REG_TEMP_SLOPE); ++ data->temp_int = ITEMP_FROM_REG(data->temp); ++ /* restart the conversion */ ++ if (data->enable) ++ ds1621_write_value(client, DS1621_COM_START, 0); ++ } ++ ++ /* reset alarms if neccessary */ ++ new_conf = data->conf; ++ if (data->temp < data->temp_over) ++ new_conf &= ~DS1621_ALARM_TEMP_HIGH; ++ if (data->temp > data->temp_hyst) ++ new_conf &= ~DS1621_ALARM_TEMP_LOW; ++ if (data->conf != new_conf) ++ ds1621_write_value(client, DS1621_REG_CONF, ++ new_conf); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void ds1621_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct ds1621_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ if (!(data->conf & DS1621_REG_CONFIG_DONE) || ++ (data->temp_counter > data->temp_slope) || ++ (data->temp_slope == 0)) { ++ *nrels_mag = 1; ++ } else { ++ *nrels_mag = 2; ++ } ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ds1621_update_client(client); ++ /* decide wether to calculate more precise temp */ ++ if (!(data->conf & DS1621_REG_CONFIG_DONE) || ++ (data->temp_counter > data->temp_slope) || ++ (data->temp_slope == 0)) { ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ } else { ++ results[0] = TEMP_FROM_REG(data->temp_over)*10; ++ results[1] = TEMP_FROM_REG(data->temp_hyst)*10; ++ results[2] = data->temp_int * 100 - 25 + ++ ((data->temp_slope - data->temp_counter) * ++ 100 / data->temp_slope); ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over = TEMP_TO_REG(results[0]); ++ ds1621_write_value(client, DS1621_REG_TEMP_OVER, ++ data->temp_over); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ ds1621_write_value(client, DS1621_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++void ds1621_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct ds1621_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ds1621_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->conf); ++ *nrels_mag = 1; ++ } ++} ++ ++void ds1621_enable(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ /* If you really screw up your chip (like I did) this is */ ++ /* sometimes needed to (re)start the continous conversion */ ++ /* there is no data to read so this might hang your SMBus! */ ++ ++ struct ds1621_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ds1621_update_client(client); ++ results[0] = !(data->conf & DS1621_REG_CONFIG_DONE); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (results[0]) { ++ ds1621_write_value(client, DS1621_COM_START, 0); ++ data->enable=1; ++ } else { ++ ds1621_write_value(client, DS1621_COM_STOP, 0); ++ data->enable=0; ++ } ++ } else { ++ ds1621_write_value(client, DS1621_COM_START, 0); ++ data->enable=1; ++ } ++ } ++} ++ ++void ds1621_continuous(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct ds1621_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ds1621_update_client(client); ++ results[0] = !(data->conf & DS1621_REG_CONFIG_1SHOT); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ ds1621_update_client(client); ++ if (*nrels_mag >= 1) { ++ if (results[0]) { ++ ds1621_write_value(client, DS1621_REG_CONF, ++ data->conf & ~DS1621_REG_CONFIG_1SHOT); ++ } else { ++ ds1621_write_value(client, DS1621_REG_CONF, ++ data->conf | DS1621_REG_CONFIG_1SHOT); ++ } ++ } else { ++ ds1621_write_value(client, DS1621_REG_CONF, ++ data->conf & ~DS1621_REG_CONFIG_1SHOT); ++ } ++ } ++} ++ ++void ds1621_polarity(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct ds1621_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ ds1621_update_client(client); ++ results[0] = !(!(data->conf & DS1621_REG_CONFIG_POLARITY)); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ ds1621_update_client(client); ++ if (*nrels_mag >= 1) { ++ if (results[0]) { ++ ds1621_write_value(client, DS1621_REG_CONF, ++ data->conf | DS1621_REG_CONFIG_POLARITY); ++ } else { ++ ds1621_write_value(client, DS1621_REG_CONF, ++ data->conf & ~DS1621_REG_CONFIG_POLARITY); ++ } ++ } ++ } ++} ++ ++static int __init sm_ds1621_init(void) ++{ ++ printk("ds1621.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&ds1621_driver); ++} ++ ++static void __exit sm_ds1621_exit(void) ++{ ++ i2c_del_driver(&ds1621_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Christian W. Zuckschwerdt <zany@triq.net>"); ++MODULE_DESCRIPTION("DS1621 driver"); ++ ++module_init(sm_ds1621_init); ++module_exit(sm_ds1621_exit); +--- linux-old/drivers/sensors/eeprom.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/eeprom.c Mon Dec 13 20:18:45 2004 +@@ -0,0 +1,418 @@ ++/* ++ eeprom.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> ++ ++ 2003-08-18 Jean Delvare <khali@linux-fr.org> ++ Divide the eeprom in 2-row (arbitrary) slices. This significantly ++ speeds sensors up, as well as various scripts using the eeprom ++ module. ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <linux/sched.h> /* for capable() */ ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x50, 0x57, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(eeprom); ++ ++static int checksum = 0; ++MODULE_PARM(checksum, "i"); ++MODULE_PARM_DESC(checksum, ++ "Only accept eeproms whose checksum is correct"); ++ ++ ++/* Many constants specified below */ ++ ++/* EEPROM registers */ ++#define EEPROM_REG_CHECKSUM 0x3f ++ ++/* possible natures */ ++#define NATURE_UNKNOWN 0 ++#define NATURE_VAIO 1 ++ ++/* Size of EEPROM in bytes */ ++#define EEPROM_SIZE 256 ++ ++/* Each client has this additional data */ ++struct eeprom_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ u8 valid; /* bitfield, bit!=0 if slice is valid */ ++ unsigned long last_updated[8]; /* In jiffies, 8 slices */ ++ ++ u8 data[EEPROM_SIZE]; /* Register values */ ++ u8 nature; ++}; ++ ++ ++static int eeprom_attach_adapter(struct i2c_adapter *adapter); ++static int eeprom_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int eeprom_detach_client(struct i2c_client *client); ++ ++#if 0 ++static int eeprom_write_value(struct i2c_client *client, u8 reg, ++ u8 value); ++#endif ++ ++static void eeprom_contents(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void eeprom_update_client(struct i2c_client *client, u8 slice); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver eeprom_driver = { ++ .owner = THIS_MODULE, ++ .name = "EEPROM READER", ++ .id = I2C_DRIVERID_EEPROM, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = eeprom_attach_adapter, ++ .detach_client = eeprom_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define EEPROM_SYSCTL1 1000 ++#define EEPROM_SYSCTL2 1001 ++#define EEPROM_SYSCTL3 1002 ++#define EEPROM_SYSCTL4 1003 ++#define EEPROM_SYSCTL5 1004 ++#define EEPROM_SYSCTL6 1005 ++#define EEPROM_SYSCTL7 1006 ++#define EEPROM_SYSCTL8 1007 ++#define EEPROM_SYSCTL9 1008 ++#define EEPROM_SYSCTL10 1009 ++#define EEPROM_SYSCTL11 1010 ++#define EEPROM_SYSCTL12 1011 ++#define EEPROM_SYSCTL13 1012 ++#define EEPROM_SYSCTL14 1013 ++#define EEPROM_SYSCTL15 1014 ++#define EEPROM_SYSCTL16 1015 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected EEPROM. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table eeprom_dir_table_template[] = { ++ {EEPROM_SYSCTL1, "00", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL2, "10", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL3, "20", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL4, "30", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL5, "40", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL6, "50", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL7, "60", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL8, "70", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL9, "80", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL10, "90", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL11, "a0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL12, "b0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL13, "c0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL14, "d0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL15, "e0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {EEPROM_SYSCTL16, "f0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &eeprom_contents}, ++ {0} ++}; ++ ++static int eeprom_id = 0; ++ ++static int eeprom_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, eeprom_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int eeprom_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct eeprom_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("eeprom.o: eeprom_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access eeprom_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct eeprom_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ memset(data->data, 0xff, EEPROM_SIZE); ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &eeprom_driver; ++ new_client->flags = 0; ++ ++ /* prevent 24RF08 corruption */ ++ i2c_smbus_write_quick(new_client, 0); ++ ++ /* Now, we do the remaining detection. It is not there, unless you force ++ the checksum to work out. */ ++ if (checksum) { ++ int cs = 0; ++ for (i = 0; i <= 0x3e; i++) ++ cs += i2c_smbus_read_byte_data(new_client, i); ++ cs &= 0xff; ++ if (i2c_smbus_read_byte_data ++ (new_client, EEPROM_REG_CHECKSUM) != cs) ++ goto ERROR1; ++ } ++ ++ data->nature = NATURE_UNKNOWN; ++ /* Detect the Vaio nature of EEPROMs. ++ We use the "PCG-" prefix as the signature. */ ++ if (address == 0x57) ++ { ++ if (i2c_smbus_read_byte_data(new_client, 0x80) == 'P' ++ && i2c_smbus_read_byte_data(new_client, 0x81) == 'C' ++ && i2c_smbus_read_byte_data(new_client, 0x82) == 'G' ++ && i2c_smbus_read_byte_data(new_client, 0x83) == '-') ++ data->nature = NATURE_VAIO; ++ } ++ ++ /* Determine the chip type - only one kind supported! */ ++ if (kind <= 0) ++ kind = eeprom; ++ ++ if (kind == eeprom) { ++ type_name = "eeprom"; ++ client_name = "EEPROM chip"; ++ } else { ++#ifdef DEBUG ++ printk("eeprom.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = eeprom_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ eeprom_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int eeprom_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct eeprom_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("eeprom.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++#if 0 ++/* No writes yet (PAE) */ ++static int eeprom_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++#endif ++ ++static void eeprom_update_client(struct i2c_client *client, u8 slice) ++{ ++ struct eeprom_data *data = client->data; ++ int i, j; ++ ++ down(&data->update_lock); ++ ++ if (!(data->valid & (1 << slice)) ++ || (jiffies - data->last_updated[slice] > 300 * HZ) ++ || (jiffies < data->last_updated[slice])) { ++ ++#ifdef DEBUG ++ printk("Starting eeprom update, slice %u\n", slice); ++#endif ++ ++ if (i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) ++ { ++ for (i = slice << 5; i < (slice + 1) << 5; ++ i += I2C_SMBUS_I2C_BLOCK_MAX) ++ if (i2c_smbus_read_i2c_block_data(client, ++ i, data->data + i) ++ != I2C_SMBUS_I2C_BLOCK_MAX) { ++ printk(KERN_WARNING "eeprom.o: block read fail at 0x%.2x!\n", i); ++ goto DONE; ++ } ++ } else { ++ if (i2c_smbus_write_byte(client, slice << 5)) { ++ printk(KERN_WARNING "eeprom.o: read start fail at 0x%.2x!\n", slice << 5); ++ goto DONE; ++ } ++ for (i = slice << 5; i < (slice + 1) << 5; i++) { ++ j = i2c_smbus_read_byte(client); ++ if (j < 0) { ++ printk(KERN_WARNING "eeprom.o: read fail at 0x%.2x!\n", i); ++ goto DONE; ++ } ++ data->data[i] = (u8) j; ++ } ++ } ++ data->last_updated[slice] = jiffies; ++ data->valid |= (1 << slice); ++ } ++DONE: ++ up(&data->update_lock); ++} ++ ++ ++void eeprom_contents(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ int i; ++ int nr = ctl_name - EEPROM_SYSCTL1; ++ struct eeprom_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ eeprom_update_client(client, nr >> 1); ++ /* Hide Vaio security settings to regular users */ ++ if (nr == 0 && data->nature == NATURE_VAIO ++ && !capable(CAP_SYS_ADMIN)) ++ for (i = 0; i < 16; i++) ++ results[i] = 0; ++ else ++ for (i = 0; i < 16; i++) ++ results[i] = data->data[i + nr * 16]; ++#ifdef DEBUG ++ printk("eeprom.o: 0x%X EEPROM contents (row %d):", ++ client->addr, nr + 1); ++ if (nr == 0 && data->nature == NATURE_VAIO) ++ printk(" <hidden for security reasons>\n"); ++ else { ++ for (i = 0; i < 16; i++) ++ printk(" 0x%02X", data->data[i + nr * 16]); ++ printk("\n"); ++ } ++#endif ++ *nrels_mag = 16; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ ++/* No writes to the EEPROM (yet, anyway) (PAE) */ ++ printk("eeprom.o: No writes to EEPROMs supported!\n"); ++ } ++} ++ ++static int __init sm_eeprom_init(void) ++{ ++ printk("eeprom.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&eeprom_driver); ++} ++ ++static void __exit sm_eeprom_exit(void) ++{ ++ i2c_del_driver(&eeprom_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("EEPROM driver"); ++ ++module_init(sm_eeprom_init); ++module_exit(sm_eeprom_exit); +--- linux-old/drivers/sensors/fscpos.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/fscpos.c Mon Dec 13 20:18:46 2004 +@@ -0,0 +1,690 @@ ++/* ++ fscpos.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2001 Hermann Jung <hej@odn.de> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ fujitsu siemens poseidon chip, ++ module based on lm80.c ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ and Philip Edelbrock <phil@netroedge.com> ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(fscpos); ++ ++/* The FSCPOS registers */ ++ ++/* chip identification */ ++#define FSCPOS_REG_IDENT_0 0x00 ++#define FSCPOS_REG_IDENT_1 0x01 ++#define FSCPOS_REG_IDENT_2 0x02 ++#define FSCPOS_REG_REVISION 0x03 ++ ++/* global control and status */ ++#define FSCPOS_REG_EVENT_STATE 0x04 ++#define FSCPOS_REG_CONTROL 0x05 ++ ++/* watchdog */ ++#define FSCPOS_REG_WDOG_PRESET 0x28 ++#define FSCPOS_REG_WDOG_STATE 0x23 ++#define FSCPOS_REG_WDOG_CONTROL 0x21 ++ ++/* fan 0 */ ++#define FSCPOS_REG_FAN0_MIN 0x55 ++#define FSCPOS_REG_FAN0_ACT 0x0e ++#define FSCPOS_REG_FAN0_STATE 0x0d ++#define FSCPOS_REG_FAN0_RIPPLE 0x0f ++ ++/* fan 1 */ ++#define FSCPOS_REG_FAN1_MIN 0x65 ++#define FSCPOS_REG_FAN1_ACT 0x6b ++#define FSCPOS_REG_FAN1_STATE 0x62 ++#define FSCPOS_REG_FAN1_RIPPLE 0x6f ++ ++/* fan 2 */ ++/* min speed fan2 not supported */ ++#define FSCPOS_REG_FAN2_ACT 0xab ++#define FSCPOS_REG_FAN2_STATE 0xa2 ++#define FSCPOS_REG_FAN2_RIPPLE 0x0af ++ ++/* voltage supervision */ ++#define FSCPOS_REG_VOLT_12 0x45 ++#define FSCPOS_REG_VOLT_5 0x42 ++#define FSCPOS_REG_VOLT_BATT 0x48 ++ ++/* temperatures */ ++/* sensor 0 */ ++#define FSCPOS_REG_TEMP0_ACT 0x64 ++#define FSCPOS_REG_TEMP0_STATE 0x71 ++ ++/* sensor 1 */ ++#define FSCPOS_REG_TEMP1_ACT 0x32 ++#define FSCPOS_REG_TEMP1_STATE 0x81 ++ ++/* sensor 2 */ ++#define FSCPOS_REG_TEMP2_ACT 0x35 ++#define FSCPOS_REG_TEMP2_STATE 0x91 ++ ++ ++ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val),0,255)) ++#define IN_FROM_REG(val,nr) (val) ++ ++/* Initial limits */ ++ ++/* For each registered FSCPOS, we need to keep some data in memory. That ++ data is pointed to by fscpos_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new fscpos client is ++ allocated. */ ++struct fscpos_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 revision; /* revision of chip */ ++ u8 global_event; /* global event status */ ++ u8 global_control; /* global control register */ ++ u8 watchdog[3]; /* watchdog */ ++ u8 volt[3]; /* 12, 5, battery current */ ++ u8 temp_act[3]; /* temperature */ ++ u8 temp_status[3]; /* status of sensor */ ++ u8 fan_act[3]; /* fans revolutions per second */ ++ u8 fan_status[3]; /* fan status */ ++ u8 fan_min[3]; /* fan min value for rps */ ++ u8 fan_ripple[3]; /* divider for rps */ ++}; ++ ++ ++static int fscpos_attach_adapter(struct i2c_adapter *adapter); ++static int fscpos_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int fscpos_detach_client(struct i2c_client *client); ++ ++static int fscpos_read_value(struct i2c_client *client, u8 register); ++static int fscpos_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void fscpos_update_client(struct i2c_client *client); ++static void fscpos_init_client(struct i2c_client *client); ++ ++ ++static void fscpos_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void fscpos_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscpos_fan_internal(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results, ++ int nr, int reg_state, int reg_min, int res_ripple); ++static void fscpos_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscpos_volt(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscpos_wdog(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int fscpos_id = 0; ++ ++static struct i2c_driver fscpos_driver = { ++ .owner = THIS_MODULE, ++ .name = "FSCPOS sensor driver", ++ .id = I2C_DRIVERID_FSCPOS, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = fscpos_attach_adapter, ++ .detach_client = fscpos_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define FSCPOS_SYSCTL_VOLT0 1000 /* 12 volt supply */ ++#define FSCPOS_SYSCTL_VOLT1 1001 /* 5 volt supply */ ++#define FSCPOS_SYSCTL_VOLT2 1002 /* batterie voltage*/ ++#define FSCPOS_SYSCTL_FAN0 1101 /* state, min, ripple, actual value fan 0 */ ++#define FSCPOS_SYSCTL_FAN1 1102 /* state, min, ripple, actual value fan 1 */ ++#define FSCPOS_SYSCTL_FAN2 1103 /* state, min, ripple, actual value fan 2 */ ++#define FSCPOS_SYSCTL_TEMP0 1201 /* state and value of sensor 0, cpu die */ ++#define FSCPOS_SYSCTL_TEMP1 1202 /* state and value of sensor 1, motherboard */ ++#define FSCPOS_SYSCTL_TEMP2 1203 /* state and value of sensor 2, chassis */ ++#define FSCPOS_SYSCTL_REV 2000 /* Revision */ ++#define FSCPOS_SYSCTL_EVENT 2001 /* global event status */ ++#define FSCPOS_SYSCTL_CONTROL 2002 /* global control byte */ ++#define FSCPOS_SYSCTL_WDOG 2003 /* state, min, ripple, actual value fan 2 */ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected FSCPOS. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table fscpos_dir_table_template[] = { ++ {FSCPOS_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_in}, ++ {FSCPOS_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_in}, ++ {FSCPOS_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_in}, ++ {FSCPOS_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_temp}, ++ {FSCPOS_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_temp}, ++ {FSCPOS_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_temp}, ++ {FSCPOS_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_volt}, ++ {FSCPOS_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_volt}, ++ {FSCPOS_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_volt}, ++ {FSCPOS_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_fan}, ++ {FSCPOS_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_fan}, ++ {FSCPOS_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_fan}, ++ {FSCPOS_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscpos_wdog}, ++ {0} ++}; ++ ++static int fscpos_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, fscpos_detect); ++} ++ ++int fscpos_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct fscpos_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("fscpos.o: fscpos_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access fscpos_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct fscpos_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &fscpos_driver; ++ new_client->flags = 0; ++ ++ /* Do the remaining detection unless force or force_fscpos parameter */ ++ if (kind < 0) { ++ if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_0) != 0x50) ++ goto ERROR1; ++ if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_1) != 0x45) ++ goto ERROR1; ++ if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_2) != 0x47) ++ goto ERROR1; ++ } ++ ++ kind = fscpos; ++ ++ type_name = "fscpos"; ++ client_name = "fsc poseidon chip"; ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = fscpos_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ fscpos_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ fscpos_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int fscpos_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct fscpos_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("fscpos.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static int fscpos_read_value(struct i2c_client *client, u8 reg) ++{ ++#ifdef DEBUG ++ printk("fscpos: read reg 0x%02x\n",reg); ++#endif ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int fscpos_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++#ifdef DEBUG ++ printk("fscpos: write reg 0x%02x, val 0x%02x\n",reg, value); ++#endif ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new FSCPOS. It should set limits, etc. */ ++static void fscpos_init_client(struct i2c_client *client) ++{ ++ struct fscpos_data *data = client->data; ++ ++ /* read revision from chip */ ++ data->revision = fscpos_read_value(client,FSCPOS_REG_REVISION); ++ /* setup missing fan2_min value */ ++ data->fan_min[2] = 0xff; ++} ++ ++static void fscpos_update_client(struct i2c_client *client) ++{ ++ struct fscpos_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > 2 * HZ) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting fscpos update\n"); ++#endif ++ data->temp_act[0] = fscpos_read_value(client, FSCPOS_REG_TEMP0_ACT); ++ data->temp_act[1] = fscpos_read_value(client, FSCPOS_REG_TEMP1_ACT); ++ data->temp_act[2] = fscpos_read_value(client, FSCPOS_REG_TEMP2_ACT); ++ data->temp_status[0] = fscpos_read_value(client, FSCPOS_REG_TEMP0_STATE); ++ data->temp_status[1] = fscpos_read_value(client, FSCPOS_REG_TEMP1_STATE); ++ data->temp_status[2] = fscpos_read_value(client, FSCPOS_REG_TEMP2_STATE); ++ ++ data->volt[0] = fscpos_read_value(client, FSCPOS_REG_VOLT_12); ++ data->volt[1] = fscpos_read_value(client, FSCPOS_REG_VOLT_5); ++ data->volt[2] = fscpos_read_value(client, FSCPOS_REG_VOLT_BATT); ++ ++ data->fan_act[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_ACT); ++ data->fan_act[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_ACT); ++ data->fan_act[2] = fscpos_read_value(client, FSCPOS_REG_FAN2_ACT); ++ data->fan_status[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_STATE); ++ data->fan_status[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_STATE); ++ data->fan_status[2] = fscpos_read_value(client, FSCPOS_REG_FAN2_STATE); ++ data->fan_min[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_MIN); ++ data->fan_min[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_MIN); ++ /* fan2_min is not supported */ ++ data->fan_ripple[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_RIPPLE); ++ data->fan_ripple[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_RIPPLE); ++ data->fan_ripple[2] = fscpos_read_value(client, FSCPOS_REG_FAN2_RIPPLE); ++ ++ data->watchdog[0] = fscpos_read_value(client, FSCPOS_REG_WDOG_PRESET); ++ data->watchdog[1] = fscpos_read_value(client, FSCPOS_REG_WDOG_STATE); ++ data->watchdog[2] = fscpos_read_value(client, FSCPOS_REG_WDOG_CONTROL); ++ ++ data->global_event = fscpos_read_value(client, FSCPOS_REG_EVENT_STATE); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void fscpos_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscpos_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscpos_update_client(client); ++ switch(ctl_name) { ++ case FSCPOS_SYSCTL_REV: ++ results[0] = data->revision ; ++ break; ++ case FSCPOS_SYSCTL_EVENT: ++ results[0] = data->global_event & 0x1f; ++ break; ++ case FSCPOS_SYSCTL_CONTROL: ++ results[0] = data->global_control & 0x01; ++ break; ++ default: ++ printk("fscpos: ctl_name %d not supported\n", ++ ctl_name); ++ *nrels_mag = 0; ++ return; ++ } ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if((ctl_name == FSCPOS_SYSCTL_CONTROL) && (*nrels_mag >= 1)) { ++ data->global_control = (results[0] & 0x01); ++ printk("fscpos: writing 0x%02x to global_control\n", ++ data->global_control); ++ fscpos_write_value(client,FSCPOS_REG_CONTROL, ++ data->global_control); ++ } ++ else ++ printk("fscpos: writing to chip not supported\n"); ++ } ++} ++ ++#define TEMP_FROM_REG(val) (val-128) ++ ++ ++void fscpos_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscpos_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscpos_update_client(client); ++ switch(ctl_name) { ++ case FSCPOS_SYSCTL_TEMP0: ++ results[0] = data->temp_status[0] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[0]); ++ break; ++ case FSCPOS_SYSCTL_TEMP1: ++ results[0] = data->temp_status[1] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[1]); ++ break; ++ case FSCPOS_SYSCTL_TEMP2: ++ results[0] = data->temp_status[2] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[2]); ++ break; ++ default: ++ printk("fscpos: ctl_name %d not supported\n", ++ ctl_name); ++ *nrels_mag = 0; ++ return; ++ } ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if(*nrels_mag >= 1) { ++ switch(ctl_name) { ++ case FSCPOS_SYSCTL_TEMP0: ++ data->temp_status[0] = ++ (data->temp_status[0] & ~0x02) ++ | (results[0] & 0x02); ++ printk("fscpos: writing value 0x%02x " ++ "to temp0_status\n", ++ data->temp_status[0]); ++ fscpos_write_value(client, ++ FSCPOS_REG_TEMP0_STATE, ++ data->temp_status[0] & 0x02); ++ break; ++ case FSCPOS_SYSCTL_TEMP1: ++ data->temp_status[1] = (data->temp_status[1] & ~0x02) | (results[0] & 0x02); ++ printk("fscpos: writing value 0x%02x to temp1_status\n", data->temp_status[1]); ++ fscpos_write_value(client,FSCPOS_REG_TEMP1_STATE, ++ data->temp_status[1] & 0x02); ++ break; ++ case FSCPOS_SYSCTL_TEMP2: ++ data->temp_status[2] = (data->temp_status[2] & ~0x02) | (results[0] & 0x02); ++ printk("fscpos: writing value 0x%02x to temp2_status\n", data->temp_status[2]); ++ fscpos_write_value(client,FSCPOS_REG_TEMP2_STATE, ++ data->temp_status[2] & 0x02); ++ break; ++ default: ++ printk("fscpos: ctl_name %d not supported\n",ctl_name); ++ } ++ } ++ else ++ printk("fscpos: writing to chip not supported\n"); ++ } ++} ++ ++#define VOLT_FROM_REG(val,mult) (val*mult/255) ++ ++void fscpos_volt(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscpos_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscpos_update_client(client); ++ switch(ctl_name) { ++ case FSCPOS_SYSCTL_VOLT0: ++ results[0] = VOLT_FROM_REG(data->volt[0],1420); ++ break; ++ case FSCPOS_SYSCTL_VOLT1: ++ results[0] = VOLT_FROM_REG(data->volt[1],660); ++ break; ++ case FSCPOS_SYSCTL_VOLT2: ++ results[0] = VOLT_FROM_REG(data->volt[2],330); ++ break; ++ default: ++ printk("fscpos: ctl_name %d not supported\n", ++ ctl_name); ++ *nrels_mag = 0; ++ return; ++ } ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ printk("fscpos: writing to chip not supported\n"); ++ } ++} ++ ++void fscpos_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ ++ switch(ctl_name) { ++ case FSCPOS_SYSCTL_FAN0: ++ fscpos_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 0,FSCPOS_REG_FAN0_STATE,FSCPOS_REG_FAN0_MIN, ++ FSCPOS_REG_FAN0_RIPPLE); ++ break; ++ case FSCPOS_SYSCTL_FAN1: ++ fscpos_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 1,FSCPOS_REG_FAN1_STATE,FSCPOS_REG_FAN1_MIN, ++ FSCPOS_REG_FAN1_RIPPLE); ++ break; ++ case FSCPOS_SYSCTL_FAN2: ++ fscpos_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 2,FSCPOS_REG_FAN2_STATE,0xff, ++ FSCPOS_REG_FAN2_RIPPLE); ++ break; ++ default: ++ printk("fscpos: illegal fan nr %d\n",ctl_name); ++ } ++} ++ ++#define RPM_FROM_REG(val) (val*60) ++ ++void fscpos_fan_internal(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results, int nr, ++ int reg_state, int reg_min, int reg_ripple ) ++{ ++ struct fscpos_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscpos_update_client(client); ++ results[0] = data->fan_status[nr] & 0x04; ++ results[1] = data->fan_min[nr]; ++ results[2] = data->fan_ripple[nr] & 0x03; ++ results[3] = RPM_FROM_REG(data->fan_act[nr]); ++ *nrels_mag = 4; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if(*nrels_mag >= 1) { ++ data->fan_status[nr] = results[0] & 0x04; ++ printk("fscpos: writing value 0x%02x to fan%d_status\n", ++ data->fan_status[nr],nr); ++ fscpos_write_value(client,reg_state, ++ data->fan_status[nr]); ++ } ++ if((*nrels_mag >= 2) && (nr < 2)) { ++ /* minimal speed for fan2 not supported */ ++ data->fan_min[nr] = results[1]; ++ printk("fscpos: writing value 0x%02x to fan%d_min\n", ++ data->fan_min[nr],nr); ++ fscpos_write_value(client,reg_min, ++ data->fan_min[nr]); ++ } ++ if(*nrels_mag >= 3) { ++ if((results[2] & 0x03) == 0) { ++ printk("fscpos: fan%d ripple 0 not allowed\n",nr); ++ return; ++ } ++ data->fan_ripple[nr] = results[2] & 0x03; ++ printk("fscpos: writing value 0x%02x to fan%d_ripple\n", ++ data->fan_ripple[nr],nr); ++ fscpos_write_value(client,reg_ripple, ++ data->fan_ripple[nr]); ++ } ++ } ++} ++ ++void fscpos_wdog(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscpos_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscpos_update_client(client); ++ results[0] = data->watchdog[0] ; ++ results[1] = data->watchdog[1] & 0x02; ++ results[2] = data->watchdog[2] & 0xb0; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->watchdog[0] = results[0] & 0xff; ++ printk("fscpos: writing value 0x%02x to wdog_preset\n", ++ data->watchdog[0]); ++ fscpos_write_value(client,FSCPOS_REG_WDOG_PRESET, ++ data->watchdog[0]); ++ } ++ if (*nrels_mag >= 2) { ++ data->watchdog[1] = results[1] & 0x02; ++ printk("fscpos: writing value 0x%02x to wdog_state\n", ++ data->watchdog[1]); ++ fscpos_write_value(client,FSCPOS_REG_WDOG_STATE, ++ data->watchdog[1]); ++ } ++ if (*nrels_mag >= 3) { ++ data->watchdog[2] = results[2] & 0xb0; ++ printk("fscpos: writing value 0x%02x to wdog_control\n", ++ data->watchdog[2]); ++ fscpos_write_value(client,FSCPOS_REG_WDOG_CONTROL, ++ data->watchdog[2]); ++ } ++ } ++} ++ ++static int __init sm_fscpos_init(void) ++{ ++ printk("fscpos.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&fscpos_driver); ++} ++ ++static void __exit sm_fscpos_exit(void) ++{ ++ i2c_del_driver(&fscpos_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Hermann Jung <hej@odn.de> based on work from Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("fujitsu siemens poseidon chip driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_fscpos_init); ++module_exit(sm_fscpos_exit); +--- linux-old/drivers/sensors/fscscy.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/fscscy.c Mon Dec 13 20:18:46 2004 +@@ -0,0 +1,915 @@ ++/* ++ fscscy.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2001 Martin Knoblauch <mkn@teraport.de, knobi@knobisoft.de> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ fujitsu siemens scylla chip, ++ module based on lm80.c, fscpos.c ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ and Philip Edelbrock <phil@netroedge.com> ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(fscscy); ++ ++/* The FSCSCY registers */ ++ ++/* chip identification */ ++#define FSCSCY_REG_IDENT_0 0x00 ++#define FSCSCY_REG_IDENT_1 0x01 ++#define FSCSCY_REG_IDENT_2 0x02 ++#define FSCSCY_REG_REVISION 0x03 ++ ++/* global control and status */ ++#define FSCSCY_REG_EVENT_STATE 0x04 ++#define FSCSCY_REG_CONTROL 0x05 ++ ++/* watchdog */ ++#define FSCSCY_REG_WDOG_PRESET 0x28 ++#define FSCSCY_REG_WDOG_STATE 0x23 ++#define FSCSCY_REG_WDOG_CONTROL 0x21 ++ ++/* ++** Fan definitions ++** ++** _RPMMIN: Minimum speed. Can be set via interface, but only for three of the fans ++** FAN1_RPMMIN is wired to Fan 0 (CPU Fans) ++** FAN4_RPMMIN is wired to Fan 2 (PS Fans ??) ++** FAN5_RPMMIN is wired to Fan 3 (AUX Fans ??) ++** _ACT: Actual Fan Speed ++** _STATE: Fan status register ++** _RIPPLE: Fan speed multiplier ++*/ ++ ++/* fan 0 */ ++#define FSCSCY_REG_FAN0_RPMMIN 0x65 ++#define FSCSCY_REG_FAN0_ACT 0x6b ++#define FSCSCY_REG_FAN0_STATE 0x62 ++#define FSCSCY_REG_FAN0_RIPPLE 0x6f ++ ++/* fan 1 */ ++#define FSCSCY_REG_FAN1_RPMMIN FSCSCY_REG_FAN0_RPMMIN ++#define FSCSCY_REG_FAN1_ACT 0x6c ++#define FSCSCY_REG_FAN1_STATE 0x61 ++#define FSCSCY_REG_FAN1_RIPPLE 0x6f ++ ++/* fan 2 */ ++#define FSCSCY_REG_FAN2_RPMMIN 0x55 ++#define FSCSCY_REG_FAN2_ACT 0x0e ++#define FSCSCY_REG_FAN2_STATE 0x0d ++#define FSCSCY_REG_FAN2_RIPPLE 0x0f ++ ++/* fan 3 */ ++#define FSCSCY_REG_FAN3_RPMMIN 0xa5 ++#define FSCSCY_REG_FAN3_ACT 0xab ++#define FSCSCY_REG_FAN3_STATE 0xa2 ++#define FSCSCY_REG_FAN3_RIPPLE 0xaf ++ ++/* fan 4 */ ++#define FSCSCY_REG_FAN4_RPMMIN FSCSCY_REG_FAN2_RPMMIN ++#define FSCSCY_REG_FAN4_ACT 0x5c ++#define FSCSCY_REG_FAN4_STATE 0x52 ++#define FSCSCY_REG_FAN4_RIPPLE 0x0f ++ ++/* fan 5 */ ++#define FSCSCY_REG_FAN5_RPMMIN FSCSCY_REG_FAN3_RPMMIN ++#define FSCSCY_REG_FAN5_ACT 0xbb ++#define FSCSCY_REG_FAN5_STATE 0xb2 ++#define FSCSCY_REG_FAN5_RIPPLE 0xbf ++ ++/* voltage supervision */ ++#define FSCSCY_REG_VOLT_12 0x45 ++#define FSCSCY_REG_VOLT_5 0x42 ++#define FSCSCY_REG_VOLT_BATT 0x48 ++ ++/* temperatures */ ++/* sensor 0 */ ++#define FSCSCY_REG_TEMP0_ACT 0x64 ++#define FSCSCY_REG_TEMP0_STATE 0x71 ++#define FSCSCY_REG_TEMP0_LIM 0x76 ++ ++/* sensor 1 */ ++#define FSCSCY_REG_TEMP1_ACT 0xD0 ++#define FSCSCY_REG_TEMP1_STATE 0xD1 ++#define FSCSCY_REG_TEMP1_LIM 0xD6 ++ ++/* sensor 2 */ ++#define FSCSCY_REG_TEMP2_ACT 0x32 ++#define FSCSCY_REG_TEMP2_STATE 0x81 ++#define FSCSCY_REG_TEMP2_LIM 0x86 ++ ++/* sensor3 */ ++#define FSCSCY_REG_TEMP3_ACT 0x35 ++#define FSCSCY_REG_TEMP3_STATE 0x91 ++#define FSCSCY_REG_TEMP3_LIM 0x96 ++ ++/* PCI Load */ ++#define FSCSCY_REG_PCILOAD 0x1a ++ ++/* Intrusion Sensor */ ++#define FSCSCY_REG_INTR_STATE 0x13 ++#define FSCSCY_REG_INTR_CTRL 0x12 ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val),0,255)) ++#define IN_FROM_REG(val,nr) (val) ++ ++/* Initial limits */ ++ ++/* For each registered FSCSCY, we need to keep some data in memory. That ++ data is pointed to by fscscy_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new fscscy client is ++ allocated. */ ++struct fscscy_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 revision; /* revision of chip */ ++ u8 global_event; /* global event status */ ++ u8 global_control; /* global control register */ ++ u8 watchdog[3]; /* watchdog */ ++ u8 volt[3]; /* 12, 5, battery current */ ++ u8 volt_min[3]; /* minimum voltages over module "lifetime" */ ++ u8 volt_max[3]; /* maximum voltages over module "lifetime" */ ++ u8 temp_act[4]; /* temperature */ ++ u8 temp_status[4]; /* status of temp. sensor */ ++ u8 temp_lim[4]; /* limit temperature of temp. sensor */ ++ u8 temp_min[4]; /* minimum of temp. sensor, this is just calculated by the module */ ++ u8 temp_max[4]; /* maximum of temp. sensor, this is just calculsted by the module */ ++ u8 fan_act[6]; /* fans revolutions per second */ ++ u8 fan_status[6]; /* fan status */ ++ u8 fan_rpmmin[6]; /* fan min value for rps */ ++ u8 fan_ripple[6]; /* divider for rps */ ++ u8 fan_min[6]; /* minimum RPM over module "lifetime" */ ++ u8 fan_max[6]; /* maximum RPM over module "lifetime" */ ++ u8 pciload; /* PCILoad value */ ++ u8 intr_status; /* Intrusion Status */ ++ u8 intr_control; /* Intrusion Control */ ++}; ++ ++ ++static int fscscy_attach_adapter(struct i2c_adapter *adapter); ++static int fscscy_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int fscscy_detach_client(struct i2c_client *client); ++ ++static int fscscy_read_value(struct i2c_client *client, u8 register); ++static int fscscy_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void fscscy_update_client(struct i2c_client *client); ++static void fscscy_init_client(struct i2c_client *client); ++ ++ ++static void fscscy_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void fscscy_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscscy_fan_internal(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results, ++ int nr, int reg_state, int reg_min, int res_ripple); ++static void fscscy_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscscy_volt(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscscy_wdog(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscscy_pciload(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void fscscy_intrusion(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int fscscy_id = 0; ++ ++static struct i2c_driver fscscy_driver = { ++ .owner = THIS_MODULE, ++ .name = "FSCSCY sensor driver", ++ .id = I2C_DRIVERID_FSCSCY, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = fscscy_attach_adapter, ++ .detach_client = fscscy_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define FSCSCY_SYSCTL_VOLT0 1000 /* 12 volt supply */ ++#define FSCSCY_SYSCTL_VOLT1 1001 /* 5 volt supply */ ++#define FSCSCY_SYSCTL_VOLT2 1002 /* batterie voltage*/ ++#define FSCSCY_SYSCTL_FAN0 1101 /* state, min, ripple, actual value fan 0 */ ++#define FSCSCY_SYSCTL_FAN1 1102 /* state, min, ripple, actual value fan 1 */ ++#define FSCSCY_SYSCTL_FAN2 1103 /* state, min, ripple, actual value fan 2 */ ++#define FSCSCY_SYSCTL_FAN3 1104 /* state, min, ripple, actual value fan 3 */ ++#define FSCSCY_SYSCTL_FAN4 1105 /* state, min, ripple, actual value fan 4 */ ++#define FSCSCY_SYSCTL_FAN5 1106 /* state, min, ripple, actual value fan 5 */ ++#define FSCSCY_SYSCTL_TEMP0 1201 /* state and value of sensor 0, cpu die */ ++#define FSCSCY_SYSCTL_TEMP1 1202 /* state and value of sensor 1, motherboard */ ++#define FSCSCY_SYSCTL_TEMP2 1203 /* state and value of sensor 2, chassis */ ++#define FSCSCY_SYSCTL_TEMP3 1204 /* state and value of sensor 3, chassis */ ++#define FSCSCY_SYSCTL_REV 2000 /* Revision */ ++#define FSCSCY_SYSCTL_EVENT 2001 /* global event status */ ++#define FSCSCY_SYSCTL_CONTROL 2002 /* global control byte */ ++#define FSCSCY_SYSCTL_WDOG 2003 /* state, min, ripple, actual value fan 2 */ ++#define FSCSCY_SYSCTL_PCILOAD 2004 /* PCILoad value */ ++#define FSCSCY_SYSCTL_INTRUSION 2005 /* state, control for intrusion sensor */ ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected FSCSCY. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table fscscy_dir_table_template[] = { ++ {FSCSCY_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_in}, ++ {FSCSCY_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_in}, ++ {FSCSCY_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_in}, ++ {FSCSCY_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_temp}, ++ {FSCSCY_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_temp}, ++ {FSCSCY_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_temp}, ++ {FSCSCY_SYSCTL_TEMP3, "temp4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_temp}, ++ {FSCSCY_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_volt}, ++ {FSCSCY_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_volt}, ++ {FSCSCY_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_volt}, ++ {FSCSCY_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_fan}, ++ {FSCSCY_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_fan}, ++ {FSCSCY_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_fan}, ++ {FSCSCY_SYSCTL_FAN3, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_fan}, ++ {FSCSCY_SYSCTL_FAN4, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_fan}, ++ {FSCSCY_SYSCTL_FAN5, "fan6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_fan}, ++ {FSCSCY_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_wdog}, ++ {FSCSCY_SYSCTL_PCILOAD, "pciload", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_pciload}, ++ {FSCSCY_SYSCTL_INTRUSION, "intrusion", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &fscscy_intrusion}, ++ {0} ++}; ++ ++static int fscscy_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, fscscy_detect); ++} ++ ++int fscscy_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct fscscy_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("fscscy.o: fscscy_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access fscscy_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct fscscy_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &fscscy_driver; ++ new_client->flags = 0; ++ ++ /* Do the remaining detection unless force or force_fscscy parameter */ ++ if (kind < 0) { ++ if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_0) != 0x53) ++ goto ERROR1; ++ if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_1) != 0x43) ++ goto ERROR1; ++ if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_2) != 0x59) ++ goto ERROR1; ++ } ++ ++ kind = fscscy; ++ ++ type_name = "fscscy"; ++ client_name = "fsc scylla chip"; ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = fscscy_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ fscscy_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ fscscy_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int fscscy_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct fscscy_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("fscscy.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static int fscscy_read_value(struct i2c_client *client, u8 reg) ++{ ++#ifdef DEBUG ++ printk("fscscy: read reg 0x%02x\n",reg); ++#endif ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int fscscy_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++#ifdef DEBUG ++ printk("fscscy: write reg 0x%02x, val 0x%02x\n",reg, value); ++#endif ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new FSCSCY. It should set limits, etc. */ ++static void fscscy_init_client(struct i2c_client *client) ++{ ++ struct fscscy_data *data = client->data; ++ ++ /* read revision from chip */ ++ data->revision = fscscy_read_value(client,FSCSCY_REG_REVISION); ++ ++ /* Initialize min/max values from chip */ ++ data->fan_min[0] = data->fan_max[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_ACT); ++ data->fan_min[1] = data->fan_max[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_ACT); ++ data->fan_min[2] = data->fan_max[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_ACT); ++ data->fan_min[3] = data->fan_max[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_ACT); ++ data->fan_min[4] = data->fan_max[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_ACT); ++ data->fan_min[4] = data->fan_max[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_ACT); ++ data->temp_min[0] = data->temp_max[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_ACT); ++ data->temp_min[1] = data->temp_max[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_ACT); ++ data->temp_min[2] = data->temp_max[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_ACT); ++ data->temp_min[3] = data->temp_max[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_ACT); ++ data->volt_min[0] = data->volt_max[0] = fscscy_read_value(client, FSCSCY_REG_VOLT_12); ++ data->volt_min[1] = data->volt_max[1] = fscscy_read_value(client, FSCSCY_REG_VOLT_5); ++ data->volt_min[2] = data->volt_max[2] = fscscy_read_value(client, FSCSCY_REG_VOLT_BATT); ++} ++ ++static void fscscy_update_client(struct i2c_client *client) ++{ ++ struct fscscy_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > 2 * HZ) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting fscscy update\n"); ++#endif ++ data->temp_act[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_ACT); ++ if (data->temp_min[0] > data->temp_act[0]) data->temp_min[0] = data->temp_act[0]; ++ if (data->temp_max[0] < data->temp_act[0]) data->temp_max[0] = data->temp_act[0]; ++ data->temp_act[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_ACT); ++ if (data->temp_min[1] > data->temp_act[1]) data->temp_min[1] = data->temp_act[1]; ++ if (data->temp_max[1] < data->temp_act[1]) data->temp_max[1] = data->temp_act[1]; ++ data->temp_act[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_ACT); ++ if (data->temp_min[2] > data->temp_act[2]) data->temp_min[2] = data->temp_act[2]; ++ if (data->temp_max[2] < data->temp_act[2]) data->temp_max[2] = data->temp_act[2]; ++ data->temp_act[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_ACT); ++ if (data->temp_min[3] > data->temp_act[3]) data->temp_min[3] = data->temp_act[3]; ++ if (data->temp_max[3] < data->temp_act[3]) data->temp_max[3] = data->temp_act[3]; ++ data->temp_status[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_STATE); ++ data->temp_status[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_STATE); ++ data->temp_status[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_STATE); ++ data->temp_status[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_STATE); ++ data->temp_lim[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_LIM); ++ data->temp_lim[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_LIM); ++ data->temp_lim[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_LIM); ++ data->temp_lim[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_LIM); ++ ++ data->volt[0] = fscscy_read_value(client, FSCSCY_REG_VOLT_12); ++ if (data->volt_min[0] > data->volt[0]) data->volt_min[0] = data->volt[0]; ++ if (data->volt_max[0] < data->volt[0]) data->volt_max[0] = data->volt[0]; ++ data->volt[1] = fscscy_read_value(client, FSCSCY_REG_VOLT_5); ++ if (data->volt_min[1] > data->volt[1]) data->volt_min[1] = data->volt[1]; ++ if (data->volt_max[1] < data->volt[1]) data->volt_max[1] = data->volt[1]; ++ data->volt[2] = fscscy_read_value(client, FSCSCY_REG_VOLT_BATT); ++ if (data->volt_min[2] > data->volt[2]) data->volt_min[2] = data->volt[2]; ++ if (data->volt_max[2] < data->volt[2]) data->volt_max[2] = data->volt[2]; ++ ++ data->fan_act[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_ACT); ++ if (data->fan_min[0] > data->fan_act[0]) data->fan_min[0] = data->fan_act[0]; ++ if (data->fan_max[0] < data->fan_act[0]) data->fan_max[0] = data->fan_act[0]; ++ data->fan_act[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_ACT); ++ if (data->fan_min[1] > data->fan_act[1]) data->fan_min[1] = data->fan_act[1]; ++ if (data->fan_max[1] < data->fan_act[1]) data->fan_max[1] = data->fan_act[1]; ++ data->fan_act[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_ACT); ++ if (data->fan_min[2] > data->fan_act[2]) data->fan_min[2] = data->fan_act[2]; ++ if (data->fan_max[2] < data->fan_act[2]) data->fan_max[2] = data->fan_act[2]; ++ data->fan_act[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_ACT); ++ if (data->fan_min[3] > data->fan_act[3]) data->fan_min[3] = data->fan_act[3]; ++ if (data->fan_max[3] < data->fan_act[3]) data->fan_max[3] = data->fan_act[3]; ++ data->fan_act[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_ACT); ++ if (data->fan_min[4] > data->fan_act[4]) data->fan_min[4] = data->fan_act[4]; ++ if (data->fan_max[4] < data->fan_act[4]) data->fan_max[4] = data->fan_act[4]; ++ data->fan_act[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_ACT); ++ if (data->fan_min[5] > data->fan_act[5]) data->fan_min[5] = data->fan_act[5]; ++ if (data->fan_max[5] < data->fan_act[5]) data->fan_max[5] = data->fan_act[5]; ++ data->fan_status[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_STATE); ++ data->fan_status[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_STATE); ++ data->fan_status[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_STATE); ++ data->fan_status[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_STATE); ++ data->fan_status[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_STATE); ++ data->fan_status[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_STATE); ++ data->fan_rpmmin[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_RPMMIN); ++ data->fan_rpmmin[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_RPMMIN); ++ data->fan_rpmmin[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_RPMMIN); ++ data->fan_rpmmin[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_RPMMIN); ++ data->fan_rpmmin[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_RPMMIN); ++ data->fan_rpmmin[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_RPMMIN); ++ data->fan_ripple[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_RIPPLE); ++ data->fan_ripple[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_RIPPLE); ++ data->fan_ripple[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_RIPPLE); ++ data->fan_ripple[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_RIPPLE); ++ data->fan_ripple[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_RIPPLE); ++ data->fan_ripple[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_RIPPLE); ++ ++ data->watchdog[0] = fscscy_read_value(client, FSCSCY_REG_WDOG_PRESET); ++ data->watchdog[1] = fscscy_read_value(client, FSCSCY_REG_WDOG_STATE); ++ data->watchdog[2] = fscscy_read_value(client, FSCSCY_REG_WDOG_CONTROL); ++ ++ data->global_event = fscscy_read_value(client, FSCSCY_REG_EVENT_STATE); ++ data->global_control = fscscy_read_value(client, FSCSCY_REG_CONTROL); ++ data->pciload = fscscy_read_value(client, FSCSCY_REG_PCILOAD); ++ data->intr_status = fscscy_read_value(client, FSCSCY_REG_INTR_STATE); ++ data->intr_control = fscscy_read_value(client, FSCSCY_REG_INTR_CTRL); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void fscscy_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscscy_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ switch(ctl_name) { ++ case FSCSCY_SYSCTL_REV: ++ results[0] = data->revision ; ++ break; ++ case FSCSCY_SYSCTL_EVENT: ++ results[0] = data->global_event & 0x9f; /* MKN */ ++ break; ++ case FSCSCY_SYSCTL_CONTROL: ++ results[0] = data->global_control & 0x19; /* MKN */ ++ break; ++ default: ++ printk("fscscy: ctl_name %d not supported\n", ++ ctl_name); ++ *nrels_mag = 0; ++ return; ++ } ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if((ctl_name == FSCSCY_SYSCTL_CONTROL) && (*nrels_mag >= 1)) { ++ data->global_control = (data->global_control & 0x18) | (results[0] & 0x01); /* MKN */ ++ printk("fscscy: writing 0x%02x to global_control\n", ++ data->global_control); ++ fscscy_write_value(client,FSCSCY_REG_CONTROL, ++ data->global_control); ++ } ++ else ++ printk("fscscy: writing to chip not supported\n"); ++ } ++} ++ ++#define TEMP_FROM_REG(val) (val-128) ++ ++ ++void fscscy_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscscy_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ switch(ctl_name) { ++ case FSCSCY_SYSCTL_TEMP0: ++ results[0] = data->temp_status[0] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[0]); ++ results[2] = TEMP_FROM_REG(data->temp_lim[0]); ++ results[3] = TEMP_FROM_REG(data->temp_min[0]); ++ results[4] = TEMP_FROM_REG(data->temp_max[0]); ++ break; ++ case FSCSCY_SYSCTL_TEMP1: ++ results[0] = data->temp_status[1] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[1]); ++ results[2] = TEMP_FROM_REG(data->temp_lim[1]); ++ results[3] = TEMP_FROM_REG(data->temp_min[1]); ++ results[4] = TEMP_FROM_REG(data->temp_max[1]); ++ break; ++ case FSCSCY_SYSCTL_TEMP2: ++ results[0] = data->temp_status[2] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[2]); ++ results[2] = TEMP_FROM_REG(data->temp_lim[2]); ++ results[3] = TEMP_FROM_REG(data->temp_min[2]); ++ results[4] = TEMP_FROM_REG(data->temp_max[2]); ++ break; ++ case FSCSCY_SYSCTL_TEMP3: ++ results[0] = data->temp_status[3] & 0x03; ++ results[1] = TEMP_FROM_REG(data->temp_act[3]); ++ results[2] = TEMP_FROM_REG(data->temp_lim[3]); ++ results[3] = TEMP_FROM_REG(data->temp_min[3]); ++ results[4] = TEMP_FROM_REG(data->temp_max[3]); ++ break; ++ default: ++ printk("fscscy: ctl_name %d not supported\n", ++ ctl_name); ++ *nrels_mag = 0; ++ return; ++ } ++ *nrels_mag = 5; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if(*nrels_mag >= 1) { ++ switch(ctl_name) { ++ case FSCSCY_SYSCTL_TEMP0: ++ data->temp_status[0] = ++ (data->temp_status[0] & ~0x02) ++ | (results[0] & 0x02); ++ printk("fscscy: writing value 0x%02x " ++ "to temp0_status\n", ++ data->temp_status[0]); ++ fscscy_write_value(client, ++ FSCSCY_REG_TEMP0_STATE, ++ data->temp_status[0] & 0x02); ++ break; ++ case FSCSCY_SYSCTL_TEMP1: ++ data->temp_status[1] = (data->temp_status[1] & ~0x02) | (results[0] & 0x02); ++ printk("fscscy: writing value 0x%02x to temp1_status\n", data->temp_status[1]); ++ fscscy_write_value(client,FSCSCY_REG_TEMP1_STATE, ++ data->temp_status[1] & 0x02); ++ break; ++ case FSCSCY_SYSCTL_TEMP2: ++ data->temp_status[2] = (data->temp_status[2] & ~0x02) | (results[0] & 0x02); ++ printk("fscscy: writing value 0x%02x to temp2_status\n", data->temp_status[2]); ++ fscscy_write_value(client,FSCSCY_REG_TEMP2_STATE, ++ data->temp_status[2] & 0x02); ++ break; ++ case FSCSCY_SYSCTL_TEMP3: ++ data->temp_status[3] = (data->temp_status[3] & ~0x02) | (results[0] & 0x02); ++ printk("fscscy: writing value 0x%02x to temp3_status\n", data->temp_status[3]); ++ fscscy_write_value(client,FSCSCY_REG_TEMP3_STATE, ++ data->temp_status[3] & 0x02); ++ break; ++ default: ++ printk("fscscy: ctl_name %d not supported\n",ctl_name); ++ } ++ } ++ else ++ printk("fscscy: writing to chip not supported\n"); ++ } ++} ++ ++#define VOLT_FROM_REG(val,mult) (val*mult/255) ++ ++void fscscy_volt(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscscy_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ switch(ctl_name) { ++ case FSCSCY_SYSCTL_VOLT0: ++ results[0] = VOLT_FROM_REG(data->volt[0],1420); ++ results[1] = VOLT_FROM_REG(data->volt_min[0],1420); ++ results[2] = VOLT_FROM_REG(data->volt_max[0],1420); ++ break; ++ case FSCSCY_SYSCTL_VOLT1: ++ results[0] = VOLT_FROM_REG(data->volt[1],660); ++ results[1] = VOLT_FROM_REG(data->volt_min[1],660); ++ results[2] = VOLT_FROM_REG(data->volt_max[1],660); ++ break; ++ case FSCSCY_SYSCTL_VOLT2: ++ results[0] = VOLT_FROM_REG(data->volt[2],330); ++ results[1] = VOLT_FROM_REG(data->volt_min[2],330); ++ results[2] = VOLT_FROM_REG(data->volt_max[2],330); ++ break; ++ default: ++ printk("fscscy: ctl_name %d not supported\n", ++ ctl_name); ++ *nrels_mag = 0; ++ return; ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ printk("fscscy: writing to chip not supported\n"); ++ } ++} ++ ++void fscscy_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ ++ switch(ctl_name) { ++ case FSCSCY_SYSCTL_FAN0: ++ fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 0,FSCSCY_REG_FAN0_STATE,FSCSCY_REG_FAN0_RPMMIN, ++ FSCSCY_REG_FAN0_RIPPLE); ++ break; ++ case FSCSCY_SYSCTL_FAN1: ++ fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 1,FSCSCY_REG_FAN1_STATE,FSCSCY_REG_FAN1_RPMMIN, ++ FSCSCY_REG_FAN1_RIPPLE); ++ break; ++ case FSCSCY_SYSCTL_FAN2: ++ fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 2,FSCSCY_REG_FAN2_STATE,FSCSCY_REG_FAN2_RPMMIN, ++ FSCSCY_REG_FAN2_RIPPLE); ++ break; ++ case FSCSCY_SYSCTL_FAN3: ++ fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 3,FSCSCY_REG_FAN3_STATE,FSCSCY_REG_FAN3_RPMMIN, ++ FSCSCY_REG_FAN3_RIPPLE); ++ break; ++ case FSCSCY_SYSCTL_FAN4: ++ fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 4,FSCSCY_REG_FAN4_STATE,FSCSCY_REG_FAN4_RPMMIN, ++ FSCSCY_REG_FAN4_RIPPLE); ++ break; ++ case FSCSCY_SYSCTL_FAN5: ++ fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results, ++ 5,FSCSCY_REG_FAN5_STATE,FSCSCY_REG_FAN5_RPMMIN, ++ FSCSCY_REG_FAN5_RIPPLE); ++ break; ++ default: ++ printk("fscscy: illegal fan nr %d\n",ctl_name); ++ } ++} ++ ++#define RPM_FROM_REG(val) (val*60) ++ ++void fscscy_fan_internal(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results, int nr, ++ int reg_state, int reg_min, int reg_ripple ) ++{ ++ struct fscscy_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ results[0] = data->fan_status[nr] & 0x0f; /* MKN */ ++ results[1] = data->fan_rpmmin[nr]; ++ results[2] = data->fan_ripple[nr] & 0x03; ++ results[3] = RPM_FROM_REG(data->fan_act[nr]); ++ results[4] = RPM_FROM_REG(data->fan_min[nr]); ++ results[5] = RPM_FROM_REG(data->fan_max[nr]); ++ *nrels_mag = 6; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if(*nrels_mag >= 1) { ++ data->fan_status[nr] = (data->fan_status[nr] & 0x0b) | (results[0] & 0x04); /* MKN */ ++ printk("fscscy: writing value 0x%02x to fan%d_status\n", ++ data->fan_status[nr],nr); ++ fscscy_write_value(client,reg_state, ++ data->fan_status[nr]); ++ } ++ if(*nrels_mag >= 2) { ++ if((results[1] & 0xff) == 0) { ++ printk("fscscy: fan%d rpmmin 0 not allowed for safety reasons\n",nr); ++ return; ++ } ++ data->fan_rpmmin[nr] = results[1]; ++ printk("fscscy: writing value 0x%02x to fan%d_min\n", ++ data->fan_rpmmin[nr],nr); ++ fscscy_write_value(client,reg_min, ++ data->fan_rpmmin[nr]); ++ } ++ if(*nrels_mag >= 3) { ++ if((results[2] & 0x03) == 0) { ++ printk("fscscy: fan%d ripple 0 is nonsense/not allowed\n",nr); ++ return; ++ } ++ data->fan_ripple[nr] = results[2] & 0x03; ++ printk("fscscy: writing value 0x%02x to fan%d_ripple\n", ++ data->fan_ripple[nr],nr); ++ fscscy_write_value(client,reg_ripple, ++ data->fan_ripple[nr]); ++ } ++ } ++} ++ ++void fscscy_wdog(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscscy_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ results[0] = data->watchdog[0] ; ++ results[1] = data->watchdog[1] & 0x02; ++ results[2] = data->watchdog[2] & 0xb0; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->watchdog[0] = results[0] & 0xff; ++ printk("fscscy: writing value 0x%02x to wdog_preset\n", ++ data->watchdog[0]); ++ fscscy_write_value(client,FSCSCY_REG_WDOG_PRESET, ++ data->watchdog[0]); ++ } ++ if (*nrels_mag >= 2) { ++ data->watchdog[1] = results[1] & 0x02; ++ printk("fscscy: writing value 0x%02x to wdog_state\n", ++ data->watchdog[1]); ++ fscscy_write_value(client,FSCSCY_REG_WDOG_STATE, ++ data->watchdog[1]); ++ } ++ if (*nrels_mag >= 3) { ++ data->watchdog[2] = results[2] & 0xb0; ++ printk("fscscy: writing value 0x%02x to wdog_control\n", ++ data->watchdog[2]); ++ fscscy_write_value(client,FSCSCY_REG_WDOG_CONTROL, ++ data->watchdog[2]); ++ } ++ } ++} ++ ++void fscscy_pciload(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscscy_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ results[0] = data->pciload; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ printk("fscscy: writing PCILOAD to chip not supported\n"); ++ } ++} ++ ++void fscscy_intrusion(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct fscscy_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ fscscy_update_client(client); ++ results[0] = data->intr_control & 0x80; ++ results[1] = data->intr_status & 0xc0; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->intr_control = results[0] & 0x80; ++ printk("fscscy: writing value 0x%02x to intr_control\n", ++ data->intr_control); ++ fscscy_write_value(client,FSCSCY_REG_INTR_CTRL, ++ data->intr_control); ++ } ++ } ++} ++ ++static int __init sm_fscscy_init(void) ++{ ++ printk("fscscy.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&fscscy_driver); ++} ++ ++static void __exit sm_fscscy_exit(void) ++{ ++ i2c_del_driver(&fscscy_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Martin Knoblauch <mkn@teraport.de> based on work (fscpos) from Hermann Jung <hej@odn.de>"); ++MODULE_DESCRIPTION("fujitsu siemens scylla chip driver"); ++ ++module_init(sm_fscscy_init); ++module_exit(sm_fscscy_exit); +--- linux-old/drivers/sensors/gl518sm.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/gl518sm.c Mon Dec 13 20:18:46 2004 +@@ -0,0 +1,989 @@ ++/* ++ gl518sm.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>, ++ Kyösti Mälkki <kmalkki@cc.hut.fi> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#ifdef __SMP__ ++#include <linux/smp_lock.h> ++#endif ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x2c, 0x2d, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_2(gl518sm_r00, gl518sm_r80); ++ ++/* Defining this will enable debug messages for the voltage iteration ++ code used with rev 0 ICs */ ++#undef DEBUG_VIN ++ ++/* Many GL518 constants specified below */ ++ ++/* The GL518 registers */ ++#define GL518_REG_CHIP_ID 0x00 ++#define GL518_REG_REVISION 0x01 ++#define GL518_REG_VENDOR_ID 0x02 ++#define GL518_REG_CONF 0x03 ++#define GL518_REG_TEMP 0x04 ++#define GL518_REG_TEMP_OVER 0x05 ++#define GL518_REG_TEMP_HYST 0x06 ++#define GL518_REG_FAN_COUNT 0x07 ++#define GL518_REG_FAN_LIMIT 0x08 ++#define GL518_REG_VIN1_LIMIT 0x09 ++#define GL518_REG_VIN2_LIMIT 0x0a ++#define GL518_REG_VIN3_LIMIT 0x0b ++#define GL518_REG_VDD_LIMIT 0x0c ++#define GL518_REG_VIN3 0x0d ++#define GL518_REG_MISC 0x0f ++#define GL518_REG_ALARM 0x10 ++#define GL518_REG_MASK 0x11 ++#define GL518_REG_INT 0x12 ++#define GL518_REG_VIN2 0x13 ++#define GL518_REG_VIN1 0x14 ++#define GL518_REG_VDD 0x15 ++ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-5:(val)+5) / 10)+119),\ ++ 0,255)) ++#define TEMP_FROM_REG(val) (((val) - 119) * 10) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((960000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) \ ++ ( (val)==0 ? 0 : (val)==255 ? 0 : (960000/((val)*(div))) ) ++ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*10+8)/19),0,255)) ++#define IN_FROM_REG(val) (((val)*19)/10) ++ ++#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*10+11)/23),0,255)) ++#define VDD_FROM_REG(val) (((val)*23)/10) ++ ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++#define ALARMS_FROM_REG(val) val ++ ++#define BEEP_ENABLE_TO_REG(val) ((val)?0:1) ++#define BEEP_ENABLE_FROM_REG(val) ((val)?0:1) ++ ++#define BEEPS_TO_REG(val) ((val) & 0x7f) ++#define BEEPS_FROM_REG(val) ((val) & 0x7f) ++ ++/* Each client has this additional data */ ++struct gl518_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ ++ int iterate_lock; ++ int quit_thread; ++ struct task_struct *thread; ++ wait_queue_head_t wq; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ unsigned long last_updated_v00; ++ /* In jiffies (used only by rev00 chips) */ ++ ++ u8 voltage[4]; /* Register values; [0] = VDD */ ++ u8 voltage_min[4]; /* Register values; [0] = VDD */ ++ u8 voltage_max[4]; /* Register values; [0] = VDD */ ++ u8 iter_voltage[4]; /* Register values; [0] = VDD */ ++ u8 fan[2]; ++ u8 fan_min[2]; ++ u8 temp; /* Register values */ ++ u8 temp_over; /* Register values */ ++ u8 temp_hyst; /* Register values */ ++ u8 alarms, beeps; /* Register value */ ++ u8 alarm_mask; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u8 beep_enable; /* Boolean */ ++ u8 iterate; /* Voltage iteration mode */ ++}; ++ ++static int gl518_attach_adapter(struct i2c_adapter *adapter); ++static int gl518_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void gl518_init_client(struct i2c_client *client); ++static int gl518_detach_client(struct i2c_client *client); ++ ++static int gl518_read_value(struct i2c_client *client, u8 reg); ++static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value); ++static void gl518_update_client(struct i2c_client *client); ++ ++static void gl518_update_client_rev00(struct i2c_client *client); ++static void gl518_update_iterate(struct i2c_client *client); ++ ++static void gl518_vin(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_beep(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_fan1off(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl518_iterate(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver gl518_driver = { ++ .owner = THIS_MODULE, ++ .name = "GL518SM sensor chip driver", ++ .id = I2C_DRIVERID_GL518, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = gl518_attach_adapter, ++ .detach_client = gl518_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define GL518_SYSCTL_VDD 1000 /* Volts * 100 */ ++#define GL518_SYSCTL_VIN1 1001 ++#define GL518_SYSCTL_VIN2 1002 ++#define GL518_SYSCTL_VIN3 1003 ++#define GL518_SYSCTL_FAN1 1101 /* RPM */ ++#define GL518_SYSCTL_FAN2 1102 ++#define GL518_SYSCTL_TEMP 1200 /* Degrees Celcius * 10 */ ++#define GL518_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define GL518_SYSCTL_ALARMS 2001 /* bitvector */ ++#define GL518_SYSCTL_BEEP 2002 /* bitvector */ ++#define GL518_SYSCTL_FAN1OFF 2003 ++#define GL518_SYSCTL_ITERATE 2004 ++ ++#define GL518_ALARM_VDD 0x01 ++#define GL518_ALARM_VIN1 0x02 ++#define GL518_ALARM_VIN2 0x04 ++#define GL518_ALARM_VIN3 0x08 ++#define GL518_ALARM_TEMP 0x10 ++#define GL518_ALARM_FAN1 0x20 ++#define GL518_ALARM_FAN2 0x40 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected GL518. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table gl518_dir_table_template[] = { ++ {GL518_SYSCTL_VIN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_vin}, ++ {GL518_SYSCTL_VIN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_vin}, ++ {GL518_SYSCTL_VIN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_vin}, ++ {GL518_SYSCTL_VDD, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_vin}, ++ {GL518_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_fan}, ++ {GL518_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_fan}, ++ {GL518_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_temp}, ++ {GL518_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_fan_div}, ++ {GL518_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_alarms}, ++ {GL518_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_beep}, ++ {GL518_SYSCTL_FAN1OFF, "fan1off", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_fan1off}, ++ {GL518_SYSCTL_ITERATE, "iterate", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl518_iterate}, ++ {0} ++}; ++ ++/* I choose here for semi-static GL518SM allocation. Complete dynamic ++ allocation could also be used; the code needed for this would probably ++ take more memory than the datastructure takes now. */ ++#define MAX_GL518_NR 4 ++static struct i2c_client *gl518_list[MAX_GL518_NR]; ++ ++static int gl518_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, gl518_detect); ++} ++ ++static int gl518_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct gl518_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("gl518sm.o: gl518_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access gl518_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct gl518_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &gl518_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if ( ++ (gl518_read_value(new_client, GL518_REG_CHIP_ID) != ++ 0x80) ++ || (gl518_read_value(new_client, GL518_REG_CONF) & ++ 0x80)) goto ERROR1; ++ } ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ i = gl518_read_value(new_client, GL518_REG_REVISION); ++ if (i == 0x00) ++ kind = gl518sm_r00; ++ else if (i == 0x80) ++ kind = gl518sm_r80; ++ else { ++ if (kind == 0) ++ printk ++ ("gl518sm.o: Ignoring 'force' parameter for unknown chip at " ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ goto ERROR1; ++ } ++ } ++ ++ type_name = "gl518sm"; ++ if (kind == gl518sm_r00) { ++ client_name = "GL518SM Revision 0x00 chip"; ++ } else if (kind == gl518sm_r80) { ++ client_name = "GL518SM Revision 0x80 chip"; ++ } else { ++#ifdef DEBUG ++ printk("gl518sm.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ for (i = 0; i < MAX_GL518_NR; i++) ++ if (!gl518_list[i]) ++ break; ++ if (i == MAX_GL518_NR) { ++ printk ++ ("gl518sm.o: No empty slots left, recompile and heighten " ++ "MAX_GL518_NR!\n"); ++ err = -ENOMEM; ++ goto ERROR2; ++ } ++ gl518_list[i] = new_client; ++ new_client->id = i; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, ++ gl518_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the GL518SM chip */ ++ if (kind == gl518sm_r00) ++ data->iterate = 0; ++ else ++ data->iterate = 3; ++ data->iterate_lock = 0; ++ data->quit_thread = 0; ++ data->thread = NULL; ++ data->alarm_mask = 0xff; ++ data->voltage[0]=data->voltage[1]=data->voltage[2]=0; ++ gl518_init_client((struct i2c_client *) new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ for (i = 0; i < MAX_GL518_NR; i++) ++ if (new_client == gl518_list[i]) ++ gl518_list[i] = NULL; ++ ERROR2: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++ ++/* Called when we have found a new GL518SM. It should set limits, etc. */ ++static void gl518_init_client(struct i2c_client *client) ++{ ++ /* Power-on defaults (bit 7=1) */ ++ gl518_write_value(client, GL518_REG_CONF, 0x80); ++ ++ /* No noisy output (bit 2=1), Comparator mode (bit 3=0), two fans (bit4=0), ++ standby mode (bit6=0) */ ++ gl518_write_value(client, GL518_REG_CONF, 0x04); ++ ++ /* Never interrupts */ ++ gl518_write_value(client, GL518_REG_MASK, 0x00); ++ ++ /* Clear status register (bit 5=1), start (bit6=1) */ ++ gl518_write_value(client, GL518_REG_CONF, 0x24); ++ gl518_write_value(client, GL518_REG_CONF, 0x44); ++} ++ ++static int gl518_detach_client(struct i2c_client *client) ++{ ++ int err, i; ++ struct gl518_data *data = client->data; ++ ++ i2c_deregister_entry(((struct gl518_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("gl518sm.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ for (i = 0; i < MAX_GL518_NR; i++) ++ if (client == gl518_list[i]) ++ break; ++ if ((i == MAX_GL518_NR)) { ++ printk("gl518sm.o: Client to detach not found.\n"); ++ return -ENOENT; ++ } ++ gl518_list[i] = NULL; ++ ++ if (data->thread) { ++ data->quit_thread = 1; ++ wake_up_interruptible(&data->wq); ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* Registers 0x07 to 0x0c are word-sized, others are byte-sized ++ GL518 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int gl518_read_value(struct i2c_client *client, u8 reg) ++{ ++ if ((reg >= 0x07) && (reg <= 0x0c)) ++ return swab16(i2c_smbus_read_word_data(client, reg)); ++ else ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* Registers 0x07 to 0x0c are word-sized, others are byte-sized ++ GL518 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if ((reg >= 0x07) && (reg <= 0x0c)) ++ return i2c_smbus_write_word_data(client, reg, swab16(value)); ++ else ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void gl518_update_client(struct i2c_client *client) ++{ ++ struct gl518_data *data = client->data; ++ int val; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting gl518 update\n"); ++#endif ++ ++ data->alarms = gl518_read_value(client, GL518_REG_INT); ++ data->beeps = gl518_read_value(client, GL518_REG_ALARM); ++ ++ val = gl518_read_value(client, GL518_REG_VDD_LIMIT); ++ data->voltage_min[0] = val & 0xff; ++ data->voltage_max[0] = (val >> 8) & 0xff; ++ val = gl518_read_value(client, GL518_REG_VIN1_LIMIT); ++ data->voltage_min[1] = val & 0xff; ++ data->voltage_max[1] = (val >> 8) & 0xff; ++ val = gl518_read_value(client, GL518_REG_VIN2_LIMIT); ++ data->voltage_min[2] = val & 0xff; ++ data->voltage_max[2] = (val >> 8) & 0xff; ++ val = gl518_read_value(client, GL518_REG_VIN3_LIMIT); ++ data->voltage_min[3] = val & 0xff; ++ data->voltage_max[3] = (val >> 8) & 0xff; ++ ++ val = gl518_read_value(client, GL518_REG_FAN_COUNT); ++ data->fan[0] = (val >> 8) & 0xff; ++ data->fan[1] = val & 0xff; ++ ++ val = gl518_read_value(client, GL518_REG_FAN_LIMIT); ++ data->fan_min[0] = (val >> 8) & 0xff; ++ data->fan_min[1] = val & 0xff; ++ ++ data->temp = gl518_read_value(client, GL518_REG_TEMP); ++ data->temp_over = ++ gl518_read_value(client, GL518_REG_TEMP_OVER); ++ data->temp_hyst = ++ gl518_read_value(client, GL518_REG_TEMP_HYST); ++ ++ val = gl518_read_value(client, GL518_REG_MISC); ++ data->fan_div[0] = (val >> 6) & 0x03; ++ data->fan_div[1] = (val >> 4) & 0x03; ++ ++ data->alarms &= data->alarm_mask; ++ ++ val = gl518_read_value(client, GL518_REG_CONF); ++ data->beep_enable = (val >> 2) & 1; ++ ++#ifndef DEBUG_VIN ++ if (data->type != gl518sm_r00) { ++ data->voltage[0] = ++ gl518_read_value(client, GL518_REG_VDD); ++ data->voltage[1] = ++ gl518_read_value(client, GL518_REG_VIN1); ++ data->voltage[2] = ++ gl518_read_value(client, GL518_REG_VIN2); ++ data->voltage[3] = ++ gl518_read_value(client, GL518_REG_VIN3); ++ } else ++ gl518_update_client_rev00(client); ++#else ++ gl518_update_client_rev00(client); ++#endif ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++/* Here we decide how to run the iteration code. ++ When called, we trigger the iteration and report the last ++ measured voltage. No delay for user apps */ ++static void gl518_update_client_rev00(struct i2c_client *client) ++{ ++ struct gl518_data *data = client->data; ++ int i; ++ ++ if (data->iterate == 1) { /* 10 sec delay */ ++ /* as that update is slow, we consider the data valid for 30 seconds */ ++ if ( ++ ((jiffies - data->last_updated_v00 > 30 * HZ) ++ || (data->alarms & 7) ++ || (!data->valid)) && (!data->iterate_lock)) { ++ data->iterate_lock = 1; ++ gl518_update_iterate(client); ++ data->iterate_lock = 0; ++ } ++ for (i = 0; i < 4; i++) ++ data->voltage[i] = data->iter_voltage[i]; ++ } else if (data->iterate == 2) { /* show results of last iteration */ ++ for (i = 0; i < 4; i++) ++ data->voltage[i] = data->iter_voltage[i]; ++ wake_up_interruptible(&data->wq); ++ } else { /* no iteration */ ++ data->voltage[3] = ++ gl518_read_value(client, GL518_REG_VIN3); ++ } ++} ++ ++static int gl518_update_thread(void *c) ++{ ++ struct i2c_client *client = c; ++ struct gl518_data *data = client->data; ++ ++#ifdef __SMP__ ++ lock_kernel(); ++#endif ++ exit_mm(current); ++ current->session = 1; ++ current->pgrp = 1; ++ sigfillset(¤t->blocked); ++ current->fs->umask = 0; ++ strcpy(current->comm, "gl518sm"); ++ ++ init_waitqueue_head(&(data->wq)); ++ data->thread = current; ++ ++#ifdef __SMP__ ++ unlock_kernel(); ++#endif ++ ++ for (;;) { ++ if (!data->iterate_lock) { ++ data->iterate_lock = 1; ++ gl518_update_iterate(client); ++ data->iterate_lock = 0; ++ } ++ ++ if ((data->quit_thread) || signal_pending(current)) ++ break; ++ interruptible_sleep_on(&data->wq); ++ } ++ ++ data->thread = NULL; ++ data->quit_thread = 0; ++ return 0; ++} ++ ++/* This updates vdd, vin1, vin2 values by doing slow and multiple ++ comparisons for the GL518SM rev 00 that lacks support for direct ++ reading of these values. Values are kept in iter_voltage */ ++ ++static void gl518_update_iterate(struct i2c_client *client) ++{ ++ struct gl518_data *data = client->data; ++ int i, j, loop_more = 1, min[3], max[3], delta[3]; ++ int alarm, beeps, irqs; ++ ++#define VIN_REG(c) c==0?GL518_REG_VDD_LIMIT:\ ++ c==1?GL518_REG_VIN1_LIMIT:\ ++ GL518_REG_VIN2_LIMIT ++ ++ /* disable beeps & irqs for vin0-2 */ ++ beeps = gl518_read_value(client, GL518_REG_ALARM); ++ irqs = gl518_read_value(client, GL518_REG_MASK); ++ gl518_write_value(client, GL518_REG_ALARM, beeps & ~0x7); ++ gl518_write_value(client, GL518_REG_MASK, irqs & ~0x7); ++ ++ alarm = data->alarms; ++ ++ for (i = 0; i < 3; i++) { ++ if (alarm & (1 << i)) { ++ min[i] = 0; ++ max[i] = 127; ++ } else { ++ min[i] = data->voltage_min[i]; ++ max[i] = ++ (data->voltage_max[i] + ++ data->voltage_min[i]) / 2; ++ } ++ delta[i] = (max[i] - min[i]) / 2; ++ } ++ ++ for (j = 0; (j < 10 && loop_more); j++) { ++ ++ for (i = 0; i < 3; i++) ++ gl518_write_value(client, VIN_REG(i), ++ max[i] << 8 | min[i]); ++ ++ if ((data->thread) && ++ ((data->quit_thread) || signal_pending(current))) ++ goto finish; ++ ++ /* we wait now 1.5 seconds before comparing */ ++ current->state = TASK_INTERRUPTIBLE; ++ schedule_timeout(HZ + HZ / 2); ++ ++ alarm = gl518_read_value(client, GL518_REG_INT); ++ ++#ifdef DEBUG_VIN ++ printk("gl518sm: iteration %2d: %4d%c %4d%c %4d%c\n", j, ++ max[0], (alarm & 1) ? '!' : ' ', ++ max[1], (alarm & 2) ? '!' : ' ', ++ max[2], (alarm & 4) ? '!' : ' '); ++#endif ++ ++ for (loop_more = 0, i = 0; i < 3; i++) { ++ if (alarm & (1 << i)) ++ max[i] += delta[i]; ++ else ++ max[i] -= delta[i]; ++ ++ if (delta[i]) ++ loop_more++; ++ delta[i] >>= 1; ++ } ++ ++ } ++ ++ for (i = 0; i < 3; i++) ++ if (alarm & (1 << i)) ++ max[i]++; ++ ++#ifdef DEBUG_VIN ++ printk("gl518sm: final :%5d %5d %5d\n", max[0], max[1], ++ max[2]); ++ printk("gl518sm: meter :%5d %5d %5d\n", data->voltage[0], ++ data->voltage[1], data->voltage[2]); ++#endif ++ ++ /* update values, including vin3 */ ++ for (i = 0; i < 3; i++) { ++ data->iter_voltage[i] = max[i]; ++ } ++ data->iter_voltage[3] = gl518_read_value(client, GL518_REG_VIN3); ++ data->last_updated_v00 = jiffies; ++ ++ finish: ++ ++ /* reset values */ ++ for (i = 0; i < 3; i++) { ++ gl518_write_value(client, VIN_REG(i), ++ data->voltage_max[i] << 8 | data-> ++ voltage_min[i]); ++ } ++ ++ gl518_write_value(client, GL518_REG_ALARM, beeps); ++ gl518_write_value(client, GL518_REG_MASK, irqs); ++ ++#undef VIN_REG ++} ++ ++void gl518_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl518_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over = TEMP_TO_REG(results[0]); ++ gl518_write_value(client, GL518_REG_TEMP_OVER, ++ data->temp_over); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ gl518_write_value(client, GL518_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++void gl518_vin(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ int nr = ctl_name - GL518_SYSCTL_VDD; ++ int regnr, old = 0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl518_update_client(client); ++ results[0] = nr ? IN_FROM_REG(data->voltage_min[nr]) : ++ VDD_FROM_REG(data->voltage_min[nr]); ++ results[1] = nr ? IN_FROM_REG(data->voltage_max[nr]) : ++ VDD_FROM_REG(data->voltage_max[nr]); ++ results[2] = nr ? IN_FROM_REG(data->voltage[nr]) : ++ VDD_FROM_REG(data->voltage[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ regnr = ++ nr == 0 ? GL518_REG_VDD_LIMIT : nr == ++ 1 ? GL518_REG_VIN1_LIMIT : nr == ++ 2 ? GL518_REG_VIN2_LIMIT : GL518_REG_VIN3_LIMIT; ++ if (*nrels_mag == 1) ++ old = gl518_read_value(client, regnr) & 0xff00; ++ if (*nrels_mag >= 2) { ++ data->voltage_max[nr] = ++ nr ? IN_TO_REG(results[1]) : ++ VDD_TO_REG(results[1]); ++ old = data->voltage_max[nr] << 8; ++ } ++ if (*nrels_mag >= 1) { ++ data->voltage_min[nr] = ++ nr ? IN_TO_REG(results[0]) : ++ VDD_TO_REG(results[0]); ++ old |= data->voltage_min[nr]; ++ gl518_write_value(client, regnr, old); ++ } ++ } ++} ++ ++ ++void gl518_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ int nr = ctl_name - GL518_SYSCTL_FAN1; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl518_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr], ++ DIV_FROM_REG(data->fan_div[nr])); ++ results[1] = ++ FAN_FROM_REG(data->fan[nr], ++ DIV_FROM_REG(data->fan_div[nr])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr] = FAN_TO_REG(results[0], ++ DIV_FROM_REG(data-> ++ fan_div ++ [nr])); ++ old = ++ gl518_read_value(client, GL518_REG_FAN_LIMIT); ++ ++ if (nr == 0) { ++ old = ++ (old & 0x00ff) | (data-> ++ fan_min[0] << 8); ++ if (results[0] == 0) ++ data->alarm_mask &= ~0x20; ++ else ++ data->alarm_mask |= 0x20; ++ } else { ++ old = (old & 0xff00) | data->fan_min[1]; ++ if (results[0] == 0) ++ data->alarm_mask &= ~0x40; ++ else ++ data->alarm_mask |= 0x40; ++ } ++ gl518_write_value(client, GL518_REG_FAN_LIMIT, ++ old); ++ } ++ } ++} ++ ++ ++void gl518_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl518_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void gl518_beep(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl518_update_client(client); ++ results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); ++ results[1] = BEEPS_FROM_REG(data->beeps); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); ++ gl518_write_value(client, GL518_REG_CONF, ++ (gl518_read_value(client, ++ GL518_REG_CONF) ++ & 0xfb) | (data-> ++ beep_enable << 2)); ++ } ++ if (*nrels_mag >= 2) { ++ data->beeps = ++ BEEPS_TO_REG(results[1]) & data->alarm_mask; ++ gl518_write_value(client, GL518_REG_ALARM, ++ data->beeps); ++ } ++ } ++} ++ ++ ++void gl518_fan_div(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ int old; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl518_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = gl518_read_value(client, GL518_REG_MISC); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0xcf) | (data->fan_div[1] << 4); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0x3f) | (data->fan_div[0] << 6); ++ } ++ gl518_write_value(client, GL518_REG_MISC, old); ++ } ++} ++ ++void gl518_fan1off(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int old; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = ++ ((gl518_read_value(client, GL518_REG_MISC) & 0x08) != ++ 0); ++ results[1] = ++ ((gl518_read_value(client, GL518_REG_CONF) & 0x10) != ++ 0); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ old = ++ gl518_read_value(client, ++ GL518_REG_MISC) & 0xf7; ++ if (results[0]) ++ old |= 0x08; ++ gl518_write_value(client, GL518_REG_MISC, old); ++ } ++ if (*nrels_mag >= 2) { ++ old = ++ gl518_read_value(client, ++ GL518_REG_CONF) & 0xef; ++ if (results[1]) ++ old |= 0x10; ++ gl518_write_value(client, GL518_REG_CONF, old); ++ } ++ } ++} ++ ++void gl518_iterate(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl518_data *data = client->data; ++ int i; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->iterate; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE && ++ data->type == gl518sm_r00 ) { ++ if ((*nrels_mag >= 1) && (data->iterate != results[0])) { ++ data->iterate = results[0]; ++ for (i = 0; i < 4; i++) { ++ data->voltage[i] = 0; ++ data->iter_voltage[i] = 0; ++ } ++ data->valid = 0; ++ ++ if ((data->iterate != 2) && (data->thread)) { ++ data->quit_thread = 1; ++ wake_up_interruptible(&data->wq); ++ } else if ((data->iterate == 2) && (!data->thread)) { ++ init_waitqueue_head(&(data->wq)); ++ kernel_thread(gl518_update_thread, ++ (void *) client, 0); ++ } ++ } ++ } ++} ++ ++static int __init sm_gl518sm_init(void) ++{ ++ printk("gl518sm.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&gl518_driver); ++} ++ ++static void __exit sm_gl518sm_exit(void) ++{ ++ i2c_del_driver(&gl518_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Kyösti Mälkki <kmalkki@cc.hut.fi>"); ++MODULE_DESCRIPTION("GL518SM driver"); ++ ++module_init(sm_gl518sm_init); ++module_exit(sm_gl518sm_exit); +--- linux-old/drivers/sensors/gl520sm.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/gl520sm.c Mon Dec 13 20:18:47 2004 +@@ -0,0 +1,809 @@ ++/* ++ gl520sm.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>, ++ Kyösti Mälkki <kmalkki@cc.hut.fi> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x2c, 0x2d, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(gl520sm); ++ ++/* Many GL520 constants specified below ++One of the inputs can be configured as either temp or voltage. ++That's why _TEMP2 and _VIN4 access the same register ++*/ ++ ++/* The GL520 registers */ ++#define GL520_REG_CHIP_ID 0x00 ++#define GL520_REG_REVISION 0x01 ++#define GL520_REG_VID 0x02 ++#define GL520_REG_CONF 0x03 ++#define GL520_REG_TEMP1 0x04 ++#define GL520_REG_TEMP1_OVER 0x05 ++#define GL520_REG_TEMP1_HYST 0x06 ++#define GL520_REG_FAN_COUNT 0x07 ++#define GL520_REG_FAN_LIMIT 0x08 ++#define GL520_REG_VIN1_LIMIT 0x09 ++#define GL520_REG_VIN2_LIMIT 0x0a ++#define GL520_REG_VIN3_LIMIT 0x0b ++#define GL520_REG_VDD_LIMIT 0x0c ++#define GL520_REG_VIN3 0x0d ++#define GL520_REG_VIN4 0x0e ++#define GL520_REG_TEMP2 0x0e ++#define GL520_REG_MISC 0x0f ++#define GL520_REG_ALARM 0x10 ++#define GL520_REG_MASK 0x11 ++#define GL520_REG_INT 0x12 ++#define GL520_REG_VIN2 0x13 ++#define GL520_REG_VIN1 0x14 ++#define GL520_REG_VDD 0x15 ++#define GL520_REG_TEMP2_OVER 0x17 ++#define GL520_REG_VIN4_MAX 0x17 ++#define GL520_REG_TEMP2_HYST 0x18 ++#define GL520_REG_VIN4_MIN 0x18 ++ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-5:(val)+5) / 10)+130),\ ++ 0,255)) ++#define TEMP_FROM_REG(val) (((val) - 130) * 10) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((960000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) \ ++ ( (val)==0 ? 0 : (val)==255 ? 0 : (960000/((val)*(div))) ) ++ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*10+8)/19),0,255)) ++#define IN_FROM_REG(val) (((val)*19)/10) ++ ++#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*10+11)/23),0,255)) ++#define VDD_FROM_REG(val) (((val)*23)/10) ++ ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++#define ALARMS_FROM_REG(val) val ++ ++#define BEEP_ENABLE_TO_REG(val) ((val)?0:1) ++#define BEEP_ENABLE_FROM_REG(val) ((val)?0:1) ++ ++#define BEEPS_TO_REG(val) (val) ++#define BEEPS_FROM_REG(val) (val) ++ ++#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ ++ 205-(val)*5) ++ ++/* Each client has this additional data */ ++struct gl520_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 voltage[5]; /* Register values; [0] = VDD */ ++ u8 voltage_min[5]; /* Register values; [0] = VDD */ ++ u8 voltage_max[5]; /* Register values; [0] = VDD */ ++ u8 fan[2]; ++ u8 fan_min[2]; ++ u8 temp[2]; /* Register values */ ++ u8 temp_over[2]; /* Register values */ ++ u8 temp_hyst[2]; /* Register values */ ++ u8 alarms, beeps, vid; /* Register value */ ++ u8 alarm_mask; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u8 beep_enable; /* Boolean */ ++ u8 two_temps; /* Boolean */ ++}; ++ ++static int gl520_attach_adapter(struct i2c_adapter *adapter); ++static int gl520_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void gl520_init_client(struct i2c_client *client); ++static int gl520_detach_client(struct i2c_client *client); ++ ++static int gl520_read_value(struct i2c_client *client, u8 reg); ++static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value); ++static void gl520_update_client(struct i2c_client *client); ++ ++static void gl520_vin(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_beep(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_fan1off(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void gl520_config(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver gl520_driver = { ++ .owner = THIS_MODULE, ++ .name = "GL520SM sensor chip driver", ++ .id = I2C_DRIVERID_GL520, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = gl520_attach_adapter, ++ .detach_client = gl520_detach_client, ++}; ++/* -- SENSORS SYSCTL START -- */ ++ ++#define GL520_SYSCTL_VDD 1000 /* Volts * 100 */ ++#define GL520_SYSCTL_VIN1 1001 ++#define GL520_SYSCTL_VIN2 1002 ++#define GL520_SYSCTL_VIN3 1003 ++#define GL520_SYSCTL_VIN4 1004 ++#define GL520_SYSCTL_FAN1 1101 /* RPM */ ++#define GL520_SYSCTL_FAN2 1102 ++#define GL520_SYSCTL_TEMP1 1200 /* Degrees Celcius * 10 */ ++#define GL520_SYSCTL_TEMP2 1201 /* Degrees Celcius * 10 */ ++#define GL520_SYSCTL_VID 1300 ++#define GL520_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define GL520_SYSCTL_ALARMS 2001 /* bitvector */ ++#define GL520_SYSCTL_BEEP 2002 /* bitvector */ ++#define GL520_SYSCTL_FAN1OFF 2003 ++#define GL520_SYSCTL_CONFIG 2004 ++ ++#define GL520_ALARM_VDD 0x01 ++#define GL520_ALARM_VIN1 0x02 ++#define GL520_ALARM_VIN2 0x04 ++#define GL520_ALARM_VIN3 0x08 ++#define GL520_ALARM_TEMP1 0x10 ++#define GL520_ALARM_FAN1 0x20 ++#define GL520_ALARM_FAN2 0x40 ++#define GL520_ALARM_TEMP2 0x80 ++#define GL520_ALARM_VIN4 0x80 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected GL520. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table gl520_dir_table_template[] = { ++ {GL520_SYSCTL_VIN1, "vin1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_vin}, ++ {GL520_SYSCTL_VIN2, "vin2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_vin}, ++ {GL520_SYSCTL_VIN3, "vin3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_vin}, ++ {GL520_SYSCTL_VIN4, "vin4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_vin}, ++ {GL520_SYSCTL_VDD, "vdd", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_vin}, ++ {GL520_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_vid}, ++ {GL520_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_fan}, ++ {GL520_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_fan}, ++ {GL520_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_temp}, ++ {GL520_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_temp}, ++ {GL520_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_fan_div}, ++ {GL520_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_alarms}, ++ {GL520_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_beep}, ++ {GL520_SYSCTL_FAN1OFF, "fan1off", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_fan1off}, ++ {GL520_SYSCTL_CONFIG, "config", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &gl520_config}, ++ {0} ++}; ++ ++static int gl520_id = 0; ++ ++static int gl520_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, gl520_detect); ++} ++ ++static int gl520_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct gl520_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ char client_name[32]; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("gl520sm.o: gl520_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access gl520_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct gl520_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &gl520_driver; ++ new_client->flags = 0; ++ ++ /* Determine the chip type. */ ++ ++ if (gl520_read_value(new_client, GL520_REG_CHIP_ID) != 0x20) { ++ printk ++ ("gl520sm.o: Ignoring 'force' parameter for unknown chip at " ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ goto ERROR1; ++ } else { ++ kind = gl520sm; ++ } ++ ++ i = gl520_read_value(new_client, GL520_REG_REVISION); ++ if (kind == gl520sm) { ++ type_name = "gl520sm"; ++ sprintf(client_name, "GL520SM Revision %02x chip", i); ++ } else { ++#ifdef DEBUG ++ printk("gl520sm.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = gl520_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ gl520_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the GL520SM chip */ ++ data->alarm_mask = 0xff; ++ gl520_init_client(new_client); ++ if (data->two_temps) ++ data->voltage_max[4] = data->voltage_min[4] = ++ data->voltage[4] = 0; ++ else ++ data->temp_hyst[1] = data->temp_over[1] = ++ data->temp[1] = 0; ++ ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++ ++/* Called when we have found a new GL520SM. */ ++static void gl520_init_client(struct i2c_client *client) ++{ ++ struct gl520_data *data = (struct gl520_data *)(client->data); ++ u8 oldconf, conf; ++ ++ conf = oldconf = gl520_read_value(client, GL520_REG_CONF); ++ data->two_temps = !(conf & 0x10); ++ ++ /* If IRQ# is disabled, we can safely force comparator mode */ ++ if (!(conf & 0x20)) ++ conf &= 0xf7; ++ ++ /* Enable monitoring if needed */ ++ conf |= 0x40; ++ ++ if (conf != oldconf) ++ gl520_write_value(client, GL520_REG_CONF, conf); ++} ++ ++static int gl520_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct gl520_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("gl520sm.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* Registers 0x07 to 0x0c are word-sized, others are byte-sized ++ GL520 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int gl520_read_value(struct i2c_client *client, u8 reg) ++{ ++ if ((reg >= 0x07) && (reg <= 0x0c)) ++ return swab16(i2c_smbus_read_word_data(client, reg)); ++ else ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* Registers 0x07 to 0x0c are word-sized, others are byte-sized ++ GL520 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if ((reg >= 0x07) && (reg <= 0x0c)) ++ return i2c_smbus_write_word_data(client, reg, swab16(value)); ++ else ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void gl520_update_client(struct i2c_client *client) ++{ ++ struct gl520_data *data = client->data; ++ int val; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting gl520 update\n"); ++#endif ++ ++ data->alarms = gl520_read_value(client, GL520_REG_INT); ++ data->beeps = gl520_read_value(client, GL520_REG_ALARM); ++ data->vid = gl520_read_value(client, GL520_REG_VID) & 0x1f; ++ ++ val = gl520_read_value(client, GL520_REG_VDD_LIMIT); ++ data->voltage_min[0] = val & 0xff; ++ data->voltage_max[0] = (val >> 8) & 0xff; ++ val = gl520_read_value(client, GL520_REG_VIN1_LIMIT); ++ data->voltage_min[1] = val & 0xff; ++ data->voltage_max[1] = (val >> 8) & 0xff; ++ val = gl520_read_value(client, GL520_REG_VIN2_LIMIT); ++ data->voltage_min[2] = val & 0xff; ++ data->voltage_max[2] = (val >> 8) & 0xff; ++ val = gl520_read_value(client, GL520_REG_VIN3_LIMIT); ++ data->voltage_min[3] = val & 0xff; ++ data->voltage_max[3] = (val >> 8) & 0xff; ++ ++ val = gl520_read_value(client, GL520_REG_FAN_COUNT); ++ data->fan[0] = (val >> 8) & 0xff; ++ data->fan[1] = val & 0xff; ++ ++ val = gl520_read_value(client, GL520_REG_FAN_LIMIT); ++ data->fan_min[0] = (val >> 8) & 0xff; ++ data->fan_min[1] = val & 0xff; ++ ++ data->temp[0] = gl520_read_value(client, GL520_REG_TEMP1); ++ data->temp_over[0] = ++ gl520_read_value(client, GL520_REG_TEMP1_OVER); ++ data->temp_hyst[0] = ++ gl520_read_value(client, GL520_REG_TEMP1_HYST); ++ ++ val = gl520_read_value(client, GL520_REG_MISC); ++ data->fan_div[0] = (val >> 6) & 0x03; ++ data->fan_div[1] = (val >> 4) & 0x03; ++ ++ data->alarms &= data->alarm_mask; ++ ++ val = gl520_read_value(client, GL520_REG_CONF); ++ data->beep_enable = (val >> 2) & 1; ++ ++ data->voltage[0] = gl520_read_value(client, GL520_REG_VDD); ++ data->voltage[1] = ++ gl520_read_value(client, GL520_REG_VIN1); ++ data->voltage[2] = ++ gl520_read_value(client, GL520_REG_VIN2); ++ data->voltage[3] = ++ gl520_read_value(client, GL520_REG_VIN3); ++ ++ /* Temp1 and Vin4 are the same input */ ++ if (data->two_temps) { ++ data->temp[1] = ++ gl520_read_value(client, GL520_REG_TEMP2); ++ data->temp_over[1] = ++ gl520_read_value(client, GL520_REG_TEMP2_OVER); ++ data->temp_hyst[1] = ++ gl520_read_value(client, GL520_REG_TEMP2_HYST); ++ } else { ++ data->voltage[4] = ++ gl520_read_value(client, GL520_REG_VIN4); ++ data->voltage_min[4] = ++ gl520_read_value(client, GL520_REG_VIN4_MIN); ++ data->voltage_max[4] = ++ gl520_read_value(client, GL520_REG_VIN4_MAX); ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++void gl520_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ int nr = ctl_name - GL520_SYSCTL_TEMP1; ++ int regnr; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); ++ results[2] = TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if ((nr == 1) && (!data->two_temps)) ++ return; ++ regnr = ++ nr == 0 ? GL520_REG_TEMP1_OVER : GL520_REG_TEMP2_OVER; ++ if (*nrels_mag >= 1) { ++ data->temp_over[nr] = TEMP_TO_REG(results[0]); ++ gl520_write_value(client, regnr, ++ data->temp_over[nr]); ++ } ++ regnr = ++ nr == 0 ? GL520_REG_TEMP1_HYST : GL520_REG_TEMP2_HYST; ++ if (*nrels_mag >= 2) { ++ data->temp_hyst[nr] = TEMP_TO_REG(results[1]); ++ gl520_write_value(client, regnr, ++ data->temp_hyst[nr]); ++ } ++ } ++} ++ ++void gl520_vin(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ int nr = ctl_name - GL520_SYSCTL_VDD; ++ int regnr, old = 0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = nr ? IN_FROM_REG(data->voltage_min[nr]) : ++ VDD_FROM_REG(data->voltage_min[nr]); ++ results[1] = nr ? IN_FROM_REG(data->voltage_max[nr]) : ++ VDD_FROM_REG(data->voltage_max[nr]); ++ results[2] = nr ? IN_FROM_REG(data->voltage[nr]) : ++ VDD_FROM_REG(data->voltage[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (nr != 4) { ++ regnr = ++ nr == 0 ? GL520_REG_VDD_LIMIT : nr == ++ 1 ? GL520_REG_VIN1_LIMIT : nr == ++ 2 ? GL520_REG_VIN2_LIMIT : ++ GL520_REG_VIN3_LIMIT; ++ if (*nrels_mag == 1) ++ old = ++ gl520_read_value(client, ++ regnr) & 0xff00; ++ if (*nrels_mag >= 2) { ++ data->voltage_max[nr] = ++ nr ? IN_TO_REG(results[1]) : ++ VDD_TO_REG(results[1]); ++ old = data->voltage_max[nr] << 8; ++ } ++ if (*nrels_mag >= 1) { ++ data->voltage_min[nr] = ++ nr ? IN_TO_REG(results[0]) : ++ VDD_TO_REG(results[0]); ++ old |= data->voltage_min[nr]; ++ gl520_write_value(client, regnr, old); ++ } ++ } else if (!data->two_temps) { ++ if (*nrels_mag == 1) ++ gl520_write_value(client, ++ GL520_REG_VIN4_MIN, ++ IN_TO_REG(results[0])); ++ if (*nrels_mag >= 2) ++ gl520_write_value(client, ++ GL520_REG_VIN4_MAX, ++ IN_TO_REG(results[1])); ++ } ++ } ++} ++ ++ ++void gl520_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ int nr = ctl_name - GL520_SYSCTL_FAN1; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr], ++ DIV_FROM_REG(data->fan_div[nr])); ++ results[1] = ++ FAN_FROM_REG(data->fan[nr], ++ DIV_FROM_REG(data->fan_div[nr])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr] = FAN_TO_REG(results[0], ++ DIV_FROM_REG(data-> ++ fan_div ++ [nr])); ++ old = ++ gl520_read_value(client, GL520_REG_FAN_LIMIT); ++ ++ if (nr == 0) { ++ old = ++ (old & 0x00ff) | (data-> ++ fan_min[nr] << 8); ++ if (results[0] == 0) ++ data->alarm_mask &= ~0x20; ++ else ++ data->alarm_mask |= 0x20; ++ } else { ++ old = (old & 0xff00) | data->fan_min[nr]; ++ if (results[0] == 0) ++ data->alarm_mask &= ~0x40; ++ else ++ data->alarm_mask |= 0x40; ++ } ++ gl520_write_value(client, GL520_REG_FAN_LIMIT, ++ old); ++ } ++ } ++} ++ ++ ++void gl520_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void gl520_beep(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); ++ results[1] = BEEPS_FROM_REG(data->beeps); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); ++ gl520_write_value(client, GL520_REG_CONF, ++ (gl520_read_value(client, ++ GL520_REG_CONF) ++ & 0xfb) | (data-> ++ beep_enable << 2)); ++ } ++ if (*nrels_mag >= 2) { ++ data->beeps = ++ BEEPS_TO_REG(results[1]) & data->alarm_mask; ++ gl520_write_value(client, GL520_REG_ALARM, ++ data->beeps); ++ } ++ } ++} ++ ++ ++void gl520_fan_div(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ int old; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = gl520_read_value(client, GL520_REG_MISC); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0xcf) | (data->fan_div[1] << 4); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0x3f) | (data->fan_div[0] << 6); ++ } ++ gl520_write_value(client, GL520_REG_MISC, old); ++ } ++} ++ ++void gl520_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ gl520_update_client(client); ++ results[0] = VID_FROM_REG(data->vid); ++ *nrels_mag = 1; ++ } ++} ++ ++void gl520_fan1off(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int old; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = ++ ((gl520_read_value(client, GL520_REG_MISC) & 0x04) != ++ 0); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ old = ++ gl520_read_value(client, ++ GL520_REG_MISC) & 0xfb; ++ if (results[0]) ++ old |= 0x04; ++ gl520_write_value(client, GL520_REG_MISC, old); ++ } ++ } ++} ++ ++void gl520_config(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct gl520_data *data = client->data; ++ int old; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = ++ ((gl520_read_value(client, GL520_REG_CONF) & 0x10) == ++ 0); ++ data->two_temps = results[0]; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ old = ++ gl520_read_value(client, ++ GL520_REG_CONF) & 0xef; ++ if (!results[1]) { ++ old |= 0x10; ++ data->two_temps = 0; ++ data->temp_hyst[1] = data->temp_over[1] = ++ data->temp[1] = 0; ++ } else { ++ data->two_temps = 1; ++ data->voltage_max[4] = data->voltage_min[4] = ++ data->voltage[4] = 0; ++ } ++ gl520_write_value(client, GL520_REG_CONF, old); ++ } ++ } ++} ++ ++static int __init sm_gl520sm_init(void) ++{ ++ printk("gl520sm.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&gl520_driver); ++} ++ ++static void __exit sm_gl520sm_exit(void) ++{ ++ i2c_del_driver(&gl520_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Kyösti Mälkki <kmalkki@cc.hut.fi>"); ++MODULE_DESCRIPTION("GL520SM driver"); ++ ++module_init(sm_gl520sm_init); ++module_exit(sm_gl520sm_exit); +--- linux-old/drivers/sensors/it87.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/it87.c Mon Dec 13 20:18:47 2004 +@@ -0,0 +1,1128 @@ ++/* ++ it87.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring. ++ ++ Supports: IT8705F Super I/O chip w/LPC interface ++ IT8712F Super I/O chup w/LPC interface & SMbus ++ Sis950 A clone of the IT8705F ++ ++ Copyright (c) 2001 Chris Gauthron <chrisg@0-in.com> ++ Largely inspired by lm78.c of the same package ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ djg@pdp8.net David Gesswein 7/18/01 ++ Modified to fix bug with not all alarms enabled. ++ Added ability to read battery voltage and select temperature sensor ++ type at module load time. ++*/ ++ ++/* ++ michael.hufer@gmx.de Michael Hufer 09/07/03 ++ Modified configure (enable/disable) chip reset at module load time. ++ Added ability to read and set fan pwm registers and the smart ++ guardian (sg) features of the chip. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0290, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_4(it87, it8705, it8712, sis950); ++ ++ ++#define REG 0x2e /* The register to read/write */ ++#define DEV 0x07 /* Register: Logical device select */ ++#define VAL 0x2f /* The value to read/write */ ++#define PME 0x04 /* The device with the fan registers in it */ ++#define DEVID 0x20 /* Register: Device ID */ ++ ++static inline void ++superio_outb(int reg, int val) ++{ ++ outb(reg, REG); ++ outb(val, VAL); ++} ++ ++static inline int ++superio_inb(int reg) ++{ ++ outb(reg, REG); ++ return inb(VAL); ++} ++ ++static inline void ++superio_select(void) ++{ ++ outb(DEV, REG); ++ outb(PME, VAL); ++} ++ ++static inline void ++superio_enter(void) ++{ ++ outb(0x87, REG); ++ outb(0x01, REG); ++ outb(0x55, REG); ++ outb(0x55, REG); ++} ++ ++static inline void ++superio_exit(void) ++{ ++ outb(0x02, REG); ++ outb(0x02, VAL); ++} ++ ++/* just IT8712F for now - this should be extended to support the other ++ chips as well */ ++#define IT87_DEVID_MATCH(id) ((id) == 0x8712) ++ ++#define IT87_ACT_REG 0x30 ++#define IT87_BASE_REG 0x60 ++ ++/* Update battery voltage after every reading if true */ ++static int update_vbat = 0; ++ ++/* Reset the registers on init */ ++static int reset = 0; ++ ++/* Many IT87 constants specified below */ ++ ++/* Length of ISA address segment */ ++#define IT87_EXTENT 8 ++ ++/* Where are the ISA address/data registers relative to the base address */ ++#define IT87_ADDR_REG_OFFSET 5 ++#define IT87_DATA_REG_OFFSET 6 ++ ++/*----- The IT87 registers -----*/ ++ ++#define IT87_REG_CONFIG 0x00 ++ ++#define IT87_REG_ALARM1 0x01 ++#define IT87_REG_ALARM2 0x02 ++#define IT87_REG_ALARM3 0x03 ++ ++#define IT87_REG_VID 0x0a ++#define IT87_REG_FAN_DIV 0x0b ++ ++#define IT87_REG_FAN(nr) (0x0c + (nr)) ++#define IT87_REG_FAN_MIN(nr) (0x0f + (nr)) ++#define IT87_REG_FAN_CTRL 0x13 ++ ++/* pwm and smart guardian registers */ ++ ++#define IT87_REG_FAN_ONOFF 0x14 ++#define IT87_REG_PWM(nr) (0x14 + (nr)) ++#define IT87_REG_SG_TL_OFF(nr) (0x58 + (nr)*8) ++#define IT87_REG_SG_TL_LOW(nr) (0x59 + (nr)*8) ++#define IT87_REG_SG_TL_MED(nr) (0x5a + (nr)*8) ++#define IT87_REG_SG_TL_HI(nr) (0x5b + (nr)*8) ++#define IT87_REG_SG_TL_OVR(nr) (0x5c + (nr)*8) ++#define IT87_REG_SG_PWM_LOW(nr) (0x5d + (nr)*8) ++#define IT87_REG_SG_PWM_MED(nr) (0x5e + (nr)*8) ++#define IT87_REG_SG_PWM_HI(nr) (0x5f + (nr)*8) ++ ++/* Monitors: 9 voltage (0 to 7, battery), 3 temp (1 to 3), 3 fan (1 to 3) */ ++ ++#define IT87_REG_VIN(nr) (0x20 + (nr)) ++#define IT87_REG_TEMP(nr) (0x28 + (nr)) ++ ++#define IT87_REG_VIN_MAX(nr) (0x30 + (nr) * 2) ++#define IT87_REG_VIN_MIN(nr) (0x31 + (nr) * 2) ++#define IT87_REG_TEMP_HIGH(nr) (0x3e + (nr) * 2) ++#define IT87_REG_TEMP_LOW(nr) (0x3f + (nr) * 2) ++ ++#define IT87_REG_I2C_ADDR 0x48 ++ ++#define IT87_REG_VIN_ENABLE 0x50 ++#define IT87_REG_TEMP_ENABLE 0x51 ++ ++#define IT87_REG_CHIPID 0x58 ++ ++/* sensor pin types */ ++#define UNUSED 0 ++#define THERMISTOR 2 ++#define PIIDIODE 3 ++ ++/* Conversions. Limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) ++#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) ++ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),-127,127)) ++#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) ++ ++#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ ++ 205-(val)*5) ++#define ALARMS_FROM_REG(val) (val) ++ ++extern inline u8 DIV_TO_REG(long val) ++{ ++ u8 i; ++ for( i = 0; i <= 7; i++ ) ++ { ++ if( val>>i == 1 ) ++ return i; ++ } ++ return 1; ++} ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++/* For each registered IT87, we need to keep some data in memory. That ++ data is pointed to by it87_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new it87 client is ++ allocated. */ ++struct it87_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[9]; /* Register value */ ++ u8 in_max[9]; /* Register value */ ++ u8 in_min[9]; /* Register value */ ++ u8 fan[3]; /* Register value */ ++ u8 fan_min[3]; /* Register value */ ++ u8 temp[3]; /* Register value */ ++ u8 temp_high[3]; /* Register value */ ++ u8 temp_low[3]; /* Register value */ ++ u8 fan_div[3]; /* Register encoding, shifted right */ ++ u8 vid; /* Register encoding, combined */ ++ u32 alarms; /* Register encoding, combined */ ++ u8 pwm[3]; /* Register value */ ++ u8 fan_ctl[2]; /* Register encoding */ ++ u8 sg_tl[3][5]; /* Register value */ ++ u8 sg_pwm[3][3]; /* Register value */ ++ u8 sens[3]; /* 2 = Thermistor, ++ 3 = PII/Celeron diode */ ++}; ++ ++ ++static int it87_attach_adapter(struct i2c_adapter *adapter); ++static int it87_find(int *address); ++static int it87_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int it87_detach_client(struct i2c_client *client); ++ ++static int it87_read_value(struct i2c_client *client, u8 register); ++static int it87_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void it87_update_client(struct i2c_client *client); ++static void it87_init_client(struct i2c_client *client); ++ ++ ++static void it87_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void it87_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_fan_ctl(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_sgpwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_sgtl(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void it87_sens(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static struct i2c_driver it87_driver = { ++ .owner = THIS_MODULE, ++ .name = "IT87xx sensor driver", ++ .id = I2C_DRIVERID_IT87, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = it87_attach_adapter, ++ .detach_client = it87_detach_client, ++}; ++ ++static int it87_id = 0; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define IT87_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define IT87_SYSCTL_IN1 1001 ++#define IT87_SYSCTL_IN2 1002 ++#define IT87_SYSCTL_IN3 1003 ++#define IT87_SYSCTL_IN4 1004 ++#define IT87_SYSCTL_IN5 1005 ++#define IT87_SYSCTL_IN6 1006 ++#define IT87_SYSCTL_IN7 1007 ++#define IT87_SYSCTL_IN8 1008 ++#define IT87_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define IT87_SYSCTL_FAN2 1102 ++#define IT87_SYSCTL_FAN3 1103 ++#define IT87_SYSCTL_TEMP1 1200 /* Degrees Celcius * 10 */ ++#define IT87_SYSCTL_TEMP2 1201 /* Degrees Celcius * 10 */ ++#define IT87_SYSCTL_TEMP3 1202 /* Degrees Celcius * 10 */ ++#define IT87_SYSCTL_VID 1300 /* Volts * 100 */ ++#define IT87_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define IT87_SYSCTL_ALARMS 2004 /* bitvector */ ++ ++#define IT87_SYSCTL_PWM1 1401 ++#define IT87_SYSCTL_PWM2 1402 ++#define IT87_SYSCTL_PWM3 1403 ++#define IT87_SYSCTL_FAN_CTL 1501 ++#define IT87_SYSCTL_FAN_ON_OFF 1502 ++#define IT87_SYSCTL_SENS1 1601 /* 1, 2, or Beta (3000-5000) */ ++#define IT87_SYSCTL_SENS2 1602 ++#define IT87_SYSCTL_SENS3 1603 ++ ++#define IT87_ALARM_IN0 0x000100 ++#define IT87_ALARM_IN1 0x000200 ++#define IT87_ALARM_IN2 0x000400 ++#define IT87_ALARM_IN3 0x000800 ++#define IT87_ALARM_IN4 0x001000 ++#define IT87_ALARM_IN5 0x002000 ++#define IT87_ALARM_IN6 0x004000 ++#define IT87_ALARM_IN7 0x008000 ++#define IT87_ALARM_FAN1 0x0001 ++#define IT87_ALARM_FAN2 0x0002 ++#define IT87_ALARM_FAN3 0x0004 ++#define IT87_ALARM_TEMP1 0x00010000 ++#define IT87_ALARM_TEMP2 0x00020000 ++#define IT87_ALARM_TEMP3 0x00040000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected IT87. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table it87_dir_table_template[] = { ++ {IT87_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_in}, ++ {IT87_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_fan}, ++ {IT87_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_fan}, ++ {IT87_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_fan}, ++ {IT87_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_temp}, ++ {IT87_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_temp}, ++ {IT87_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_temp}, ++ {IT87_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_vid}, ++ {IT87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_fan_div}, ++ {IT87_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_alarms}, ++ {IT87_SYSCTL_FAN_CTL, "fan_ctl", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_fan_ctl}, ++ {IT87_SYSCTL_FAN_ON_OFF, "fan_on_off", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_fan_ctl}, ++ {IT87_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_pwm}, ++ {IT87_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_pwm}, ++ {IT87_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_pwm}, ++ {IT87_SYSCTL_PWM1, "sg_pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sgpwm}, ++ {IT87_SYSCTL_PWM2, "sg_pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sgpwm}, ++ {IT87_SYSCTL_PWM3, "sg_pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sgpwm}, ++ {IT87_SYSCTL_PWM1, "sg_tl1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sgtl}, ++ {IT87_SYSCTL_PWM2, "sg_tl2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sgtl}, ++ {IT87_SYSCTL_PWM3, "sg_tl3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sgtl}, ++ {IT87_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sens}, ++ {IT87_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sens}, ++ {IT87_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &it87_sens}, ++ {0} ++}; ++ ++ ++/* This function is called when: ++ * it87_driver is inserted (when this module is loaded), for each ++ available adapter ++ * when a new adapter is inserted (and it87_driver is still present) */ ++static int it87_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, it87_detect); ++} ++ ++static int it87_find(int *address) ++{ ++ u16 val; ++ ++ superio_enter(); ++ val = (superio_inb(DEVID) << 8) | ++ superio_inb(DEVID + 1); ++ if (!IT87_DEVID_MATCH(val)) { ++ superio_exit(); ++ return -ENODEV; ++ } ++ ++ superio_select(); ++ val = (superio_inb(IT87_BASE_REG) << 8) | ++ superio_inb(IT87_BASE_REG + 1); ++ superio_exit(); ++ *address = val & ~(IT87_EXTENT - 1); ++ if (*address == 0) { ++ return -ENODEV; ++ } ++ return 0; ++} ++ ++/* This function is called by i2c_detect */ ++int it87_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct it87_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ int is_isa = i2c_is_isa_adapter(adapter); ++ ++ if (!is_isa ++ && !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ return 0; ++ ++ if (is_isa ++ && check_region(address, IT87_EXTENT)) ++ return 0; ++ ++ /* Probe whether there is anything available on this address. Already ++ done for SMBus clients */ ++ if (is_isa && kind < 0) { ++#define REALLY_SLOW_IO ++ /* We need the timeouts for at least some IT87-like chips. ++ But only if we read 'undefined' registers. */ ++ i = inb_p(address + 1); ++ if (inb_p(address + 2) != i ++ || inb_p(address + 3) != i ++ || inb_p(address + 7) != i) ++ return -ENODEV; ++#undef REALLY_SLOW_IO ++ ++ /* Let's just hope nothing breaks here */ ++ i = inb_p(address + 5) & 0x7f; ++ outb_p(~i & 0x7f, address + 5); ++ if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) { ++ outb_p(i, address + 5); ++ return -ENODEV; ++ } ++ } ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access it87_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct it87_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ if (is_isa) ++ init_MUTEX(&data->lock); ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &it87_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80) ++ || (!is_isa ++ && it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) { ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ } ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ i = it87_read_value(new_client, IT87_REG_CHIPID); ++ if (i == 0x90) { ++ kind = it87; ++ } ++ else { ++ if (kind == 0) ++ printk ++ ("it87.o: Ignoring 'force' parameter for unknown chip at " ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ } ++ ++ if (kind == it87) { ++ type_name = "it87"; ++ client_name = "IT87 chip"; ++ } /* else if (kind == it8712) { ++ type_name = "it8712"; ++ client_name = "IT87-J chip"; ++ } */ else { ++#ifdef DEBUG ++ printk("it87.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Reserve the ISA region */ ++ if (is_isa) ++ request_region(address, IT87_EXTENT, type_name); ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = it87_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ it87_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the IT87 chip */ ++ it87_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ if (is_isa) ++ release_region(address, IT87_EXTENT); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int it87_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct it87_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("it87.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ if(i2c_is_isa_client(client)) ++ release_region(client->addr, IT87_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* The SMBus locks itself, but ISA access must be locked explicitely! ++ We don't want to lock the whole ISA bus, so we lock each client ++ separately. ++ We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the IT87 access and should not be necessary. ++ There are some ugly typecasts here, but the good new is - they should ++ nowhere else be necessary! */ ++static int it87_read_value(struct i2c_client *client, u8 reg) ++{ ++ int res; ++ if (i2c_is_isa_client(client)) { ++ down(&(((struct it87_data *) (client->data))->lock)); ++ outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); ++ res = inb_p(client->addr + IT87_DATA_REG_OFFSET); ++ up(&(((struct it87_data *) (client->data))->lock)); ++ return res; ++ } else ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* The SMBus locks itself, but ISA access muse be locked explicitely! ++ We don't want to lock the whole ISA bus, so we lock each client ++ separately. ++ We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the IT87 access and should not be necessary. ++ There are some ugly typecasts here, but the good new is - they should ++ nowhere else be necessary! */ ++static int it87_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ if (i2c_is_isa_client(client)) { ++ down(&(((struct it87_data *) (client->data))->lock)); ++ outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); ++ outb_p(value, client->addr + IT87_DATA_REG_OFFSET); ++ up(&(((struct it87_data *) (client->data))->lock)); ++ return 0; ++ } else ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new IT87. */ ++static void it87_init_client(struct i2c_client *client) ++{ ++ int tmp; ++ ++ if (reset) { ++ /* Reset all except Watchdog values and last conversion values ++ This sets fan-divs to 2, among others */ ++ it87_write_value(client, IT87_REG_CONFIG, 0x80); ++ } ++ ++ /* Check if temperature channnels are reset manually or by some reason */ ++ tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); ++ if ((tmp & 0x3f) == 0) { ++ /* Temp1,Temp3=thermistor; Temp2=thermal diode */ ++ tmp = (tmp & 0xc0) | 0x2a; ++ it87_write_value(client, IT87_REG_TEMP_ENABLE, tmp); ++ } ++ ++ /* Check if voltage monitors are reset manually or by some reason */ ++ tmp = it87_read_value(client, IT87_REG_VIN_ENABLE); ++ if ((tmp & 0xff) == 0) { ++ /* Enable all voltage monitors */ ++ it87_write_value(client, IT87_REG_VIN_ENABLE, 0xff); ++ } ++ ++ /* Check if tachometers are reset manually or by some reason */ ++ tmp = it87_read_value(client, IT87_REG_FAN_CTRL); ++ if ((tmp & 0x70) == 0) { ++ /* Enable all fan tachometers */ ++ tmp = (tmp & 0x8f) | 0x70; ++ it87_write_value(client, IT87_REG_FAN_CTRL, tmp); ++ } ++ ++ /* Start monitoring */ ++ it87_write_value(client, IT87_REG_CONFIG, ++ (it87_read_value(client, IT87_REG_CONFIG) & 0x36) ++ | (update_vbat ? 0x41 : 0x01)); ++} ++ ++static void it87_update_client(struct i2c_client *client) ++{ ++ struct it87_data *data = client->data; ++ int i, tmp, tmp2; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++ if (update_vbat) { ++ /* Cleared after each update, so reenable. Value ++ returned by this read will be previous value */ ++ it87_write_value(client, IT87_REG_CONFIG, ++ it87_read_value(client, IT87_REG_CONFIG) | 0x40); ++ } ++ for (i = 0; i <= 7; i++) { ++ data->in[i] = ++ it87_read_value(client, IT87_REG_VIN(i)); ++ data->in_min[i] = ++ it87_read_value(client, IT87_REG_VIN_MIN(i)); ++ data->in_max[i] = ++ it87_read_value(client, IT87_REG_VIN_MAX(i)); ++ } ++ data->in[8] = ++ it87_read_value(client, IT87_REG_VIN(8)); ++ /* VBAT sensor doesn't have limit registers, set ++ to min and max value */ ++ data->in_min[8] = 0; ++ data->in_max[8] = 255; ++ ++ for (i = 1; i <= 3; i++) { ++ data->fan[i - 1] = ++ it87_read_value(client, IT87_REG_FAN(i)); ++ data->fan_min[i - 1] = ++ it87_read_value(client, IT87_REG_FAN_MIN(i)); ++ } ++ for (i = 1; i <= 3; i++) { ++ data->temp[i - 1] = ++ it87_read_value(client, IT87_REG_TEMP(i)); ++ data->temp_high[i - 1] = ++ it87_read_value(client, IT87_REG_TEMP_HIGH(i)); ++ data->temp_low[i - 1] = ++ it87_read_value(client, IT87_REG_TEMP_LOW(i)); ++ } ++ ++ /* The 8705 does not have VID capability */ ++ /*if (data->type == it8712) { ++ data->vid = it87_read_value(client, IT87_REG_VID); ++ data->vid &= 0x1f; ++ } ++ else */ { ++ data->vid = 0x1f; ++ } ++ ++ i = it87_read_value(client, IT87_REG_FAN_DIV); ++ data->fan_div[0] = i & 0x07; ++ data->fan_div[1] = (i >> 3) & 0x07; ++ data->fan_div[2] = ( (i&0x40)==0x40 ? 3 : 1 ); ++ ++ for( i = 1; i <= 3; i++ ) { ++ data->pwm[i-1] = it87_read_value(client, IT87_REG_PWM(i)); ++ data->sg_tl[i-1][0] = it87_read_value(client, IT87_REG_SG_TL_OFF(i)); ++ data->sg_tl[i-1][1] = it87_read_value(client, IT87_REG_SG_TL_LOW(i)); ++ data->sg_tl[i-1][2] = it87_read_value(client, IT87_REG_SG_TL_MED(i)); ++ data->sg_tl[i-1][3] = it87_read_value(client, IT87_REG_SG_TL_HI(i)); ++ data->sg_tl[i-1][4] = it87_read_value(client, IT87_REG_SG_TL_OVR(i)); ++ data->sg_pwm[i-1][0] = it87_read_value(client, IT87_REG_SG_PWM_LOW(i)); ++ data->sg_pwm[i-1][1] = it87_read_value(client, IT87_REG_SG_PWM_MED(i)); ++ data->sg_pwm[i-1][2] = it87_read_value(client, IT87_REG_SG_PWM_HI(i)); ++ } ++ data->alarms = ++ it87_read_value(client, IT87_REG_ALARM1) | ++ (it87_read_value(client, IT87_REG_ALARM2) << 8) | ++ (it87_read_value(client, IT87_REG_ALARM3) << 16); ++ data->fan_ctl[0] = it87_read_value(client, IT87_REG_FAN_CTRL); ++ data->fan_ctl[1] = it87_read_value(client, IT87_REG_FAN_ONOFF); ++ ++ tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); ++ for(i = 0; i < 3; i++) { ++ tmp2 = (tmp >> i) & 0x09; ++ if(tmp2 == 0x01) ++ data->sens[i] = PIIDIODE; ++ else if(tmp2 == 0x08) ++ data->sens[i] = THERMISTOR; ++ else ++ data->sens[i] = UNUSED; ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ - Each function must return the magnitude (power of 10 to divide the ++ data with) if it is called with operation==SENSORS_PROC_REAL_INFO. ++ - It must put a maximum of *nrels elements in results reflecting the ++ data of this file, and set *nrels to the number it actually put ++ in it, if operation==SENSORS_PROC_REAL_READ. ++ - Finally, it must get upto *nrels elements from results and write them ++ to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void it87_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = ctl_name - IT87_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ it87_write_value(client, IT87_REG_VIN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ it87_write_value(client, IT87_REG_VIN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void it87_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = ctl_name - IT87_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ it87_write_value(client, IT87_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void it87_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = ctl_name - IT87_SYSCTL_TEMP1 + 1; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_high[nr - 1]); ++ results[1] = TEMP_FROM_REG(data->temp_low[nr - 1]); ++ results[2] = TEMP_FROM_REG(data->temp[nr - 1]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_high[nr - 1] = TEMP_TO_REG(results[0]); ++ it87_write_value(client, IT87_REG_TEMP_HIGH(nr), ++ data->temp_high[nr - 1]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_low[nr - 1] = TEMP_TO_REG(results[1]); ++ it87_write_value(client, IT87_REG_TEMP_LOW(nr), ++ data->temp_low[nr - 1]); ++ } ++ } ++} ++ ++void it87_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = ctl_name - IT87_SYSCTL_PWM1 + 1; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = data->pwm[nr - 1]; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->pwm[nr - 1] = results[0]; ++ it87_write_value(client, IT87_REG_PWM(nr), data->pwm[nr - 1]); ++ } ++ } ++} ++ ++void it87_sgpwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = ctl_name - IT87_SYSCTL_PWM1 + 1; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = data->sg_pwm[nr - 1][0]; ++ results[1] = data->sg_pwm[nr - 1][1]; ++ results[2] = data->sg_pwm[nr - 1][2]; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->sg_pwm[nr - 1][0] = results[0]; ++ it87_write_value(client, IT87_REG_SG_PWM_LOW(nr), data->sg_pwm[nr - 1][0]); ++ } ++ if (*nrels_mag >= 2) { ++ data->sg_pwm[nr - 1][1] = results[1]; ++ it87_write_value(client, IT87_REG_SG_PWM_MED(nr), data->sg_pwm[nr - 1][1]); ++ } ++ if (*nrels_mag >= 3) { ++ data->sg_pwm[nr - 1][2] = results[2]; ++ it87_write_value(client, IT87_REG_SG_PWM_HI(nr), data->sg_pwm[nr - 1][2]); ++ } ++ } ++} ++ ++void it87_sgtl(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = ctl_name - IT87_SYSCTL_PWM1 + 1; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = TEMP_FROM_REG(data->sg_tl[nr - 1][0]); ++ results[1] = TEMP_FROM_REG(data->sg_tl[nr - 1][1]); ++ results[2] = TEMP_FROM_REG(data->sg_tl[nr - 1][2]); ++ results[3] = TEMP_FROM_REG(data->sg_tl[nr - 1][3]); ++ results[4] = TEMP_FROM_REG(data->sg_tl[nr - 1][4]); ++ *nrels_mag = 5; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->sg_tl[nr - 1][0] = TEMP_TO_REG(results[0]); ++ it87_write_value(client, IT87_REG_SG_TL_OFF(nr), data->sg_tl[nr - 1][0]); ++ } ++ if (*nrels_mag >= 2) { ++ data->sg_tl[nr - 1][1] = TEMP_TO_REG(results[1]); ++ it87_write_value(client, IT87_REG_SG_TL_LOW(nr), data->sg_tl[nr - 1][1]); ++ } ++ if (*nrels_mag >= 3) { ++ data->sg_tl[nr - 1][2] = TEMP_TO_REG(results[2]); ++ it87_write_value(client, IT87_REG_SG_TL_MED(nr), data->sg_tl[nr - 1][2]); ++ } ++ if (*nrels_mag >= 4) { ++ data->sg_tl[nr - 1][3] = TEMP_TO_REG(results[3]); ++ it87_write_value(client, IT87_REG_SG_TL_HI(nr), data->sg_tl[nr - 1][3]); ++ } ++ if (*nrels_mag >= 5) { ++ data->sg_tl[nr - 1][4] = TEMP_TO_REG(results[4]); ++ it87_write_value(client, IT87_REG_SG_TL_OVR(nr), data->sg_tl[nr - 1][4]); ++ } ++ } ++} ++ ++void it87_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = VID_FROM_REG(data->vid); ++ *nrels_mag = 1; ++ } ++} ++ ++void it87_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void it87_fan_div(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ results[2] = DIV_FROM_REG(data->fan_div[2]);; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = it87_read_value(client, IT87_REG_FAN_DIV); ++ if (*nrels_mag >= 3) { ++ data->fan_div[2] = DIV_TO_REG(results[2]); ++ if( data->fan[2]!=3 ) { ++ data->fan_div[2] = 1; ++ old = (old & 0xbf); ++ } else { ++ old = (old | 0x40); ++ } ++ } ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0xc3) | (data->fan_div[1] << 3); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xf8) | data->fan_div[0]; ++ it87_write_value(client, IT87_REG_FAN_DIV, old); ++ } ++ } ++} ++ ++void it87_fan_ctl(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int index = ctl_name - IT87_SYSCTL_FAN_CTL; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ it87_update_client(client); ++ results[0] = data->fan_ctl[index]; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_ctl[index] = results[0]; ++ if( index == 0 ) ++ it87_write_value(client, IT87_REG_FAN_CTRL, data->fan_ctl[index] ); ++ else ++ it87_write_value(client, IT87_REG_FAN_ONOFF, data->fan_ctl[index] ); ++ } ++ } ++} ++ ++void it87_sens(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct it87_data *data = client->data; ++ int nr = 1 + ctl_name - IT87_SYSCTL_SENS1; ++ u8 tmp, val1, val2; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->sens[nr - 1]; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ val1 = 0x01 << (nr - 1); ++ val2 = 0x08 << (nr - 1); ++ tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); ++ switch (results[0]) { ++ case PIIDIODE: ++ tmp &= ~ val2; ++ tmp |= val1; ++ break; ++ case THERMISTOR: ++ tmp &= ~ val1; ++ tmp |= val2; ++ break; ++ case UNUSED: ++ tmp &= ~ val1; ++ tmp &= ~ val2; ++ break; ++ default: ++ printk(KERN_ERR "it87.o: Invalid sensor type %ld; " ++ "must be 0 (unused), 2 (thermistor) " ++ "or 3 (diode)\n", results[0]); ++ return; ++ } ++ it87_write_value(client, ++ IT87_REG_TEMP_ENABLE, tmp); ++ data->sens[nr - 1] = results[0]; ++ } ++ } ++} ++ ++static int __init sm_it87_init(void) ++{ ++ int addr; ++ ++ printk("it87.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ if (!it87_find(&addr)) { ++ normal_isa[0] = addr; ++ } ++ return i2c_add_driver(&it87_driver); ++} ++ ++static void __exit sm_it87_exit(void) ++{ ++ i2c_del_driver(&it87_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Chris Gauthron <chrisg@0-in.com>"); ++MODULE_DESCRIPTION("IT8705F, IT8712F, Sis950 driver"); ++MODULE_PARM(update_vbat, "i"); ++MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); ++MODULE_PARM(reset, "i"); ++MODULE_PARM_DESC(reset, "Reset the chip's registers, default no"); ++ ++module_init(sm_it87_init); ++module_exit(sm_it87_exit); +--- linux-old/drivers/sensors/lm75.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm75.c Mon Dec 13 20:18:47 2004 +@@ -0,0 +1,331 @@ ++/* ++ lm75.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include "lm75.h" ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(lm75); ++ ++/* Many LM75 constants specified below */ ++ ++/* The LM75 registers */ ++#define LM75_REG_TEMP 0x00 ++#define LM75_REG_CONF 0x01 ++#define LM75_REG_TEMP_HYST 0x02 ++#define LM75_REG_TEMP_OS 0x03 ++ ++/* Each client has this additional data */ ++struct lm75_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u16 temp, temp_os, temp_hyst; /* Register values */ ++}; ++ ++static int lm75_attach_adapter(struct i2c_adapter *adapter); ++static int lm75_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void lm75_init_client(struct i2c_client *client); ++static int lm75_detach_client(struct i2c_client *client); ++ ++static int lm75_read_value(struct i2c_client *client, u8 reg); ++static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value); ++static void lm75_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm75_update_client(struct i2c_client *client); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver lm75_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM75 sensor chip driver", ++ .id = I2C_DRIVERID_LM75, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm75_attach_adapter, ++ .detach_client = lm75_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define LM75_SYSCTL_TEMP 1200 /* Degrees Celcius * 10 */ ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected LM75. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table lm75_dir_table_template[] = { ++ {LM75_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm75_temp}, ++ {0} ++}; ++ ++static int lm75_id = 0; ++ ++static int lm75_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, lm75_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int lm75_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct lm75_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("lm75.o: lm75_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA)) ++ goto error0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access lm75_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct lm75_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto error0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &lm75_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. There is no identification- ++ dedicated register so we have to rely on several tricks: ++ unused bits, registers cycling over 8-address boundaries, ++ addresses 0x04-0x07 returning the last read value. ++ The cycling+unused addresses combination is not tested, ++ since it would significantly slow the detection down and would ++ hardly add any value. */ ++ if (kind < 0) { ++ int cur, conf, hyst, os; ++ ++ /* Unused addresses */ ++ cur = i2c_smbus_read_word_data(new_client, 0); ++ conf = i2c_smbus_read_byte_data(new_client, 1); ++ hyst = i2c_smbus_read_word_data(new_client, 2); ++ if (i2c_smbus_read_word_data(new_client, 4) != hyst ++ || i2c_smbus_read_word_data(new_client, 5) != hyst ++ || i2c_smbus_read_word_data(new_client, 6) != hyst ++ || i2c_smbus_read_word_data(new_client, 7) != hyst) ++ goto error1; ++ os = i2c_smbus_read_word_data(new_client, 3); ++ if (i2c_smbus_read_word_data(new_client, 4) != os ++ || i2c_smbus_read_word_data(new_client, 5) != os ++ || i2c_smbus_read_word_data(new_client, 6) != os ++ || i2c_smbus_read_word_data(new_client, 7) != os) ++ goto error1; ++ ++ /* Unused bits */ ++ if (conf & 0xe0) ++ goto error1; ++ ++ /* Addresses cycling */ ++ for (i = 8; i < 0xff; i += 8) ++ if (i2c_smbus_read_byte_data(new_client, i + 1) != conf ++ || i2c_smbus_read_word_data(new_client, i + 2) != hyst ++ || i2c_smbus_read_word_data(new_client, i + 3) != os) ++ goto error1; ++ } ++ ++ /* Determine the chip type - only one kind supported! */ ++ if (kind <= 0) ++ kind = lm75; ++ ++ if (kind == lm75) { ++ type_name = "lm75"; ++ client_name = "LM75 chip"; ++ } else { ++ pr_debug("lm75.o: Internal error: unknown kind (%d)?!?", kind); ++ goto error1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = lm75_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto error3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ lm75_dir_table_template)) < 0) { ++ err = i; ++ goto error4; ++ } ++ data->sysctl_id = i; ++ ++ lm75_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ error4: ++ i2c_detach_client(new_client); ++ error3: ++ error1: ++ kfree(data); ++ error0: ++ return err; ++} ++ ++static int lm75_detach_client(struct i2c_client *client) ++{ ++ struct lm75_data *data = client->data; ++ ++ i2c_deregister_entry(data->sysctl_id); ++ i2c_detach_client(client); ++ kfree(client->data); ++ return 0; ++} ++ ++/* All registers are word-sized, except for the configuration register. ++ LM75 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int lm75_read_value(struct i2c_client *client, u8 reg) ++{ ++ if (reg == LM75_REG_CONF) ++ return i2c_smbus_read_byte_data(client, reg); ++ else ++ return swab16(i2c_smbus_read_word_data(client, reg)); ++} ++ ++/* All registers are word-sized, except for the configuration register. ++ LM75 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if (reg == LM75_REG_CONF) ++ return i2c_smbus_write_byte_data(client, reg, value); ++ else ++ return i2c_smbus_write_word_data(client, reg, swab16(value)); ++} ++ ++static void lm75_init_client(struct i2c_client *client) ++{ ++ /* Initialize the LM75 chip */ ++ lm75_write_value(client, LM75_REG_CONF, 0); ++} ++ ++static void lm75_update_client(struct i2c_client *client) ++{ ++ struct lm75_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ pr_debug("Starting lm75 update\n"); ++ ++ data->temp = lm75_read_value(client, LM75_REG_TEMP); ++ data->temp_os = lm75_read_value(client, LM75_REG_TEMP_OS); ++ data->temp_hyst = ++ lm75_read_value(client, LM75_REG_TEMP_HYST); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void lm75_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm75_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm75_update_client(client); ++ results[0] = LM75_TEMP_FROM_REG(data->temp_os); ++ results[1] = LM75_TEMP_FROM_REG(data->temp_hyst); ++ results[2] = LM75_TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_os = LM75_TEMP_TO_REG(results[0]); ++ lm75_write_value(client, LM75_REG_TEMP_OS, ++ data->temp_os); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = LM75_TEMP_TO_REG(results[1]); ++ lm75_write_value(client, LM75_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++static int __init sm_lm75_init(void) ++{ ++ printk(KERN_INFO "lm75.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&lm75_driver); ++} ++ ++static void __exit sm_lm75_exit(void) ++{ ++ i2c_del_driver(&lm75_driver); ++} ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"); ++MODULE_DESCRIPTION("LM75 driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_lm75_init); ++module_exit(sm_lm75_exit); +--- linux-old/drivers/sensors/lm75.h Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm75.h Mon Dec 13 20:18:47 2004 +@@ -0,0 +1,49 @@ ++/* ++ lm75.h - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ This file contains common code for encoding/decoding LM75 type ++ temperature readings, which are emulated by many of the chips ++ we support. As the user is unlikely to load more than one driver ++ which contains this code, we don't worry about the wasted space. ++*/ ++ ++#include <linux/i2c-proc.h> ++ ++/* straight from the datasheet */ ++#define LM75_TEMP_MIN (-550) ++#define LM75_TEMP_MAX 1250 ++ ++/* TEMP: 0.1C/bit (-55C to +125C) ++ REG: (0.5C/bit, two's complement) << 7 */ ++static inline u16 LM75_TEMP_TO_REG(int temp) ++{ ++ int ntemp = SENSORS_LIMIT(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); ++ ntemp += (ntemp<0 ? -2 : 2); ++ return (u16)((ntemp / 5) << 7); ++} ++ ++static inline int LM75_TEMP_FROM_REG(u16 reg) ++{ ++ /* use integer division instead of equivalent right shift to ++ guarantee arithmetic shift and preserve the sign */ ++ return ((s16)reg / 128) * 5; ++} ++ +--- linux-old/drivers/sensors/lm78.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm78.c Mon Dec 13 20:18:47 2004 +@@ -0,0 +1,729 @@ ++/* ++ lm78.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0290, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_3(lm78, lm78j, lm79); ++ ++/* Many LM78 constants specified below */ ++ ++/* Length of ISA address segment */ ++#define LM78_EXTENT 8 ++ ++/* Where are the ISA address/data registers relative to the base address */ ++#define LM78_ADDR_REG_OFFSET 5 ++#define LM78_DATA_REG_OFFSET 6 ++ ++/* The LM78 registers */ ++#define LM78_REG_IN_MAX(nr) (0x2b + (nr) * 2) ++#define LM78_REG_IN_MIN(nr) (0x2c + (nr) * 2) ++#define LM78_REG_IN(nr) (0x20 + (nr)) ++ ++#define LM78_REG_FAN_MIN(nr) (0x3a + (nr)) ++#define LM78_REG_FAN(nr) (0x27 + (nr)) ++ ++#define LM78_REG_TEMP 0x27 ++#define LM78_REG_TEMP_OVER 0x39 ++#define LM78_REG_TEMP_HYST 0x3a ++ ++#define LM78_REG_ALARM1 0x41 ++#define LM78_REG_ALARM2 0x42 ++ ++#define LM78_REG_VID_FANDIV 0x47 ++ ++#define LM78_REG_CONFIG 0x40 ++#define LM78_REG_CHIPID 0x49 ++#define LM78_REG_I2C_ADDR 0x48 ++ ++ ++/* Conversions. Limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) ++#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) ++ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),0,255)) ++#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) ++ ++#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ ++ 205-(val)*5) ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++/* There are some complications in a module like this. First off, LM78 chips ++ may be both present on the SMBus and the ISA bus, and we have to handle ++ those cases separately at some places. Second, there might be several ++ LM78 chips available (well, actually, that is probably never done; but ++ it is a clean illustration of how to handle a case like that). Finally, ++ a specific chip may be attached to *both* ISA and SMBus, and we would ++ not like to detect it double. Fortunately, in the case of the LM78 at ++ least, a register tells us what SMBus address we are on, so that helps ++ a bit - except if there could be more than one SMBus. Groan. No solution ++ for this yet. */ ++ ++/* This module may seem overly long and complicated. In fact, it is not so ++ bad. Quite a lot of bookkeeping is done. A real driver can often cut ++ some corners. */ ++ ++/* For each registered LM78, we need to keep some data in memory. That ++ data is pointed to by lm78_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new lm78 client is ++ allocated. */ ++struct lm78_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[7]; /* Register value */ ++ u8 in_max[7]; /* Register value */ ++ u8 in_min[7]; /* Register value */ ++ u8 fan[3]; /* Register value */ ++ u8 fan_min[3]; /* Register value */ ++ u8 temp; /* Register value */ ++ u8 temp_over; /* Register value */ ++ u8 temp_hyst; /* Register value */ ++ u8 fan_div[3]; /* Register encoding, shifted right */ ++ u8 vid; /* Register encoding, combined */ ++ u16 alarms; /* Register encoding, combined */ ++}; ++ ++ ++static int lm78_attach_adapter(struct i2c_adapter *adapter); ++static int lm78_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int lm78_detach_client(struct i2c_client *client); ++ ++static int lm78_read_value(struct i2c_client *client, u8 register); ++static int lm78_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void lm78_update_client(struct i2c_client *client); ++static void lm78_init_client(struct i2c_client *client); ++ ++ ++static void lm78_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void lm78_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm78_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm78_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm78_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm78_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static struct i2c_driver lm78_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM78(-J) and LM79 sensor driver", ++ .id = I2C_DRIVERID_LM78, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm78_attach_adapter, ++ .detach_client = lm78_detach_client, ++}; ++ ++static int lm78_id = 0; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define LM78_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define LM78_SYSCTL_IN1 1001 ++#define LM78_SYSCTL_IN2 1002 ++#define LM78_SYSCTL_IN3 1003 ++#define LM78_SYSCTL_IN4 1004 ++#define LM78_SYSCTL_IN5 1005 ++#define LM78_SYSCTL_IN6 1006 ++#define LM78_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define LM78_SYSCTL_FAN2 1102 ++#define LM78_SYSCTL_FAN3 1103 ++#define LM78_SYSCTL_TEMP 1200 /* Degrees Celcius * 10 */ ++#define LM78_SYSCTL_VID 1300 /* Volts * 100 */ ++#define LM78_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define LM78_SYSCTL_ALARMS 2001 /* bitvector */ ++ ++#define LM78_ALARM_IN0 0x0001 ++#define LM78_ALARM_IN1 0x0002 ++#define LM78_ALARM_IN2 0x0004 ++#define LM78_ALARM_IN3 0x0008 ++#define LM78_ALARM_IN4 0x0100 ++#define LM78_ALARM_IN5 0x0200 ++#define LM78_ALARM_IN6 0x0400 ++#define LM78_ALARM_FAN1 0x0040 ++#define LM78_ALARM_FAN2 0x0080 ++#define LM78_ALARM_FAN3 0x0800 ++#define LM78_ALARM_TEMP 0x0010 ++#define LM78_ALARM_BTI 0x0020 ++#define LM78_ALARM_CHAS 0x1000 ++#define LM78_ALARM_FIFO 0x2000 ++#define LM78_ALARM_SMI_IN 0x4000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected LM78. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table lm78_dir_table_template[] = { ++ {LM78_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_in}, ++ {LM78_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_fan}, ++ {LM78_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_fan}, ++ {LM78_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_fan}, ++ {LM78_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_temp}, ++ {LM78_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_vid}, ++ {LM78_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_fan_div}, ++ {LM78_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm78_alarms}, ++ {0} ++}; ++ ++ ++/* This function is called when: ++ * lm78_driver is inserted (when this module is loaded), for each ++ available adapter ++ * when a new adapter is inserted (and lm78_driver is still present) */ ++static int lm78_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, lm78_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int lm78_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct lm78_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ int is_isa = i2c_is_isa_adapter(adapter); ++ ++ if (!is_isa ++ && !i2c_check_functionality(adapter, ++ I2C_FUNC_SMBUS_BYTE_DATA)) goto ++ ERROR0; ++ ++ if (is_isa) { ++ if (check_region(address, LM78_EXTENT)) ++ goto ERROR0; ++ } ++ ++ /* Probe whether there is anything available on this address. Already ++ done for SMBus clients */ ++ if (kind < 0) { ++ if (is_isa) { ++ ++#define REALLY_SLOW_IO ++ /* We need the timeouts for at least some LM78-like chips. But only ++ if we read 'undefined' registers. */ ++ i = inb_p(address + 1); ++ if (inb_p(address + 2) != i) ++ goto ERROR0; ++ if (inb_p(address + 3) != i) ++ goto ERROR0; ++ if (inb_p(address + 7) != i) ++ goto ERROR0; ++#undef REALLY_SLOW_IO ++ ++ /* Let's just hope nothing breaks here */ ++ i = inb_p(address + 5) & 0x7f; ++ outb_p(~i & 0x7f, address + 5); ++ if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) { ++ outb_p(i, address + 5); ++ return 0; ++ } ++ } ++ } ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access lm78_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct lm78_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ if (is_isa) ++ init_MUTEX(&data->lock); ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &lm78_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if (lm78_read_value(new_client, LM78_REG_CONFIG) & 0x80) ++ goto ERROR1; ++ if (!is_isa ++ && (lm78_read_value(new_client, LM78_REG_I2C_ADDR) != ++ address)) goto ERROR1; ++ } ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ i = lm78_read_value(new_client, LM78_REG_CHIPID); ++ if (i == 0x00 || i == 0x20) ++ kind = lm78; ++ else if (i == 0x40) ++ kind = lm78j; ++ else if ((i & 0xfe) == 0xc0) ++ kind = lm79; ++ else { ++ if (kind == 0) ++ printk ++ ("lm78.o: Ignoring 'force' parameter for unknown chip at " ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ goto ERROR1; ++ } ++ } ++ ++ if (kind == lm78) { ++ type_name = "lm78"; ++ client_name = "LM78 chip"; ++ } else if (kind == lm78j) { ++ type_name = "lm78-j"; ++ client_name = "LM78-J chip"; ++ } else if (kind == lm79) { ++ type_name = "lm79"; ++ client_name = "LM79 chip"; ++ } else { ++#ifdef DEBUG ++ printk("lm78.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Reserve the ISA region */ ++ if (is_isa) ++ request_region(address, LM78_EXTENT, type_name); ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = lm78_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ lm78_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the LM78 chip */ ++ lm78_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ if (is_isa) ++ release_region(address, LM78_EXTENT); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int lm78_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct lm78_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("lm78.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ if(i2c_is_isa_client(client)) ++ release_region(client->addr, LM78_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* The SMBus locks itself, but ISA access must be locked explicitely! ++ We don't want to lock the whole ISA bus, so we lock each client ++ separately. ++ We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the LM78 access and should not be necessary. ++ There are some ugly typecasts here, but the good new is - they should ++ nowhere else be necessary! */ ++static int lm78_read_value(struct i2c_client *client, u8 reg) ++{ ++ int res; ++ if (i2c_is_isa_client(client)) { ++ down(&(((struct lm78_data *) (client->data))->lock)); ++ outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET); ++ res = inb_p(client->addr + LM78_DATA_REG_OFFSET); ++ up(&(((struct lm78_data *) (client->data))->lock)); ++ return res; ++ } else ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* The SMBus locks itself, but ISA access muse be locked explicitely! ++ We don't want to lock the whole ISA bus, so we lock each client ++ separately. ++ We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the LM78 access and should not be necessary. ++ There are some ugly typecasts here, but the good new is - they should ++ nowhere else be necessary! */ ++static int lm78_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ if (i2c_is_isa_client(client)) { ++ down(&(((struct lm78_data *) (client->data))->lock)); ++ outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET); ++ outb_p(value, client->addr + LM78_DATA_REG_OFFSET); ++ up(&(((struct lm78_data *) (client->data))->lock)); ++ return 0; ++ } else ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new LM78. */ ++static void lm78_init_client(struct i2c_client *client) ++{ ++ u8 config = lm78_read_value(client, LM78_REG_CONFIG); ++ ++ /* Start monitoring */ ++ if (!(config & 0x01)) ++ lm78_write_value(client, LM78_REG_CONFIG, ++ (config & 0xf7) | 0x01); ++} ++ ++static void lm78_update_client(struct i2c_client *client) ++{ ++ struct lm78_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting lm78 update\n"); ++#endif ++ for (i = 0; i <= 6; i++) { ++ data->in[i] = ++ lm78_read_value(client, LM78_REG_IN(i)); ++ data->in_min[i] = ++ lm78_read_value(client, LM78_REG_IN_MIN(i)); ++ data->in_max[i] = ++ lm78_read_value(client, LM78_REG_IN_MAX(i)); ++ } ++ for (i = 1; i <= 3; i++) { ++ data->fan[i - 1] = ++ lm78_read_value(client, LM78_REG_FAN(i)); ++ data->fan_min[i - 1] = ++ lm78_read_value(client, LM78_REG_FAN_MIN(i)); ++ } ++ data->temp = lm78_read_value(client, LM78_REG_TEMP); ++ data->temp_over = ++ lm78_read_value(client, LM78_REG_TEMP_OVER); ++ data->temp_hyst = ++ lm78_read_value(client, LM78_REG_TEMP_HYST); ++ i = lm78_read_value(client, LM78_REG_VID_FANDIV); ++ data->vid = i & 0x0f; ++ if (data->type == lm79) ++ data->vid |= ++ (lm78_read_value(client, LM78_REG_CHIPID) & ++ 0x01) << 4; ++ else ++ data->vid |= 0x10; ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = i >> 6; ++ data->alarms = lm78_read_value(client, LM78_REG_ALARM1) + ++ (lm78_read_value(client, LM78_REG_ALARM2) << 8); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ ++ data->fan_div[2] = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void lm78_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm78_data *data = client->data; ++ int nr = ctl_name - LM78_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm78_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ lm78_write_value(client, LM78_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ lm78_write_value(client, LM78_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void lm78_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm78_data *data = client->data; ++ int nr = ctl_name - LM78_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm78_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data-> ++ fan_div[nr - 1])); ++ results[1] = ++ FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr - ++ 1])); ++ lm78_write_value(client, LM78_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void lm78_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm78_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm78_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over = TEMP_TO_REG(results[0]); ++ lm78_write_value(client, LM78_REG_TEMP_OVER, ++ data->temp_over); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ lm78_write_value(client, LM78_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++void lm78_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm78_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm78_update_client(client); ++ results[0] = VID_FROM_REG(data->vid); ++ *nrels_mag = 1; ++ } ++} ++ ++void lm78_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm78_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm78_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++/* Note: we save and restore the fan minimum here, because its value is ++ determined in part by the fan divisor. This follows the principle of ++ least surprise: the user doesn't expect the fan minimum to change just ++ because the divisor changed. */ ++void lm78_fan_div(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm78_data *data = client->data; ++ int old, min; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm78_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ results[2] = 2; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = lm78_read_value(client, LM78_REG_VID_FANDIV); ++ if (*nrels_mag >= 2) { ++ min = FAN_FROM_REG(data->fan_min[1], ++ DIV_FROM_REG(data->fan_div[1])); ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ data->fan_min[1] = FAN_TO_REG(min, ++ DIV_FROM_REG(data->fan_div[1])); ++ lm78_write_value(client, LM78_REG_FAN_MIN(2), ++ data->fan_min[1]); ++ } ++ if (*nrels_mag >= 1) { ++ min = FAN_FROM_REG(data->fan_min[0], ++ DIV_FROM_REG(data->fan_div[0])); ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ data->fan_min[0] = FAN_TO_REG(min, ++ DIV_FROM_REG(data->fan_div[0])); ++ lm78_write_value(client, LM78_REG_FAN_MIN(1), ++ data->fan_min[0]); ++ lm78_write_value(client, LM78_REG_VID_FANDIV, old); ++ } ++ } ++} ++ ++static int __init sm_lm78_init(void) ++{ ++ printk("lm78.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&lm78_driver); ++} ++ ++static void __exit sm_lm78_exit(void) ++{ ++ i2c_del_driver(&lm78_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"); ++MODULE_DESCRIPTION("LM78, LM78-J and LM79 driver"); ++ ++module_init(sm_lm78_init); ++module_exit(sm_lm78_exit); +--- linux-old/drivers/sensors/lm80.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm80.c Mon Dec 13 20:18:48 2004 +@@ -0,0 +1,606 @@ ++/* ++ lm80.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ and Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(lm80); ++ ++/* Many LM80 constants specified below */ ++ ++/* The LM80 registers */ ++#define LM80_REG_IN_MAX(nr) (0x2a + (nr) * 2) ++#define LM80_REG_IN_MIN(nr) (0x2b + (nr) * 2) ++#define LM80_REG_IN(nr) (0x20 + (nr)) ++ ++#define LM80_REG_FAN1_MIN 0x3c ++#define LM80_REG_FAN2_MIN 0x3d ++#define LM80_REG_FAN1 0x28 ++#define LM80_REG_FAN2 0x29 ++ ++#define LM80_REG_TEMP 0x27 ++#define LM80_REG_TEMP_HOT_MAX 0x38 ++#define LM80_REG_TEMP_HOT_HYST 0x39 ++#define LM80_REG_TEMP_OS_MAX 0x3a ++#define LM80_REG_TEMP_OS_HYST 0x3b ++ ++#define LM80_REG_CONFIG 0x00 ++#define LM80_REG_ALARM1 0x01 ++#define LM80_REG_ALARM2 0x02 ++#define LM80_REG_MASK1 0x03 ++#define LM80_REG_MASK2 0x04 ++#define LM80_REG_FANDIV 0x05 ++#define LM80_REG_RES 0x06 ++ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define IN_TO_REG(val) (SENSORS_LIMIT((val),0,255)) ++#define IN_FROM_REG(val) (val) ++ ++static inline unsigned char FAN_TO_REG(unsigned rpm, unsigned div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:\ ++ (val)==255?0:1350000/((div)*(val))) ++ ++static inline long TEMP_FROM_REG(u16 temp) ++{ ++ long res; ++ ++ temp >>= 4; ++ if (temp < 0x0800) ++ res = 625 * (long) temp; ++ else ++ res = ((long) temp - 0x01000) * 625; ++ ++ return res / 100; ++} ++ ++#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*100) ++ ++#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-50)/100):\ ++ ((val)+50)/100), \ ++ 0,255) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++ ++struct lm80_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[7]; /* Register value */ ++ u8 in_max[7]; /* Register value */ ++ u8 in_min[7]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u16 temp; /* Register values, shifted right */ ++ u8 temp_hot_max; /* Register value */ ++ u8 temp_hot_hyst; /* Register value */ ++ u8 temp_os_max; /* Register value */ ++ u8 temp_os_hyst; /* Register value */ ++ u16 alarms; /* Register encoding, combined */ ++}; ++ ++ ++ ++static int lm80_attach_adapter(struct i2c_adapter *adapter); ++static int lm80_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int lm80_detach_client(struct i2c_client *client); ++ ++static int lm80_read_value(struct i2c_client *client, u8 reg); ++static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value); ++static void lm80_update_client(struct i2c_client *client); ++static void lm80_init_client(struct i2c_client *client); ++ ++ ++static void lm80_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void lm80_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm80_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm80_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm80_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int lm80_id = 0; ++ ++static struct i2c_driver lm80_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM80 sensor driver", ++ .id = I2C_DRIVERID_LM80, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm80_attach_adapter, ++ .detach_client = lm80_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define LM80_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define LM80_SYSCTL_IN1 1001 ++#define LM80_SYSCTL_IN2 1002 ++#define LM80_SYSCTL_IN3 1003 ++#define LM80_SYSCTL_IN4 1004 ++#define LM80_SYSCTL_IN5 1005 ++#define LM80_SYSCTL_IN6 1006 ++#define LM80_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define LM80_SYSCTL_FAN2 1102 ++#define LM80_SYSCTL_TEMP 1250 /* Degrees Celcius * 100 */ ++#define LM80_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define LM80_SYSCTL_ALARMS 2001 /* bitvector */ ++ ++#define LM80_ALARM_IN0 0x0001 ++#define LM80_ALARM_IN1 0x0002 ++#define LM80_ALARM_IN2 0x0004 ++#define LM80_ALARM_IN3 0x0008 ++#define LM80_ALARM_IN4 0x0010 ++#define LM80_ALARM_IN5 0x0020 ++#define LM80_ALARM_IN6 0x0040 ++#define LM80_ALARM_FAN1 0x0400 ++#define LM80_ALARM_FAN2 0x0800 ++#define LM80_ALARM_TEMP_HOT 0x0100 ++#define LM80_ALARM_TEMP_OS 0x2000 ++#define LM80_ALARM_CHAS 0x1000 ++#define LM80_ALARM_BTI 0x0200 ++#define LM80_ALARM_INT_IN 0x0080 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected LM80. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table lm80_dir_table_template[] = { ++ {LM80_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_in}, ++ {LM80_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_fan}, ++ {LM80_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_fan}, ++ {LM80_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_temp}, ++ {LM80_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_fan_div}, ++ {LM80_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm80_alarms}, ++ {0} ++}; ++ ++static int lm80_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, lm80_detect); ++} ++ ++int lm80_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, cur; ++ struct i2c_client *new_client; ++ struct lm80_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("lm80.o: lm80_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access lm80_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct lm80_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &lm80_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. It is lousy. */ ++ if (lm80_read_value(new_client, LM80_REG_ALARM2) & 0xc0) ++ goto ERROR1; ++ for (i = 0x2a; i <= 0x3d; i++) { ++ cur = i2c_smbus_read_byte_data(new_client, i); ++ if ((i2c_smbus_read_byte_data(new_client, i + 0x40) != cur) ++ || (i2c_smbus_read_byte_data(new_client, i + 0x80) != ++ cur) ++ || (i2c_smbus_read_byte_data(new_client, i + 0xc0) != ++ cur)) goto ERROR1; ++ } ++ ++ /* Determine the chip type - only one kind supported! */ ++ if (kind <= 0) ++ kind = lm80; ++ ++ if (kind == lm80) { ++ type_name = "lm80"; ++ client_name = "LM80 chip"; ++ } else { ++#ifdef DEBUG ++ printk("lm80.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = lm80_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ lm80_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ lm80_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int lm80_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct lm80_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("lm80.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static int lm80_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new LM80. */ ++static void lm80_init_client(struct i2c_client *client) ++{ ++ /* Reset all except Watchdog values and last conversion values ++ This sets fan-divs to 2, among others. This makes most other ++ initializations unnecessary */ ++ lm80_write_value(client, LM80_REG_CONFIG, 0x80); ++ /* Set 11-bit temperature resolution */ ++ lm80_write_value(client, LM80_REG_RES, 0x08); ++ ++ /* Start monitoring */ ++ lm80_write_value(client, LM80_REG_CONFIG, 0x01); ++} ++ ++static void lm80_update_client(struct i2c_client *client) ++{ ++ struct lm80_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > 2 * HZ) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting lm80 update\n"); ++#endif ++ for (i = 0; i <= 6; i++) { ++ data->in[i] = ++ lm80_read_value(client, LM80_REG_IN(i)); ++ data->in_min[i] = ++ lm80_read_value(client, LM80_REG_IN_MIN(i)); ++ data->in_max[i] = ++ lm80_read_value(client, LM80_REG_IN_MAX(i)); ++ } ++ data->fan[0] = lm80_read_value(client, LM80_REG_FAN1); ++ data->fan_min[0] = ++ lm80_read_value(client, LM80_REG_FAN1_MIN); ++ data->fan[1] = lm80_read_value(client, LM80_REG_FAN2); ++ data->fan_min[1] = ++ lm80_read_value(client, LM80_REG_FAN2_MIN); ++ ++ data->temp = ++ (lm80_read_value(client, LM80_REG_TEMP) << 8) | ++ (lm80_read_value(client, LM80_REG_RES) & 0xf0); ++ data->temp_os_max = ++ lm80_read_value(client, LM80_REG_TEMP_OS_MAX); ++ data->temp_os_hyst = ++ lm80_read_value(client, LM80_REG_TEMP_OS_HYST); ++ data->temp_hot_max = ++ lm80_read_value(client, LM80_REG_TEMP_HOT_MAX); ++ data->temp_hot_hyst = ++ lm80_read_value(client, LM80_REG_TEMP_HOT_HYST); ++ ++ i = lm80_read_value(client, LM80_REG_FANDIV); ++ data->fan_div[0] = (i >> 2) & 0x03; ++ data->fan_div[1] = (i >> 4) & 0x03; ++ data->alarms = lm80_read_value(client, LM80_REG_ALARM1) + ++ (lm80_read_value(client, LM80_REG_ALARM2) << 8); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void lm80_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm80_data *data = client->data; ++ int nr = ctl_name - LM80_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm80_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ lm80_write_value(client, LM80_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ lm80_write_value(client, LM80_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void lm80_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm80_data *data = client->data; ++ int nr = ctl_name - LM80_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm80_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data-> ++ fan_div[nr - 1])); ++ results[1] = ++ FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr - ++ 1])); ++ lm80_write_value(client, ++ nr == ++ 1 ? LM80_REG_FAN1_MIN : ++ LM80_REG_FAN2_MIN, ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void lm80_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm80_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm80_update_client(client); ++ results[0] = TEMP_LIMIT_FROM_REG(data->temp_hot_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->temp_hot_hyst); ++ results[2] = TEMP_LIMIT_FROM_REG(data->temp_os_max); ++ results[3] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst); ++ results[4] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 5; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_hot_max = TEMP_LIMIT_TO_REG(results[0]); ++ lm80_write_value(client, LM80_REG_TEMP_HOT_MAX, ++ data->temp_hot_max); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hot_hyst = ++ TEMP_LIMIT_TO_REG(results[1]); ++ lm80_write_value(client, LM80_REG_TEMP_HOT_HYST, ++ data->temp_hot_hyst); ++ } ++ if (*nrels_mag >= 3) { ++ data->temp_os_max = TEMP_LIMIT_TO_REG(results[2]); ++ lm80_write_value(client, LM80_REG_TEMP_OS_MAX, ++ data->temp_os_max); ++ } ++ if (*nrels_mag >= 4) { ++ data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[3]); ++ lm80_write_value(client, LM80_REG_TEMP_OS_HYST, ++ data->temp_os_hyst); ++ } ++ } ++} ++ ++void lm80_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm80_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm80_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void lm80_fan_div(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm80_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm80_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ results[2] = 2; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = lm80_read_value(client, LM80_REG_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0xcf) | (data->fan_div[1] << 4); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xf3) | (data->fan_div[0] << 2); ++ lm80_write_value(client, LM80_REG_FANDIV, old); ++ } ++ } ++} ++ ++static int __init sm_lm80_init(void) ++{ ++ printk("lm80.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&lm80_driver); ++} ++ ++static void __exit sm_lm80_exit(void) ++{ ++ i2c_del_driver(&lm80_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("LM80 driver"); ++ ++module_init(sm_lm80_init); ++module_exit(sm_lm80_exit); +--- linux-old/drivers/sensors/lm83.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm83.c Mon Dec 13 20:18:48 2004 +@@ -0,0 +1,513 @@ ++/* ++ * lm83.c - Part of lm_sensors, Linux kernel modules for hardware ++ * monitoring ++ * Copyright (C) 2003 Jean Delvare <khali@linux-fr.org> ++ * ++ * Heavily inspired from the lm78, lm75 and adm1021 drivers. The LM83 is ++ * a sensor chip made by National Semiconductor. It reports up to four ++ * temperatures (its own plus up to three external ones) with a 1 deg ++ * resolution and a 3-4 deg accuracy. Complete datasheet can be obtained ++ * from National's website at: ++ * http://www.national.com/pf/LM/LM83.html ++ * Since the datasheet omits to give the chip stepping code, I give it ++ * here: 0x03 (at register 0xff). ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* ++ * Addresses to scan ++ * Address is selected using 2 three-level pins, resulting in 9 possible ++ * addresses. ++ */ ++ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x18, 0x1a, 0x29, 0x2b, ++ 0x4c, 0x4e, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* ++ * Insmod parameters ++ */ ++ ++SENSORS_INSMOD_1(lm83); ++ ++/* ++ * The LM83 registers ++ * Manufacturer ID is 0x01 for National Semiconductor. ++ */ ++ ++#define LM83_REG_R_MAN_ID 0xFE ++#define LM83_REG_R_CHIP_ID 0xFF ++#define LM83_REG_R_CONFIG 0x03 ++#define LM83_REG_W_CONFIG 0x09 ++#define LM83_REG_R_STATUS1 0x02 ++#define LM83_REG_R_STATUS2 0x35 ++#define LM83_REG_R_LOCAL_TEMP 0x00 ++#define LM83_REG_R_LOCAL_HIGH 0x05 ++#define LM83_REG_W_LOCAL_HIGH 0x0B ++#define LM83_REG_R_REMOTE1_TEMP 0x30 ++#define LM83_REG_R_REMOTE1_HIGH 0x38 ++#define LM83_REG_W_REMOTE1_HIGH 0x50 ++#define LM83_REG_R_REMOTE2_TEMP 0x01 ++#define LM83_REG_R_REMOTE2_HIGH 0x07 ++#define LM83_REG_W_REMOTE2_HIGH 0x0D ++#define LM83_REG_R_REMOTE3_TEMP 0x31 ++#define LM83_REG_R_REMOTE3_HIGH 0x3A ++#define LM83_REG_W_REMOTE3_HIGH 0x52 ++#define LM83_REG_R_TCRIT 0x42 ++#define LM83_REG_W_TCRIT 0x5A ++ ++/* ++ * Conversions and various macros ++ * The LM83 uses signed 8-bit values. ++ */ ++ ++#define TEMP_FROM_REG(val) ((val) > 127 ? (val) - 0x100 : (val)) ++#define TEMP_TO_REG(val) ((val) <= -50 ? -50 + 0x100 : \ ++ (val) >= 127 ? 127 : \ ++ (val) >= 0 ? (val) : \ ++ (val) + 0x100) ++ ++static const u8 LM83_REG_R_TEMP[] = { ++ LM83_REG_R_LOCAL_TEMP, ++ LM83_REG_R_REMOTE1_TEMP, ++ LM83_REG_R_REMOTE2_TEMP, ++ LM83_REG_R_REMOTE3_TEMP ++}; ++ ++static const u8 LM83_REG_R_HIGH[] = { ++ LM83_REG_R_LOCAL_HIGH, ++ LM83_REG_R_REMOTE1_HIGH, ++ LM83_REG_R_REMOTE2_HIGH, ++ LM83_REG_R_REMOTE3_HIGH ++}; ++ ++static const u8 LM83_REG_W_HIGH[] = { ++ LM83_REG_W_LOCAL_HIGH, ++ LM83_REG_W_REMOTE1_HIGH, ++ LM83_REG_W_REMOTE2_HIGH, ++ LM83_REG_W_REMOTE3_HIGH ++}; ++ ++/* ++ * Functions declaration ++ */ ++ ++static int lm83_attach_adapter(struct i2c_adapter *adapter); ++static int lm83_detect(struct i2c_adapter *adapter, int address, unsigned ++ short flags, int kind); ++static int lm83_detach_client(struct i2c_client *client); ++static void lm83_update_client(struct i2c_client *client); ++static void lm83_temp(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++static void lm83_tcrit(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++static void lm83_alarms(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++ ++/* ++ * Driver data (common to all clients) ++ */ ++ ++static struct i2c_driver lm83_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM83 sensor driver", ++ .id = I2C_DRIVERID_LM83, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm83_attach_adapter, ++ .detach_client = lm83_detach_client, ++}; ++ ++/* ++ * Client data (each client gets its own) ++ */ ++ ++struct lm83_data ++{ ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* zero until following fields are valid */ ++ unsigned long last_updated; /* in jiffies */ ++ ++ /* registers values */ ++ u8 temp[4], temp_high[4], tcrit; ++ u16 alarms; /* bitvector, combined */ ++}; ++ ++/* ++ * Proc entries ++ * These files are created for each detected LM83. ++ */ ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define LM83_SYSCTL_LOCAL_TEMP 1200 ++#define LM83_SYSCTL_REMOTE1_TEMP 1201 ++#define LM83_SYSCTL_REMOTE2_TEMP 1202 ++#define LM83_SYSCTL_REMOTE3_TEMP 1203 ++#define LM83_SYSCTL_TCRIT 1208 ++#define LM83_SYSCTL_ALARMS 1210 ++ ++#define LM83_ALARM_LOCAL_HIGH 0x0040 ++#define LM83_ALARM_LOCAL_CRIT 0x0001 ++#define LM83_ALARM_REMOTE1_HIGH 0x8000 ++#define LM83_ALARM_REMOTE1_CRIT 0x0100 ++#define LM83_ALARM_REMOTE1_OPEN 0x2000 ++#define LM83_ALARM_REMOTE2_HIGH 0x0010 ++#define LM83_ALARM_REMOTE2_CRIT 0x0002 ++#define LM83_ALARM_REMOTE2_OPEN 0x0004 ++#define LM83_ALARM_REMOTE3_HIGH 0x1000 ++#define LM83_ALARM_REMOTE3_CRIT 0x0200 ++#define LM83_ALARM_REMOTE3_OPEN 0x0400 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++ ++static ctl_table lm83_dir_table_template[] = ++{ ++ {LM83_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp}, ++ {LM83_SYSCTL_REMOTE1_TEMP, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp}, ++ {LM83_SYSCTL_REMOTE2_TEMP, "temp3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp}, ++ {LM83_SYSCTL_REMOTE3_TEMP, "temp4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp}, ++ {LM83_SYSCTL_TCRIT, "tcrit", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_tcrit}, ++ {LM83_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_alarms}, ++ {0} ++}; ++ ++/* ++ * Internal variables ++ */ ++ ++static int lm83_id = 0; ++ ++/* ++ * Real code ++ */ ++ ++static int lm83_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, lm83_detect); ++} ++ ++/* ++ * The following function does more than just detection. If detection ++ * succeeds, it also registers the new chip. ++ */ ++static int lm83_detect(struct i2c_adapter *adapter, int address, unsigned ++ short flags, int kind) ++{ ++ struct i2c_client *new_client; ++ struct lm83_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) ++ { ++ printk("lm83.o: Called for an ISA bus adapter, aborting.\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ { ++#ifdef DEBUG ++ printk("lm83.o: I2C bus doesn't support byte read mode, " ++ "skipping.\n"); ++#endif ++ return 0; ++ } ++ ++ if (!(data = kmalloc(sizeof(struct lm83_data), GFP_KERNEL))) ++ { ++ printk("lm83.o: Out of memory in lm83_detect (new_client).\n"); ++ return -ENOMEM; ++ } ++ ++ /* ++ * The common I2C client data is placed right before the ++ * LM83-specific data. The LM83-specific data is pointed to by the ++ * data field from the I2C client data. ++ */ ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &lm83_driver; ++ new_client->flags = 0; ++ ++ /* ++ * Now we do the remaining detection. A negative kind means that ++ * the driver was loaded with no force parameter (default), so we ++ * must both detect and identify the chip (actually there is only ++ * one possible kind of chip for now, LM83). A zero kind means that ++ * the driver was loaded with the force parameter, the detection ++ * step shall be skipped. A positive kind means that the driver ++ * was loaded with the force parameter and a given kind of chip is ++ * requested, so both the detection and the identification steps ++ * are skipped. ++ */ ++ ++ /* Default to an LM83 if forced */ ++ if (kind == 0) ++ kind = lm83; ++ ++ if (kind < 0) /* detection */ ++ { ++ if (((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) ++ & 0xA8) != 0x00) ++ || ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) ++ & 0x48) != 0x00) ++ || ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) ++ & 0x41) != 0x00)) ++ { ++#ifdef DEBUG ++ printk(KERN_DEBUG "lm83.o: Detection failed at 0x%02x.\n", ++ address); ++#endif ++ goto ERROR1; ++ } ++ } ++ ++ if (kind <= 0) /* identification */ ++ { ++ u8 man_id, chip_id; ++ ++ man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID); ++ chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID); ++ if (man_id == 0x01) /* National Semiconductor */ ++ { ++ if (chip_id == 0x03) ++ kind = lm83; ++ } ++ } ++ ++ if (kind <= 0) /* identification failed */ ++ { ++ printk("lm83.o: Unsupported chip.\n"); ++ goto ERROR1; ++ } ++ ++ if (kind == lm83) ++ { ++ type_name = "lm83"; ++ client_name = "LM83 chip"; ++ } ++ else ++ { ++ printk("lm83.o: Unknown kind %d.\n", kind); ++ goto ERROR1; ++ } ++ ++ /* ++ * OK, we got a valid chip so we can fill in the remaining client ++ * fields. ++ */ ++ ++ strcpy(new_client->name, client_name); ++ new_client->id = lm83_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* ++ * Tell the I2C layer a new client has arrived. ++ */ ++ ++ if ((err = i2c_attach_client(new_client))) ++ { ++#ifdef DEBUG ++ printk("lm83.o: Failed attaching client.\n"); ++#endif ++ goto ERROR1; ++ } ++ ++ /* ++ * Register a new directory entry. ++ */ ++ ++ if ((err = i2c_register_entry(new_client, type_name, ++ lm83_dir_table_template)) < 0) ++ { ++#ifdef DEBUG ++ printk("lm83.o: Failed registering directory entry.\n"); ++#endif ++ goto ERROR2; ++ } ++ data->sysctl_id = err; ++ ++ /* ++ * Initialize the LM83 chip ++ * (Nothing to do for this one.) ++ */ ++ ++ return 0; ++ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ return err; ++} ++ ++static int lm83_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct lm83_data *) (client->data))->sysctl_id); ++ if ((err = i2c_detach_client(client))) ++ { ++ printk("lm83.o: Client deregistration failed, client not " ++ "detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ return 0; ++} ++ ++static void lm83_update_client(struct i2c_client *client) ++{ ++ struct lm83_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ * 2) || ++ (jiffies < data->last_updated) || !data->valid) ++ { ++ int nr; ++#ifdef DEBUG ++ printk("lm83.o: Updating LM83 data.\n"); ++#endif ++ for (nr = 0; nr < 4 ; nr++) ++ { ++ data->temp[nr] = ++ i2c_smbus_read_byte_data(client, LM83_REG_R_TEMP[nr]); ++ data->temp_high[nr] = ++ i2c_smbus_read_byte_data(client, LM83_REG_R_HIGH[nr]); ++ } ++ data->tcrit = i2c_smbus_read_byte_data(client, LM83_REG_R_TCRIT); ++ data->alarms = ++ i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) + ++ (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) << 8); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++static void lm83_temp(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm83_data *data = client->data; ++ int nr = ctl_name - LM83_SYSCTL_LOCAL_TEMP; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm83_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_high[nr]); ++ results[1] = TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 2; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->temp_high[nr] = TEMP_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr], ++ data->temp_high[nr]); ++ } ++ } ++} ++ ++static void lm83_tcrit(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm83_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm83_update_client(client); ++ results[0] = TEMP_FROM_REG(data->tcrit); ++ *nrels_mag = 1; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->tcrit = TEMP_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, LM83_REG_W_TCRIT, ++ data->tcrit); ++ } ++ } ++} ++ ++static void lm83_alarms(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm83_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm83_update_client(client); ++ results[0] = data->alarms; ++ *nrels_mag = 1; ++ } ++} ++ ++static int __init sm_lm83_init(void) ++{ ++ printk(KERN_INFO "lm83.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&lm83_driver); ++} ++ ++static void __exit sm_lm83_exit(void) ++{ ++ i2c_del_driver(&lm83_driver); ++} ++ ++MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); ++MODULE_DESCRIPTION("LM83 sensor driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_lm83_init); ++module_exit(sm_lm83_exit); +--- linux-old/drivers/sensors/lm85.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm85.c Mon Dec 13 20:18:49 2004 +@@ -0,0 +1,2043 @@ ++/* ++ lm85.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ Copyright (c) 2002, 2003 Philip Pokorny <ppokorny@penguincomputing.com> ++ Copyright (c) 2003 Margit Schubert-While <margitsw@t-online.de> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++ CHANGELOG ++ ++ 2002-11-13 First patch for LM85 functionality ++ 2002-11-18 LM85 functionality mostly done ++ 2002-12-02 Adding ADM1027 functionality ++ 2002-12-06 Adding ADT7463 functionality ++ 2003-01-09 Code cleanup. ++ Save reserved bits in case they are implemented ++ in a future chip. (Solve problem with lockups ++ on ADM1027 due to chip initialization) ++ Added chip initialization bypass option ++ 2003-02-12 Add THERM asserted counts for ADT7463 ++ Added #ifdef so we can compile against 2.6.5 ++ without updating i2c-ids.h ++ 2003-02-17 Prepare for switch to 2.7.0 development ++ Implement tmin_control for ADT7463 ++ Expose THERM asserted counts to /proc ++ Code cleanup ++ 2003-02-19 Working with Margit and LM_SENSORS developers ++ 2003-02-23 Removed chip initialization entirely ++ Scale voltages in driver at Margit's request ++ Change PWM from 0-100% to 0-255 per LM sensors standard ++ 2003-02-27 Documentation and code cleanups ++ Added this CHANGELOG ++ Print additional precision for temperatures and voltages ++ Many thanks to Margit Schubert-While and Brandt xxxxxx ++ for help testing this version ++ 2003-02-28 More diagnostic messages regarding BIOS setup ++ 2003-03-01 Added Interrupt mask register support. ++ 2003-03-08 Fixed problem with pseudo 16-bit registers ++ Cleaned up some compiler warnings. ++ Fixed problem with Operating Point and THERM counting ++ 2003-03-21 Initial support for EMC6D100 and EMC6D101 chips ++ 2003-06-30 Add support for EMC6D100 extra voltage inputs. ++*/ ++ ++#include <linux/version.h> ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++#ifndef I2C_DRIVERID_LM85 ++#define I2C_DRIVERID_LM85 1039 ++#endif ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_5(lm85b, lm85c, adm1027, adt7463, emc6d100); ++ ++/* Many LM85 constants specified below */ ++ ++/* The LM85 registers */ ++#define LM85_REG_IN(nr) (0x20 + (nr)) ++#define LM85_REG_IN_MIN(nr) (0x44 + (nr) * 2) ++#define LM85_REG_IN_MAX(nr) (0x45 + (nr) * 2) ++ ++#define LM85_REG_TEMP(nr) (0x25 + (nr)) ++#define LM85_REG_TEMP_MIN(nr) (0x4e + (nr) * 2) ++#define LM85_REG_TEMP_MAX(nr) (0x4f + (nr) * 2) ++ ++/* Fan speeds are LSB, MSB (2 bytes) */ ++#define LM85_REG_FAN(nr) (0x28 + (nr) *2) ++#define LM85_REG_FAN_MIN(nr) (0x54 + (nr) *2) ++ ++#define LM85_REG_PWM(nr) (0x30 + (nr)) ++ ++#define ADT7463_REG_OPPOINT(nr) (0x33 + (nr)) ++ ++#define ADT7463_REG_TMIN_CTL1 0x36 ++#define ADT7463_REG_TMIN_CTL2 0x37 ++#define ADT7463_REG_TMIN_CTL 0x0136 ++ ++#define LM85_REG_DEVICE 0x3d ++#define LM85_REG_COMPANY 0x3e ++#define LM85_REG_VERSTEP 0x3f ++/* These are the recognized values for the above regs */ ++#define LM85_DEVICE_ADX 0x27 ++#define LM85_COMPANY_NATIONAL 0x01 ++#define LM85_COMPANY_ANALOG_DEV 0x41 ++#define LM85_COMPANY_SMSC 0x5c ++#define LM85_VERSTEP_VMASK 0xf0 ++#define LM85_VERSTEP_SMASK 0x0f ++#define LM85_VERSTEP_GENERIC 0x60 ++#define LM85_VERSTEP_LM85C 0x60 ++#define LM85_VERSTEP_LM85B 0x62 ++#define LM85_VERSTEP_ADM1027 0x60 ++#define LM85_VERSTEP_ADT7463 0x62 ++#define LM85_VERSTEP_EMC6D100_A0 0x60 ++#define LM85_VERSTEP_EMC6D100_A1 0x61 ++ ++#define LM85_REG_CONFIG 0x40 ++ ++#define LM85_REG_ALARM1 0x41 ++#define LM85_REG_ALARM2 0x42 ++#define LM85_REG_ALARM 0x0141 ++ ++#define LM85_REG_VID 0x43 ++ ++/* Automated FAN control */ ++#define LM85_REG_AFAN_CONFIG(nr) (0x5c + (nr)) ++#define LM85_REG_AFAN_RANGE(nr) (0x5f + (nr)) ++#define LM85_REG_AFAN_SPIKE1 0x62 ++#define LM85_REG_AFAN_SPIKE2 0x63 ++#define LM85_REG_AFAN_MINPWM(nr) (0x64 + (nr)) ++#define LM85_REG_AFAN_LIMIT(nr) (0x67 + (nr)) ++#define LM85_REG_AFAN_CRITICAL(nr) (0x6a + (nr)) ++#define LM85_REG_AFAN_HYST1 0x6d ++#define LM85_REG_AFAN_HYST2 0x6e ++ ++#define LM85_REG_TACH_MODE 0x74 ++#define LM85_REG_SPINUP_CTL 0x75 ++ ++#define ADM1027_REG_TEMP_OFFSET(nr) (0x70 + (nr)) ++#define ADM1027_REG_CONFIG2 0x73 ++#define ADM1027_REG_INTMASK1 0x74 ++#define ADM1027_REG_INTMASK2 0x75 ++#define ADM1027_REG_INTMASK 0x0174 ++#define ADM1027_REG_EXTEND_ADC1 0x76 ++#define ADM1027_REG_EXTEND_ADC2 0x77 ++#define ADM1027_REG_EXTEND_ADC 0x0176 ++#define ADM1027_REG_CONFIG3 0x78 ++#define ADM1027_REG_FAN_PPR 0x7b ++ ++#define ADT7463_REG_THERM 0x79 ++#define ADT7463_REG_THERM_LIMIT 0x7A ++#define ADT7463_REG_CONFIG4 0x7D ++ ++#define EMC6D100_REG_SFR 0x7c ++#define EMC6D100_REG_ALARM3 0x7d ++#define EMC6D100_REG_CONF 0x7f ++#define EMC6D100_REG_INT_EN 0x80 ++/* IN5, IN6 and IN7 */ ++#define EMC6D100_REG_IN(nr) (0x70 + ((nr)-5)) ++#define EMC6D100_REG_IN_MIN(nr) (0x73 + ((nr)-5) * 2) ++#define EMC6D100_REG_IN_MAX(nr) (0x74 + ((nr)-5) * 2) ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ */ ++ ++/* IN are scaled 1.000 == 0xc0, mag = 3 */ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*0xc0+500)/1000),0,255)) ++#define INEXT_FROM_REG(val,ext) (((val)*1000 + (ext)*250 + 96)/0xc0) ++#define IN_FROM_REG(val) (INEXT_FROM_REG(val,0)) ++ ++/* IN are scaled acording to built-in resistors */ ++static int lm85_scaling[] = { /* .001 Volts */ ++ 2500, 2250, 3300, 5000, 12000, ++ 3300, 1500, 1800, /* EMC6D100 */ ++ }; ++#define SCALE(val,from,to) (((val)*(to) + ((from)/2))/(from)) ++#define INS_TO_REG(n,val) (SENSORS_LIMIT(SCALE(val,lm85_scaling[n],192),0,255)) ++#define INSEXT_FROM_REG(n,val,ext) (SCALE((val)*4 + (ext),192*4,lm85_scaling[n])) ++#define INS_FROM_REG(n,val) (INSEXT_FROM_REG(n,val,0)) ++ ++/* FAN speed is measured using 90kHz clock */ ++#define FAN_TO_REG(val) (SENSORS_LIMIT( (val)<=0?0: 5400000/(val),0,65534)) ++#define FAN_FROM_REG(val) ((val)==0?-1:(val)==0xffff?0:5400000/(val)) ++ ++/* Temperature is reported in .01 degC increments */ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)+50)/100,-127,127)) ++#define TEMPEXT_FROM_REG(val,ext) ((val)*100 + (ext)*25) ++#define TEMP_FROM_REG(val) (TEMPEXT_FROM_REG(val,0)) ++#define EXTTEMP_TO_REG(val) (SENSORS_LIMIT((val)/25,-127,127)) ++#define OPPOINT_TO_REG(val) (SENSORS_LIMIT(val,-127,127)) ++#define OPPOINT_FROM_REG(val) (val) ++ ++#define PWM_TO_REG(val) (SENSORS_LIMIT(val,0,255)) ++#define PWM_FROM_REG(val) (val) ++ ++#define EXT_FROM_REG(val,sensor) (((val)>>(sensor * 2))&0x03) ++ ++/* ZONEs have the following parameters: ++ * Limit (low) temp, 1. degC ++ * Hysteresis (below limit), 1. degC (0-15) ++ * Range of speed control, .1 degC (2-80) ++ * Critical (high) temp, 1. degC ++ * ++ * FAN PWMs have the following parameters: ++ * Reference Zone, 1, 2, 3, etc. ++ * Spinup time, .05 sec ++ * PWM value at limit/low temp, 1 count ++ * PWM Frequency, 1. Hz ++ * PWM is Min or OFF below limit, flag ++ * Invert PWM output, flag ++ * ++ * Some chips filter the temp, others the fan. ++ * Filter constant (or disabled) .1 seconds ++ */ ++ ++/* These are the zone temperature range encodings */ ++static int lm85_range_map[] = { /* .1 degC */ ++ 20, 25, 33, 40, 50, 66, ++ 80, 100, 133, 160, 200, 266, ++ 320, 400, 533, 800 ++ }; ++static int RANGE_TO_REG( int range ) ++{ ++ int i; ++ ++ if( range >= lm85_range_map[15] ) { return 15 ; } ++ for( i = 0 ; i < 15 ; ++i ) ++ if( range <= lm85_range_map[i] ) ++ break ; ++ return( i & 0x0f ); ++} ++#define RANGE_FROM_REG(val) (lm85_range_map[(val)&0x0f]) ++ ++/* These are the Acoustic Enhancement, or Temperature smoothing encodings ++ * NOTE: The enable/disable bit is INCLUDED in these encodings as the ++ * MSB (bit 3, value 8). If the enable bit is 0, the encoded value ++ * is ignored, or set to 0. ++ */ ++static int lm85_smooth_map[] = { /* .1 sec */ ++ 350, 176, 118, 70, 44, 30, 16, 8 ++/* 35.4 * 1/1, 1/2, 1/3, 1/5, 1/8, 1/12, 1/24, 1/48 */ ++ }; ++static int SMOOTH_TO_REG( int smooth ) ++{ ++ int i; ++ ++ if( smooth <= 0 ) { return 0 ; } /* Disabled */ ++ for( i = 0 ; i < 7 ; ++i ) ++ if( smooth >= lm85_smooth_map[i] ) ++ break ; ++ return( (i & 0x07) | 0x08 ); ++} ++#define SMOOTH_FROM_REG(val) ((val)&0x08?lm85_smooth_map[(val)&0x07]:0) ++ ++/* These are the fan spinup delay time encodings */ ++static int lm85_spinup_map[] = { /* .1 sec */ ++ 0, 1, 2, 4, 7, 10, 20, 40 ++ }; ++static int SPINUP_TO_REG( int spinup ) ++{ ++ int i; ++ ++ if( spinup >= lm85_spinup_map[7] ) { return 7 ; } ++ for( i = 0 ; i < 7 ; ++i ) ++ if( spinup <= lm85_spinup_map[i] ) ++ break ; ++ return( i & 0x07 ); ++} ++#define SPINUP_FROM_REG(val) (lm85_spinup_map[(val)&0x07]) ++ ++/* These are the PWM frequency encodings */ ++static int lm85_freq_map[] = { /* .1 Hz */ ++ 100, 150, 230, 300, 380, 470, 620, 980 ++ }; ++static int FREQ_TO_REG( int freq ) ++{ ++ int i; ++ ++ if( freq >= lm85_freq_map[7] ) { return 7 ; } ++ for( i = 0 ; i < 7 ; ++i ) ++ if( freq <= lm85_freq_map[i] ) ++ break ; ++ return( i & 0x07 ); ++} ++#define FREQ_FROM_REG(val) (lm85_freq_map[(val)&0x07]) ++ ++/* Since we can't use strings, I'm abusing these numbers ++ * to stand in for the following meanings: ++ * 1 -- PWM responds to Zone 1 ++ * 2 -- PWM responds to Zone 2 ++ * 3 -- PWM responds to Zone 3 ++ * 23 -- PWM responds to the higher temp of Zone 2 or 3 ++ * 123 -- PWM responds to highest of Zone 1, 2, or 3 ++ * 0 -- PWM is always at 0% (ie, off) ++ * -1 -- PWM is always at 100% ++ * -2 -- PWM responds to manual control ++ */ ++static int lm85_zone_map[] = { 1, 2, 3, -1, 0, 23, 123, -2 }; ++static int ZONE_TO_REG( int zone ) ++{ ++ int i; ++ ++ for( i = 0 ; i <= 7 ; ++i ) ++ if( zone == lm85_zone_map[i] ) ++ break ; ++ if( i > 7 ) /* Not found. */ ++ i = 3; /* Always 100% */ ++ return( (i & 0x07)<<5 ); ++} ++#define ZONE_FROM_REG(val) (lm85_zone_map[((val)>>5)&0x07]) ++ ++#define HYST_TO_REG(val) (SENSORS_LIMIT((-(val)+5)/10,0,15)) ++#define HYST_FROM_REG(val) (-(val)*10) ++ ++#define OFFSET_TO_REG(val) (SENSORS_LIMIT((val)/25,-127,127)) ++#define OFFSET_FROM_REG(val) ((val)*25) ++ ++#define PPR_MASK(fan) (0x03<<(fan *2)) ++#define PPR_TO_REG(val,fan) (SENSORS_LIMIT((val)-1,0,3)<<(fan *2)) ++#define PPR_FROM_REG(val,fan) ((((val)>>(fan * 2))&0x03)+1) ++ ++/* sensors_vid.h defines vid_from_reg() */ ++#define VID_FROM_REG(val,vrm) (vid_from_reg((val),(vrm))) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++/* When converting to REG, we need to fixup the carry-over bit */ ++#define INTMASK_FROM_REG(val) (val) ++#define INTMASK_TO_REG(val) (SENSORS_LIMIT((val)|((val)&0xff00?0x80:0),0,65535)) ++ ++/* Unlike some other drivers we DO NOT set initial limits. Use ++ * the config file to set limits. Some users have reported ++ * motherboards shutting down when we set limits in a previous ++ * version of this driver. This may be caused by APM/ACPI ++ * detecting an out-of-limit condition when we had the wrong ++ * limits set. ++ */ ++ ++/* Typically used with Pentium 4 systems v9.1 VRM spec */ ++#define LM85_INIT_VRM 91 ++ ++/* Chip sampling rates ++ * ++ * Some sensors are not updated more frequently than once per second ++ * so it doesn't make sense to read them more often than that. ++ * We cache the results and return the saved data if the driver ++ * is called again before a second has elapsed. ++ * ++ * Also, there is significant configuration data for this chip ++ * given the automatic PWM fan control that is possible. There ++ * are about 47 bytes of config data to only 22 bytes of actual ++ * readings. So, we keep the config data up to date in the cache ++ * when it is written and only sample it once every 5 *minutes* ++ */ ++#define LM85_DATA_INTERVAL (1 * HZ) ++#define LM85_CONFIG_INTERVAL (5 * 60 * HZ) ++ ++/* For each registered LM85, we need to keep some data in memory. That ++ data is pointed to by client->data. The structure itself is ++ dynamically allocated, when a new lm85 client is allocated. */ ++ ++/* LM85 can automatically adjust fan speeds based on temperature ++ * This structure encapsulates an entire Zone config. There are ++ * three zones (one for each temperature input) on the lm85 ++ */ ++struct lm85_zone { ++ s8 limit; /* Low temp limit */ ++ u8 hyst; /* Low limit hysteresis. (0-15) */ ++ u8 range; /* Temp range, encoded */ ++ s8 critical; /* "All fans ON" temp limit */ ++}; ++ ++struct lm85_autofan { ++ u8 config; /* Register value */ ++ u8 freq; /* PWM frequency, encoded */ ++ u8 min_pwm; /* Minimum PWM value, encoded */ ++ u8 min_off; /* Min PWM or OFF below "limit", flag */ ++}; ++ ++struct lm85_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ int valid; /* !=0 if following fields are valid */ ++ unsigned long last_reading; /* In jiffies */ ++ unsigned long last_config; /* In jiffies */ ++ ++ u8 in[8]; /* Register value */ ++ u8 in_max[8]; /* Register value */ ++ u8 in_min[8]; /* Register value */ ++ s8 temp[3]; /* Register value */ ++ s8 temp_min[3]; /* Register value */ ++ s8 temp_max[3]; /* Register value */ ++ s8 temp_offset[3]; /* Register value */ ++ u16 fan[4]; /* Register value */ ++ u16 fan_min[4]; /* Register value */ ++ u8 pwm[3]; /* Register value */ ++ u8 spinup_ctl; /* Register encoding, combined */ ++ u8 tach_mode; /* Register encoding, combined */ ++ u16 extend_adc; /* Register value */ ++ u8 fan_ppr; /* Register value */ ++ u8 smooth[3]; /* Register encoding */ ++ u8 vid; /* Register value */ ++ u8 vrm; /* VRM version */ ++ u8 syncpwm3; /* Saved PWM3 for TACH 2,3,4 config */ ++ s8 oppoint[3]; /* Register value */ ++ u16 tmin_ctl; /* Register value */ ++ long therm_total; /* Cummulative therm count */ ++ long therm_ovfl; /* Count of therm overflows */ ++ u8 therm_limit; /* Register value */ ++ u32 alarms; /* Register encoding, combined */ ++ u32 alarm_mask; /* Register encoding, combined */ ++ struct lm85_autofan autofan[3]; ++ struct lm85_zone zone[3]; ++}; ++ ++static int lm85_attach_adapter(struct i2c_adapter *adapter); ++static int lm85_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int lm85_detach_client(struct i2c_client *client); ++static int lm85_read_value(struct i2c_client *client, u16 register); ++static int lm85_write_value(struct i2c_client *client, u16 register, int value); ++static void lm85_update_client(struct i2c_client *client); ++static void lm85_init_client(struct i2c_client *client); ++ ++ ++static void lm85_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++static void lm85_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_zone(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_pwm_config(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_pwm_zone(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_smooth(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static void lm85_spinup_ctl(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm85_tach_mode(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static void adm1027_tach_mode(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1027_temp_offset(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1027_fan_ppr(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adm1027_alarm_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static void adt7463_tmin_ctl(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void adt7463_therm_signal(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static void emc6d100_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static struct i2c_driver lm85_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM85 compatible sensor driver", ++ .id = I2C_DRIVERID_LM85, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = &lm85_attach_adapter, ++ .detach_client = &lm85_detach_client, ++}; ++ ++/* Unique ID assigned to each LM85 detected */ ++static int lm85_id = 0; ++ ++/* -- SENSORS SYSCTL START -- */ ++/* Common parameters */ ++#define LM85_SYSCTL_IN0 1000 ++#define LM85_SYSCTL_IN1 1001 ++#define LM85_SYSCTL_IN2 1002 ++#define LM85_SYSCTL_IN3 1003 ++#define LM85_SYSCTL_IN4 1004 ++#define LM85_SYSCTL_FAN1 1005 ++#define LM85_SYSCTL_FAN2 1006 ++#define LM85_SYSCTL_FAN3 1007 ++#define LM85_SYSCTL_FAN4 1008 ++#define LM85_SYSCTL_TEMP1 1009 ++#define LM85_SYSCTL_TEMP2 1010 ++#define LM85_SYSCTL_TEMP3 1011 ++#define LM85_SYSCTL_VID 1012 ++#define LM85_SYSCTL_ALARMS 1013 ++#define LM85_SYSCTL_PWM1 1014 ++#define LM85_SYSCTL_PWM2 1015 ++#define LM85_SYSCTL_PWM3 1016 ++#define LM85_SYSCTL_VRM 1017 ++#define LM85_SYSCTL_PWM_CFG1 1019 ++#define LM85_SYSCTL_PWM_CFG2 1020 ++#define LM85_SYSCTL_PWM_CFG3 1021 ++#define LM85_SYSCTL_PWM_ZONE1 1022 ++#define LM85_SYSCTL_PWM_ZONE2 1023 ++#define LM85_SYSCTL_PWM_ZONE3 1024 ++#define LM85_SYSCTL_ZONE1 1025 ++#define LM85_SYSCTL_ZONE2 1026 ++#define LM85_SYSCTL_ZONE3 1027 ++#define LM85_SYSCTL_SMOOTH1 1028 ++#define LM85_SYSCTL_SMOOTH2 1029 ++#define LM85_SYSCTL_SMOOTH3 1030 ++ ++/* Vendor specific values */ ++#define LM85_SYSCTL_SPINUP_CTL 1100 ++#define LM85_SYSCTL_TACH_MODE 1101 ++ ++/* Analog Devices variant of the LM85 */ ++#define ADM1027_SYSCTL_TACH_MODE 1200 ++#define ADM1027_SYSCTL_TEMP_OFFSET1 1201 ++#define ADM1027_SYSCTL_TEMP_OFFSET2 1202 ++#define ADM1027_SYSCTL_TEMP_OFFSET3 1203 ++#define ADM1027_SYSCTL_FAN_PPR 1204 ++#define ADM1027_SYSCTL_ALARM_MASK 1205 ++ ++/* Analog Devices variant of the LM85/ADM1027 */ ++#define ADT7463_SYSCTL_TMIN_CTL1 1300 ++#define ADT7463_SYSCTL_TMIN_CTL2 1301 ++#define ADT7463_SYSCTL_TMIN_CTL3 1302 ++#define ADT7463_SYSCTL_THERM_SIGNAL 1303 ++ ++/* SMSC variant of the LM85 */ ++#define EMC6D100_SYSCTL_IN5 1400 ++#define EMC6D100_SYSCTL_IN6 1401 ++#define EMC6D100_SYSCTL_IN7 1402 ++ ++#define LM85_ALARM_IN0 0x0001 ++#define LM85_ALARM_IN1 0x0002 ++#define LM85_ALARM_IN2 0x0004 ++#define LM85_ALARM_IN3 0x0008 ++#define LM85_ALARM_TEMP1 0x0010 ++#define LM85_ALARM_TEMP2 0x0020 ++#define LM85_ALARM_TEMP3 0x0040 ++#define LM85_ALARM_ALARM2 0x0080 ++#define LM85_ALARM_IN4 0x0100 ++#define LM85_ALARM_RESERVED 0x0200 ++#define LM85_ALARM_FAN1 0x0400 ++#define LM85_ALARM_FAN2 0x0800 ++#define LM85_ALARM_FAN3 0x1000 ++#define LM85_ALARM_FAN4 0x2000 ++#define LM85_ALARM_TEMP1_FAULT 0x4000 ++#define LM85_ALARM_TEMP3_FAULT 0x08000 ++#define LM85_ALARM_IN6 0x10000 ++#define LM85_ALARM_IN7 0x20000 ++#define LM85_ALARM_IN5 0x40000 ++/* -- SENSORS SYSCTL END -- */ ++ ++/* The /proc/sys entries */ ++/* These files are created for each detected LM85. This is just a template; ++ * The actual list is built from this and additional per-chip ++ * custom lists below. Note the XXX_LEN macros. These must be ++ * compile time constants because they will be used to allocate ++ * space for the final template passed to i2c_register_entry. ++ * We depend on the ability of GCC to evaluate expressions at ++ * compile time to turn these expressions into compile time ++ * constants, but this can generate a warning. ++ */ ++static ctl_table lm85_common[] = { ++ {LM85_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_in}, ++ {LM85_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_in}, ++ {LM85_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_in}, ++ {LM85_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_in}, ++ {LM85_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_in}, ++ {LM85_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_fan}, ++ {LM85_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_fan}, ++ {LM85_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_fan}, ++ {LM85_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_fan}, ++ {LM85_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_temp}, ++ {LM85_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_temp}, ++ {LM85_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_temp}, ++ {LM85_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_vid}, ++ {LM85_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_vrm}, ++ {LM85_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_alarms}, ++ {LM85_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_pwm}, ++ {LM85_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_pwm}, ++ {LM85_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_pwm}, ++ {LM85_SYSCTL_PWM_CFG1, "pwm1_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_pwm_config}, ++ {LM85_SYSCTL_PWM_CFG2, "pwm2_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_pwm_config}, ++ {LM85_SYSCTL_PWM_CFG3, "pwm3_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_pwm_config}, ++ {LM85_SYSCTL_PWM_ZONE1, "pwm1_zone", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_pwm_zone}, ++ {LM85_SYSCTL_PWM_ZONE2, "pwm2_zone", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_pwm_zone}, ++ {LM85_SYSCTL_PWM_ZONE3, "pwm3_zone", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_pwm_zone}, ++ {LM85_SYSCTL_ZONE1, "zone1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_zone}, ++ {LM85_SYSCTL_ZONE2, "zone2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_zone}, ++ {LM85_SYSCTL_ZONE3, "zone3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_zone}, ++ {LM85_SYSCTL_SMOOTH1, "smooth1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_smooth}, ++ {LM85_SYSCTL_SMOOTH2, "smooth2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_smooth}, ++ {LM85_SYSCTL_SMOOTH3, "smooth3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm85_smooth}, ++ {0} ++}; ++#define CTLTBL_COMMON (sizeof(lm85_common)/sizeof(lm85_common[0])) ++ ++/* NOTE: tach_mode is a shared name, but implemented with ++ * different functions ++ */ ++static ctl_table lm85_specific[] = { ++ {LM85_SYSCTL_SPINUP_CTL, "spinup_ctl", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_spinup_ctl}, ++ {LM85_SYSCTL_TACH_MODE, "tach_mode", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_tach_mode}, ++/* {0} The doc generator needs this. */ ++}; ++#define CTLTBL_LM85 (sizeof(lm85_specific)/sizeof(lm85_specific[0])) ++ ++static ctl_table adm1027_specific[] = { ++ {ADM1027_SYSCTL_TACH_MODE, "tach_mode", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_tach_mode}, ++ {ADM1027_SYSCTL_TEMP_OFFSET1, "temp1_offset", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_temp_offset}, ++ {ADM1027_SYSCTL_TEMP_OFFSET2, "temp2_offset", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_temp_offset}, ++ {ADM1027_SYSCTL_TEMP_OFFSET3, "temp3_offset", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_temp_offset}, ++ {ADM1027_SYSCTL_FAN_PPR, "fan_ppr", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_fan_ppr}, ++ {ADM1027_SYSCTL_ALARM_MASK, "alarm_mask", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_alarm_mask}, ++/* {0} The doc generator needs this. */ ++}; ++#define CTLTBL_ADM1027 (sizeof(adm1027_specific)/sizeof(adm1027_specific[0])) ++ ++static ctl_table adt7463_specific[] = { ++ {ADT7463_SYSCTL_TMIN_CTL1, "tmin_ctl1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_tmin_ctl}, ++ {ADT7463_SYSCTL_TMIN_CTL2, "tmin_ctl2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_tmin_ctl}, ++ {ADT7463_SYSCTL_TMIN_CTL3, "tmin_ctl3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_tmin_ctl}, ++ {ADT7463_SYSCTL_THERM_SIGNAL, "therm_signal", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_therm_signal}, ++/* {0} The doc generator needs this. */ ++}; ++#define CTLTBL_ADT7463 (sizeof(adt7463_specific)/sizeof(adt7463_specific[0])) ++ ++static ctl_table emc6d100_specific[] = { ++ {EMC6D100_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &emc6d100_in}, ++ {EMC6D100_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &emc6d100_in}, ++ {EMC6D100_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &emc6d100_in}, ++/* {0} The doc generator needs this. */ ++}; ++#define CTLTBL_EMC6D100 (sizeof(emc6d100_specific)/sizeof(emc6d100_specific[0])) ++ ++ ++#define MAX2(a,b) ((a)>(b)?(a):(b)) ++#define MAX3(a,b,c) ((a)>(b)?MAX2((a),(c)):MAX2((b),(c))) ++#define MAX4(a,b,c,d) ((a)>(b)?MAX3((a),(c),(d)):MAX3((b),(c),(d))) ++ ++#define CTLTBL_MAX (CTLTBL_COMMON + MAX3(CTLTBL_LM85, CTLTBL_ADM1027+CTLTBL_ADT7463, CTLTBL_EMC6D100)) ++ ++/* This function is called when: ++ * lm85_driver is inserted (when this module is loaded), for each ++ available adapter ++ * when a new adapter is inserted (and lm85_driver is still present) */ ++int lm85_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, lm85_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int lm85_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ int company, verstep ; ++ struct i2c_client *new_client; ++ struct lm85_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ struct ctl_table template[CTLTBL_MAX] ; ++ int template_used ; ++ ++ if (i2c_is_isa_adapter(adapter)) { ++ /* This chip has no ISA interface */ ++ goto ERROR0 ; ++ }; ++ ++ if (!i2c_check_functionality(adapter, ++ I2C_FUNC_SMBUS_BYTE_DATA)) { ++ /* We need to be able to do byte I/O */ ++ goto ERROR0 ; ++ }; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access lm85_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct lm85_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &lm85_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ company = lm85_read_value(new_client, LM85_REG_COMPANY); ++ verstep = lm85_read_value(new_client, LM85_REG_VERSTEP); ++ ++#ifdef DEBUG ++ printk("lm85: Detecting device at %d,0x%02x with" ++ " COMPANY: 0x%02x and VERSTEP: 0x%02x\n", ++ i2c_adapter_id(new_client->adapter), new_client->addr, ++ company, verstep ++ ); ++#endif ++ ++ /* If auto-detecting, Determine the chip type. */ ++ if (kind <= 0) { ++#ifdef DEBUG ++ printk("lm85: Autodetecting device at %d,0x%02x ...\n", ++ i2c_adapter_id(adapter), address ); ++#endif ++ if( company == LM85_COMPANY_NATIONAL ++ && verstep == LM85_VERSTEP_LM85C ) { ++ kind = lm85c ; ++ } else if( company == LM85_COMPANY_NATIONAL ++ && verstep == LM85_VERSTEP_LM85B ) { ++ kind = lm85b ; ++ } else if( company == LM85_COMPANY_NATIONAL ++ && (verstep & LM85_VERSTEP_VMASK) == LM85_VERSTEP_GENERIC) { ++ printk("lm85: Detected National Semiconductor chip\n"); ++ printk("lm85: Unrecgonized version/stepping 0x%02x" ++ " Defaulting to Generic LM85.\n", verstep ); ++ kind = any_chip ; ++ } else if( company == LM85_COMPANY_ANALOG_DEV ++ && verstep == LM85_VERSTEP_ADM1027 ) { ++ kind = adm1027 ; ++ } else if( company == LM85_COMPANY_ANALOG_DEV ++ && verstep == LM85_VERSTEP_ADT7463 ) { ++ kind = adt7463 ; ++ } else if( company == LM85_COMPANY_ANALOG_DEV ++ && (verstep & LM85_VERSTEP_VMASK) == LM85_VERSTEP_GENERIC) { ++ printk("lm85: Detected Analog Devices chip\n"); ++ printk("lm85: Unrecgonized version/stepping 0x%02x" ++ " Defaulting to Generic LM85.\n", verstep ); ++ kind = any_chip ; ++ } else if( company == LM85_COMPANY_SMSC ++ && (verstep == LM85_VERSTEP_EMC6D100_A0 ++ || verstep == LM85_VERSTEP_EMC6D100_A1) ) { ++ /* Unfortunately, we can't tell a '100 from a '101 ++ * from the registers. Since a '101 is a '100 ++ * in a package with fewer pins and therefore no ++ * 3.3V, 1.5V or 1.8V inputs, perhaps if those ++ * inputs read 0, then it's a '101. ++ */ ++ kind = emc6d100 ; ++ } else if( company == LM85_COMPANY_SMSC ++ && (verstep & LM85_VERSTEP_VMASK) == LM85_VERSTEP_GENERIC) { ++ printk("lm85: Detected SMSC chip\n"); ++ printk("lm85: Unrecognized version/stepping 0x%02x" ++ " Defaulting to Generic LM85.\n", verstep ); ++ kind = any_chip ; ++ } else if( kind == any_chip ++ && (verstep & LM85_VERSTEP_VMASK) == LM85_VERSTEP_GENERIC) { ++ printk("lm85: Generic LM85 Version 6 detected\n"); ++ /* Leave kind as "any_chip" */ ++ } else { ++#ifdef DEBUG ++ printk("lm85: Autodetection failed\n"); ++#endif ++ /* Not an LM85 ... */ ++ if( kind == any_chip ) { /* User used force=x,y */ ++ printk("lm85: Generic LM85 Version 6 not" ++ " found at %d,0x%02x. Try force_lm85c.\n", ++ i2c_adapter_id(adapter), address ); ++ } ++ err = 0 ; ++ goto ERROR1; ++ } ++ } ++ ++ /* Fill in the chip specific driver values */ ++ switch (kind) { ++ case any_chip : ++ type_name = "lm85"; ++ strcpy(new_client->name, "Generic LM85"); ++ template_used = 0 ; ++ break ; ++ case lm85b : ++ type_name = "lm85b"; ++ strcpy(new_client->name, "National LM85-B"); ++ memcpy( template, lm85_specific, sizeof(lm85_specific) ); ++ template_used = CTLTBL_LM85 ; ++ break ; ++ case lm85c : ++ type_name = "lm85c"; ++ strcpy(new_client->name, "National LM85-C"); ++ memcpy( template, lm85_specific, sizeof(lm85_specific) ); ++ template_used = CTLTBL_LM85 ; ++ break ; ++ case adm1027 : ++ type_name = "adm1027"; ++ strcpy(new_client->name, "Analog Devices ADM1027"); ++ memcpy( template, adm1027_specific, sizeof(adm1027_specific) ); ++ template_used = CTLTBL_ADM1027 ; ++ break ; ++ case adt7463 : ++ type_name = "adt7463"; ++ strcpy(new_client->name, "Analog Devices ADT7463"); ++ memcpy( template, adt7463_specific, sizeof(adt7463_specific) ); ++ template_used = CTLTBL_ADT7463 ; ++ memcpy( template+template_used, adm1027_specific, sizeof(adm1027_specific) ); ++ template_used += CTLTBL_ADM1027 ; ++ break ; ++ case emc6d100 : ++ type_name = "emc6d100"; ++ strcpy(new_client->name, "SMSC EMC6D100"); ++ memcpy(template, emc6d100_specific, sizeof(emc6d100_specific)); ++ template_used = CTLTBL_EMC6D100 ; ++ break ; ++ default : ++ printk("lm85: Internal error, invalid kind (%d)!", kind); ++ err = -EFAULT ; ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields */ ++ new_client->id = lm85_id++; ++ printk("lm85: Assigning ID %d to %s at %d,0x%02x\n", ++ new_client->id, new_client->name, ++ i2c_adapter_id(new_client->adapter), ++ new_client->addr ++ ); ++ ++ /* Housekeeping values */ ++ data->type = kind; ++ data->valid = 0; ++ ++ /* Set the VRM version */ ++ data->vrm = LM85_INIT_VRM ; ++ ++ /* Zero the accumulators */ ++ data->therm_total = 0; ++ data->therm_ovfl = 0; ++ ++ init_MUTEX(&data->update_lock); ++ ++ /* Initialize the LM85 chip */ ++ lm85_init_client(new_client); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR1; ++ ++ /* Finish out the template */ ++ memcpy( template + template_used, lm85_common, sizeof(lm85_common) ); ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ template)) < 0) { ++ err = i; ++ goto ERROR2; ++ } ++ data->sysctl_id = i; ++ ++ return 0; ++ ++ /* Error out and cleanup code */ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++int lm85_detach_client(struct i2c_client *client) ++{ ++ int err; ++ int id ; ++ ++ id = client->id; ++ i2c_deregister_entry(((struct lm85_data *)(client->data))->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk("lm85(%d): Client deregistration failed," ++ " client not detached.\n", id ); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++int lm85_read_value(struct i2c_client *client, u16 reg) ++{ ++ int res; ++ ++ /* What size location is it? */ ++ switch( reg ) { ++ case LM85_REG_FAN(0) : /* Read WORD data */ ++ case LM85_REG_FAN(1) : ++ case LM85_REG_FAN(2) : ++ case LM85_REG_FAN(3) : ++ case LM85_REG_FAN_MIN(0) : ++ case LM85_REG_FAN_MIN(1) : ++ case LM85_REG_FAN_MIN(2) : ++ case LM85_REG_FAN_MIN(3) : ++ case LM85_REG_ALARM : /* Read ALARM1 and ALARM2 */ ++ case ADM1027_REG_INTMASK : /* Read MASK1 and MASK2 */ ++ case ADM1027_REG_EXTEND_ADC : /* Read ADC1 and ADC2 */ ++ reg &= 0xff ; /* Pseudo words have address + 0x0100 */ ++ res = i2c_smbus_read_byte_data(client, reg) & 0xff ; ++ res |= (i2c_smbus_read_byte_data(client, reg+1) & 0xff) << 8 ; ++ break ; ++ case ADT7463_REG_TMIN_CTL : /* Read WORD MSB, LSB */ ++ reg &= 0xff ; /* Pseudo words have address + 0x0100 */ ++ res = (i2c_smbus_read_byte_data(client, reg) & 0xff) << 8 ; ++ res |= i2c_smbus_read_byte_data(client, reg+1) & 0xff ; ++ break ; ++ default: /* Read BYTE data */ ++ res = i2c_smbus_read_byte_data(client, reg & 0xff) & 0xff ; ++ break ; ++ } ++ ++ return res ; ++} ++ ++int lm85_write_value(struct i2c_client *client, u16 reg, int value) ++{ ++ int res ; ++ ++ switch( reg ) { ++ case LM85_REG_FAN(0) : /* Write WORD data */ ++ case LM85_REG_FAN(1) : ++ case LM85_REG_FAN(2) : ++ case LM85_REG_FAN(3) : ++ case LM85_REG_FAN_MIN(0) : ++ case LM85_REG_FAN_MIN(1) : ++ case LM85_REG_FAN_MIN(2) : ++ case LM85_REG_FAN_MIN(3) : ++ case ADM1027_REG_INTMASK : ++ /* NOTE: ALARM and ADC are read only, so not included here */ ++ reg &= 0xff ; /* Pseudo words have address + 0x0100 */ ++ res = i2c_smbus_write_byte_data(client, reg, value & 0xff) ; ++ res |= i2c_smbus_write_byte_data(client, reg+1, (value>>8) & 0xff) ; ++ break ; ++ case ADT7463_REG_TMIN_CTL : /* Write WORD MSB, LSB */ ++ reg &= 0xff ; /* Pseudo words have address + 0x0100 */ ++ res = i2c_smbus_write_byte_data(client, reg, (value>>8) & 0xff); ++ res |= i2c_smbus_write_byte_data(client, reg+1, value & 0xff) ; ++ break ; ++ default: /* Write BYTE data */ ++ res = i2c_smbus_write_byte_data(client, reg & 0xff, value); ++ break ; ++ } ++ ++ return res ; ++} ++ ++/* Called when we have found a new LM85. It should set limits, etc. */ ++void lm85_init_client(struct i2c_client *client) ++{ ++ int value; ++ struct lm85_data *data = client->data; ++ ++#ifdef DEBUG ++ printk("lm85(%d): Initializing device\n", client->id); ++#endif ++ ++ /* Warn if part was not "READY" */ ++ value = lm85_read_value(client, LM85_REG_CONFIG); ++#ifdef DEBUG ++ printk("lm85(%d): LM85_REG_CONFIG is: 0x%02x\n", client->id, value ); ++#endif ++ if( value & 0x02 ) { ++ printk("lm85(%d): Client (%d,0x%02x) config is locked.\n", ++ client->id, ++ i2c_adapter_id(client->adapter), client->addr ); ++ }; ++ if( ! (value & 0x04) ) { ++ printk("lm85(%d): Client (%d,0x%02x) is not ready.\n", ++ client->id, ++ i2c_adapter_id(client->adapter), client->addr ); ++ }; ++ if( (data->type == adm1027 || data->type == adt7463) ++ && (value & 0x10) ++ ) { ++ printk("lm85(%d): Client (%d,0x%02x) VxI mode is set. " ++ "Please report this to the lm85 maintainer.\n", ++ client->id, ++ i2c_adapter_id(client->adapter), client->addr ); ++ }; ++ ++ /* See if SYNC to PWM3 is set */ ++ if( data->type == adt7463 ++ && (lm85_read_value(client, LM85_REG_AFAN_SPIKE1) & 0x10) ++ ) { ++ printk("lm85(%d): Sync to PWM3 is set. Expect PWM3 " ++ "to control fans 2, 3, and 4\n", ++ client->id ); ++ }; ++ ++ /* See if PWM2 is #SMBALERT */ ++ if( (data->type == adm1027 || data->type == adt7463) ++ && (lm85_read_value(client, ADM1027_REG_CONFIG3) & 0x01) ++ ) { ++ printk("lm85(%d): PWM2 is SMBALERT. PWM2 not available.\n", ++ client->id ); ++ }; ++ ++ /* Check if 2.5V and 5V inputs are reconfigured */ ++ if( data->type == adt7463 ) { ++ value = lm85_read_value(client, ADT7463_REG_CONFIG4); ++ if( value & 0x01 ) { ++ printk("lm85(%d): 2.5V input (in0) is SMBALERT. " ++ "in0 not available.\n", client->id ); ++ }; ++ if( value & 0x02 ) { ++ printk("lm85(%d): 5V input (in3) is THERM. " ++ "in3 not available.\n", client->id ); ++ } ++ }; ++ ++ /* FIXME? Display EMC6D100 config info? */ ++ ++ /* WE INTENTIONALLY make no changes to the limits, ++ * offsets, pwms, fans and zones. If they were ++ * configured, we don't want to mess with them. ++ * If they weren't, the default is 100% PWM, no ++ * control and will suffice until 'sensors -s' ++ * can be run by the user. ++ */ ++ ++ /* Start monitoring */ ++ value = lm85_read_value(client, LM85_REG_CONFIG); ++ /* Try to clear LOCK, Set START, save everything else */ ++ value = ((value & ~ 0x02) | 0x01) & 0xff ; ++#ifdef DEBUG ++ printk("lm85(%d): Setting CONFIG to: 0x%02x\n", client->id, value ); ++#endif ++ lm85_write_value(client, LM85_REG_CONFIG, value); ++ ++} ++ ++void lm85_update_client(struct i2c_client *client) ++{ ++ struct lm85_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if (!data->valid ++ || (jiffies - data->last_reading > LM85_DATA_INTERVAL )) { ++ /* Things that change quickly */ ++ ++#ifdef DEBUG ++ printk("lm85(%d): Reading sensor values\n", client->id); ++#endif ++ /* Have to read extended bits first to "freeze" the ++ * more significant bits that are read later. ++ */ ++ switch( data->type ) { ++ case adm1027 : ++ case adt7463 : ++ data->extend_adc = ++ lm85_read_value(client, ADM1027_REG_EXTEND_ADC); ++ break ; ++ default : ++ data->extend_adc = 0 ; ++ break ; ++ } ++ ++ for (i = 0; i <= 4; ++i) { ++ data->in[i] = ++ lm85_read_value(client, LM85_REG_IN(i)); ++ } ++ ++ for (i = 0; i <= 3; ++i) { ++ data->fan[i] = ++ lm85_read_value(client, LM85_REG_FAN(i)); ++ } ++ ++ for (i = 0; i <= 2; ++i) { ++ data->temp[i] = ++ lm85_read_value(client, LM85_REG_TEMP(i)); ++ } ++ ++ for (i = 0; i <= 2; ++i) { ++ data->pwm[i] = ++ lm85_read_value(client, LM85_REG_PWM(i)); ++ } ++ ++ data->alarms = lm85_read_value(client, LM85_REG_ALARM); ++ ++ switch( ((struct lm85_data *)(client->data))->type ) { ++ case adt7463 : ++ /* REG_THERM code duplicated in therm_signal() */ ++ i = lm85_read_value(client, ADT7463_REG_THERM); ++ if( data->therm_total < LONG_MAX - 256 ) { ++ data->therm_total += i ; ++ } ++ if( i >= 255 ) { ++ ++data->therm_ovfl ; ++ } ++ break ; ++ case emc6d100 : ++ /* Three more voltage sensors */ ++ for (i = 5; i <= 7; ++i) { ++ data->in[i] = ++ lm85_read_value(client, EMC6D100_REG_IN(i)); ++ } ++ /* More alarm bits */ ++ data->alarms |= ++ lm85_read_value(client, EMC6D100_REG_ALARM3) << 16; ++ ++ break ; ++ default : break ; /* no warnings */ ++ } ++ ++ data->last_reading = jiffies ; ++ }; /* last_reading */ ++ ++ if (!data->valid ++ || (jiffies - data->last_config > LM85_CONFIG_INTERVAL) ) { ++ /* Things that don't change often */ ++ ++#ifdef DEBUG ++ printk("lm85(%d): Reading config values\n", client->id); ++#endif ++ for (i = 0; i <= 4; ++i) { ++ data->in_min[i] = ++ lm85_read_value(client, LM85_REG_IN_MIN(i)); ++ data->in_max[i] = ++ lm85_read_value(client, LM85_REG_IN_MAX(i)); ++ } ++ ++ for (i = 0; i <= 3; ++i) { ++ data->fan_min[i] = ++ lm85_read_value(client, LM85_REG_FAN_MIN(i)); ++ } ++ ++ for (i = 0; i <= 2; ++i) { ++ data->temp_min[i] = ++ lm85_read_value(client, LM85_REG_TEMP_MIN(i)); ++ data->temp_max[i] = ++ lm85_read_value(client, LM85_REG_TEMP_MAX(i)); ++ } ++ ++ data->vid = lm85_read_value(client, LM85_REG_VID); ++ ++ for (i = 0; i <= 2; ++i) { ++ int val ; ++ data->autofan[i].config = ++ lm85_read_value(client, LM85_REG_AFAN_CONFIG(i)); ++ val = lm85_read_value(client, LM85_REG_AFAN_RANGE(i)); ++ data->autofan[i].freq = val & 0x07 ; ++ data->zone[i].range = (val >> 4) & 0x0f ; ++ data->autofan[i].min_pwm = ++ lm85_read_value(client, LM85_REG_AFAN_MINPWM(i)); ++ data->zone[i].limit = ++ lm85_read_value(client, LM85_REG_AFAN_LIMIT(i)); ++ data->zone[i].critical = ++ lm85_read_value(client, LM85_REG_AFAN_CRITICAL(i)); ++ } ++ ++ i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); ++ data->smooth[0] = i & 0x0f ; ++ data->syncpwm3 = i & 0x10 ; /* Save PWM3 config */ ++ data->autofan[0].min_off = i & 0x20 ; ++ data->autofan[1].min_off = i & 0x40 ; ++ data->autofan[2].min_off = i & 0x80 ; ++ i = lm85_read_value(client, LM85_REG_AFAN_SPIKE2); ++ data->smooth[1] = (i>>4) & 0x0f ; ++ data->smooth[2] = i & 0x0f ; ++ ++ i = lm85_read_value(client, LM85_REG_AFAN_HYST1); ++ data->zone[0].hyst = (i>>4) & 0x0f ; ++ data->zone[1].hyst = i & 0x0f ; ++ ++ i = lm85_read_value(client, LM85_REG_AFAN_HYST2); ++ data->zone[2].hyst = (i>>4) & 0x0f ; ++ ++ switch( ((struct lm85_data *)(client->data))->type ) { ++ case lm85b : ++ case lm85c : ++ data->tach_mode = lm85_read_value(client, ++ LM85_REG_TACH_MODE ); ++ data->spinup_ctl = lm85_read_value(client, ++ LM85_REG_SPINUP_CTL ); ++ break ; ++ case adt7463 : ++ for (i = 0; i <= 2; ++i) { ++ data->oppoint[i] = lm85_read_value(client, ++ ADT7463_REG_OPPOINT(i) ); ++ } ++ data->tmin_ctl = lm85_read_value(client, ++ ADT7463_REG_TMIN_CTL ); ++ data->therm_limit = lm85_read_value(client, ++ ADT7463_REG_THERM_LIMIT ); ++ /* FALL THROUGH */ ++ case adm1027 : ++ for (i = 0; i <= 2; ++i) { ++ data->temp_offset[i] = lm85_read_value(client, ++ ADM1027_REG_TEMP_OFFSET(i) ); ++ } ++ data->tach_mode = lm85_read_value(client, ++ ADM1027_REG_CONFIG3 ); ++ data->fan_ppr = lm85_read_value(client, ++ ADM1027_REG_FAN_PPR ); ++ data->alarm_mask = lm85_read_value(client, ++ ADM1027_REG_INTMASK ); ++ break ; ++ case emc6d100 : ++ for (i = 5; i <= 7; ++i) { ++ data->in_min[i] = ++ lm85_read_value(client, EMC6D100_REG_IN_MIN(i)); ++ data->in_max[i] = ++ lm85_read_value(client, EMC6D100_REG_IN_MAX(i)); ++ } ++ break ; ++ default : break ; /* no warnings */ ++ } ++ ++ data->last_config = jiffies; ++ }; /* last_config */ ++ ++ data->valid = 1; ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the data ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ */ ++void lm85_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_IN0; ++ ++ if (nr < 0 || nr > 4) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; /* 1.000 */ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ int ext = 0 ; ++ lm85_update_client(client); ++ ext = EXT_FROM_REG(data->extend_adc, nr); ++ results[0] = INS_FROM_REG(nr,data->in_min[nr]); ++ results[1] = INS_FROM_REG(nr,data->in_max[nr]); ++ results[2] = INSEXT_FROM_REG(nr,data->in[nr],ext); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->in_max[nr] = INS_TO_REG(nr,results[1]); ++ lm85_write_value(client, LM85_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ if (*nrels_mag > 0) { ++ data->in_min[nr] = INS_TO_REG(nr,results[0]); ++ lm85_write_value(client, LM85_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_FAN1 ; ++ ++ if (nr < 0 || nr > 3) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr]); ++ results[1] = FAN_FROM_REG(data->fan[nr]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->fan_min[nr] = FAN_TO_REG(results[0]); ++ lm85_write_value(client, LM85_REG_FAN_MIN(nr), ++ data->fan_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++ ++void lm85_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_TEMP1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ int ext = 0 ; ++ lm85_update_client(client); ++ ++ /* +5 for offset of temp data in ext reg */ ++ ext = EXT_FROM_REG(data->extend_adc, nr+5); ++ ++ results[0] = TEMP_FROM_REG(data->temp_min[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_max[nr]); ++ results[2] = TEMPEXT_FROM_REG(data->temp[nr],ext); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->temp_max[nr] = TEMP_TO_REG(results[1]); ++ lm85_write_value(client, LM85_REG_TEMP_MAX(nr), ++ data->temp_max[nr]); ++ } ++ if (*nrels_mag > 0) { ++ data->temp_min[nr] = TEMP_TO_REG(results[0]); ++ lm85_write_value(client, LM85_REG_TEMP_MIN(nr), ++ data->temp_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_PWM1 ; ++ int pwm_zone ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm[nr]); ++ pwm_zone = ZONE_FROM_REG(data->autofan[nr].config); ++ /* PWM "enabled" if not off (0) nor on (-1) */ ++ results[1] = pwm_zone != 0 && pwm_zone != -1 ; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ /* PWM enable is read-only */ ++ if (*nrels_mag > 0) { ++ data->pwm[nr] = PWM_TO_REG(results[0]); ++ lm85_write_value(client, LM85_REG_PWM(nr), ++ data->pwm[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ ++ if( ctl_name != LM85_SYSCTL_VID ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = VID_FROM_REG((data->vid)&0x3f,data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void lm85_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ ++ if( ctl_name != LM85_SYSCTL_VRM ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm ; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->vrm = results[0] ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ ++ if( ctl_name != LM85_SYSCTL_ALARMS ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void lm85_spinup_ctl(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int old; ++ ++ if( ctl_name != LM85_SYSCTL_SPINUP_CTL ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = (data->spinup_ctl & 1) != 0 ; ++ results[1] = (data->spinup_ctl & 2) != 0 ; ++ results[2] = (data->spinup_ctl & 4) != 0 ; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ old = data->spinup_ctl ; ++ if (*nrels_mag > 2) { ++ old = (old & (~4)) | (results[2]?4:0) ; ++ } ++ if (*nrels_mag > 1) { ++ old = (old & (~2)) | (results[1]?2:0) ; ++ } ++ if (*nrels_mag > 0) { ++ old = (old & (~1)) | (results[0]?1:0) ; ++ lm85_write_value(client, LM85_REG_SPINUP_CTL, old); ++ data->spinup_ctl = old ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_tach_mode(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int old; ++ ++ /* Tach Mode 1, Tach Mode 2, Tach Mode 3 & 4 */ ++ ++ if( ctl_name != LM85_SYSCTL_TACH_MODE ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = (data->tach_mode & 0x03) ; ++ results[1] = (data->tach_mode & 0x0c) >> 2 ; ++ results[2] = (data->tach_mode & 0x30) >> 4 ; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ old = data->tach_mode ; ++ if (*nrels_mag > 2) { ++ old = (old & (~0x30)) | ((results[2]&3) << 4) ; ++ } ++ if (*nrels_mag > 1) { ++ old = (old & (~0x0c)) | ((results[1]&3) << 2) ; ++ } ++ if (*nrels_mag > 0) { ++ old = (old & (~0x03)) | (results[0]&3) ; ++ lm85_write_value(client, LM85_REG_TACH_MODE, old); ++ data->tach_mode = old ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1027_tach_mode(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int old; ++ ++ /* Tach/DC 1, Tach/DC 2, Tach/DC 3, Tach/DC 4 */ ++ ++ if( ctl_name != ADM1027_SYSCTL_TACH_MODE ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = (data->tach_mode & 0x10) != 0 ; ++ results[1] = (data->tach_mode & 0x20) != 0 ; ++ results[2] = (data->tach_mode & 0x40) != 0 ; ++ results[3] = (data->tach_mode & 0x80) != 0 ; ++ *nrels_mag = 4; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ old = data->tach_mode ; ++ if (*nrels_mag > 3) { ++ old = (old & (~0x80)) | (results[3] ? 0x80 : 0) ; ++ } ++ if (*nrels_mag > 2) { ++ old = (old & (~0x40)) | (results[2] ? 0x40 : 0) ; ++ } ++ if (*nrels_mag > 1) { ++ old = (old & (~0x20)) | (results[1] ? 0x20 : 0) ; ++ } ++ if (*nrels_mag > 0) { ++ old = (old & (~0x10)) | (results[0] ? 0x10 : 0) ; ++ ++ /* Enable fast measurements if any TACH's are DC */ ++ old = (old & (~0x08)) | ((old&0xf0) ? 0x08 : 0) ; ++ ++ lm85_write_value(client, ADM1027_REG_CONFIG3, old); ++ data->tach_mode = old ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_pwm_config(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_PWM_CFG1 ; ++ ++ /* Spinup, min PWM, PWM Frequency, min below limit, Invert */ ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ ++ results[0] = SPINUP_FROM_REG(data->autofan[nr].config); ++ results[1] = PWM_FROM_REG(data->autofan[nr].min_pwm)*10; ++ results[2] = FREQ_FROM_REG(data->autofan[nr].freq); ++ results[3] = data->autofan[nr].min_off ? 10 : 0 ; ++ results[4] = (data->autofan[nr].config & 0x10) ? 10 : 0 ; ++ *nrels_mag = 5; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ int old_config ; ++ ++ down(&data->update_lock); ++ old_config = data->autofan[nr].config ; ++ if (*nrels_mag > 4) { ++ old_config = (old_config & (~0x10)) | (results[4]?0x10:0) ; ++ } ++ if (*nrels_mag > 3) { ++ data->autofan[nr].min_off = results[3] != 0 ; ++ lm85_write_value(client, LM85_REG_AFAN_SPIKE1, ++ data->smooth[0] ++ | data->syncpwm3 ++ | (data->autofan[0].min_off ? 0x20 : 0) ++ | (data->autofan[1].min_off ? 0x40 : 0) ++ | (data->autofan[2].min_off ? 0x80 : 0) ++ ); ++ } ++ if (*nrels_mag > 2) { ++ data->autofan[nr].freq = FREQ_TO_REG(results[2]) ; ++ lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), ++ (data->zone[nr].range << 4) ++ | data->autofan[nr].freq ++ ); ++ } ++ if (*nrels_mag > 1) { ++ data->autofan[nr].min_pwm = PWM_TO_REG((results[1]+5)/10); ++ lm85_write_value(client, LM85_REG_AFAN_MINPWM(nr), ++ data->autofan[nr].min_pwm ++ ); ++ } ++ if (*nrels_mag > 0) { ++ old_config = (old_config & (~0x07)) | SPINUP_TO_REG(results[0]) ; ++ lm85_write_value(client, LM85_REG_AFAN_CONFIG(nr), old_config); ++ data->autofan[nr].config = old_config ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_smooth(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_SMOOTH1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = SMOOTH_FROM_REG(data->smooth[nr]); ++ *nrels_mag = 1; ++ ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if( *nrels_mag > 0 ) { ++ data->smooth[nr] = SMOOTH_TO_REG(results[0]); ++ } ++ if( nr == 0 ) { ++ lm85_write_value(client, LM85_REG_AFAN_SPIKE1, ++ data->smooth[0] ++ | data->syncpwm3 ++ | (data->autofan[0].min_off ? 0x20 : 0) ++ | (data->autofan[1].min_off ? 0x40 : 0) ++ | (data->autofan[2].min_off ? 0x80 : 0) ++ ); ++ } else { ++ lm85_write_value(client, LM85_REG_AFAN_SPIKE2, ++ (data->smooth[1] << 4) | data->smooth[2]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_zone(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_ZONE1 ; ++ ++ /* Limit, Hysteresis (neg), Range, Critical */ ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ ++ results[0] = TEMP_FROM_REG(data->zone[nr].limit) / 10; ++ results[1] = HYST_FROM_REG(data->zone[nr].hyst); ++ results[2] = RANGE_FROM_REG(data->zone[nr].range); ++ results[3] = TEMP_FROM_REG(data->zone[nr].critical) / 10; ++ *nrels_mag = 4; ++ ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 3) { ++ data->zone[nr].critical = TEMP_TO_REG(results[3]*10); ++ lm85_write_value(client, LM85_REG_AFAN_CRITICAL(nr), ++ data->zone[nr].critical ); ++ } ++ if (*nrels_mag > 2) { ++ data->zone[nr].range = RANGE_TO_REG(results[2]); ++ lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), ++ (data->zone[nr].range << 4) ++ | data->autofan[nr].freq ++ ); ++ } ++ if (*nrels_mag > 1) { ++ data->zone[nr].hyst = HYST_TO_REG(results[1]); ++ if( nr == 0 || nr == 1 ) { ++ lm85_write_value(client, LM85_REG_AFAN_HYST1, ++ (data->zone[0].hyst << 4) ++ | data->zone[1].hyst ++ ); ++ } else { ++ lm85_write_value(client, LM85_REG_AFAN_HYST2, ++ (data->zone[2].hyst << 4) ++ ); ++ } ++ } ++ if (*nrels_mag > 0) { ++ data->zone[nr].limit = TEMP_TO_REG(results[0]*10); ++ lm85_write_value(client, LM85_REG_AFAN_LIMIT(nr), ++ data->zone[nr].limit ++ ); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void lm85_pwm_zone(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - LM85_SYSCTL_PWM_ZONE1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = ZONE_FROM_REG(data->autofan[nr].config); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->autofan[nr].config = ++ (data->autofan[nr].config & (~0xe0)) ++ | ZONE_TO_REG(results[0]) ; ++ lm85_write_value(client, LM85_REG_AFAN_CONFIG(nr), ++ data->autofan[nr].config); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1027_temp_offset(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - ADM1027_SYSCTL_TEMP_OFFSET1 ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ switch( data->type ) { ++ case adm1027 : ++ default : ++ results[0] = TEMP_FROM_REG(data->temp_offset[nr]); ++ break ; ++ case adt7463 : ++ results[0] = TEMPEXT_FROM_REG(0,data->temp_offset[nr]); ++ break ; ++ } ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ switch( data->type ) { ++ case adm1027 : ++ default : ++ data->temp_offset[nr] = TEMP_TO_REG(results[0]); ++ break ; ++ case adt7463 : ++ data->temp_offset[nr] = EXTTEMP_TO_REG(results[0]); ++ break ; ++ }; ++ lm85_write_value(client, ADM1027_REG_TEMP_OFFSET(nr), ++ data->temp_offset[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1027_fan_ppr(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int old ; ++ ++ if (ctl_name != ADM1027_SYSCTL_FAN_PPR) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = PPR_FROM_REG(data->fan_ppr,0); ++ results[1] = PPR_FROM_REG(data->fan_ppr,1); ++ results[2] = PPR_FROM_REG(data->fan_ppr,2); ++ results[3] = PPR_FROM_REG(data->fan_ppr,3); ++ *nrels_mag = 4; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ old = data->fan_ppr ; ++ if (*nrels_mag > 3) { ++ old = (old & ~PPR_MASK(3)) | PPR_TO_REG(results[3],3); ++ }; ++ if (*nrels_mag > 2) { ++ old = (old & ~PPR_MASK(2)) | PPR_TO_REG(results[2],2); ++ }; ++ if (*nrels_mag > 1) { ++ old = (old & ~PPR_MASK(1)) | PPR_TO_REG(results[1],1); ++ }; ++ if (*nrels_mag > 0) { ++ old = (old & ~PPR_MASK(0)) | PPR_TO_REG(results[0],0); ++ lm85_write_value(client, ADM1027_REG_FAN_PPR, old); ++ data->fan_ppr = old ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adm1027_alarm_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ ++ if( ctl_name != ADM1027_SYSCTL_ALARM_MASK ) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = INTMASK_FROM_REG(data->alarm_mask); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 0) { ++ data->alarm_mask = INTMASK_TO_REG(results[0]); ++ lm85_write_value(client, ADM1027_REG_INTMASK, ++ data->alarm_mask); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adt7463_tmin_ctl(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - ADT7463_SYSCTL_TMIN_CTL1 ; ++ u16 old ; ++ ++ if (nr < 0 || nr > 2) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ old = data->tmin_ctl ; ++ results[0] = (old & ( 0x2000 << nr )) != 0 ; ++ results[1] = (old >> (nr*3)) & 0x07 ; ++ results[2] = (old & ( 0x0400 << nr )) != 0 ; ++ results[3] = OPPOINT_FROM_REG(data->oppoint[nr]); ++ *nrels_mag = 4; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ old = data->tmin_ctl ; ++ if (*nrels_mag > 3) { ++ data->oppoint[nr] = OPPOINT_TO_REG(results[3]); ++ lm85_write_value(client, ADT7463_REG_OPPOINT(nr), ++ data->oppoint[nr]); ++ }; ++ if (*nrels_mag > 2) { ++ if( results[2] ) { ++ old |= (0x0400 << nr) ; ++ } else { ++ old &= ~(0x0400 << nr) ; ++ } ++ }; ++ if (*nrels_mag > 1) { ++ old &= ~(0x07 << (nr*3)) ; ++ old |= (results[1] & 0x07) << (nr*3) ; ++ }; ++ if (*nrels_mag > 0) { ++ if( results[0] ) { ++ old |= 0x2000 << nr ; ++ } else { ++ old &= ~(0x2000 << nr) ; ++ } ++ lm85_write_value(client, ADT7463_REG_TMIN_CTL, old); ++ data->tmin_ctl = old ; ++ } ++ up(&data->update_lock); ++ } ++} ++ ++void adt7463_therm_signal(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int counts ; ++ ++ if (ctl_name != ADT7463_SYSCTL_THERM_SIGNAL) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ /* Don't call update_client here because ++ * ADT7463_REG_THERM has to be read every ++ * 5 seconds to prevent lost counts ++ */ ++ down(&data->update_lock); ++ counts = lm85_read_value(client, ADT7463_REG_THERM) & 0xff; ++ if( data->therm_total < LONG_MAX - 256 ) { ++ data->therm_total += counts ; ++ } ++ if( counts >= 255 ) { ++ ++data->therm_ovfl ; ++ } ++ up(&data->update_lock); ++ ++ results[0] = data->therm_limit ; ++ results[1] = data->therm_total ; ++ results[2] = data->therm_ovfl ; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ /* therm_total and therm_ovfl are read only */ ++ if (*nrels_mag > 0) { ++ data->therm_limit = SENSORS_LIMIT(results[0],0,255); ++ lm85_write_value(client, ADT7463_REG_THERM_LIMIT, ++ data->therm_limit); ++ }; ++ up(&data->update_lock); ++ } ++} ++ ++ ++void emc6d100_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm85_data *data = client->data; ++ int nr = ctl_name - EMC6D100_SYSCTL_IN5 +5; ++ ++ if (nr < 5 || nr > 7) ++ return ; /* ERROR */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; /* 1.000 */ ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm85_update_client(client); ++ results[0] = INS_FROM_REG(nr,data->in_min[nr]); ++ results[1] = INS_FROM_REG(nr,data->in_max[nr]); ++ results[2] = INS_FROM_REG(nr,data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ down(&data->update_lock); ++ if (*nrels_mag > 1) { ++ data->in_max[nr] = INS_TO_REG(nr,results[1]); ++ lm85_write_value(client, EMC6D100_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ if (*nrels_mag > 0) { ++ data->in_min[nr] = INS_TO_REG(nr,results[0]); ++ lm85_write_value(client, EMC6D100_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ up(&data->update_lock); ++ } ++} ++ ++ ++static int __init sm_lm85_init(void) ++{ ++ printk("lm85: Version %s (%s)\n", LM_VERSION, LM_DATE); ++ printk("lm85: See http://www.penguincomputing.com/lm_sensors for more info.\n" ); ++ return i2c_add_driver(&lm85_driver); ++} ++ ++static void __exit sm_lm85_exit(void) ++{ ++ i2c_del_driver(&lm85_driver); ++} ++ ++/* Thanks to Richard Barrington for adding the LM85 to sensors-detect. ++ * Thanks to Margit Schubert-While <margitsw@t-online.de> for help with ++ * post 2.7.0 CVS changes ++ */ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Philip Pokorny <ppokorny@penguincomputing.com"); ++MODULE_DESCRIPTION("LM85-B, LM85-C driver"); ++ ++module_init(sm_lm85_init); ++module_exit(sm_lm85_exit); +--- linux-old/drivers/sensors/lm87.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm87.c Mon Dec 13 20:18:49 2004 +@@ -0,0 +1,988 @@ ++/* ++ LM87.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2000 Frodo Looijaard <frodol@dds.nl> ++ Philip Edelbrock <phil@netroedge.com> ++ Stephen Rousset <stephen.rousset@rocketlogix.com> ++ Dan Eaton <dan.eaton@rocketlogix.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++/* Chip configuration settings. These should be set to reflect the ++HARDWARE configuration of your chip. By default (read: when all of ++these are left commented out), this driver assumes that the ++configuration is the same as National's defaults for the Channel Mode ++register. ++ ++Set to '1' the appropriate defines, as nessesary: ++ ++ - External temp sensors 2 (possible second CPU temp) ++ This will disable the 2.5V and Vccp2 readings. ++ Ironically, National decided that you can read the ++ temperature of a second CPU or it's core voltage, ++ but not both! Comment out if FAULT is reported. */ ++ ++/* #define LM87_EXT2 1 */ ++ ++/* Aux analog input. When enabled, the Fan 1 reading ++ will be disabled */ ++ ++/* #define LM87_AIN1 1 */ ++ ++/* Aux analog input 2. When enabled, the Fan 2 reading ++ will be disabled */ ++ ++/* #define LM87_AIN2 1 */ ++ ++/* Internal Vcc is 5V instead of 3.3V */ ++ ++/* #define LM87_5V_VCC 1 */ ++ ++/* That's the end of the hardware config defines. I would have made ++ them insmod params, but it would be too much work. ;') */ ++ ++ ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(lm87); ++ ++/* The following is the calculation for the register offset ++ * for the monitored items minimum and maximum locations. ++ */ ++#define LM87_REG_IN_MAX(nr) (0x2b + ((nr) * 2)) ++#define LM87_REG_IN_MIN(nr) (0x2c + ((nr) * 2)) ++#define LM87_REG_IN(nr) (0x20 + (nr)) ++ ++/* Initial limits */ ++ ++/* ++ * LM87 register definition ++ * ++ */ ++ ++ /* The LM87 registers */ ++#define LM87_INT_TEMP_HI_LIMIT_LOCKABLE 0x13 ++#define LM87_EXT_TEMP_HI_LIMIT_LOCKABLE 0x14 ++#define LM87_REG_TEST 0x15 ++#define LM87_REG_CHANNEL_MODE 0x16 ++#define LM87_REG_INT_TEMP_HI_LIMIT 0x17 ++#define LM87_REG_EXT_TEMP_HI_LIMIT 0x18 ++#define LM87_REG_ANALOG_OUT 0x19 ++ ++ /* These are all read-only */ ++#define LM87_REG_2_5V_EXT_TEMP_2 0x20 ++#define LM87_REG_VCCP1 0x21 ++#define LM87_REG_3_3V 0x22 ++#define LM87_REG_5V 0x23 ++#define LM87_REG_12V 0x24 ++#define LM87_REG_VCCP2 0x25 ++#define LM87_REG_EXT_TEMP_1 0x26 ++#define LM87_REG_INT_TEMP 0x27 /* LM87 temp. */ ++#define LM87_REG_FAN1_AIN1 0x28 ++#define LM87_REG_FAN2_AIN2 0x29 ++ ++/* These are read/write */ ++#define LM87_REG_AIN1_LOW 0x1A ++#define LM87_REG_AIN2_LOW 0x1B ++#define LM87_REG_2_5V_EXT_TEMP_2_HIGH 0x2B ++#define LM87_REG_2_5V_EXT_TEMP_2_LOW 0x2C ++#define LM87_REG_VCCP1_HIGH 0x2D ++#define LM87_REG_VCCP1_LOW 0x2E ++#define LM87_REG_3_3V_HIGH 0x2F ++#define LM87_REG_3_3V_LOW 0x30 ++#define LM87_REG_5V_HIGH 0x31 ++#define LM87_REG_5V_LOW 0x32 ++#define LM87_REG_12V_HIGH 0x33 ++#define LM87_REG_12V_LOW 0x34 ++#define LM87_REG_VCCP2_HIGH 0x35 ++#define LM87_REG_VCCP2_LOW 0x36 ++#define LM87_REG_EXT_TEMP_1_HIGH 0x37 ++#define LM87_REG_EXT_TEMP_1_LOW 0x38 ++#define LM87_REG_INT_TEMP_HIGH 0x39 ++#define LM87_REG_INT_TEMP_LOW 0x3A ++#define LM87_REG_FAN1_AIN1_LIMIT 0x3B ++#define LM87_REG_FAN2_AIN2_LIMIT 0x3C ++#define LM87_REG_COMPANY_ID 0x3E ++#define LM87_REG_DIE_REV 0x3F ++ ++#define LM87_REG_CONFIG 0x40 ++#define LM87_REG_INT1_STAT 0x41 ++#define LM87_REG_INT2_STAT 0x42 ++#define LM87_REG_INT1_MASK 0x43 ++#define LM87_REG_INT2_MASK 0x44 ++#define LM87_REG_CHASSIS_CLEAR 0x46 ++#define LM87_REG_VID_FAN_DIV 0x47 ++#define LM87_REG_VID4 0x49 ++#define LM87_REG_CONFIG_2 0x4A ++#define LM87_REG_INTRPT_STATUS_1_MIRROR 0x4C ++#define LM87_REG_INTRPT_STATUS_2_MIRROR 0x4D ++#define LM87_REG_SMBALERT_NUM_ENABLE 0x80 ++ ++ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define IN_TO_REG(val,nr) (SENSORS_LIMIT(((val) & 0xff),0,255)) ++#define IN_FROM_REG(val,nr) (val) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:\ ++ (val)==255?0:1350000/((div)*(val))) ++ ++#define TEMP_FROM_REG(temp) (temp * 10) ++ ++#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) ++ ++#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),0,255) ++#if 0 ++#define TEMP_FROM_REG(temp) \ ++ ((temp)<256?((((temp)&0x1fe) >> 1) * 10) + ((temp) & 1) * 5: \ ++ ((((temp)&0x1fe) >> 1) -255) * 10 - ((temp) & 1) * 5) \ ++ ++#define TEMP_LIMIT_FROM_REG(val) (val) ++ ++#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT((val),0,255) ++#endif ++ ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1))) ++ ++/* For each registered LM87, we need to keep some data in memory. That ++ data is pointed to by LM87_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new LM87 client is ++ allocated. */ ++struct lm87_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[6]; /* Scaled Register value */ ++ u8 in_max[6]; /* Scaled Register value */ ++ u8 in_min[6]; /* Scaled Register value */ ++ u8 ain1; /* Register value */ ++ u8 ain1_min; /* Register value */ ++ u8 ain1_max; /* Register value */ ++ u8 ain2; /* Register value */ ++ u8 ain2_min; /* Register value */ ++ u8 ain2_max; /* Register value */ ++ u8 fan; /* Register value */ ++ u8 fan_min; /* Register value */ ++ u8 fan_div; /* Register encoding, shifted right */ ++ u8 fan2; /* Register value */ ++ u8 fan2_min; /* Register value */ ++ u8 fan2_div; /* Register encoding, shifted right */ ++ int ext2_temp; /* Temp, shifted right */ ++ int ext_temp; /* Temp, shifted right */ ++ int int_temp; /* Temp, shifted right */ ++ u8 ext_temp_max; /* Register value */ ++ u8 ext_temp_min; /* Register value */ ++ u8 ext2_temp_max; /* Register value */ ++ u8 ext2_temp_min; /* Register value */ ++ u8 int_temp_max; /* Register value */ ++ u8 int_temp_min; /* Register value */ ++ u16 alarms; /* Register encoding, combined */ ++ u8 analog_out; /* Register value */ ++ u8 vid; /* Register value combined */ ++ u8 vrm; /* VRM version * 10 */ ++}; ++ ++static int lm87_attach_adapter(struct i2c_adapter *adapter); ++static int lm87_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int lm87_detach_client(struct i2c_client *client); ++ ++static int lm87_read_value(struct i2c_client *client, u8 register); ++static int lm87_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void lm87_update_client(struct i2c_client *client); ++static void lm87_init_client(struct i2c_client *client); ++ ++ ++static void lm87_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++#if defined (LM87_AIN1) || defined (LM87_AIN2) ++static void lm87_ain(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++#endif ++static void lm87_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm87_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm87_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm87_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm87_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, ++ long *results); ++static void lm87_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm87_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int lm87_id = 0; ++ ++static struct i2c_driver LM87_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM87 sensor driver", ++ .id = I2C_DRIVERID_LM87, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm87_attach_adapter, ++ .detach_client = lm87_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define LM87_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define LM87_SYSCTL_IN1 1001 ++#define LM87_SYSCTL_IN2 1002 ++#define LM87_SYSCTL_IN3 1003 ++#define LM87_SYSCTL_IN4 1004 ++#define LM87_SYSCTL_IN5 1005 ++#define LM87_SYSCTL_AIN1 1006 ++#define LM87_SYSCTL_AIN2 1007 ++#define LM87_SYSCTL_FAN1 1102 ++#define LM87_SYSCTL_FAN2 1103 ++#define LM87_SYSCTL_TEMP1 1250 /* Degrees Celcius * 100 */ ++#define LM87_SYSCTL_TEMP2 1251 /* Degrees Celcius * 100 */ ++#define LM87_SYSCTL_TEMP3 1252 /* Degrees Celcius * 100 */ ++#define LM87_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define LM87_SYSCTL_ALARMS 2001 /* bitvector */ ++#define LM87_SYSCTL_ANALOG_OUT 2002 ++#define LM87_SYSCTL_VID 2003 ++#define LM87_SYSCTL_VRM 2004 ++ ++#define LM87_ALARM_IN0 0x0001 ++#define LM87_ALARM_IN1 0x0002 ++#define LM87_ALARM_IN2 0x0004 ++#define LM87_ALARM_IN3 0x0008 ++#define LM87_ALARM_TEMP1 0x0010 ++#define LM87_ALARM_TEMP2 0x0020 ++#define LM87_ALARM_TEMP3 0x0020 /* same?? */ ++#define LM87_ALARM_FAN1 0x0040 ++#define LM87_ALARM_FAN2 0x0080 ++#define LM87_ALARM_IN4 0x0100 ++#define LM87_ALARM_IN5 0x0200 ++#define LM87_ALARM_RESERVED1 0x0400 ++#define LM87_ALARM_RESERVED2 0x0800 ++#define LM87_ALARM_CHAS 0x1000 ++#define LM87_ALARM_THERM_SIG 0x2000 ++#define LM87_ALARM_TEMP2_FAULT 0x4000 ++#define LM87_ALARM_TEMP3_FAULT 0x08000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* The /proc/sys entries */ ++/* These files are created for each detected LM87. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++ ++static ctl_table LM87_dir_table_template[] = { ++#ifdef LM87_AIN1 ++ {LM87_SYSCTL_AIN1, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_ain}, ++#endif ++#ifdef LM87_AIN2 ++ {LM87_SYSCTL_AIN2, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_ain}, ++#endif ++#ifndef LM87_EXT2 ++ {LM87_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_in}, ++ {LM87_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_in}, ++#endif ++ {LM87_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_in}, ++ {LM87_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_in}, ++ {LM87_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_in}, ++ {LM87_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_in}, ++#ifndef LM87_AIN1 ++ {LM87_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_fan}, ++ {LM87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_fan_div}, ++#define LM87_FANDIV_FLAG ++#endif ++#ifndef LM87_AIN2 ++ {LM87_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_fan}, ++#ifndef LM87_FANDIV_FLAG ++ {LM87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_fan_div}, ++#endif /* LM87_FANDIV_FLAG */ ++#endif /* LM87_AIN2 */ ++#ifdef LM87_EXT2 ++ {LM87_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_temp}, ++#endif ++ {LM87_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_temp}, ++ {LM87_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_temp}, ++ {LM87_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_alarms}, ++ {LM87_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_analog_out}, ++ {LM87_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_vid}, ++ {LM87_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm87_vrm}, ++ {0} ++}; ++ ++static int lm87_attach_adapter(struct i2c_adapter *adapter) ++{ ++ int error; ++ struct i2c_client_address_data lm87_client_data; ++ ++ lm87_client_data.normal_i2c = addr_data.normal_i2c; ++ lm87_client_data.normal_i2c_range = addr_data.normal_i2c_range; ++ lm87_client_data.probe = addr_data.probe; ++ lm87_client_data.probe_range = addr_data.probe_range; ++ lm87_client_data.ignore = addr_data.ignore; ++ lm87_client_data.ignore_range = addr_data.ignore_range; ++ lm87_client_data.force = addr_data.forces->force; ++ ++ error = i2c_probe(adapter, &lm87_client_data, lm87_detect); ++ i2c_detect(adapter, &addr_data, lm87_detect); ++ ++ return error; ++} ++ ++static int lm87_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct lm87_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access LM87_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct lm87_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &LM87_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if (((lm87_read_value(new_client, LM87_REG_CONFIG) & 0x80) ++ != 0x00) || ++ (lm87_read_value(new_client, LM87_REG_COMPANY_ID) != 0x02)) ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put into the global list */ ++ type_name = "lm87"; ++ client_name = "LM87 chip"; ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = lm87_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ LM87_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the LM87 chip */ ++ lm87_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int lm87_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct lm87_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("lm87.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static int lm87_read_value(struct i2c_client *client, u8 reg) ++{ ++ return 0xFF & i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int lm87_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new LM87. It should set limits, etc. */ ++static void lm87_init_client(struct i2c_client *client) ++{ ++ struct lm87_data *data = client->data; ++ ++ /* Reset all except Watchdog values and last conversion values ++ This sets fan-divs to 2, among others. This makes most other ++ initializations unnecessary */ ++ lm87_write_value(client, LM87_REG_CONFIG, 0x80); ++ ++ /* Setup Channel Mode register for configuration of monitoring ++ * Default is 00000000b ++ * bit 0 - Configures Fan 1/AIN 1 input (1 = AIN) ++ * bit 1 - Configures Fan 2/AIN 2 input (1 = AIN) ++ * bit 2 - Configures 2.5V&Vccp2/D2 input (1 = 2nd Therm.) ++ * bit 3 - Configures Vcc for 5V/3.3V reading (0 = 3.3V) ++ * bit 4 - Configures IRQ0 Enable if = 1 ++ * bit 5 - Configures IRQ1 Enable if = 1 ++ * bit 6 - Configures IRQ2 Enable if = 1 ++ * bit 7 - Configures VID/IRQ input as interrupts if = 1 ++ */ ++ ++/* I know, not clean, but it works. :'p */ ++ lm87_write_value(client, LM87_REG_CHANNEL_MODE, ++#ifdef LM87_AIN1 ++ 0x01 ++#else ++0 ++#endif ++ | ++#ifdef LM87_AIN2 ++ 0x02 ++#else ++0 ++#endif ++ | ++#ifdef LM87_EXT2 ++ 0x04 ++#else ++0 ++#endif ++ | ++#ifdef LM87_5V_VCC ++0x08 ++#else ++0 ++#endif ++ ); ++ ++ data->vrm = DEFAULT_VRM; ++ ++ /* Start monitoring */ ++ lm87_write_value(client, LM87_REG_CONFIG, 0x01); ++} ++ ++static void lm87_update_client(struct i2c_client *client) ++{ ++ struct lm87_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ) || /* 1 sec cache */ ++ (jiffies < data->last_updated) || ++ !data->valid) { ++ for (i = 0; i <= 5; i++) { ++ data->in[i] = ++ lm87_read_value(client,LM87_REG_IN(i)); ++ data->in_min[i] = ++ lm87_read_value(client,LM87_REG_IN_MIN(i)); ++ data->in_max[i] = ++ lm87_read_value(client,LM87_REG_IN_MAX(i)); ++ } ++ data->ain1 = ++ lm87_read_value(client,LM87_REG_FAN1_AIN1); ++ data->ain1_min = ++ lm87_read_value(client,LM87_REG_AIN1_LOW); ++ data->ain1_max = ++ lm87_read_value(client,LM87_REG_FAN1_AIN1_LIMIT); ++ data->ain2 = ++ lm87_read_value(client,LM87_REG_FAN2_AIN2); ++ data->ain2_min = ++ lm87_read_value(client,LM87_REG_AIN2_LOW); ++ data->ain2_max = ++ lm87_read_value(client,LM87_REG_FAN2_AIN2_LIMIT); ++ ++ data->fan = ++ lm87_read_value(client, LM87_REG_FAN1_AIN1); ++ data->fan_min = ++ lm87_read_value(client, LM87_REG_FAN1_AIN1_LIMIT); ++ data->fan2 = ++ lm87_read_value(client, LM87_REG_FAN2_AIN2); ++ data->fan2_min = ++ lm87_read_value(client, LM87_REG_FAN2_AIN2_LIMIT); ++ ++ data->ext2_temp = ++ lm87_read_value(client, LM87_REG_2_5V_EXT_TEMP_2); ++ data->ext_temp = ++ lm87_read_value(client, LM87_REG_EXT_TEMP_1); ++ data->int_temp = ++ lm87_read_value(client, LM87_REG_INT_TEMP); ++ ++ data->ext2_temp_max = ++ lm87_read_value(client, LM87_REG_2_5V_EXT_TEMP_2_HIGH); ++ data->ext2_temp_min = ++ lm87_read_value(client, LM87_REG_2_5V_EXT_TEMP_2_LOW); ++ ++ data->ext_temp_max = ++ lm87_read_value(client, LM87_REG_EXT_TEMP_1_HIGH); ++ data->ext_temp_min = ++ lm87_read_value(client, LM87_REG_EXT_TEMP_1_LOW); ++ ++ data->int_temp_max = ++ lm87_read_value(client, LM87_REG_INT_TEMP_HIGH); ++ data->int_temp_min = ++ lm87_read_value(client, LM87_REG_INT_TEMP_LOW); ++ ++ i = lm87_read_value(client, LM87_REG_VID_FAN_DIV); ++ data->fan_div = (i >> 4) & 0x03; ++ data->fan2_div = (i >> 6) & 0x03; ++ data->vid = i & 0x0f; ++ data->vid |= ++ (lm87_read_value(client, LM87_REG_VID4) & 0x01) ++ << 4; ++ data->alarms = ++ lm87_read_value(client, LM87_REG_INT1_STAT) + ++ (lm87_read_value(client, LM87_REG_INT2_STAT) << 8); ++ data->analog_out = ++ lm87_read_value(client, LM87_REG_ANALOG_OUT); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void lm87_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ long scales[6] = { 250, 270, ++#ifdef LM87_5V_VCC ++500, ++#else ++330, ++#endif ++ 500, 1200, 270 }; ++ ++ struct lm87_data *data = client->data; ++ int nr = ctl_name - LM87_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++ results[0] = ++ ((long)data->in_min[nr] * scales[nr]) / 192; ++ results[1] = ++ ((long)data->in_max[nr] * scales[nr]) / 192; ++ results[2] = ++ ((long)data->in[nr] * scales[nr]) / 192; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = ++ (results[0] * 192) / scales[nr]; ++ lm87_write_value(client, LM87_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = ++ (results[1] * 192) / scales[nr]; ++ lm87_write_value(client, LM87_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++#if defined (LM87_AIN1) || defined (LM87_AIN2) ++void lm87_ain(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++ if (ctl_name == LM87_SYSCTL_AIN1) { ++ results[0] = data->ain1_min; ++ results[1] = data->ain1_max; ++ results[2] = data->ain1; ++ } else { ++ results[0] = data->ain2_min; ++ results[1] = data->ain2_max; ++ results[2] = data->ain2; ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (ctl_name == LM87_SYSCTL_AIN1) { ++ data->ain1_min = results[0]; ++ lm87_write_value(client, LM87_REG_AIN1_LOW, ++ data->ain1_min); ++ } else { ++ data->ain2_min = results[0]; ++ lm87_write_value(client, LM87_REG_AIN2_LOW, ++ data->ain2_min); ++ } ++ } ++ if (*nrels_mag >= 2) { ++ if (ctl_name == LM87_SYSCTL_AIN1) { ++ data->ain1_max = results[1]; ++ lm87_write_value(client, LM87_REG_FAN1_AIN1_LIMIT, ++ data->ain1_max); ++ } else { ++ data->ain2_max = results[1]; ++ lm87_write_value(client, LM87_REG_FAN2_AIN2_LIMIT, ++ data->ain2_max); ++ } ++ } ++ } ++} ++#endif ++ ++void lm87_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++ if (ctl_name == LM87_SYSCTL_FAN1) { ++ results[0] = FAN_FROM_REG(data->fan_min, ++ DIV_FROM_REG(data->fan_div)); ++ results[1] = FAN_FROM_REG(data->fan, ++ DIV_FROM_REG(data->fan_div)); ++ } else { ++ results[0] = FAN_FROM_REG(data->fan2_min, ++ DIV_FROM_REG(data->fan2_div)); ++ results[1] = FAN_FROM_REG(data->fan2, ++ DIV_FROM_REG(data->fan2_div)); ++ } ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 0) { ++ if (ctl_name == LM87_SYSCTL_FAN1) { ++ data->fan_min = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data->fan_div)); ++ lm87_write_value(client, LM87_REG_FAN1_AIN1_LIMIT, ++ data->fan_min); ++ } else { ++ data->fan2_min = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data->fan2_div)); ++ lm87_write_value(client, LM87_REG_FAN2_AIN2_LIMIT, ++ data->fan2_min); ++ } ++ } ++ } ++} ++ ++ ++void lm87_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm87_update_client(client); ++ ++ /* find out which temp. is being requested */ ++ if (ctl_name == LM87_SYSCTL_TEMP3) ++ { ++ results[0] = TEMP_LIMIT_FROM_REG(data->ext2_temp_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->ext2_temp_min); ++ results[2] = TEMP_FROM_REG(data->ext2_temp); ++ } ++ else if(ctl_name == LM87_SYSCTL_TEMP2) ++ { ++ results[0] = TEMP_LIMIT_FROM_REG(data->ext_temp_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->ext_temp_min); ++ results[2] = TEMP_FROM_REG(data->ext_temp); ++ } ++ else if(ctl_name == LM87_SYSCTL_TEMP1) ++ { ++ results[0] = TEMP_LIMIT_FROM_REG(data->int_temp_max); ++ results[1] = TEMP_LIMIT_FROM_REG(data->int_temp_min); ++ results[2] = TEMP_FROM_REG(data->int_temp); ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (ctl_name == LM87_SYSCTL_TEMP3) { ++ data->ext2_temp_max = TEMP_LIMIT_TO_REG(results[0]); ++ lm87_write_value(client, LM87_REG_2_5V_EXT_TEMP_2_HIGH, ++ data->ext2_temp_max); ++ } ++ if (ctl_name == LM87_SYSCTL_TEMP2) { ++ data->ext_temp_max = TEMP_LIMIT_TO_REG(results[0]); ++ lm87_write_value(client, LM87_REG_EXT_TEMP_1_HIGH, ++ data->ext_temp_max); ++ } ++ if (ctl_name == LM87_SYSCTL_TEMP1) { ++ data->int_temp_max = TEMP_LIMIT_TO_REG(results[0]); ++ lm87_write_value(client, LM87_REG_INT_TEMP_HIGH, ++ data->int_temp_max); ++ } ++ } ++ if (*nrels_mag >= 2) { ++ if (ctl_name == LM87_SYSCTL_TEMP3) { ++ data->ext2_temp_min = TEMP_LIMIT_TO_REG(results[1]); ++ lm87_write_value(client, LM87_REG_2_5V_EXT_TEMP_2_LOW, ++ data->ext2_temp_min); ++ } ++ if (ctl_name == LM87_SYSCTL_TEMP2) { ++ data->ext_temp_min = TEMP_LIMIT_TO_REG(results[1]); ++ lm87_write_value(client, LM87_REG_EXT_TEMP_1_LOW, ++ data->ext_temp_min); ++ } ++ if (ctl_name == LM87_SYSCTL_TEMP1) { ++ data->int_temp_min = TEMP_LIMIT_TO_REG(results[1]); ++ lm87_write_value(client, LM87_REG_INT_TEMP_LOW, ++ data->int_temp_min); ++ } ++ } ++ } ++} ++ ++void lm87_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void lm87_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++/* This gets a little hairy depending on the hardware config */ ++ ++ struct lm87_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++#ifndef LM87_AIN1 ++ results[0] = DIV_FROM_REG(data->fan_div); ++# ifndef LM87_AIN2 ++ results[1] = DIV_FROM_REG(data->fan2_div); ++ *nrels_mag = 2; ++# else ++ *nrels_mag = 1; ++# endif ++#else /* Must be referring to fan 2 */ ++ results[0] = DIV_FROM_REG(data->fan2_div); ++ *nrels_mag = 1; ++#endif ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = lm87_read_value(client, LM87_REG_VID_FAN_DIV); ++/* Note: it's OK to change fan2 div even if fan2 isn't enabled */ ++#ifndef LM87_AIN1 ++ if (*nrels_mag >= 2) { ++ data->fan2_div = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan2_div << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div << 4); ++ lm87_write_value(client, LM87_REG_VID_FAN_DIV, old); ++ } ++#else /* Must be referring to fan 2 */ ++ if (*nrels_mag >= 1) { ++ data->fan2_div = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan2_div << 6); ++ lm87_write_value(client, LM87_REG_VID_FAN_DIV, old); ++ } ++#endif ++ } ++} ++ ++void lm87_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++ results[0] = data->analog_out; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->analog_out = results[0]; ++ lm87_write_value(client, LM87_REG_ANALOG_OUT, ++ data->analog_out); ++ } ++ } ++} ++ ++void lm87_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ lm87_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void lm87_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct lm87_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++static int __init sm_lm87_init(void) ++{ ++ printk("lm87.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&LM87_driver); ++} ++ ++static void __exit sm_lm87_exit(void) ++{ ++ i2c_del_driver(&LM87_driver); ++} ++ ++ ++ ++MODULE_LICENSE("GPL"); ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, " ++ "Mark Studebaker <mdsxyz123@yahoo.com>, and Stephen Rousset <stephen.rousset@rocketlogix.com>"); ++ ++MODULE_DESCRIPTION("LM87 driver"); ++ ++module_init(sm_lm87_init); ++module_exit(sm_lm87_exit); +--- linux-old/drivers/sensors/lm90.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm90.c Mon Dec 13 20:18:49 2004 +@@ -0,0 +1,782 @@ ++/* ++ * lm90.c - Part of lm_sensors, Linux kernel modules for hardware ++ * monitoring ++ * Copyright (C) 2003-2004 Jean Delvare <khali@linux-fr.org> ++ * ++ * Based on the lm83 driver. The LM90 is a sensor chip made by National ++ * Semiconductor. It reports up to two temperatures (its own plus up to ++ * one external one) with a 0.125 deg resolution (1 deg for local ++ * temperature) and a 3-4 deg accuracy. Complete datasheet can be ++ * obtained from National's website at: ++ * http://www.national.com/pf/LM/LM90.html ++ * ++ * This driver also supports the LM89 and LM99, two other sensor chips ++ * made by National Semiconductor. Both have an increased remote ++ * temperature measurement accuracy (1 degree), and the LM99 ++ * additionally shifts remote temperatures (measured and limits) by 16 ++ * degrees, which allows for higher temperatures measurement. The ++ * driver doesn't handle it since it can be done easily in user-space. ++ * Complete datasheets can be obtained from National's website at: ++ * http://www.national.com/pf/LM/LM89.html ++ * http://www.national.com/pf/LM/LM99.html ++ * Note that there is no way to differenciate between both chips. ++ * ++ * This driver also supports the LM86, another sensor chip made by ++ * National Semiconductor. It is exactly similar to the LM90 except it ++ * has a higher accuracy. ++ * Complete datasheet can be obtained from National's website at: ++ * http://www.national.com/pf/LM/LM86.html ++ * ++ * This driver also supports the ADM1032, a sensor chip made by Analog ++ * Devices. That chip is similar to the LM90, with a few differences ++ * that are not handled by this driver. Complete datasheet can be ++ * obtained from Analog's website at: ++ * http://products.analog.com/products/info.asp?product=ADM1032 ++ * Among others, it has a higher accuracy than the LM90, much like the ++ * LM86 does. ++ * ++ * This driver also supports the MAX6657 and MAX6658, sensor chips made ++ * by Maxim. These chips are similar to the LM86. Complete datasheet ++ * can be obtained at Maxim's website at: ++ * http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578 ++ * Note that there is no way to differenciate between both chips (but ++ * no need either). ++ * ++ * Since the LM90 was the first chipset supported by this driver, most ++ * comments will refer to this chipset, but are actually general and ++ * concern all supported chipsets, unless mentioned otherwise. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++#ifndef I2C_DRIVERID_LM90 ++#define I2C_DRIVERID_LM90 1042 ++#endif ++ ++/* ++ * Addresses to scan ++ * Address is fully defined internally and cannot be changed. ++ * LM86, LM89, LM90, LM99, ADM1032, MAX6657 and MAX6658 have address 0x4c. ++ * LM89-1, and LM99-1 have address 0x4d. ++ */ ++ ++static unsigned short normal_i2c[] = { 0x4c, 0x4d, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* ++ * Insmod parameters ++ */ ++ ++SENSORS_INSMOD_5(lm90, adm1032, lm99, lm86, max6657); ++ ++/* ++ * The LM90 registers ++ */ ++ ++#define LM90_REG_R_MAN_ID 0xFE ++#define LM90_REG_R_CHIP_ID 0xFF ++#define LM90_REG_R_CONFIG1 0x03 ++#define LM90_REG_W_CONFIG1 0x09 ++#define LM90_REG_R_CONFIG2 0xBF ++#define LM90_REG_W_CONFIG2 0xBF ++#define LM90_REG_R_CONVRATE 0x04 ++#define LM90_REG_W_CONVRATE 0x0A ++#define LM90_REG_R_STATUS 0x02 ++#define LM90_REG_R_LOCAL_TEMP 0x00 ++#define LM90_REG_R_LOCAL_HIGH 0x05 ++#define LM90_REG_W_LOCAL_HIGH 0x0B ++#define LM90_REG_R_LOCAL_LOW 0x06 ++#define LM90_REG_W_LOCAL_LOW 0x0C ++#define LM90_REG_R_LOCAL_CRIT 0x20 ++#define LM90_REG_W_LOCAL_CRIT 0x20 ++#define LM90_REG_R_REMOTE_TEMPH 0x01 ++#define LM90_REG_R_REMOTE_TEMPL 0x10 ++#define LM90_REG_R_REMOTE_OFFSH 0x11 ++#define LM90_REG_W_REMOTE_OFFSH 0x11 ++#define LM90_REG_R_REMOTE_OFFSL 0x12 ++#define LM90_REG_W_REMOTE_OFFSL 0x12 ++#define LM90_REG_R_REMOTE_HIGHH 0x07 ++#define LM90_REG_W_REMOTE_HIGHH 0x0D ++#define LM90_REG_R_REMOTE_HIGHL 0x13 ++#define LM90_REG_W_REMOTE_HIGHL 0x13 ++#define LM90_REG_R_REMOTE_LOWH 0x08 ++#define LM90_REG_W_REMOTE_LOWH 0x0E ++#define LM90_REG_R_REMOTE_LOWL 0x14 ++#define LM90_REG_W_REMOTE_LOWL 0x14 ++#define LM90_REG_R_REMOTE_CRIT 0x19 ++#define LM90_REG_W_REMOTE_CRIT 0x19 ++#define LM90_REG_R_TCRIT_HYST 0x21 ++#define LM90_REG_W_TCRIT_HYST 0x21 ++ ++/* ++ * Conversions and various macros ++ * The LM90 uses signed 8-bit values for the local temperatures, ++ * and signed 11-bit values for the remote temperatures (except ++ * T_CRIT). The 11-bit conversion formulas may not round negative ++ * numbers perfectly, but who cares? ++ */ ++ ++#define TEMP1_FROM_REG(val) (val & 0x80 ? val-0x100 : val) ++#define TEMP1_TO_REG(val) (val < 0 ? val+0x100 : val) ++#define TEMP2_FROM_REG(val) (((val & 0x8000 ? val-0x10000 : val) \ ++ * 10 + 128) >> 8) ++#define TEMP2_TO_REG(val) (((val << 8) / 10 + (val < 0 ? \ ++ 0x10000 : 0)) & 0xFFE0) ++#define HYST_TO_REG(val) (val < 0 ? 0 : val > 31 ? 31 : val) ++ ++/* ++ * Functions declaration ++ */ ++ ++static int lm90_attach_adapter(struct i2c_adapter *adapter); ++static int lm90_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void lm90_init_client(struct i2c_client *client); ++static int lm90_detach_client(struct i2c_client *client); ++static void lm90_local_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm90_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm90_local_tcrit(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm90_remote_tcrit(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm90_local_hyst(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm90_remote_hyst(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void lm90_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++/* ++ * Driver data (common to all clients) ++ */ ++ ++static struct i2c_driver lm90_driver = { ++ .owner = THIS_MODULE, ++ .name = "LM90/ADM1032 sensor driver", ++ .id = I2C_DRIVERID_LM90, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm90_attach_adapter, ++ .detach_client = lm90_detach_client ++}; ++ ++/* ++ * Client data (each client gets its own) ++ */ ++ ++struct lm90_data ++{ ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* zero until following fields are valid */ ++ unsigned long last_updated; /* in jiffies */ ++ ++ /* registers values */ ++ u8 local_temp, local_high, local_low; ++ u16 remote_temp, remote_high, remote_low; /* combined */ ++ u8 local_crit, remote_crit; ++ u8 hyst; /* linked to two sysctl files (hyst1 RW, hyst2 RO) */ ++ u16 alarms; /* bitvector, combined */ ++}; ++ ++/* ++ * Proc entries ++ * These files are created for each detected LM90. ++ */ ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define LM90_SYSCTL_LOCAL_TEMP 1200 ++#define LM90_SYSCTL_REMOTE_TEMP 1201 ++#define LM90_SYSCTL_LOCAL_TCRIT 1204 ++#define LM90_SYSCTL_REMOTE_TCRIT 1205 ++#define LM90_SYSCTL_LOCAL_HYST 1207 ++#define LM90_SYSCTL_REMOTE_HYST 1208 ++#define LM90_SYSCTL_ALARMS 1210 ++ ++#define LM90_ALARM_LOCAL_HIGH 0x40 ++#define LM90_ALARM_LOCAL_LOW 0x20 ++#define LM90_ALARM_LOCAL_CRIT 0x01 ++#define LM90_ALARM_REMOTE_HIGH 0x10 ++#define LM90_ALARM_REMOTE_LOW 0x08 ++#define LM90_ALARM_REMOTE_CRIT 0x02 ++#define LM90_ALARM_REMOTE_OPEN 0x04 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++ ++static ctl_table lm90_dir_table_template[] = ++{ ++ {LM90_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_temp}, ++ {LM90_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_temp}, ++ {LM90_SYSCTL_LOCAL_TCRIT, "tcrit1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_tcrit}, ++ {LM90_SYSCTL_REMOTE_TCRIT, "tcrit2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_tcrit}, ++ {LM90_SYSCTL_LOCAL_HYST, "hyst1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_hyst}, ++ {LM90_SYSCTL_REMOTE_HYST, "hyst2", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_hyst}, ++ {LM90_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_alarms}, ++ {0} ++}; ++ ++/* ++ * Internal variables ++ */ ++ ++static int lm90_id = 0; ++ ++/* ++ * Real code ++ */ ++ ++static int lm90_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, lm90_detect); ++} ++ ++/* ++ * The following function does more than just detection. If detection ++ * succeeds, it also registers the new chip. ++ */ ++static int lm90_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ struct i2c_client *new_client; ++ struct lm90_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) ++ { ++ printk("lm90.o: Called for an ISA bus adapter, aborting.\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ { ++#ifdef DEBUG ++ printk("lm90.o: I2C bus doesn't support byte read mode, " ++ "skipping.\n"); ++#endif ++ return 0; ++ } ++ ++ if (!(data = kmalloc(sizeof(struct lm90_data), GFP_KERNEL))) ++ { ++ printk("lm90.o: Out of memory in lm90_detect (new_client).\n"); ++ return -ENOMEM; ++ } ++ ++ /* ++ * The common I2C client data is placed right before the ++ * LM90-specific data. The LM90-specific data is pointed to by the ++ * data field from the I2C client data. ++ */ ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &lm90_driver; ++ new_client->flags = 0; ++ ++ /* ++ * Now we do the remaining detection. A negative kind means that ++ * the driver was loaded with no force parameter (default), so we ++ * must both detect and identify the chip. A zero kind means that ++ * the driver was loaded with the force parameter, the detection ++ * step shall be skipped. A positive kind means that the driver ++ * was loaded with the force parameter and a given kind of chip is ++ * requested, so both the detection and the identification steps ++ * are skipped. ++ */ ++ ++ /* Default to an LM90 if forced */ ++ if (kind == 0) ++ kind = lm90; ++ ++ if (kind < 0) /* detection and identification */ ++ { ++ u8 man_id, chip_id, reg_config1, reg_convrate; ++ ++ man_id = i2c_smbus_read_byte_data(new_client, ++ LM90_REG_R_MAN_ID); ++ chip_id = i2c_smbus_read_byte_data(new_client, ++ LM90_REG_R_CHIP_ID); ++ reg_config1 = i2c_smbus_read_byte_data(new_client, ++ LM90_REG_R_CONFIG1); ++ reg_convrate = i2c_smbus_read_byte_data(new_client, ++ LM90_REG_R_CONVRATE); ++ ++ if (man_id == 0x01) /* National Semiconductor */ ++ { ++ u8 reg_config2; ++ ++ reg_config2 = i2c_smbus_read_byte_data(new_client, ++ LM90_REG_R_CONFIG2); ++ ++ if ((reg_config1 & 0x2A) == 0x00 ++ && (reg_config2 & 0xF8) == 0x00 ++ && reg_convrate <= 0x09) ++ { ++ if (address == 0x4C ++ && (chip_id & 0xF0) == 0x20) /* LM90 */ ++ kind = lm90; ++ else if ((chip_id & 0xF0) == 0x30) /* LM89/LM99 */ ++ kind = lm99; ++ else if (address == 0x4C ++ && (chip_id & 0xF0) == 0x10) /* LM86 */ ++ kind = lm99; ++ } ++ } ++ else if (man_id == 0x41) /* Analog Devices */ ++ { ++ if (address == 0x4C ++ && (chip_id & 0xF0) == 0x40 /* ADM1032 */ ++ && (reg_config1 & 0x3F) == 0x00 ++ && reg_convrate <= 0x0A) ++ kind = adm1032; ++ } ++ else if (man_id == 0x4D) /* Maxim */ ++ { ++ if (address == 0x4C ++ && (reg_config1 & 0x1F) == 0 ++ && reg_convrate <= 0x09) ++ kind = max6657; ++ } ++ } ++ ++ if (kind <= 0) /* identification failed */ ++ { ++ printk("lm90.o: Unsupported chip.\n"); ++ goto ERROR1; ++ } ++ ++ if (kind == lm90) ++ { ++ type_name = "lm90"; ++ client_name = "LM90 chip"; ++ } ++ else if (kind == adm1032) ++ { ++ type_name = "adm1032"; ++ client_name = "ADM1032 chip"; ++ } ++ else if (kind == lm99) ++ { ++ type_name = "lm99"; ++ client_name = "LM99 chip"; ++ } ++ else if (kind == lm86) ++ { ++ type_name = "lm86"; ++ client_name = "LM86 chip"; ++ } ++ else if (kind == max6657) ++ { ++ type_name = "max6657"; ++ client_name = "MAX6657 chip"; ++ } ++ else ++ { ++ printk("lm90.o: Unknown kind %d.\n", kind); ++ goto ERROR1; ++ } ++ ++ /* ++ * OK, we got a valid chip so we can fill in the remaining client ++ * fields. ++ */ ++ ++ strcpy(new_client->name, client_name); ++ new_client->id = lm90_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* ++ * Tell the I2C layer a new client has arrived. ++ */ ++ ++ if ((err = i2c_attach_client(new_client))) ++ { ++#ifdef DEBUG ++ printk("lm90.o: Failed attaching client.\n"); ++#endif ++ goto ERROR1; ++ } ++ ++ /* ++ * Register a new directory entry. ++ */ ++ ++ if ((err = i2c_register_entry(new_client, type_name, ++ lm90_dir_table_template)) < 0) ++ { ++#ifdef DEBUG ++ printk("lm90.o: Failed registering directory entry.\n"); ++#endif ++ goto ERROR2; ++ } ++ data->sysctl_id = err; ++ ++ /* ++ * Initialize the LM90 chip. ++ */ ++ ++ lm90_init_client(new_client); ++ return 0; ++ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ return err; ++} ++ ++static void lm90_init_client(struct i2c_client *client) ++{ ++ u8 config; ++ ++ /* ++ * Start the conversions. ++ */ ++ ++ i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, ++ 5); /* 2 Hz */ ++ config = i2c_smbus_read_byte_data(client, LM90_REG_R_CONFIG1); ++ if (config & 0x40) ++ i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, ++ config & 0xBF); /* run */ ++} ++ ++ ++static int lm90_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct lm90_data *) (client->data))->sysctl_id); ++ if ((err = i2c_detach_client(client))) ++ { ++ printk("lm90.o: Client deregistration failed, client not " ++ "detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ return 0; ++} ++ ++static void lm90_update_client(struct i2c_client *client) ++{ ++ struct lm90_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ * 2) || ++ (jiffies < data->last_updated) || !data->valid) ++ { ++ u8 oldh, newh; ++#ifdef DEBUG ++ printk("lm90.o: Updating data.\n"); ++#endif ++ ++ data->local_temp = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_LOCAL_TEMP); ++ data->local_high = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_LOCAL_HIGH); ++ data->local_low = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_LOCAL_LOW); ++ data->local_crit = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_LOCAL_CRIT); ++ ++ /* ++ * There is a trick here. We have to read two registers to ++ * have the remote sensor temperature, but we have to beware ++ * a conversion could occur inbetween the readings. The ++ * datasheet says we should either use the one-shot ++ * conversion register, which we don't want to do (disables ++ * hardware monitoring) or monitor the busy bit, which is ++ * impossible (we can't read the values and monitor that bit ++ * at the exact same time). So the solution used here is to ++ * read the high byte once, then the low byte, then the high ++ * byte again. If the new high byte matches the old one, ++ * then we have a valid reading. Else we have to read the low ++ * byte again, and now we believe we have a correct reading. ++ */ ++ ++ oldh = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_TEMPH); ++ data->remote_temp = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_TEMPL); ++ newh = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_TEMPH); ++ if (newh != oldh) ++ { ++ data->remote_temp = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_TEMPL); ++#ifdef DEBUG ++ oldh = /* actually newer */ ++ i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_TEMPH); ++ if (newh != oldh) ++ printk("lm90.o: Remote temperature may be wrong.\n"); ++#endif ++ } ++ data->remote_temp |= (newh << 8); ++ data->remote_high = ++ (i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_HIGHH) << 8) ++ + i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_HIGHL); ++ data->remote_low = ++ (i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_LOWH) << 8) ++ + i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_LOWL); ++ data->remote_crit = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_REMOTE_CRIT); ++ ++ data->hyst = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_TCRIT_HYST); ++ data->alarms = ++ i2c_smbus_read_byte_data(client, LM90_REG_R_STATUS); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++static void lm90_local_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = TEMP1_FROM_REG(data->local_high); ++ results[1] = TEMP1_FROM_REG(data->local_low); ++ results[2] = TEMP1_FROM_REG(data->local_temp); ++ *nrels_mag = 3; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->local_high = TEMP1_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_LOCAL_HIGH, ++ data->local_high); ++ } ++ if (*nrels_mag >= 2) ++ { ++ data->local_low = TEMP1_TO_REG(results[1]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_LOCAL_LOW, ++ data->local_low); ++ } ++ } ++} ++ ++static void lm90_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = TEMP2_FROM_REG(data->remote_high); ++ results[1] = TEMP2_FROM_REG(data->remote_low); ++ results[2] = TEMP2_FROM_REG(data->remote_temp); ++ *nrels_mag = 3; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->remote_high = TEMP2_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_HIGHH, ++ data->remote_high >> 8); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_HIGHL, ++ data->remote_high & 0xFF); ++ } ++ if (*nrels_mag >= 2) ++ { ++ data->remote_low = TEMP2_TO_REG(results[1]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_LOWH, ++ data->remote_low >> 8); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_LOWL, ++ data->remote_low & 0xFF); ++ } ++ } ++} ++ ++static void lm90_local_tcrit(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = TEMP1_FROM_REG(data->local_crit); ++ *nrels_mag = 1; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->local_crit = TEMP1_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_LOCAL_CRIT, ++ data->local_crit); ++ } ++ } ++} ++ ++static void lm90_remote_tcrit(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = TEMP1_FROM_REG(data->remote_crit); ++ *nrels_mag = 1; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->remote_crit = TEMP1_TO_REG(results[0]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_CRIT, ++ data->remote_crit); ++ } ++ } ++} ++ ++/* ++ * One quick note about hysteresis. Internally, the hysteresis value ++ * is held in a single register by the LM90, as a relative value. ++ * This relative value applies to both the local critical temperature ++ * and the remote critical temperature. Since all temperatures exported ++ * through procfs have to be absolute, we have to do some conversions. ++ * The solution retained here is to export two absolute values, one for ++ * each critical temperature. In order not to confuse the users too ++ * much, only one file is writable. Would we fail to do so, users ++ * would probably attempt to write to both files, as if they were ++ * independant, and since they aren't, they wouldn't understand why ++ * setting one affects the other one (and would probably claim there's ++ * a bug in the driver). ++ */ ++ ++static void lm90_local_hyst(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = TEMP1_FROM_REG(data->local_crit) - ++ TEMP1_FROM_REG(data->hyst); ++ *nrels_mag = 1; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) ++ { ++ if (*nrels_mag >= 1) ++ { ++ data->hyst = HYST_TO_REG(data->local_crit - results[0]); ++ i2c_smbus_write_byte_data(client, LM90_REG_W_TCRIT_HYST, ++ data->hyst); ++ } ++ } ++} ++ ++static void lm90_remote_hyst(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = TEMP1_FROM_REG(data->remote_crit) - ++ TEMP1_FROM_REG(data->hyst); ++ *nrels_mag = 1; ++ } ++} ++ ++static void lm90_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct lm90_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; /* magnitude */ ++ else if (operation == SENSORS_PROC_REAL_READ) ++ { ++ lm90_update_client(client); ++ results[0] = data->alarms; ++ *nrels_mag = 1; ++ } ++} ++ ++static int __init sm_lm90_init(void) ++{ ++ printk(KERN_INFO "lm90.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&lm90_driver); ++} ++ ++static void __exit sm_lm90_exit(void) ++{ ++ i2c_del_driver(&lm90_driver); ++} ++ ++MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); ++MODULE_DESCRIPTION("LM90/ADM1032 sensor driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_lm90_init); ++module_exit(sm_lm90_exit); +--- linux-old/drivers/sensors/lm92.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/lm92.c Mon Dec 13 20:18:49 2004 +@@ -0,0 +1,421 @@ ++ ++/* ++ * LM92 - Part of lm_sensors, Linux kernel modules for hardware ++ * monitoring ++ * ++ * Author: Abraham van der Merwe <abraham@2d3d.co.za> ++ * ++ * Linux support for the National Semiconductor LM92 Temperature ++ * Sensor. ++ * ++ * Based on code from the lm-sensors project which is available ++ * at http://www.lm-sensors.nu/. lm87.c have been particularly ++ * helpful (: ++ * ++ * This source code is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * version 2 as published by the Free Software Foundation. ++ */ ++ ++#include <linux/config.h> ++#include <linux/module.h> ++#include <linux/kernel.h> ++#include <linux/string.h> ++#include <linux/slab.h> ++#include <linux/init.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/proc_fs.h> ++#include <linux/sysctl.h> ++#include <asm/semaphore.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* if defined, 4 faults must occur consecutively to set alarm flags */ ++/* #define ENABLE_FAULT_QUEUE */ ++ ++#define LM92_REG_TEMPERATURE 0x00 /* ro, 16-bit */ ++#define LM92_REG_CONFIGURATION 0x01 /* rw, 8-bit */ ++#define LM92_REG_TRIP_HYSTERESIS 0x02 /* rw, 16-bit */ ++#define LM92_REG_TRIP_CRITICAL 0x03 /* rw, 16-bit */ ++#define LM92_REG_TRIP_LOW 0x04 /* rw, 16-bit */ ++#define LM92_REG_TRIP_HIGH 0x05 /* rw, 16-bit */ ++#define LM92_REG_MANUFACTURER 0x07 /* ro, 16-bit */ ++ ++#define LM92_MANUFACTURER_ID 0x8001 ++ ++#define TEMP_MIN (-4096) ++#define TEMP_MAX 4095 ++ ++#define LIMIT(x) do { \ ++ if ((x) < TEMP_MIN) (x) = TEMP_MIN; \ ++ if ((x) > TEMP_MAX) (x) = TEMP_MAX; \ ++ } while (0) ++ ++#define PROC_TO_NATIVE(x) ((x) / 625) ++#define NATIVE_TO_PROC(x) ((x) * 625) ++#define CELSIUS(x) ((x) * 16) ++ ++static void lm92_temp (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results); ++static void lm92_alarms (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results); ++ ++/* -- SENSORS SYSCTL START -- */ ++#define LM92_SYSCTL_ALARMS 2001 /* high, low, critical */ ++#define LM92_SYSCTL_TEMP 1200 /* high, low, critical, hysteresis, input */ ++ ++#define LM92_ALARM_TEMP_HIGH 0x01 ++#define LM92_ALARM_TEMP_LOW 0x02 ++#define LM92_ALARM_TEMP_CRIT 0x04 ++#define LM92_TEMP_HIGH 0x08 ++#define LM92_TEMP_LOW 0x10 ++#define LM92_TEMP_CRIT 0x20 ++#define LM92_TEMP_HYST 0x40 ++#define LM92_TEMP_INPUT 0x80 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++static ctl_table lm92_dir_table[] = { ++ {LM92_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm92_temp, NULL}, ++ {LM92_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &lm92_alarms, NULL}, ++ {0} ++}; ++ ++/* NOTE: all temperatures are degrees centigrade * 16 */ ++typedef struct { ++ struct i2c_client client; ++ int sysctl_id; ++ unsigned long timestamp; ++ struct { ++ long high; ++ long low; ++ long crit; ++ long hyst; ++ long input; ++ } temp; ++ struct { ++ long low; ++ long high; ++ long crit; ++ } alarms; ++} lm92_t; ++ ++/* this is needed for each client driver method */ ++static struct i2c_driver lm92_driver; ++ ++/* ensure exclusive access to chip and static variables */ ++static DECLARE_MUTEX (mutex); ++ ++/* addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* insmod parameters */ ++SENSORS_INSMOD_1 (lm92); ++ ++static inline int lm92_write8 (struct i2c_client *client,u8 reg,u8 value) ++{ ++ return (i2c_smbus_write_byte_data (client,reg,value) < 0 ? -EIO : 0); ++} ++ ++static inline int lm92_read16 (struct i2c_client *client,u8 reg,u16 *value) ++{ ++ s32 tmp = i2c_smbus_read_word_data (client,reg); ++ ++ if (tmp < 0) return (-EIO); ++ ++ /* convert the data to little endian format */ ++ *value = swab16((u16) tmp); ++ ++ return (0); ++} ++ ++static inline int lm92_write16 (struct i2c_client *client,u8 reg,u16 value) ++{ ++ /* convert the data to big endian format */ ++ if (i2c_smbus_write_word_data(client, reg, swab16(value)) < 0) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int lm92_read (struct i2c_client *client) ++{ ++ lm92_t *data = (lm92_t *) client->data; ++ u16 value[5]; ++ ++ if ((jiffies - data->timestamp) > HZ) { ++ if (lm92_read16 (client,LM92_REG_TEMPERATURE,value) < 0 || ++ lm92_read16 (client,LM92_REG_TRIP_HYSTERESIS,value + 1) < 0 || ++ lm92_read16 (client,LM92_REG_TRIP_CRITICAL,value + 2) < 0 || ++ lm92_read16 (client,LM92_REG_TRIP_LOW,value + 3) < 0 || ++ lm92_read16 (client,LM92_REG_TRIP_HIGH,value + 4) < 0) ++ return (-EIO); ++ ++ data->temp.input = (s16) value[0] >> 3; ++ data->temp.hyst = (s16) value[1] >> 3; ++ data->temp.crit = (s16) value[2] >> 3; ++ data->temp.low = (s16) value[3] >> 3; ++ data->temp.high = (s16) value[4] >> 3; ++ ++ data->alarms.low = value[0] & 1; ++ data->alarms.high = (value[0] & 2) >> 1; ++ data->alarms.crit = (value[0] & 4) >> 2; ++ ++ data->timestamp = jiffies; ++ } ++ ++ return (0); ++} ++ ++static int lm92_write (struct i2c_client *client) ++{ ++ lm92_t *data = (lm92_t *) client->data; ++ ++ LIMIT (data->temp.hyst); ++ LIMIT (data->temp.crit); ++ LIMIT (data->temp.low); ++ LIMIT (data->temp.high); ++ ++ if (lm92_write16 (client,LM92_REG_TRIP_HYSTERESIS,((s16) data->temp.hyst << 3)) < 0 || ++ lm92_write16 (client,LM92_REG_TRIP_CRITICAL,((s16) data->temp.crit << 3)) < 0 || ++ lm92_write16 (client,LM92_REG_TRIP_LOW,((s16) data->temp.low << 3)) < 0 || ++ lm92_write16 (client,LM92_REG_TRIP_HIGH,((s16) data->temp.high << 3)) < 0) ++ return (-EIO); ++ ++ return (0); ++} ++ ++static void lm92_temp (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results) ++{ ++ if (!down_interruptible (&mutex)) { ++ lm92_t *data = (lm92_t *) client->data; ++ ++ if (operation == SENSORS_PROC_REAL_READ) { ++ lm92_read (client); ++ results[0] = NATIVE_TO_PROC (data->temp.input); ++ results[1] = NATIVE_TO_PROC (data->temp.high); ++ results[2] = NATIVE_TO_PROC (data->temp.low); ++ results[3] = NATIVE_TO_PROC (data->temp.crit); ++ results[4] = NATIVE_TO_PROC (data->temp.hyst); ++ *nrels_mag = 5; ++ } else if (operation == SENSORS_PROC_REAL_WRITE && *nrels_mag == 4) { ++ data->temp.high = PROC_TO_NATIVE (results[0]); ++ data->temp.low = PROC_TO_NATIVE (results[1]); ++ data->temp.crit = PROC_TO_NATIVE (results[2]); ++ data->temp.hyst = PROC_TO_NATIVE (results[3]); ++ lm92_write (client); ++ } else if (operation == SENSORS_PROC_REAL_INFO) { ++ *nrels_mag = 4; ++ } ++ ++ up (&mutex); ++ } ++} ++ ++static void lm92_alarms (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results) ++{ ++ if (!down_interruptible (&mutex)) { ++ lm92_t *data = (lm92_t *) client->data; ++ ++ if (operation == SENSORS_PROC_REAL_READ) { ++ lm92_read (client); ++ results[0] = data->alarms.high || (data->alarms.low << 1) || (data->alarms.crit << 2); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_INFO) { ++ *nrels_mag = 0; ++ } ++ ++ up (&mutex); ++ } ++} ++ ++static int max6635_check(struct i2c_client *client) ++{ ++ int i; ++ u16 temp_low, temp_high, temp_hyst, temp_crit; ++ u8 conf; ++ ++ temp_low = i2c_smbus_read_word_data(client, LM92_REG_TRIP_LOW); ++ temp_high = i2c_smbus_read_word_data(client, LM92_REG_TRIP_HIGH); ++ temp_hyst = i2c_smbus_read_word_data(client, LM92_REG_TRIP_HYSTERESIS); ++ temp_crit = i2c_smbus_read_word_data(client, LM92_REG_TRIP_CRITICAL); ++ ++ if ((temp_low & 0x7f00) || (temp_high & 0x7f00) ++ || (temp_hyst & 0x7f00) || (temp_crit & 0x7f00)) ++ return 0; ++ ++ conf = i2c_smbus_read_byte_data(client, LM92_REG_CONFIGURATION); ++ ++ for (i=0; i<128; i+=16) { ++ if (temp_low != i2c_smbus_read_word_data(client, LM92_REG_TRIP_LOW + i) ++ || temp_high != i2c_smbus_read_word_data(client, LM92_REG_TRIP_HIGH + i) ++ || temp_hyst != i2c_smbus_read_word_data(client, LM92_REG_TRIP_HYSTERESIS + i) ++ || temp_crit != i2c_smbus_read_word_data(client, LM92_REG_TRIP_CRITICAL + i) ++ || conf != i2c_smbus_read_byte_data(client, LM92_REG_CONFIGURATION + i)) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int lm92_init_client (struct i2c_client *client) ++{ ++ lm92_t *data = (lm92_t *) client->data; ++ u8 value = 0; ++ int result; ++ ++ /* force reads to query the chip */ ++ data->timestamp = 0; ++ ++ /* setup the configuration register */ ++ ++#ifdef ENABLE_FAULT_QUEUE ++ value |= 0x10; ++#endif /* #ifdef ENABLE_FAULT_QUEUE */ ++ ++ if (lm92_write8 (client,LM92_REG_CONFIGURATION,value) < 0) ++ return (-ENODEV); ++ ++ /* set default alarm trigger values */ ++ ++ data->temp.high = CELSIUS (64); ++ data->temp.low = CELSIUS (10); ++ data->temp.crit = CELSIUS (80); ++ data->temp.hyst = CELSIUS (2); ++ ++ if ((result = lm92_write (client)) < 0) ++ return (result); ++ ++ /* read everything once so that our cached data is updated */ ++ ++ if ((result = lm92_read (client)) < 0) ++ return (result); ++ ++ return (0); ++} ++ ++static int lm92_detect (struct i2c_adapter *adapter,int address,unsigned short flags,int kind) ++{ ++ static int id = 0; ++ struct i2c_client *client; ++ lm92_t *data; ++ int result; ++ u16 manufacturer; ++ ++ if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_BYTE_DATA)) ++ return (-ENODEV); ++ ++ if (!(data = kmalloc(sizeof(lm92_t), GFP_KERNEL))) ++ return (-ENOMEM); ++ ++ client = &data->client; ++ client->addr = address; ++ client->data = data; ++ client->adapter = adapter; ++ client->driver = &lm92_driver; ++ client->flags = 0; ++ strcpy (client->name,lm92_driver.name); ++ ++ if (down_interruptible (&mutex)) { ++ result = -ERESTARTSYS; ++ goto ERROR1; ++ } ++ ++ if (kind < 0) { ++ /* Is it an lm92? */ ++ if (address < 0x4c ++ && (lm92_read16(client,LM92_REG_MANUFACTURER,&manufacturer) < 0 ++ || manufacturer != LM92_MANUFACTURER_ID)) { ++ /* Is it a MAX6635/MAX6635/MAX6635? */ ++ if (!max6635_check(client)) { ++ result = -ENODEV; ++ goto ERROR2; ++ } ++ } ++ } ++ ++ if ((result = i2c_attach_client (client))) { ++ goto ERROR2; ++ } ++ ++ if ((result = i2c_register_entry (client,client->name,lm92_dir_table)) < 0) { ++ goto ERROR3; ++ } ++ data->sysctl_id = result; ++ ++ if ((result = lm92_init_client (client)) < 0) { ++ goto ERROR4; ++ } ++ ++ client->id = id++; ++ ++ up (&mutex); ++ ++ return (0); ++ ++ERROR4: ++ i2c_deregister_entry(data->sysctl_id); ++ERROR3: ++ i2c_detach_client(client); ++ERROR2: ++ up(&mutex); ++ERROR1: ++ kfree(data); ++ return result; ++} ++ ++static int lm92_attach_adapter (struct i2c_adapter *adapter) ++{ ++ return i2c_detect (adapter,&addr_data,lm92_detect); ++} ++ ++static int lm92_detach_client (struct i2c_client *client) ++{ ++ int result; ++ ++ i2c_deregister_entry (((lm92_t *) (client->data))->sysctl_id); ++ ++ if ((result = i2c_detach_client (client))) ++ return (result); ++ ++ kfree(client->data); ++ ++ return (0); ++} ++ ++ ++static struct i2c_driver lm92_driver = { ++ .owner = THIS_MODULE, ++ .name = "lm92", ++ .id = I2C_DRIVERID_LM92, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = lm92_attach_adapter, ++ .detach_client = lm92_detach_client, ++}; ++ ++static int __init sm_lm92_init(void) ++{ ++ printk ("lm92.o version %s (%s)\n",LM_VERSION,LM_DATE); ++ return i2c_add_driver(&lm92_driver); ++} ++ ++ ++static void __exit sm_lm92_exit(void) ++{ ++ i2c_del_driver(&lm92_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ("Abraham van der Merwe <abraham@2d3d.co.za>"); ++MODULE_DESCRIPTION ("Linux support for LM92 Temperature Sensor"); ++ ++MODULE_LICENSE ("GPL"); ++ ++module_init(sm_lm92_init); ++module_exit(sm_lm92_exit); ++ +--- linux-old/drivers/sensors/matorb.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/matorb.c Mon Dec 13 20:18:49 2004 +@@ -0,0 +1,286 @@ ++/* ++ matorb.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> ++ and Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++ ++#define DEBUG 1 ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x2E, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(matorb); ++ ++/* Many MATORB constants specified below */ ++ ++ ++/* Each client has this additional data */ ++struct matorb_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++}; ++ ++static int matorb_attach_adapter(struct i2c_adapter *adapter); ++static int matorb_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void matorb_init_client(struct i2c_client *client); ++static int matorb_detach_client(struct i2c_client *client); ++ ++static int matorb_write_value(struct i2c_client *client, u8 reg, ++ u16 value); ++static void matorb_disp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void matorb_update_client(struct i2c_client *client); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver matorb_driver = { ++ .owner = THIS_MODULE, ++ .name = "Matrix Orbital LCD driver", ++ .id = I2C_DRIVERID_MATORB, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = matorb_attach_adapter, ++ .detach_client = matorb_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define MATORB_SYSCTL_DISP 1000 ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected MATORB. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table matorb_dir_table_template[] = { ++ {MATORB_SYSCTL_DISP, "disp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &matorb_disp}, ++ {0} ++}; ++ ++static int matorb_id = 0; ++ ++static int matorb_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, matorb_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int matorb_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, cur; ++ struct i2c_client *new_client; ++ struct matorb_data *data; ++ int err = 0; ++ const char *type_name = "matorb"; ++ const char *client_name = "matorb"; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("matorb.o: matorb_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE | ++ I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) ++ goto ERROR0; ++ ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access matorb_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct matorb_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &matorb_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. It is lousy. */ ++ cur = i2c_smbus_write_byte_data(new_client, 0x0FE, 0x58); /* clear screen */ ++ ++ printk("matorb.o: debug detect 0x%X\n", cur); ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = matorb_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ matorb_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ matorb_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int matorb_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct matorb_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("matorb.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++#if 0 ++/* All registers are word-sized, except for the configuration register. ++ MATORB uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int matorb_read_value(struct i2c_client *client, u8 reg) ++{ ++ return -1; /* Doesn't support reads */ ++} ++#endif ++ ++/* All registers are word-sized, except for the configuration register. ++ MATORB uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int matorb_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if (reg == 0) { ++ return i2c_smbus_write_byte(client, value); ++ } else { ++ return i2c_smbus_write_byte_data(client, reg, value); ++ } ++} ++ ++static void matorb_init_client(struct i2c_client *client) ++{ ++ /* Initialize the MATORB chip */ ++} ++ ++static void matorb_update_client(struct i2c_client *client) ++{ ++ struct matorb_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting matorb update\n"); ++#endif ++ ++/* nothing yet */ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void matorb_disp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int i; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ matorb_update_client(client); ++ results[0] = 0; ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ for (i = 1; i <= *nrels_mag; i++) { ++ matorb_write_value(client, 0, results[i - 1]); ++ } ++ } ++} ++ ++static int __init sm_matorb_init(void) ++{ ++ printk("matorb.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&matorb_driver); ++} ++ ++static void __exit sm_matorb_exit(void) ++{ ++ i2c_del_driver(&matorb_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("MATORB driver"); ++ ++module_init(sm_matorb_init); ++module_exit(sm_matorb_exit); +--- linux-old/drivers/sensors/max6650.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/max6650.c Mon Dec 13 20:18:50 2004 +@@ -0,0 +1,545 @@ ++/* ++ * max6650.c - Part of lm_sensors, Linux kernel modules for hardware ++ * monitoring. ++ * ++ * Author: John Morris <john.morris@spirentcom.com> ++ * ++ * Copyright (c) 2003 Spirent Communications ++ * ++ * This module has only been tested with the MAX6651 chip. It should ++ * work with the MAX6650 also, though with reduced functionality. It ++ * does not yet distinguish max6650 and max6651 chips. ++ * ++ * Tha datasheet was last seen at: ++ * ++ * http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/i2c-id.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++#ifndef I2C_DRIVERID_MAX6650 ++#define I2C_DRIVERID_MAX6650 1044 ++#endif ++ ++/* ++ * Addresses to scan. There are four disjoint possibilities, by pin config. ++ */ ++ ++static unsigned short normal_i2c[] = {0x1b, 0x1f, 0x48, 0x4b, SENSORS_I2C_END}; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* ++ * Insmod parameters ++ */ ++ ++SENSORS_INSMOD_1(max6650); ++ ++/* ++ * MAX 6650/6651 registers ++ */ ++ ++#define MAX6650_REG_SPEED 0x00 ++#define MAX6650_REG_CONFIG 0x02 ++#define MAX6650_REG_GPIO_DEF 0x04 ++#define MAX6650_REG_DAC 0x06 ++#define MAX6650_REG_ALARM_EN 0x08 ++#define MAX6650_REG_ALARM 0x0A ++#define MAX6650_REG_TACH0 0x0C ++#define MAX6650_REG_TACH1 0x0E ++#define MAX6650_REG_TACH2 0x10 ++#define MAX6650_REG_TACH3 0x12 ++#define MAX6650_REG_GPIO_STAT 0x14 ++#define MAX6650_REG_COUNT 0x16 ++ ++/* ++ * Config register bits ++ */ ++ ++#define MAX6650_CFG_MODE_MASK 0x30 ++#define MAX6650_CFG_MODE_ON 0x00 ++#define MAX6650_CFG_MODE_OFF 0x10 ++#define MAX6650_CFG_MODE_CLOSED_LOOP 0x20 ++#define MAX6650_CFG_MODE_OPEN_LOOP 0x30 ++ ++static const u8 tach_reg[] = ++{ ++ MAX6650_REG_TACH0, MAX6650_REG_TACH1, ++ MAX6650_REG_TACH2, MAX6650_REG_TACH3 ++}; ++ ++#define MAX6650_INT_CLK 254000 /* Default clock speed - 254 kHz */ ++ ++/* ++ * Functions declaration ++ */ ++ ++static void max6650_fan (struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++static void max6650_speed (struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++static void max6650_xdump (struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++static int max6650_detect(struct i2c_adapter *adapter, int address, unsigned ++ short flags, int kind); ++static int max6650_attach_adapter(struct i2c_adapter *adapter); ++static int max6650_detach_client(struct i2c_client *client); ++static void max6650_init_client(struct i2c_client *client); ++static int max6650_read(struct i2c_client *client, u8 reg); ++ ++/* ++ * Driver data (common to all clients) ++ */ ++ ++ ++static struct i2c_driver max6650_driver = { ++ .owner = THIS_MODULE, ++ .name = "MAX6650/1 sensor driver", ++ .id = I2C_DRIVERID_MAX6650, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = max6650_attach_adapter, ++ .detach_client = max6650_detach_client ++}; ++ ++/* ++ * Client data (each client gets its own) ++ */ ++ ++struct max6650_data ++{ ++ struct i2c_client client; ++ int sysctl_id; ++ struct semaphore update_lock; ++ char valid; /* zero until following fields are valid */ ++ unsigned long last_updated; /* in jiffies */ ++ ++ /* register values */ ++ ++ u8 speed; ++ u8 config; ++ u8 tach[4]; ++ u8 count; ++}; ++ ++/* ++ * Proc entries ++ * These files are created for each detected max6650. ++ */ ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define MAX6650_SYSCTL_FAN1 1101 ++#define MAX6650_SYSCTL_FAN2 1102 ++#define MAX6650_SYSCTL_FAN3 1103 ++#define MAX6650_SYSCTL_FAN4 1104 ++#define MAX6650_SYSCTL_SPEED 1105 ++#define MAX6650_SYSCTL_XDUMP 1106 ++ ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++ ++static ctl_table max6650_dir_table_template[] = ++{ ++ {MAX6650_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, ++ {MAX6650_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, ++ {MAX6650_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, ++ {MAX6650_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, ++ {MAX6650_SYSCTL_SPEED, "speed", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_speed}, ++ {MAX6650_SYSCTL_XDUMP, "xdump", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_xdump}, ++ {0} ++}; ++ ++/* ++ * Internal variables ++ */ ++ ++static int max6650_id = 0; ++ ++/* ++ * Real code ++ */ ++ ++static int max6650_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, max6650_detect); ++} ++ ++/* ++ * The following function does more than just detection. If detection ++ * succeeds, it also registers the new chip. ++ */ ++ ++static int max6650_detect(struct i2c_adapter *adapter, int address, unsigned ++ short flags, int kind) ++{ ++ struct i2c_client *new_client; ++ struct max6650_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk("max6650.o: Called for an ISA bus adapter, aborting.\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { ++#ifdef DEBUG ++ printk("max6650.o: I2C bus doesn't support byte read mode, skipping.\n"); ++#endif ++ return 0; ++ } ++ ++ if (!(data = kmalloc(sizeof(struct max6650_data), GFP_KERNEL))) { ++ printk("max6650.o: Out of memory in max6650_detect (new_client).\n"); ++ return -ENOMEM; ++ } ++ ++ /* ++ * The common I2C client data is placed right before the ++ * max6650-specific data. The max6650-specific data is pointed to by the ++ * data field from the I2C client data. ++ */ ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &max6650_driver; ++ new_client->flags = 0; ++ ++ /* ++ * Now we do the remaining detection. A negative kind means that ++ * the driver was loaded with no force parameter (default), so we ++ * must both detect and identify the chip (actually there is only ++ * one possible kind of chip for now, max6650). A zero kind means that ++ * the driver was loaded with the force parameter, the detection ++ * step shall be skipped. A positive kind means that the driver ++ * was loaded with the force parameter and a given kind of chip is ++ * requested, so both the detection and the identification steps ++ * are skipped. ++ * ++ * Currently I can find no way to distinguish between a MAX6650 and ++ * a MAX6651. This driver has only been tried on the latter. ++ */ ++ ++ if (kind < 0) { /* detection */ ++ if ( ++ (max6650_read(new_client, MAX6650_REG_CONFIG) & 0xC0) || ++ (max6650_read(new_client, MAX6650_REG_GPIO_STAT) & 0xE0) || ++ (max6650_read(new_client, MAX6650_REG_ALARM_EN) & 0xE0) || ++ (max6650_read(new_client, MAX6650_REG_ALARM) & 0xE0) || ++ (max6650_read(new_client, MAX6650_REG_COUNT) & 0xFC) ++ ) ++ { ++#ifdef DEBUG ++ printk("max6650.o: max6650 detection failed at 0x%02x.\n", ++ address); ++#endif ++ goto ERROR1; ++ } ++ } ++ ++ if (kind <= 0) { /* identification */ ++ kind = max6650; ++ } ++ ++ if (kind <= 0) { /* identification failed */ ++ printk("max6650.o: Unsupported chip.\n"); ++ goto ERROR1; ++ } ++ ++ if (kind == max6650) { ++ type_name = "max6650"; ++ client_name = "max6650 chip"; ++ } else { ++ printk("max6650.o: Unknown kind %d.\n", kind); ++ goto ERROR1; ++ } ++ ++ /* ++ * OK, we got a valid chip so we can fill in the remaining client ++ * fields. ++ */ ++ ++ strcpy(new_client->name, client_name); ++ new_client->id = max6650_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* ++ * Tell the I2C layer a new client has arrived. ++ */ ++ ++ if ((err = i2c_attach_client(new_client))) { ++#ifdef DEBUG ++ printk("max6650.o: Failed attaching client.\n"); ++#endif ++ goto ERROR1; ++ } ++ ++ /* ++ * Register a new directory entry. ++ */ ++ if ((err = i2c_register_entry(new_client, type_name, ++ max6650_dir_table_template)) < 0) { ++#ifdef DEBUG ++ printk("max6650.o: Failed registering directory entry.\n"); ++#endif ++ goto ERROR2; ++ } ++ data->sysctl_id = err; ++ ++ /* ++ * Initialize the max6650 chip ++ */ ++ max6650_init_client(new_client); ++ return 0; ++ ++ERROR2: ++ i2c_detach_client(new_client); ++ERROR1: ++ kfree(data); ++ return err; ++} ++ ++static void max6650_init_client(struct i2c_client *client) ++{ ++ /* Nothing to do here - assume the BIOS has initialized the chip */ ++} ++ ++static int max6650_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct max6650_data *) (client->data))->sysctl_id); ++ if ((err = i2c_detach_client(client))) { ++ printk("max6650.o: Client deregistration failed, " ++ "client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ return 0; ++} ++ ++static int max6650_read(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int max6650_write(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void max6650_update_client(struct i2c_client *client) ++{ ++ int i; ++ struct max6650_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ) || ++ (jiffies < data->last_updated) || !data->valid) { ++#ifdef DEBUG ++ printk("max6650.o: Updating max6650 data.\n"); ++#endif ++ data->speed = max6650_read (client, MAX6650_REG_SPEED); ++ data->config = max6650_read (client, MAX6650_REG_CONFIG); ++ for (i = 0; i < 4; i++) { ++ data->tach[i] = max6650_read(client, tach_reg[i]); ++ } ++ data->count = max6650_read (client, MAX6650_REG_COUNT); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ up(&data->update_lock); ++} ++ ++static void max6650_fan (struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results) ++{ ++ int index = ctl_name - MAX6650_SYSCTL_FAN1; ++ struct max6650_data *data = client->data; ++ int tcount; /* Tachometer count time, 0.25 second units */ ++ ++ if (operation == SENSORS_PROC_REAL_INFO) { ++ *nrels_mag = 0; ++ } else if (operation == SENSORS_PROC_REAL_READ) { ++ max6650_update_client(client); ++ ++ /* ++ * Calculation details: ++ * ++ * Each tachometer counts over an interval given by the "count" ++ * register (0.25, 0.5, 1 or 2 seconds). This module assumes ++ * that the fans produce two pulses per revolution (this seems ++ * to be the most common). ++ */ ++ ++ tcount = 1 << data->count; /* 0.25 second units */ ++ results[0] = (data->tach[index] * 240) / tcount; /* counts per min */ ++ results[0] /= 2; /* Assume two counts per rev */ ++ *nrels_mag = 1; ++ } ++} ++ ++/* ++ * Set the fan speed to the specified RPM (or read back the RPM setting). ++ * ++ * The MAX6650/1 will automatically control fan speed when in closed loop ++ * mode. ++ * ++ * Assumptions: ++ * ++ * 1) The MAX6650/1 is running from its internal 254kHz clock (perhaps ++ * this should be made a module parameter). ++ * ++ * 2) The prescaler (low three bits of the config register) has already ++ * been set to an appropriate value. ++ * ++ * The relevant equations are given on pages 21 and 22 of the datasheet. ++ * ++ * From the datasheet, the relevant equation when in regulation is: ++ * ++ * [fCLK / (128 x (KTACH + 1))] = 2 x FanSpeed / KSCALE ++ * ++ * where: ++ * ++ * fCLK is the oscillator frequency (either the 254kHz internal ++ * oscillator or the externally applied clock) ++ * ++ * KTACH is the value in the speed register ++ * ++ * FanSpeed is the speed of the fan in rps ++ * ++ * KSCALE is the prescaler value (1, 2, 4, 8, or 16) ++ * ++ * When reading, we need to solve for FanSpeed. When writing, we need to ++ * solve for KTACH. ++ * ++ * Note: this tachometer is completely separate from the tachometers ++ * used to measure the fan speeds. Only one fan's speed (fan1) is ++ * controlled. ++ */ ++ ++static void max6650_speed (struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results) ++{ ++ struct max6650_data *data = client->data; ++ int kscale, ktach, fclk, rpm; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) { ++ *nrels_mag = 0; ++ } else if (operation == SENSORS_PROC_REAL_READ) { ++ /* ++ * Use the datasheet equation: ++ * ++ * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)] ++ * ++ * then multiply by 60 to give rpm. ++ */ ++ ++ max6650_update_client(client); ++ ++ kscale = 1 << (data->config & 7); ++ ktach = data->speed; ++ fclk = MAX6650_INT_CLK; ++ rpm = 60 * kscale * fclk / (256 * (ktach + 1)); ++ ++ results[0] = rpm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE && *nrels_mag >= 1) { ++ /* ++ * Divide the required speed by 60 to get from rpm to rps, then ++ * use the datasheet equation: ++ * ++ * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1 ++ */ ++ ++ max6650_update_client(client); ++ ++ rpm = results[0]; ++ kscale = 1 << (data->config & 7); ++ fclk = MAX6650_INT_CLK; ++ ktach = ((fclk * kscale) / (256 * rpm / 60)) - 1; ++ ++ data->speed = ktach; ++ data->config = (data->config & ~MAX6650_CFG_MODE_MASK) | ++ MAX6650_CFG_MODE_CLOSED_LOOP; ++ max6650_write (client, MAX6650_REG_CONFIG, data->config); ++ max6650_write (client, MAX6650_REG_SPEED, data->speed); ++ } ++} ++ ++/* ++ * Debug - dump all registers except the tach counts. ++ */ ++ ++static void max6650_xdump (struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results) ++{ ++ if (operation == SENSORS_PROC_REAL_INFO) { ++ *nrels_mag = 0; ++ } else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = max6650_read (client, MAX6650_REG_SPEED); ++ results[1] = max6650_read (client, MAX6650_REG_CONFIG); ++ results[2] = max6650_read (client, MAX6650_REG_GPIO_DEF); ++ results[3] = max6650_read (client, MAX6650_REG_DAC); ++ results[4] = max6650_read (client, MAX6650_REG_ALARM_EN); ++ results[5] = max6650_read (client, MAX6650_REG_ALARM); ++ results[6] = max6650_read (client, MAX6650_REG_GPIO_STAT); ++ results[7] = max6650_read (client, MAX6650_REG_COUNT); ++ *nrels_mag = 8; ++ } ++} ++ ++static int __init sm_max6650_init(void) ++{ ++ printk(KERN_INFO "max6650.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&max6650_driver); ++} ++ ++static void __exit sm_max6650_exit(void) ++{ ++ i2c_del_driver(&max6650_driver); ++} ++ ++MODULE_AUTHOR("john.morris@spirentcom.com"); ++MODULE_DESCRIPTION("max6650 sensor driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_max6650_init); ++module_exit(sm_max6650_exit); +--- linux-old/drivers/sensors/maxilife.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/maxilife.c Mon Dec 13 20:18:50 2004 +@@ -0,0 +1,1387 @@ ++/* ++ maxilife.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1999-2000 Fons Rademakers <Fons.Rademakers@cern.ch> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* The is the driver for the HP MaxiLife Health monitoring system ++ as used in the line of HP Kayak Workstation PC's. ++ ++ The driver supports the following MaxiLife firmware versions: ++ ++ 0) HP KAYAK XU/XAs (Dual Pentium II Slot 1, Deschutes/Klamath) ++ 1) HP KAYAK XU (Dual Xeon [Slot 2] 400/450 Mhz) ++ 2) HP KAYAK XA (Pentium II Slot 1, monoprocessor) ++ ++ Currently firmware auto detection is not implemented. To use the ++ driver load it with the correct option for you Kayak. For example: ++ ++ insmod maxilife.o maxi_version=0 | 1 | 2 ++ ++ maxi_version=0 is the default ++ ++ This version of MaxiLife is called MaxiLife'98 and has been ++ succeeded by MaxiLife'99, see below. ++ ++ The new version of the driver also supports MaxiLife NBA (New BIOS ++ Architecture). This new MaxiLife controller provides a much cleaner ++ machine independent abstraction layer to the MaxiLife controller. ++ Instead of accessing directly registers (different for each revision) ++ one now accesses the sensors via unique mailbox tokens that do not ++ change between revisions. Also the quantities are already in physical ++ units (degrees, rpms, voltages, etc.) and don't need special conversion ++ formulas. This new MaxiLife is available on the new 2000 machines, ++ like the Kayak XU800 and XM600. This hardware is also autodetected. ++*/ ++ ++static const char *version_str = "2.00 29/2/2000 Fons Rademakers"; ++ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++#undef AUTODETECT /* try to autodetect MaxiLife version */ ++/*#define AUTODETECT*/ ++#define NOWRITE /* don't allow writing to MaxiLife registers */ ++ ++#ifdef AUTODETECT ++#include <linux/vmalloc.h> ++#include <linux/ctype.h> ++#endif ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x10, 0x14, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(maxilife); ++ ++/* Macro definitions */ ++#define LOW(MyWord) ((u8) (MyWord)) ++#define HIGH(MyWord) ((u8) (((u16)(MyWord) >> 8) & 0xFF)) ++ ++/*----------------- MaxiLife'98 registers and conversion formulas ------------*/ ++#define MAXI_REG_TEMP(nr) (0x60 + (nr)) ++ ++#define MAXI_REG_FAN(nr) (0x65 + (nr)) ++#define MAXI_REG_FAN_MIN(nr) ((nr)==0 ? 0xb3 : (nr)==1 ? 0xb3 : 0xab) ++#define MAXI_REG_FAN_MINAS(nr) ((nr)==0 ? 0xb3 : (nr)==1 ? 0xab : 0xb3) ++#define MAXI_REG_FAN_SPEED(nr) ((nr)==0 ? 0xe4 : (nr)==1 ? 0xe5 : 0xe9) ++ ++#define MAXI_REG_PLL 0xb9 ++#define MAXI_REG_PLL_MIN 0xba ++#define MAXI_REG_PLL_MAX 0xbb ++ ++#define MAXI_REG_VID(nr) ((nr)==0 ? 0xd1 : (nr)==1 ? 0xd9 : \ ++ (nr)==2 ? 0xd4 : 0xc5) ++#define MAXI_REG_VID_MIN(nr) MAXI_REG_VID(nr)+1 ++#define MAXI_REG_VID_MAX(nr) MAXI_REG_VID(nr)+2 ++ ++#define MAXI_REG_DIAG_RT1 0x2c ++#define MAXI_REG_DIAG_RT2 0x2d ++ ++#define MAXI_REG_BIOS_CTRL 0x2a ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++ /* 0xfe: fan off, 0xff: stopped (alarm) */ ++ /* 19531 / val * 60 == 1171860 / val */ ++#define FAN_FROM_REG(val) ((val)==0xfe ? 0 : (val)==0xff ? -1 : \ ++ (val)==0x00 ? -1 : (1171860 / (val))) ++ ++static inline u8 FAN_TO_REG(long rpm) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1171860 + rpm / 2) / (rpm), 1, 254); ++} ++ ++#define TEMP_FROM_REG(val) ((val) * 5) ++#define TEMP_TO_REG(val) (SENSORS_LIMIT((val+2) / 5),0,0xff) ++#define PLL_FROM_REG(val) (((val) * 1000) / 32) ++#define PLL_TO_REG(val) (SENSORS_LIMIT((((val) * 32 + 500) / 1000),\ ++ 0,0xff)) ++#define VID_FROM_REG(val) ((val) ? (((val) * 27390) / 256) + 3208 : 0) ++#define VID_TO_REG(val) (SENSORS_LIMIT((((val) - 3208) * 256) / 27390, \ ++ 0,255)) ++#define ALARMS_FROM_REG(val) (val) ++ ++/*----------------- MaxiLife'99 mailbox and token definitions ----------------*/ ++/* MaxiLife mailbox data register map */ ++#define MAXI_REG_MBX_STATUS 0x5a ++#define MAXI_REG_MBX_CMD 0x5b ++#define MAXI_REG_MBX_TOKEN_H 0x5c ++#define MAXI_REG_MBX_TOKEN_L 0x5d ++#define MAXI_REG_MBX_DATA 0x60 ++ ++/* Mailbox status register definition */ ++#define MAXI_STAT_IDLE 0xff ++#define MAXI_STAT_OK 0x00 ++#define MAXI_STAT_BUSY 0x0b ++/* other values not used */ ++ ++/* Mailbox command register opcodes */ ++#define MAXI_CMD_READ 0x02 ++#define MAXI_CMD_WRITE 0x03 ++/* other values not used */ ++ ++/* MaxiLife NBA Hardware monitoring tokens */ ++ ++/* Alarm tokens (0x1xxx) */ ++#define MAXI_TOK_ALARM(nr) (0x1000 + (nr)) ++#define MAXI_TOK_ALARM_EVENT 0x1000 ++#define MAXI_TOK_ALARM_FAN 0x1001 ++#define MAXI_TOK_ALARM_TEMP 0x1002 ++#define MAXI_TOK_ALARM_VID 0x1003 /* voltages */ ++#define MAXI_TOK_ALARM_AVID 0x1004 /* additional voltages */ ++#define MAXI_TOK_ALARM_PWR 0x1101 /* power supply glitch */ ++ ++/* Fan status tokens (0x20xx) */ ++#define MAXI_TOK_FAN(nr) (0x2000 + (nr)) ++#define MAXI_TOK_FAN_CPU 0x2000 ++#define MAXI_TOK_FAN_PCI 0x2001 ++#define MAXI_TOK_FAN_HDD 0x2002 /* hard disk bay fan */ ++#define MAXI_TOK_FAN_SINK 0x2003 /* heatsink */ ++ ++/* Temperature status tokens (0x21xx) */ ++#define MAXI_TOK_TEMP(nr) (0x2100 + (nr)) ++#define MAXI_TOK_TEMP_CPU1 0x2100 ++#define MAXI_TOK_TEMP_CPU2 0x2101 ++#define MAXI_TOK_TEMP_PCI 0x2102 /* PCI/ambient temp */ ++#define MAXI_TOK_TEMP_HDD 0x2103 /* hard disk bay temp */ ++#define MAXI_TOK_TEMP_MEM 0x2104 /* mother board temp */ ++#define MAXI_TOK_TEMP_CPU 0x2105 /* CPU reference temp */ ++ ++/* Voltage status tokens (0x22xx) */ ++#define MAXI_TOK_VID(nr) (0x2200 + (nr)) ++#define MAXI_TOK_VID_12 0x2200 /* +12 volt */ ++#define MAXI_TOK_VID_CPU1 0x2201 /* cpu 1 voltage */ ++#define MAXI_TOK_VID_CPU2 0x2202 /* cpu 2 voltage */ ++#define MAXI_TOK_VID_L2 0x2203 /* level 2 cache voltage */ ++#define MAXI_TOK_VID_M12 0x2204 /* -12 volt */ ++ ++/* Additive voltage status tokens (0x23xx) */ ++#define MAXI_TOK_AVID(nr) (0x2300 + (nr)) ++#define MAXI_TOK_AVID_15 0x2300 /* 1.5 volt */ ++#define MAXI_TOK_AVID_18 0x2301 /* 1.8 volt */ ++#define MAXI_TOK_AVID_25 0x2302 /* 2.5 volt */ ++#define MAXI_TOK_AVID_33 0x2303 /* 3.3 volt */ ++#define MAXI_TOK_AVID_5 0x2304 /* 5 volt */ ++#define MAXI_TOK_AVID_M5 0x2305 /* -5 volt */ ++#define MAXI_TOK_AVID_BAT 0x2306 /* battery voltage */ ++ ++/* Threshold tokens (0x3xxx) */ ++#define MAXI_TOK_MIN(token) ((token) + 0x1000) ++#define MAXI_TOK_MAX(token) ((token) + 0x1800) ++ ++/* LCD Panel (0x4xxx) */ ++#define MAXI_TOK_LCD(nr) (0x4000 + (nr)) ++#define MAXI_TOK_LCD_LINE1 0x4000 ++#define MAXI_TOK_LCD_LINE2 0x4001 ++#define MAXI_TOK_LCD_LINE3 0x4002 ++#define MAXI_TOK_LCD_LINE4 0x4003 ++ ++ /* 0xfe: fan off, 0xff: stopped (alarm) */ ++ /* or not available */ ++#define FAN99_FROM_REG(val) ((val)==0xfe ? 0 : (val)==0xff ? -1 : ((val)*39)) ++ ++ /* when no CPU2 temp is 127 (0x7f) */ ++#define TEMP99_FROM_REG(val) ((val)==0x7f ? -1 : (val)==0xff ? -1 : (val)) ++ ++#define VID99_FROM_REG(nr,val) ((val)==0xff ? 0 : \ ++ (nr)==1 ? ((val) * 608) : \ ++ (nr)==2 ? ((val) * 160) : \ ++ (nr)==3 ? ((val) * 160) : \ ++ (nr)==4 ? (val) /* no formula spcified */ : \ ++ (nr)==5 ? ((val) * 823 - 149140) : 0) ++ ++ ++/* The following product codenames apply: ++ Cristal/Geronimo: HP KAYAK XU/XAs ++ (Dual Pentium II Slot 1, Deschutes/Klamath) ++ Cognac: HP KAYAK XU (Dual Xeon [Slot 2] 400/450 Mhz) ++ Ashaki: HP KAYAK XA (Pentium II Slot 1, monoprocessor) ++ NBA: New BIOS Architecture, Kayak XU800, XM600, ... */ ++ ++enum maxi_type { cristal, cognac, ashaki, nba }; ++enum sensor_type { fan, temp, vid, pll, lcd, alarm }; ++ ++/* For each registered MaxiLife controller, we need to keep some data in ++ memory. That data is pointed to by maxi_list[NR]->data. The structure ++ itself is dynamically allocated, at the same time when a new MaxiLife ++ client is allocated. We assume MaxiLife will only be present on the ++ SMBus and not on the ISA bus. */ ++struct maxi_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum maxi_type type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 fan[4]; /* Register value */ ++ u8 fan_min[4]; /* Register value */ ++ u8 fan_speed[4]; /* Register value */ ++ u8 fan_div[4]; /* Static value */ ++ u8 temp[6]; /* Register value */ ++ u8 temp_max[6]; /* Static value */ ++ u8 temp_hyst[6]; /* Static value */ ++ u8 pll; /* Register value */ ++ u8 pll_min; /* Register value */ ++ u8 pll_max; /* register value */ ++ u8 vid[5]; /* Register value */ ++ u8 vid_min[5]; /* Register value */ ++ u8 vid_max[5]; /* Register value */ ++ u8 lcd[4][17]; /* Four LCD lines */ ++ u16 alarms; /* Register encoding, combined */ ++}; ++ ++ ++static int maxi_attach_adapter(struct i2c_adapter *adapter); ++static int maxi_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int maxi_detach_client(struct i2c_client *client); ++ ++static int maxi_read_value(struct i2c_client *client, u8 register); ++static int maxi_read_token(struct i2c_client *client, u16 token); ++#ifndef NOWRITE ++static int maxi_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++#endif ++static int maxi_write_token_loop(struct i2c_client *client, u16 token, ++ u8 len, u8 * values); ++ ++static void maxi_update_client(struct i2c_client *client); ++static void maxi99_update_client(struct i2c_client *client, ++ enum sensor_type sensor, int which); ++static void maxi_init_client(struct i2c_client *client); ++ ++static void maxi_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi99_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi99_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi_pll(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi99_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi_lcd(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void maxi_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++/* The driver. I choose to use type i2c_driver, as at is identical to ++ the smbus_driver. */ ++static struct i2c_driver maxi_driver = { ++ .owner = THIS_MODULE, ++ .name = "HP MaxiLife driver", ++ .id = I2C_DRIVERID_MAXILIFE, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = maxi_attach_adapter, ++ .detach_client = maxi_detach_client, ++}; ++ ++static int maxi_id = 0; ++ ++/* Default firmware version. Use module option "maxi_version" ++ to set desired version. Auto detect is not yet working */ ++static int maxi_version = cristal; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define MAXI_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define MAXI_SYSCTL_FAN2 1102 /* Rotations/min */ ++#define MAXI_SYSCTL_FAN3 1103 /* Rotations/min */ ++#define MAXI_SYSCTL_FAN4 1104 /* Rotations/min */ ++#define MAXI_SYSCTL_TEMP1 1201 /* Degrees Celcius */ ++#define MAXI_SYSCTL_TEMP2 1202 /* Degrees Celcius */ ++#define MAXI_SYSCTL_TEMP3 1203 /* Degrees Celcius */ ++#define MAXI_SYSCTL_TEMP4 1204 /* Degrees Celcius */ ++#define MAXI_SYSCTL_TEMP5 1205 /* Degrees Celcius */ ++#define MAXI_SYSCTL_TEMP6 1206 /* Degrees Celcius */ ++#define MAXI_SYSCTL_PLL 1301 /* MHz */ ++#define MAXI_SYSCTL_VID1 1401 /* Volts / 6.337, for nba just Volts */ ++#define MAXI_SYSCTL_VID2 1402 /* Volts */ ++#define MAXI_SYSCTL_VID3 1403 /* Volts */ ++#define MAXI_SYSCTL_VID4 1404 /* Volts */ ++#define MAXI_SYSCTL_VID5 1405 /* Volts */ ++#define MAXI_SYSCTL_LCD1 1501 /* Line 1 of LCD */ ++#define MAXI_SYSCTL_LCD2 1502 /* Line 2 of LCD */ ++#define MAXI_SYSCTL_LCD3 1503 /* Line 3 of LCD */ ++#define MAXI_SYSCTL_LCD4 1504 /* Line 4 of LCD */ ++#define MAXI_SYSCTL_ALARMS 2001 /* Bitvector (see below) */ ++ ++#define MAXI_ALARM_VID4 0x0001 ++#define MAXI_ALARM_TEMP2 0x0002 ++#define MAXI_ALARM_VID1 0x0004 ++#define MAXI_ALARM_VID2 0x0008 ++#define MAXI_ALARM_VID3 0x0010 ++#define MAXI_ALARM_PLL 0x0080 ++#define MAXI_ALARM_TEMP4 0x0100 ++#define MAXI_ALARM_TEMP5 0x0200 ++#define MAXI_ALARM_FAN1 0x1000 ++#define MAXI_ALARM_FAN2 0x2000 ++#define MAXI_ALARM_FAN3 0x4000 ++ ++#define MAXI_ALARM_FAN 0x0100 /* To be used with MaxiLife'99 */ ++#define MAXI_ALARM_VID 0x0200 /* The MSB specifies which sensor */ ++#define MAXI_ALARM_TEMP 0x0400 /* in the alarm group failed, i.e.: */ ++#define MAXI_ALARM_VADD 0x0800 /* 0x0402 = TEMP2 failed = CPU2 temp */ ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected MaxiLife processor. ++ This is just a template; though at first sight, you might think we ++ could use a statically allocated list, we need some way to get back ++ to the parent - which is done through one of the 'extra' fields ++ which are initialized when a new copy is allocated. */ ++static ctl_table maxi_dir_table_template[] = { ++ {MAXI_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_fan}, ++ {MAXI_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_fan}, ++ {MAXI_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_fan}, ++ {MAXI_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_fan}, ++ {MAXI_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_temp}, ++ {MAXI_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_temp}, ++ {MAXI_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_temp}, ++ {MAXI_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_temp}, ++ {MAXI_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_temp}, ++ {MAXI_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_temp}, ++ {MAXI_SYSCTL_PLL, "pll", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_pll}, ++ {MAXI_SYSCTL_VID1, "vid1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_vid}, ++ {MAXI_SYSCTL_VID2, "vid2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_vid}, ++ {MAXI_SYSCTL_VID3, "vid3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_vid}, ++ {MAXI_SYSCTL_VID4, "vid4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_vid}, ++ {MAXI_SYSCTL_VID5, "vid5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_vid}, ++ {MAXI_SYSCTL_LCD1, "lcd1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_lcd}, ++ {MAXI_SYSCTL_LCD2, "lcd2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_lcd}, ++ {MAXI_SYSCTL_LCD3, "lcd3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_lcd}, ++ {MAXI_SYSCTL_LCD4, "lcd4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_lcd}, ++ {MAXI_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &maxi_alarms}, ++ {0} ++}; ++ ++/* This function is called when: ++ - maxi_driver is inserted (when this module is loaded), for each ++ available adapter ++ - when a new adapter is inserted (and maxi_driver is still present) */ ++static int maxi_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, maxi_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int maxi_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ struct i2c_client *new_client; ++ struct maxi_data *data; ++ enum maxi_type type = 0; ++ int i, j, err = 0; ++ const char *type_name = NULL, *client_name = NULL; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access maxi_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct maxi_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ /* Fill the new client structure with data */ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &maxi_driver; ++ new_client->flags = 0; ++ ++ /* Now we do the remaining detection. */ ++ if (kind < 0) { ++ if (i2c_smbus_read_byte_data ++ (new_client, MAXI_REG_MBX_STATUS) < 0) ++ goto ERROR2; ++ } ++ ++ /* Determine the chip type - only one kind supported */ ++ if (kind <= 0) ++ kind = maxilife; ++ ++ if (kind == maxilife) { ++ /* Detect if the machine has a MaxiLife NBA controller. ++ The right way to perform this check is to do a read/modify/write ++ on register MbxStatus (5A): ++ - Read 5A (value 0 for non-NBA firmware, FF (MbxIdle on NBA-firmware) ++ - Write 55 on 5A, then read back 5A ++ Non-NBA firmware: value is 55 (reg 5A is a standard writable reg) ++ NBA firmaware: value is FF (write-protect on MbxStatus active) */ ++ int stat; ++ i2c_smbus_write_byte_data(new_client, MAXI_REG_MBX_STATUS, ++ 0x55); ++ stat = ++ i2c_smbus_read_byte_data(new_client, ++ MAXI_REG_MBX_STATUS); ++ ++ /*if (stat == MAXI_STAT_IDLE || stat == MAXI_STAT_OK) */ ++ if (stat != 0x55) ++ maxi_version = nba; ++#ifdef AUTODETECT ++ else { ++ /* The right way to get the platform info is to read the firmware ++ revision from serial EEPROM (addr=0x54), at offset 0x0045. ++ This is a string as: ++ "CG 00.04" -> Cristal [XU] / Geronimo [XAs] ++ "CO 00.03" -> Cognac [XU] ++ "AS 00.01" -> Ashaki [XA] */ ++#if 0 ++ int biosctl; ++ biosctl = ++ i2c_smbus_read_byte_data(new_client, ++ MAXI_REG_BIOS_CTRL); ++ i2c_smbus_write_byte_data(new_client, ++ MAXI_REG_BIOS_CTRL, ++ biosctl | 4); ++ err = eeprom_read_byte_data(adapter, 0x54, 0x45); ++ i2c_smbus_write_byte_data(new_client, ++ MAXI_REG_BIOS_CTRL, ++ biosctl); ++#endif ++ int i; ++ char *biosmem, *bm; ++ bm = biosmem = ioremap(0xe0000, 0x20000); ++ if (biosmem) { ++ printk("begin of bios search\n"); ++ for (i = 0; i < 0x20000; i++) { ++ if (*bm == 'C') { ++ char *s = bm; ++ while (s && isprint(*s)) { ++ printk("%c", *s); ++ s++; ++ } ++ printk("\n"); ++ if (!strncmp ++ (bm, "CG 00.04", 8)) { ++ maxi_version = ++ cristal; ++ printk ++ ("maxilife: found MaxiLife Rev CG 00.04\n"); ++ break; ++ } ++ if (!strncmp ++ (bm, "CO 00.03", 8)) { ++ maxi_version = ++ cognac; ++ printk ++ ("maxilife: found MaxiLife Rev CO 00.03\n"); ++ break; ++ } ++ } ++ if (*bm == 'A' && *(bm + 1) == 'S') { ++ char *s = bm; ++ while (s && isprint(*s)) { ++ printk("%c", *s); ++ s++; ++ } ++ printk("\n"); ++ if (!strncmp ++ (bm, "AS 00.01", 8)) { ++ maxi_version = ++ ashaki; ++ printk ++ ("maxilife: found MaxiLife Rev AS 00.01\n"); ++ break; ++ } ++ } ++ bm++; ++ } ++ printk("end of bios search\n"); ++ } else ++ printk("could not map bios memory\n"); ++ } ++#endif ++ ++ if (maxi_version == cristal) { ++ type = cristal; ++ type_name = "maxilife-cg"; ++ client_name = "HP MaxiLife Rev CG 00.04"; ++ printk ++ ("maxilife: HP KAYAK XU/XAs (Dual Pentium II Slot 1)\n"); ++ } else if (maxi_version == cognac) { ++ type = cognac; ++ type_name = "maxilife-co"; ++ client_name = "HP MaxiLife Rev CO 00.03"; ++ printk ++ ("maxilife: HP KAYAK XU (Dual Xeon Slot 2 400/450 Mhz)\n"); ++ } else if (maxi_version == ashaki) { ++ type = ashaki; ++ type_name = "maxilife-as"; ++ client_name = "HP MaxiLife Rev AS 00.01"; ++ printk ++ ("maxilife: HP KAYAK XA (Pentium II Slot 1, monoprocessor)\n"); ++ } else if (maxi_version == nba) { ++ type = nba; ++ type_name = "maxilife-nba"; ++ client_name = "HP MaxiLife NBA"; ++ printk("maxilife: HP KAYAK XU800/XM600\n"); ++ } else { ++#ifdef AUTODETECT ++ printk ++ ("maxilife: Warning: probed non-maxilife chip?!? (%x)\n", ++ err); ++#else ++ printk ++ ("maxilife: Error: specified wrong maxi_version (%d)\n", ++ maxi_version); ++#endif ++ goto ERROR2; ++ } ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ((struct maxi_data *) (new_client->data))->type = type; ++ ++ for (i = 0; i < 4; i++) ++ for (j = 0; j < 17; j++) ++ ((struct maxi_data *) (new_client->data))-> ++ lcd[i][j] = (u8) 0; ++ ++ new_client->id = maxi_id++; ++ ++ data->valid = 0; ++ init_MUTEX(&data->lock); ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell i2c-core that a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR2; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((err = i2c_register_entry(new_client, type_name, ++ maxi_dir_table_template)) < 0) ++ goto ERROR4; ++ data->sysctl_id = err; ++ ++ /* Initialize the MaxiLife chip */ ++ maxi_init_client(new_client); ++ return 0; ++ ++ /* OK, this is not exactly good programming practice, usually. ++ But it is very code-efficient in this case. */ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR2: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++/* This function is called whenever a client should be removed: ++ - maxi_driver is removed (when this module is unloaded) ++ - when an adapter is removed which has a maxi client (and maxi_driver ++ is still present). */ ++static int maxi_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct maxi_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("maxilife: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ kfree(client->data); ++ return 0; ++} ++ ++/* Read byte from specified register (-1 in case of error, value otherwise). */ ++static int maxi_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* Read the byte value for a MaxiLife token (-1 in case of error, value otherwise */ ++static int maxi_read_token(struct i2c_client *client, u16 token) ++{ ++ u8 lowToken, highToken; ++ int error, value; ++ ++ lowToken = LOW(token); ++ highToken = HIGH(token); ++ ++ /* Set mailbox status register to idle state. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, ++ MAXI_STAT_IDLE); ++ if (error < 0) ++ return error; ++ ++ /* Check for mailbox idle state. */ ++ error = i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); ++ if (error != MAXI_STAT_IDLE) ++ return -1; ++ ++ /* Write the most significant byte of the token we want to read. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_H, ++ highToken); ++ if (error < 0) ++ return error; ++ ++ /* Write the least significant byte of the token we want to read. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_L, ++ lowToken); ++ if (error < 0) ++ return error; ++ ++ /* Write the read token opcode to the mailbox. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_CMD, ++ MAXI_CMD_READ); ++ if (error < 0) ++ return error; ++ ++ /* Check for transaction completion */ ++ do { ++ error = ++ i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); ++ } while (error == MAXI_STAT_BUSY); ++ if (error != MAXI_STAT_OK) ++ return -1; ++ ++ /* Read the value of the token. */ ++ value = i2c_smbus_read_byte_data(client, MAXI_REG_MBX_DATA); ++ if (value == -1) ++ return -1; ++ ++ /* set mailbox status to idle to complete transaction. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, ++ MAXI_STAT_IDLE); ++ if (error < 0) ++ return error; ++ ++ return value; ++} ++ ++#ifndef NOWRITE ++/* Write byte to specified register (-1 in case of error, 0 otherwise). */ ++static int maxi_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++#endif ++ ++/* Write a set of len byte values to MaxiLife token (-1 in case of error, 0 otherwise). */ ++int maxi_write_token_loop(struct i2c_client *client, u16 token, u8 len, ++ u8 * values) ++{ ++ u8 lowToken, highToken, bCounter; ++ int error; ++ ++ lowToken = LOW(token); ++ highToken = HIGH(token); ++ ++ /* Set mailbox status register to idle state. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, ++ MAXI_STAT_IDLE); ++ if (error < 0) ++ return error; ++ ++ /* Check for mailbox idle state. */ ++ error = i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); ++ if (error != MAXI_STAT_IDLE) ++ return -1; ++ ++ for (bCounter = 0; (bCounter < len && bCounter < 32); bCounter++) { ++ error = ++ i2c_smbus_write_byte_data(client, ++ (u8) (MAXI_REG_MBX_DATA + ++ bCounter), ++ values[bCounter]); ++ if (error < 0) ++ return error; ++ } ++ ++ /* Write the most significant byte of the token we want to read. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_H, ++ highToken); ++ if (error < 0) ++ return error; ++ ++ /* Write the least significant byte of the token we want to read. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_L, ++ lowToken); ++ if (error < 0) ++ return error; ++ ++ /* Write the write token opcode to the mailbox. */ ++ error = ++ i2c_smbus_write_byte_data(client, MAXI_REG_MBX_CMD, ++ MAXI_CMD_WRITE); ++ if (error < 0) ++ return error; ++ ++ /* Check for transaction completion */ ++ do { ++ error = ++ i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); ++ } while (error == MAXI_STAT_BUSY); ++ if (error != MAXI_STAT_OK) ++ return -1; ++ ++ /* set mailbox status to idle to complete transaction. */ ++ return i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, ++ MAXI_STAT_IDLE); ++} ++ ++/* Called when we have found a new MaxiLife. It should set limits, etc. */ ++static void maxi_init_client(struct i2c_client *client) ++{ ++ struct maxi_data *data = client->data; ++ ++ if (data->type == nba) { ++ strcpy(data->lcd[2], " Linux MaxiLife"); ++ maxi_write_token_loop(client, MAXI_TOK_LCD(2), ++ strlen(data->lcd[2]) + 1, ++ data->lcd[2]); ++ } ++} ++ ++static void maxi_update_client(struct i2c_client *client) ++{ ++ struct maxi_data *data = client->data; ++ int i; ++ ++ if (data->type == nba) { ++ printk ++ ("maxi_update_client should never be called by nba\n"); ++ return; ++ } ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("maxilife: Starting MaxiLife update\n"); ++#endif ++ for (i = 0; i < 5; i++) ++ data->temp[i] = ++ maxi_read_value(client, MAXI_REG_TEMP(i)); ++ switch (data->type) { ++ case cristal: ++ data->temp[0] = 0; /* not valid */ ++ data->temp_max[0] = 0; ++ data->temp_hyst[0] = 0; ++ data->temp_max[1] = 110; /* max PCI slot temp */ ++ data->temp_hyst[1] = 100; ++ data->temp_max[2] = 120; /* max BX chipset temp */ ++ data->temp_hyst[2] = 110; ++ data->temp_max[3] = 100; /* max HDD temp */ ++ data->temp_hyst[3] = 90; ++ data->temp_max[4] = 120; /* max CPU temp */ ++ data->temp_hyst[4] = 110; ++ break; ++ ++ case cognac: ++ data->temp_max[0] = 120; /* max CPU1 temp */ ++ data->temp_hyst[0] = 110; ++ data->temp_max[1] = 110; /* max PCI slot temp */ ++ data->temp_hyst[1] = 100; ++ data->temp_max[2] = 120; /* max CPU2 temp */ ++ data->temp_hyst[2] = 110; ++ data->temp_max[3] = 100; /* max HDD temp */ ++ data->temp_hyst[3] = 90; ++ data->temp_max[4] = 120; /* max reference CPU temp */ ++ data->temp_hyst[4] = 110; ++ break; ++ ++ case ashaki: ++ data->temp[0] = 0; /* not valid */ ++ data->temp_max[0] = 0; ++ data->temp_hyst[0] = 0; ++ data->temp_max[1] = 110; /* max PCI slot temp */ ++ data->temp_hyst[1] = 100; ++ data->temp[2] = 0; /* not valid */ ++ data->temp_max[2] = 0; ++ data->temp_hyst[2] = 0; ++ data->temp_max[3] = 100; /* max HDD temp */ ++ data->temp_hyst[3] = 90; ++ data->temp_max[4] = 120; /* max CPU temp */ ++ data->temp_hyst[4] = 110; ++ break; ++ ++ default: ++ printk("maxilife: Unknown MaxiLife chip\n"); ++ } ++ data->temp[5] = 0; /* only used by MaxiLife'99 */ ++ data->temp_max[5] = 0; ++ data->temp_hyst[5] = 0; ++ ++ for (i = 0; i < 3; i++) { ++ data->fan[i] = ++ maxi_read_value(client, MAXI_REG_FAN(i)); ++ data->fan_speed[i] = ++ maxi_read_value(client, MAXI_REG_FAN_SPEED(i)); ++ data->fan_div[i] = 4; ++ if (data->type == ashaki) ++ data->fan_min[i] = ++ maxi_read_value(client, ++ MAXI_REG_FAN_MINAS(i)); ++ else ++ data->fan_min[i] = ++ maxi_read_value(client, ++ MAXI_REG_FAN_MIN(i)); ++ } ++ data->fan[3] = 0xff; /* only used by MaxiLife'99 */ ++ data->fan_speed[3] = 0; ++ data->fan_div[3] = 4; /* avoid possible /0 */ ++ data->fan_min[3] = 0; ++ ++ data->pll = maxi_read_value(client, MAXI_REG_PLL); ++ data->pll_min = maxi_read_value(client, MAXI_REG_PLL_MIN); ++ data->pll_max = maxi_read_value(client, MAXI_REG_PLL_MAX); ++ ++ for (i = 0; i < 4; i++) { ++ data->vid[i] = ++ maxi_read_value(client, MAXI_REG_VID(i)); ++ data->vid_min[i] = ++ maxi_read_value(client, MAXI_REG_VID_MIN(i)); ++ data->vid_max[i] = ++ maxi_read_value(client, MAXI_REG_VID_MAX(i)); ++ } ++ switch (data->type) { ++ case cristal: ++ data->vid[3] = 0; /* no voltage cache L2 */ ++ data->vid_min[3] = 0; ++ data->vid_max[3] = 0; ++ break; ++ ++ case cognac: ++ break; ++ ++ case ashaki: ++ data->vid[1] = 0; /* no voltage CPU 2 */ ++ data->vid_min[1] = 0; ++ data->vid_max[1] = 0; ++ data->vid[3] = 0; /* no voltage cache L2 */ ++ data->vid_min[3] = 0; ++ data->vid_max[3] = 0; ++ break; ++ ++ default: ++ printk("maxilife: Unknown MaxiLife chip\n"); ++ } ++ data->vid[4] = 0; /* only used by MaxliLife'99 */ ++ data->vid_min[4] = 0; ++ data->vid_max[4] = 0; ++ ++ data->alarms = maxi_read_value(client, MAXI_REG_DIAG_RT1) + ++ (maxi_read_value(client, MAXI_REG_DIAG_RT2) << 8); ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++void maxi99_update_client(struct i2c_client *client, ++ enum sensor_type sensor, int which) ++{ ++ static unsigned long last_updated[6][6]; /* sensor, which */ ++ struct maxi_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ /*maxi_write_token_loop(client, MAXI_TOK_LCD_LINE3, 13, "Linux 2.2.13"); */ ++ ++ if ((jiffies - last_updated[sensor][which] > 2 * HZ) || ++ (jiffies < last_updated[sensor][which] ++ || !last_updated[sensor][which])) { ++ ++ int tmp, i; ++ ++ switch (sensor) { ++ case fan: ++ for (i = 0; i < 4; i++) { ++ if (i == which) { ++ tmp = ++ maxi_read_token(client, ++ MAXI_TOK_FAN ++ (i)); ++ data->fan[i] = ++ maxi_read_token(client, ++ MAXI_TOK_FAN ++ (i)); ++ data->fan_speed[i] = ++ maxi_read_token(client, ++ MAXI_TOK_MAX ++ (MAXI_TOK_FAN ++ (i))); ++ data->fan_div[i] = 1; ++ data->fan_min[i] = 0; ++ } ++ } ++ break; ++ ++ case temp: ++ for (i = 0; i < 6; i++) { ++ if (i == which) { ++ data->temp[i] = ++ maxi_read_token(client, ++ MAXI_TOK_TEMP ++ (i)); ++ data->temp_max[i] = ++ maxi_read_token(client, ++ MAXI_TOK_MAX ++ (MAXI_TOK_TEMP ++ (i))); ++ data->temp_hyst[i] = ++ data->temp_max[i] - 5; ++ } ++ } ++ break; ++ ++ case vid: ++ for (i = 0; i < 5; i++) { ++ if (i == which) { ++ data->vid[i] = ++ maxi_read_token(client, ++ MAXI_TOK_VID ++ (i)); ++ data->vid_min[i] = ++ maxi_read_token(client, ++ MAXI_TOK_MIN ++ (MAXI_TOK_VID ++ (i))); ++ data->vid_max[i] = ++ maxi_read_token(client, ++ MAXI_TOK_MAX ++ (MAXI_TOK_VID ++ (i))); ++ } ++ } ++ break; ++ ++ case pll: ++ data->pll = 0; ++ data->pll_min = 0; ++ data->pll_max = 0; ++ break; ++ ++ case alarm: ++ data->alarms = ++ (maxi_read_token(client, MAXI_TOK_ALARM_EVENT) ++ << 8); ++ if (data->alarms) ++ data->alarms += ++ data->alarms == ++ (1 << 8) ? maxi_read_token(client, ++ MAXI_TOK_ALARM_FAN) ++ : data->alarms == ++ (2 << 8) ? maxi_read_token(client, ++ MAXI_TOK_ALARM_VID) ++ : data->alarms == ++ (4 << 8) ? maxi_read_token(client, ++ MAXI_TOK_ALARM_TEMP) ++ : data->alarms == ++ (8 << 8) ? maxi_read_token(client, ++ MAXI_TOK_ALARM_FAN) ++ : 0; ++ break; ++ ++ default: ++ printk("maxilife: Unknown sensor type\n"); ++ } ++ ++ last_updated[sensor][which] = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the data ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void maxi_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ int nr; ++ ++ if (data->type == nba) { ++ maxi99_fan(client, operation, ctl_name, nrels_mag, ++ results); ++ return; ++ } ++ ++ nr = ctl_name - MAXI_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ maxi_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1]); ++ results[1] = data->fan_div[nr - 1]; ++ results[2] = FAN_FROM_REG(data->fan[nr - 1]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++#ifndef NOWRITE ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0]); ++ maxi_write_value(client, MAXI_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++#endif ++ } ++} ++ ++void maxi99_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ int nr; ++ ++ nr = ctl_name - MAXI_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ maxi99_update_client(client, fan, nr - 1); ++ results[0] = FAN99_FROM_REG(data->fan_min[nr - 1]); /* min rpm */ ++ results[1] = data->fan_div[nr - 1]; /* divisor */ ++ results[2] = FAN99_FROM_REG(data->fan[nr - 1]); /* rpm */ ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++#ifndef NOWRITE ++ /* still to do */ ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0]); ++ maxi_write_value(client, MAXI_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++#endif ++ } ++} ++ ++void maxi_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ int nr; ++ ++ if (data->type == nba) { ++ maxi99_temp(client, operation, ctl_name, nrels_mag, ++ results); ++ return; ++ } ++ ++ nr = ctl_name - MAXI_SYSCTL_TEMP1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ maxi_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_max[nr - 1]); ++ results[1] = TEMP_FROM_REG(data->temp_hyst[nr - 1]); ++ results[2] = TEMP_FROM_REG(data->temp[nr - 1]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ /* temperature range can not be changed */ ++ } ++} ++ ++void maxi99_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ int nr; ++ ++ nr = ctl_name - MAXI_SYSCTL_TEMP1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ maxi99_update_client(client, temp, nr - 1); ++ results[0] = TEMP99_FROM_REG(data->temp_max[nr - 1]); ++ results[1] = TEMP99_FROM_REG(data->temp_hyst[nr - 1]); ++ results[2] = TEMP99_FROM_REG(data->temp[nr - 1]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ /* temperature range can not be changed */ ++ } ++} ++ ++void maxi_pll(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ if (data->type == nba) ++ maxi99_update_client(client, pll, 0); ++ else ++ maxi_update_client(client); ++ results[0] = PLL_FROM_REG(data->pll_min); ++ results[1] = PLL_FROM_REG(data->pll_max); ++ results[2] = PLL_FROM_REG(data->pll); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++#ifndef NOWRITE ++ if (*nrels_mag >= 1) { ++ data->pll_min = PLL_TO_REG(results[0]); ++ maxi_write_value(client, MAXI_REG_PLL_MIN, ++ data->pll_min); ++ } ++ if (*nrels_mag >= 2) { ++ data->pll_max = PLL_TO_REG(results[1]); ++ maxi_write_value(client, MAXI_REG_PLL_MAX, ++ data->pll_max); ++ } ++#endif ++ } ++} ++ ++void maxi_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ int nr; ++ ++ if (data->type == nba) { ++ maxi99_vid(client, operation, ctl_name, nrels_mag, ++ results); ++ return; ++ } ++ ++ nr = ctl_name - MAXI_SYSCTL_VID1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 4; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ maxi_update_client(client); ++ results[0] = VID_FROM_REG(data->vid_min[nr - 1]); ++ results[1] = VID_FROM_REG(data->vid_max[nr - 1]); ++ results[2] = VID_FROM_REG(data->vid[nr - 1]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++#ifndef NOWRITE ++ if (*nrels_mag >= 1) { ++ data->vid_min[nr - 1] = VID_TO_REG(results[0]); ++ maxi_write_value(client, MAXI_REG_VID_MIN(nr), ++ data->vid_min[nr - 1]); ++ } ++ if (*nrels_mag >= 2) { ++ data->vid_max[nr - 1] = VID_TO_REG(results[1]); ++ maxi_write_value(client, MAXI_REG_VID_MAX(nr), ++ data->vid_max[nr - 1]); ++ } ++#endif ++ } ++} ++ ++void maxi99_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ int nr = ctl_name - MAXI_SYSCTL_VID1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 4; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ maxi99_update_client(client, vid, nr - 1); ++ results[0] = VID99_FROM_REG(nr, data->vid_min[nr - 1]); ++ results[1] = VID99_FROM_REG(nr, data->vid_max[nr - 1]); ++ results[2] = VID99_FROM_REG(nr, data->vid[nr - 1]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++#ifndef NOWRITE ++ /* still to do */ ++ if (*nrels_mag >= 1) { ++ data->vid_min[nr - 1] = VID_TO_REG(results[0]); ++ maxi_write_value(client, MAXI_REG_VID_MIN(nr), ++ data->vid_min[nr - 1]); ++ } ++ if (*nrels_mag >= 2) { ++ data->vid_max[nr - 1] = VID_TO_REG(results[1]); ++ maxi_write_value(client, MAXI_REG_VID_MAX(nr), ++ data->vid_max[nr - 1]); ++ } ++#endif ++ } ++} ++ ++void maxi_lcd(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ /* Allows writing and reading from LCD display */ ++ ++ struct maxi_data *data = client->data; ++ int nr; ++ ++ if (data->type != nba) ++ return; ++ ++ nr = ctl_name - MAXI_SYSCTL_LCD1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = *((long *) &data->lcd[nr - 1][0]); ++ results[1] = *((long *) &data->lcd[nr - 1][4]); ++ results[2] = *((long *) &data->lcd[nr - 1][8]); ++ results[3] = *((long *) &data->lcd[nr - 1][12]); ++ *nrels_mag = 4; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ /* ++ Writing a string to line 3 of the LCD can be done like: ++ echo -n "Linux MaxiLife" | od -A n -l > \ ++ /proc/sys/dev/sensors/maxilife-nba-i2c-0-14/lcd3 ++ */ ++ if (*nrels_mag >= 1) ++ *((long *) &data->lcd[nr - 1][0]) = results[0]; ++ if (*nrels_mag >= 2) ++ *((long *) &data->lcd[nr - 1][4]) = results[1]; ++ if (*nrels_mag >= 3) ++ *((long *) &data->lcd[nr - 1][8]) = results[2]; ++ if (*nrels_mag >= 4) ++ *((long *) &data->lcd[nr - 1][12]) = results[3]; ++ maxi_write_token_loop(client, MAXI_TOK_LCD(nr - 1), ++ strlen(data->lcd[nr - 1]) + 1, ++ data->lcd[nr - 1]); ++#if 0 ++ if (*nrels_mag >= 1) ++ printk("nr=%d, result[0] = %.4s\n", nr, ++ (char *) &results[0]); ++ if (*nrels_mag >= 2) ++ printk("nr=%d, result[1] = %.4s\n", nr, ++ (char *) &results[1]); ++ if (*nrels_mag >= 3) ++ printk("nr=%d, result[2] = %.4s\n", nr, ++ (char *) &results[2]); ++ if (*nrels_mag >= 4) ++ printk("nr=%d, result[3] = %.4s\n", nr, ++ (char *) &results[3]); ++#endif ++ } ++ ++} ++ ++void maxi_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct maxi_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ if (data->type == nba) ++ maxi99_update_client(client, alarm, 0); ++ else ++ maxi_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++static int __init sm_maxilife_init(void) ++{ ++ printk("maxilife: Version %s (lm_sensors %s (%s))\n", version_str, ++ LM_VERSION, LM_DATE); ++ return i2c_add_driver(&maxi_driver); ++} ++ ++static void __exit sm_maxilife_exit(void) ++{ ++ i2c_del_driver(&maxi_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Fons Rademakers <Fons.Rademakers@cern.ch>"); ++MODULE_DESCRIPTION("HP MaxiLife driver"); ++MODULE_PARM(maxi_version, "i"); ++MODULE_PARM_DESC(maxi_version, "MaxiLife firmware version"); ++ ++module_init(sm_maxilife_init); ++module_exit(sm_maxilife_exit); +--- linux-old/drivers/sensors/mtp008.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/mtp008.c Mon Dec 13 20:18:51 2004 +@@ -0,0 +1,1103 @@ ++/* ++ mtp008.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (C) 2001, 2004 Kris Van Hees <aedil@alchar.org> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = {SENSORS_I2C_END}; ++static unsigned short normal_i2c_range[] = {0x2c, 0x2e, SENSORS_I2C_END}; ++static unsigned int normal_isa[] = {SENSORS_ISA_END}; ++static unsigned int normal_isa_range[] = {SENSORS_ISA_END}; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(mtp008); ++ ++/* The MTP008 registers */ ++/* in0 .. in6 */ ++#define MTP008_REG_IN(nr) (0x20 + (nr)) ++#define MTP008_REG_IN_MAX(nr) (0x2b + (nr) * 2) ++#define MTP008_REG_IN_MIN(nr) (0x2c + (nr) * 2) ++ ++/* temp1 */ ++#define MTP008_REG_TEMP 0x27 ++#define MTP008_REG_TEMP_MAX 0x39 ++#define MTP008_REG_TEMP_MIN 0x3a ++ ++/* fan1 .. fan3 */ ++#define MTP008_REG_FAN(nr) (0x27 + (nr)) ++#define MTP008_REG_FAN_MIN(nr) (0x3a + (nr)) ++ ++#define MTP008_REG_CONFIG 0x40 ++#define MTP008_REG_INT_STAT1 0x41 ++#define MTP008_REG_INT_STAT2 0x42 ++ ++#define MTP008_REG_SMI_MASK1 0x43 ++#define MTP008_REG_SMI_MASK2 0x44 ++ ++#define MTP008_REG_NMI_MASK1 0x45 ++#define MTP008_REG_NMI_MASK2 0x46 ++ ++#define MTP008_REG_VID_FANDIV 0x47 ++ ++#define MTP008_REG_I2C_ADDR 0x48 ++ ++#define MTP008_REG_RESET_VID4 0x49 ++ ++#define MTP008_REG_OVT_PROP 0x50 ++ ++#define MTP008_REG_BEEP_CTRL1 0x51 ++#define MTP008_REG_BEEP_CTRL2 0x52 ++ ++/* pwm1 .. pwm3 nr range 1-3 */ ++#define MTP008_REG_PWM_CTRL(nr) (0x52 + (nr)) ++ ++#define MTP008_REG_PIN_CTRL1 0x56 ++#define MTP008_REG_PIN_CTRL2 0x57 ++ ++#define MTP008_REG_CHIPID 0x58 ++ ++/* ++ * Pin control register configuration constants. ++ */ ++#define MTP008_CFG_VT1_PII 0x08 ++#define MTP008_CFG_VT2_AIN 0x00 ++#define MTP008_CFG_VT2_VT 0x03 ++#define MTP008_CFG_VT2_PII 0x04 ++#define MTP008_CFG_VT2_MASK 0x06 ++#define MTP008_CFG_VT3_VT 0x01 ++ ++/* sensor pin types */ ++#define VOLTAGE 1 ++#define THERMISTOR 2 ++#define PIIDIODE 3 ++ ++/* ++ * Conversion routines and macros. Limit checking is only done on ++ * the TO_REG variants. ++ * ++ * Note that IN values are expressed as 100 times the actual voltage to avoid ++ * having to use floating point values. As such, IN values are between 0 and ++ * 409 (0V to 4.096V). ++ */ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8) / 16), 0, 255)) ++#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) ++ ++/* ++ * The fan cotation count (as stored in the register) is calculated using the ++ * following formula: ++ * count = (22.5K * 60) / (rpm * div) = 1350000 / (rpm * div) ++ * and the rpm is therefore: ++ * rpm = 1350000 / (count * div) ++ */ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ ++ return SENSORS_LIMIT( ++ (1350000 + rpm * div / 2) / (rpm * div), ++ 1, 254 ++ ); ++} ++ ++#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 \ ++ : (val) == 255 ? 0 \ ++ : 1350000 / \ ++ ((val) * (div)) \ ++ ) ++ ++/* ++ * Temperatures are stored as two's complement values of the Celsius value. It ++ * actually uses 10 times the Celsius value to avoid using floating point ++ * values. ++ */ ++#define TEMP_TO_REG(val) ( \ ++ (val) < 0 \ ++ ? SENSORS_LIMIT(((val) - 5) / 10, 0, 255) \ ++ : SENSORS_LIMIT(((val) + 5) / 10, 0, 255) \ ++ ) ++#define TEMP_FROM_REG(val) ( \ ++ ( \ ++ (val) > 0x80 ? (val) - 0x100 \ ++ : (val) \ ++ ) * 10 \ ++ ) ++ ++/* ++ * VCORE voltage: ++ * 0x00 to 0x0f = 2.05 to 1.30 (0.05 per unit) ++ * 0x10 to 0x1e = 3.50 to 2.10 (0.10 per unit) ++ * 0x1f = No CPU ++ */ ++#define VID_FROM_REG(val) ((val) == 0x1f \ ++ ? 0 \ ++ : (val) < 0x10 ? 205 - (val) * 5 \ ++ : 510 - (val) * 10) ++ ++/* ++ * Fan divider. ++ */ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val) == 8 ? 3 \ ++ : (val) == 4 ? 2 \ ++ : (val) == 2 ? 1 \ ++ : 0) ++ ++/* ++ * Alarms (interrupt status). ++ */ ++#define ALARMS_FROM_REG(val) (val) ++ ++/* ++ * Beep controls. ++ */ ++#define BEEPS_FROM_REG(val) (val) ++#define BEEPS_TO_REG(val) (val) ++ ++/* ++ * PWM control. nr range 1 to 3 ++ */ ++#define PWM_FROM_REG(val) (val) ++#define PWM_TO_REG(val) (val) ++#define PWMENABLE_FROM_REG(nr, val) (((val) >> ((nr) + 3)) & 1) ++ ++/* ++ * For each registered MTP008, we need to keep some data in memory. The ++ * structure itself is dynamically allocated, at the same time when a new ++ * mtp008 client is allocated. ++ */ ++struct mtp008_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[7]; /* Register value */ ++ u8 in_max[7]; /* Register value */ ++ u8 in_min[7]; /* Register value */ ++ u8 temp; /* Register value */ ++ u8 temp_max; /* Register value */ ++ u8 temp_min; /* Register value */ ++ u8 fan[3]; /* Register value */ ++ u8 fan_min[3]; /* Register value */ ++ u8 vid; /* Register encoding */ ++ u8 fan_div[3]; /* Register encoding */ ++ u16 alarms; /* Register encoding */ ++ u16 beeps; /* Register encoding */ ++ u8 pwm[4]; /* Register value */ ++ u8 sens[3]; /* 1 = Analog input, ++ 2 = Thermistor, ++ 3 = PII/Celeron diode */ ++ u8 pwmenable; /* Register 0x57 value */ ++}; ++ ++static int mtp008_attach_adapter(struct i2c_adapter *adapter); ++static int mtp008_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int mtp008_detach_client(struct i2c_client *client); ++ ++static int mtp008_read_value(struct i2c_client *client, u8 register); ++static int mtp008_write_value(struct i2c_client *client, u8 register, u8 value); ++static void mtp008_update_client(struct i2c_client *client); ++static void mtp008_init_client(struct i2c_client *client); ++ ++static void mtp008_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_beep(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_sens(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void mtp008_getsensortype(struct mtp008_data *data, u8 inp); ++ ++static int mtp008_id = 0; ++ ++static struct i2c_driver mtp008_driver = ++{ ++ .owner = THIS_MODULE, ++ .name = "MTP008 sensor driver", ++ .id = I2C_DRIVERID_MTP008, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = mtp008_attach_adapter, ++ .detach_client = mtp008_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define MTP008_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define MTP008_SYSCTL_IN1 1001 ++#define MTP008_SYSCTL_IN2 1002 ++#define MTP008_SYSCTL_IN3 1003 ++#define MTP008_SYSCTL_IN4 1004 ++#define MTP008_SYSCTL_IN5 1005 ++#define MTP008_SYSCTL_IN6 1006 ++#define MTP008_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define MTP008_SYSCTL_FAN2 1102 ++#define MTP008_SYSCTL_FAN3 1103 ++#define MTP008_SYSCTL_TEMP1 1200 /* Degrees Celcius * 10 */ ++#define MTP008_SYSCTL_TEMP2 1201 /* Degrees Celcius * 10 */ ++#define MTP008_SYSCTL_TEMP3 1202 /* Degrees Celcius * 10 */ ++#define MTP008_SYSCTL_VID 1300 /* Volts * 100 */ ++#define MTP008_SYSCTL_PWM1 1401 ++#define MTP008_SYSCTL_PWM2 1402 ++#define MTP008_SYSCTL_PWM3 1403 ++#define MTP008_SYSCTL_SENS1 1501 /* 1, 2, or Beta (3000-5000) */ ++#define MTP008_SYSCTL_SENS2 1502 ++#define MTP008_SYSCTL_SENS3 1503 ++#define MTP008_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define MTP008_SYSCTL_ALARMS 2001 /* bitvector */ ++#define MTP008_SYSCTL_BEEP 2002 /* bitvector */ ++ ++#define MTP008_ALARM_IN0 0x0001 ++#define MTP008_ALARM_IN1 0x0002 ++#define MTP008_ALARM_IN2 0x0004 ++#define MTP008_ALARM_IN3 0x0008 ++#define MTP008_ALARM_IN4 0x0100 ++#define MTP008_ALARM_IN5 0x0200 ++#define MTP008_ALARM_IN6 0x0400 ++#define MTP008_ALARM_FAN1 0x0040 ++#define MTP008_ALARM_FAN2 0x0080 ++#define MTP008_ALARM_FAN3 0x0800 ++#define MTP008_ALARM_TEMP1 0x0010 ++#define MTP008_ALARM_TEMP2 0x0100 ++#define MTP008_ALARM_TEMP3 0x0200 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* The /proc/sys entries */ ++/* These files are created for each detected chip. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++ ++static ctl_table mtp008_dir_table_template[] = ++{ ++ {MTP008_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, ++ {MTP008_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan}, ++ {MTP008_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan}, ++ {MTP008_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan}, ++ {MTP008_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_temp}, ++ {MTP008_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_temp_add}, ++ {MTP008_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_temp_add}, ++ {MTP008_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_vid}, ++ {MTP008_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan_div}, ++ {MTP008_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_alarms}, ++ {MTP008_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_beep}, ++ {MTP008_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_pwm}, ++ {MTP008_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_pwm}, ++ {MTP008_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_pwm}, ++ {MTP008_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_sens}, ++ {MTP008_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_sens}, ++ {MTP008_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_sens}, ++ {0} ++}; ++ ++/* This function is called when: ++ * mtp008_driver is inserted (when this module is loaded), for each available ++ * adapter when a new adapter is inserted (and mtp008_driver is still present) ++ */ ++static int mtp008_attach_adapter(struct i2c_adapter *adapter) ++{ ++ struct i2c_client_address_data mtp008_addr_data; ++ ++ mtp008_addr_data.normal_i2c = addr_data.normal_i2c; ++ mtp008_addr_data.normal_i2c_range = addr_data.normal_i2c_range; ++ mtp008_addr_data.probe = addr_data.probe; ++ mtp008_addr_data.probe_range = addr_data.probe_range; ++ mtp008_addr_data.ignore = addr_data.ignore; ++ mtp008_addr_data.ignore_range = addr_data.ignore_range; ++ mtp008_addr_data.force = addr_data.forces->force; ++ ++ return i2c_probe(adapter, &mtp008_addr_data, mtp008_detect); ++} ++ ++int mtp008_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ const char *type_name = ""; ++ const char *client_name = ""; ++ int is_isa, err, sysid; ++ struct i2c_client *new_client; ++ struct mtp008_data *data; ++ ++ err = 0; ++ ++ is_isa = i2c_is_isa_adapter(adapter); ++ if (is_isa || ++ !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* ++ * We presume we have a valid client. We now create the client ++ * structure, even though we cannot fill it completely yet. But it ++ * allows us to use mtp008_(read|write)_value(). ++ */ ++ if (!(data = kmalloc(sizeof(struct mtp008_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &mtp008_driver; ++ new_client->flags = 0; ++ ++ /* ++ * Remaining detection. ++ */ ++ if (kind < 0) { ++ if (mtp008_read_value(new_client, MTP008_REG_CHIPID) != 0xac) ++ goto ERROR1; ++ } ++ /* ++ * Fill in the remaining client fields and put it into the global list. ++ */ ++ type_name = "mtp008"; ++ client_name = "MTP008 chip"; ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = mtp008_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* ++ * Tell the I2C layer that a new client has arrived. ++ */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR1; ++ ++ /* ++ * Register a new directory entry with the sensors module. ++ */ ++ if ((sysid = i2c_register_entry(new_client, type_name, ++ mtp008_dir_table_template)) < 0) { ++ err = sysid; ++ goto ERROR2; ++ } ++ data->sysctl_id = sysid; ++ ++ /* ++ * Initialize the MTP008 chip. ++ */ ++ mtp008_init_client(new_client); ++ ++ return 0; ++ ++ /* ++ * Error handling. Bad programming practise but very code efficient. ++ */ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ ++ ERROR0: ++ return err; ++} ++ ++static int mtp008_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry( ++ ((struct mtp008_data *) (client->data))->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk("mtp008.o: Deregistration failed, " ++ "client not detached.\n"); ++ return err; ++ } ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++static int mtp008_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg) & 0xff; ++} ++ ++static int mtp008_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++/* Called when we have found a new MTP008. It should set limits, etc. */ ++static void mtp008_init_client(struct i2c_client *client) ++{ ++ u8 save1, save2; ++ struct mtp008_data *data; ++ ++ data = client->data; ++ ++ /* ++ * Initialize the Myson MTP008 hardware monitoring chip. ++ * Save the pin settings that the BIOS hopefully set. ++ */ ++ save1 = mtp008_read_value(client, MTP008_REG_PIN_CTRL1); ++ save2 = mtp008_read_value(client, MTP008_REG_PIN_CTRL2); ++ mtp008_write_value(client, MTP008_REG_CONFIG, ++ (mtp008_read_value(client, MTP008_REG_CONFIG) & 0x7f) | 0x80); ++ mtp008_write_value(client, MTP008_REG_PIN_CTRL1, save1); ++ mtp008_write_value(client, MTP008_REG_PIN_CTRL2, save2); ++ ++ mtp008_getsensortype(data, save2); ++ ++ ++ /* ++ * Start monitoring. ++ */ ++ mtp008_write_value( ++ client, MTP008_REG_CONFIG, ++ (mtp008_read_value(client, MTP008_REG_CONFIG) & 0xf7) | 0x01 ++ ); ++} ++ ++static void mtp008_update_client(struct i2c_client *client) ++{ ++ int i; ++ u8 inp; ++ struct mtp008_data *data; ++ ++ data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++#ifdef DEBUG ++ printk("Starting MTP008 update\n"); ++#endif ++ ++ /* ++ * Read in the analog inputs. We're reading AIN4 and AIN5 as ++ * regular analog inputs, even though they may have been ++ * configured as temperature readings instead. Interpretation ++ * of these values is done elsewhere. ++ */ ++ for (i = 0; i < 7; i++) { ++ data->in[i] = ++ mtp008_read_value(client, MTP008_REG_IN(i)); ++ data->in_max[i] = ++ mtp008_read_value(client, MTP008_REG_IN_MAX(i)); ++ data->in_min[i] = ++ mtp008_read_value(client, MTP008_REG_IN_MIN(i)); ++ } ++ ++ /* ++ * Read the temperature sensor. ++ */ ++ data->temp = mtp008_read_value(client, MTP008_REG_TEMP); ++ data->temp_max = mtp008_read_value(client, MTP008_REG_TEMP_MAX); ++ data->temp_min = mtp008_read_value(client, MTP008_REG_TEMP_MIN); ++ ++ /* ++ * Read the first 2 fan dividers and the VID setting. Read the ++ * third fan divider from a different register. ++ */ ++ inp = mtp008_read_value(client, MTP008_REG_VID_FANDIV); ++ data->vid = inp & 0x0f; ++ data->vid |= (mtp008_read_value(client, ++ MTP008_REG_RESET_VID4) & 0x01) << 4; ++ ++ data->fan_div[0] = (inp >> 4) & 0x03; ++ data->fan_div[1] = inp >> 6; ++ data->fan_div[2] = ++ mtp008_read_value(client, MTP008_REG_PIN_CTRL1) >> 6; ++ ++ /* ++ * Read the interrupt status registers. ++ */ ++ data->alarms = ++ (mtp008_read_value(client, ++ MTP008_REG_INT_STAT1) & 0xdf) | ++ (mtp008_read_value(client, ++ MTP008_REG_INT_STAT2) & 0x0f) << 8; ++ ++ /* ++ * Read the beep control registers. ++ */ ++ data->beeps = ++ (mtp008_read_value(client, ++ MTP008_REG_BEEP_CTRL1) & 0xdf) | ++ (mtp008_read_value(client, ++ MTP008_REG_BEEP_CTRL2) & 0x8f) << 8; ++ ++ /* ++ * Read the sensor configuration. ++ */ ++ inp = mtp008_read_value(client, MTP008_REG_PIN_CTRL2); ++ mtp008_getsensortype(data, inp); ++ data->pwmenable = inp; ++ ++ /* ++ * Read the PWM registers if enabled. ++ */ ++ for (i = 1; i <= 3; i++) ++ { ++ if(PWMENABLE_FROM_REG(i, inp)) ++ data->pwm[i-1] = mtp008_read_value(client, ++ MTP008_REG_PWM_CTRL(i)); ++ else ++ data->pwm[i-1] = 255; ++ } ++ ++ /* ++ * Read the fan sensors. Skip 3 if PWM1 enabled. ++ */ ++ for (i = 1; i <= 3; i++) { ++ if(i == 3 && PWMENABLE_FROM_REG(1, inp)) { ++ data->fan[2] = 0; ++ data->fan_min[2] = 0; ++ } else { ++ data->fan[i-1] = mtp008_read_value(client, ++ MTP008_REG_FAN(i)); ++ data->fan_min[i-1] = mtp008_read_value(client, ++ MTP008_REG_FAN_MIN(i)); ++ } ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ up(&data->update_lock); ++} ++ ++static void mtp008_getsensortype(struct mtp008_data *data, u8 inp) ++{ ++ inp &= 0x0f; ++ data->sens[0] = (inp >> 3) + 2; /* 2 or 3 */ ++ data->sens[1] = ((inp >> 1) & 0x03) + 1; /* 1, 2 or 3 */ ++ data->sens[2] = (inp & 0x01) + 1; /* 1 or 2 */ ++} ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++void mtp008_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int nr; ++ struct mtp008_data *data; ++ ++ nr = ctl_name - MTP008_SYSCTL_IN0; ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 2; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ if((nr != 4 && nr != 5) || data->sens[nr - 3] == VOLTAGE) { ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ } else { ++ results[0] = 0; ++ results[1] = 0; ++ results[2] = 0; ++ } ++ ++ *nrels_mag = 3; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if((nr != 4 && nr != 5) || data->sens[nr - 3] == VOLTAGE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ mtp008_write_value(client, MTP008_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ mtp008_write_value(client, MTP008_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++ } ++} ++ ++void mtp008_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int nr; ++ struct mtp008_data *data; ++ ++ nr = ctl_name - MTP008_SYSCTL_FAN1; ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 0; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = FAN_FROM_REG(data->fan_min[nr], ++ DIV_FROM_REG(data->fan_div[nr])); ++ results[1] = FAN_FROM_REG(data->fan[nr], ++ DIV_FROM_REG(data->fan_div[nr])); ++ ++ *nrels_mag = 2; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr] = ++ FAN_TO_REG(results[0], ++ DIV_FROM_REG(data->fan_div[nr])); ++ mtp008_write_value(client, MTP008_REG_FAN_MIN(nr + 1), ++ data->fan_min[nr]); ++ } ++ } ++} ++ ++void mtp008_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct mtp008_data *data; ++ ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 1; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = TEMP_FROM_REG(data->temp_max); ++ results[1] = TEMP_FROM_REG(data->temp_min); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if (*nrels_mag >= 1) { ++ data->temp_max = TEMP_TO_REG(results[0]); ++ mtp008_write_value(client, MTP008_REG_TEMP_MAX, ++ data->temp_max); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_min = TEMP_TO_REG(results[1]); ++ mtp008_write_value(client, MTP008_REG_TEMP_MIN, ++ data->temp_min); ++ } ++ } ++} ++ ++void mtp008_temp_add(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int nr; ++ struct mtp008_data *data; ++ ++ nr = 3 + ctl_name - MTP008_SYSCTL_TEMP1; /* AIN4 or AIN5 */ ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 1; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ if(data->sens[nr - 3] != VOLTAGE) { ++ results[0] = TEMP_FROM_REG(data->in_max[nr]); ++ results[1] = TEMP_FROM_REG(data->in_min[nr]); ++ results[2] = TEMP_FROM_REG(data->in[nr]); ++ } else { ++ results[0] = 0; ++ results[1] = 0; ++ results[2] = 0; ++ } ++ *nrels_mag = 3; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if(data->sens[nr - 3] != VOLTAGE) { ++ if (*nrels_mag >= 1) { ++ data->in_max[nr] = TEMP_TO_REG(results[0]); ++ mtp008_write_value(client, ++ MTP008_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_min[nr] = TEMP_TO_REG(results[1]); ++ mtp008_write_value(client, ++ MTP008_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ } ++ } ++} ++ ++void mtp008_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct mtp008_data *data; ++ ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 2; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = VID_FROM_REG(data->vid); ++ ++ *nrels_mag = 1; ++ } ++} ++ ++void mtp008_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct mtp008_data *data; ++ u8 val; ++ ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 0; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ results[2] = DIV_FROM_REG(data->fan_div[2]); ++ ++ *nrels_mag = 3; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if (*nrels_mag >= 3) { ++ data->fan_div[2] = DIV_TO_REG(results[2]); ++ val = mtp008_read_value(client, MTP008_REG_PIN_CTRL1); ++ val = (val & 0x3f) | (data->fan_div[2] & 0x03) << 6; ++ mtp008_write_value(client, MTP008_REG_PIN_CTRL1, val); ++ } ++ if (*nrels_mag >= 1) { ++ val = mtp008_read_value(client, MTP008_REG_VID_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ val = (val & 0x3f) | ++ (data->fan_div[1] & 0x03) << 6; ++ } ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ val = (val & 0xcf) | (data->fan_div[0] & 0x03) << 4; ++ mtp008_write_value(client, MTP008_REG_VID_FANDIV, val); ++ } ++ } ++} ++ ++void mtp008_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct mtp008_data *data; ++ ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 0; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ ++ *nrels_mag = 1; ++ } ++} ++ ++void mtp008_beep(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct mtp008_data *data; ++ ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 0; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = BEEPS_FROM_REG(data->beeps); ++ ++ *nrels_mag = 1; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if (*nrels_mag >= 1) { ++ data->beeps = BEEPS_TO_REG(results[0]) & 0xdf8f; ++ ++ mtp008_write_value(client, MTP008_REG_BEEP_CTRL1, ++ data->beeps & 0xff); ++ mtp008_write_value(client, MTP008_REG_BEEP_CTRL2, ++ data->beeps >> 8); ++ } ++ } ++} ++ ++void mtp008_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ int nr; ++ struct mtp008_data *data; ++ ++ nr = ctl_name - MTP008_SYSCTL_PWM1; ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 0; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ mtp008_update_client(client); ++ ++ results[0] = PWM_FROM_REG(data->pwm[nr]); ++ results[1] = PWMENABLE_FROM_REG(nr + 1, data->pwmenable); ++ *nrels_mag = 2; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if (*nrels_mag >= 1) { ++ if (*nrels_mag >= 2) { ++ if(results[1]) ++ data->pwmenable |= 0x10 << nr; ++ else ++ data->pwmenable &= ~(0x10 << nr); ++ mtp008_write_value(client, MTP008_REG_PIN_CTRL2, ++ data->pwmenable); ++ } ++ data->pwm[nr] = PWM_TO_REG(results[0]); ++ mtp008_write_value(client, MTP008_REG_PWM_CTRL(nr), ++ data->pwm[nr]); ++ } ++ } ++} ++ ++void mtp008_sens(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ const char *opts = ""; ++ int nr; ++ u8 tmp; ++ struct mtp008_data *data; ++ ++ nr = 1 + ctl_name - MTP008_SYSCTL_SENS1; ++ data = client->data; ++ ++ switch (operation) { ++ case SENSORS_PROC_REAL_INFO: ++ *nrels_mag = 0; ++ ++ break; ++ case SENSORS_PROC_REAL_READ: ++ results[0] = data->sens[nr - 1]; ++ ++ *nrels_mag = 1; ++ ++ break; ++ case SENSORS_PROC_REAL_WRITE: ++ if (*nrels_mag >= 1) { ++ tmp = mtp008_read_value(client, MTP008_REG_PIN_CTRL2); ++ ++ switch (nr) { ++ case 1: /* VT or PII */ ++ opts = "2 or 3"; ++ ++ switch (results[0]) { ++ case THERMISTOR: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp & ~MTP008_CFG_VT1_PII); ++ data->sens[0] = 2; ++ return; ++ case PIIDIODE: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp | MTP008_CFG_VT1_PII); ++ data->sens[0] = 3; ++ return; ++ } ++ ++ break; ++ case 2: /* AIN, VT or PII */ ++ tmp &= ~MTP008_CFG_VT2_MASK; ++ opts = "1, 2 or 3"; ++ ++ switch (results[0]) { ++ case VOLTAGE: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp | MTP008_CFG_VT2_AIN); ++ data->sens[1] = 1; ++ return; ++ case THERMISTOR: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp | MTP008_CFG_VT2_VT); ++ data->sens[1] = 2; ++ return; ++ case PIIDIODE: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp | MTP008_CFG_VT2_PII); ++ data->sens[1] = 3; ++ return; ++ } ++ ++ break; ++ case 3: /* AIN or VT */ ++ opts = "1 or 2"; ++ ++ switch (results[0]) { ++ case VOLTAGE: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp & ~MTP008_CFG_VT3_VT); ++ data->sens[2] = 1; ++ return; ++ case THERMISTOR: ++ mtp008_write_value( ++ client, MTP008_REG_PIN_CTRL2, ++ tmp | MTP008_CFG_VT3_VT); ++ data->sens[2] = 2; ++ return; ++ } ++ ++ break; ++ } ++ ++ printk("mtp008.o: Invalid sensor type %ld " ++ "for sensor %d; must be %s.\n", ++ results[0], nr, opts); ++ } ++ } ++} ++ ++static int __init sm_mtp008_init(void) ++{ ++ printk("mtp008.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&mtp008_driver); ++} ++ ++static void __exit sm_mtp008_exit(void) ++{ ++ i2c_del_driver(&mtp008_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "and Kris Van Hees <aedil@alchar.org>"); ++MODULE_DESCRIPTION("MTP008 driver"); ++ ++module_init(sm_mtp008_init); ++module_exit(sm_mtp008_exit); +--- linux-old/drivers/sensors/pc87360.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/pc87360.c Mon Dec 13 20:18:51 2004 +@@ -0,0 +1,1357 @@ ++/* ++ * pc87360.c - Part of lm_sensors, Linux kernel modules ++ * for hardware monitoring ++ * Copyright (C) 2004 Jean Delvare <khali@linux-fr.org> ++ * ++ * Copied from smsc47m1.c: ++ * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ * ++ * Supports the following chips: ++ * ++ * Chip #vin #fan #pwm #temp devid ++ * PC87360 - 2 2 - 0xE1 ++ * PC87363 - 2 2 - 0xE8 ++ * PC87364 - 3 3 - 0xE4 ++ * PC87365 11 3 3 2 0xE5 ++ * PC87366 11 3 3 3-4 0xE9 ++ * ++ * This driver assumes that no more than one chip is present, and the ++ * standard Super-I/O address is used (0x2E/0x2F). ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++static struct i2c_force_data forces[] = {{NULL}}; ++static u8 devid; ++static unsigned int extra_isa[] = { 0x0000, 0x0000, 0x0000 }; ++static u8 confreg[4]; ++ ++enum chips { any_chip, pc87360, pc87363, pc87364, pc87365, pc87366 }; ++static struct i2c_address_data addr_data = { ++ .normal_i2c = normal_i2c, ++ .normal_i2c_range = normal_i2c_range, ++ .normal_isa = normal_isa, ++ .normal_isa_range = normal_isa_range, ++ .probe = normal_i2c, /* cheat */ ++ .probe_range = normal_i2c_range, /* cheat */ ++ .ignore = normal_i2c, /* cheat */ ++ .ignore_range = normal_i2c_range, /* cheat */ ++ .forces = forces, ++}; ++ ++static int init = 1; ++MODULE_PARM(init, "i"); ++MODULE_PARM_DESC(init, ++ "Chip initialization level:\n" ++ " 0: None\n" ++ "*1: Forcibly enable internal voltage and temperature channels, except in9\n" ++ " 2: Forcibly enable all voltage and temperature channels, except in9\n" ++ " 3: Forcibly enable all voltage and temperature channels, including in9"); ++ ++/* ++ * Super-I/O registers and operations ++ */ ++ ++#define REG 0x2e /* The register to read/write */ ++#define VAL 0x2f /* The value to read/write */ ++ ++#define DEV 0x07 /* Register: Logical device select */ ++#define DEVID 0x20 /* Register: Device ID */ ++#define ACT 0x30 /* Register: Device activation */ ++#define BASE 0x60 /* Register: Base address */ ++ ++#define FSCM 0x09 /* Logical device: fans */ ++#define VLM 0x0d /* Logical device: voltages */ ++#define TMS 0x0e /* Logical device: temperatures */ ++static const u8 logdev[3] = { FSCM, VLM, TMS }; ++ ++#define LD_FAN 0 ++#define LD_IN 1 ++#define LD_TEMP 2 ++ ++static inline void superio_outb(int reg, int val) ++{ ++ outb(reg, REG); ++ outb(val, VAL); ++} ++ ++static inline int superio_inb(int reg) ++{ ++ outb(reg, REG); ++ return inb(VAL); ++} ++ ++static inline void superio_exit(void) ++{ ++ outb(0x02, REG); ++ outb(0x02, VAL); ++} ++ ++/* ++ * Logical devices ++ */ ++ ++#define PC87360_EXTENT 0x10 ++#define PC87365_REG_BANK 0x09 ++#define NO_BANK 0xff ++ ++/* ++ * Fan registers and conversions ++ */ ++ ++/* nr has to be 0 or 1 (PC87360/87363) or 2 (PC87364/87365/87366) */ ++#define PC87360_REG_PRESCALE(nr) (0x00 + 2 * (nr)) ++#define PC87360_REG_PWM(nr) (0x01 + 2 * (nr)) ++#define PC87360_REG_FAN_MIN(nr) (0x06 + 3 * (nr)) ++#define PC87360_REG_FAN(nr) (0x07 + 3 * (nr)) ++#define PC87360_REG_FAN_STATUS(nr) (0x08 + 3 * (nr)) ++ ++#define FAN_FROM_REG(val,div) ((val)==0?0: \ ++ 480000/((val)*(div))) ++#define FAN_TO_REG(val,div) ((val)<=100?0: \ ++ 480000/((val)*(div))) ++#define FAN_DIV_FROM_REG(val) (1 << ((val >> 5) & 0x03)) ++#define FAN_DIV_TO_REG(val) ((val)==8?0x60:(val)==4?0x40: \ ++ (val)==1?0x00:0x20) ++#define FAN_STATUS_FROM_REG(val) ((val) & 0x07) ++ ++#define FAN_CONFIG_MONITOR(val,nr) (((val) >> (2 + nr * 3)) & 1) ++#define FAN_CONFIG_CONTROL(val,nr) (((val) >> (3 + nr * 3)) & 1) ++#define FAN_CONFIG_INVERT(val,nr) (((val) >> (4 + nr * 3)) & 1) ++ ++#define PWM_FROM_REG(val,inv) ((inv) ? 255 - (val) : (val)) ++static inline u8 PWM_TO_REG(int val, int inv) ++{ ++ if (inv) ++ val = 255 - val; ++ if (val < 0) ++ return 0; ++ if (val > 255) ++ return 255; ++ return val; ++} ++ ++/* ++ * Voltage registers and conversions ++ */ ++ ++#define PC87365_REG_IN_CONVRATE 0x07 ++#define PC87365_REG_IN_CONFIG 0x08 ++#define PC87365_REG_IN 0x0B ++#define PC87365_REG_IN_MIN 0x0D ++#define PC87365_REG_IN_MAX 0x0C ++#define PC87365_REG_IN_STATUS 0x0A ++#define PC87365_REG_IN_ALARMS1 0x00 ++#define PC87365_REG_IN_ALARMS2 0x01 ++#define PC87365_REG_VID 0x06 ++ ++#define IN_FROM_REG(val,ref) (((val) * (ref) + 128) / 256) ++#define IN_TO_REG(val,ref) ((val)<0 ? 0 : \ ++ (val)*256>=(ref)*255 ? 255: \ ++ ((val) * 256 + (ref) / 2) / (ref)) ++ ++/* ++ * Temperature registers and conversions ++ */ ++ ++#define PC87365_REG_TEMP_CONFIG 0x08 ++#define PC87365_REG_TEMP 0x0B ++#define PC87365_REG_TEMP_MIN 0x0D ++#define PC87365_REG_TEMP_MAX 0x0C ++#define PC87365_REG_TEMP_CRIT 0x0E ++#define PC87365_REG_TEMP_STATUS 0x0A ++#define PC87365_REG_TEMP_ALARMS 0x00 ++ ++#define TEMP_FROM_REG(val) ((val)&0x80 ? (val) - 0x100 : (val)) ++#define TEMP_TO_REG(val) ((val)<-55 ? 201 : (val)>127 ? 0x7F : \ ++ (val)<0 ? (val) + 0x100 : (val)) ++ ++struct pc87360_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ int address[3]; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 fannr, innr, tempnr; ++ ++ u8 fan[3]; /* Register value */ ++ u8 fan_min[3]; /* Register value */ ++ u8 fan_status[3]; /* Register value */ ++ u8 pwm[3]; /* Register value */ ++ u16 fan_conf; /* Configuration register values, combined */ ++ ++ u16 in_vref; /* 10mV/bit */ ++ u8 in[14]; /* Register value */ ++ u8 in_min[14]; /* Register value */ ++ u8 in_max[14]; /* Register value */ ++ u8 in_crit[3]; /* Register value */ ++ u8 in_status[14]; /* Register value */ ++ u16 in_alarms; /* Register values, combined, masked */ ++ u8 vid_conf; /* Configuration register value */ ++ u8 vrm; ++ u8 vid; /* Register value */ ++ ++ u8 temp[3]; /* Register value */ ++ u8 temp_min[3]; /* Register value */ ++ u8 temp_max[3]; /* Register value */ ++ u8 temp_crit[3]; /* Register value */ ++ u8 temp_status[3]; /* Register value */ ++ u8 temp_alarms; /* Register value, masked */ ++}; ++ ++ ++static int pc87360_attach_adapter(struct i2c_adapter *adapter); ++static int pc87360_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int pc87360_detach_client(struct i2c_client *client); ++ ++static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, ++ u8 reg); ++static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, ++ u8 reg, u8 value); ++static void pc87360_init_client(struct i2c_client *client, int use_thermistors); ++static void pc87360_update_client(struct i2c_client *client); ++static int pc87360_find(u8 *devid, int *address); ++ ++ ++void pc87365_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++ ++static void pc87360_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pc87360_fan_status(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pc87360_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pc87360_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++void pc87365_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++void pc87365_in_status(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++void pc87365_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++void pc87365_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++ ++void pc87365_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++void pc87365_temp_status(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results); ++ ++static int pc87360_id = 0; ++ ++static struct i2c_driver pc87360_driver = { ++ .owner = THIS_MODULE, ++ .name = "PC8736x hardware monitor", ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = pc87360_attach_adapter, ++ .detach_client = pc87360_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define PC87365_SYSCTL_ALARMS 100 /* bit field */ ++ ++#define PC87360_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define PC87360_SYSCTL_FAN2 1102 ++#define PC87360_SYSCTL_FAN3 1103 /* not for PC87360/PC87363 */ ++#define PC87360_SYSCTL_FAN_DIV 1201 /* 1, 2, 4 or 8 */ ++#define PC87360_SYSCTL_FAN1_STATUS 1301 /* bit field */ ++#define PC87360_SYSCTL_FAN2_STATUS 1302 ++#define PC87360_SYSCTL_FAN3_STATUS 1303 /* not for PC87360/PC87363 */ ++#define PC87360_SYSCTL_PWM1 1401 /* 0-255 */ ++#define PC87360_SYSCTL_PWM2 1402 ++#define PC87360_SYSCTL_PWM3 1403 /* not for PC87360/PC87363 */ ++ ++#define PC87360_STATUS_FAN_READY 0x01 ++#define PC87360_STATUS_FAN_LOW 0x02 ++#define PC87360_STATUS_FAN_OVERFLOW 0x04 ++ ++#define PC87365_SYSCTL_IN0 2100 /* mV */ ++#define PC87365_SYSCTL_IN1 2101 ++#define PC87365_SYSCTL_IN2 2102 ++#define PC87365_SYSCTL_IN3 2103 ++#define PC87365_SYSCTL_IN4 2104 ++#define PC87365_SYSCTL_IN5 2105 ++#define PC87365_SYSCTL_IN6 2106 ++#define PC87365_SYSCTL_IN7 2107 ++#define PC87365_SYSCTL_IN8 2108 ++#define PC87365_SYSCTL_IN9 2109 ++#define PC87365_SYSCTL_IN10 2110 ++#define PC87365_SYSCTL_TEMP4 2111 /* not for PC87365 */ ++#define PC87365_SYSCTL_TEMP5 2112 /* not for PC87365 */ ++#define PC87365_SYSCTL_TEMP6 2113 /* not for PC87365 */ ++#define PC87365_SYSCTL_IN0_STATUS 2300 /* bit field */ ++#define PC87365_SYSCTL_IN1_STATUS 2301 ++#define PC87365_SYSCTL_IN2_STATUS 2302 ++#define PC87365_SYSCTL_IN3_STATUS 2303 ++#define PC87365_SYSCTL_IN4_STATUS 2304 ++#define PC87365_SYSCTL_IN5_STATUS 2305 ++#define PC87365_SYSCTL_IN6_STATUS 2306 ++#define PC87365_SYSCTL_IN7_STATUS 2307 ++#define PC87365_SYSCTL_IN8_STATUS 2308 ++#define PC87365_SYSCTL_IN9_STATUS 2309 ++#define PC87365_SYSCTL_IN10_STATUS 2310 ++#define PC87365_SYSCTL_TEMP4_STATUS 2311 /* not for PC87365 */ ++#define PC87365_SYSCTL_TEMP5_STATUS 2312 /* not for PC87365 */ ++#define PC87365_SYSCTL_TEMP6_STATUS 2313 /* not for PC87365 */ ++ ++#define PC87365_SYSCTL_VID 2400 ++#define PC87365_SYSCTL_VRM 2401 ++ ++#define PC87365_STATUS_IN_MIN 0x02 ++#define PC87365_STATUS_IN_MAX 0x04 ++ ++#define PC87365_SYSCTL_TEMP1 3101 /* degrees Celcius */ ++#define PC87365_SYSCTL_TEMP2 3102 ++#define PC87365_SYSCTL_TEMP3 3103 /* not for PC87365 */ ++#define PC87365_SYSCTL_TEMP1_STATUS 3301 /* bit field */ ++#define PC87365_SYSCTL_TEMP2_STATUS 3302 ++#define PC87365_SYSCTL_TEMP3_STATUS 3303 /* not for PC87365 */ ++ ++#define PC87365_STATUS_TEMP_MIN 0x02 ++#define PC87365_STATUS_TEMP_MAX 0x04 ++#define PC87365_STATUS_TEMP_CRIT 0x08 ++#define PC87365_STATUS_TEMP_OPEN 0x40 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++static ctl_table pc87360_dir_table_template[] = { /* PC87363 and PC87364 too */ ++ {PC87360_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, ++ {PC87360_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, ++ {PC87360_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, ++ {PC87360_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_div}, ++ {PC87360_SYSCTL_FAN1_STATUS, "fan1_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, ++ {PC87360_SYSCTL_FAN2_STATUS, "fan2_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, ++ {PC87360_SYSCTL_FAN3_STATUS, "fan3_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, ++ {PC87360_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, ++ {PC87360_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, ++ {PC87360_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, ++ {0} ++}; ++ ++static ctl_table pc87365_dir_table_template[] = { /* PC87366 too */ ++ {PC87365_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_alarms}, ++ {PC87360_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, ++ {PC87360_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, ++ {PC87360_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, ++ {PC87360_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_div}, ++ {PC87360_SYSCTL_FAN1_STATUS, "fan1_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, ++ {PC87360_SYSCTL_FAN2_STATUS, "fan2_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, ++ {PC87360_SYSCTL_FAN3_STATUS, "fan3_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, ++ {PC87360_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, ++ {PC87360_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, ++ {PC87360_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, ++ {PC87365_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN9, "in9", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN10, "in10", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_IN0_STATUS, "in0_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN1_STATUS, "in1_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN2_STATUS, "in2_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN3_STATUS, "in3_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN4_STATUS, "in4_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN5_STATUS, "in5_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN6_STATUS, "in6_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN7_STATUS, "in7_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN8_STATUS, "in8_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN9_STATUS, "in9_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_IN10_STATUS, "in10_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp}, ++ {PC87365_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp}, ++ {PC87365_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp}, ++ {PC87365_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, ++ {PC87365_SYSCTL_TEMP1_STATUS, "temp1_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp_status}, ++ {PC87365_SYSCTL_TEMP2_STATUS, "temp2_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp_status}, ++ {PC87365_SYSCTL_TEMP3_STATUS, "temp3_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp_status}, ++ {PC87365_SYSCTL_TEMP4_STATUS, "temp4_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_TEMP5_STATUS, "temp5_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_TEMP6_STATUS, "temp6_status", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, ++ {PC87365_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_vid}, ++ {PC87365_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_vrm}, ++ {0} ++}; ++ ++static int pc87360_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, pc87360_detect); ++} ++ ++static int pc87360_find(u8 *devid, int *address) ++{ ++ u16 val; ++ int i; ++ int nrdev; /* logical device count */ ++ ++ /* No superio_enter */ ++ ++ /* Identify device */ ++ val = superio_inb(DEVID); ++ switch (val) { ++ case 0xE1: /* PC87360 */ ++ case 0xE8: /* PC87363 */ ++ case 0xE4: /* PC87364 */ ++ nrdev = 1; ++ break; ++ case 0xE5: /* PC87365 */ ++ case 0xE9: /* PC87366 */ ++ nrdev = 3; ++ break; ++ default: ++ superio_exit(); ++ return -ENODEV; ++ } ++ /* Remember the device id */ ++ *devid = val; ++ ++ for (i = 0; i < nrdev; i++) { ++ /* select logical device */ ++ superio_outb(DEV, logdev[i]); ++ ++ val = superio_inb(ACT); ++ if (!(val & 0x01)) { ++ printk(KERN_INFO "pc87360.o: Device 0x%02x not " ++ "activated\n", logdev[i]); ++ continue; ++ } ++ ++ val = (superio_inb(BASE) << 8) ++ | superio_inb(BASE + 1); ++ if (!val) { ++ printk(KERN_INFO "pc87360.o: Base address not set for " ++ "device 0x%02x\n", logdev[i]); ++ continue; ++ } ++ ++ address[i] = val; ++ ++ if (i==0) { /* Fans */ ++ confreg[0] = superio_inb(0xF0); ++ confreg[1] = superio_inb(0xF1); ++ ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Fan 1: mon=%d " ++ "ctrl=%d inv=%d\n", (confreg[0]>>2)&1, ++ (confreg[0]>>3)&1, (confreg[0]>>4)&1); ++ printk(KERN_DEBUG "pc87360.o: Fan 2: mon=%d " ++ "ctrl=%d inv=%d\n", (confreg[0]>>5)&1, ++ (confreg[0]>>6)&1, (confreg[0]>>7)&1); ++ printk(KERN_DEBUG "pc87360.o: Fan 3: mon=%d " ++ "ctrl=%d inv=%d\n", confreg[1]&1, ++ (confreg[1]>>1)&1, (confreg[1]>>2)&1); ++#endif ++ } else if (i==1) { /* Voltages */ ++ /* Are we using thermistors? */ ++ if (*devid == 0xE9) { /* PC87366 */ ++ /* These registers are not logical-device ++ specific, just that we won't need them if ++ we don't use the VLM device */ ++ confreg[2] = superio_inb(0x2B); ++ confreg[3] = superio_inb(0x25); ++ ++ if (confreg[2] & 0x40) { ++ printk(KERN_INFO "pc87360.o: Using " ++ "thermistors for temperature " ++ "monitoring\n"); ++ } ++ if (confreg[3] & 0xE0) { ++ printk(KERN_INFO "pc87360.o: VID " ++ "inputs routed (mode %u)\n", ++ confreg[3] >> 5); ++ } ++ } ++ } ++ } ++ ++ superio_exit(); ++ return 0; ++} ++ ++/* We don't really care about the address. ++ Read from extra_isa instead. */ ++int pc87360_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct pc87360_data *data; ++ int err = 0; ++ const char *type_name = "pc87360"; ++ const char *client_name = "PC8736x chip"; ++ ctl_table *template = pc87360_dir_table_template; ++ int use_thermistors = 0; ++ ++ if (!i2c_is_isa_adapter(adapter)) { ++ return 0; ++ } ++ ++ for (i = 0; i < 3; i++) { ++ if (extra_isa[i] ++ && check_region(extra_isa[i], PC87360_EXTENT)) { ++ printk(KERN_ERR "pc87360.o: Region 0x%x-0x%x already " ++ "in use!\n", extra_isa[i], ++ extra_isa[i]+PC87360_EXTENT-1); ++ return -ENODEV; ++ } ++ } ++ ++ if (!(data = kmalloc(sizeof(struct pc87360_data), GFP_KERNEL))) { ++ return -ENOMEM; ++ } ++ memset(data, 0x00, sizeof(struct pc87360_data)); ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &pc87360_driver; ++ new_client->flags = 0; ++ ++ data->fannr = 2; ++ data->innr = 0; ++ data->tempnr = 0; ++ ++ switch (devid) { ++ case 0xe8: ++ type_name = "pc87363"; ++ break; ++ case 0xe4: ++ type_name = "pc87364"; ++ data->fannr = 3; ++ break; ++ case 0xe5: ++ type_name = "pc87365"; ++ template = pc87365_dir_table_template; ++ data->fannr = extra_isa[0] ? 3 : 0; ++ data->innr = extra_isa[1] ? 11 : 0; ++ data->tempnr = extra_isa[2] ? 2 : 0; ++ break; ++ case 0xe9: ++ type_name = "pc87366"; ++ template = pc87365_dir_table_template; ++ data->fannr = extra_isa[0] ? 3 : 0; ++ data->innr = extra_isa[1] ? 14 : 0; ++ data->tempnr = extra_isa[2] ? 3 : 0; ++ break; ++ } ++ ++ /* Retrieve the fans configuration from Super-I/O space */ ++ if (data->fannr) ++ data->fan_conf = confreg[0] | (confreg[1] << 8); ++ ++ for (i = 0; i < 3; i++) { ++ if ((data->address[i] = extra_isa[i])) { ++ request_region(extra_isa[i], PC87360_EXTENT, "pc87360"); ++ } ++ } ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = pc87360_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR1; ++ ++ /* Use the correct reference voltage ++ Unless both the VLM and the TMS logical devices agree to ++ use an external Vref, the internal one is used. */ ++ if (data->innr) { ++ i = pc87360_read_value(data, LD_IN, NO_BANK, ++ PC87365_REG_IN_CONFIG); ++ if (data->tempnr) { ++ i &= pc87360_read_value(data, LD_TEMP, NO_BANK, ++ PC87365_REG_TEMP_CONFIG); ++ } ++ data->in_vref = (i&0x02) ? 3025 : 2966; ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Using %s reference voltage\n", ++ (i&0x02) ? "external" : "internal"); ++#endif ++ ++ data->vid_conf = confreg[3]; ++ data->vrm = 90; ++ } ++ ++ /* Fan clock dividers may be needed before any data is read */ ++ for (i = 0; i < data->fannr; i++) { ++ data->fan_status[i] = pc87360_read_value(data, LD_FAN, ++ NO_BANK, PC87360_REG_FAN_STATUS(i)); ++ } ++ ++ if (init > 0) { ++ if (devid == 0xe9 && data->address[1]) /* PC87366 */ ++ use_thermistors = confreg[2] & 0x40; ++ ++ pc87360_init_client(new_client, use_thermistors); ++ } ++ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, template)) < 0) { ++ err = i; ++ goto ERROR2; ++ } ++ data->sysctl_id = i; ++ ++ return 0; ++ ++ERROR2: ++ i2c_detach_client(new_client); ++ERROR1: ++ for (i = 0; i < 3; i++) { ++ if (data->address[i]) { ++ release_region(data->address[i], PC87360_EXTENT); ++ } ++ } ++ kfree(data); ++ return err; ++} ++ ++static int pc87360_detach_client(struct i2c_client *client) ++{ ++ struct pc87360_data *data = client->data; ++ int i, err; ++ ++ i2c_deregister_entry(data->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk(KERN_ERR "pc87360.o: Client deregistration failed, " ++ "client not detached.\n"); ++ return err; ++ } ++ ++ for (i = 0; i < 3; i++) { ++ if (data->address[i]) { ++ release_region(data->address[i], PC87360_EXTENT); ++ } ++ } ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* ldi is the logical device index ++ bank is for voltages and temperatures only */ ++static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, ++ u8 reg) ++{ ++ int res; ++ ++ down(&(data->lock)); ++ if (bank != NO_BANK) { ++ outb_p(bank, data->address[ldi] + PC87365_REG_BANK); ++ } ++ res = inb_p(data->address[ldi] + reg); ++ up(&(data->lock)); ++ return res; ++} ++ ++static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, ++ u8 reg, u8 value) ++{ ++ down(&(data->lock)); ++ if (bank != NO_BANK) { ++ outb_p(bank, data->address[ldi] + PC87365_REG_BANK); ++ } ++ outb_p(value, data->address[ldi] + reg); ++ up(&(data->lock)); ++} ++ ++static void pc87360_init_client(struct i2c_client *client, int use_thermistors) ++{ ++ struct pc87360_data *data = client->data; ++ int i, nr; ++ const u8 init_in[14] = { 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 1, 2, 2, 2 }; ++ const u8 init_temp[3] = { 2, 2, 1 }; ++ u8 reg; ++ ++ if (init >= 2 && data->innr) { ++ reg = pc87360_read_value(data, LD_IN, NO_BANK, ++ PC87365_REG_IN_CONVRATE); ++ printk(KERN_INFO "pc87360.o: VLM conversion set to" ++ "1s period, 160us delay\n"); ++ pc87360_write_value(data, LD_IN, NO_BANK, ++ PC87365_REG_IN_CONVRATE, ++ (reg & 0xC0) | 0x11); ++ } ++ ++ nr = data->innr < 11 ? data->innr : 11; ++ for (i=0; i<nr; i++) { ++ if (init >= init_in[i]) { ++ /* Forcibly enable voltage channel */ ++ reg = pc87360_read_value(data, LD_IN, i, ++ PC87365_REG_IN_STATUS); ++ if (!(reg & 0x01)) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Forcibly " ++ "enabling in%d\n", i); ++#endif ++ pc87360_write_value(data, LD_IN, i, ++ PC87365_REG_IN_STATUS, ++ (reg & 0x68) | 0x87); ++ } ++ } ++ } ++ ++ /* We can't blindly trust the Super-I/O space configuration bit, ++ most BIOS won't set it properly */ ++ for (i=11; i<data->innr; i++) { ++ reg = pc87360_read_value(data, LD_IN, i, ++ PC87365_REG_TEMP_STATUS); ++ use_thermistors = use_thermistors || (reg & 0x01); ++ } ++ ++ i = use_thermistors ? 2 : 0; ++ for (; i<data->tempnr; i++) { ++ if (init >= init_temp[i]) { ++ /* Forcibly enable temperature channel */ ++ reg = pc87360_read_value(data, LD_TEMP, i, ++ PC87365_REG_TEMP_STATUS); ++ if (!(reg & 0x01)) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Forcibly " ++ "enabling temp%d\n", i+1); ++#endif ++ pc87360_write_value(data, LD_TEMP, i, ++ PC87365_REG_TEMP_STATUS, ++ 0xCF); ++ } ++ } ++ } ++ ++ if (use_thermistors) { ++ for (i=11; i<data->innr; i++) { ++ if (init >= init_in[i]) { ++ /* The pin may already be used by thermal ++ diodes */ ++ reg = pc87360_read_value(data, LD_TEMP, (i-11)/2, ++ PC87365_REG_TEMP_STATUS); ++ if (reg & 0x01) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Skipping " ++ "temp%d, pin already in use by ", ++ "temp%d\n", i-7, (i-11)/2); ++#endif ++ continue; ++ } ++ ++ /* Forcibly enable thermistor channel */ ++ reg = pc87360_read_value(data, LD_IN, i, ++ PC87365_REG_IN_STATUS); ++ if (!(reg & 0x01)) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Forcibly " ++ "enabling temp%d\n", i-7); ++#endif ++ pc87360_write_value(data, LD_IN, i, ++ PC87365_REG_TEMP_STATUS, ++ (reg & 0x60) | 0x8F); ++ } ++ } ++ } ++ } ++ ++ if (data->innr) { ++ reg = pc87360_read_value(data, LD_IN, NO_BANK, ++ PC87365_REG_IN_CONFIG); ++ if (reg & 0x01) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Forcibly " ++ "enabling monitoring (VLM)\n"); ++#endif ++ pc87360_write_value(data, LD_IN, NO_BANK, ++ PC87365_REG_IN_CONFIG, ++ reg & 0xFE); ++ } ++ } ++ ++ if (data->tempnr) { ++ reg = pc87360_read_value(data, LD_TEMP, NO_BANK, ++ PC87365_REG_TEMP_CONFIG); ++ if (reg & 0x01) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Forcibly " ++ "enabling monitoring (TMS)\n"); ++#endif ++ pc87360_write_value(data, LD_TEMP, NO_BANK, ++ PC87365_REG_TEMP_CONFIG, ++ reg & 0xFE); ++ } ++ ++ if (init >= 2) { ++ /* Chip config as documented by National Semi. */ ++ pc87360_write_value(data, LD_TEMP, 0xF, 0xA, 0x08); ++ /* We voluntarily omit the bank here, in case the ++ sequence itself matters. It shouldn't be a problem, ++ since nobody else is supposed to access the ++ device at that point. */ ++ pc87360_write_value(data, LD_TEMP, NO_BANK, 0xB, 0x04); ++ pc87360_write_value(data, LD_TEMP, NO_BANK, 0xC, 0x35); ++ pc87360_write_value(data, LD_TEMP, NO_BANK, 0xD, 0x05); ++ pc87360_write_value(data, LD_TEMP, NO_BANK, 0xE, 0x05); ++ } ++ } ++} ++ ++static void pc87360_autodiv(struct pc87360_data *data, int nr) ++{ ++ u8 old_min = data->fan_min[nr]; ++ ++ /* Increase clock divider if needed and possible */ ++ if ((data->fan_status[nr] & 0x04) /* overflow flag */ ++ || (data->fan[nr] >= 224)) { /* next to overflow */ ++ if ((data->fan_status[nr] & 0x60) != 0x60) { ++ data->fan_status[nr] += 0x20; ++ data->fan_min[nr] >>= 1; ++ data->fan[nr] >>= 1; ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Increasing " ++ "clock divider to %d for fan %d\n", ++ FAN_DIV_FROM_REG(data->fan_status[nr]), ++ nr+1); ++#endif ++ } ++ } else { ++ /* Decrease clock divider if possible */ ++ while (!(data->fan_min[nr] & 0x80) /* fan min "nails" divider */ ++ && data->fan[nr] < 85 /* bad accuracy */ ++ && (data->fan_status[nr] & 0x60) != 0x00) { ++ data->fan_status[nr] -= 0x20; ++ data->fan_min[nr] <<= 1; ++ data->fan[nr] <<= 1; ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Decreasing " ++ "clock divider to %d for fan %d\n", ++ FAN_DIV_FROM_REG(data->fan_status[nr]), ++ nr+1); ++#endif ++ } ++ } ++ ++ /* Write new fan min if it changed */ ++ if (old_min != data->fan_min[nr]) { ++ pc87360_write_value(data, LD_FAN, NO_BANK, ++ PC87360_REG_FAN_MIN(nr), ++ data->fan_min[nr]); ++ } ++} ++ ++static void pc87360_update_client(struct i2c_client *client) ++{ ++ struct pc87360_data *data = client->data; ++ u8 i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ * 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "pc87360.o: Data update\n"); ++#endif ++ ++ /* Fans */ ++ for (i = 0; i < data->fannr; i++) { ++ if (FAN_CONFIG_MONITOR(data->fan_conf, i)) { ++ data->fan_status[i] = pc87360_read_value(data, ++ LD_FAN, NO_BANK, ++ PC87360_REG_FAN_STATUS(i)); ++ data->fan[i] = pc87360_read_value(data, LD_FAN, ++ NO_BANK, PC87360_REG_FAN(i)); ++ data->fan_min[i] = pc87360_read_value(data, ++ LD_FAN, NO_BANK, ++ PC87360_REG_FAN_MIN(i)); ++ /* Change clock divider if needed */ ++ pc87360_autodiv(data, i); ++ /* Clear bits and write new divider */ ++ pc87360_write_value(data, LD_FAN, NO_BANK, ++ PC87360_REG_FAN_STATUS(i), ++ data->fan_status[i]); ++ } ++ data->pwm[i] = pc87360_read_value(data, LD_FAN, ++ NO_BANK, PC87360_REG_PWM(i)); ++ } ++ ++ /* Voltages */ ++ for (i = 0; i < data->innr; i++) { ++ data->in_status[i] = pc87360_read_value(data, LD_IN, i, ++ PC87365_REG_IN_STATUS); ++ /* Clear bits */ ++ pc87360_write_value(data, LD_IN, i, ++ PC87365_REG_IN_STATUS, ++ data->in_status[i]); ++ if ((data->in_status[i] & 0x81) == 0x81) { ++ data->in[i] = pc87360_read_value(data, LD_IN, ++ i, PC87365_REG_IN); ++ } ++ if (data->in_status[i] & 0x01) { ++ data->in_min[i] = pc87360_read_value(data, ++ LD_IN, i, ++ PC87365_REG_IN_MIN); ++ data->in_max[i] = pc87360_read_value(data, ++ LD_IN, i, ++ PC87365_REG_IN_MAX); ++ if (i >= 11) ++ data->in_crit[i-11] = ++ pc87360_read_value(data, LD_IN, ++ i, PC87365_REG_TEMP_CRIT); ++ } ++ } ++ if (data->innr) { ++ data->in_alarms = pc87360_read_value(data, LD_IN, ++ NO_BANK, PC87365_REG_IN_ALARMS1) ++ | ((pc87360_read_value(data, LD_IN, ++ NO_BANK, PC87365_REG_IN_ALARMS2) ++ & 0x07) << 8); ++ data->vid = (data->vid_conf & 0xE0) ? ++ pc87360_read_value(data, LD_IN, ++ NO_BANK, PC87365_REG_VID) : 0x1F; ++ } ++ ++ /* Temperatures */ ++ for (i = 0; i < data->tempnr; i++) { ++ data->temp_status[i] = pc87360_read_value(data, ++ LD_TEMP, i, ++ PC87365_REG_TEMP_STATUS); ++ /* Clear bits */ ++ pc87360_write_value(data, LD_TEMP, i, ++ PC87365_REG_TEMP_STATUS, ++ data->temp_status[i]); ++ if ((data->temp_status[i] & 0x81) == 0x81) { ++ data->temp[i] = pc87360_read_value(data, ++ LD_TEMP, i, ++ PC87365_REG_TEMP); ++ } ++ if (data->temp_status[i] & 0x01) { ++ data->temp_min[i] = pc87360_read_value(data, ++ LD_TEMP, i, ++ PC87365_REG_TEMP_MIN); ++ data->temp_max[i] = pc87360_read_value(data, ++ LD_TEMP, i, ++ PC87365_REG_TEMP_MAX); ++ data->temp_crit[i] = pc87360_read_value(data, ++ LD_TEMP, i, ++ PC87365_REG_TEMP_CRIT); ++ } ++ } ++ if (data->tempnr) { ++ data->temp_alarms = pc87360_read_value(data, LD_TEMP, ++ NO_BANK, PC87365_REG_TEMP_ALARMS) ++ & 0x3F; ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void pc87365_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = data->in_alarms; ++ results[1] = data->temp_alarms; ++ *nrels_mag = 2; ++ } ++} ++ ++void pc87360_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87360_SYSCTL_FAN1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr], ++ FAN_DIV_FROM_REG(data->fan_status[nr])); ++ results[1] = FAN_FROM_REG(data->fan[nr], ++ FAN_DIV_FROM_REG(data->fan_status[nr])); ++ *nrels_mag = 2; ++ } ++ /* We ignore National's recommendation */ ++ else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (nr >= data->fannr) ++ return; ++ if (*nrels_mag >= 1) { ++ int fan_min = FAN_TO_REG(results[0], ++ FAN_DIV_FROM_REG(data->fan_status[nr])); ++ /* If it wouldn't fit, change clock divisor */ ++ while (fan_min > 255 ++ && (data->fan_status[nr] & 0x60) != 0x60) { ++ fan_min >>= 1; ++ data->fan[nr] >>= 1; ++ data->fan_status[nr] += 0x20; ++ } ++ data->fan_min[nr] = fan_min > 255 ? 255 : fan_min; ++ pc87360_write_value(data, LD_FAN, NO_BANK, ++ PC87360_REG_FAN_MIN(nr), ++ data->fan_min[nr]); ++ /* Write new divider, preserve alarm bits */ ++ pc87360_write_value(data, LD_FAN, NO_BANK, ++ PC87360_REG_FAN_STATUS(nr), ++ data->fan_status[nr] & 0xF9); ++ } ++ } ++} ++ ++void pc87360_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int i; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ for (i = 0; i < data->fannr; i++) { ++ results[i] = FAN_DIV_FROM_REG(data->fan_status[i]); ++ } ++ for (; i < 3; i++) { ++ results[i] = 0; ++ } ++ *nrels_mag = 3; ++ } ++} ++ ++void pc87360_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87360_SYSCTL_PWM1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm[nr], ++ FAN_CONFIG_INVERT(data->fan_conf, nr)); ++ results[1] = FAN_CONFIG_CONTROL(data->fan_conf, nr); ++ *nrels_mag = 2; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (nr >= data->fannr) ++ return; ++ if (*nrels_mag >= 1) { ++ data->pwm[nr] = PWM_TO_REG(results[0], ++ FAN_CONFIG_INVERT(data->fan_conf, nr)); ++ pc87360_write_value(data, LD_FAN, NO_BANK, ++ PC87360_REG_PWM(nr), ++ data->pwm[nr]); ++ } ++ } ++} ++ ++void pc87360_fan_status(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87360_SYSCTL_FAN1_STATUS; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = FAN_STATUS_FROM_REG(data->fan_status[nr]); ++ *nrels_mag = 1; ++ } ++} ++ ++void pc87365_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87365_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr], data->in_vref); ++ results[1] = IN_FROM_REG(data->in_max[nr], data->in_vref); ++ if (nr < 11) { ++ *nrels_mag = 3; ++ } else { ++ results[2] = IN_FROM_REG(data->in_crit[nr-11], ++ data->in_vref); ++ *nrels_mag = 4; ++ } ++ results[(*nrels_mag)-1] = IN_FROM_REG(data->in[nr], ++ data->in_vref); ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0], ++ data->in_vref); ++ pc87360_write_value(data, LD_IN, nr, ++ PC87365_REG_IN_MIN, ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1], ++ data->in_vref); ++ pc87360_write_value(data, LD_IN, nr, ++ PC87365_REG_IN_MAX, ++ data->in_max[nr]); ++ } ++ if (*nrels_mag >= 3 && nr >= 11) { ++ data->in_crit[nr-11] = IN_TO_REG(results[2], ++ data->in_vref); ++ pc87360_write_value(data, LD_IN, nr, ++ PC87365_REG_TEMP_CRIT, ++ data->in_crit[nr-11]); ++ } ++ } ++} ++ ++void pc87365_in_status(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87365_SYSCTL_IN0_STATUS; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = data->in_status[nr]; ++ *nrels_mag = 1; ++ } ++} ++ ++void pc87365_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = vid_from_reg(data->vid & 0x1f, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void pc87365_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++void pc87365_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87365_SYSCTL_TEMP1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_max[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_min[nr]); ++ results[2] = TEMP_FROM_REG(data->temp_crit[nr]); ++ results[3] = TEMP_FROM_REG(data->temp[nr]); ++ *nrels_mag = 4; ++ } ++ else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (nr >= data->tempnr) ++ return; ++ if (*nrels_mag >= 1) { ++ data->temp_max[nr] = TEMP_TO_REG(results[0]); ++ pc87360_write_value(data, LD_TEMP, nr, ++ PC87365_REG_TEMP_MAX, ++ data->temp_max[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_min[nr] = TEMP_TO_REG(results[1]); ++ pc87360_write_value(data, LD_TEMP, nr, ++ PC87365_REG_TEMP_MIN, ++ data->temp_min[nr]); ++ } ++ if (*nrels_mag >= 3) { ++ data->temp_crit[nr] = TEMP_TO_REG(results[2]); ++ pc87360_write_value(data, LD_TEMP, nr, ++ PC87365_REG_TEMP_CRIT, ++ data->temp_crit[nr]); ++ } ++ } ++} ++ ++void pc87365_temp_status(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pc87360_data *data = client->data; ++ int nr = ctl_name - PC87365_SYSCTL_TEMP1_STATUS; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pc87360_update_client(client); ++ results[0] = data->temp_status[nr]; ++ *nrels_mag = 1; ++ } ++} ++ ++ ++static int __init pc87360_init(void) ++{ ++ int i; ++ ++ printk(KERN_INFO "pc87360.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ if (pc87360_find(&devid, extra_isa)) { ++ printk(KERN_WARNING "pc87360.o: PC8736x not detected, " ++ "module not inserted.\n"); ++ return -ENODEV; ++ } ++ ++ /* Arbitrarily pick one of the addresses */ ++ for (i = 0; i < 3; i++) { ++ if (extra_isa[i] != 0x0000) { ++ normal_isa[0] = extra_isa[i]; ++ break; ++ } ++ } ++ ++ if (normal_isa[0] == 0x0000) { ++ printk(KERN_WARNING "pc87360.o: No active logical device, " ++ "module not inserted.\n"); ++ return -ENODEV; ++ ++ } ++ ++ return i2c_add_driver(&pc87360_driver); ++} ++ ++static void __exit pc87360_exit(void) ++{ ++ i2c_del_driver(&pc87360_driver); ++} ++ ++ ++MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); ++MODULE_DESCRIPTION("PC8736x hardware monitor"); ++MODULE_LICENSE("GPL"); ++ ++module_init(pc87360_init); ++module_exit(pc87360_exit); +--- linux-old/drivers/sensors/pcf8574.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/pcf8574.c Mon Dec 13 20:18:51 2004 +@@ -0,0 +1,309 @@ ++/* ++ pcf8574.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2000 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ Dan Eaton <dan.eaton@rocketlogix.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* A few notes about the PCF8574: ++ ++* The PCF8574 is an 8-bit I/O expander for the I2C bus produced by ++ Philips Semiconductors. It is designed to provide a byte I2C ++ interface to up to 8 separate devices. ++ ++* The PCF8574 appears as a very simple SMBus device which can be ++ read from or written to with SMBUS byte read/write accesses. ++ ++* Because of the general purpose nature of this device, it will most ++ likely be necessary to customize the /proc interface to suit the ++ specific application. ++ ++ --Dan ++ ++*/ ++ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x20, 0x27, 0x38, 0x3f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_2(pcf8574, pcf8574a); ++ ++/* The PCF8574 registers */ ++ ++/* (No registers. [Wow! This thing is SIMPLE!] ) */ ++ ++/* Initial values */ ++#define PCF8574_INIT 255 /* All outputs on (input mode) */ ++ ++/* Each client has this additional data */ ++struct pcf8574_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ ++ u8 read, write; /* Register values */ ++}; ++ ++static int pcf8574_attach_adapter(struct i2c_adapter *adapter); ++static int pcf8574_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int pcf8574_detach_client(struct i2c_client *client); ++ ++static void pcf8574_read(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pcf8574_write(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pcf8574_update_client(struct i2c_client *client); ++static void pcf8574_init_client(struct i2c_client *client); ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver pcf8574_driver = { ++ .owner = THIS_MODULE, ++ .name = "PCF8574 sensor chip driver", ++ .id = I2C_DRIVERID_PCF8574, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = pcf8574_attach_adapter, ++ .detach_client = pcf8574_detach_client, ++}; ++ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define PCF8574_SYSCTL_READ 1000 ++#define PCF8574_SYSCTL_WRITE 1001 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected PCF8574. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table pcf8574_dir_table_template[] = { ++ {PCF8574_SYSCTL_READ, "read", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8574_read}, ++ {PCF8574_SYSCTL_WRITE, "write", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8574_write}, ++ {0} ++}; ++ ++static int pcf8574_id = 0; ++ ++static int pcf8574_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, pcf8574_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int pcf8574_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct pcf8574_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("pcf8574.o: pcf8574_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. */ ++ if (!(data = kmalloc(sizeof(struct pcf8574_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &pcf8574_driver; ++ new_client->flags = 0; ++ ++ /* Now, we would do the remaining detection. But the PCF8574 is plainly ++ impossible to detect! Stupid chip. */ ++ ++ /* Determine the chip type */ ++ if (kind <= 0) { ++ if (address >= 0x38 && address <= 0x3f) ++ kind = pcf8574a; ++ else ++ kind = pcf8574; ++ } ++ ++ if (kind == pcf8574a) { ++ type_name = "pcf8574a"; ++ client_name = "PCF8574A chip"; ++ } else { ++ type_name = "pcf8574a"; ++ client_name = "PCF8574A chip"; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = pcf8574_id++; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR1; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ pcf8574_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR2; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the PCF8574 chip */ ++ pcf8574_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int pcf8574_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct pcf8574_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk("pcf8574.o: Client deregistration failed, " ++ "client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* Called when we have found a new PCF8574. */ ++static void pcf8574_init_client(struct i2c_client *client) ++{ ++ struct pcf8574_data *data = client->data; ++ data->write = PCF8574_INIT; ++ i2c_smbus_write_byte(client, data->write); ++} ++ ++ ++static void pcf8574_update_client(struct i2c_client *client) ++{ ++ struct pcf8574_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++#ifdef DEBUG ++ printk("Starting pcf8574 update\n"); ++#endif ++ ++ data->read = i2c_smbus_read_byte(client); ++ ++ up(&data->update_lock); ++} ++ ++ ++void pcf8574_read(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct pcf8574_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pcf8574_update_client(client); ++ results[0] = data->read; ++ *nrels_mag = 1; ++ } ++} ++void pcf8574_write(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct pcf8574_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->write; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->write = results[0]; ++ i2c_smbus_write_byte(client, data->write); ++ } ++ } ++} ++ ++ ++static int __init sm_pcf8574_init(void) ++{ ++ printk("pcf8574.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&pcf8574_driver); ++} ++ ++static void __exit sm_pcf8574_exit(void) ++{ ++ i2c_del_driver(&pcf8574_driver); ++} ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "Dan Eaton <dan.eaton@rocketlogix.com> and " ++ "Aurelien Jarno <aurelien@aurel32.net>"); ++MODULE_DESCRIPTION("PCF8574 driver"); ++ ++module_init(sm_pcf8574_init); ++module_exit(sm_pcf8574_exit); +--- linux-old/drivers/sensors/pcf8591.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/pcf8591.c Mon Dec 13 20:18:51 2004 +@@ -0,0 +1,448 @@ ++/* ++ pcf8591.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2001 Aurelien Jarno <aurelien@aurel32.net> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(pcf8591); ++ ++/* The PCF8591 control byte */ ++/* 7 6 5 4 3 2 1 0 */ ++/* | 0 |AOEF| AIP | 0 |AINC| AICH | */ ++ ++#define PCF8591_CONTROL_BYTE_AOEF 0x40 /* Analog Output Enable Flag */ ++ /* (analog output active if 1) */ ++ ++#define PCF8591_CONTROL_BYTE_AIP 0x30 /* Analog Input Programming */ ++ /* 0x00 = four single ended inputs */ ++ /* 0x10 = three differential inputs */ ++ /* 0x20 = single ended and differential mixed */ ++ /* 0x30 = two differential inputs */ ++ ++#define PCF8591_CONTROL_BYTE_AINC 0x04 /* Autoincrement Flag */ ++ /* (switch on if 1) */ ++ ++#define PCF8591_CONTROL_BYTE_AICH 0x03 /* Analog Output Enable Flag */ ++ /* 0x00 = channel 0 */ ++ /* 0x01 = channel 1 */ ++ /* 0x02 = channel 2 */ ++ /* 0x03 = channel 3 */ ++ ++ ++/* Initial values */ ++#define PCF8591_INIT_CONTROL_BYTE (PCF8591_CONTROL_BYTE_AOEF | PCF8591_CONTROL_BYTE_AINC) ++ /* DAC out enabled, four single ended inputs, autoincrement */ ++ ++#define PCF8591_INIT_AOUT 0 /* DAC out = 0 */ ++ ++ ++/* Conversions. */ ++#define REG_TO_SIGNED(reg) (reg & 0x80)?(reg - 256):(reg) ++ /* Convert signed 8 bit value to signed value */ ++ ++ ++struct pcf8591_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 control_byte; ++ u8 ch[4]; ++ u8 aout; ++}; ++ ++static int pcf8591_attach_adapter(struct i2c_adapter *adapter); ++static int pcf8591_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int pcf8591_detach_client(struct i2c_client *client); ++ ++static void pcf8591_update_client(struct i2c_client *client); ++static void pcf8591_init_client(struct i2c_client *client); ++ ++static void pcf8591_ain_conf(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pcf8591_ain(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pcf8591_aout_enable(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void pcf8591_aout(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver pcf8591_driver = { ++ .owner = THIS_MODULE, ++ .name = "PCF8591 sensor chip driver", ++ .id = I2C_DRIVERID_PCF8591, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = pcf8591_attach_adapter, ++ .detach_client = pcf8591_detach_client, ++}; ++ ++static int pcf8591_id = 0; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define PCF8591_SYSCTL_AIN_CONF 1000 /* Analog input configuration */ ++#define PCF8591_SYSCTL_CH0 1001 /* Input channel 1 */ ++#define PCF8591_SYSCTL_CH1 1002 /* Input channel 2 */ ++#define PCF8591_SYSCTL_CH2 1003 /* Input channel 3 */ ++#define PCF8591_SYSCTL_CH3 1004 /* Input channel 4 */ ++#define PCF8591_SYSCTL_AOUT_ENABLE 1005 /* Analog output enable flag */ ++#define PCF8591_SYSCTL_AOUT 1006 /* Analog output */ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected PCF8591. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table pcf8591_dir_table_template[] = { ++ {PCF8591_SYSCTL_AIN_CONF, "ain_conf", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_ain_conf}, ++ {PCF8591_SYSCTL_CH0, "ch0", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_ain}, ++ {PCF8591_SYSCTL_CH1, "ch1", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_ain}, ++ {PCF8591_SYSCTL_CH2, "ch2", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_ain}, ++ {PCF8591_SYSCTL_CH3, "ch3", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_ain}, ++ {PCF8591_SYSCTL_AOUT_ENABLE, "aout_enable", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_aout_enable}, ++ {PCF8591_SYSCTL_AOUT, "aout", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &pcf8591_aout}, ++ {0} ++}; ++ ++ ++/* This function is called when: ++ * pcf8591_driver is inserted (when this module is loaded), for each ++ available adapter ++ * when a new adapter is inserted (and pcf8591_driver is still present) */ ++static int pcf8591_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, pcf8591_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int pcf8591_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct pcf8591_data *data; ++ int err = 0; ++ ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ (KERN_ERR "pcf8591.o: pcf8591_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE ++ | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. */ ++ if (!(data = kmalloc(sizeof(struct pcf8591_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &pcf8591_driver; ++ new_client->flags = 0; ++ ++ /* Now, we would do the remaining detection. But the PCF8591 is plainly ++ impossible to detect! Stupid chip. */ ++ ++ /* Determine the chip type - only one kind supported! */ ++ if (kind <= 0) ++ kind = pcf8591; ++ ++ type_name = "pcf8591"; ++ client_name = "PCF8591 chip"; ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = pcf8591_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ pcf8591_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the PCF8591 chip */ ++ pcf8591_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int pcf8591_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct pcf8591_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ (KERN_ERR "pcf8591.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* Called when we have found a new PCF8591. */ ++static void pcf8591_init_client(struct i2c_client *client) ++{ ++ struct pcf8591_data *data = client->data; ++ data->control_byte = PCF8591_INIT_CONTROL_BYTE; ++ data->aout = PCF8591_INIT_AOUT; ++ ++ i2c_smbus_write_byte_data(client, data->control_byte, data->aout); ++} ++ ++static void pcf8591_update_client(struct i2c_client *client) ++{ ++ struct pcf8591_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk(KERN_DEBUG "Starting pcf8591 update\n"); ++#endif ++ ++ i2c_smbus_write_byte(client, data->control_byte); ++ i2c_smbus_read_byte(client); /* The first byte transmitted contains the */ ++ /* conversion code of the previous read cycled */ ++ /* FLUSH IT ! */ ++ ++ ++ /* Number of byte to read to signed depend on the analog input mode */ ++ data->ch[0] = i2c_smbus_read_byte(client); ++ data->ch[1] = i2c_smbus_read_byte(client); ++ /* In all case, read at least two values */ ++ ++ if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) != 0x30) ++ data->ch[2] = i2c_smbus_read_byte(client); ++ /* Read the third value if not in "two differential inputs" mode */ ++ ++ if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0x00) ++ data->ch[3] = i2c_smbus_read_byte(client); ++ /* Read the fourth value only in "four single ended inputs" mode */ ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. */ ++void pcf8591_ain_conf(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pcf8591_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = (data->control_byte & PCF8591_CONTROL_BYTE_AIP) >> 4; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (results[0] >= 0 && results[0] <= 3) ++ { ++ data->control_byte &= ~PCF8591_CONTROL_BYTE_AIP; ++ data->control_byte |= (results[0] << 4); ++ i2c_smbus_write_byte(client, data->control_byte); ++ data->valid = 0; ++ } ++ } ++ } ++} ++ ++void pcf8591_ain(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pcf8591_data *data = client->data; ++ int nr = ctl_name - PCF8591_SYSCTL_CH0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ pcf8591_update_client(client); ++ ++ /* Number of data to show and conversion to signed depend on */ ++ /* the analog input mode */ ++ ++ switch(nr) { ++ case 0: ++ if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) ++ | ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2)) ++ results[0] = data->ch[0]; /* single ended */ ++ else ++ results[0] = REG_TO_SIGNED(data->ch[0]);/* differential */ ++ break; ++ case 1: ++ if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) ++ | ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2)) ++ results[0] = data->ch[1]; /* single ended */ ++ else ++ results[0] = REG_TO_SIGNED(data->ch[1]);/* differential */ ++ break; ++ case 2: ++ if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 3) ++ results[0] = 0; /* channel not used */ ++ else if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) ++ results[0] = data->ch[2]; /* single ended */ ++ else ++ results[0] = REG_TO_SIGNED(data->ch[2]);/* differential */ ++ break; ++ case 3: ++ if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) ++ | ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2)) ++ results[0] = data->ch[3]; /* single ended */ ++ else ++ results[0] = 0; /* channel not used */ ++ break; ++ } ++ *nrels_mag = 1; ++ } ++} ++ ++void pcf8591_aout_enable(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pcf8591_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = !(!(data->control_byte & PCF8591_CONTROL_BYTE_AOEF)); ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (results[0]) ++ data->control_byte |= PCF8591_CONTROL_BYTE_AOEF; ++ else ++ data->control_byte &= ~PCF8591_CONTROL_BYTE_AOEF; ++ ++ i2c_smbus_write_byte(client, data->control_byte); ++ } ++ } ++} ++ ++void pcf8591_aout(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct pcf8591_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->aout; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (results[0] >= 0 && results[0] <= 255) /* ignore values outside DAC range */ ++ { ++ data->aout = results[0]; ++ i2c_smbus_write_byte_data(client, data->control_byte, data->aout); ++ } ++ } ++ } ++} ++ ++static int __init sm_pcf8591_init(void) ++{ ++ printk(KERN_INFO "pcf8591.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&pcf8591_driver); ++} ++ ++static void __exit sm_pcf8591_exit(void) ++{ ++ i2c_del_driver(&pcf8591_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Aurelien Jarno <aurelien@aurel32.net>"); ++MODULE_DESCRIPTION("PCF8591 driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_pcf8591_init); ++module_exit(sm_pcf8591_exit); +--- linux-old/drivers/sensors/sis5595.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/sis5595.c Mon Dec 13 20:18:52 2004 +@@ -0,0 +1,735 @@ ++/* ++ sis5595.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ Copyright (c) 1998 - 2001 Frodo Looijaard <frodol@dds.nl>, ++ Kyösti Mälkki <kmalkki@cc.hut.fi>, and ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Supports following revisions: ++ Version PCI ID PCI Revision ++ 1 1039/0008 AF or less ++ 2 1039/0008 B0 or greater ++ ++ Note: these chips contain a 0008 device which is incompatible with the ++ 5595. We recognize these by the presence of the listed ++ "blacklist" PCI ID and refuse to load. ++ ++ NOT SUPPORTED PCI ID BLACKLIST PCI ID ++ 540 0008 0540 ++ 550 0008 0550 ++ 5513 0008 5511 ++ 5581 0008 5597 ++ 5582 0008 5597 ++ 5597 0008 5597 ++ 5598 0008 5597/5598 ++ 630 0008 0630 ++ 645 0008 0645 ++ 730 0008 0730 ++ 735 0008 0735 ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/pci.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the device at the given address. */ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the sensors"); ++ ++/* Addresses to scan. ++ Note that we can't determine the ISA address until we have initialized ++ our module */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(sis5595); ++ ++static int blacklist[] = { ++ PCI_DEVICE_ID_SI_540, ++ PCI_DEVICE_ID_SI_550, ++ PCI_DEVICE_ID_SI_630, ++ PCI_DEVICE_ID_SI_730, ++ PCI_DEVICE_ID_SI_5511, /* 5513 chip has the 0008 device but ++ that ID shows up in other chips so we ++ use the 5511 ID for recognition */ ++ PCI_DEVICE_ID_SI_5597, ++ PCI_DEVICE_ID_SI_5598, ++ 0x645, ++ 0x735, ++ 0 }; ++/* ++ SiS southbridge has a LM78-like chip integrated on the same IC. ++ This driver is a customized copy of lm78.c ++*/ ++ ++/* Many SIS5595 constants specified below */ ++ ++/* Length of ISA address segment */ ++#define SIS5595_EXTENT 8 ++/* PCI Config Registers */ ++#define SIS5595_REVISION_REG 0x08 ++#define SIS5595_BASE_REG 0x68 ++#define SIS5595_PIN_REG 0x7A ++#define SIS5595_ENABLE_REG 0x7B ++ ++/* Where are the ISA address/data registers relative to the base address */ ++#define SIS5595_ADDR_REG_OFFSET 5 ++#define SIS5595_DATA_REG_OFFSET 6 ++ ++/* The SIS5595 registers */ ++#define SIS5595_REG_IN_MAX(nr) (0x2b + (nr) * 2) ++#define SIS5595_REG_IN_MIN(nr) (0x2c + (nr) * 2) ++#define SIS5595_REG_IN(nr) (0x20 + (nr)) ++ ++#define SIS5595_REG_FAN_MIN(nr) (0x3a + (nr)) ++#define SIS5595_REG_FAN(nr) (0x27 + (nr)) ++ ++/* On the first version of the chip, the temp registers are separate. ++ On the second version, ++ TEMP pin is shared with IN4, configured in PCI register 0x7A. ++ The registers are the same as well. ++ OVER and HYST are really MAX and MIN. */ ++ ++#define REV2MIN 0xb0 ++#define SIS5595_REG_TEMP (( data->revision) >= REV2MIN) ? \ ++ SIS5595_REG_IN(4) : 0x27 ++#define SIS5595_REG_TEMP_OVER (( data->revision) >= REV2MIN) ? \ ++ SIS5595_REG_IN_MAX(4) : 0x39 ++#define SIS5595_REG_TEMP_HYST (( data->revision) >= REV2MIN) ? \ ++ SIS5595_REG_IN_MIN(4) : 0x3a ++ ++#define SIS5595_REG_CONFIG 0x40 ++#define SIS5595_REG_ALARM1 0x41 ++#define SIS5595_REG_ALARM2 0x42 ++#define SIS5595_REG_FANDIV 0x47 ++ ++/* Conversions. Limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) ++#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) ++ ++/* Version 1 datasheet temp=.83*reg + 52.12 */ ++#define TEMP_FROM_REG(val) (((((val)>=0x80?(val)-0x100:(val))*83)+5212)/10) ++/* inverse 1.20*val - 62.77 */ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?\ ++ ((((val)*12)-6327)/100):\ ++ ((((val)*12)-6227)/100)),0,255)) ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++ ++/* For the SIS5595, we need to keep some data in memory. That ++ data is pointed to by sis5595_list[NR]->data. The structure itself is ++ dynamically allocated, at the time when the new sis5595 client is ++ allocated. */ ++struct sis5595_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ char maxins; /* == 3 if temp enabled, otherwise == 4 */ ++ u8 revision; /* Reg. value */ ++ ++ u8 in[5]; /* Register value */ ++ u8 in_max[5]; /* Register value */ ++ u8 in_min[5]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 temp; /* Register value */ ++ u8 temp_over; /* Register value - really max */ ++ u8 temp_hyst; /* Register value - really min */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u16 alarms; /* Register encoding, combined */ ++}; ++ ++static struct pci_dev *s_bridge; /* pointer to the (only) sis5595 */ ++ ++static int sis5595_attach_adapter(struct i2c_adapter *adapter); ++static int sis5595_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int sis5595_detach_client(struct i2c_client *client); ++ ++static int sis5595_read_value(struct i2c_client *client, u8 register); ++static int sis5595_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void sis5595_update_client(struct i2c_client *client); ++static void sis5595_init_client(struct i2c_client *client); ++static int sis5595_find_sis(int *address); ++ ++ ++static void sis5595_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void sis5595_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void sis5595_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void sis5595_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void sis5595_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int sis5595_id = 0; ++ ++/* The driver. I choose to use type i2c_driver, as at is identical to both ++ smbus_driver and isa_driver, and clients could be of either kind */ ++static struct i2c_driver sis5595_driver = { ++ .owner = THIS_MODULE, ++ .name = "SiS 5595", ++ .id = I2C_DRIVERID_SIS5595, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = sis5595_attach_adapter, ++ .detach_client = sis5595_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define SIS5595_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define SIS5595_SYSCTL_IN1 1001 ++#define SIS5595_SYSCTL_IN2 1002 ++#define SIS5595_SYSCTL_IN3 1003 ++#define SIS5595_SYSCTL_IN4 1004 ++#define SIS5595_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define SIS5595_SYSCTL_FAN2 1102 ++#define SIS5595_SYSCTL_TEMP 1200 /* Degrees Celcius * 10 */ ++#define SIS5595_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define SIS5595_SYSCTL_ALARMS 2001 /* bitvector */ ++ ++#define SIS5595_ALARM_IN0 0x01 ++#define SIS5595_ALARM_IN1 0x02 ++#define SIS5595_ALARM_IN2 0x04 ++#define SIS5595_ALARM_IN3 0x08 ++#define SIS5595_ALARM_BTI 0x20 ++#define SIS5595_ALARM_FAN1 0x40 ++#define SIS5595_ALARM_FAN2 0x80 ++#define SIS5595_ALARM_IN4 0x8000 ++#define SIS5595_ALARM_TEMP 0x8000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected SIS5595. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table sis5595_dir_table_template[] = { ++ {SIS5595_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_in}, ++ {SIS5595_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_in}, ++ {SIS5595_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_in}, ++ {SIS5595_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_in}, ++ {SIS5595_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_in}, ++ {SIS5595_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_fan}, ++ {SIS5595_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_fan}, ++ {SIS5595_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_temp}, ++ {SIS5595_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_fan_div}, ++ {SIS5595_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &sis5595_alarms}, ++ {0} ++}; ++ ++/* This is called when the module is loaded */ ++static int sis5595_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, sis5595_detect); ++} ++ ++/* Locate SiS bridge and correct base address for SIS5595 */ ++static int sis5595_find_sis(int *address) ++{ ++ u16 val; ++ int *i; ++ ++ if (!pci_present()) ++ return -ENODEV; ++ ++ if (!(s_bridge = ++ pci_find_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503, ++ NULL))) ++ return -ENODEV; ++ ++ /* Look for imposters */ ++ for(i = blacklist; *i != 0; i++) { ++ if (pci_find_device(PCI_VENDOR_ID_SI, *i, NULL)) { ++ printk("sis5595.o: Error: Looked for SIS5595 but found unsupported device %.4X\n", *i); ++ return -ENODEV; ++ } ++ } ++ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(s_bridge, SIS5595_BASE_REG, &val)) ++ return -ENODEV; ++ ++ *address = val & ~(SIS5595_EXTENT - 1); ++ if (*address == 0 && force_addr == 0) { ++ printk("sis5595.o: base address not set - upgrade BIOS or use force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ if (force_addr) ++ *address = force_addr; /* so detect will get called */ ++ ++ return 0; ++} ++ ++int sis5595_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct sis5595_data *data; ++ int err = 0; ++ const char *type_name = "sis5595"; ++ const char *client_name = "SIS5595 chip"; ++ char val; ++ u16 a; ++ ++ /* Make sure we are probing the ISA bus!! */ ++ if (!i2c_is_isa_adapter(adapter)) { ++ printk ++ ("sis5595.o: sis5595_detect called for an I2C bus adapter?!?\n"); ++ return 0; ++ } ++ ++ if(force_addr) ++ address = force_addr & ~(SIS5595_EXTENT - 1); ++ if (check_region(address, SIS5595_EXTENT)) { ++ printk("sis5595.o: region 0x%x already in use!\n", address); ++ return -ENODEV; ++ } ++ if(force_addr) { ++ printk("sis5595.o: forcing ISA address 0x%04X\n", address); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(s_bridge, SIS5595_BASE_REG, address)) ++ return -ENODEV; ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(s_bridge, SIS5595_BASE_REG, &a)) ++ return -ENODEV; ++ if ((a & ~(SIS5595_EXTENT - 1)) != address) { ++ /* doesn't work for some chips? */ ++ printk("sis5595.o: force address failed\n"); ++ return -ENODEV; ++ } ++ } ++ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_byte(s_bridge, SIS5595_ENABLE_REG, &val)) ++ return -ENODEV; ++ if((val & 0x80) == 0) { ++ printk("sis5595.o: enabling sensors\n"); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_byte(s_bridge, SIS5595_ENABLE_REG, ++ val | 0x80)) ++ return -ENODEV; ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_byte(s_bridge, SIS5595_ENABLE_REG, &val)) ++ return -ENODEV; ++ if((val & 0x80) == 0) { /* doesn't work for some chips! */ ++ printk("sis5595.o: sensors enable failed - not supported?\n"); ++ return -ENODEV; ++ } ++ } ++ ++ if (!(data = kmalloc(sizeof(struct sis5595_data), GFP_KERNEL))) { ++ return -ENOMEM; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &sis5595_driver; ++ new_client->flags = 0; ++ ++ /* Reserve the ISA region */ ++ request_region(address, SIS5595_EXTENT, type_name); ++ ++ /* Check revision and pin registers to determine whether 3 or 4 voltages */ ++ pci_read_config_byte(s_bridge, SIS5595_REVISION_REG, &(data->revision)); ++ if(data->revision < REV2MIN) { ++ data->maxins = 3; ++ } else { ++ pci_read_config_byte(s_bridge, SIS5595_PIN_REG, &val); ++ if(val & 0x80) ++ /* 3 voltages, 1 temp */ ++ data->maxins = 3; ++ else ++ /* 4 voltages, no temps */ ++ data->maxins = 4; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = sis5595_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, ++ sis5595_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the SIS5595 chip */ ++ sis5595_init_client(new_client); ++ return 0; ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ release_region(address, SIS5595_EXTENT); ++ kfree(data); ++ return err; ++} ++ ++static int sis5595_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct sis5595_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("sis5595.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ release_region(client->addr, SIS5595_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* ISA access must be locked explicitly. ++ There are some ugly typecasts here, but the good news is - they should ++ nowhere else be necessary! */ ++static int sis5595_read_value(struct i2c_client *client, u8 reg) ++{ ++ int res; ++ ++ down(&(((struct sis5595_data *) (client->data))->lock)); ++ outb_p(reg, client->addr + SIS5595_ADDR_REG_OFFSET); ++ res = inb_p(client->addr + SIS5595_DATA_REG_OFFSET); ++ up(&(((struct sis5595_data *) (client->data))->lock)); ++ return res; ++} ++ ++static int sis5595_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ down(&(((struct sis5595_data *) (client->data))->lock)); ++ outb_p(reg, client->addr + SIS5595_ADDR_REG_OFFSET); ++ outb_p(value, client->addr + SIS5595_DATA_REG_OFFSET); ++ up(&(((struct sis5595_data *) (client->data))->lock)); ++ return 0; ++} ++ ++/* Called when we have found a new SIS5595. */ ++static void sis5595_init_client(struct i2c_client *client) ++{ ++ u8 reg; ++ ++ /* Start monitoring */ ++ reg = i2c_smbus_read_byte_data(client, SIS5595_REG_CONFIG); ++ sis5595_write_value(client, SIS5595_REG_CONFIG, (reg|0x01)&0x7F); ++} ++ ++static void sis5595_update_client(struct i2c_client *client) ++{ ++ struct sis5595_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++ for (i = 0; i <= data->maxins; i++) { ++ data->in[i] = ++ sis5595_read_value(client, SIS5595_REG_IN(i)); ++ data->in_min[i] = ++ sis5595_read_value(client, ++ SIS5595_REG_IN_MIN(i)); ++ data->in_max[i] = ++ sis5595_read_value(client, ++ SIS5595_REG_IN_MAX(i)); ++ } ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = ++ sis5595_read_value(client, SIS5595_REG_FAN(i)); ++ data->fan_min[i - 1] = ++ sis5595_read_value(client, ++ SIS5595_REG_FAN_MIN(i)); ++ } ++ if(data->maxins == 3) { ++ data->temp = ++ sis5595_read_value(client, SIS5595_REG_TEMP); ++ data->temp_over = ++ sis5595_read_value(client, SIS5595_REG_TEMP_OVER); ++ data->temp_hyst = ++ sis5595_read_value(client, SIS5595_REG_TEMP_HYST); ++ } ++ i = sis5595_read_value(client, SIS5595_REG_FANDIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = i >> 6; ++ data->alarms = ++ sis5595_read_value(client, SIS5595_REG_ALARM1) | ++ (sis5595_read_value(client, SIS5595_REG_ALARM2) << 8); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++ ++/* Return 0 for in4 and disallow writes if pin used for temp */ ++void sis5595_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct sis5595_data *data = client->data; ++ int nr = ctl_name - SIS5595_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ if(nr <= 3 || data->maxins == 4) { ++ sis5595_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ } else { ++ results[0] = 0; ++ results[1] = 0; ++ results[2] = 0; ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if(nr <= 3 || data->maxins == 4) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ sis5595_write_value(client, ++ SIS5595_REG_IN_MIN(nr), data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ sis5595_write_value(client, ++ SIS5595_REG_IN_MAX(nr), data->in_max[nr]); ++ } ++ } ++ } ++} ++ ++void sis5595_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct sis5595_data *data = client->data; ++ int nr = ctl_name - SIS5595_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ sis5595_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr-1])); ++ sis5595_write_value(client, ++ SIS5595_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++/* Return 0 for temp and disallow writes if pin used for in4 */ ++void sis5595_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct sis5595_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ if(data->maxins == 3) { ++ sis5595_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ } else { ++ results[0] = 0; ++ results[1] = 0; ++ results[2] = 0; ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if(data->maxins == 3) { ++ if (*nrels_mag >= 1) { ++ data->temp_over = TEMP_TO_REG(results[0]); ++ sis5595_write_value(client, ++ SIS5595_REG_TEMP_OVER, data->temp_over); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ sis5595_write_value(client, ++ SIS5595_REG_TEMP_HYST, data->temp_hyst); ++ } ++ } ++ } ++} ++ ++void sis5595_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct sis5595_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ sis5595_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void sis5595_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct sis5595_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ sis5595_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = sis5595_read_value(client, SIS5595_REG_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ sis5595_write_value(client, SIS5595_REG_FANDIV, old); ++ } ++ } ++} ++ ++static int __init sm_sis5595_init(void) ++{ ++ int addr; ++ ++ printk("sis5595.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ if (sis5595_find_sis(&addr)) { ++ printk("sis5595.o: SIS5595 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ normal_isa[0] = addr; ++ ++ return i2c_add_driver(&sis5595_driver); ++} ++ ++static void __exit sm_sis5595_exit(void) ++{ ++ i2c_del_driver(&sis5595_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Kyösti Mälkki <kmalkki@cc.hut.fi>"); ++MODULE_DESCRIPTION("SiS 5595 Sensor device"); ++ ++module_init(sm_sis5595_init); ++module_exit(sm_sis5595_exit); +--- linux-old/drivers/sensors/smsc47m1.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/smsc47m1.c Mon Dec 13 20:18:52 2004 +@@ -0,0 +1,515 @@ ++/* ++ smsc47m1.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the sensors"); ++ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++SENSORS_INSMOD_1(smsc47m1); ++ ++/* modified from kernel/include/traps.c */ ++#define REG 0x2e /* The register to read/write */ ++#define DEV 0x07 /* Register: Logical device select */ ++#define VAL 0x2f /* The value to read/write */ ++#define PME 0x0a /* The device with the fan registers in it */ ++#define DEVID 0x20 /* Register: Device ID */ ++ ++static inline void ++superio_outb(int reg, int val) ++{ ++ outb(reg, REG); ++ outb(val, VAL); ++} ++ ++static inline int ++superio_inb(int reg) ++{ ++ outb(reg, REG); ++ return inb(VAL); ++} ++ ++static inline void ++superio_select(void) ++{ ++ outb(DEV, REG); ++ outb(PME, VAL); ++} ++ ++static inline void ++superio_enter(void) ++{ ++ outb(0x55, REG); ++} ++ ++static inline void ++superio_exit(void) ++{ ++ outb(0xAA, REG); ++} ++ ++/* ++ * SMSC LPC47M10x (device id 0x59), LPC47M14x (device id 0x5F) and ++ * LPC47B27x (device id 0x51) have fan control. ++ * The 47M15x and 47M192 chips "with hardware monitoring block" ++ * can do much more besides (device id 0x60). ++ */ ++#define SMSC_DEVID_MATCH(id) ((id) == 0x51 || (id) == 0x59 || (id) == 0x5F) ++ ++#define SMSC_ACT_REG 0x30 ++#define SMSC_BASE_REG 0x60 ++ ++#define SMSC_EXTENT 0x80 ++ ++#define SMSC47M1_REG_ALARM1 0x04 ++#define SMSC47M1_REG_TPIN2 0x33 ++#define SMSC47M1_REG_TPIN1 0x34 ++#define SMSC47M1_REG_PPIN(nr) (0x37 - (nr)) ++#define SMSC47M1_REG_PWM(nr) (0x55 + (nr)) ++#define SMSC47M1_REG_FANDIV 0x58 ++#define SMSC47M1_REG_FAN(nr) (0x58 + (nr)) ++#define SMSC47M1_REG_FAN_MIN(nr) (0x5a + (nr)) ++ ++static inline u8 MIN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 0; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT(192 - ((983040 + rpm * div / 2) / (rpm * div)), ++ 0, 191); ++} ++ ++#define MIN_FROM_REG(val,div) ((val)>=192?0: \ ++ 983040/((192-(val))*(div))) ++#define FAN_FROM_REG(val,div,preload) ((val)==0?-1:(val)==255?0: \ ++ 983040/(((val)-preload)*(div))) ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++/* reg is 6 middle bits; /proc is 8 bits */ ++#define PWM_FROM_REG(val) (((val) << 1) & 0xfc) ++#define PWM_TO_REG(val) (((SENSORS_LIMIT((val), 0, 255)) >> 1) & 0x7e) ++ ++struct smsc47m1_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u8 alarms; /* Register encoding */ ++ u8 pwm[2]; /* Register value (bit 7 is enable) */ ++}; ++ ++ ++static int smsc47m1_attach_adapter(struct i2c_adapter *adapter); ++static int smsc47m1_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int smsc47m1_detach_client(struct i2c_client *client); ++ ++static int smsc47m1_read_value(struct i2c_client *client, u8 register); ++static int smsc47m1_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void smsc47m1_update_client(struct i2c_client *client); ++static void smsc47m1_init_client(struct i2c_client *client); ++static int smsc47m1_find(int *address); ++ ++ ++static void smsc47m1_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void smsc47m1_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void smsc47m1_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void smsc47m1_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int smsc47m1_id = 0; ++ ++static struct i2c_driver smsc47m1_driver = { ++ .owner = THIS_MODULE, ++ .name = "SMSC 47M1xx fan monitor", ++ .id = I2C_DRIVERID_SMSC47M1, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = smsc47m1_attach_adapter, ++ .detach_client = smsc47m1_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define SMSC47M1_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define SMSC47M1_SYSCTL_FAN2 1102 ++#define SMSC47M1_SYSCTL_PWM1 1401 ++#define SMSC47M1_SYSCTL_PWM2 1402 ++#define SMSC47M1_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define SMSC47M1_SYSCTL_ALARMS 2004 /* bitvector */ ++ ++#define SMSC47M1_ALARM_FAN1 0x0001 ++#define SMSC47M1_ALARM_FAN2 0x0002 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++static ctl_table smsc47m1_dir_table_template[] = { ++ {SMSC47M1_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &smsc47m1_fan}, ++ {SMSC47M1_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &smsc47m1_fan}, ++ {SMSC47M1_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &smsc47m1_fan_div}, ++ {SMSC47M1_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &smsc47m1_alarms}, ++ {SMSC47M1_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &smsc47m1_pwm}, ++ {SMSC47M1_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &smsc47m1_pwm}, ++ {0} ++}; ++ ++static int smsc47m1_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, smsc47m1_detect); ++} ++ ++static int smsc47m1_find(int *address) ++{ ++ u16 val; ++ ++ superio_enter(); ++ val= superio_inb(DEVID); ++ if (!SMSC_DEVID_MATCH(val)) { ++ superio_exit(); ++ return -ENODEV; ++ } ++ ++ superio_select(); ++ val = (superio_inb(SMSC_BASE_REG) << 8) | ++ superio_inb(SMSC_BASE_REG + 1); ++ *address = val & ~(SMSC_EXTENT - 1); ++ if (*address == 0 && force_addr == 0) { ++ printk("smsc47m1.o: base address not set - use force_addr=0xaddr\n"); ++ superio_exit(); ++ return -ENODEV; ++ } ++ if (force_addr) ++ *address = force_addr; /* so detect will get called */ ++ ++ superio_exit(); ++ return 0; ++} ++ ++int smsc47m1_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct smsc47m1_data *data; ++ int err = 0; ++ const char *type_name = "smsc47m1"; ++ const char *client_name = "47M1xx chip"; ++ ++ if (!i2c_is_isa_adapter(adapter)) { ++ return 0; ++ } ++ ++ if(force_addr) ++ address = force_addr & ~(SMSC_EXTENT - 1); ++ if (check_region(address, SMSC_EXTENT)) { ++ printk("smsc47m1.o: region 0x%x already in use!\n", address); ++ return -ENODEV; ++ } ++ if(force_addr) { ++ printk("smsc47m1.o: forcing ISA address 0x%04X\n", address); ++ superio_enter(); ++ superio_select(); ++ superio_outb(SMSC_BASE_REG, address >> 8); ++ superio_outb(SMSC_BASE_REG+1, address & 0xff); ++ superio_exit(); ++ } ++ ++ if (!(data = kmalloc(sizeof(struct smsc47m1_data), GFP_KERNEL))) { ++ return -ENOMEM; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &smsc47m1_driver; ++ new_client->flags = 0; ++ ++ request_region(address, SMSC_EXTENT, "smsc47m1-fans"); ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = smsc47m1_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, ++ smsc47m1_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ smsc47m1_init_client(new_client); ++ return 0; ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ release_region(address, SMSC_EXTENT); ++ kfree(data); ++ return err; ++} ++ ++static int smsc47m1_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct smsc47m1_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("smsc47m1.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ release_region(client->addr, SMSC_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static int smsc47m1_read_value(struct i2c_client *client, u8 reg) ++{ ++ int res; ++ ++ down(&(((struct smsc47m1_data *) (client->data))->lock)); ++ res = inb_p(client->addr + reg); ++ up(&(((struct smsc47m1_data *) (client->data))->lock)); ++ return res; ++} ++ ++static int smsc47m1_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ down(&(((struct smsc47m1_data *) (client->data))->lock)); ++ outb_p(value, client->addr + reg); ++ up(&(((struct smsc47m1_data *) (client->data))->lock)); ++ return 0; ++} ++ ++static void smsc47m1_init_client(struct i2c_client *client) ++{ ++ /* configure pins for tach function */ ++ smsc47m1_write_value(client, SMSC47M1_REG_TPIN1, 0x05); ++ smsc47m1_write_value(client, SMSC47M1_REG_TPIN2, 0x05); ++} ++ ++static void smsc47m1_update_client(struct i2c_client *client) ++{ ++ struct smsc47m1_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = ++ smsc47m1_read_value(client, SMSC47M1_REG_FAN(i)); ++ data->fan_min[i - 1] = ++ smsc47m1_read_value(client, SMSC47M1_REG_FAN_MIN(i)); ++ data->pwm[i - 1] = ++ smsc47m1_read_value(client, SMSC47M1_REG_PWM(i)); ++ } ++ ++ i = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = i >> 6; ++ data->alarms = ++ smsc47m1_read_value(client, SMSC47M1_REG_ALARM1) >> 6; ++ if(data->alarms) ++ smsc47m1_write_value(client, SMSC47M1_REG_ALARM1, 0xc0); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void smsc47m1_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct smsc47m1_data *data = client->data; ++ int nr = ctl_name - SMSC47M1_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ smsc47m1_update_client(client); ++ results[0] = MIN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1]), ++ data->fan_min[nr - 1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = MIN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr-1])); ++ smsc47m1_write_value(client, SMSC47M1_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void smsc47m1_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct smsc47m1_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ smsc47m1_update_client(client); ++ results[0] = data->alarms; ++ *nrels_mag = 1; ++ } ++} ++ ++void smsc47m1_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct smsc47m1_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ smsc47m1_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ smsc47m1_write_value(client, SMSC47M1_REG_FANDIV, old); ++ } ++ } ++} ++ ++void smsc47m1_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct smsc47m1_data *data = client->data; ++ int nr = 1 + ctl_name - SMSC47M1_SYSCTL_PWM1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ smsc47m1_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm[nr - 1]); ++ results[1] = data->pwm[nr - 1] & 0x01; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->pwm[nr - 1] &= 0x81; ++ data->pwm[nr - 1] |= PWM_TO_REG(results[0]); ++ if (*nrels_mag >= 2) { ++ if(results[1] && (data->pwm[nr-1] & 0x01)) { ++ /* enable PWM */ ++/* hope BIOS did it already ++ smsc47m1_write_value(client, ++ SMSC47M1_REG_PPIN(nr), 0x04); ++*/ ++ data->pwm[nr - 1] &= 0xfe; ++ } else if((!results[1]) && (!(data->pwm[nr-1] & 0x01))) { ++ /* disable PWM */ ++ data->pwm[nr - 1] |= 0x01; ++ } ++ } ++ smsc47m1_write_value(client, SMSC47M1_REG_PWM(nr), ++ data->pwm[nr - 1]); ++ } ++ } ++} ++ ++static int __init sm_smsc47m1_init(void) ++{ ++ int addr; ++ ++ printk("smsc47m1.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ if (smsc47m1_find(&addr)) { ++ printk("smsc47m1.o: SMSC 47M1xx not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ normal_isa[0] = addr; ++ ++ return i2c_add_driver(&smsc47m1_driver); ++} ++ ++static void __exit sm_smsc47m1_exit(void) ++{ ++ i2c_del_driver(&smsc47m1_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("SMSC 47M1xx Fan sensors"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_smsc47m1_init); ++module_exit(sm_smsc47m1_exit); +--- linux-old/drivers/sensors/thmc50.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/thmc50.c Mon Dec 13 20:18:52 2004 +@@ -0,0 +1,496 @@ ++/* ++ thmc50.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and ++ Philip Edelbrock <phil@netroedge.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++#define DEBUG 1 ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++MODULE_LICENSE("GPL"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x2D, 0x2E, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(thmc50); ++ ++/* Many THMC50 constants specified below */ ++ ++/* The THMC50 registers */ ++#define THMC50_REG_TEMP 0x27 ++#define THMC50_REG_CONF 0x40 ++#define THMC50_REG_TEMP_HYST 0x3A ++#define THMC50_REG_TEMP_OS 0x39 ++ ++#define THMC50_REG_TEMP_TRIP 0x13 ++#define THMC50_REG_TEMP_REMOTE_TRIP 0x14 ++#define THMC50_REG_TEMP_DEFAULT_TRIP 0x17 ++#define THMC50_REG_TEMP_REMOTE_DEFAULT_TRIP 0x18 ++#define THMC50_REG_ANALOG_OUT 0x19 ++#define THMC50_REG_REMOTE_TEMP 0x26 ++#define THMC50_REG_REMOTE_TEMP_HYST 0x38 ++#define THMC50_REG_REMOTE_TEMP_OS 0x37 ++ ++#define THMC50_REG_INTER 0x41 ++#define THMC50_REG_INTER_MIRROR 0x4C ++#define THMC50_REG_INTER_MASK 0x43 ++ ++#define THMC50_REG_COMPANY_ID 0x3E ++#define THMC50_REG_DIE_CODE 0x3F ++ ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define TEMP_FROM_REG(val) ((val>127)?val - 0x0100:val) ++#define TEMP_TO_REG(val) ((val<0)?0x0100+val:val) ++ ++/* Each client has this additional data */ ++struct thmc50_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u16 temp, temp_os, temp_hyst, ++ remote_temp, remote_temp_os, remote_temp_hyst, ++ inter, inter_mask, die_code, analog_out; /* Register values */ ++}; ++ ++static int thmc50_attach_adapter(struct i2c_adapter *adapter); ++static int thmc50_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void thmc50_init_client(struct i2c_client *client); ++static int thmc50_detach_client(struct i2c_client *client); ++ ++static int thmc50_read_value(struct i2c_client *client, u8 reg); ++static int thmc50_write_value(struct i2c_client *client, u8 reg, ++ u16 value); ++static void thmc50_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void thmc50_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, ++ long *results); ++static void thmc50_inter(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void thmc50_inter_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void thmc50_die_code(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void thmc50_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void thmc50_update_client(struct i2c_client *client); ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver thmc50_driver = { ++ .owner = THIS_MODULE, ++ .name = "THMC50 sensor chip driver", ++ .id = I2C_DRIVERID_THMC50, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = thmc50_attach_adapter, ++ .detach_client = thmc50_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define THMC50_SYSCTL_TEMP 1200 /* Degrees Celcius */ ++#define THMC50_SYSCTL_REMOTE_TEMP 1201 /* Degrees Celcius */ ++#define THMC50_SYSCTL_INTER 1202 ++#define THMC50_SYSCTL_INTER_MASK 1203 ++#define THMC50_SYSCTL_DIE_CODE 1204 ++#define THMC50_SYSCTL_ANALOG_OUT 1205 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected THMC50. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table thmc50_dir_table_template[] = { ++ {THMC50_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &thmc50_temp}, ++ {THMC50_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &thmc50_remote_temp}, ++ {THMC50_SYSCTL_INTER, "inter", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &thmc50_inter}, ++ {THMC50_SYSCTL_INTER_MASK, "inter_mask", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &thmc50_inter_mask}, ++ {THMC50_SYSCTL_DIE_CODE, "die_code", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &thmc50_die_code}, ++ {THMC50_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &thmc50_analog_out}, ++ {0} ++}; ++ ++ ++static int thmc50_id = 0; ++ ++static int thmc50_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, thmc50_detect); ++} ++ ++/* This function is called by i2c_detect */ ++int thmc50_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int company, i; ++ struct i2c_client *new_client; ++ struct thmc50_data *data; ++ int err = 0; ++ const char *type_name, *client_name; ++ ++#ifdef DEBUG ++ printk("thmc50.o: Probing for THMC50 at 0x%2X on bus %d\n", ++ address, adapter->id); ++#endif ++ ++ /* Make sure we aren't probing the ISA bus!! This is just a safety check ++ at this moment; i2c_detect really won't call us. */ ++#ifdef DEBUG ++ if (i2c_is_isa_adapter(adapter)) { ++ printk ++ ("thmc50.o: thmc50_detect called for an ISA bus adapter?!?\n"); ++ return 0; ++ } ++#endif ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto ERROR0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access thmc50_{read,write}_value. */ ++ if (!(data = kmalloc(sizeof(struct thmc50_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &thmc50_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ company = ++ i2c_smbus_read_byte_data(new_client, THMC50_REG_COMPANY_ID); ++ ++ if (company != 0x49) { ++#ifdef DEBUG ++ printk ++ ("thmc50.o: Detect of THMC50 failed (reg 3E: 0x%X)\n", ++ company); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Determine the chip type - only one kind supported! */ ++ kind = thmc50; ++ ++ if (kind == thmc50) { ++ type_name = "thmc50"; ++ client_name = "THMC50 chip"; ++ } else { ++#ifdef DEBUG ++ printk("thmc50.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ goto ERROR1; ++ } ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = thmc50_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ thmc50_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ thmc50_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int thmc50_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct thmc50_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("thmc50.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* All registers are word-sized, except for the configuration register. ++ THMC50 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int thmc50_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* All registers are word-sized, except for the configuration register. ++ THMC50 uses a high-byte first convention, which is exactly opposite to ++ the usual practice. */ ++static int thmc50_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void thmc50_init_client(struct i2c_client *client) ++{ ++ thmc50_write_value(client, THMC50_REG_CONF, 1); ++} ++ ++static void thmc50_update_client(struct i2c_client *client) ++{ ++ struct thmc50_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting thmc50 update\n"); ++#endif ++ ++ data->temp = thmc50_read_value(client, THMC50_REG_TEMP); ++ data->temp_os = ++ thmc50_read_value(client, THMC50_REG_TEMP_OS); ++ data->temp_hyst = ++ thmc50_read_value(client, THMC50_REG_TEMP_HYST); ++ data->remote_temp = ++ thmc50_read_value(client, THMC50_REG_REMOTE_TEMP); ++ data->remote_temp_os = ++ thmc50_read_value(client, THMC50_REG_REMOTE_TEMP_OS); ++ data->remote_temp_hyst = ++ thmc50_read_value(client, THMC50_REG_REMOTE_TEMP_HYST); ++ data->inter = thmc50_read_value(client, THMC50_REG_INTER); ++ data->inter_mask = ++ thmc50_read_value(client, THMC50_REG_INTER_MASK); ++ data->die_code = ++ thmc50_read_value(client, THMC50_REG_DIE_CODE); ++ data->analog_out = ++ thmc50_read_value(client, THMC50_REG_ANALOG_OUT); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void thmc50_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct thmc50_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ thmc50_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_os); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_os = TEMP_TO_REG(results[0]); ++ thmc50_write_value(client, THMC50_REG_TEMP_OS, ++ data->temp_os); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ thmc50_write_value(client, THMC50_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++ ++void thmc50_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct thmc50_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ thmc50_update_client(client); ++ results[0] = TEMP_FROM_REG(data->remote_temp_os); ++ results[1] = TEMP_FROM_REG(data->remote_temp_hyst); ++ results[2] = TEMP_FROM_REG(data->remote_temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->remote_temp_os = TEMP_TO_REG(results[0]); ++ thmc50_write_value(client, ++ THMC50_REG_REMOTE_TEMP_OS, ++ data->remote_temp_os); ++ } ++ if (*nrels_mag >= 2) { ++ data->remote_temp_hyst = TEMP_TO_REG(results[1]); ++ thmc50_write_value(client, ++ THMC50_REG_REMOTE_TEMP_HYST, ++ data->remote_temp_hyst); ++ } ++ } ++} ++ ++ ++void thmc50_inter(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct thmc50_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ thmc50_update_client(client); ++ results[0] = data->inter; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ printk("thmc50.o: No writes to Interrupt register!\n"); ++ } ++} ++ ++ ++void thmc50_inter_mask(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct thmc50_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ thmc50_update_client(client); ++ results[0] = data->inter_mask; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->inter_mask = results[0]; ++ thmc50_write_value(client, THMC50_REG_INTER_MASK, ++ data->inter_mask); ++ } ++ } ++} ++ ++ ++void thmc50_die_code(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct thmc50_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ thmc50_update_client(client); ++ results[0] = data->die_code; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ printk("thmc50.o: No writes to Die-Code register!\n"); ++ } ++} ++ ++ ++void thmc50_analog_out(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct thmc50_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ thmc50_update_client(client); ++ results[0] = data->analog_out; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->analog_out = results[0]; ++ thmc50_write_value(client, THMC50_REG_ANALOG_OUT, ++ data->analog_out); ++ } ++ } ++} ++ ++ ++ ++ ++static int __init sm_thmc50_init(void) ++{ ++ printk("thmc50.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ return i2c_add_driver(&thmc50_driver); ++} ++ ++static void __exit sm_thmc50_exit(void) ++{ ++ i2c_del_driver(&thmc50_driver); ++} ++ ++ ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("THMC50 driver"); ++ ++module_init(sm_thmc50_init); ++module_exit(sm_thmc50_exit); +--- linux-old/drivers/sensors/via686a.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/via686a.c Mon Dec 13 20:18:52 2004 +@@ -0,0 +1,849 @@ ++/* ++ via686a.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>, ++ Kyösti Mälkki <kmalkki@cc.hut.fi>, ++ Mark Studebaker <mdsxyz123@yahoo.com>, ++ and Bob Dougherty <bobd@stanford.edu> ++ (Some conversion-factor data were contributed by Jonathan Teh Soon Yew ++ <j.teh@iname.com> and Alex van Kaam <darkside@chello.nl>.) ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Supports the Via VT82C686A, VT82C686B south bridges. ++ Reports all as a 686A. ++ See doc/chips/via686a for details. ++ Warning - only supports a single device. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/pci.h> ++#include <linux/delay.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++ ++/* If force_addr is set to anything different from 0, we forcibly enable ++ the device at the given address. */ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the sensors"); ++ ++/* Addresses to scan. ++ Note that we can't determine the ISA address until we have initialized ++ our module */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(via686a); ++ ++/* ++ The Via 686a southbridge has a LM78-like chip integrated on the same IC. ++ This driver is a customized copy of lm78.c ++*/ ++ ++/* Many VIA686A constants specified below */ ++ ++/* Length of ISA address segment */ ++#define VIA686A_EXTENT 0x80 ++#define VIA686A_BASE_REG 0x70 ++#define VIA686A_ENABLE_REG 0x74 ++ ++/* The VIA686A registers */ ++/* ins numbered 0-4 */ ++#define VIA686A_REG_IN_MAX(nr) (0x2b + ((nr) * 2)) ++#define VIA686A_REG_IN_MIN(nr) (0x2c + ((nr) * 2)) ++#define VIA686A_REG_IN(nr) (0x22 + (nr)) ++ ++/* fans numbered 1-2 */ ++#define VIA686A_REG_FAN_MIN(nr) (0x3a + (nr)) ++#define VIA686A_REG_FAN(nr) (0x28 + (nr)) ++ ++// the following values are as speced by VIA: ++static const u8 regtemp[] = { 0x20, 0x21, 0x1f }; ++static const u8 regover[] = { 0x39, 0x3d, 0x1d }; ++static const u8 reghyst[] = { 0x3a, 0x3e, 0x1e }; ++ ++/* temps numbered 1-3 */ ++#define VIA686A_REG_TEMP(nr) (regtemp[(nr) - 1]) ++#define VIA686A_REG_TEMP_OVER(nr) (regover[(nr) - 1]) ++#define VIA686A_REG_TEMP_HYST(nr) (reghyst[(nr) - 1]) ++#define VIA686A_REG_TEMP_LOW1 0x4b // bits 7-6 ++#define VIA686A_REG_TEMP_LOW23 0x49 // 2 = bits 5-4, 3 = bits 7-6 ++ ++#define VIA686A_REG_ALARM1 0x41 ++#define VIA686A_REG_ALARM2 0x42 ++#define VIA686A_REG_FANDIV 0x47 ++#define VIA686A_REG_CONFIG 0x40 ++// The following register sets temp interrupt mode (bits 1-0 for temp1, ++// 3-2 for temp2, 5-4 for temp3). Modes are: ++// 00 interrupt stays as long as value is out-of-range ++// 01 interrupt is cleared once register is read (default) ++// 10 comparator mode- like 00, but ignores hysteresis ++// 11 same as 00 ++#define VIA686A_REG_TEMP_MODE 0x4b ++// We'll just assume that you want to set all 3 simultaneously: ++#define VIA686A_TEMP_MODE_MASK 0x3F ++#define VIA686A_TEMP_MODE_CONTINUOUS (0x00) ++ ++/* Conversions. Limit checking is only done on the TO_REG ++ variants. */ ++ ++/********* VOLTAGE CONVERSIONS (Bob Dougherty) ********/ ++// From HWMon.cpp (Copyright 1998-2000 Jonathan Teh Soon Yew): ++// voltagefactor[0]=1.25/2628; (2628/1.25=2102.4) // Vccp ++// voltagefactor[1]=1.25/2628; (2628/1.25=2102.4) // +2.5V ++// voltagefactor[2]=1.67/2628; (2628/1.67=1573.7) // +3.3V ++// voltagefactor[3]=2.6/2628; (2628/2.60=1010.8) // +5V ++// voltagefactor[4]=6.3/2628; (2628/6.30=417.14) // +12V ++// in[i]=(data[i+2]*25.0+133)*voltagefactor[i]; ++// That is: ++// volts = (25*regVal+133)*factor ++// regVal = (volts/factor-133)/25 ++// (These conversions were contributed by Jonathan Teh Soon Yew ++// <j.teh@iname.com>) ++static inline u8 IN_TO_REG(long val, int inNum) ++{ ++ /* To avoid floating point, we multiply constants by 10 (100 for +12V). ++ Rounding is done (120500 is actually 133000 - 12500). ++ Remember that val is expressed in 0.01V/bit, which is why we divide ++ by an additional 1000 (10000 for +12V): 100 for val and 10 (100) ++ for the constants. */ ++ if (inNum <= 1) ++ return (u8) ++ SENSORS_LIMIT((val * 21024 - 120500) / 25000, 0, 255); ++ else if (inNum == 2) ++ return (u8) ++ SENSORS_LIMIT((val * 15737 - 120500) / 25000, 0, 255); ++ else if (inNum == 3) ++ return (u8) ++ SENSORS_LIMIT((val * 10108 - 120500) / 25000, 0, 255); ++ else ++ return (u8) ++ SENSORS_LIMIT((val * 41714 - 1205000) / 250000, 0, 255); ++} ++ ++static inline long IN_FROM_REG(u8 val, int inNum) ++{ ++ /* To avoid floating point, we multiply constants by 10 (100 for +12V). ++ We also multiply them by 100 because we want 0.01V/bit for the ++ output value. Rounding is done. */ ++ if (inNum <= 1) ++ return (long) ((25000 * val + 133000 + 21024 / 2) / 21024); ++ else if (inNum == 2) ++ return (long) ((25000 * val + 133000 + 15737 / 2) / 15737); ++ else if (inNum == 3) ++ return (long) ((25000 * val + 133000 + 10108 / 2) / 10108); ++ else ++ return (long) ((250000 * val + 1330000 + 41714 / 2) / 41714); ++} ++ ++/********* FAN RPM CONVERSIONS ********/ ++// Higher register values = slower fans (the fan's strobe gates a counter). ++// But this chip saturates back at 0, not at 255 like all the other chips. ++// So, 0 means 0 RPM ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 0; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, 255); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1350000/((val)*(div))) ++ ++/******** TEMP CONVERSIONS (Bob Dougherty) *********/ ++// linear fits from HWMon.cpp (Copyright 1998-2000 Jonathan Teh Soon Yew) ++// if(temp<169) ++// return double(temp)*0.427-32.08; ++// else if(temp>=169 && temp<=202) ++// return double(temp)*0.582-58.16; ++// else ++// return double(temp)*0.924-127.33; ++// ++// A fifth-order polynomial fits the unofficial data (provided by Alex van ++// Kaam <darkside@chello.nl>) a bit better. It also give more reasonable ++// numbers on my machine (ie. they agree with what my BIOS tells me). ++// Here's the fifth-order fit to the 8-bit data: ++// temp = 1.625093e-10*val^5 - 1.001632e-07*val^4 + 2.457653e-05*val^3 - ++// 2.967619e-03*val^2 + 2.175144e-01*val - 7.090067e+0. ++// ++// (2000-10-25- RFD: thanks to Uwe Andersen <uandersen@mayah.com> for ++// finding my typos in this formula!) ++// ++// Alas, none of the elegant function-fit solutions will work because we ++// aren't allowed to use floating point in the kernel and doing it with ++// integers doesn't rpovide enough precision. So we'll do boring old ++// look-up table stuff. The unofficial data (see below) have effectively ++// 7-bit resolution (they are rounded to the nearest degree). I'm assuming ++// that the transfer function of the device is monotonic and smooth, so a ++// smooth function fit to the data will allow us to get better precision. ++// I used the 5th-order poly fit described above and solved for ++// VIA register values 0-255. I *10 before rounding, so we get tenth-degree ++// precision. (I could have done all 1024 values for our 10-bit readings, ++// but the function is very linear in the useful range (0-80 deg C), so ++// we'll just use linear interpolation for 10-bit readings.) So, tempLUT ++// is the temp at via register values 0-255: ++static const long tempLUT[] = ++ { -709, -688, -667, -646, -627, -607, -589, -570, -553, -536, -519, ++ -503, -487, -471, -456, -442, -428, -414, -400, -387, -375, ++ -362, -350, -339, -327, -316, -305, -295, -285, -275, -265, ++ -255, -246, -237, -229, -220, -212, -204, -196, -188, -180, ++ -173, -166, -159, -152, -145, -139, -132, -126, -120, -114, ++ -108, -102, -96, -91, -85, -80, -74, -69, -64, -59, -54, -49, ++ -44, -39, -34, -29, -25, -20, -15, -11, -6, -2, 3, 7, 12, 16, ++ 20, 25, 29, 33, 37, 42, 46, 50, 54, 59, 63, 67, 71, 75, 79, 84, ++ 88, 92, 96, 100, 104, 109, 113, 117, 121, 125, 130, 134, 138, ++ 142, 146, 151, 155, 159, 163, 168, 172, 176, 181, 185, 189, ++ 193, 198, 202, 206, 211, 215, 219, 224, 228, 232, 237, 241, ++ 245, 250, 254, 259, 263, 267, 272, 276, 281, 285, 290, 294, ++ 299, 303, 307, 312, 316, 321, 325, 330, 334, 339, 344, 348, ++ 353, 357, 362, 366, 371, 376, 380, 385, 390, 395, 399, 404, ++ 409, 414, 419, 423, 428, 433, 438, 443, 449, 454, 459, 464, ++ 469, 475, 480, 486, 491, 497, 502, 508, 514, 520, 526, 532, ++ 538, 544, 551, 557, 564, 571, 578, 584, 592, 599, 606, 614, ++ 621, 629, 637, 645, 654, 662, 671, 680, 689, 698, 708, 718, ++ 728, 738, 749, 759, 770, 782, 793, 805, 818, 830, 843, 856, ++ 870, 883, 898, 912, 927, 943, 958, 975, 991, 1008, 1026, 1044, ++ 1062, 1081, 1101, 1121, 1141, 1162, 1184, 1206, 1229, 1252, ++ 1276, 1301, 1326, 1352, 1378, 1406, 1434, 1462 ++}; ++ ++/* the original LUT values from Alex van Kaam <darkside@chello.nl> ++ (for via register values 12-240): ++{-50,-49,-47,-45,-43,-41,-39,-38,-37,-35,-34,-33,-32,-31, ++-30,-29,-28,-27,-26,-25,-24,-24,-23,-22,-21,-20,-20,-19,-18,-17,-17,-16,-15, ++-15,-14,-14,-13,-12,-12,-11,-11,-10,-9,-9,-8,-8,-7,-7,-6,-6,-5,-5,-4,-4,-3, ++-3,-2,-2,-1,-1,0,0,1,1,1,3,3,3,4,4,4,5,5,5,6,6,7,7,8,8,9,9,9,10,10,11,11,12, ++12,12,13,13,13,14,14,15,15,16,16,16,17,17,18,18,19,19,20,20,21,21,21,22,22, ++22,23,23,24,24,25,25,26,26,26,27,27,27,28,28,29,29,30,30,30,31,31,32,32,33, ++33,34,34,35,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45, ++45,46,46,47,48,48,49,49,50,51,51,52,52,53,53,54,55,55,56,57,57,58,59,59,60, ++61,62,62,63,64,65,66,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,83,84, ++85,86,88,89,91,92,94,96,97,99,101,103,105,107,109,110}; ++*/ ++ ++// Here's the reverse LUT. I got it by doing a 6-th order poly fit (needed ++// an extra term for a good fit to these inverse data!) and then ++// solving for each temp value from -50 to 110 (the useable range for ++// this chip). Here's the fit: ++// viaRegVal = -1.160370e-10*val^6 +3.193693e-08*val^5 - 1.464447e-06*val^4 ++// - 2.525453e-04*val^3 + 1.424593e-02*val^2 + 2.148941e+00*val +7.275808e+01) ++// Note that n=161: ++static const u8 viaLUT[] = ++ { 12, 12, 13, 14, 14, 15, 16, 16, 17, 18, 18, 19, 20, 20, 21, 22, 23, ++ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 39, 40, ++ 41, 43, 45, 46, 48, 49, 51, 53, 55, 57, 59, 60, 62, 64, 66, ++ 69, 71, 73, 75, 77, 79, 82, 84, 86, 88, 91, 93, 95, 98, 100, ++ 103, 105, 107, 110, 112, 115, 117, 119, 122, 124, 126, 129, ++ 131, 134, 136, 138, 140, 143, 145, 147, 150, 152, 154, 156, ++ 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, ++ 182, 183, 185, 187, 188, 190, 192, 193, 195, 196, 198, 199, ++ 200, 202, 203, 205, 206, 207, 208, 209, 210, 211, 212, 213, ++ 214, 215, 216, 217, 218, 219, 220, 221, 222, 222, 223, 224, ++ 225, 226, 226, 227, 228, 228, 229, 230, 230, 231, 232, 232, ++ 233, 233, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, ++ 239, 240 ++}; ++ ++/* Converting temps to (8-bit) hyst and over registers ++ No interpolation here. ++ The +50 is because the temps start at -50 */ ++static inline u8 TEMP_TO_REG(long val) ++{ ++ return viaLUT[val <= -500 ? 0 : val >= 1100 ? 160 : ++ (val < 0 ? val - 5 : val + 5) / 10 + 50]; ++} ++ ++/* for 8-bit temperature hyst and over registers */ ++#define TEMP_FROM_REG(val) (tempLUT[(val)]) ++ ++/* for 10-bit temperature readings */ ++// You might _think_ this is too long to inline, but's it's really only ++// called once... ++static inline long TEMP_FROM_REG10(u16 val) ++{ ++ // the temp values are already *10, so we don't need to do that. ++ long temp; ++ u16 eightBits = val >> 2; ++ u16 twoBits = val & 3; ++ ++ /* no interpolation for these */ ++ if (twoBits == 0 || eightBits == 255) ++ return (long) tempLUT[eightBits]; ++ ++ /* do some linear interpolation */ ++ temp = (4 - twoBits) * tempLUT[eightBits] ++ + twoBits * tempLUT[eightBits + 1]; ++ /* achieve rounding */ ++ return (temp < 0 ? temp - 2 : temp + 2) / 4; ++} ++ ++#define ALARMS_FROM_REG(val) (val) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++ ++/* For the VIA686A, we need to keep some data in memory. ++ The structure is dynamically allocated, at the same time when a new ++ via686a client is allocated. */ ++struct via686a_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[5]; /* Register value */ ++ u8 in_max[5]; /* Register value */ ++ u8 in_min[5]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u16 temp[3]; /* Register value 10 bit */ ++ u8 temp_over[3]; /* Register value */ ++ u8 temp_hyst[3]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u16 alarms; /* Register encoding, combined */ ++}; ++ ++static struct pci_dev *s_bridge; /* pointer to the (only) via686a */ ++ ++static int via686a_attach_adapter(struct i2c_adapter *adapter); ++static int via686a_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int via686a_detach_client(struct i2c_client *client); ++ ++static int via686a_read_value(struct i2c_client *client, u8 register); ++static void via686a_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void via686a_update_client(struct i2c_client *client); ++static void via686a_init_client(struct i2c_client *client); ++ ++ ++static void via686a_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void via686a_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void via686a_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void via686a_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void via686a_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int via686a_id = 0; ++ ++/* The driver. I choose to use type i2c_driver, as at is identical to both ++ smbus_driver and isa_driver, and clients could be of either kind */ ++static struct i2c_driver via686a_driver = { ++ .owner = THIS_MODULE, ++ .name = "VIA 686A", ++ .id = I2C_DRIVERID_VIA686A, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = via686a_attach_adapter, ++ .detach_client = via686a_detach_client, ++}; ++ ++ ++ ++/* The /proc/sys entries */ ++ ++/* -- SENSORS SYSCTL START -- */ ++#define VIA686A_SYSCTL_IN0 1000 ++#define VIA686A_SYSCTL_IN1 1001 ++#define VIA686A_SYSCTL_IN2 1002 ++#define VIA686A_SYSCTL_IN3 1003 ++#define VIA686A_SYSCTL_IN4 1004 ++#define VIA686A_SYSCTL_FAN1 1101 ++#define VIA686A_SYSCTL_FAN2 1102 ++#define VIA686A_SYSCTL_TEMP 1200 ++#define VIA686A_SYSCTL_TEMP2 1201 ++#define VIA686A_SYSCTL_TEMP3 1202 ++#define VIA686A_SYSCTL_FAN_DIV 2000 ++#define VIA686A_SYSCTL_ALARMS 2001 ++ ++#define VIA686A_ALARM_IN0 0x01 ++#define VIA686A_ALARM_IN1 0x02 ++#define VIA686A_ALARM_IN2 0x04 ++#define VIA686A_ALARM_IN3 0x08 ++#define VIA686A_ALARM_TEMP 0x10 ++#define VIA686A_ALARM_FAN1 0x40 ++#define VIA686A_ALARM_FAN2 0x80 ++#define VIA686A_ALARM_IN4 0x100 ++#define VIA686A_ALARM_TEMP2 0x800 ++#define VIA686A_ALARM_CHAS 0x1000 ++#define VIA686A_ALARM_TEMP3 0x8000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected VIA686A. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table via686a_dir_table_template[] = { ++ {VIA686A_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_in}, ++ {VIA686A_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_in}, ++ {VIA686A_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_in}, ++ {VIA686A_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_in}, ++ {VIA686A_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_in}, ++ {VIA686A_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_fan}, ++ {VIA686A_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_fan}, ++ {VIA686A_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &via686a_temp}, ++ {VIA686A_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_temp}, ++ {VIA686A_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_temp}, ++ {VIA686A_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_fan_div}, ++ {VIA686A_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_alarms}, ++ {0} ++}; ++ ++static inline int via686a_read_value(struct i2c_client *client, u8 reg) ++{ ++ return (inb_p(client->addr + reg)); ++} ++ ++static inline void via686a_write_value(struct i2c_client *client, u8 reg, ++ u8 value) ++{ ++ outb_p(value, client->addr + reg); ++} ++ ++/* This is called when the module is loaded */ ++static int via686a_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, via686a_detect); ++} ++ ++int via686a_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct via686a_data *data; ++ int err = 0; ++ const char *type_name = "via686a"; ++ u16 val; ++ ++ /* Make sure we are probing the ISA bus!! */ ++ if (!i2c_is_isa_adapter(adapter)) { ++ printk ++ ("via686a.o: via686a_detect called for an I2C bus adapter?!?\n"); ++ return 0; ++ } ++ ++ /* 8231 requires multiple of 256, we enforce that on 686 as well */ ++ if(force_addr) ++ address = force_addr & 0xFF00; ++ if (check_region(address, VIA686A_EXTENT)) { ++ printk("via686a.o: region 0x%x already in use!\n", ++ address); ++ return -ENODEV; ++ } ++ ++ if(force_addr) { ++ printk("via686a.o: forcing ISA address 0x%04X\n", address); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(s_bridge, VIA686A_BASE_REG, address)) ++ return -ENODEV; ++ } ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(s_bridge, VIA686A_ENABLE_REG, &val)) ++ return -ENODEV; ++ if (!(val & 0x0001)) { ++ printk("via686a.o: enabling sensors\n"); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(s_bridge, VIA686A_ENABLE_REG, ++ val | 0x0001)) ++ return -ENODEV; ++ } ++ ++ if (!(data = kmalloc(sizeof(struct via686a_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &via686a_driver; ++ new_client->flags = 0; ++ ++ /* Reserve the ISA region */ ++ request_region(address, VIA686A_EXTENT, "via686a-sensors"); ++ ++ /* Fill in the remaining client fields and put into the global list */ ++ strcpy(new_client->name, "Via 686A Integrated Sensors"); ++ ++ new_client->id = via686a_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, ++ via686a_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the VIA686A chip */ ++ via686a_init_client(new_client); ++ return 0; ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ release_region(address, VIA686A_EXTENT); ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int via686a_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct via686a_data *) ++ (client->data))->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("via686a.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ release_region(client->addr, VIA686A_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++/* Called when we have found a new VIA686A. */ ++static void via686a_init_client(struct i2c_client *client) ++{ ++ u8 reg; ++ ++ /* Start monitoring */ ++ reg = via686a_read_value(client, VIA686A_REG_CONFIG); ++ via686a_write_value(client, VIA686A_REG_CONFIG, (reg|0x01)&0x7F); ++ ++ /* Configure temp interrupt mode for continuous-interrupt operation */ ++ via686a_write_value(client, VIA686A_REG_TEMP_MODE, ++ via686a_read_value(client, VIA686A_REG_TEMP_MODE) & ++ !(VIA686A_TEMP_MODE_MASK | VIA686A_TEMP_MODE_CONTINUOUS)); ++} ++ ++static void via686a_update_client(struct i2c_client *client) ++{ ++ struct via686a_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || ++ time_before(jiffies, data->last_updated) || !data->valid) { ++ ++ for (i = 0; i <= 4; i++) { ++ data->in[i] = ++ via686a_read_value(client, VIA686A_REG_IN(i)); ++ data->in_min[i] = via686a_read_value(client, ++ VIA686A_REG_IN_MIN ++ (i)); ++ data->in_max[i] = ++ via686a_read_value(client, VIA686A_REG_IN_MAX(i)); ++ } ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = ++ via686a_read_value(client, VIA686A_REG_FAN(i)); ++ data->fan_min[i - 1] = via686a_read_value(client, ++ VIA686A_REG_FAN_MIN(i)); ++ } ++ for (i = 1; i <= 3; i++) { ++ data->temp[i - 1] = via686a_read_value(client, ++ VIA686A_REG_TEMP(i)) << 2; ++ data->temp_over[i - 1] = ++ via686a_read_value(client, ++ VIA686A_REG_TEMP_OVER(i)); ++ data->temp_hyst[i - 1] = ++ via686a_read_value(client, ++ VIA686A_REG_TEMP_HYST(i)); ++ } ++ /* add in lower 2 bits ++ temp1 uses bits 7-6 of VIA686A_REG_TEMP_LOW1 ++ temp2 uses bits 5-4 of VIA686A_REG_TEMP_LOW23 ++ temp3 uses bits 7-6 of VIA686A_REG_TEMP_LOW23 ++ */ ++ data->temp[0] |= (via686a_read_value(client, ++ VIA686A_REG_TEMP_LOW1) ++ & 0xc0) >> 6; ++ data->temp[1] |= ++ (via686a_read_value(client, VIA686A_REG_TEMP_LOW23) & ++ 0x30) >> 4; ++ data->temp[2] |= ++ (via686a_read_value(client, VIA686A_REG_TEMP_LOW23) & ++ 0xc0) >> 6; ++ ++ i = via686a_read_value(client, VIA686A_REG_FANDIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = i >> 6; ++ data->alarms = ++ via686a_read_value(client, ++ VIA686A_REG_ALARM1) | ++ (via686a_read_value(client, VIA686A_REG_ALARM2) << 8); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++static void via686a_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct via686a_data *data = client->data; ++ int nr = ctl_name - VIA686A_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ via686a_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr], nr); ++ results[1] = IN_FROM_REG(data->in_max[nr], nr); ++ results[2] = IN_FROM_REG(data->in[nr], nr); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0], nr); ++ via686a_write_value(client, VIA686A_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1], nr); ++ via686a_write_value(client, VIA686A_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void via686a_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct via686a_data *data = client->data; ++ int nr = ctl_name - VIA686A_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ via686a_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div ++ [nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = FAN_TO_REG(results[0], ++ DIV_FROM_REG(data-> ++ fan_div[nr -1])); ++ via686a_write_value(client, ++ VIA686A_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++void via686a_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct via686a_data *data = client->data; ++ int nr = ctl_name - VIA686A_SYSCTL_TEMP; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ via686a_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); ++ results[2] = TEMP_FROM_REG10(data->temp[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over[nr] = TEMP_TO_REG(results[0]); ++ via686a_write_value(client, ++ VIA686A_REG_TEMP_OVER(nr + 1), ++ data->temp_over[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst[nr] = TEMP_TO_REG(results[1]); ++ via686a_write_value(client, ++ VIA686A_REG_TEMP_HYST(nr + 1), ++ data->temp_hyst[nr]); ++ } ++ } ++} ++ ++void via686a_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct via686a_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ via686a_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void via686a_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct via686a_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ via686a_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = via686a_read_value(client, VIA686A_REG_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ via686a_write_value(client, VIA686A_REG_FANDIV, ++ old); ++ } ++ } ++} ++ ++ ++static struct pci_device_id via686a_pci_ids[] __devinitdata = { ++ {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, ++ { 0, } ++}; ++ ++static int __devinit via686a_pci_probe(struct pci_dev *dev, ++ const struct pci_device_id *id) ++{ ++ u16 val; ++ int addr = 0; ++ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(dev, VIA686A_BASE_REG, &val)) ++ return -ENODEV; ++ ++ addr = val & ~(VIA686A_EXTENT - 1); ++ if (addr == 0 && force_addr == 0) { ++ printk("via686a.o: base address not set - upgrade BIOS or use force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ if (force_addr) ++ addr = force_addr; /* so detect will get called */ ++ ++ if (!addr) { ++ printk("via686a.o: No Via 686A sensors found.\n"); ++ return -ENODEV; ++ } ++ normal_isa[0] = addr; ++ s_bridge = dev; ++ return i2c_add_driver(&via686a_driver); ++} ++ ++static void __devexit via686a_pci_remove(struct pci_dev *dev) ++{ ++ i2c_del_driver(&via686a_driver); ++} ++ ++static struct pci_driver via686a_pci_driver = { ++ .name = "via686a", ++ .id_table = via686a_pci_ids, ++ .probe = via686a_pci_probe, ++ .remove = __devexit_p(via686a_pci_remove), ++}; ++ ++static int __init sm_via686a_init(void) ++{ ++ printk("via686a.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return pci_module_init(&via686a_pci_driver); ++} ++ ++static void __exit sm_via686a_exit(void) ++{ ++ pci_unregister_driver(&via686a_pci_driver); ++} ++ ++MODULE_AUTHOR("Kyösti Mälkki <kmalkki@cc.hut.fi>, " ++ "Mark Studebaker <mdsxyz123@yahoo.com> " ++ "and Bob Dougherty <bobd@stanford.edu>"); ++MODULE_DESCRIPTION("VIA 686A Sensor device"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_via686a_init); ++module_exit(sm_via686a_exit); +--- linux-old/drivers/sensors/vt1211.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/vt1211.c Mon Dec 13 20:18:53 2004 +@@ -0,0 +1,823 @@ ++/* ++ vt1211.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Supports VIA VT1211 Super I/O sensors via ISA (LPC) accesses only. */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the sensors"); ++ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++SENSORS_INSMOD_1(vt1211); ++ ++/* modified from kernel/include/traps.c */ ++#define REG 0x2e /* The register to read/write */ ++#define DEV 0x07 /* Register: Logical device select */ ++#define VAL 0x2f /* The value to read/write */ ++#define PME 0x0b /* The device with the hardware monitor */ ++#define DEVID 0x20 /* Register: Device ID */ ++ ++static inline void ++superio_outb(int reg, int val) ++{ ++ outb(reg, REG); ++ outb(val, VAL); ++} ++ ++static inline int ++superio_inb(int reg) ++{ ++ outb(reg, REG); ++ return inb(VAL); ++} ++ ++static inline void ++superio_select(void) ++{ ++ outb(DEV, REG); ++ outb(PME, VAL); ++} ++ ++static inline void ++superio_enter(void) ++{ ++ outb(0x87, REG); ++ outb(0x87, REG); ++} ++ ++static inline void ++superio_exit(void) ++{ ++ outb(0xAA, REG); ++} ++ ++#define VT1211_DEVID 0x3c ++#define VT1211_ACT_REG 0x30 ++#define VT1211_BASE_REG 0x60 ++ ++#define VT1211_EXTENT 0x80 ++ ++/* pwm numbered 1-2 */ ++#define VT1211_REG_PWM(nr) (0x5f + (nr)) ++#define VT1211_REG_PWM_CTL 0x51 ++ ++/* The VT1211 registers */ ++/* We define the sensors as follows. Somewhat convoluted to minimize ++ changes from via686a. ++ Sensor Voltage Mode Temp Mode ++ -------- ------------ --------- ++ Reading 1 temp3 ++ Reading 3 temp1 not in vt1211 ++ UCH1/Reading2 in0 temp2 ++ UCH2 in1 temp4 ++ UCH3 in2 temp5 ++ UCH4 in3 temp6 ++ UCH5 in4 temp7 ++ 3.3V in5 ++ -12V in6 not in vt1211 ++*/ ++ ++/* ins numbered 0-6 */ ++#define VT1211_REG_IN_MAX(nr) ((nr)==0 ? 0x3d : 0x29 + ((nr) * 2)) ++#define VT1211_REG_IN_MIN(nr) ((nr)==0 ? 0x3e : 0x2a + ((nr) * 2)) ++#define VT1211_REG_IN(nr) (0x21 + (nr)) ++ ++/* fans numbered 1-2 */ ++#define VT1211_REG_FAN_MIN(nr) (0x3a + (nr)) ++#define VT1211_REG_FAN(nr) (0x28 + (nr)) ++ ++static const u8 regtemp[] = { 0x20, 0x21, 0x1f, 0x22, 0x23, 0x24, 0x25 }; ++static const u8 regover[] = { 0x39, 0x3d, 0x1d, 0x2b, 0x2d, 0x2f, 0x31 }; ++static const u8 reghyst[] = { 0x3a, 0x3e, 0x1e, 0x2c, 0x2e, 0x30, 0x32 }; ++ ++/* temps numbered 1-7 */ ++#define VT1211_REG_TEMP(nr) (regtemp[(nr) - 1]) ++#define VT1211_REG_TEMP_OVER(nr) (regover[(nr) - 1]) ++#define VT1211_REG_TEMP_HYST(nr) (reghyst[(nr) - 1]) ++#define VT1211_REG_TEMP_LOW3 0x4b /* bits 7-6 */ ++#define VT1211_REG_TEMP_LOW2 0x49 /* bits 5-4 */ ++#define VT1211_REG_TEMP_LOW47 0x4d ++ ++#define VT1211_REG_CONFIG 0x40 ++#define VT1211_REG_ALARM1 0x41 ++#define VT1211_REG_ALARM2 0x42 ++#define VT1211_REG_VID 0x45 ++#define VT1211_REG_FANDIV 0x47 ++#define VT1211_REG_UCH_CONFIG 0x4a ++#define VT1211_REG_TEMP1_CONFIG 0x4b ++#define VT1211_REG_TEMP2_CONFIG 0x4c ++ ++/* temps 1-7; voltages 0-6 */ ++#define ISTEMP(i, ch_config) ((i) == 1 ? 1 : \ ++ (i) == 3 ? 1 : \ ++ (i) == 2 ? ((ch_config) >> 1) & 0x01 : \ ++ ((ch_config) >> ((i)-1)) & 0x01) ++#define ISVOLT(i, ch_config) ((i) > 4 ? 1 : !(((ch_config) >> ((i)+2)) & 0x01)) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++#define PWM_FROM_REG(val) (val) ++#define PWM_TO_REG(val) SENSORS_LIMIT((val), 0, 255) ++ ++#define TEMP_FROM_REG(val) ((val)*10) ++#define TEMP_FROM_REG10(val) (((val)*10)/4) ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),0,255)) ++#define IN_FROM_REG(val) /*(((val)*10+5)/10)*/ (val) ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 5)/10),0,255)) ++ ++ ++/********* FAN RPM CONVERSIONS ********/ ++/* But this chip saturates back at 0, not at 255 like all the other chips. ++ So, 0 means 0 RPM */ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 0; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1310720 + rpm * div / 2) / (rpm * div), 1, 255); ++} ++ ++#define MIN_TO_REG(a,b) FAN_TO_REG(a,b) ++#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1310720/((val)*(div))) ++ ++struct vt1211_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[7]; /* Register value */ ++ u8 in_max[7]; /* Register value */ ++ u8 in_min[7]; /* Register value */ ++ u16 temp[7]; /* Register value 10 bit */ ++ u8 temp_over[7]; /* Register value */ ++ u8 temp_hyst[7]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u16 alarms; /* Register encoding */ ++ u8 pwm[2]; /* Register value */ ++ u8 pwm_ctl; /* Register value */ ++ u8 vid; /* Register encoding */ ++ u8 vrm; ++ u8 uch_config; ++}; ++ ++static int vt1211_attach_adapter(struct i2c_adapter *adapter); ++static int vt1211_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int vt1211_detach_client(struct i2c_client *client); ++ ++static inline int vt_rdval(struct i2c_client *client, u8 register); ++static inline void vt1211_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void vt1211_update_client(struct i2c_client *client); ++static void vt1211_init_client(struct i2c_client *client); ++static int vt1211_find(int *address); ++ ++ ++static void vt1211_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_uch(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt1211_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int vt1211_id = 0; ++ ++static struct i2c_driver vt1211_driver = { ++ .owner = THIS_MODULE, ++ .name = "VT1211 sensors driver", ++ .id = I2C_DRIVERID_VT1211, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = vt1211_attach_adapter, ++ .detach_client = vt1211_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define VT1211_SYSCTL_IN0 1000 ++#define VT1211_SYSCTL_IN1 1001 ++#define VT1211_SYSCTL_IN2 1002 ++#define VT1211_SYSCTL_IN3 1003 ++#define VT1211_SYSCTL_IN4 1004 ++#define VT1211_SYSCTL_IN5 1005 ++#define VT1211_SYSCTL_IN6 1006 ++#define VT1211_SYSCTL_FAN1 1101 ++#define VT1211_SYSCTL_FAN2 1102 ++#define VT1211_SYSCTL_TEMP 1200 ++#define VT1211_SYSCTL_TEMP2 1201 ++#define VT1211_SYSCTL_TEMP3 1202 ++#define VT1211_SYSCTL_TEMP4 1203 ++#define VT1211_SYSCTL_TEMP5 1204 ++#define VT1211_SYSCTL_TEMP6 1205 ++#define VT1211_SYSCTL_TEMP7 1206 ++#define VT1211_SYSCTL_VID 1300 ++#define VT1211_SYSCTL_PWM1 1401 ++#define VT1211_SYSCTL_PWM2 1402 ++#define VT1211_SYSCTL_VRM 1600 ++#define VT1211_SYSCTL_UCH 1700 ++#define VT1211_SYSCTL_FAN_DIV 2000 ++#define VT1211_SYSCTL_ALARMS 2001 ++ ++#define VT1211_ALARM_IN1 0x01 ++#define VT1211_ALARM_IN2 0x02 ++#define VT1211_ALARM_IN5 0x04 ++#define VT1211_ALARM_IN3 0x08 ++#define VT1211_ALARM_TEMP 0x10 ++#define VT1211_ALARM_FAN1 0x40 ++#define VT1211_ALARM_FAN2 0x80 ++#define VT1211_ALARM_IN4 0x100 ++#define VT1211_ALARM_IN6 0x200 ++#define VT1211_ALARM_TEMP2 0x800 ++#define VT1211_ALARM_CHAS 0x1000 ++#define VT1211_ALARM_TEMP3 0x8000 ++/* duplicates */ ++#define VT1211_ALARM_IN0 VT1211_ALARM_TEMP ++#define VT1211_ALARM_TEMP4 VT1211_ALARM_IN1 ++#define VT1211_ALARM_TEMP5 VT1211_ALARM_IN2 ++#define VT1211_ALARM_TEMP6 VT1211_ALARM_IN3 ++#define VT1211_ALARM_TEMP7 VT1211_ALARM_IN4 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++static ctl_table vt1211_dir_table_template[] = { ++ {VT1211_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++ {VT1211_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++ {VT1211_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++ {VT1211_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++ {VT1211_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++ {VT1211_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++/* ++ datasheet says these are reserved ++ {VT1211_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_in}, ++ {VT1211_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_temp}, ++*/ ++ {VT1211_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt1211_temp}, ++ {VT1211_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt1211_temp}, ++ {VT1211_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt1211_temp}, ++ {VT1211_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt1211_temp}, ++ {VT1211_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt1211_temp}, ++ {VT1211_SYSCTL_TEMP7, "temp7", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt1211_temp}, ++ {VT1211_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_fan}, ++ {VT1211_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_fan}, ++ {VT1211_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_fan_div}, ++ {VT1211_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_alarms}, ++ {VT1211_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_pwm}, ++ {VT1211_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_pwm}, ++ {VT1211_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_vid}, ++ {VT1211_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_vrm}, ++ {VT1211_SYSCTL_UCH, "uch_config", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt1211_uch}, ++ {0} ++}; ++ ++static int vt1211_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, vt1211_detect); ++} ++ ++static int vt1211_find(int *address) ++{ ++ u16 val; ++ ++ superio_enter(); ++ val= superio_inb(DEVID); ++ if(VT1211_DEVID != val) { ++ superio_exit(); ++ return -ENODEV; ++ } ++ ++ superio_select(); ++ val = (superio_inb(VT1211_BASE_REG) << 8) | ++ superio_inb(VT1211_BASE_REG + 1); ++ *address = val & ~(VT1211_EXTENT - 1); ++ if (*address == 0 && force_addr == 0) { ++ printk("vt1211.o: base address not set - use force_addr=0xaddr\n"); ++ superio_exit(); ++ return -ENODEV; ++ } ++ if (force_addr) ++ *address = force_addr; /* so detect will get called */ ++ ++ superio_exit(); ++ return 0; ++} ++ ++int vt1211_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct vt1211_data *data; ++ int err = 0; ++ u8 val; ++ const char *type_name = "vt1211"; ++ const char *client_name = "VT1211 chip"; ++ ++ if (!i2c_is_isa_adapter(adapter)) { ++ return 0; ++ } ++ ++ if(force_addr) ++ address = force_addr & ~(VT1211_EXTENT - 1); ++ if (check_region(address, VT1211_EXTENT)) { ++ printk("vt1211.o: region 0x%x already in use!\n", address); ++ return -ENODEV; ++ } ++ if(force_addr) { ++ printk("vt1211.o: forcing ISA address 0x%04X\n", address); ++ superio_enter(); ++ superio_select(); ++ superio_outb(VT1211_BASE_REG, address >> 8); ++ superio_outb(VT1211_BASE_REG+1, address & 0xff); ++ superio_exit(); ++ } ++ ++ superio_enter(); ++ superio_select(); ++ if((val = 0x01 & superio_inb(VT1211_ACT_REG)) == 0) ++ superio_outb(VT1211_ACT_REG, 1); ++ superio_exit(); ++ ++ if (!(data = kmalloc(sizeof(struct vt1211_data), GFP_KERNEL))) { ++ return -ENOMEM; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &vt1211_driver; ++ new_client->flags = 0; ++ ++ request_region(address, VT1211_EXTENT, "vt1211-sensors"); ++ strcpy(new_client->name, client_name); ++ ++ new_client->id = vt1211_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, ++ vt1211_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ vt1211_init_client(new_client); ++ return 0; ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ release_region(address, VT1211_EXTENT); ++ kfree(data); ++ return err; ++} ++ ++static int vt1211_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct vt1211_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("vt1211.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ release_region(client->addr, VT1211_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++static inline int vt_rdval(struct i2c_client *client, u8 reg) ++{ ++ return (inb_p(client->addr + reg)); ++} ++ ++static inline void vt1211_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ outb_p(value, client->addr + reg); ++} ++ ++static void vt1211_init_client(struct i2c_client *client) ++{ ++ struct vt1211_data *data = client->data; ++ ++ data->vrm = DEFAULT_VRM; ++ /* set "default" interrupt mode for alarms, which isn't the default */ ++ vt1211_write_value(client, VT1211_REG_TEMP1_CONFIG, 0); ++ vt1211_write_value(client, VT1211_REG_TEMP2_CONFIG, 0); ++} ++ ++static void vt1211_update_client(struct i2c_client *client) ++{ ++ struct vt1211_data *data = client->data; ++ int i, j; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ data->uch_config = vt_rdval(client, VT1211_REG_UCH_CONFIG); ++ for (i = 0; i <= 5; i++) { ++ if(ISVOLT(i, data->uch_config)) { ++ data->in[i] = vt_rdval(client, VT1211_REG_IN(i)); ++ data->in_min[i] = vt_rdval(client, ++ VT1211_REG_IN_MIN(i)); ++ data->in_max[i] = vt_rdval(client, ++ VT1211_REG_IN_MAX(i)); ++ } else { ++ data->in[i] = 0; ++ data->in_min[i] = 0; ++ data->in_max[i] = 0; ++ } ++ } ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = vt_rdval(client, VT1211_REG_FAN(i)); ++ data->fan_min[i - 1] = vt_rdval(client, ++ VT1211_REG_FAN_MIN(i)); ++ } ++ for (i = 2; i <= 7; i++) { ++ if(ISTEMP(i, data->uch_config)) { ++ data->temp[i - 1] = vt_rdval(client, ++ VT1211_REG_TEMP(i)) << 2; ++ switch(i) { ++ case 1: ++ /* ? */ ++ j = 0; ++ break; ++ case 2: ++ j = (vt_rdval(client, ++ VT1211_REG_TEMP_LOW2) & ++ 0x30) >> 4; ++ break; ++ case 3: ++ j = (vt_rdval(client, ++ VT1211_REG_TEMP_LOW3) & ++ 0xc0) >> 6; ++ break; ++ case 4: ++ case 5: ++ case 6: ++ case 7: ++ default: ++ j = (vt_rdval(client, ++ VT1211_REG_TEMP_LOW47) >> ++ ((i-4)*2)) & 0x03; ++ break; ++ ++ } ++ data->temp[i - 1] |= j; ++ data->temp_over[i - 1] = vt_rdval(client, ++ VT1211_REG_TEMP_OVER(i)); ++ data->temp_hyst[i - 1] = vt_rdval(client, ++ VT1211_REG_TEMP_HYST(i)); ++ } else { ++ data->temp[i - 1] = 0; ++ data->temp_over[i - 1] = 0; ++ data->temp_hyst[i - 1] = 0; ++ } ++ } ++ ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = vt_rdval(client, VT1211_REG_FAN(i)); ++ data->fan_min[i - 1] = vt_rdval(client, ++ VT1211_REG_FAN_MIN(i)); ++ data->pwm[i - 1] = vt_rdval(client, VT1211_REG_PWM(i)); ++ } ++ ++ data->pwm_ctl = vt_rdval(client, VT1211_REG_PWM_CTL); ++ i = vt_rdval(client, VT1211_REG_FANDIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = i >> 6; ++ data->alarms = vt_rdval(client, VT1211_REG_ALARM1) | ++ (vt_rdval(client, VT1211_REG_ALARM2) << 8); ++ data->vid= vt_rdval(client, VT1211_REG_VID) & 0x1f; ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void vt1211_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ int nr = ctl_name - VT1211_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ vt1211_write_value(client, VT1211_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ vt1211_write_value(client, VT1211_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void vt1211_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ int nr = ctl_name - VT1211_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div ++ [nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = MIN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr-1])); ++ vt1211_write_value(client, VT1211_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void vt1211_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ int nr = ctl_name - VT1211_SYSCTL_TEMP; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); ++ results[2] = TEMP_FROM_REG10(data->temp[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over[nr] = TEMP_TO_REG(results[0]); ++ vt1211_write_value(client, ++ VT1211_REG_TEMP_OVER(nr + 1), ++ data->temp_over[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst[nr] = TEMP_TO_REG(results[1]); ++ vt1211_write_value(client, ++ VT1211_REG_TEMP_HYST(nr + 1), ++ data->temp_hyst[nr]); ++ } ++ } ++} ++ ++void vt1211_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = data->alarms; ++ *nrels_mag = 1; ++ } ++} ++ ++void vt1211_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = vt_rdval(client, VT1211_REG_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ vt1211_write_value(client, VT1211_REG_FANDIV, old); ++ } ++ } ++} ++ ++void vt1211_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ int nr = 1 + ctl_name - VT1211_SYSCTL_PWM1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm[nr - 1]); ++ results[1] = (data->pwm_ctl >> (3 + (4 * (nr - 1)))) & 1; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->pwm[nr - 1] = PWM_TO_REG(results[0]); ++ if (*nrels_mag >= 2) { ++ if(results[1]) { ++ data->pwm_ctl |= ++ (0x08 << (4 * (nr - 1))); ++ vt1211_write_value(client, ++ VT1211_REG_PWM_CTL, ++ data->pwm_ctl); ++ } else { ++ data->pwm_ctl &= ++ ~ (0x08 << (4 * (nr - 1))); ++ vt1211_write_value(client, ++ VT1211_REG_PWM_CTL, ++ data->pwm_ctl); ++ } ++ } ++ vt1211_write_value(client, VT1211_REG_PWM(nr), ++ data->pwm[nr - 1]); ++ } ++ } ++} ++ ++void vt1211_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt1211_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void vt1211_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++void vt1211_uch(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt1211_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->uch_config & 0x7c; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->uch_config = (data->uch_config & 0x83)|(results[0] & 0x7c); ++ vt1211_write_value(client, VT1211_REG_UCH_CONFIG, ++ data->uch_config); ++ } ++ } ++} ++ ++static int __init sm_vt1211_init(void) ++{ ++ int addr; ++ ++ printk("vt1211.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ if (vt1211_find(&addr)) { ++ printk("vt1211.o: VT1211 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ normal_isa[0] = addr; ++ ++ return i2c_add_driver(&vt1211_driver); ++} ++ ++static void __exit sm_vt1211_exit(void) ++{ ++ i2c_del_driver(&vt1211_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("VT1211 sensors"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_vt1211_init); ++module_exit(sm_vt1211_exit); +--- linux-old/drivers/sensors/vt8231.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/vt8231.c Mon Dec 13 20:18:53 2004 +@@ -0,0 +1,794 @@ ++/* ++ vt8231.c - Part of lm_sensors, Linux kernel modules ++ for hardware monitoring ++ ++ Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* Supports VIA VT8231 South Bridge embedded sensors */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/pci.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++ ++ ++static int force_addr = 0; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the sensors"); ++ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++SENSORS_INSMOD_1(vt8231); ++ ++#define VIA686A_EXTENT 0x80 ++#define VIA686A_BASE_REG 0x70 ++#define VIA686A_ENABLE_REG 0x74 ++ ++/* pwm numbered 1-2 */ ++#define VT8231_REG_PWM(nr) (0x5f + (nr)) ++#define VT8231_REG_PWM_CTL 0x51 ++ ++/* The VT8231 registers */ ++/* We define the sensors as follows. Somewhat convoluted to minimize ++ changes from via686a. ++ Sensor Voltage Mode Temp Mode ++ -------- ------------ --------- ++ Reading 1 temp3 ++ Reading 3 temp1 not in vt8231 ++ UCH1/Reading2 in0 temp2 ++ UCH2 in1 temp4 ++ UCH3 in2 temp5 ++ UCH4 in3 temp6 ++ UCH5 in4 temp7 ++ 3.3V in5 ++ -12V in6 not in vt8231 ++*/ ++ ++/* ins numbered 0-6 */ ++#define VT8231_REG_IN_MAX(nr) ((nr)==0 ? 0x3d : 0x29 + ((nr) * 2)) ++#define VT8231_REG_IN_MIN(nr) ((nr)==0 ? 0x3e : 0x2a + ((nr) * 2)) ++#define VT8231_REG_IN(nr) (0x21 + (nr)) ++ ++/* fans numbered 1-2 */ ++#define VT8231_REG_FAN_MIN(nr) (0x3a + (nr)) ++#define VT8231_REG_FAN(nr) (0x28 + (nr)) ++ ++static const u8 regtemp[] = { 0x20, 0x21, 0x1f, 0x22, 0x23, 0x24, 0x25 }; ++static const u8 regover[] = { 0x39, 0x3d, 0x1d, 0x2b, 0x2d, 0x2f, 0x31 }; ++static const u8 reghyst[] = { 0x3a, 0x3e, 0x1e, 0x2c, 0x2e, 0x30, 0x32 }; ++ ++/* temps numbered 1-7 */ ++#define VT8231_REG_TEMP(nr) (regtemp[(nr) - 1]) ++#define VT8231_REG_TEMP_OVER(nr) (regover[(nr) - 1]) ++#define VT8231_REG_TEMP_HYST(nr) (reghyst[(nr) - 1]) ++#define VT8231_REG_TEMP_LOW3 0x4b /* bits 7-6 */ ++#define VT8231_REG_TEMP_LOW2 0x49 /* bits 5-4 */ ++#define VT8231_REG_TEMP_LOW47 0x4d ++ ++#define VT8231_REG_CONFIG 0x40 ++#define VT8231_REG_ALARM1 0x41 ++#define VT8231_REG_ALARM2 0x42 ++#define VT8231_REG_VID 0x45 ++#define VT8231_REG_FANDIV 0x47 ++#define VT8231_REG_UCH_CONFIG 0x4a ++#define VT8231_REG_TEMP1_CONFIG 0x4b ++#define VT8231_REG_TEMP2_CONFIG 0x4c ++ ++/* temps 1-7; voltages 0-6 */ ++#define ISTEMP(i, ch_config) ((i) == 1 ? 1 : \ ++ (i) == 3 ? 1 : \ ++ (i) == 2 ? ((ch_config) >> 1) & 0x01 : \ ++ ((ch_config) >> ((i)-1)) & 0x01) ++#define ISVOLT(i, ch_config) ((i) > 4 ? 1 : !(((ch_config) >> ((i)+2)) & 0x01)) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) ++#define PWM_FROM_REG(val) (val) ++#define PWM_TO_REG(val) SENSORS_LIMIT((val), 0, 255) ++ ++#define TEMP_FROM_REG(val) ((val)*10) ++#define TEMP_FROM_REG10(val) (((val)*10)/4) ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),0,255)) ++#define IN_FROM_REG(val) /*(((val)*10+5)/10)*/ (val) ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 5)/10),0,255)) ++ ++ ++/********* FAN RPM CONVERSIONS ********/ ++/* But this chip saturates back at 0, not at 255 like all the other chips. ++ So, 0 means 0 RPM */ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 0; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1310720 + rpm * div / 2) / (rpm * div), 1, 255); ++} ++ ++#define MIN_TO_REG(a,b) FAN_TO_REG(a,b) ++#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1310720/((val)*(div))) ++ ++struct vt8231_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 in[7]; /* Register value */ ++ u8 in_max[7]; /* Register value */ ++ u8 in_min[7]; /* Register value */ ++ u16 temp[7]; /* Register value 10 bit */ ++ u8 temp_over[7]; /* Register value */ ++ u8 temp_hyst[7]; /* Register value */ ++ u8 fan[2]; /* Register value */ ++ u8 fan_min[2]; /* Register value */ ++ u8 fan_div[2]; /* Register encoding, shifted right */ ++ u16 alarms; /* Register encoding */ ++ u8 pwm[2]; /* Register value */ ++ u8 pwm_ctl; /* Register value */ ++ u8 vid; /* Register encoding */ ++ u8 vrm; ++ u8 uch_config; ++}; ++ ++static int vt8231_attach_adapter(struct i2c_adapter *adapter); ++static int vt8231_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int vt8231_detach_client(struct i2c_client *client); ++ ++static inline int vt_rdval(struct i2c_client *client, u8 register); ++static inline void vt8231_write_value(struct i2c_client *client, u8 register, ++ u8 value); ++static void vt8231_update_client(struct i2c_client *client); ++static void vt8231_init_client(struct i2c_client *client); ++static int vt8231_find(int *address); ++ ++ ++static void vt8231_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_uch(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void vt8231_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int vt8231_id = 0; ++ ++static struct i2c_driver vt8231_driver = { ++ .owner = THIS_MODULE, ++ .name = "VT8231 sensors driver", ++ .id = I2C_DRIVERID_VT8231, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = vt8231_attach_adapter, ++ .detach_client = vt8231_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++#define VT8231_SYSCTL_IN0 1000 ++#define VT8231_SYSCTL_IN1 1001 ++#define VT8231_SYSCTL_IN2 1002 ++#define VT8231_SYSCTL_IN3 1003 ++#define VT8231_SYSCTL_IN4 1004 ++#define VT8231_SYSCTL_IN5 1005 ++#define VT8231_SYSCTL_IN6 1006 ++#define VT8231_SYSCTL_FAN1 1101 ++#define VT8231_SYSCTL_FAN2 1102 ++#define VT8231_SYSCTL_TEMP 1200 ++#define VT8231_SYSCTL_TEMP2 1201 ++#define VT8231_SYSCTL_TEMP3 1202 ++#define VT8231_SYSCTL_TEMP4 1203 ++#define VT8231_SYSCTL_TEMP5 1204 ++#define VT8231_SYSCTL_TEMP6 1205 ++#define VT8231_SYSCTL_TEMP7 1206 ++#define VT8231_SYSCTL_VID 1300 ++#define VT8231_SYSCTL_PWM1 1401 ++#define VT8231_SYSCTL_PWM2 1402 ++#define VT8231_SYSCTL_VRM 1600 ++#define VT8231_SYSCTL_UCH 1700 ++#define VT8231_SYSCTL_FAN_DIV 2000 ++#define VT8231_SYSCTL_ALARMS 2001 ++ ++#define VT8231_ALARM_IN1 0x01 ++#define VT8231_ALARM_IN2 0x02 ++#define VT8231_ALARM_IN5 0x04 ++#define VT8231_ALARM_IN3 0x08 ++#define VT8231_ALARM_TEMP 0x10 ++#define VT8231_ALARM_FAN1 0x40 ++#define VT8231_ALARM_FAN2 0x80 ++#define VT8231_ALARM_IN4 0x100 ++#define VT8231_ALARM_IN6 0x200 ++#define VT8231_ALARM_TEMP2 0x800 ++#define VT8231_ALARM_CHAS 0x1000 ++#define VT8231_ALARM_TEMP3 0x8000 ++/* duplicates */ ++#define VT8231_ALARM_IN0 VT8231_ALARM_TEMP ++#define VT8231_ALARM_TEMP4 VT8231_ALARM_IN1 ++#define VT8231_ALARM_TEMP5 VT8231_ALARM_IN2 ++#define VT8231_ALARM_TEMP6 VT8231_ALARM_IN3 ++#define VT8231_ALARM_TEMP7 VT8231_ALARM_IN4 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++static ctl_table vt8231_dir_table_template[] = { ++ {VT8231_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++ {VT8231_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++ {VT8231_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++ {VT8231_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++ {VT8231_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++ {VT8231_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++/* ++ not in 8231 ++ {VT8231_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_in}, ++ {VT8231_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_temp}, ++*/ ++ {VT8231_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, ++ {VT8231_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, ++ {VT8231_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, ++ {VT8231_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, ++ {VT8231_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, ++ {VT8231_SYSCTL_TEMP7, "temp7", NULL, 0, 0644, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, ++ {VT8231_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_fan}, ++ {VT8231_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_fan}, ++ {VT8231_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_fan_div}, ++ {VT8231_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_alarms}, ++ {VT8231_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_pwm}, ++ {VT8231_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_pwm}, ++ {VT8231_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_vid}, ++ {VT8231_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_vrm}, ++ {VT8231_SYSCTL_UCH, "uch_config", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &vt8231_uch}, ++ {0} ++}; ++ ++static struct pci_dev *s_bridge; ++ ++static int vt8231_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, vt8231_detect); ++} ++ ++/* Locate chip and get correct base address */ ++static int vt8231_find(int *address) ++{ ++ u16 val; ++ ++ if (!pci_present()) ++ return -ENODEV; ++ ++ if (!(s_bridge = pci_find_device(PCI_VENDOR_ID_VIA, ++ 0x8235, NULL))) ++ return -ENODEV; ++ ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(s_bridge, VIA686A_BASE_REG, &val)) ++ return -ENODEV; ++ *address = val & ~(VIA686A_EXTENT - 1); ++ if (*address == 0 && force_addr == 0) { ++ printk("vt8231.o: base address not set - upgrade BIOS or use force_addr=0xaddr\n"); ++ return -ENODEV; ++ } ++ if (force_addr) ++ *address = force_addr; /* so detect will get called */ ++ ++ return 0; ++} ++ ++int vt8231_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct vt8231_data *data; ++ int err = 0; ++ const char *type_name = "vt8231"; ++ u16 val; ++ ++ if (!i2c_is_isa_adapter(adapter)) { ++ return 0; ++ } ++ ++ /* 8231 requires multiple of 256 */ ++ if(force_addr) ++ address = force_addr & 0xFF00; ++ if (check_region(address, VIA686A_EXTENT)) { ++ printk("vt8231.o: region 0x%x already in use!\n", ++ address); ++ return -ENODEV; ++ } ++ ++ if(force_addr) { ++ printk("vt8231.o: forcing ISA address 0x%04X\n", address); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(s_bridge, VIA686A_BASE_REG, address)) ++ return -ENODEV; ++ } ++ if (PCIBIOS_SUCCESSFUL != ++ pci_read_config_word(s_bridge, VIA686A_ENABLE_REG, &val)) ++ return -ENODEV; ++ if (!(val & 0x0001)) { ++ printk("vt8231.o: enabling sensors\n"); ++ if (PCIBIOS_SUCCESSFUL != ++ pci_write_config_word(s_bridge, VIA686A_ENABLE_REG, ++ val | 0x0001)) ++ return -ENODEV; ++ } ++ ++ if (!(data = kmalloc(sizeof(struct vt8231_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &vt8231_driver; ++ new_client->flags = 0; ++ ++ /* Reserve the ISA region */ ++ request_region(address, VIA686A_EXTENT, "vt8231-sensors"); ++ ++ /* Fill in the remaining client fields and put into the global list */ ++ strcpy(new_client->name, "Via 8231 Integrated Sensors"); ++ ++ new_client->id = vt8231_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry((struct i2c_client *) new_client, ++ type_name, ++ vt8231_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR4; ++ } ++ data->sysctl_id = i; ++ ++ vt8231_init_client(new_client); ++ return 0; ++ ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ release_region(address, VIA686A_EXTENT); ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int vt8231_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct vt8231_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("vt8231.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ release_region(client->addr, VIA686A_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++static inline int vt_rdval(struct i2c_client *client, u8 reg) ++{ ++ return (inb_p(client->addr + reg)); ++} ++ ++static inline void vt8231_write_value(struct i2c_client *client, u8 reg, u8 value) ++{ ++ outb_p(value, client->addr + reg); ++} ++ ++static void vt8231_init_client(struct i2c_client *client) ++{ ++ struct vt8231_data *data = client->data; ++ ++ data->vrm = DEFAULT_VRM; ++ /* set "default" interrupt mode for alarms, which isn't the default */ ++ vt8231_write_value(client, VT8231_REG_TEMP1_CONFIG, 0); ++ vt8231_write_value(client, VT8231_REG_TEMP2_CONFIG, 0); ++} ++ ++static void vt8231_update_client(struct i2c_client *client) ++{ ++ struct vt8231_data *data = client->data; ++ int i, j; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ data->uch_config = vt_rdval(client, VT8231_REG_UCH_CONFIG); ++ for (i = 0; i <= 5; i++) { ++ if(ISVOLT(i, data->uch_config)) { ++ data->in[i] = vt_rdval(client, VT8231_REG_IN(i)); ++ data->in_min[i] = vt_rdval(client, ++ VT8231_REG_IN_MIN(i)); ++ data->in_max[i] = vt_rdval(client, ++ VT8231_REG_IN_MAX(i)); ++ } else { ++ data->in[i] = 0; ++ data->in_min[i] = 0; ++ data->in_max[i] = 0; ++ } ++ } ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = vt_rdval(client, VT8231_REG_FAN(i)); ++ data->fan_min[i - 1] = vt_rdval(client, ++ VT8231_REG_FAN_MIN(i)); ++ } ++ for (i = 2; i <= 7; i++) { ++ if(ISTEMP(i, data->uch_config)) { ++ data->temp[i - 1] = vt_rdval(client, ++ VT8231_REG_TEMP(i)) << 2; ++ switch(i) { ++ case 1: ++ /* ? */ ++ j = 0; ++ break; ++ case 2: ++ j = (vt_rdval(client, ++ VT8231_REG_TEMP_LOW2) & ++ 0x30) >> 4; ++ break; ++ case 3: ++ j = (vt_rdval(client, ++ VT8231_REG_TEMP_LOW3) & ++ 0xc0) >> 6; ++ break; ++ case 4: ++ case 5: ++ case 6: ++ case 7: ++ default: ++ j = (vt_rdval(client, ++ VT8231_REG_TEMP_LOW47) >> ++ ((i-4)*2)) & 0x03; ++ break; ++ ++ } ++ data->temp[i - 1] |= j; ++ data->temp_over[i - 1] = vt_rdval(client, ++ VT8231_REG_TEMP_OVER(i)); ++ data->temp_hyst[i - 1] = vt_rdval(client, ++ VT8231_REG_TEMP_HYST(i)); ++ } else { ++ data->temp[i - 1] = 0; ++ data->temp_over[i - 1] = 0; ++ data->temp_hyst[i - 1] = 0; ++ } ++ } ++ ++ for (i = 1; i <= 2; i++) { ++ data->fan[i - 1] = vt_rdval(client, VT8231_REG_FAN(i)); ++ data->fan_min[i - 1] = vt_rdval(client, ++ VT8231_REG_FAN_MIN(i)); ++ data->pwm[i - 1] = vt_rdval(client, VT8231_REG_PWM(i)); ++ } ++ ++ data->pwm_ctl = vt_rdval(client, VT8231_REG_PWM_CTL); ++ i = vt_rdval(client, VT8231_REG_FANDIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = i >> 6; ++ data->alarms = vt_rdval(client, VT8231_REG_ALARM1) | ++ (vt_rdval(client, VT8231_REG_ALARM2) << 8); ++ data->vid= vt_rdval(client, VT8231_REG_VID) & 0x1f; ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++void vt8231_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ int nr = ctl_name - VT8231_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ vt8231_write_value(client, VT8231_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ vt8231_write_value(client, VT8231_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void vt8231_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ int nr = ctl_name - VT8231_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div ++ [nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = MIN_TO_REG(results[0], ++ DIV_FROM_REG ++ (data-> ++ fan_div[nr-1])); ++ vt8231_write_value(client, VT8231_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++ ++void vt8231_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ int nr = ctl_name - VT8231_SYSCTL_TEMP; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over[nr]); ++ results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); ++ results[2] = TEMP_FROM_REG10(data->temp[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over[nr] = TEMP_TO_REG(results[0]); ++ vt8231_write_value(client, ++ VT8231_REG_TEMP_OVER(nr + 1), ++ data->temp_over[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst[nr] = TEMP_TO_REG(results[1]); ++ vt8231_write_value(client, ++ VT8231_REG_TEMP_HYST(nr + 1), ++ data->temp_hyst[nr]); ++ } ++ } ++} ++ ++void vt8231_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = data->alarms; ++ *nrels_mag = 1; ++ } ++} ++ ++void vt8231_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ int old; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = vt_rdval(client, VT8231_REG_FANDIV); ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = DIV_TO_REG(results[1]); ++ old = (old & 0x3f) | (data->fan_div[1] << 6); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = DIV_TO_REG(results[0]); ++ old = (old & 0xcf) | (data->fan_div[0] << 4); ++ vt8231_write_value(client, VT8231_REG_FANDIV, old); ++ } ++ } ++} ++ ++void vt8231_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ int nr = 1 + ctl_name - VT8231_SYSCTL_PWM1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm[nr - 1]); ++ results[1] = (data->pwm_ctl >> (3 + (4 * (nr - 1)))) & 1; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->pwm[nr - 1] = PWM_TO_REG(results[0]); ++ if (*nrels_mag >= 2) { ++ if(results[1]) { ++ data->pwm_ctl |= ++ (0x08 << (4 * (nr - 1))); ++ vt8231_write_value(client, ++ VT8231_REG_PWM_CTL, ++ data->pwm_ctl); ++ } else { ++ data->pwm_ctl &= ++ ~ (0x08 << (4 * (nr - 1))); ++ vt8231_write_value(client, ++ VT8231_REG_PWM_CTL, ++ data->pwm_ctl); ++ } ++ } ++ vt8231_write_value(client, VT8231_REG_PWM(nr), ++ data->pwm[nr - 1]); ++ } ++ } ++} ++ ++void vt8231_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ vt8231_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void vt8231_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++void vt8231_uch(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct vt8231_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->uch_config & 0x7c; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->uch_config = (data->uch_config & 0x83)|(results[0] & 0x7c); ++ vt8231_write_value(client, VT8231_REG_UCH_CONFIG, ++ data->uch_config); ++ } ++ } ++} ++ ++static int __init sm_vt8231_init(void) ++{ ++ int addr; ++ ++ printk("vt8231.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ ++ if (vt8231_find(&addr)) { ++ printk("vt8231.o: VT8231 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ normal_isa[0] = addr; ++ ++ return i2c_add_driver(&vt8231_driver);} ++ ++static void __exit sm_vt8231_exit(void) ++{ ++ i2c_del_driver(&vt8231_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("VT8231 sensors"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_vt8231_init); ++module_exit(sm_vt8231_exit); +--- linux-old/drivers/sensors/w83627hf.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/w83627hf.c Mon Dec 13 20:18:53 2004 +@@ -0,0 +1,1422 @@ ++/* ++ w83627hf.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998 - 2003 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ and Mark Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Supports following chips: ++ ++ Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA ++ w83627hf 9 3 2 3 0x20 0x5ca3 no yes(LPC) ++ w83627thf 7 3 3 3 0x90 0x5ca3 no yes(LPC) ++ w83637hf 7 3 3 3 0x80 0x5ca3 no yes(LPC) ++ w83697hf 8 2 2 2 0x60 0x5ca3 no yes(LPC) ++ ++ For other winbond chips, and for i2c support in the above chips, ++ use w83781d.c. ++ ++ Note: automatic ("cruise") fan control for 697, 637 & 627thf not ++ supported yet. ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++#include "lm75.h" ++ ++static int force_addr; ++MODULE_PARM(force_addr, "i"); ++MODULE_PARM_DESC(force_addr, ++ "Initialize the base address of the sensors"); ++static int force_i2c = 0x1f; ++MODULE_PARM(force_i2c, "i"); ++MODULE_PARM_DESC(force_i2c, ++ "Initialize the i2c address of the sensors"); ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_4(w83627hf, w83627thf, w83697hf, w83637hf); ++ ++static int init = 1; ++MODULE_PARM(init, "i"); ++MODULE_PARM_DESC(init, "Set to zero to bypass chip initialization"); ++ ++/* modified from kernel/include/traps.c */ ++#define REG 0x2e /* The register to read/write */ ++#define DEV 0x07 /* Register: Logical device select */ ++#define VAL 0x2f /* The value to read/write */ ++ ++/* logical device numbers for superio_select (below) */ ++#define W83627HF_LD_FDC 0x00 ++#define W83627HF_LD_PRT 0x01 ++#define W83627HF_LD_UART1 0x02 ++#define W83627HF_LD_UART2 0x03 ++#define W83627HF_LD_KBC 0x05 ++#define W83627HF_LD_CIR 0x06 /* w83627hf only */ ++#define W83627HF_LD_GAME 0x07 ++#define W83627HF_LD_MIDI 0x07 ++#define W83627HF_LD_GPIO1 0x07 ++#define W83627HF_LD_GPIO5 0x07 /* w83627thf only */ ++#define W83627HF_LD_GPIO2 0x08 ++#define W83627HF_LD_GPIO3 0x09 ++#define W83627HF_LD_GPIO4 0x09 /* w83627thf only */ ++#define W83627HF_LD_ACPI 0x0a ++#define W83627HF_LD_HWM 0x0b ++ ++#define DEVID 0x20 /* Register: Device ID */ ++ ++#define W83627THF_GPIO5_IOSR 0xf3 /* w83627thf only */ ++#define W83627THF_GPIO5_DR 0xf4 /* w83627thf only */ ++#define W83627THF_GPIO5_INVR 0xf5 /* w83627thf only */ ++ ++static inline void ++superio_outb(int reg, int val) ++{ ++ outb(reg, REG); ++ outb(val, VAL); ++} ++ ++static inline int ++superio_inb(int reg) ++{ ++ outb(reg, REG); ++ return inb(VAL); ++} ++ ++static inline void ++superio_select(int ld) ++{ ++ outb(DEV, REG); ++ outb(ld, VAL); ++} ++ ++static inline void ++superio_enter(void) ++{ ++ outb(0x87, REG); ++ outb(0x87, REG); ++} ++ ++static inline void ++superio_exit(void) ++{ ++ outb(0xAA, REG); ++} ++ ++#define W627_DEVID 0x52 ++#define W627THF_DEVID 0x82 ++#define W697_DEVID 0x60 ++#define W637_DEVID 0x70 ++#define WINB_ACT_REG 0x30 ++#define WINB_BASE_REG 0x60 ++/* Constants specified below */ ++ ++/* Length of ISA address segment */ ++#define WINB_EXTENT 8 ++ ++/* Where are the ISA address/data registers relative to the base address */ ++#define W83781D_ADDR_REG_OFFSET 5 ++#define W83781D_DATA_REG_OFFSET 6 ++ ++/* The W83781D registers */ ++/* The W83782D registers for nr=7,8 are in bank 5 */ ++#define W83781D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ ++ (0x554 + (((nr) - 7) * 2))) ++#define W83781D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ ++ (0x555 + (((nr) - 7) * 2))) ++#define W83781D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ ++ (0x550 + (nr) - 7)) ++ ++#define W83781D_REG_FAN_MIN(nr) (0x3a + (nr)) ++#define W83781D_REG_FAN(nr) (0x27 + (nr)) ++ ++#define W83781D_REG_TEMP2 0x0150 ++#define W83781D_REG_TEMP3 0x0250 ++#define W83781D_REG_TEMP2_HYST 0x153 ++#define W83781D_REG_TEMP3_HYST 0x253 ++#define W83781D_REG_TEMP2_CONFIG 0x152 ++#define W83781D_REG_TEMP3_CONFIG 0x252 ++#define W83781D_REG_TEMP2_OVER 0x155 ++#define W83781D_REG_TEMP3_OVER 0x255 ++ ++#define W83781D_REG_TEMP 0x27 ++#define W83781D_REG_TEMP_OVER 0x39 ++#define W83781D_REG_TEMP_HYST 0x3A ++#define W83781D_REG_BANK 0x4E ++ ++#define W83781D_REG_CONFIG 0x40 ++#define W83781D_REG_ALARM1 0x41 ++#define W83781D_REG_ALARM2 0x42 ++#define W83781D_REG_ALARM3 0x450 ++ ++#define W83781D_REG_IRQ 0x4C ++#define W83781D_REG_BEEP_CONFIG 0x4D ++#define W83781D_REG_BEEP_INTS1 0x56 ++#define W83781D_REG_BEEP_INTS2 0x57 ++#define W83781D_REG_BEEP_INTS3 0x453 ++ ++#define W83781D_REG_VID_FANDIV 0x47 ++ ++#define W83781D_REG_CHIPID 0x49 ++#define W83781D_REG_WCHIPID 0x58 ++#define W83781D_REG_CHIPMAN 0x4F ++#define W83781D_REG_PIN 0x4B ++ ++#define W83781D_REG_VBAT 0x5D ++ ++#define W83627HF_REG_PWM1 0x5A ++#define W83627HF_REG_PWM2 0x5B ++#define W83627HF_REG_PWMCLK12 0x5C ++ ++#define W83627THF_REG_PWM1 0x01 /* 697HF and 637HF too */ ++#define W83627THF_REG_PWM2 0x03 /* 697HF and 637HF too */ ++#define W83627THF_REG_PWM3 0x11 /* 637HF too */ ++ ++#define W83627THF_REG_VRM_OVT_CFG 0x18 /* 637HF too */ ++ ++static const u8 regpwm_627hf[] = { W83627HF_REG_PWM1, W83627HF_REG_PWM2 }; ++static const u8 regpwm[] = { W83627THF_REG_PWM1, W83627THF_REG_PWM2, ++ W83627THF_REG_PWM3 }; ++#define W836X7HF_REG_PWM(type, nr) (((type) == w83627hf) ? \ ++ regpwm_627hf[(nr) - 1] : regpwm[(nr) - 1]) ++ ++#define W83781D_REG_I2C_ADDR 0x48 ++#define W83781D_REG_I2C_SUBADDR 0x4A ++ ++/* Sensor selection */ ++#define W83781D_REG_SCFG1 0x5D ++static const u8 BIT_SCFG1[] = { 0x02, 0x04, 0x08 }; ++#define W83781D_REG_SCFG2 0x59 ++static const u8 BIT_SCFG2[] = { 0x10, 0x20, 0x40 }; ++#define W83781D_DEFAULT_BETA 3435 ++ ++/* Conversions. Limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) ++#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) ++ ++#define IN_TO_REG_VRM9(val) \ ++ (SENSORS_LIMIT((((val) * 1000 - 70000 + 244) / 488), 0, 255)) ++#define IN_FROM_REG_VRM9(reg) (((reg) * 488 + 70000 + 500) / 1000) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define TEMP_MIN (-1280) ++#define TEMP_MAX ( 1270) ++ ++/* TEMP: 1/10 degrees C (-128C to +127C) ++ REG: 1C/bit, two's complement */ ++static u8 TEMP_TO_REG(int temp) ++{ ++ int ntemp = SENSORS_LIMIT(temp, TEMP_MIN, TEMP_MAX); ++ ntemp += (ntemp<0 ? -5 : 5); ++ return (u8)(ntemp / 10); ++} ++ ++static int TEMP_FROM_REG(u8 reg) ++{ ++ return (s8)reg * 10; ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) ++ ++#define PWM_TO_REG(val) (SENSORS_LIMIT((val),0,255)) ++#define BEEPS_TO_REG(val) ((val) & 0xffffff) ++ ++#define BEEP_ENABLE_TO_REG(val) ((val)?1:0) ++#define BEEP_ENABLE_FROM_REG(val) ((val)?1:0) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++static inline u8 DIV_TO_REG(long val) ++{ ++ int i; ++ val = SENSORS_LIMIT(val, 1, 128) >> 1; ++ for (i = 0; i < 6; i++) { ++ if (val == 0) ++ break; ++ val >>= 1; ++ } ++ return ((u8) i); ++} ++ ++/* For each registered chip, we need to keep some data in memory. That ++ data is pointed to by w83627hf_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new client is allocated. */ ++struct w83627hf_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ struct i2c_client *lm75; /* for secondary I2C addresses */ ++ /* pointer to array of 2 subclients */ ++ ++ u8 in[9]; /* Register value */ ++ u8 in_max[9]; /* Register value */ ++ u8 in_min[9]; /* Register value */ ++ u8 fan[3]; /* Register value */ ++ u8 fan_min[3]; /* Register value */ ++ u8 temp; ++ u8 temp_over; /* Register value */ ++ u8 temp_hyst; /* Register value */ ++ u16 temp_add[2]; /* Register value */ ++ u16 temp_add_over[2]; /* Register value */ ++ u16 temp_add_hyst[2]; /* Register value */ ++ u8 fan_div[3]; /* Register encoding, shifted right */ ++ u8 vid; /* Register encoding, combined */ ++ u32 alarms; /* Register encoding, combined */ ++ u32 beeps; /* Register encoding, combined */ ++ u8 beep_enable; /* Boolean */ ++ u8 pwm[3]; /* Register value */ ++ u8 pwmenable[3]; /* bool */ ++ u16 sens[3]; /* 782D/783S only. ++ 1 = pentium diode; 2 = 3904 diode; ++ 3000-5000 = thermistor beta. ++ Default = 3435. ++ Other Betas unimplemented */ ++ u8 vrm; ++ u8 vrm_ovt; /* Register value, 627thf & 637hf only */ ++}; ++ ++ ++static int w83627hf_attach_adapter(struct i2c_adapter *adapter); ++static int w83627hf_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int w83627hf_detach_client(struct i2c_client *client); ++ ++static int w83627hf_read_value(struct i2c_client *client, u16 register); ++static int w83627hf_write_value(struct i2c_client *client, u16 register, ++ u16 value); ++static void w83627hf_update_client(struct i2c_client *client); ++static void w83627hf_init_client(struct i2c_client *client); ++ ++ ++static void w83627hf_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_beep(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83627hf_sens(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++ ++static int w83627hf_id = 0; ++ ++static struct i2c_driver w83627hf_driver = { ++ .owner = THIS_MODULE, ++ .name = "W83627HF sensor driver", ++ .id = I2C_DRIVERID_W83627HF, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = w83627hf_attach_adapter, ++ .detach_client = w83627hf_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++/* WARNING these are copied from w83781d.c and have not been renamed. ++ Note that the 627hf and 697hf are supported by both drivers. ++ Do not make incompatible changes here or we will have errors ++ in the generated file ../include/sensors.h !!! ++*/ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define W83781D_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define W83781D_SYSCTL_IN1 1001 ++#define W83781D_SYSCTL_IN2 1002 ++#define W83781D_SYSCTL_IN3 1003 ++#define W83781D_SYSCTL_IN4 1004 ++#define W83781D_SYSCTL_IN5 1005 ++#define W83781D_SYSCTL_IN6 1006 ++#define W83781D_SYSCTL_IN7 1007 ++#define W83781D_SYSCTL_IN8 1008 ++#define W83781D_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define W83781D_SYSCTL_FAN2 1102 ++#define W83781D_SYSCTL_FAN3 1103 ++#define W83781D_SYSCTL_TEMP1 1200 /* Degrees Celcius * 10 */ ++#define W83781D_SYSCTL_TEMP2 1201 /* Degrees Celcius * 10 */ ++#define W83781D_SYSCTL_TEMP3 1202 /* Degrees Celcius * 10 */ ++#define W83781D_SYSCTL_VID 1300 /* Volts * 1000 */ ++#define W83781D_SYSCTL_VRM 1301 ++#define W83781D_SYSCTL_PWM1 1401 ++#define W83781D_SYSCTL_PWM2 1402 ++#define W83781D_SYSCTL_PWM3 1403 ++#define W83781D_SYSCTL_SENS1 1501 /* 1, 2, or Beta (3000-5000) */ ++#define W83781D_SYSCTL_SENS2 1502 ++#define W83781D_SYSCTL_SENS3 1503 ++#define W83781D_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define W83781D_SYSCTL_ALARMS 2001 /* bitvector */ ++#define W83781D_SYSCTL_BEEP 2002 /* bitvector */ ++ ++#define W83781D_ALARM_IN0 0x0001 ++#define W83781D_ALARM_IN1 0x0002 ++#define W83781D_ALARM_IN2 0x0004 ++#define W83781D_ALARM_IN3 0x0008 ++#define W83781D_ALARM_IN4 0x0100 ++#define W83781D_ALARM_IN5 0x0200 ++#define W83781D_ALARM_IN6 0x0400 ++#define W83782D_ALARM_IN7 0x10000 ++#define W83782D_ALARM_IN8 0x20000 ++#define W83781D_ALARM_FAN1 0x0040 ++#define W83781D_ALARM_FAN2 0x0080 ++#define W83781D_ALARM_FAN3 0x0800 ++#define W83781D_ALARM_TEMP1 0x0010 ++#define W83781D_ALARM_TEMP23 0x0020 /* 781D only */ ++#define W83781D_ALARM_TEMP2 0x0020 /* 782D/783S */ ++#define W83781D_ALARM_TEMP3 0x2000 /* 782D only */ ++#define W83781D_ALARM_CHAS 0x1000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected chip. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++ ++/* without pwm3-4 */ ++static ctl_table w83627hf_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {0} ++}; ++ ++/* similar to w83782d but no fan3, no vid */ ++static ctl_table w83697hf_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ /* no in1 to maintain compatibility with 781d and 782d. */ ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp_add}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {0} ++}; ++ ++/* no in5 and in6 */ ++/* We use this one for W83637HF too */ ++static ctl_table w83627thf_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83627hf_sens}, ++ {0} ++}; ++ ++ ++/* This function is called when: ++ * w83627hf_driver is inserted (when this module is loaded), for each ++ available adapter ++ * when a new adapter is inserted (and w83627hf_driver is still present) */ ++static int w83627hf_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, w83627hf_detect); ++} ++ ++static int w83627hf_find(int *address) ++{ ++ u16 val; ++ ++ superio_enter(); ++ val= superio_inb(DEVID); ++ if(val != W627_DEVID && val !=W627THF_DEVID && val != W697_DEVID && val != W637_DEVID) { ++ superio_exit(); ++ return -ENODEV; ++ } ++ ++ superio_select(W83627HF_LD_HWM); ++ val = (superio_inb(WINB_BASE_REG) << 8) | ++ superio_inb(WINB_BASE_REG + 1); ++ *address = val & ~(WINB_EXTENT - 1); ++ if (*address == 0 && force_addr == 0) { ++ printk("w83627hf.o: base address not set - use force_addr=0xaddr\n"); ++ superio_exit(); ++ return -ENODEV; ++ } ++ if (force_addr) ++ *address = force_addr; /* so detect will get called */ ++ ++ superio_exit(); ++ return 0; ++} ++ ++int w83627hf_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, val; ++ struct i2c_client *new_client; ++ struct w83627hf_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ if (!i2c_is_isa_adapter(adapter)) ++ return 0; ++ ++ if(force_addr) ++ address = force_addr & ~(WINB_EXTENT - 1); ++ if (check_region(address, WINB_EXTENT)) { ++ printk("w83627hf.o: region 0x%x already in use!\n", address); ++ return -ENODEV; ++ } ++ if(force_addr) { ++ printk("w83627hf.o: forcing ISA address 0x%04X\n", address); ++ superio_enter(); ++ superio_select(W83627HF_LD_HWM); ++ superio_outb(WINB_BASE_REG, address >> 8); ++ superio_outb(WINB_BASE_REG+1, address & 0xff); ++ superio_exit(); ++ } ++ ++ superio_enter(); ++ val= superio_inb(DEVID); ++ if(val == W627_DEVID) ++ kind = w83627hf; ++ else if(val == W697_DEVID) ++ kind = w83697hf; ++ else if(val == W627THF_DEVID) ++ kind = w83627thf; ++ else if(val == W637_DEVID) ++ kind = w83637hf; ++ ++ superio_select(W83627HF_LD_HWM); ++ if((val = 0x01 & superio_inb(WINB_ACT_REG)) == 0) ++ superio_outb(WINB_ACT_REG, 1); ++ superio_exit(); ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access w83627hf_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct w83627hf_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &w83627hf_driver; ++ new_client->flags = 0; ++ ++ ++ if (kind == w83627hf) { ++ type_name = "w83627hf"; ++ client_name = "W83627HF chip"; ++ } else if (kind == w83627thf) { ++ type_name = "w83627thf"; ++ client_name = "W83627THF chip"; ++ } else if (kind == w83697hf) { ++ type_name = "w83697hf"; ++ client_name = "W83697HF chip"; ++ } else if (kind == w83637hf) { ++ type_name = "w83637hf"; ++ client_name = "W83637HF chip"; ++ } else { ++ goto ERROR1; ++ } ++ ++ request_region(address, WINB_EXTENT, type_name); ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ new_client->id = w83627hf_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ data->lm75 = NULL; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ (kind == w83697hf) ? ++ w83697hf_dir_table_template : ++ (kind == w83627hf) ? ++ w83627hf_dir_table_template : ++ /* w83627thf table also used for 637 */ ++ w83627thf_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR7; ++ } ++ data->sysctl_id = i; ++ ++ /* Initialize the chip */ ++ w83627hf_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR7: ++ i2c_detach_client(new_client); ++ ERROR3: ++ release_region(address, WINB_EXTENT); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int w83627hf_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct w83627hf_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ (KERN_ERR "w83627hf.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ release_region(client->addr, WINB_EXTENT); ++ kfree(client->data); ++ ++ return 0; ++} ++ ++ ++/* ++ ISA access must always be locked explicitly! ++ We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the W83781D access and should not be necessary. ++ There are some ugly typecasts here, but the good news is - they should ++ nowhere else be necessary! */ ++static int w83627hf_read_value(struct i2c_client *client, u16 reg) ++{ ++ int res, word_sized; ++ ++ down(&(((struct w83627hf_data *) (client->data))->lock)); ++ word_sized = (((reg & 0xff00) == 0x100) ++ || ((reg & 0xff00) == 0x200)) ++ && (((reg & 0x00ff) == 0x50) ++ || ((reg & 0x00ff) == 0x53) ++ || ((reg & 0x00ff) == 0x55)); ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(reg >> 8, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); ++ res = inb_p(client->addr + W83781D_DATA_REG_OFFSET); ++ if (word_sized) { ++ outb_p((reg & 0xff) + 1, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ res = ++ (res << 8) + inb_p(client->addr + ++ W83781D_DATA_REG_OFFSET); ++ } ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ up(&(((struct w83627hf_data *) (client->data))->lock)); ++ return res; ++} ++ ++static int w83627thf_read_gpio5(struct i2c_client *client) ++{ ++ int res, inv; ++ ++ down(&(((struct w83627hf_data *) (client->data))->lock)); ++ superio_enter(); ++ superio_select(W83627HF_LD_GPIO5); ++ res = superio_inb(W83627THF_GPIO5_DR); ++ inv = superio_inb(W83627THF_GPIO5_INVR); ++ superio_exit(); ++ up(&(((struct w83627hf_data *) (client->data))->lock)); ++ return res; ++} ++ ++static int w83627hf_write_value(struct i2c_client *client, u16 reg, u16 value) ++{ ++ int word_sized; ++ ++ down(&(((struct w83627hf_data *) (client->data))->lock)); ++ word_sized = (((reg & 0xff00) == 0x100) ++ || ((reg & 0xff00) == 0x200)) ++ && (((reg & 0x00ff) == 0x53) ++ || ((reg & 0x00ff) == 0x55)); ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(reg >> 8, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); ++ if (word_sized) { ++ outb_p(value >> 8, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ outb_p((reg & 0xff) + 1, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ } ++ outb_p(value & 0xff, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ up(&(((struct w83627hf_data *) (client->data))->lock)); ++ return 0; ++} ++ ++/* Called when we have found a new W83781D. It should set limits, etc. */ ++static void w83627hf_init_client(struct i2c_client *client) ++{ ++ struct w83627hf_data *data = client->data; ++ int i; ++ int type = data->type; ++ u8 tmp; ++ ++ /* Minimize conflicts with other winbond i2c-only clients... */ ++ /* disable i2c subclients... how to disable main i2c client?? */ ++ /* force i2c address to relatively uncommon address */ ++ w83627hf_write_value(client, W83781D_REG_I2C_SUBADDR, 0x89); ++ w83627hf_write_value(client, W83781D_REG_I2C_ADDR, force_i2c); ++ ++ /* Read VID only once */ ++ if (w83627hf == data->type || w83637hf == data->type) { ++ int lo = w83627hf_read_value(client, W83781D_REG_VID_FANDIV); ++ int hi = w83627hf_read_value(client, W83781D_REG_CHIPID); ++ data->vid = (lo & 0x0f) | ((hi & 0x01) << 4); ++ } else if (w83627thf == data->type) { ++ data->vid = w83627thf_read_gpio5(client) & 0x1f; ++ } ++ ++ /* Read VRM & OVT Config only once */ ++ if (w83627thf == data->type || w83637hf == data->type) ++ data->vrm_ovt = ++ w83627hf_read_value(client, W83627THF_REG_VRM_OVT_CFG); ++ else ++ data->vrm_ovt = 0; ++ ++ /* Choose VRM based on "VRM & OVT" register */ ++ data->vrm = (data->vrm_ovt & 0x01) ? 90 : 82; ++ ++ tmp = w83627hf_read_value(client, W83781D_REG_SCFG1); ++ for (i = 1; i <= 3; i++) { ++ if (!(tmp & BIT_SCFG1[i - 1])) { ++ data->sens[i - 1] = W83781D_DEFAULT_BETA; ++ } else { ++ if (w83627hf_read_value ++ (client, ++ W83781D_REG_SCFG2) & BIT_SCFG2[i - 1]) ++ data->sens[i - 1] = 1; ++ else ++ data->sens[i - 1] = 2; ++ } ++ if ((type == w83697hf) && (i == 2)) ++ break; ++ } ++ ++ data->pwmenable[0] = 1; ++ data->pwmenable[1] = 1; ++ data->pwmenable[2] = 1; ++ ++ if(init) { ++ if (type == w83627hf) { ++ /* enable PWM2 control (can't hurt since PWM reg ++ should have been reset to 0xff) */ ++ w83627hf_write_value(client, W83627HF_REG_PWMCLK12, 0x19); ++ } ++ /* enable comparator mode for temp2 and temp3 so ++ alarm indication will work correctly */ ++ i = w83627hf_read_value(client, W83781D_REG_IRQ); ++ if (!(i & 0x40)) ++ w83627hf_write_value(client, W83781D_REG_IRQ, ++ i | 0x40); ++ } ++ ++ /* Start monitoring */ ++ w83627hf_write_value(client, W83781D_REG_CONFIG, ++ (w83627hf_read_value(client, ++ W83781D_REG_CONFIG) & 0xf7) ++ | 0x01); ++} ++ ++static void w83627hf_update_client(struct i2c_client *client) ++{ ++ struct w83627hf_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ for (i = 0; i <= 8; i++) { ++ /* skip missing sensors */ ++ if (((data->type == w83697hf) && (i == 1)) || ++ ((data->type == w83627thf || data->type == w83637hf) && ++ (i == 4 || i == 5))) ++ continue; ++ data->in[i] = ++ w83627hf_read_value(client, W83781D_REG_IN(i)); ++ data->in_min[i] = ++ w83627hf_read_value(client, ++ W83781D_REG_IN_MIN(i)); ++ data->in_max[i] = ++ w83627hf_read_value(client, ++ W83781D_REG_IN_MAX(i)); ++ } ++ for (i = 1; i <= 3; i++) { ++ data->fan[i - 1] = ++ w83627hf_read_value(client, W83781D_REG_FAN(i)); ++ data->fan_min[i - 1] = ++ w83627hf_read_value(client, ++ W83781D_REG_FAN_MIN(i)); ++ } ++ for (i = 1; i <= 3; i++) { ++ u8 tmp = w83627hf_read_value(client, ++ W836X7HF_REG_PWM(data->type, i)); ++ if (data->type == w83627thf) ++ tmp &= 0xf0; /* bits 0-3 are reserved in 627THF */ ++ data->pwm[i - 1] = tmp; ++ if(i == 2 && (data->type == w83627hf || data->type == w83697hf)) ++ break; ++ } ++ ++ data->temp = w83627hf_read_value(client, W83781D_REG_TEMP); ++ data->temp_over = ++ w83627hf_read_value(client, W83781D_REG_TEMP_OVER); ++ data->temp_hyst = ++ w83627hf_read_value(client, W83781D_REG_TEMP_HYST); ++ data->temp_add[0] = ++ w83627hf_read_value(client, W83781D_REG_TEMP2); ++ data->temp_add_over[0] = ++ w83627hf_read_value(client, W83781D_REG_TEMP2_OVER); ++ data->temp_add_hyst[0] = ++ w83627hf_read_value(client, W83781D_REG_TEMP2_HYST); ++ if (data->type != w83697hf) { ++ data->temp_add[1] = ++ w83627hf_read_value(client, W83781D_REG_TEMP3); ++ data->temp_add_over[1] = ++ w83627hf_read_value(client, W83781D_REG_TEMP3_OVER); ++ data->temp_add_hyst[1] = ++ w83627hf_read_value(client, W83781D_REG_TEMP3_HYST); ++ } ++ ++ i = w83627hf_read_value(client, W83781D_REG_VID_FANDIV); ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = (i >> 6) & 0x03; ++ if (data->type != w83697hf) { ++ data->fan_div[2] = (w83627hf_read_value(client, ++ W83781D_REG_PIN) >> 6) & 0x03; ++ } ++ i = w83627hf_read_value(client, W83781D_REG_VBAT); ++ data->fan_div[0] |= (i >> 3) & 0x04; ++ data->fan_div[1] |= (i >> 4) & 0x04; ++ if (data->type != w83697hf) ++ data->fan_div[2] |= (i >> 5) & 0x04; ++ data->alarms = ++ w83627hf_read_value(client, W83781D_REG_ALARM1) | ++ (w83627hf_read_value(client, W83781D_REG_ALARM2) << 8) | ++ (w83627hf_read_value(client, W83781D_REG_ALARM3) << 16); ++ i = w83627hf_read_value(client, W83781D_REG_BEEP_INTS2); ++ data->beep_enable = i >> 7; ++ data->beeps = ((i & 0x7f) << 8) | ++ w83627hf_read_value(client, W83781D_REG_BEEP_INTS1) | ++ w83627hf_read_value(client, W83781D_REG_BEEP_INTS3) << 16; ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++void w83627hf_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int nr = ctl_name - W83781D_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ ++ if (nr == 0 && (data->vrm_ovt & 0x01)) { ++ /* use VRM9 calculation */ ++ results[0] = IN_FROM_REG_VRM9(data->in_min[0]); ++ results[1] = IN_FROM_REG_VRM9(data->in_max[0]); ++ results[2] = IN_FROM_REG_VRM9(data->in[0]); ++ ++ } else { ++ /* use VRM8 (standard) calculation */ ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ } ++ ++ *nrels_mag = 3; ++ ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (nr == 0 && (data->vrm_ovt & 0x01)) ++ /* use VRM9 calculation */ ++ data->in_min[0] = IN_TO_REG_VRM9(results[0]); ++ else ++ /* use VRM8 (standard) calculation */ ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ ++ w83627hf_write_value(client, W83781D_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ if (nr == 0 && (data->vrm_ovt & 0x01)) ++ /* use VRM9 calculation */ ++ data->in_min[0] = IN_TO_REG_VRM9(results[1]); ++ else ++ /* use VRM8 (standard) calculation */ ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ ++ w83627hf_write_value(client, W83781D_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void w83627hf_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int nr = ctl_name - W83781D_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = ++ FAN_TO_REG(results[0], ++ DIV_FROM_REG(data->fan_div[nr-1])); ++ w83627hf_write_value(client, ++ W83781D_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++void w83627hf_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over = TEMP_TO_REG(results[0]); ++ w83627hf_write_value(client, W83781D_REG_TEMP_OVER, ++ data->temp_over); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ w83627hf_write_value(client, W83781D_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++void w83627hf_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int nr = ctl_name - W83781D_SYSCTL_TEMP2; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = ++ LM75_TEMP_FROM_REG(data->temp_add_over[nr]); ++ results[1] = ++ LM75_TEMP_FROM_REG(data->temp_add_hyst[nr]); ++ results[2] = LM75_TEMP_FROM_REG(data->temp_add[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_add_over[nr] = ++ LM75_TEMP_TO_REG(results[0]); ++ w83627hf_write_value(client, ++ nr ? W83781D_REG_TEMP3_OVER : ++ W83781D_REG_TEMP2_OVER, ++ data->temp_add_over[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_add_hyst[nr] = ++ LM75_TEMP_TO_REG(results[1]); ++ w83627hf_write_value(client, ++ nr ? W83781D_REG_TEMP3_HYST : ++ W83781D_REG_TEMP2_HYST, ++ data->temp_add_hyst[nr]); ++ } ++ } ++} ++ ++void w83627hf_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void w83627hf_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++void w83627hf_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = data->alarms; ++ *nrels_mag = 1; ++ } ++} ++ ++void w83627hf_beep(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int val; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); ++ results[1] = data->beeps; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 2) { ++ data->beeps = BEEPS_TO_REG(results[1]); ++ w83627hf_write_value(client, W83781D_REG_BEEP_INTS1, ++ data->beeps & 0xff); ++ w83627hf_write_value(client, ++ W83781D_REG_BEEP_INTS3, ++ ((data-> beeps) >> 16) & ++ 0xff); ++ val = (data->beeps >> 8) & 0x7f; ++ } else if (*nrels_mag >= 1) ++ val = ++ w83627hf_read_value(client, ++ W83781D_REG_BEEP_INTS2) & ++ 0x7f; ++ if (*nrels_mag >= 1) { ++ data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); ++ w83627hf_write_value(client, W83781D_REG_BEEP_INTS2, ++ val | data->beep_enable << 7); ++ } ++ } ++} ++ ++/* w83697hf only has two fans */ ++void w83627hf_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int old, old2, old3 = 0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ if (data->type == w83697hf) { ++ *nrels_mag = 2; ++ } else { ++ results[2] = DIV_FROM_REG(data->fan_div[2]); ++ *nrels_mag = 3; ++ } ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = w83627hf_read_value(client, W83781D_REG_VID_FANDIV); ++ /* w83627hf doesn't have extended divisor bits */ ++ old3 = ++ w83627hf_read_value(client, W83781D_REG_VBAT); ++ if (*nrels_mag >= 3 && data->type != w83697hf) { ++ data->fan_div[2] = ++ DIV_TO_REG(results[2]); ++ old2 = w83627hf_read_value(client, W83781D_REG_PIN); ++ old2 = ++ (old2 & 0x3f) | ((data->fan_div[2] & 0x03) << 6); ++ w83627hf_write_value(client, W83781D_REG_PIN, old2); ++ old3 = ++ (old3 & 0x7f) | ++ ((data->fan_div[2] & 0x04) << 5); ++ } ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = ++ DIV_TO_REG(results[1]); ++ old = ++ (old & 0x3f) | ((data->fan_div[1] & 0x03) << 6); ++ old3 = ++ (old3 & 0xbf) | ++ ((data->fan_div[1] & 0x04) << 4); ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = ++ DIV_TO_REG(results[0]); ++ old = ++ (old & 0xcf) | ((data->fan_div[0] & 0x03) << 4); ++ w83627hf_write_value(client, W83781D_REG_VID_FANDIV, ++ old); ++ old3 = ++ (old3 & 0xdf) | ++ ((data->fan_div[0] & 0x04) << 3); ++ w83627hf_write_value(client, ++ W83781D_REG_VBAT, ++ old3); ++ } ++ } ++} ++ ++/* we do not currently support disabling PWM with 2nd argument; ++ set first argument to 255 to disable */ ++void w83627hf_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int nr = 1 + ctl_name - W83781D_SYSCTL_PWM1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83627hf_update_client(client); ++ results[0] = data->pwm[nr - 1]; ++ results[1] = data->pwmenable[nr - 1]; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (data->type == w83627thf) { ++ /* bits 0-3 are reserved in 627THF */ ++ data->pwm[nr - 1] = PWM_TO_REG(results[0]) & 0xf0; ++ w83627hf_write_value(client, ++ W836X7HF_REG_PWM(data->type, nr), ++ data->pwm[nr - 1] | ++ (w83627hf_read_value(client, ++ W836X7HF_REG_PWM(data->type, nr)) & 0x0f)); ++ } else { ++ data->pwm[nr - 1] = PWM_TO_REG(results[0]); ++ w83627hf_write_value(client, ++ W836X7HF_REG_PWM(data->type, nr), ++ data->pwm[nr - 1]); ++ } ++ } ++ } ++} ++ ++void w83627hf_sens(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83627hf_data *data = client->data; ++ int nr = 1 + ctl_name - W83781D_SYSCTL_SENS1; ++ u8 tmp; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->sens[nr - 1]; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ switch (results[0]) { ++ case 1: /* PII/Celeron diode */ ++ tmp = w83627hf_read_value(client, ++ W83781D_REG_SCFG1); ++ w83627hf_write_value(client, ++ W83781D_REG_SCFG1, ++ tmp | BIT_SCFG1[nr - ++ 1]); ++ tmp = w83627hf_read_value(client, ++ W83781D_REG_SCFG2); ++ w83627hf_write_value(client, ++ W83781D_REG_SCFG2, ++ tmp | BIT_SCFG2[nr - ++ 1]); ++ data->sens[nr - 1] = results[0]; ++ break; ++ case 2: /* 3904 */ ++ tmp = w83627hf_read_value(client, ++ W83781D_REG_SCFG1); ++ w83627hf_write_value(client, ++ W83781D_REG_SCFG1, ++ tmp | BIT_SCFG1[nr - ++ 1]); ++ tmp = w83627hf_read_value(client, ++ W83781D_REG_SCFG2); ++ w83627hf_write_value(client, ++ W83781D_REG_SCFG2, ++ tmp & ~BIT_SCFG2[nr - ++ 1]); ++ data->sens[nr - 1] = results[0]; ++ break; ++ case W83781D_DEFAULT_BETA: /* thermistor */ ++ tmp = w83627hf_read_value(client, ++ W83781D_REG_SCFG1); ++ w83627hf_write_value(client, ++ W83781D_REG_SCFG1, ++ tmp & ~BIT_SCFG1[nr - ++ 1]); ++ data->sens[nr - 1] = results[0]; ++ break; ++ default: ++ printk ++ (KERN_ERR "w83627hf.o: Invalid sensor type %ld; must be 1, 2, or %d\n", ++ results[0], W83781D_DEFAULT_BETA); ++ break; ++ } ++ } ++ } ++} ++ ++static int __init sm_w83627hf_init(void) ++{ ++ int addr; ++ ++ printk(KERN_INFO "w83627hf.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ if (w83627hf_find(&addr)) { ++ printk("w83627hf.o: W83627/697 not detected, module not inserted.\n"); ++ return -ENODEV; ++ } ++ normal_isa[0] = addr; ++ ++ return i2c_add_driver(&w83627hf_driver); ++} ++ ++static void __exit sm_w83627hf_exit(void) ++{ ++ i2c_del_driver(&w83627hf_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "and Mark Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("W83627HF driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_w83627hf_init); ++module_exit(sm_w83627hf_exit); +--- linux-old/drivers/sensors/w83781d.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/w83781d.c Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,1957 @@ ++/* ++ w83781d.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998 - 2003 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, ++ and Mark Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ Supports following chips: ++ ++ Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA ++ as99127f 7 3 0 3 0x31 0x12c3 yes no ++ as99127f rev.2 (type name = as99127f) 0x31 0x5ca3 yes no ++ w83627hf 9 3 2 3 0x21 0x5ca3 yes yes(LPC) ++ w83697hf 8 2 2 2 0x60 0x5ca3 no yes(LPC) ++ w83781d 7 3 0 3 0x10-1 0x5ca3 yes yes ++ w83782d 9 3 2-4 3 0x30 0x5ca3 yes yes ++ w83783s 5-6 3 2 1-2 0x40 0x5ca3 yes no ++ w83791d 10 5 5 3 0x71 0x5ca3 yes no ++ ++*/ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/ioport.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <asm/io.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++#include <linux/sensors_vid.h> ++#include "lm75.h" ++ ++/* RT Table support #defined so we can take it out if it gets bothersome */ ++#define W83781D_RT 1 ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { 0x0290, SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_7(w83781d, w83782d, w83783s, w83627hf, as99127f, w83697hf, w83791d); ++SENSORS_MODULE_PARM(force_subclients, "List of subclient addresses: " \ ++ "{bus, clientaddr, subclientaddr1, subclientaddr2}"); ++ ++static int init = 1; ++MODULE_PARM(init, "i"); ++MODULE_PARM_DESC(init, "Set to zero to bypass chip initialization"); ++ ++/* Constants specified below */ ++ ++/* Length of ISA address segment */ ++#define W83781D_EXTENT 8 ++ ++/* Where are the ISA address/data registers relative to the base address */ ++#define W83781D_ADDR_REG_OFFSET 5 ++#define W83781D_DATA_REG_OFFSET 6 ++ ++/* The W83781D registers */ ++/* The W83782D registers for nr=7,8 are in bank 5 */ ++#define W83781D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ ++ (0x554 + (((nr) - 7) * 2))) ++#define W83781D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ ++ (0x555 + (((nr) - 7) * 2))) ++#define W83781D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ ++ (0x550 + (nr) - 7)) ++ ++#define W83791D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ ++ (0xb4 + (((nr) - 7) * 2))) ++#define W83791D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ ++ (0xb5 + (((nr) - 7) * 2))) ++#define W83791D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ ++ (0xb0 + (nr) - 7)) ++ ++#define W83781D_REG_FAN_MIN(nr) ((nr < 4) ? (0x3a + (nr)) : \ ++ (0xba + (nr) - 4)) ++#define W83781D_REG_FAN(nr) ((nr < 4) ? (0x27 + (nr)) : \ ++ (0xbc + (nr) - 4)) ++ ++#define W83781D_REG_TEMP2 0x0150 ++#define W83781D_REG_TEMP3 0x0250 ++#define W83781D_REG_TEMP2_HYST 0x153 ++#define W83781D_REG_TEMP3_HYST 0x253 ++#define W83781D_REG_TEMP2_CONFIG 0x152 ++#define W83781D_REG_TEMP3_CONFIG 0x252 ++#define W83781D_REG_TEMP2_OVER 0x155 ++#define W83781D_REG_TEMP3_OVER 0x255 ++ ++#define W83781D_REG_TEMP 0x27 ++#define W83781D_REG_TEMP_OVER 0x39 ++#define W83781D_REG_TEMP_HYST 0x3A ++#define W83781D_REG_BANK 0x4E ++ ++#define W83781D_REG_CONFIG 0x40 ++#define W83781D_REG_ALARM1 0x41 ++#define W83781D_REG_ALARM2 0x42 ++#define W83781D_REG_ALARM3 0x450 /* not on W83781D */ ++ ++#define W83781D_REG_IRQ 0x4C ++#define W83781D_REG_BEEP_CONFIG 0x4D ++#define W83781D_REG_BEEP_INTS1 0x56 ++#define W83781D_REG_BEEP_INTS2 0x57 ++#define W83781D_REG_BEEP_INTS3 0x453 /* not on W83781D */ ++ ++#define W83781D_REG_VID_FANDIV 0x47 ++ ++#define W83781D_REG_CHIPID 0x49 ++#define W83781D_REG_WCHIPID 0x58 ++#define W83781D_REG_CHIPMAN 0x4F ++#define W83781D_REG_PIN 0x4B ++ ++/* 782D/783S only */ ++#define W83781D_REG_VBAT 0x5D ++ ++/* PWM 782D (1-4) and 783S (1-2) only */ ++#define W83781D_REG_PWM1 0x5B /* 782d and 783s/627hf datasheets disagree */ ++ /* on which is which; */ ++#define W83781D_REG_PWM2 0x5A /* We follow the 782d convention here, */ ++ /* However 782d is probably wrong. */ ++#define W83781D_REG_PWM3 0x5E ++#define W83781D_REG_PWM4 0x5F ++#define W83781D_REG_PWMCLK12 0x5C ++#define W83781D_REG_PWMCLK34 0x45C ++ ++#define W83791D_REG_PWM1 0x81 ++#define W83791D_REG_PWM2 0x83 ++#define W83791D_REG_PWM3 0x94 ++ ++#define W83627HF_REG_PWM1 0x01 ++#define W83627HF_REG_PWM2 0x03 ++#define W83627HF_REG_PWMCLK1 0x00 ++#define W83627HF_REG_PWMCLK2 0x02 ++ ++static const u8 regpwm[] = { W83781D_REG_PWM1, W83781D_REG_PWM2, ++ W83781D_REG_PWM3, W83781D_REG_PWM4 ++}; ++ ++static const u8 regpwm_w83791d[] = { W83791D_REG_PWM1, W83791D_REG_PWM2, ++ W83791D_REG_PWM3 ++}; ++ ++#define W83781D_REG_PWM(type, nr) (((type) == w83791d) ? \ ++ regpwm_w83791d[(nr) - 1] : \ ++ ((type) == w83697hf) ? \ ++ (((nr) * 2) - 1) : \ ++ regpwm[(nr) - 1]) ++ ++#define W83781D_REG_I2C_ADDR 0x48 ++#define W83781D_REG_I2C_SUBADDR 0x4A ++ ++/* The following are undocumented in the data sheets however we ++ received the information in an email from Winbond tech support */ ++/* Sensor selection - not on 781d */ ++#define W83781D_REG_SCFG1 0x5D ++static const u8 BIT_SCFG1[] = { 0x02, 0x04, 0x08 }; ++#define W83781D_REG_SCFG2 0x59 ++static const u8 BIT_SCFG2[] = { 0x10, 0x20, 0x40 }; ++#define W83781D_DEFAULT_BETA 3435 ++ ++/* RT Table registers */ ++#define W83781D_REG_RT_IDX 0x50 ++#define W83781D_REG_RT_VAL 0x51 ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) ++#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) ++ ++static inline u8 FAN_TO_REG(long rpm, int div) ++{ ++ if (rpm == 0) ++ return 255; ++ rpm = SENSORS_LIMIT(rpm, 1, 1000000); ++ return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, ++ 254); ++} ++ ++#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) ++ ++#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ ++ ((val)+5)/10),0,255)) ++#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) ++ ++#define AS99127_TEMP_ADD_TO_REG(val) (SENSORS_LIMIT((((((val) + 2)*4)/10) \ ++ << 7),0,0xffff)) ++#define AS99127_TEMP_ADD_FROM_REG(val) ((((val) >> 7) * 10) / 4) ++ ++#define ALARMS_FROM_REG(val) (val) ++#define PWM_FROM_REG(val) (val) ++#define PWM_TO_REG(val) (SENSORS_LIMIT((val),0,255)) ++#define BEEPS_FROM_REG(val,type) ((type)==as99127f?(val)^0x7FFF:(val)) ++#define BEEPS_TO_REG(val,type) ((type)==as99127f?(~(val))&0x7FFF:(val)&0xffffff) ++ ++#define BEEP_ENABLE_TO_REG(val) ((val)?1:0) ++#define BEEP_ENABLE_FROM_REG(val) ((val)?1:0) ++ ++#define DIV_FROM_REG(val) (1 << (val)) ++ ++static inline u8 DIV_TO_REG(long val, enum chips type) ++{ ++ int i; ++ val = SENSORS_LIMIT(val, 1, ++ ((type == w83781d || type == as99127f) ? 8 : 128)) >> 1; ++ for (i = 0; i < 6; i++) { ++ if (val == 0) ++ break; ++ val >>= 1; ++ } ++ return ((u8) i); ++} ++ ++/* There are some complications in a module like this. First off, W83781D chips ++ may be both present on the SMBus and the ISA bus, and we have to handle ++ those cases separately at some places. Second, there might be several ++ W83781D chips available (well, actually, that is probably never done; but ++ it is a clean illustration of how to handle a case like that). Finally, ++ a specific chip may be attached to *both* ISA and SMBus, and we would ++ not like to detect it double. Fortunately, in the case of the W83781D at ++ least, a register tells us what SMBus address we are on, so that helps ++ a bit - except if there could be more than one SMBus. Groan. No solution ++ for this yet. */ ++ ++/* This module may seem overly long and complicated. In fact, it is not so ++ bad. Quite a lot of bookkeeping is done. A real driver can often cut ++ some corners. */ ++ ++/* For each registered W83781D, we need to keep some data in memory. That ++ data is pointed to by w83781d_list[NR]->data. The structure itself is ++ dynamically allocated, at the same time when a new w83781d client is ++ allocated. */ ++struct w83781d_data { ++ struct i2c_client client; ++ struct semaphore lock; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ struct i2c_client *lm75; /* for secondary I2C addresses */ ++ /* pointer to array of 2 subclients */ ++ ++ u8 in[10]; /* Register value - 8 & 9 for 782D and 791D only 10 for 791D */ ++ u8 in_max[10]; /* Register value - 8 & 9 for 782D and 791D only 10 for 791D */ ++ u8 in_min[10]; /* Register value - 8 & 9 for 782D and 791D only 10 for 791D */ ++ u8 fan[5]; /* Register value - 4 & 5 for 791D only */ ++ u8 fan_min[5]; /* Register value - 4 & 5 for 791D only */ ++ u8 temp; ++ u8 temp_over; /* Register value */ ++ u8 temp_hyst; /* Register value */ ++ u16 temp_add[2]; /* Register value */ ++ u16 temp_add_over[2]; /* Register value */ ++ u16 temp_add_hyst[2]; /* Register value */ ++ u8 fan_div[3]; /* Register encoding, shifted right */ ++ u8 vid; /* Register encoding, combined */ ++ u32 alarms; /* Register encoding, combined */ ++ u32 beeps; /* Register encoding, combined */ ++ u8 beep_enable; /* Boolean */ ++ u8 pwm[4]; /* Register value */ ++ u8 pwmenable[4]; /* bool */ ++ u16 sens[3]; /* 782D/783S only. ++ 1 = pentium diode; 2 = 3904 diode; ++ 3000-5000 = thermistor beta. ++ Default = 3435. ++ Other Betas unimplemented */ ++#ifdef W83781D_RT ++ u8 rt[3][32]; /* Register value */ ++#endif ++ u8 vrm; ++}; ++ ++ ++static int w83781d_attach_adapter(struct i2c_adapter *adapter); ++static int w83781d_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static int w83781d_detach_client(struct i2c_client *client); ++ ++static int w83781d_read_value(struct i2c_client *client, u16 register); ++static int w83781d_write_value(struct i2c_client *client, u16 register, ++ u16 value); ++static void w83781d_update_client(struct i2c_client *client); ++static void w83781d_init_client(struct i2c_client *client); ++ ++ ++static void w83781d_in(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_fan(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_vid(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_vrm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_beep(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_pwm(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void w83781d_sens(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++#ifdef W83781D_RT ++static void w83781d_rt(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++#endif ++ ++static int w83781d_id = 0; ++ ++static struct i2c_driver w83781d_driver = { ++ .owner = THIS_MODULE, ++ .name = "W83781D sensor driver", ++ .id = I2C_DRIVERID_W83781D, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = w83781d_attach_adapter, ++ .detach_client = w83781d_detach_client, ++}; ++ ++/* The /proc/sys entries */ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define W83781D_SYSCTL_IN0 1000 /* Volts * 100 */ ++#define W83781D_SYSCTL_IN1 1001 ++#define W83781D_SYSCTL_IN2 1002 ++#define W83781D_SYSCTL_IN3 1003 ++#define W83781D_SYSCTL_IN4 1004 ++#define W83781D_SYSCTL_IN5 1005 ++#define W83781D_SYSCTL_IN6 1006 ++#define W83781D_SYSCTL_IN7 1007 ++#define W83781D_SYSCTL_IN8 1008 ++#define W83781D_SYSCTL_IN9 1009 ++#define W83781D_SYSCTL_FAN1 1101 /* Rotations/min */ ++#define W83781D_SYSCTL_FAN2 1102 ++#define W83781D_SYSCTL_FAN3 1103 ++#define W83781D_SYSCTL_FAN4 1104 ++#define W83781D_SYSCTL_FAN5 1105 ++ ++#define W83781D_SYSCTL_TEMP1 1200 /* Degrees Celcius * 10 */ ++#define W83781D_SYSCTL_TEMP2 1201 /* Degrees Celcius * 10 */ ++#define W83781D_SYSCTL_TEMP3 1202 /* Degrees Celcius * 10 */ ++#define W83781D_SYSCTL_VID 1300 /* Volts * 1000 */ ++#define W83781D_SYSCTL_VRM 1301 ++#define W83781D_SYSCTL_PWM1 1401 ++#define W83781D_SYSCTL_PWM2 1402 ++#define W83781D_SYSCTL_PWM3 1403 ++#define W83781D_SYSCTL_PWM4 1404 ++#define W83781D_SYSCTL_SENS1 1501 /* 1, 2, or Beta (3000-5000) */ ++#define W83781D_SYSCTL_SENS2 1502 ++#define W83781D_SYSCTL_SENS3 1503 ++#define W83781D_SYSCTL_RT1 1601 /* 32-entry table */ ++#define W83781D_SYSCTL_RT2 1602 /* 32-entry table */ ++#define W83781D_SYSCTL_RT3 1603 /* 32-entry table */ ++#define W83781D_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ ++#define W83781D_SYSCTL_ALARMS 2001 /* bitvector */ ++#define W83781D_SYSCTL_BEEP 2002 /* bitvector */ ++ ++#define W83781D_ALARM_IN0 0x0001 ++#define W83781D_ALARM_IN1 0x0002 ++#define W83781D_ALARM_IN2 0x0004 ++#define W83781D_ALARM_IN3 0x0008 ++#define W83781D_ALARM_IN4 0x0100 ++#define W83781D_ALARM_IN5 0x0200 ++#define W83781D_ALARM_IN6 0x0400 ++#define W83782D_ALARM_IN7 0x10000 ++#define W83782D_ALARM_IN8 0x20000 ++#define W83781D_ALARM_FAN1 0x0040 ++#define W83781D_ALARM_FAN2 0x0080 ++#define W83781D_ALARM_FAN3 0x0800 ++#define W83781D_ALARM_TEMP1 0x0010 ++#define W83781D_ALARM_TEMP23 0x0020 /* 781D only */ ++#define W83781D_ALARM_TEMP2 0x0020 /* 782D/783S */ ++#define W83781D_ALARM_TEMP3 0x2000 /* 782D only */ ++#define W83781D_ALARM_CHAS 0x1000 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected chip. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++ ++/* just a guess - no datasheet */ ++static ctl_table as99127f_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++ {0} ++}; ++ ++static ctl_table w83781d_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++#ifdef W83781D_RT ++ {W83781D_SYSCTL_RT1, "rt1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_rt}, ++ {W83781D_SYSCTL_RT2, "rt2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_rt}, ++ {W83781D_SYSCTL_RT3, "rt3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_rt}, ++#endif ++ {0} ++}; ++ ++/* without pwm3-4 */ ++static ctl_table w83782d_isa_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {0} ++}; ++ ++/* with pwm3-4 */ ++static ctl_table w83782d_i2c_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM4, "pwm4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {0} ++}; ++ ++/* w83791D has 10 voltages 5 fans and 3 temps. 2 of the temps are on other ++ devices. */ ++static ctl_table w83791d_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN9, "in9", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN5, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vid}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM4, "pwm4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vrm}, ++ {0} ++}; ++ ++static ctl_table w83783s_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ /* no in1 to maintain compatibility with 781d and 782d. */ ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vid}, ++ {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_vrm}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {0} ++}; ++ ++/* similar to w83782d but no fan3, no vid */ ++static ctl_table w83697hf_dir_table_template[] = { ++ {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ /* no in1 to maintain compatibility with 781d and 782d. */ ++ {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_in}, ++ {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan}, ++ {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp}, ++ {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_temp_add}, ++ {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_fan_div}, ++ {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_alarms}, ++ {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_beep}, ++ {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_pwm}, ++ {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &w83781d_sens}, ++ {0} ++}; ++ ++ ++/* This function is called when: ++ * w83781d_driver is inserted (when this module is loaded), for each ++ available adapter ++ * when a new adapter is inserted (and w83781d_driver is still present) */ ++static int w83781d_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, w83781d_detect); ++} ++ ++static int w83781d_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i, val1 = 0, val2, id; ++ struct i2c_client *new_client; ++ struct w83781d_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ int is_isa = i2c_is_isa_adapter(adapter); ++ enum vendor { winbond, asus } vendid; ++ ++ if (!is_isa ++ && !i2c_check_functionality(adapter, ++ I2C_FUNC_SMBUS_BYTE_DATA)) goto ++ ERROR0; ++ ++ if (is_isa) { ++ if (!request_region(address, W83781D_EXTENT, "w83781d")) ++ goto ERROR0; ++ release_region(address, W83781D_EXTENT); ++ } ++ ++ /* Probe whether there is anything available on this address. Already ++ done for SMBus clients */ ++ if (kind < 0) { ++ if (is_isa) { ++ ++#define REALLY_SLOW_IO ++ /* We need the timeouts for at least some LM78-like chips. But only ++ if we read 'undefined' registers. */ ++ i = inb_p(address + 1); ++ if (inb_p(address + 2) != i) ++ goto ERROR0; ++ if (inb_p(address + 3) != i) ++ goto ERROR0; ++ if (inb_p(address + 7) != i) ++ goto ERROR0; ++#undef REALLY_SLOW_IO ++ ++ /* Let's just hope nothing breaks here */ ++ i = inb_p(address + 5) & 0x7f; ++ outb_p(~i & 0x7f, address + 5); ++ if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) { ++ outb_p(i, address + 5); ++ return 0; ++ } ++ } ++ } ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access w83781d_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct w83781d_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ init_MUTEX(&data->lock); ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &w83781d_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ /* The w8378?d may be stuck in some other bank than bank 0. This may ++ make reading other information impossible. Specify a force=... or ++ force_*=... parameter, and the Winbond will be reset to the right ++ bank. */ ++ if (kind < 0) { ++ if (w83781d_read_value(new_client, W83781D_REG_CONFIG) & ++ 0x80) { ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ val1 = w83781d_read_value(new_client, W83781D_REG_BANK); ++ val2 = w83781d_read_value(new_client, W83781D_REG_CHIPMAN); ++ /* Check for Winbond or Asus ID if in bank 0 */ ++ if ((!(val1 & 0x07)) && ++ (((!(val1 & 0x80)) && (val2 != 0xa3) && (val2 != 0xc3)) ++ || ((val1 & 0x80) && (val2 != 0x5c) && (val2 != 0x12)))) { ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ /* If Winbond SMBus, check address at 0x48. ++ Asus doesn't support, except for the as99127f rev.2 */ ++ if ((!is_isa) && (((!(val1 & 0x80)) && (val2 == 0xa3)) || ++ ((val1 & 0x80) && (val2 == 0x5c)))) { ++ if (w83781d_read_value ++ (new_client, W83781D_REG_I2C_ADDR) != address) { ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ } ++ } ++ ++ /* We have either had a force parameter, or we have already detected the ++ Winbond. Put it now into bank 0 and Vendor ID High Byte */ ++ w83781d_write_value(new_client, W83781D_REG_BANK, ++ (w83781d_read_value(new_client, ++ W83781D_REG_BANK) & 0x78) | ++ 0x80); ++ ++ /* Determine the chip type. */ ++ if (kind <= 0) { ++ /* get vendor ID */ ++ val2 = w83781d_read_value(new_client, W83781D_REG_CHIPMAN); ++ if (val2 == 0x5c) ++ vendid = winbond; ++ else if (val2 == 0x12) ++ vendid = asus; ++ else { ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ val1 = ++ w83781d_read_value(new_client, W83781D_REG_WCHIPID); ++ if ((val1 == 0x10 || val1 == 0x11) && vendid == winbond) ++ kind = w83781d; ++ else if (val1 == 0x30 && vendid == winbond) ++ kind = w83782d; ++ else if (val1 == 0x40 && vendid == winbond && !is_isa && address == 0x2d) ++ kind = w83783s; ++ else if (val1 == 0x21 && vendid == winbond) ++ kind = w83627hf; ++ else if (val1 == 0x71 && vendid == winbond && address >= 0x2c) ++ kind = w83791d; ++ else if (val1 == 0x31 && !is_isa && address >= 0x28) ++ kind = as99127f; ++ else if (val1 == 0x60 && vendid == winbond && is_isa) ++ kind = w83697hf; ++ else { ++ if (kind == 0) ++ printk ++ (KERN_WARNING "w83781d.o: Ignoring 'force' parameter for unknown chip at" ++ "adapter %d, address 0x%02x\n", ++ i2c_adapter_id(adapter), address); ++ err = -EINVAL; ++ goto ERROR1; ++ } ++ } ++ ++ if (kind == w83781d) { ++ type_name = "w83781d"; ++ client_name = "W83781D chip"; ++ } else if (kind == w83782d) { ++ type_name = "w83782d"; ++ client_name = "W83782D chip"; ++ } else if (kind == w83783s) { ++ type_name = "w83783s"; ++ client_name = "W83783S chip"; ++ } else if (kind == w83627hf) { ++ type_name = "w83627hf"; ++ client_name = "W83627HF chip"; ++ } else if (kind == as99127f) { ++ type_name = "as99127f"; ++ client_name = "AS99127F chip"; ++ } else if (kind == w83697hf) { ++ type_name = "w83697hf"; ++ client_name = "W83697HF chip"; ++ } else if (kind == w83791d) { ++ type_name = "w83791d"; ++ client_name = "W83791D chip"; ++ } else { ++#ifdef DEBUG ++ printk(KERN_ERR "w83781d.o: Internal error: unknown kind (%d)?!?", ++ kind); ++#endif ++ err = -ENODEV; ++ goto ERROR1; ++ } ++ ++ /* Reserve the ISA region */ ++ if (is_isa) ++ request_region(address, W83781D_EXTENT, type_name); ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = w83781d_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto ERROR3; ++ ++ /* attach secondary i2c lm75-like clients */ ++ if (!is_isa) { ++ if (!(data->lm75 = kmalloc(2 * sizeof(struct i2c_client), ++ GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto ERROR4; ++ } ++ id = i2c_adapter_id(adapter); ++ if(force_subclients[0] == id && force_subclients[1] == address) { ++ for(i = 2; i <= 3; i++) { ++ if(force_subclients[i] < 0x48 || ++ force_subclients[i] > 0x4f) { ++ printk(KERN_ERR "w83781d.o: Invalid subclient address %d; must be 0x48-0x4f\n", ++ force_subclients[i]); ++ err = -EINVAL; ++ goto ERROR5; ++ } ++ } ++ w83781d_write_value(new_client, ++ W83781D_REG_I2C_SUBADDR, ++ (force_subclients[2] & 0x07) | ++ ((force_subclients[3] & 0x07) <<4)); ++ data->lm75[0].addr = force_subclients[2]; ++ } else { ++ val1 = w83781d_read_value(new_client, ++ W83781D_REG_I2C_SUBADDR); ++ data->lm75[0].addr = 0x48 + (val1 & 0x07); ++ } ++ if (kind != w83783s) { ++ if(force_subclients[0] == id && ++ force_subclients[1] == address) { ++ data->lm75[1].addr = force_subclients[3]; ++ } else { ++ data->lm75[1].addr = 0x48 + ((val1 >> 4) & 0x07); ++ } ++ if(data->lm75[0].addr == data->lm75[1].addr) { ++ printk(KERN_ERR "w83781d.o: Duplicate addresses 0x%x for subclients.\n", ++ data->lm75[0].addr); ++ err = -EBUSY; ++ goto ERROR5; ++ } ++ } ++ if (kind == w83781d) ++ client_name = "W83781D subclient"; ++ else if (kind == w83782d) ++ client_name = "W83782D subclient"; ++ else if (kind == w83783s) ++ client_name = "W83783S subclient"; ++ else if (kind == w83627hf) ++ client_name = "W83627HF subclient"; ++ else if (kind == as99127f) ++ client_name = "AS99127F subclient"; ++ else if (kind == w83791d) ++ client_name = "W83791D subclient"; ++ ++ ++ for (i = 0; i <= 1; i++) { ++ data->lm75[i].data = NULL; /* store all data in w83781d */ ++ data->lm75[i].adapter = adapter; ++ data->lm75[i].driver = &w83781d_driver; ++ data->lm75[i].flags = 0; ++ strcpy(data->lm75[i].name, client_name); ++ data->lm75[i].id = w83781d_id++; ++ if ((err = i2c_attach_client(&(data->lm75[i])))) { ++ printk(KERN_ERR "w83781d.o: Subclient %d registration at address 0x%x failed.\n", ++ i, data->lm75[i].addr); ++ if (i == 1) ++ goto ERROR6; ++ goto ERROR5; ++ } ++ if (kind == w83783s) ++ break; ++ } ++ } else { ++ data->lm75 = NULL; ++ } ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, ++ type_name, ++ (kind == as99127f) ? ++ as99127f_dir_table_template : ++ (kind == w83781d) ? ++ w83781d_dir_table_template : ++ (kind == w83783s) ? ++ w83783s_dir_table_template : ++ (kind == w83697hf) ? ++ w83697hf_dir_table_template : ++ (kind == w83791d ) ? ++ w83791d_dir_table_template : ++ (is_isa || kind == w83627hf) ? ++ w83782d_isa_dir_table_template : ++ w83782d_i2c_dir_table_template)) < 0) { ++ err = i; ++ goto ERROR7; ++ } ++ data->sysctl_id = i; ++ ++ /* Only PWM2 can be disabled */ ++ for(i = 0; i < 4; i++) ++ data->pwmenable[i] = 1; ++ ++ /* Initialize the chip */ ++ w83781d_init_client(new_client); ++ return 0; ++ ++/* OK, this is not exactly good programming practice, usually. But it is ++ very code-efficient in this case. */ ++ ++ ERROR7: ++ if (!is_isa) ++ i2c_detach_client(& ++ (((struct ++ w83781d_data *) (new_client->data))-> ++ lm75[1])); ++ ERROR6: ++ if (!is_isa) ++ i2c_detach_client(& ++ (((struct ++ w83781d_data *) (new_client->data))-> ++ lm75[0])); ++ ERROR5: ++ if (!is_isa) ++ kfree(((struct w83781d_data *) (new_client->data))->lm75); ++ ERROR4: ++ i2c_detach_client(new_client); ++ ERROR3: ++ if (is_isa) ++ release_region(address, W83781D_EXTENT); ++ ERROR1: ++ kfree(data); ++ ERROR0: ++ return err; ++} ++ ++static int w83781d_detach_client(struct i2c_client *client) ++{ ++ int err; ++ struct w83781d_data *data = client->data; ++ ++ i2c_deregister_entry(data->sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ (KERN_ERR "w83781d.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ if(i2c_is_isa_client(client)) { ++ release_region(client->addr, W83781D_EXTENT); ++ } else { ++ i2c_detach_client(&(data->lm75[0])); ++ if (data->type != w83783s) ++ i2c_detach_client(&(data->lm75[1])); ++ kfree(data->lm75); ++ } ++ kfree(data); ++ ++ return 0; ++} ++ ++/* The SMBus locks itself, usually, but nothing may access the Winbond between ++ bank switches. ISA access must always be locked explicitly! ++ We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, ++ would slow down the W83781D access and should not be necessary. ++ There are some ugly typecasts here, but the good news is - they should ++ nowhere else be necessary! */ ++static int w83781d_read_value(struct i2c_client *client, u16 reg) ++{ ++ int res, word_sized, bank; ++ struct i2c_client *cl; ++ ++ down(&(((struct w83781d_data *) (client->data))->lock)); ++ if (i2c_is_isa_client(client)) { ++ word_sized = (((reg & 0xff00) == 0x100) ++ || ((reg & 0xff00) == 0x200)) ++ && (((reg & 0x00ff) == 0x50) ++ || ((reg & 0x00ff) == 0x53) ++ || ((reg & 0x00ff) == 0x55)); ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(reg >> 8, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); ++ res = inb_p(client->addr + W83781D_DATA_REG_OFFSET); ++ if (word_sized) { ++ outb_p((reg & 0xff) + 1, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ res = ++ (res << 8) + inb_p(client->addr + ++ W83781D_DATA_REG_OFFSET); ++ } ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ } else { ++ bank = (reg >> 8) & 0x0f; ++ if (bank > 2) ++ /* switch banks */ ++ i2c_smbus_write_byte_data(client, W83781D_REG_BANK, ++ bank); ++ if (bank == 0 || bank > 2) { ++ res = i2c_smbus_read_byte_data(client, reg & 0xff); ++ } else { ++ /* switch to subclient */ ++ cl = ++ &(((struct w83781d_data *) (client->data))-> ++ lm75[bank - 1]); ++ /* convert from ISA to LM75 I2C addresses */ ++ switch (reg & 0xff) { ++ case 0x50: /* TEMP */ ++ res = swab16(i2c_smbus_read_word_data(cl, 0)); ++ break; ++ case 0x52: /* CONFIG */ ++ res = i2c_smbus_read_byte_data(cl, 1); ++ break; ++ case 0x53: /* HYST */ ++ res = swab16(i2c_smbus_read_word_data(cl, 2)); ++ break; ++ case 0x55: /* OVER */ ++ default: ++ res = swab16(i2c_smbus_read_word_data(cl, 3)); ++ break; ++ } ++ } ++ if (bank > 2) ++ i2c_smbus_write_byte_data(client, W83781D_REG_BANK, ++ 0); ++ } ++ up(&(((struct w83781d_data *) (client->data))->lock)); ++ return res; ++} ++ ++static int w83781d_write_value(struct i2c_client *client, u16 reg, u16 value) ++{ ++ int word_sized, bank; ++ struct i2c_client *cl; ++ ++ down(&(((struct w83781d_data *) (client->data))->lock)); ++ if (i2c_is_isa_client(client)) { ++ word_sized = (((reg & 0xff00) == 0x100) ++ || ((reg & 0xff00) == 0x200)) ++ && (((reg & 0x00ff) == 0x53) ++ || ((reg & 0x00ff) == 0x55)); ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(reg >> 8, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); ++ if (word_sized) { ++ outb_p(value >> 8, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ outb_p((reg & 0xff) + 1, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ } ++ outb_p(value & 0xff, ++ client->addr + W83781D_DATA_REG_OFFSET); ++ if (reg & 0xff00) { ++ outb_p(W83781D_REG_BANK, ++ client->addr + W83781D_ADDR_REG_OFFSET); ++ outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); ++ } ++ } else { ++ bank = (reg >> 8) & 0x0f; ++ if (bank > 2) ++ /* switch banks */ ++ i2c_smbus_write_byte_data(client, W83781D_REG_BANK, ++ bank); ++ if (bank == 0 || bank > 2) { ++ i2c_smbus_write_byte_data(client, reg & 0xff, ++ value & 0xff); ++ } else { ++ /* switch to subclient */ ++ cl = &(((struct w83781d_data *) (client->data))-> ++ lm75[bank - 1]); ++ /* convert from ISA to LM75 I2C addresses */ ++ switch (reg & 0xff) { ++ case 0x52: /* CONFIG */ ++ i2c_smbus_write_byte_data(cl, 1, value & 0xff); ++ break; ++ case 0x53: /* HYST */ ++ i2c_smbus_write_word_data(cl, 2, swab16(value)); ++ break; ++ case 0x55: /* OVER */ ++ i2c_smbus_write_word_data(cl, 3, swab16(value)); ++ break; ++ } ++ } ++ if (bank > 2) ++ i2c_smbus_write_byte_data(client, W83781D_REG_BANK, ++ 0); ++ } ++ up(&(((struct w83781d_data *) (client->data))->lock)); ++ return 0; ++} ++ ++/* Called when we have found a new W83781D. It should set limits, etc. */ ++static void w83781d_init_client(struct i2c_client *client) ++{ ++ struct w83781d_data *data = client->data; ++ int i, p; ++ int type = data->type; ++ u8 tmp; ++ ++ if(init && type != as99127f) { /* this resets registers we don't have ++ documentation for on the as99127f */ ++ /* save these registers */ ++ i = w83781d_read_value(client, W83781D_REG_BEEP_CONFIG); ++ p = w83781d_read_value(client, W83781D_REG_PWMCLK12); ++ /* Reset all except Watchdog values and last conversion values ++ This sets fan-divs to 2, among others */ ++ w83781d_write_value(client, W83781D_REG_CONFIG, 0x80); ++ /* Restore the registers and disable power-on abnormal beep. ++ This saves FAN 1/2/3 input/output values set by BIOS. */ ++ w83781d_write_value(client, W83781D_REG_BEEP_CONFIG, i | 0x80); ++ w83781d_write_value(client, W83781D_REG_PWMCLK12, p); ++ /* Disable master beep-enable (reset turns it on). ++ Individual beeps should be reset to off but for some reason ++ disabling this bit helps some people not get beeped */ ++ w83781d_write_value(client, W83781D_REG_BEEP_INTS2, 0); ++ } ++ ++ data->vrm = (type == w83791d) ? 90 : 82; ++ ++ if ((type != w83781d) && (type != as99127f)) { ++ tmp = w83781d_read_value(client, W83781D_REG_SCFG1); ++ for (i = 1; i <= 3; i++) { ++ if (!(tmp & BIT_SCFG1[i - 1])) { ++ data->sens[i - 1] = W83781D_DEFAULT_BETA; ++ } else { ++ if (w83781d_read_value ++ (client, ++ W83781D_REG_SCFG2) & BIT_SCFG2[i - 1]) ++ data->sens[i - 1] = 1; ++ else ++ data->sens[i - 1] = 2; ++ } ++ if ((type == w83783s || type == w83697hf) && (i == 2)) ++ break; ++ } ++ } ++#ifdef W83781D_RT ++/* ++ Fill up the RT Tables. ++ We assume that they are 32 bytes long, in order for temp 1-3. ++ Data sheet documentation is sparse. ++ We also assume that it is only for the 781D although I suspect ++ that the others support it as well.... ++*/ ++ ++ if (init && type == w83781d) { ++ u16 k = 0; ++/* ++ Auto-indexing doesn't seem to work... ++ w83781d_write_value(client,W83781D_REG_RT_IDX,0); ++*/ ++ for (i = 0; i < 3; i++) { ++ int j; ++ for (j = 0; j < 32; j++) { ++ w83781d_write_value(client, ++ W83781D_REG_RT_IDX, ++ k++); ++ data->rt[i][j] = ++ w83781d_read_value(client, ++ W83781D_REG_RT_VAL); ++ } ++ } ++ } ++#endif /* W83781D_RT */ ++ ++ if(init) { ++ w83781d_write_value(client, W83781D_REG_TEMP2_CONFIG, 0x00); ++ if (type != w83783s && type != w83697hf) { ++ w83781d_write_value(client, W83781D_REG_TEMP3_CONFIG, ++ 0x00); ++ } ++ if (type != w83781d) { ++ /* enable comparator mode for temp2 and temp3 so ++ alarm indication will work correctly */ ++ i = w83781d_read_value(client, W83781D_REG_IRQ); ++ if (!(i & 0x40)) ++ w83781d_write_value(client, W83781D_REG_IRQ, ++ i | 0x40); ++ } ++ } ++ ++ /* Start monitoring */ ++ w83781d_write_value(client, W83781D_REG_CONFIG, ++ (w83781d_read_value(client, ++ W83781D_REG_CONFIG) & 0xf7) ++ | 0x01); ++} ++ ++static void w83781d_update_client(struct i2c_client *client) ++{ ++ struct w83781d_data *data = client->data; ++ int i; ++ ++ down(&data->update_lock); ++ ++ if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || ++ time_before(jiffies, data->last_updated) || !data->valid) { ++ pr_debug(KERN_DEBUG "Starting device update\n"); ++ ++ for (i = 0; i <= 9; i++) { ++ if ((data->type == w83783s || data->type == w83697hf) ++ && (i == 1)) ++ continue; /* 783S has no in1 */ ++ if (data->type == w83791d) { ++ data->in[i] = ++ w83781d_read_value(client, W83791D_REG_IN(i)); ++ data->in_min[i] = ++ w83781d_read_value(client, ++ W83791D_REG_IN_MIN(i)); ++ data->in_max[i] = ++ w83781d_read_value(client, ++ W83791D_REG_IN_MAX(i)); ++ } else { ++ data->in[i] = ++ w83781d_read_value(client, W83781D_REG_IN(i)); ++ data->in_min[i] = ++ w83781d_read_value(client, ++ W83781D_REG_IN_MIN(i)); ++ data->in_max[i] = ++ w83781d_read_value(client, ++ W83781D_REG_IN_MAX(i)); ++ } ++ if ((data->type != w83782d) && (data->type != w83697hf) ++ && (data->type != w83627hf) && (i == 6) ++ && (data->type != w83791d)) ++ break; ++ ++ if (data->type != w83791d && i == 8) ++ break; ++ } ++ for (i = 1; i <= 5; i++) { ++ data->fan[i - 1] = ++ w83781d_read_value(client, W83781D_REG_FAN(i)); ++ data->fan_min[i - 1] = ++ w83781d_read_value(client, ++ W83781D_REG_FAN_MIN(i)); ++ if (data->type != w83791d && i == 3) break; ++ } ++ if (data->type != w83781d && data->type != as99127f) { ++ for (i = 1; i <= 4; i++) { ++ data->pwm[i - 1] = ++ w83781d_read_value(client, ++ W83781D_REG_PWM(data->type, i)); ++ if (((data->type == w83783s) ++ || (data->type == w83627hf) ++ || (data->type == w83697hf) ++ || ((data->type == w83782d) ++ && i2c_is_isa_client(client))) ++ && i == 2) ++ break; ++ } ++ /* Only PWM2 can be disabled */ ++ data->pwmenable[1] = (w83781d_read_value(client, ++ W83781D_REG_PWMCLK12) & 0x08) >> 3; ++ } ++ ++ data->temp = w83781d_read_value(client, W83781D_REG_TEMP); ++ data->temp_over = ++ w83781d_read_value(client, W83781D_REG_TEMP_OVER); ++ data->temp_hyst = ++ w83781d_read_value(client, W83781D_REG_TEMP_HYST); ++ data->temp_add[0] = ++ w83781d_read_value(client, W83781D_REG_TEMP2); ++ data->temp_add_over[0] = ++ w83781d_read_value(client, W83781D_REG_TEMP2_OVER); ++ data->temp_add_hyst[0] = ++ w83781d_read_value(client, W83781D_REG_TEMP2_HYST); ++ if (data->type != w83783s && data->type != w83697hf) { ++ data->temp_add[1] = ++ w83781d_read_value(client, W83781D_REG_TEMP3); ++ data->temp_add_over[1] = ++ w83781d_read_value(client, W83781D_REG_TEMP3_OVER); ++ data->temp_add_hyst[1] = ++ w83781d_read_value(client, W83781D_REG_TEMP3_HYST); ++ } ++ i = w83781d_read_value(client, W83781D_REG_VID_FANDIV); ++ if (data->type != w83697hf) { ++ data->vid = i & 0x0f; ++ data->vid |= ++ (w83781d_read_value(client, W83781D_REG_CHIPID) & 0x01) ++ << 4; ++ } ++ data->fan_div[0] = (i >> 4) & 0x03; ++ data->fan_div[1] = (i >> 6) & 0x03; ++ if (data->type != w83697hf) { ++ data->fan_div[2] = (w83781d_read_value(client, ++ W83781D_REG_PIN) >> 6) & 0x03; ++ } ++ if ((data->type != w83781d) && (data->type != as99127f)) { ++ i = w83781d_read_value(client, W83781D_REG_VBAT); ++ data->fan_div[0] |= (i >> 3) & 0x04; ++ data->fan_div[1] |= (i >> 4) & 0x04; ++ if (data->type != w83697hf) ++ data->fan_div[2] |= (i >> 5) & 0x04; ++ } ++ data->alarms = ++ w83781d_read_value(client, ++ W83781D_REG_ALARM1) + ++ (w83781d_read_value(client, W83781D_REG_ALARM2) << 8); ++ if ((data->type == w83782d) || (data->type == w83627hf) || ++ (data->type == w83697hf)) { ++ data->alarms |= ++ w83781d_read_value(client, ++ W83781D_REG_ALARM3) << 16; ++ } ++ i = w83781d_read_value(client, W83781D_REG_BEEP_INTS2); ++ data->beep_enable = i >> 7; ++ data->beeps = ((i & 0x7f) << 8) + ++ w83781d_read_value(client, W83781D_REG_BEEP_INTS1); ++ if ((data->type != w83781d) && (data->type != as99127f) ++ && (data->type != w83791d)) { ++ data->beeps |= ++ w83781d_read_value(client, ++ W83781D_REG_BEEP_INTS3) << 16; ++ } ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++ ++/* The next few functions are the call-back functions of the /proc/sys and ++ sysctl files. Which function is used is defined in the ctl_table in ++ the extra1 field. ++ Each function must return the magnitude (power of 10 to divide the date ++ with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must ++ put a maximum of *nrels elements in results reflecting the data of this ++ file, and set *nrels to the number it actually put in it, if operation== ++ SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from ++ results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. ++ Note that on SENSORS_PROC_REAL_READ, I do not check whether results is ++ large enough (by checking the incoming value of *nrels). This is not very ++ good practice, but as long as you put less than about 5 values in results, ++ you can assume it is large enough. */ ++static void w83781d_in(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int nr = ctl_name - W83781D_SYSCTL_IN0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 2; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = IN_FROM_REG(data->in_min[nr]); ++ results[1] = IN_FROM_REG(data->in_max[nr]); ++ results[2] = IN_FROM_REG(data->in[nr]); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->in_min[nr] = IN_TO_REG(results[0]); ++ w83781d_write_value(client, W83781D_REG_IN_MIN(nr), ++ data->in_min[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ data->in_max[nr] = IN_TO_REG(results[1]); ++ w83781d_write_value(client, W83781D_REG_IN_MAX(nr), ++ data->in_max[nr]); ++ } ++ } ++} ++ ++void w83781d_fan(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int nr = ctl_name - W83781D_SYSCTL_FAN1 + 1; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = FAN_FROM_REG(data->fan_min[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ results[1] = FAN_FROM_REG(data->fan[nr - 1], ++ DIV_FROM_REG(data->fan_div[nr - 1])); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->fan_min[nr - 1] = ++ FAN_TO_REG(results[0], ++ DIV_FROM_REG(data->fan_div[nr-1])); ++ w83781d_write_value(client, ++ W83781D_REG_FAN_MIN(nr), ++ data->fan_min[nr - 1]); ++ } ++ } ++} ++ ++void w83781d_temp(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp_hyst); ++ results[2] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->temp_over = TEMP_TO_REG(results[0]); ++ w83781d_write_value(client, W83781D_REG_TEMP_OVER, ++ data->temp_over); ++ } ++ if (*nrels_mag >= 2) { ++ data->temp_hyst = TEMP_TO_REG(results[1]); ++ w83781d_write_value(client, W83781D_REG_TEMP_HYST, ++ data->temp_hyst); ++ } ++ } ++} ++ ++void w83781d_temp_add(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int nr = ctl_name - W83781D_SYSCTL_TEMP2; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ if (data->type == as99127f) { ++ results[0] = ++ AS99127_TEMP_ADD_FROM_REG(data-> ++ temp_add_over[nr]); ++ results[1] = ++ AS99127_TEMP_ADD_FROM_REG(data-> ++ temp_add_hyst[nr]); ++ results[2] = ++ AS99127_TEMP_ADD_FROM_REG(data->temp_add[nr]); ++ } else { ++ results[0] = ++ LM75_TEMP_FROM_REG(data->temp_add_over[nr]); ++ results[1] = ++ LM75_TEMP_FROM_REG(data->temp_add_hyst[nr]); ++ results[2] = LM75_TEMP_FROM_REG(data->temp_add[nr]); ++ } ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ if (data->type == as99127f) ++ data->temp_add_over[nr] = ++ AS99127_TEMP_ADD_TO_REG(results[0]); ++ else ++ data->temp_add_over[nr] = ++ LM75_TEMP_TO_REG(results[0]); ++ w83781d_write_value(client, ++ nr ? W83781D_REG_TEMP3_OVER : ++ W83781D_REG_TEMP2_OVER, ++ data->temp_add_over[nr]); ++ } ++ if (*nrels_mag >= 2) { ++ if (data->type == as99127f) ++ data->temp_add_hyst[nr] = ++ AS99127_TEMP_ADD_TO_REG(results[1]); ++ else ++ data->temp_add_hyst[nr] = ++ LM75_TEMP_TO_REG(results[1]); ++ w83781d_write_value(client, ++ nr ? W83781D_REG_TEMP3_HYST : ++ W83781D_REG_TEMP2_HYST, ++ data->temp_add_hyst[nr]); ++ } ++ } ++} ++ ++ ++void w83781d_vid(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 3; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = vid_from_reg(data->vid, data->vrm); ++ *nrels_mag = 1; ++ } ++} ++ ++void w83781d_vrm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 1; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->vrm; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) ++ data->vrm = results[0]; ++ } ++} ++ ++void w83781d_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = ALARMS_FROM_REG(data->alarms); ++ *nrels_mag = 1; ++ } ++} ++ ++void w83781d_beep(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int val; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); ++ results[1] = BEEPS_FROM_REG(data->beeps, data->type); ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 2) { ++ data->beeps = BEEPS_TO_REG(results[1], data->type); ++ w83781d_write_value(client, W83781D_REG_BEEP_INTS1, ++ data->beeps & 0xff); ++ if ((data->type != w83781d) && ++ (data->type != as99127f)) { ++ w83781d_write_value(client, ++ W83781D_REG_BEEP_INTS3, ++ ((data-> beeps) >> 16) & ++ 0xff); ++ } ++ val = (data->beeps >> 8) & 0x7f; ++ } else if (*nrels_mag >= 1) ++ val = ++ w83781d_read_value(client, ++ W83781D_REG_BEEP_INTS2) & ++ 0x7f; ++ if (*nrels_mag >= 1) { ++ data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); ++ w83781d_write_value(client, W83781D_REG_BEEP_INTS2, ++ val | data->beep_enable << 7); ++ } ++ } ++} ++ ++/* w83697hf only has two fans */ ++void w83781d_fan_div(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int old, old2, old3 = 0; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = DIV_FROM_REG(data->fan_div[0]); ++ results[1] = DIV_FROM_REG(data->fan_div[1]); ++ if (data->type == w83697hf) { ++ *nrels_mag = 2; ++ } else { ++ results[2] = DIV_FROM_REG(data->fan_div[2]); ++ *nrels_mag = 3; ++ } ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ old = w83781d_read_value(client, W83781D_REG_VID_FANDIV); ++ /* w83781d and as99127f don't have extended divisor bits */ ++ if ((data->type != w83781d) && data->type != as99127f) { ++ old3 = ++ w83781d_read_value(client, W83781D_REG_VBAT); ++ } ++ if (*nrels_mag >= 3 && data->type != w83697hf) { ++ data->fan_div[2] = ++ DIV_TO_REG(results[2], data->type); ++ old2 = w83781d_read_value(client, W83781D_REG_PIN); ++ old2 = ++ (old2 & 0x3f) | ((data->fan_div[2] & 0x03) << 6); ++ w83781d_write_value(client, W83781D_REG_PIN, old2); ++ if ((data->type != w83781d) && ++ (data->type != as99127f)) { ++ old3 = ++ (old3 & 0x7f) | ++ ((data->fan_div[2] & 0x04) << 5); ++ } ++ } ++ if (*nrels_mag >= 2) { ++ data->fan_div[1] = ++ DIV_TO_REG(results[1], data->type); ++ old = ++ (old & 0x3f) | ((data->fan_div[1] & 0x03) << 6); ++ if ((data->type != w83781d) && ++ (data->type != as99127f)) { ++ old3 = ++ (old3 & 0xbf) | ++ ((data->fan_div[1] & 0x04) << 4); ++ } ++ } ++ if (*nrels_mag >= 1) { ++ data->fan_div[0] = ++ DIV_TO_REG(results[0], data->type); ++ old = ++ (old & 0xcf) | ((data->fan_div[0] & 0x03) << 4); ++ w83781d_write_value(client, W83781D_REG_VID_FANDIV, ++ old); ++ if ((data->type != w83781d) && ++ (data->type != as99127f)) { ++ old3 = ++ (old3 & 0xdf) | ++ ((data->fan_div[0] & 0x04) << 3); ++ w83781d_write_value(client, ++ W83781D_REG_VBAT, ++ old3); ++ } ++ } ++ } ++} ++ ++void w83781d_pwm(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int nr = 1 + ctl_name - W83781D_SYSCTL_PWM1; ++ int j, k; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ w83781d_update_client(client); ++ results[0] = PWM_FROM_REG(data->pwm[nr - 1]); ++ results[1] = data->pwmenable[nr - 1]; ++ *nrels_mag = 2; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->pwm[nr - 1] = PWM_TO_REG(results[0]); ++ w83781d_write_value(client, ++ W83781D_REG_PWM(data->type, nr), ++ data->pwm[nr - 1]); ++ } ++ /* only PWM2 can be enabled/disabled */ ++ if (*nrels_mag >= 2 && nr == 2) { ++ j = w83781d_read_value(client, W83781D_REG_PWMCLK12); ++ k = w83781d_read_value(client, W83781D_REG_BEEP_CONFIG); ++ if(results[1]) { ++ if(!(j & 0x08)) ++ w83781d_write_value(client, ++ W83781D_REG_PWMCLK12, j | 0x08); ++ if(k & 0x10) ++ w83781d_write_value(client, ++ W83781D_REG_BEEP_CONFIG, k & 0xef); ++ data->pwmenable[1] = 1; ++ } else { ++ if(j & 0x08) ++ w83781d_write_value(client, ++ W83781D_REG_PWMCLK12, j & 0xf7); ++ if(!(k & 0x10)) ++ w83781d_write_value(client, ++ W83781D_REG_BEEP_CONFIG, j | 0x10); ++ data->pwmenable[1] = 0; ++ } ++ } ++ } ++} ++ ++void w83781d_sens(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int nr = 1 + ctl_name - W83781D_SYSCTL_SENS1; ++ u8 tmp; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ results[0] = data->sens[nr - 1]; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ switch (results[0]) { ++ case 1: /* PII/Celeron diode */ ++ tmp = w83781d_read_value(client, ++ W83781D_REG_SCFG1); ++ w83781d_write_value(client, ++ W83781D_REG_SCFG1, ++ tmp | BIT_SCFG1[nr - ++ 1]); ++ tmp = w83781d_read_value(client, ++ W83781D_REG_SCFG2); ++ w83781d_write_value(client, ++ W83781D_REG_SCFG2, ++ tmp | BIT_SCFG2[nr - ++ 1]); ++ data->sens[nr - 1] = results[0]; ++ break; ++ case 2: /* 3904 */ ++ tmp = w83781d_read_value(client, ++ W83781D_REG_SCFG1); ++ w83781d_write_value(client, ++ W83781D_REG_SCFG1, ++ tmp | BIT_SCFG1[nr - ++ 1]); ++ tmp = w83781d_read_value(client, ++ W83781D_REG_SCFG2); ++ w83781d_write_value(client, ++ W83781D_REG_SCFG2, ++ tmp & ~BIT_SCFG2[nr - ++ 1]); ++ data->sens[nr - 1] = results[0]; ++ break; ++ case W83781D_DEFAULT_BETA: /* thermistor */ ++ tmp = w83781d_read_value(client, ++ W83781D_REG_SCFG1); ++ w83781d_write_value(client, ++ W83781D_REG_SCFG1, ++ tmp & ~BIT_SCFG1[nr - ++ 1]); ++ data->sens[nr - 1] = results[0]; ++ break; ++ default: ++ printk ++ (KERN_ERR "w83781d.o: Invalid sensor type %ld; must be 1, 2, or %d\n", ++ results[0], W83781D_DEFAULT_BETA); ++ break; ++ } ++ } ++ } ++} ++ ++#ifdef W83781D_RT ++static void w83781d_rt(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct w83781d_data *data = client->data; ++ int nr = 1 + ctl_name - W83781D_SYSCTL_RT1; ++ int i; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ for (i = 0; i < 32; i++) { ++ results[i] = data->rt[nr - 1][i]; ++ } ++ *nrels_mag = 32; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag > 32) ++ *nrels_mag = 32; ++ for (i = 0; i < *nrels_mag; i++) { ++ /* fixme: no bounds checking 0-255 */ ++ data->rt[nr - 1][i] = results[i]; ++ w83781d_write_value(client, W83781D_REG_RT_IDX, i); ++ w83781d_write_value(client, W83781D_REG_RT_VAL, ++ data->rt[nr - 1][i]); ++ } ++ } ++} ++#endif ++ ++static int __init sm_w83781d_init(void) ++{ ++ printk(KERN_INFO "w83781d.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&w83781d_driver); ++} ++ ++static void __exit sm_w83781d_exit(void) ++{ ++ i2c_del_driver(&w83781d_driver); ++} ++ ++ ++ ++MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, " ++ "Philip Edelbrock <phil@netroedge.com>, " ++ "and Mark Studebaker <mdsxyz123@yahoo.com>"); ++MODULE_DESCRIPTION("W83781D driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_w83781d_init); ++module_exit(sm_w83781d_exit); +--- linux-old/drivers/sensors/w83l785ts.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/w83l785ts.c Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,388 @@ ++/* ++ * w83l785ts.c - Part of lm_sensors, Linux kernel modules for hardware ++ * monitoring ++ * Copyright (C) 2003-2004 Jean Delvare <khali@linux-fr.org> ++ * ++ * Inspired from the lm83 driver. The W83L785TS-S is a sensor chip made ++ * by Winbond. It reports a single external temperature with a 1 deg ++ * resolution and a 3 deg accuracy. Data sheet can be obtained from ++ * Winbond's website at: ++ * http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/W83L785TS-S.pdf ++ * ++ * Thanks to James Bolt <james@evilpenguin.com> for benchmarking the read ++ * error handling mechanism. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#include <linux/delay.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++#ifndef I2C_DRIVERID_W83L785TS ++#define I2C_DRIVERID_W83L785TS 1047 ++#endif ++ ++/* How many retries on register read error */ ++#define MAX_RETRIES 5 ++ ++/* ++ * Address to scan ++ * Address is fully defined internally and cannot be changed. ++ */ ++ ++static unsigned short normal_i2c[] = { 0x2e, SENSORS_I2C_END }; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* ++ * Insmod parameters ++ */ ++ ++SENSORS_INSMOD_1(w83l785ts); ++ ++/* ++ * The W83L785TS-S registers ++ * Manufacturer ID is 0x5CA3 for Winbond. ++ */ ++ ++#define W83L785TS_REG_MAN_ID1 0x4D ++#define W83L785TS_REG_MAN_ID2 0x4C ++#define W83L785TS_REG_CHIP_ID 0x4E ++#define W83L785TS_REG_CONFIG 0x40 ++#define W83L785TS_REG_TYPE 0x52 ++#define W83L785TS_REG_TEMP 0x27 ++#define W83L785TS_REG_TEMP_OVER 0x53 /* not sure about this one */ ++ ++/* ++ * Conversions ++ * The W83L785TS-S uses signed 8-bit values. ++ */ ++ ++#define TEMP_FROM_REG(val) (val & 0x80 ? val-0x100 : val) ++ ++/* ++ * Functions declaration ++ */ ++ ++static int w83l785ts_attach_adapter(struct i2c_adapter *adapter); ++static int w83l785ts_detect(struct i2c_adapter *adapter, int address, unsigned ++ short flags, int kind); ++static int w83l785ts_detach_client(struct i2c_client *client); ++static u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval); ++static void w83l785ts_update_client(struct i2c_client *client); ++static void w83l785ts_temp(struct i2c_client *client, int operation, int ++ ctl_name, int *nrels_mag, long *results); ++ ++/* ++ * Driver data (common to all clients) ++ */ ++ ++static struct i2c_driver w83l785ts_driver = { ++ .owner = THIS_MODULE, ++ .name = "W83L785S-S sensor driver", ++ .id = I2C_DRIVERID_W83L785TS, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = w83l785ts_attach_adapter, ++ .detach_client = w83l785ts_detach_client, ++}; ++ ++/* ++ * Client data (each client gets its own) ++ */ ++ ++struct w83l785ts_data { ++ struct i2c_client client; ++ int sysctl_id; ++ ++ struct semaphore update_lock; ++ char valid; /* zero until following fields are valid */ ++ unsigned long last_updated; /* in jiffies */ ++ ++ /* registers values */ ++ u8 temp, temp_over; ++}; ++ ++/* ++ * Proc entries ++ * These files are created for each detected W83L785TS-S. ++ */ ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define W83L785TS_SYSCTL_TEMP 1200 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++ ++static ctl_table w83l785ts_dir_table_template[] = ++{ ++ {W83L785TS_SYSCTL_TEMP, "temp", NULL, 0, 0444, NULL, ++ &i2c_proc_real, &i2c_sysctl_real, NULL, &w83l785ts_temp}, ++ {0} ++}; ++ ++/* ++ * Internal variables ++ */ ++ ++static int w83l785ts_id = 0; ++ ++/* ++ * Real code ++ */ ++ ++static int w83l785ts_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, w83l785ts_detect); ++} ++ ++/* ++ * The following function does more than just detection. If detection ++ * succeeds, it also registers the new chip. ++ */ ++static int w83l785ts_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ struct i2c_client *new_client; ++ struct w83l785ts_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "w83l785ts.o: I2C bus doesn't support " ++ "byte read mode, skipping.\n"); ++#endif ++ return 0; ++ } ++ ++ if (!(data = kmalloc(sizeof(struct w83l785ts_data), GFP_KERNEL))) { ++ printk(KERN_ERR "w83l785ts.o: Out of memory in w83l785ts_detect " ++ "(new_client).\n"); ++ return -ENOMEM; ++ } ++ ++ /* ++ * The common I2C client data is placed right after the ++ * W83L785TS-specific. The W83L785TS-specific data is pointed to by the ++ * data field from the I2C client data. ++ */ ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &w83l785ts_driver; ++ new_client->flags = 0; ++ ++ /* ++ * Now we do the remaining detection. A negative kind means that ++ * the driver was loaded with no force parameter (default), so we ++ * must both detect and identify the chip (actually there is only ++ * one possible kind of chip for now, W83L785TS-S). A zero kind means ++ * that the driver was loaded with the force parameter, the detection ++ * step shall be skipped. A positive kind means that the driver ++ * was loaded with the force parameter and a given kind of chip is ++ * requested, so both the detection and the identification steps ++ * are skipped. ++ */ ++ ++ if (kind < 0) { /* detection */ ++ if (((w83l785ts_read_value(new_client, W83L785TS_REG_CONFIG, 0) ++ & 0x80) != 0x00) ++ || ((w83l785ts_read_value(new_client, W83L785TS_REG_TYPE, 0) ++ & 0xFC) != 0x00)) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "w83l785ts.o: Detection failed at " ++ "0x%02x.\n", address); ++#endif ++ goto ERROR1; ++ } ++ } ++ ++ if (kind <= 0) { /* identification */ ++ u16 man_id; ++ u8 chip_id; ++ ++ man_id = (w83l785ts_read_value(new_client, W83L785TS_REG_MAN_ID1, 0) << 8) ++ + w83l785ts_read_value(new_client, W83L785TS_REG_MAN_ID2, 0); ++ chip_id = w83l785ts_read_value(new_client, W83L785TS_REG_CHIP_ID, 0); ++ if (man_id == 0x5CA3) { /* Winbond */ ++ if (chip_id == 0x70) ++ kind = w83l785ts; ++ } ++ } ++ ++ if (kind <= 0) { /* identification failed */ ++ printk(KERN_INFO "w83l785ts.o: Unsupported chip.\n"); ++ goto ERROR1; ++ } ++ ++ if (kind == w83l785ts) { ++ type_name = "w83l785ts"; ++ client_name = "W83L785TS-S chip"; ++ } else { ++ printk(KERN_ERR "w83l785ts.o: Unknown kind %d.\n", kind); ++ goto ERROR1; ++ } ++ ++ /* ++ * OK, we got a valid chip so we can fill in the remaining client ++ * fields. ++ */ ++ ++ strcpy(new_client->name, client_name); ++ new_client->id = w83l785ts_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* ++ * Tell the I2C layer a new client has arrived. ++ */ ++ ++ if ((err = i2c_attach_client(new_client))) { ++#ifdef DEBUG ++ printk(KERN_ERR "w83l785ts.o: Failed attaching client.\n"); ++#endif ++ goto ERROR1; ++ } ++ ++ /* ++ * Register a new directory entry. ++ */ ++ ++ if ((err = i2c_register_entry(new_client, type_name, ++ w83l785ts_dir_table_template)) < 0) { ++#ifdef DEBUG ++ printk(KERN_ERR "w83l785ts.o: Failed registering directory " ++ "entry.\n"); ++#endif ++ goto ERROR2; ++ } ++ data->sysctl_id = err; ++ ++ /* ++ * Initialize the W83L785TS chip ++ * Nothing yet, assume it is already started. ++ */ ++ ++ return 0; ++ ++ ERROR2: ++ i2c_detach_client(new_client); ++ ERROR1: ++ kfree(data); ++ return err; ++} ++ ++static int w83l785ts_detach_client(struct i2c_client *client) ++{ ++ int err; ++ ++ i2c_deregister_entry(((struct w83l785ts_data *) (client->data))->sysctl_id); ++ if ((err = i2c_detach_client(client))) { ++ printk(KERN_ERR "w83l785ts.o: Client deregistration failed, " ++ "client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ return 0; ++} ++ ++static u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval) ++{ ++ int value, i; ++ ++ /* Frequent read errors have been reported on Asus boards, so we ++ * retry on read errors. If it still fails (unlikely), return the ++ * default value requested by the caller. */ ++ for (i = 1; i <= MAX_RETRIES; i++) { ++ value = i2c_smbus_read_byte_data(client, reg); ++ if (value >= 0) ++ return value; ++ printk(KERN_WARNING "w83l785ts.o: Read failed, will retry " ++ "in %d.\n", i); ++ mdelay(i); ++ } ++ ++ printk(KERN_ERR "w83l785ts.o: Couldn't read value from register. " ++ "Please report.\n"); ++ return defval; ++} ++ ++static void w83l785ts_update_client(struct i2c_client *client) ++{ ++ struct w83l785ts_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ * 2) ++ || (jiffies < data->last_updated) ++ || !data->valid) { ++#ifdef DEBUG ++ printk(KERN_DEBUG "w83l785ts.o: Updating data.\n"); ++#endif ++ data->temp = w83l785ts_read_value(client, W83L785TS_REG_TEMP, ++ data->temp); ++ data->temp_over = w83l785ts_read_value(client, ++ W83L785TS_REG_TEMP_OVER, data->temp_over); ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++static void w83l785ts_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct w83l785ts_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) { ++ *nrels_mag = 0; /* magnitude */ ++ } else if (operation == SENSORS_PROC_REAL_READ) { ++ w83l785ts_update_client(client); ++ results[0] = TEMP_FROM_REG(data->temp_over); ++ results[1] = TEMP_FROM_REG(data->temp); ++ *nrels_mag = 2; ++ } ++} ++ ++static int __init sm_w83l785ts_init(void) ++{ ++ printk(KERN_INFO "w83l785ts.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&w83l785ts_driver); ++} ++ ++static void __exit sm_w83l785ts_exit(void) ++{ ++ i2c_del_driver(&w83l785ts_driver); ++} ++ ++MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); ++MODULE_DESCRIPTION("W83L785TS-S sensor driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sm_w83l785ts_init); ++module_exit(sm_w83l785ts_exit); +--- linux-old/drivers/sensors/xeontemp.c Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/xeontemp.c Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,388 @@ ++/* ++ xeontemp.c - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 1998, 1999,2003 Frodo Looijaard <frodol@dds.nl>, ++ Philip Edelbrock <phil@netroedge.com>, and ++ Mark D. Studebaker <mdsxyz123@yahoo.com> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* The Xeon temperature sensor looks just like an ADM1021 with the remote ++ sensor only. There is are no ID registers so detection is difficult. */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/i2c-proc.h> ++#include <linux/init.h> ++#define LM_DATE "20041007" ++#define LM_VERSION "2.8.8" ++ ++#ifndef I2C_DRIVERID_XEONTEMP ++#define I2C_DRIVERID_XEONTEMP 1045 ++#endif ++ ++/* Addresses to scan */ ++static unsigned short normal_i2c[] = { 0x18, 0x1a, 0x29, 0x2b, ++ 0x4c, 0x4e, SENSORS_I2C_END ++}; ++static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; ++static unsigned int normal_isa[] = { SENSORS_ISA_END }; ++static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; ++ ++/* Insmod parameters */ ++SENSORS_INSMOD_1(xeontemp); ++ ++/* xeontemp constants specified below */ ++ ++/* The registers */ ++/* Read-only */ ++#define XEONTEMP_REG_REMOTE_TEMP 0x01 ++#define XEONTEMP_REG_STATUS 0x02 ++/* These use different addresses for reading/writing */ ++#define XEONTEMP_REG_CONFIG_R 0x03 ++#define XEONTEMP_REG_CONFIG_W 0x09 ++#define XEONTEMP_REG_CONV_RATE_R 0x04 ++#define XEONTEMP_REG_CONV_RATE_W 0x0A ++/* limits */ ++#define XEONTEMP_REG_REMOTE_TOS_R 0x07 ++#define XEONTEMP_REG_REMOTE_TOS_W 0x0D ++#define XEONTEMP_REG_REMOTE_THYST_R 0x08 ++#define XEONTEMP_REG_REMOTE_THYST_W 0x0E ++/* write-only */ ++#define XEONTEMP_REG_ONESHOT 0x0F ++ ++#define XEONTEMP_ALARM_RTEMP (XEONTEMP_ALARM_RTEMP_HIGH | XEONTEMP_ALARM_RTEMP_LOW\ ++ | XEONTEMP_ALARM_RTEMP_NA) ++#define XEONTEMP_ALARM_ALL XEONTEMP_ALARM_RTEMP ++ ++/* Conversions. Rounding and limit checking is only done on the TO_REG ++ variants. Note that you should be a bit careful with which arguments ++ these macros are called: arguments may be evaluated more than once. ++ Fixing this is just not worth it. */ ++/* Conversions note: 1021 uses normal integer signed-byte format*/ ++#define TEMP_FROM_REG(val) (val > 127 ? val-256 : val) ++#define TEMP_TO_REG(val) (SENSORS_LIMIT((val < 0 ? val+256 : val),0,255)) ++ ++/* Each client has this additional data */ ++struct xeontemp_data { ++ struct i2c_client client; ++ int sysctl_id; ++ enum chips type; ++ ++ struct semaphore update_lock; ++ char valid; /* !=0 if following fields are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ ++ u8 remote_temp, remote_temp_os, remote_temp_hyst, alarms; ++ u8 fail; ++}; ++ ++static int xeontemp_attach_adapter(struct i2c_adapter *adapter); ++static int xeontemp_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind); ++static void xeontemp_init_client(struct i2c_client *client); ++static int xeontemp_detach_client(struct i2c_client *client); ++static int xeontemp_read_value(struct i2c_client *client, u8 reg); ++static int xeontemp_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask); ++static int xeontemp_write_value(struct i2c_client *client, u8 reg, ++ u16 value); ++static void xeontemp_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, ++ long *results); ++static void xeontemp_alarms(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results); ++static void xeontemp_update_client(struct i2c_client *client); ++ ++/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */ ++static int read_only = 0; ++ ++ ++/* This is the driver that will be inserted */ ++static struct i2c_driver xeontemp_driver = { ++ .owner = THIS_MODULE, ++ .name = "Xeon temp sensor driver", ++ .id = I2C_DRIVERID_XEONTEMP, ++ .flags = I2C_DF_NOTIFY, ++ .attach_adapter = xeontemp_attach_adapter, ++ .detach_client = xeontemp_detach_client, ++}; ++ ++/* -- SENSORS SYSCTL START -- */ ++ ++#define XEONTEMP_SYSCTL_REMOTE_TEMP 1201 ++#define XEONTEMP_SYSCTL_ALARMS 1203 ++ ++#define XEONTEMP_ALARM_RTEMP_HIGH 0x10 ++#define XEONTEMP_ALARM_RTEMP_LOW 0x08 ++#define XEONTEMP_ALARM_RTEMP_NA 0x04 ++ ++/* -- SENSORS SYSCTL END -- */ ++ ++/* These files are created for each detected xeontemp. This is just a template; ++ though at first sight, you might think we could use a statically ++ allocated list, we need some way to get back to the parent - which ++ is done through one of the 'extra' fields which are initialized ++ when a new copy is allocated. */ ++static ctl_table xeontemp_dir_table_template[] = { ++ {XEONTEMP_SYSCTL_REMOTE_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &xeontemp_remote_temp}, ++ {XEONTEMP_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, ++ &i2c_sysctl_real, NULL, &xeontemp_alarms}, ++ {0} ++}; ++ ++static int xeontemp_id = 0; ++ ++static int xeontemp_attach_adapter(struct i2c_adapter *adapter) ++{ ++ return i2c_detect(adapter, &addr_data, xeontemp_detect); ++} ++ ++static int xeontemp_detect(struct i2c_adapter *adapter, int address, ++ unsigned short flags, int kind) ++{ ++ int i; ++ struct i2c_client *new_client; ++ struct xeontemp_data *data; ++ int err = 0; ++ const char *type_name = ""; ++ const char *client_name = ""; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ goto error0; ++ ++ /* OK. For now, we presume we have a valid client. We now create the ++ client structure, even though we cannot fill it completely yet. ++ But it allows us to access xeontemp_{read,write}_value. */ ++ ++ if (!(data = kmalloc(sizeof(struct xeontemp_data), GFP_KERNEL))) { ++ err = -ENOMEM; ++ goto error0; ++ } ++ ++ new_client = &data->client; ++ new_client->addr = address; ++ new_client->data = data; ++ new_client->adapter = adapter; ++ new_client->driver = &xeontemp_driver; ++ new_client->flags = 0; ++ ++ /* Now, we do the remaining detection. */ ++ ++ if (kind < 0) { ++ if ( ++ (xeontemp_read_value(new_client, XEONTEMP_REG_STATUS) & ++ 0x03) != 0x00) ++ goto error1; ++ } ++ ++ /* Determine the chip type. */ ++ ++ if (kind <= 0) { ++ kind = xeontemp; ++ } ++ ++ type_name = "xeontemp"; ++ client_name = "xeon sensors"; ++ ++ /* Fill in the remaining client fields and put it into the global list */ ++ strcpy(new_client->name, client_name); ++ data->type = kind; ++ ++ new_client->id = xeontemp_id++; ++ data->valid = 0; ++ init_MUTEX(&data->update_lock); ++ ++ /* Tell the I2C layer a new client has arrived */ ++ if ((err = i2c_attach_client(new_client))) ++ goto error3; ++ ++ /* Register a new directory entry with module sensors */ ++ if ((i = i2c_register_entry(new_client, type_name, ++ xeontemp_dir_table_template)) < 0) { ++ err = i; ++ goto error4; ++ } ++ data->sysctl_id = i; ++ ++ xeontemp_init_client(new_client); ++ return 0; ++ ++ error4: ++ i2c_detach_client(new_client); ++ error3: ++ error1: ++ kfree(data); ++ error0: ++ return err; ++} ++ ++static void xeontemp_init_client(struct i2c_client *client) ++{ ++ /* Enable ADC and disable suspend mode */ ++ xeontemp_write_value(client, XEONTEMP_REG_CONFIG_W, 0); ++ /* Set Conversion rate to 1/sec (this can be tinkered with) */ ++ xeontemp_write_value(client, XEONTEMP_REG_CONV_RATE_W, 0x04); ++} ++ ++static int xeontemp_detach_client(struct i2c_client *client) ++{ ++ ++ int err; ++ ++ i2c_deregister_entry(((struct xeontemp_data *) (client->data))-> ++ sysctl_id); ++ ++ if ((err = i2c_detach_client(client))) { ++ printk ++ ("xeontemp.o: Client deregistration failed, client not detached.\n"); ++ return err; ++ } ++ ++ kfree(client->data); ++ ++ return 0; ++ ++} ++ ++ ++/* All registers are byte-sized */ ++static int xeontemp_read_value(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++/* only update value if read succeeded; set fail bit if failed */ ++static int xeontemp_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask) ++{ ++ int i; ++ struct xeontemp_data *data = client->data; ++ ++ i = i2c_smbus_read_byte_data(client, reg); ++ if (i < 0) { ++ data->fail |= mask; ++ return i; ++ } ++ *val = i; ++ return 0; ++} ++ ++static int xeontemp_write_value(struct i2c_client *client, u8 reg, u16 value) ++{ ++ if (read_only > 0) ++ return 0; ++ ++ return i2c_smbus_write_byte_data(client, reg, value); ++} ++ ++static void xeontemp_update_client(struct i2c_client *client) ++{ ++ struct xeontemp_data *data = client->data; ++ ++ down(&data->update_lock); ++ ++ if ((jiffies - data->last_updated > HZ + HZ / 2) || ++ (jiffies < data->last_updated) || !data->valid) { ++ ++#ifdef DEBUG ++ printk("Starting xeontemp update\n"); ++#endif ++ ++ data->fail = 0; ++ xeontemp_rd_good(&(data->remote_temp), client, ++ XEONTEMP_REG_REMOTE_TEMP, XEONTEMP_ALARM_RTEMP); ++ xeontemp_rd_good(&(data->remote_temp_os), client, ++ XEONTEMP_REG_REMOTE_TOS_R, XEONTEMP_ALARM_RTEMP); ++ xeontemp_rd_good(&(data->remote_temp_hyst), client, ++ XEONTEMP_REG_REMOTE_THYST_R, ++ XEONTEMP_ALARM_RTEMP); ++ data->alarms = XEONTEMP_ALARM_ALL; ++ if (!xeontemp_rd_good(&(data->alarms), client, ++ XEONTEMP_REG_STATUS, 0)) ++ data->alarms &= XEONTEMP_ALARM_ALL; ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ up(&data->update_lock); ++} ++ ++void xeontemp_remote_temp(struct i2c_client *client, int operation, ++ int ctl_name, int *nrels_mag, long *results) ++{ ++ struct xeontemp_data *data = client->data; ++ ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ xeontemp_update_client(client); ++ results[0] = TEMP_FROM_REG(data->remote_temp_os); ++ results[1] = TEMP_FROM_REG(data->remote_temp_hyst); ++ results[2] = TEMP_FROM_REG(data->remote_temp); ++ *nrels_mag = 3; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ if (*nrels_mag >= 1) { ++ data->remote_temp_os = TEMP_TO_REG(results[0]); ++ xeontemp_write_value(client, ++ XEONTEMP_REG_REMOTE_TOS_W, ++ data->remote_temp_os); ++ } ++ if (*nrels_mag >= 2) { ++ data->remote_temp_hyst = TEMP_TO_REG(results[1]); ++ xeontemp_write_value(client, ++ XEONTEMP_REG_REMOTE_THYST_W, ++ data->remote_temp_hyst); ++ } ++ } ++} ++ ++void xeontemp_alarms(struct i2c_client *client, int operation, int ctl_name, ++ int *nrels_mag, long *results) ++{ ++ struct xeontemp_data *data = client->data; ++ if (operation == SENSORS_PROC_REAL_INFO) ++ *nrels_mag = 0; ++ else if (operation == SENSORS_PROC_REAL_READ) { ++ xeontemp_update_client(client); ++ results[0] = data->alarms | data->fail; ++ *nrels_mag = 1; ++ } else if (operation == SENSORS_PROC_REAL_WRITE) { ++ /* Can't write to it */ ++ } ++} ++ ++static int __init sm_xeontemp_init(void) ++{ ++ printk(KERN_INFO "xeontemp.o version %s (%s)\n", LM_VERSION, LM_DATE); ++ return i2c_add_driver(&xeontemp_driver); ++} ++ ++static void __exit sm_xeontemp_exit(void) ++{ ++ i2c_del_driver(&xeontemp_driver); ++} ++ ++MODULE_AUTHOR ++ ("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>"); ++MODULE_DESCRIPTION("xeontemp driver"); ++MODULE_LICENSE("GPL"); ++ ++MODULE_PARM(read_only, "i"); ++MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); ++ ++module_init(sm_xeontemp_init) ++module_exit(sm_xeontemp_exit) +--- linux-old/include/linux/sensors_compat.h Thu Jan 1 00:00:00 1970 ++++ linux/include/linux/sensors_compat.h Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,31 @@ ++/* ++ * Stolen from kernel 2.5.69 ++ * device.h - generic, centralized driver model ++ * To make it easier to backport from 2.5 ++ * ++ * Copyright (c) 2001-2003 Patrick Mochel <mochel@osdl.org> ++ * ++ */ ++ ++#ifndef _SENSORS_COMPAT_H_ ++#define _SENSORS_COMPAT_H_ ++ ++/* debugging and troubleshooting/diagnostic helpers. */ ++#define dev_printk(level, dev, format, arg...) \ ++ printk(level "%s: " format , (dev)->name , ## arg) ++ ++#ifdef DEBUG ++#define dev_dbg(dev, format, arg...) \ ++ dev_printk(KERN_DEBUG , dev , format , ## arg) ++#else ++#define dev_dbg(dev, format, arg...) do {} while (0) ++#endif ++ ++#define dev_err(dev, format, arg...) \ ++ dev_printk(KERN_ERR , dev , format , ## arg) ++#define dev_info(dev, format, arg...) \ ++ dev_printk(KERN_INFO , dev , format , ## arg) ++#define dev_warn(dev, format, arg...) \ ++ dev_printk(KERN_WARNING , dev , format , ## arg) ++ ++#endif /* _SENSORS_COMPAT_H_ */ +--- linux-old/include/linux/sensors_vid.h Thu Jan 1 00:00:00 1970 ++++ linux/include/linux/sensors_vid.h Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,95 @@ ++/* ++ sensors_vid.h - Part of lm_sensors, Linux kernel modules for hardware ++ monitoring ++ Copyright (c) 2002-2004 Mark D. Studebaker <mdsxyz123@yahoo.com> ++ With assistance from Trent Piepho <xyzzy@speakeasy.org> ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program; if not, write to the Free Software ++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++*/ ++ ++/* ++ This file contains common code for decoding VID pins. ++ This file is #included in various sensor chip drivers. ++ As the user is unlikely to load more than one driver which ++ includes this code we don't worry about the wasted space. ++ References: VRM x.y DC-DC Converter Design Guidelines, ++ VRD 10.0 Design Guide, ++ available at http://developer.intel.com ++*/ ++ ++/* ++ AMD Opteron processors don't follow the Intel VRM spec. ++ I'm going to "make up" 2.4 as the VRM spec for the Opterons. ++ No good reason just a mnemonic for the 24x Opteron processor ++ series ++ ++ Opteron VID encoding is: ++ ++ 00000 = 1.550 V ++ 00001 = 1.525 V ++ . . . . ++ 11110 = 0.800 V ++ 11111 = 0.000 V (off) ++ */ ++ ++/* ++ Legal val values 00 - 1F except for VRD 10.0, 0x00-0x3f. ++ vrm is the Intel VRM document version. ++ Note: vrm version is scaled by 10 and the return value is scaled by 1000 ++ to avoid floating point in the kernel. ++*/ ++ ++#define DEFAULT_VRM 82 ++ ++static inline int vid_from_reg(int val, int vrm) ++{ ++ int vid; ++ ++ switch(vrm) { ++ ++ case 100: /* VRD 10.0 */ ++ if((val & 0x1f) == 0x1f) ++ return 0; ++ if((val & 0x1f) <= 0x09 || val == 0x0a) ++ vid = 10875 - (val & 0x1f) * 250; ++ else ++ vid = 18625 - (val & 0x1f) * 250; ++ if(val & 0x20) ++ vid -= 125; ++ vid /= 10; /* only return 3 dec. places for now */ ++ return vid; ++ ++ case 24: /* Opteron processor */ ++ return(val == 0x1f ? 0 : 1550 - val * 25); ++ ++ case 91: /* VRM 9.1 */ ++ case 90: /* VRM 9.0 */ ++ return(val == 0x1f ? 0 : ++ 1850 - val * 25); ++ ++ case 85: /* VRM 8.5 */ ++ return((val & 0x10 ? 25 : 0) + ++ ((val & 0x0f) > 0x04 ? 2050 : 1250) - ++ ((val & 0x0f) * 50)); ++ ++ case 84: /* VRM 8.4 */ ++ val &= 0x0f; ++ /* fall through */ ++ default: /* VRM 8.2 */ ++ return(val == 0x1f ? 0 : ++ val & 0x10 ? 5100 - (val) * 100 : ++ 2050 - (val) * 50); ++ } ++} +--- linux-old/drivers/sensors/Config.in Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/Config.in Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,62 @@ ++# ++# Sensor device configuration ++# All depend on CONFIG_I2C_PROC. ++# ISA-only devices depend on CONFIG_I2C_ISA also. ++# ++ ++if [ "$CONFIG_I2C" = "m" -o "$CONFIG_I2C" = "y" ] ; then ++if [ "$CONFIG_I2C_PROC" = "m" -o "$CONFIG_I2C_PROC" = "y" ] ; then ++ mainmenu_option next_comment ++ comment 'Hardware sensors support' ++ ++ dep_mbool 'Hardware sensors support' CONFIG_SENSORS $CONFIG_I2C $CONFIG_I2C_PROC ++ ++ if [ "$CONFIG_SENSORS" != "n" ]; then ++ dep_tristate ' Analog Devices ADM1021 and compatibles' CONFIG_SENSORS_ADM1021 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Analog Devices ADM1024' CONFIG_SENSORS_ADM1024 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Analog Devices ADM1025' CONFIG_SENSORS_ADM1025 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Analog Devices ADM1026' CONFIG_SENSORS_ADM1026 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Analog Devices ADM9240 and compatibles' CONFIG_SENSORS_ADM9240 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Asus ASB100' CONFIG_SENSORS_ASB100 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Dallas DS1621 and DS1625' CONFIG_SENSORS_DS1621 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Fujitsu-Siemens Poseidon' CONFIG_SENSORS_FSCPOS $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Fujitsu-Siemens Scylla' CONFIG_SENSORS_FSCSCY $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Genesys Logic GL518SM' CONFIG_SENSORS_GL518SM $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Genesys Logic GL520SM' CONFIG_SENSORS_GL520SM $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' HP Maxilife' CONFIG_SENSORS_MAXILIFE $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Intel Xeon Thermal Sensor' CONFIG_SENSORS_XEONTEMP $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' ITE 8705/8712, SiS950' CONFIG_SENSORS_IT87 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Maxim MAX6650, MAX6651' CONFIG_SENSORS_MAX6650 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Myson MTP008' CONFIG_SENSORS_MTP008 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM75 and compatibles' CONFIG_SENSORS_LM75 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM78' CONFIG_SENSORS_LM78 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM80' CONFIG_SENSORS_LM80 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM83' CONFIG_SENSORS_LM83 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM85, Analog Devices ADM1027' CONFIG_SENSORS_LM85 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM87' CONFIG_SENSORS_LM87 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM90 and compatibles' CONFIG_SENSORS_LM90 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor LM92' CONFIG_SENSORS_LM92 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' National Semiconductor PC8736x Sensors' CONFIG_SENSORS_PC87360 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Silicon Integrated Systems Corp. SiS5595' CONFIG_SENSORS_SIS5595 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA ++ dep_tristate ' SMSC47M1xx Integrated Sensors' CONFIG_SENSORS_SMSC47M1 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA ++ dep_tristate ' Texas Instruments THMC50 and compatibles' CONFIG_SENSORS_THMC50 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' VIA 686a Integrated Hardware Monitor' CONFIG_SENSORS_VIA686A $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA ++ dep_tristate ' VIA VT1211 Integrated Sensors' CONFIG_SENSORS_VT1211 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA ++ dep_tristate ' VIA VT8231 Integrated Sensors' CONFIG_SENSORS_VT8231 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA ++ dep_tristate ' Winbond W83781D, W83782D, W83783S, W83627HF, Asus AS99127F' CONFIG_SENSORS_W83781D $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Winbond W83627HF, W83627THF, W83697HF' CONFIG_SENSORS_W83627HF $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA ++ dep_tristate ' Winbond W83L785TS-S' CONFIG_SENSORS_W83L785TS $CONFIG_I2C $CONFIG_I2C_PROC ++ bool 'Other I2C devices' CONFIG_SENSORS_OTHER ++ if [ "$CONFIG_SENSORS_OTHER" = "y" ] ; then ++ dep_tristate ' Brooktree BT869 Video Modulator' CONFIG_SENSORS_BT869 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' DDC Monitor EDID EEPROM' CONFIG_SENSORS_DDCMON $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' EEprom (DIMM) reader ' CONFIG_SENSORS_EEPROM $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Matrix-Orbital LCD Displays' CONFIG_SENSORS_MATORB $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Philips PCF8574 Parallel I/O' CONFIG_SENSORS_PCF8574 $CONFIG_I2C $CONFIG_I2C_PROC ++ dep_tristate ' Philips PCF8591 D/A and A/D' CONFIG_SENSORS_PCF8591 $CONFIG_I2C $CONFIG_I2C_PROC ++ fi ++ fi ++ endmenu ++fi ++fi ++ +--- linux-old/Makefile Sat Aug 14 18:38:44 2004 ++++ linux/Makefile Mon Dec 13 20:18:54 2004 +@@ -195,6 +195,7 @@ + DRIVERS-$(CONFIG_ISDN_BOOL) += drivers/isdn/vmlinux-obj.o + DRIVERS-$(CONFIG_CRYPTO) += crypto/crypto.o + ++DRIVERS-$(CONFIG_SENSORS) += drivers/sensors/sensor.o + DRIVERS := $(DRIVERS-y) + + +--- linux-old/drivers/Makefile Mon Nov 17 01:07:35 2003 ++++ linux/drivers/Makefile Mon Dec 13 20:18:54 2004 +@@ -8,7 +8,7 @@ + + mod-subdirs := dio hil mtd sbus video macintosh usb input telephony ide \ + message/i2o message/fusion scsi md ieee1394 pnp isdn atm \ +- fc4 net/hamradio i2c acpi bluetooth usb/gadget ++ fc4 net/hamradio i2c acpi bluetooth usb/gadget sensors + + subdir-y := parport char block net sound misc media cdrom hotplug + subdir-m := $(subdir-y) +@@ -45,6 +45,7 @@ + # CONFIG_HAMRADIO can be set without CONFIG_NETDEVICE being set -- ch + subdir-$(CONFIG_HAMRADIO) += net/hamradio + subdir-$(CONFIG_I2C) += i2c ++subdir-$(CONFIG_SENSORS) += sensors + subdir-$(CONFIG_ACPI_BOOT) += acpi + + subdir-$(CONFIG_BLUEZ) += bluetooth +--- linux-old/drivers/sensors/Makefile Thu Jan 1 00:00:00 1970 ++++ linux/drivers/sensors/Makefile Mon Dec 13 20:18:54 2004 +@@ -0,0 +1,49 @@ ++# ++# Makefile for the kernel hardware sensors drivers. ++# ++ ++MOD_LIST_NAME := SENSORS_MODULES ++O_TARGET := sensor.o ++ ++obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o ++obj-$(CONFIG_SENSORS_ADM1024) += adm1024.o ++obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o ++obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o ++obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o ++obj-$(CONFIG_SENSORS_ASB100) += asb100.o ++obj-$(CONFIG_SENSORS_BT869) += bt869.o ++obj-$(CONFIG_SENSORS_DDCMON) += ddcmon.o ++obj-$(CONFIG_SENSORS_DS1621) += ds1621.o ++obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o ++obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o ++obj-$(CONFIG_SENSORS_FSCSCY) += fscscy.o ++obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o ++obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o ++obj-$(CONFIG_SENSORS_IT87) += it87.o ++obj-$(CONFIG_SENSORS_LM75) += lm75.o ++obj-$(CONFIG_SENSORS_LM78) += lm78.o ++obj-$(CONFIG_SENSORS_LM80) += lm80.o ++obj-$(CONFIG_SENSORS_LM83) += lm83.o ++obj-$(CONFIG_SENSORS_LM85) += lm85.o ++obj-$(CONFIG_SENSORS_LM87) += lm87.o ++obj-$(CONFIG_SENSORS_LM90) += lm90.o ++obj-$(CONFIG_SENSORS_LM92) += lm92.o ++obj-$(CONFIG_SENSORS_MAX6650) += max6650.o ++obj-$(CONFIG_SENSORS_MAXILIFE) += maxilife.o ++obj-$(CONFIG_SENSORS_MTP008) += mtp008.o ++obj-$(CONFIG_SENSORS_PC87360) += pc87360.o ++obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o ++obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o ++obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o ++obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o ++obj-$(CONFIG_SENSORS_THMC50) += thmc50.o ++obj-$(CONFIG_SENSORS_VIA686A) += via686a.o ++obj-$(CONFIG_SENSORS_VT1211) += vt1211.o ++obj-$(CONFIG_SENSORS_VT8231) += vt8231.o ++obj-$(CONFIG_SENSORS_W83781D) += w83781d.o ++obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o ++obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o ++obj-$(CONFIG_SENSORS_XEONTEMP) += xeontemp.o ++ ++include $(TOPDIR)/Rules.make ++ +--- linux-old/drivers/char/Config.in Sat Aug 14 18:38:49 2004 ++++ linux/drivers/char/Config.in Mon Dec 13 20:18:54 2004 +@@ -191,6 +191,8 @@ + + source drivers/i2c/Config.in + ++source drivers/sensors/Config.in ++ + mainmenu_option next_comment + comment 'Mice' + tristate 'Bus Mouse Support' CONFIG_BUSMOUSE +--- linux-old/drivers/i2c/Config.in Wed Jul 7 00:38:02 2004 ++++ linux/drivers/i2c/Config.in Mon Dec 13 20:18:54 2004 +@@ -63,6 +63,30 @@ + fi + + # This is needed for automatic patch generation: sensors code starts here ++ bool 'I2C mainboard interfaces' CONFIG_I2C_MAINBOARD ++ if [ "$CONFIG_I2C_MAINBOARD" = "y" ]; then ++ dep_tristate ' Acer Labs ALI 1535' CONFIG_I2C_ALI1535 $CONFIG_I2C ++ dep_tristate ' Acer Labs ALI 1533 and 1543C' CONFIG_I2C_ALI15X3 $CONFIG_I2C ++ dep_tristate ' Apple Hydra Mac I/O' CONFIG_I2C_HYDRA $CONFIG_I2C_ALGOBIT ++ dep_tristate ' AMD 756/766/768/8111' CONFIG_I2C_AMD756 $CONFIG_I2C ++ dep_tristate ' AMD 8111 SMBus 2.0' CONFIG_I2C_AMD8111 $CONFIG_I2C ++ if [ "$CONFIG_ALPHA" = "y" ]; then ++ dep_tristate ' DEC Tsunami I2C interface' CONFIG_I2C_TSUNAMI $CONFIG_I2C_ALGOBIT ++ fi ++ dep_tristate ' Intel 82801AA, AB, BA, DB' CONFIG_I2C_I801 $CONFIG_I2C ++ dep_tristate ' Intel i810AA/AB/E and i815' CONFIG_I2C_I810 $CONFIG_I2C_ALGOBIT ++ dep_tristate ' Intel 82371AB PIIX4(E), 443MX, ServerWorks OSB4/CSB5, SMSC Victory66' CONFIG_I2C_PIIX4 $CONFIG_I2C ++ dep_tristate ' Nvidia Nforce2' CONFIG_I2C_NFORCE2 $CONFIG_I2C ++ dep_tristate ' SiS 5595' CONFIG_I2C_SIS5595 $CONFIG_I2C ++ dep_tristate ' SiS 630/730' CONFIG_I2C_SIS630 $CONFIG_I2C ++ dep_tristate ' SiS 645/961,645DX/961,735' CONFIG_I2C_SIS645 $CONFIG_I2C $CONFIG_HOTPLUG ++ dep_tristate ' Savage 4' CONFIG_I2C_SAVAGE4 $CONFIG_I2C_ALGOBIT ++ dep_tristate ' VIA Technologies, Inc. VT82C586B' CONFIG_I2C_VIA $CONFIG_I2C_ALGOBIT ++ dep_tristate ' VIA Technologies, Inc. VT596A/B, 686A/B, 8231, 8233, 8233A, 8235' CONFIG_I2C_VIAPRO $CONFIG_I2C ++ dep_tristate ' Voodoo3 I2C interface' CONFIG_I2C_VOODOO3 $CONFIG_I2C_ALGOBIT ++ dep_tristate ' Pseudo ISA adapter (for some hardware sensors)' CONFIG_I2C_ISA $CONFIG_I2C ++ fi ++ + # This is needed for automatic patch generation: sensors code ends here + + dep_tristate 'I2C device interface' CONFIG_I2C_CHARDEV $CONFIG_I2C +--- linux-old/drivers/i2c/Makefile Wed Jul 7 00:38:02 2004 ++++ linux/drivers/i2c/Makefile Mon Dec 13 20:18:54 2004 +@@ -28,6 +28,24 @@ + obj-$(CONFIG_I2C_ALGO_AU1550) += i2c-algo-au1550.o i2c-au1550.o i2c-au1550.o + + # This is needed for automatic patch generation: sensors code starts here ++obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o ++obj-$(CONFIG_I2C_ALI15X3) += i2c-ali15x3.o ++obj-$(CONFIG_I2C_AMD756) += i2c-amd756.o ++obj-$(CONFIG_I2C_AMD8111) += i2c-amd8111.o ++obj-$(CONFIG_I2C_HYDRA) += i2c-hydra.o ++obj-$(CONFIG_I2C_I801) += i2c-i801.o ++obj-$(CONFIG_I2C_I810) += i2c-i810.o ++obj-$(CONFIG_I2C_ISA) += i2c-isa.o ++obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o ++obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o ++obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o ++obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o ++obj-$(CONFIG_I2C_SIS645) += i2c-sis645.o ++obj-$(CONFIG_I2C_SAVAGE4) += i2c-savage4.o ++obj-$(CONFIG_I2C_TSUNAMI) += i2c-tsunami.o ++obj-$(CONFIG_I2C_VIA) += i2c-via.o ++obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o ++obj-$(CONFIG_I2C_VOODOO3) += i2c-voodoo3.o + # This is needed for automatic patch generation: sensors code ends here + + include $(TOPDIR)/Rules.make +--- linux-old/Documentation/Configure.help Mon Dec 13 20:09:52 2004 ++++ linux/Documentation/Configure.help Mon Dec 13 20:18:56 2004 +@@ -29449,4 +29449,527 @@ + # fill-prefix:" " + # adaptive-fill:nil + # fill-column:70 ++I2C mainboard interfaces ++CONFIG_I2C_MAINBOARD ++ Many modern mainboards have some kind of I2C interface integrated. This ++ is often in the form of a SMBus, or System Management Bus, which is ++ basically the same as I2C but which uses only a subset of the I2C ++ protocol. ++ ++ You will also want the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Acer Labs ALI 1535 ++CONFIG_I2C_ALI1535 ++ If you say yes to this option, support will be included for the Acer ++ Labs ALI 1535 mainboard I2C interface. This can also be ++ built as a module. ++ ++Acer Labs ALI 1533 and 1543C ++CONFIG_I2C_ALI15X3 ++ If you say yes to this option, support will be included for the Acer ++ Labs ALI 1533 and 1543C mainboard I2C interfaces. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++AMD 756/766/768/8111 ++CONFIG_I2C_AMD756 ++ If you say yes to this option, support will be included for the AMD ++ 756/766/768/8111 mainboard I2C interfaces. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++AMD 8111 SMBus 2.0 ++CONFIG_I2C_AMD8111 ++ If you say yes to this option, support will be included for the AMD ++ 8111 mainboard SMBus 2.0 interface. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++Apple Hydra Mac I/O ++CONFIG_I2C_HYDRA ++ If you say yes to this option, support will be included for the ++ Hydra mainboard I2C interface. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++Intel I801 ++CONFIG_I2C_I801 ++ If you say yes to this option, support will be included for the ++ Intel I801 mainboard I2C interfaces. "I810" mainboard sensor chips are ++ generally located on the I801's I2C bus. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++Intel I810/I815 based Mainboard ++CONFIG_I2C_I810 ++ If you say yes to this option, support will be included for the ++ Intel I810/I815 mainboard I2C interfaces. The I2C busses these chips ++ are generally used only for video devices. For "810" mainboard sensor ++ chips, use the I801 I2C driver instead. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++Intel 82371AB PIIX4(E) / ServerWorks OSB4 and CSB5 ++CONFIG_I2C_PIIX4 ++ If you say yes to this option, support will be included for the ++ Intel PIIX4, PIIX4E, and 443MX, Serverworks OSB4/CSB5, ++ and SMSC Victory66 mainboard ++ I2C interfaces. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++Nvidia Nforce2 based Mainboard ++CONFIG_I2C_NFORCE2 ++ If you say yes to this option, support will be included for the ++ Nvidia Nforce2 family of mainboard I2C interfaces. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++Silicon Integrated Systems Corp. SiS5595 based Mainboard ++CONFIG_I2C_SIS5595 ++ If you say yes to this option, support will be included for the ++ SiS5595 mainboard I2C interfaces. For integrated sensors on the ++ Sis5595, use CONFIG_SENSORS_SIS5595. This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++Silicon Integrated Systems Corp. SiS630/730 based Mainboard ++CONFIG_I2C_SIS630 ++ If you say yes to this option, support will be included for the SiS 630 ++ and 730 mainboard I2C interfaces. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++Silicon Integrated Systems Corp. SiS645/961,645DX/961,735 based Mainboard ++CONFIG_I2C_SIS645 ++ If you say yes to this option, support will be included for the SiS 645/961, ++ 645DX/961 and 735 mainboard I2C interfaces. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++VIA Technologies, Inc. VT82C586B ++CONFIG_I2C_VIA ++ If you say yes to this option, support will be included for the VIA ++ Technologies I2C adapter found on some motherboards. This can also ++ be built as a module which can be inserted and removed while the ++ kernel is running. ++ ++VIA Technologies, Inc. VT82C596, 596B, 686A/B, 8233, 8235 ++CONFIG_I2C_VIAPRO ++ If you say yes to this option, support will be included for the VIA ++ Technologies I2C adapter on these chips. For integrated sensors on the ++ Via 686A/B, use CONFIG_SENSORS_VIA686A. This can also be ++ be built as a module which can be inserted and removed while the ++ kernel is running. ++ ++3DFX Banshee / Voodoo3 ++CONFIG_I2C_VOODOO3 ++ If you say yes to this option, support will be included for the ++ 3DFX Banshee and Voodoo3 I2C interfaces. The I2C busses on the these ++ chips are generally used only for video devices. ++ This can also be ++ built as a module which can be inserted and removed while the kernel ++ is running. ++ ++DEC Tsunami 21272 ++CONFIG_I2C_TSUNAMI ++ If you say yes to this option, support will be included for the DEC ++ Tsunami chipset I2C adapter. Requires the Alpha architecture; ++ do not enable otherwise. This can also be built as a module which ++ can be inserted and removed while the kernel is running. ++ ++Pseudo ISA adapter (for hardware sensors modules) ++CONFIG_I2C_ISA ++ This provides support for accessing some hardware sensor chips over ++ the ISA bus rather than the I2C or SMBus. If you want to do this, ++ say yes here. This feature can also be built as a module which can ++ be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Analog Devices ADM1021 and compatibles ++CONFIG_SENSORS_ADM1021 ++ If you say yes here you get support for Analog Devices ADM1021 ++ and ADM1023 sensor chips and clones: Maxim MAX1617 and MAX1617A, ++ Genesys Logic GL523SM, National Semi LM84, TI THMC10 and Onsemi ++ MC1066. This can also be built as a module which can be inserted ++ and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Analog Devices ADM1024 ++CONFIG_SENSORS_ADM1024 ++ If you say yes here you get support for Analog Devices ADM1024 sensor ++ chips. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Analog Devices ADM1025 ++CONFIG_SENSORS_ADM1025 ++ If you say yes here you get support for Analog Devices ADM1025 sensor ++ chips. This can also be built as a module which can be inserted and ++ removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Analog Devices ADM1026 ++CONFIG_SENSORS_ADM1026 ++ If you say yes here you get support for Analog Devices ADM1026 sensor ++ chips. This can also be built as a module which can be inserted and ++ removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Analog Devices ADM9240 and compatibles ++CONFIG_SENSORS_ADM9240 ++ If you say yes here you get support for Analog Devices ADM9240 ++ sensor chips and clones: the Dallas Semiconductor DS1780 and ++ the National Semiconductor LM81. This can also be built as a ++ module which can be inserted and removed while the kernel is ++ running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Asus ASB100 ++CONFIG_SENSORS_ASB100 ++ If you say yes here you get support for the Asus ASB100 (aka ++ "Bach") sensor chip. This can also be built as a module. ++ ++ You will also need the latest user-space utilities: you can find ++ them in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++Dallas DS1621 and DS1625 ++CONFIG_SENSORS_DS1621 ++ If you say yes here you get support for the Dallas DS1621 and DS1625x ++ sensor chips. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Fujitsu-Siemens Poseidon ++CONFIG_SENSORS_FSCPOS ++ If you say yes here you get support for the Fujitsu-Siemens Poseidon ++ sensor chip. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Fujitsu-Siemens Scylla ++CONFIG_SENSORS_FSCSCY ++ If you say yes here you get support for the Fujitsu-Siemens Scylla ++ sensor chip. This can also be built as a module. This driver may/should ++ also work with the following Fujitsu-Siemens chips: "Poseidon", ++ "Poseidon II" and "Hydra". You may have to force loading of the module ++ for motherboards in these cases. Be careful - those motherboards have ++ not been tested with this driver. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Genesys Logic GL518SM ++CONFIG_SENSORS_GL518SM ++ If you say yes here you get support for Genesys Logic GL518SM sensor ++ chips. This can also be built as a module which can be inserted and ++ removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Genesys Logic GL520SM ++CONFIG_SENSORS_GL520SM ++ If you say yes here you get support for Genesys Logic GL518SM sensor ++ chips. This can also be built as a module which can be inserted and ++ removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++HP Maxilife ++CONFIG_SENSORS_MAXILIFE ++ If you say yes here you get support for the HP Maxilife ++ sensor chip. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Intel Xeon Thermal Sensor ++CONFIG_SENSORS_XEONTEMP ++ If you say yes here you get support for the Intel Xeon processor ++ built-in thermal sensor. This can also be built as a module which ++ can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilities: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++ITE 8705, 8712, Sis950 ++CONFIG_SENSORS_IT87 ++ If you say yes here you get support for the ITE 8705 and 8712 and ++ SiS950 sensor chips. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Maxim MAX6650, MAX6651 ++CONFIG_SENSORS_MAX6650 ++ If you say yes here you get support for the Maxim MAX6650 and ++ MAX6651 sensor chips. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Myson MTP008 ++CONFIG_SENSORS_MTP008 ++ If you say yes here you get support for the Myson MTP008 ++ sensor chip. This can also be built as a module. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor LM75 and compatibles ++CONFIG_SENSORS_LM75 ++ If you say yes here you get support for National Semiconductor LM75 ++ sensor chips and clones: Dallas Semiconductor DS75 and DS1775 (in ++ 9-bit precision mode), and TelCom (now Microchip) TCN75. This can ++ also be built as a module which can be inserted and removed while ++ the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor LM78 ++CONFIG_SENSORS_LM78 ++ If you say yes here you get support for National Semiconductor LM78 ++ sensor chips family: the LM78-J and LM79. Many clone chips will ++ also work at least somewhat with this driver. This can also be built ++ as a module which can be inserted and removed while the kernel is ++ running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor LM80 ++CONFIG_SENSORS_LM80 ++ If you say yes here you get support for National Semiconductor LM80 ++ sensor chips. This can also be built as a module which can be ++ inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor LM83 ++CONFIG_SENSORS_LM83 ++ If you say yes here you get support for the National Semiconductor ++ LM83 sensor chip. This can also be built as a module. ++ ++ You will also need the latest user-space utilities: you can find ++ them in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++National Semiconductor LM85 ++CONFIG_SENSORS_LM85 ++ If you say yes here you get support for National Semiconductor LM85 ++ sensor chips and compatibles. Compatible chips include the Analog ++ Devices ADM1027 and ADT7463 and SMSC EMC6D100 and EMC6D101. This ++ can also be built as a module which can be inserted and removed ++ while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor LM87 ++CONFIG_SENSORS_LM87 ++ If you say yes here you get support for National Semiconductor LM87 ++ sensor chips. This can also be built as a module which can be ++ inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor LM90 ++CONFIG_SENSORS_LM90 ++ If you say yes here you get support for the National Semiconductor ++ LM90, LM89 and LM99, and Analog Devices ADM1032 sensor chips. This ++ can also be built as a module. ++ ++ You will also need the latest user-space utilities: you can find ++ them in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++National Semiconductor LM92 ++CONFIG_SENSORS_LM92 ++ If you say yes here you get support for National Semiconductor LM92 ++ sensor chips. This can also be built as a module which can be ++ inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++National Semiconductor PC8736x Sensors ++CONFIG_SENSORS_PC87360 ++ If you say yes here you get support for the integrated hardware ++ monitoring in the National Semicoductor PC87360, PC87363, PC87364, ++ PC87365 and PC87366 Super I/O chips. This can also be built as a ++ module which can be inserted and removed while the kernel is ++ running. ++ ++ You will also need the latest user-space utilities: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++Philips PCF8574 ++CONFIG_SENSORS_PCF8574 ++ If you say yes here you get support for the Philips PCF8574 ++ I2C 8-bit Parallel I/O device. ++ This can also be built as a module which can be ++ inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Philips PCF8591 ++CONFIG_SENSORS_PCF8591 ++ If you say yes here you get support for the Philips PCF8591 ++ I2C Quad D/A + Single A/D I/O device. ++ This can also be built as a module which can be ++ inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Silicon Integrated Systems Corp. SiS5595 Sensor ++CONFIG_SENSORS_SIS5595 ++ If you say yes here you get support for the integrated sensors in ++ SiS5595 South Bridges. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++SMSC47M1xx Super I/O Fan Support ++CONFIG_SENSORS_SMSC47M1 ++ If you say yes here you get support for the integrated fan ++ monitoring and control in the SMSC 47M1xx Super I/O chips. ++ This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Texas Instruments THMC50 / Analog Devices ADM1022 ++CONFIG_SENSORS_THMC50 ++ If you say yes here you get support for Texas Instruments THMC50 ++ sensor chips and clones: the Analog Devices ADM1022. ++ This can also be built as a module which ++ can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Via VT82C686A/B ++CONFIG_SENSORS_VIA686A ++ If you say yes here you get support for the integrated sensors in ++ Via 686A/B South Bridges. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Via VT1211 Sensors ++CONFIG_SENSORS_VT1211 ++ If you say yes here you get support for the integrated sensors in ++ the Via VT1211 Super I/O device. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Via VT8231 Sensors ++CONFIG_SENSORS_VT8231 ++ If you say yes here you get support for the integrated sensors in ++ the Via VT8231 device. This can also be built as a module ++ which can be inserted and removed while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Winbond W83781D, W83782D, W83783S, W83627HF, AS99127F ++CONFIG_SENSORS_W83781D ++ If you say yes here you get support for the Winbond W8378x series ++ of sensor chips: the W83781D, W83782D, W83783S and W83682HF, ++ and the similar Asus AS99127F. This ++ can also be built as a module which can be inserted and removed ++ while the kernel is running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ ++Winbond W83627HF, W83627THF, W83697HF ++CONFIG_SENSORS_W83627HF ++ If you say yes here you get support for the Winbond W836x7 series ++ of sensor chips: the Winbond W83627HF, W83627THF and W83697HF. This ++ can also be built as a module which can be inserted and removed ++ while the kernel is running. ++ ++ You will also need the latest user-space utilities: you can find ++ them in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++Winbond W83L785TS-S ++CONFIG_SENSORS_W83L785TS ++ If you say yes here you get support for the Winbond W83L785TS-S ++ sensor chip. This can also be built as a module. ++ ++ You will also need the latest user-space utilities: you can find ++ them in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu/ ++ ++EEprom (DIMM) reader ++CONFIG_SENSORS_EEPROM ++ If you say yes here you get read-only access to the EEPROM data ++ available on modern memory DIMMs, and which could theoretically ++ also be available on other devices. This can also be built as a ++ module which can be inserted and removed while the kernel is ++ running. ++ ++ You will also need the latest user-space utilties: you can find them ++ in the lm_sensors package, which you can download at ++ http://www.lm-sensors.nu ++ + # End: +--- linux-old/MAINTAINERS Sun Oct 3 01:33:28 2004 ++++ linux/MAINTAINERS Mon Dec 13 20:18:56 2004 +@@ -1659,6 +1659,15 @@ + L: linux-ide@vger.kernel.org + S: Supported + ++SENSORS DRIVERS ++P: Frodo Looijaard ++M: frodol@dds.nl ++P: Philip Edelbrock ++M: phil@netroedge.com ++L: sensors@stimpy.netroedge.com ++W: http://www.lm-sensors.nu/ ++S: Maintained ++ + SGI VISUAL WORKSTATION 320 AND 540 + P: Bent Hagemark + M: bh@sgi.com +--- linux-old/arch/i386/kernel/dmi_scan.c Fri Apr 16 03:14:11 2004 ++++ linux/arch/i386/kernel/dmi_scan.c Mon Dec 13 20:18:56 2004 +@@ -15,6 +15,7 @@ + #include "pci-i386.h" + + unsigned long dmi_broken; ++int is_unsafe_smbus; + int is_sony_vaio_laptop; + + struct dmi_header +@@ -371,6 +372,19 @@ + } + + /* ++ * Don't access SMBus on IBM systems which get corrupted eeproms ++ */ ++ ++static __init int disable_smbus(struct dmi_blacklist *d) ++{ ++ if (is_unsafe_smbus == 0) { ++ is_unsafe_smbus = 1; ++ printk(KERN_INFO "%s machine detected. Disabling SMBus accesses.\n", d->ident); ++ } ++ return 0; ++} ++ ++/* + * Check for a Sony Vaio system + * + * On a Sony system we want to enable the use of the sonypi +@@ -675,6 +689,10 @@ + NO_MATCH, NO_MATCH, + } }, + ++ { disable_smbus, "IBM", { ++ MATCH(DMI_SYS_VENDOR, "IBM"), ++ NO_MATCH, NO_MATCH, NO_MATCH ++ } }, + { sony_vaio_laptop, "Sony Vaio", { /* This is a Sony Vaio laptop */ + MATCH(DMI_SYS_VENDOR, "Sony Corporation"), + MATCH(DMI_PRODUCT_NAME, "PCG-"), +--- linux-old/arch/i386/kernel/i386_ksyms.c Fri Apr 16 03:14:11 2004 ++++ linux/arch/i386/kernel/i386_ksyms.c Mon Dec 13 20:18:56 2004 +@@ -179,6 +179,9 @@ + EXPORT_SYMBOL(atomic_dec_and_lock); + #endif + ++extern int is_unsafe_smbus; ++EXPORT_SYMBOL(is_unsafe_smbus); ++ + extern int is_sony_vaio_laptop; + EXPORT_SYMBOL(is_sony_vaio_laptop); + |