--- linux-old/drivers/i2c/dmi_scan.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/dmi_scan.c Thu Feb 19 15:51:23 2004 @@ -0,0 +1,229 @@ +/* + Taken from arch/i386/kernel/dmi_scan.c. + Changes dmi_ident to be non-static so we can access. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +struct dmi_header +{ + u8 type; + u8 length; + u16 handle; +}; + +#define dmi_printk(x) +//#define dmi_printk(x) printk x + +static char * __init dmi_string(struct dmi_header *dm, u8 s) +{ + u8 *bp=(u8 *)dm; + bp+=dm->length; + if(!s) + return ""; + s--; + while(s>0 && *bp) + { + bp+=strlen(bp); + bp++; + s--; + } + return bp; +} + +/* + * We have to be cautious here. We have seen BIOSes with DMI pointers + * pointing to completely the wrong place for example + */ + +static int __init dmi_table(u32 base, int len, int num, void (*decode)(struct dmi_header *)) +{ + u8 *buf; + struct dmi_header *dm; + u8 *data; + int i=0; + + buf = ioremap(base, len); + if(buf==NULL) + return -1; + + data = buf; + + /* + * Stop when we see all the items the table claimed to have + * OR we run off the end of the table (also happens) + */ + + while(ilength; + while(data-buf>4, buf[14]&0x0F)); + else + dmi_printk((KERN_INFO "DMI present.\n")); + dmi_printk((KERN_INFO "%d structures occupying %d bytes.\n", + num, len)); + dmi_printk((KERN_INFO "DMI table at 0x%08X.\n", + base)); + if(dmi_table(base,len, num, decode)==0) + return 0; + } + fp+=16; + } + return -1; +} + + +char *dmi_ident[DMI_STRING_MAX]; + +/* + * Save a DMI string + */ + +static void __init dmi_save_ident(struct dmi_header *dm, int slot, int string) +{ + char *d = (char*)dm; + char *p = dmi_string(dm, d[string]); + if(p==NULL || *p == 0) + return; + if (dmi_ident[slot]) + return; + dmi_ident[slot] = kmalloc(strlen(p)+1, GFP_KERNEL); + if(dmi_ident[slot]) + strcpy(dmi_ident[slot], p); + else + printk(KERN_ERR "dmi_save_ident: out of memory.\n"); +} + +/* + * Process a DMI table entry. Right now all we care about are the BIOS + * and machine entries. For 2.5 we should pull the smbus controller info + * out of here. + */ + +static void __init dmi_decode(struct dmi_header *dm) +{ + switch(dm->type) + { + case 0: + dmi_printk(("BIOS Vendor: %s\n", + dmi_string(dm, data[4]))); + dmi_save_ident(dm, DMI_BIOS_VENDOR, 4); + dmi_printk(("BIOS Version: %s\n", + dmi_string(dm, data[5]))); + dmi_save_ident(dm, DMI_BIOS_VERSION, 5); + dmi_printk(("BIOS Release: %s\n", + dmi_string(dm, data[8]))); + dmi_save_ident(dm, DMI_BIOS_DATE, 8); + break; + case 1: + dmi_printk(("System Vendor: %s\n", + dmi_string(dm, data[4]))); + dmi_save_ident(dm, DMI_SYS_VENDOR, 4); + dmi_printk(("Product Name: %s\n", + dmi_string(dm, data[5]))); + dmi_save_ident(dm, DMI_PRODUCT_NAME, 5); + dmi_printk(("Version: %s\n", + dmi_string(dm, data[6]))); + dmi_save_ident(dm, DMI_PRODUCT_VERSION, 6); + dmi_printk(("Serial Number: %s\n", + dmi_string(dm, data[7]))); + break; + case 2: + dmi_printk(("Board Vendor: %s\n", + dmi_string(dm, data[4]))); + dmi_save_ident(dm, DMI_BOARD_VENDOR, 4); + dmi_printk(("Board Name: %s\n", + dmi_string(dm, data[5]))); + dmi_save_ident(dm, DMI_BOARD_NAME, 5); + dmi_printk(("Board Version: %s\n", + dmi_string(dm, data[6]))); + dmi_save_ident(dm, DMI_BOARD_VERSION, 6); + break; + } +} + +void __init dmi_scan_mach(void) +{ + int err; + printk("dmi_scan.o version %s (%s)\n", LM_VERSION, LM_DATE); + err = dmi_iterate(dmi_decode); + if(err) + printk("dmi_scan.o: SM BIOS not found\n"); + else + printk("dmi_scan.o: SM BIOS found\n"); +} + +MODULE_DESCRIPTION("SM BIOS DMI Scanner"); +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL(dmi_ident); +EXPORT_SYMBOL(dmi_scan_mach); --- linux-old/include/linux/dmi_scan.h Thu Jan 1 00:00:00 1970 +++ linux/include/linux/dmi_scan.h Thu Feb 19 15:51:23 2004 @@ -0,0 +1,15 @@ +enum +{ + DMI_BIOS_VENDOR, + DMI_BIOS_VERSION, + DMI_BIOS_DATE, + DMI_SYS_VENDOR, + DMI_PRODUCT_NAME, + DMI_PRODUCT_VERSION, + DMI_BOARD_VENDOR, + DMI_BOARD_NAME, + DMI_BOARD_VERSION, + DMI_STRING_MAX +}; + +extern char *dmi_ident[DMI_STRING_MAX]; --- linux-old/drivers/i2c/i2c-ali1535.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-ali1535.c Thu Feb 19 15:51:23 2004 @@ -0,0 +1,601 @@ +/* + i2c-ali1535.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2000 Frodo Looijaard , + Philip Edelbrock , + Mark D. Studebaker , + Dan Eaton and + Stephen Rousset + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + + +/* 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); +} + + +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); + release_region(ali1535_smba, ALI1535_SMB_IOSIZE); +} + +#ifdef RLX +EXPORT_SYMBOL(ali1535_smba); +EXPORT_SYMBOL(ali1535_access); +EXPORT_SYMBOL(i2c_ali1535_sem); +#endif + +MODULE_AUTHOR + ("Frodo Looijaard , Philip Edelbrock , " + "Mark D. Studebaker and Dan Eaton "); +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 Thu Feb 19 15:51:26 2004 @@ -0,0 +1,533 @@ +/* + ali15x3.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1999 Frodo Looijaard and + Philip Edelbrock and + Mark D. Studebaker + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +/* 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 , " + "Philip Edelbrock , " + "and Mark D. Studebaker "); +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 Thu Feb 19 15:51:26 2004 @@ -0,0 +1,425 @@ +/* + amd756.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + + Copyright (c) 1999-2002 Merlin Hughes + + Shamelessly ripped from i2c-piix4.c: + + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 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) +*/ + +/* + Supports AMD756, AMD766, AMD768, AMD8111 and nVidia nForce + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +#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 void __devexit amd756_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&amd756_adapter); +} + +static struct pci_driver amd756_driver = { + .name = "amd756 smbus", + .id_table = amd756_ids, + .probe = amd756_probe, + .remove = __devexit_p(amd756_remove), +}; + +static int __init i2c_amd756_init(void) +{ + printk(KERN_INFO "i2c-amd756.o version %s (%s)\n", LM_VERSION, LM_DATE); + return pci_module_init(&amd756_driver); +} + + +static void __exit i2c_amd756_exit(void) +{ + pci_unregister_driver(&amd756_driver); + release_region(amd756_ioport, SMB_IOSIZE); +} + +MODULE_AUTHOR("Merlin Hughes "); +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 Thu Feb 19 15:51:26 2004 @@ -0,0 +1,416 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +#ifndef I2C_HW_SMBUS_AMD8111 +#error Your i2c is too old - i2c-2.7.0 or greater required! +#endif + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR ("Vojtech Pavlik "); +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 Thu Feb 19 15:51:26 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 + + Based on i2c Support for Via Technologies 82C586B South Bridge + Copyright (c) 1998, 1999 Kyösti Mälkki + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include /* 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); +} + + +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); + iounmap((void *) hydra_base); +} + + + +MODULE_AUTHOR("Geert Uytterhoeven "); +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 Thu Feb 19 15:51:27 2004 @@ -0,0 +1,642 @@ +/* + i801.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998 - 2002 Frodo Looijaard , + Philip Edelbrock , and Mark D. Studebaker + + + 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) + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +/* 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) + 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, + }, + { 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); +} + +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); + release_region(i801_smba, (isich4 ? 16 : 8)); +} + +MODULE_AUTHOR ("Frodo Looijaard , " + "Philip Edelbrock , " + "and Mark D. Studebaker "); +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 Thu Feb 19 15:51:27 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 , + Philip Edelbrock , + Ralph Metzler , and + Mark D. Studebaker + + Based on code written by Ralph Metzler 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 +#include +#include +#include +#include +#include +#include /* for HZ */ +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 , Philip Edelbrock , Ralph Metzler , and Mark D. Studebaker "); +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 Thu Feb 19 15:51:27 2004 @@ -0,0 +1,74 @@ +/* + i2c-isa.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 "); +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-piix4.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-piix4.c Thu Feb 19 15:51:27 2004 @@ -0,0 +1,564 @@ +/* + piix4.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998 - 2002 Frodo Looijaard and + Philip Edelbrock + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + + +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. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,34) +void dmi_scan_mach(void); +#endif + +static int __devinit ibm_dmi_probe(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,34) + extern int is_unsafe_smbus; + return is_unsafe_smbus; +#else +#define IBM_SIGNATURE "IBM" + dmi_scan_mach(); + if(dmi_ident[DMI_SYS_VENDOR] == NULL) + return 0; + if(strncmp(dmi_ident[DMI_SYS_VENDOR], IBM_SIGNATURE, + strlen(IBM_SIGNATURE)) == 0) + return 1; + return 0; +#endif +} +#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()) { + 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 and " + "Philip Edelbrock "); +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 Thu Feb 19 15:51:27 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 + Mark D. Studebaker + + 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 +#include +#include +#include +#include +#include +#include /* for HZ */ +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 " + "and Mark D. Studebaker "); +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 Thu Feb 19 15:51:27 2004 @@ -0,0 +1,481 @@ +/* + sis5595.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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); +} + + +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); + release_region(sis5595_base + SMB_INDEX, 2); +} + + + +MODULE_AUTHOR("Frodo Looijaard "); +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 Thu Feb 19 15:51:27 2004 @@ -0,0 +1,527 @@ +/* + i2c-sis630.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + + Copyright (c) 2002,2003 Alexander Malysh + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + + +#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 "); +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 Thu Feb 19 15:51:27 2004 @@ -0,0 +1,579 @@ +/* + sis645.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + + Copyright (c) 2003 Mark M. Hoffman + + 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 +*/ + +/* + Note: we assume there can only be one SiS645 with one SMBus interface +*/ + +/* #define DEBUG 1 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +#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 + +/* 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_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 "); +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 Thu Feb 19 15:51:27 2004 @@ -0,0 +1,155 @@ +/* + i2c-tsunami.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2001 Oleg Vdovikin + + Based on code written by Ralph Metzler 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 +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 "); +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 Thu Feb 19 15:51:27 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 + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for HZ */ +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 "); +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 Thu Feb 19 15:51:28 2004 @@ -0,0 +1,514 @@ +/* + i2c-viapro.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998 - 2002 Frodo Looijaard , + Philip Edelbrock , Kyösti Mälkki , + Mark D. Studebaker + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +#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 and " + "Philip Edelbrock "); +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 Thu Feb 19 15:51:28 2004 @@ -0,0 +1,281 @@ +/* + voodoo3.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard , + Philip Edelbrock , + Ralph Metzler , and + Mark D. Studebaker + + Based on code written by Ralph Metzler 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 +#include +#include +#include +#include +#include +#include /* for HZ */ +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 , Philip Edelbrock , Ralph Metzler , and Mark D. Studebaker "); +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 Thu Feb 19 15:51:28 2004 @@ -0,0 +1,601 @@ +/* + adm1021.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 { + 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} +}; + +/* I choose here for semi-static allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct adm1021_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto error0; + } + + data = (struct adm1021_data *) (new_client + 1); + 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) + 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) & 0x0F0) == 0x030) + 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; + /* LM84 Mfr ID in a different place */ + else + if (adm1021_read_value + (new_client, ADM1021_REG_CONV_RATE_R) == 0x00) + kind = lm84; + else if (i == 0x54) + kind = mc1066; + 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"; + } else { +#ifdef DEBUG + printk("adm1021.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 = 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 */ + adm1021_init_client(new_client); + return 0; + + error4: + i2c_detach_client(new_client); + error3: + error1: + kfree(new_client); + 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, 0); + /* 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); + + 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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:28 2004 @@ -0,0 +1,793 @@ +/* + adm1024.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Add by Ken Bowley from the adm1025.c written by + Gordon Wu and from adm9240.c written by + Copyright (c) 1999 Frodo Looijaard + and Philip Edelbrock + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 { + 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); + +/* I choose here for semi-static ADM1024 allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct adm1024_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct adm1024_data *) (new_client + 1); + 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(new_client); + 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); + + 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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:28 2004 @@ -0,0 +1,577 @@ +/* + adm1025.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2000 Chen-Yuan Wu + Copyright (c) 2003-2004 Jean Delvare + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +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) & 0xff),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),0,255) + +#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 { + 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 analog_out; /* Register value */ + 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); + +/* I choose here for semi-static ADM1025 allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct adm1025_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct adm1025_data *) (new_client + 1); + 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) + 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(new_client); + 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); + + 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; + + data->vrm = DEFAULT_VRM; + + /* 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 " + " and Jean Delvare "); +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 Thu Feb 19 15:51:28 2004 @@ -0,0 +1,1748 @@ +/* + adm1026.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2002, 2003 Philip Pokorny + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +/* 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 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 (!(new_client = kmalloc((sizeof(struct i2c_client)) + + sizeof(struct adm1026_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct adm1026_data *) (new_client + 1); + 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(new_client); + 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); + + 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 + and Philip Edelbrock + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +MODULE_LICENSE("GPL"); + +/* 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) & 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 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 ADM9240, we need to keep some data in memory. That + data is pointed to by adm9240_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new adm9240 client is + allocated. */ +struct adm9240_data { + 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 register); +static int adm9240_write_value(struct i2c_client *client, u8 register, + 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); + +/* I choose here for semi-static ADM9240 allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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 = ""; + + /* 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 + ("adm9240.o: adm9240_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 adm9240_{read,write}_value. */ + + if (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct adm9240_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct adm9240_data *) (new_client + 1); + 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(new_client); + 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); + + return 0; + +} + + +static int adm9240_read_value(struct i2c_client *client, u8 reg) +{ + return 0xFF & 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. It should set limits, etc. */ +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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:29 2004 @@ -0,0 +1,1024 @@ +/* + asb100.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + + Copyright (c) 2003 Mark M. Hoffman + + (derived from w83781d.c) + + Copyright (c) 1998 - 2003 Frodo Looijaard , + Philip Edelbrock , and + Mark Studebaker + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include +#include "lm75.h" + +/* 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 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct asb100_data), GFP_KERNEL))) { + pr_debug("asb100.o: detect failed, kmalloc failed!\n"); + err = -ENOMEM; + goto ERROR0; + } + + data = (struct asb100_data *) (new_client + 1); + 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(new_client); +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; + } + + kfree(client); + + return 0; +} + +static u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +/* 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 = swap_bytes(i2c_smbus_read_word_data (cl, 0)); + break; + case 0x52: /* CONFIG */ + res = i2c_smbus_read_byte_data(cl, 1); + break; + case 0x53: /* HYST */ + res = swap_bytes(i2c_smbus_read_word_data (cl, 2)); + break; + case 0x55: /* MAX */ + default: + res = swap_bytes(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, swap_bytes(value)); + break; + case 0x55: /* MAX */ + i2c_smbus_write_word_data(cl, 3, swap_bytes(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 , " + "Frodo Looijaard , " + "Philip Edelbrock , and" + "Mark Studebaker "); + +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 Thu Feb 19 15:51:29 2004 @@ -0,0 +1,893 @@ +/* + bt869.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + + Copyright (c) 1998, 1999 Frodo Looijaard + Copyright (c) 2001, 2002 Stephen Davies + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 + + +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 + +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct bt869_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = + (struct bt869_data *) (((struct i2c_client *) new_client) + 1); + 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(new_client); + 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); + + 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 , Philip Edelbrock , Stephen Davies "); +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 Thu Feb 19 15:51:29 2004 @@ -0,0 +1,591 @@ +/* + ddcmon.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999, 2000 Frodo Looijaard , + Philip Edelbrock , + and Mark Studebaker + Copyright (c) 2003 Jean Delvare + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct ddcmon_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct ddcmon_data *) (new_client + 1); + memset(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; + + /* Now, we do the remaining detection. */ + if (checksum) { + int cs = 0; + /* prevent 24RF08 corruption (just in case) */ + i2c_smbus_write_quick(new_client, 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(new_client); + 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); + 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; idata + 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 , " + "Philip Edelbrock , " + "Mark Studebaker " + "and Jean Delvare "); +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 Thu Feb 19 15:51:29 2004 @@ -0,0 +1,535 @@ +/* + ds1621.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Christian W. Zuckschwerdt 2000-11-23 + based on lm75.c by Frodo Looijaard + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 u16 swap_bytes(u16 val); +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)) + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct ds1621_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct ds1621_data *) (new_client + 1); + 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(new_client); + 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); + + return 0; +} + + +static u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +/* 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 swap_bytes(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, + swap_bytes(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 "); +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 Thu Feb 19 15:51:30 2004 @@ -0,0 +1,418 @@ +/* + eeprom.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 2003-08-18 Jean Delvare + 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 +#include +#include +#include +#include +#include /* for capable() */ +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct eeprom_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct eeprom_data *) (new_client + 1); + memset(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; + + /* Now, we do the remaining detection. It is not there, unless you force + the checksum to work out. */ + if (checksum) { + int cs = 0; + /* prevent 24RF08 corruption */ + i2c_smbus_write_quick(new_client, 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(new_client); + 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); + + 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(" \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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:30 2004 @@ -0,0 +1,697 @@ +/* + fscpos.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2001 Hermann Jung + + 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 + and Philip Edelbrock +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct fscpos_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct fscpos_data *) (new_client + 1); + 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(new_client); + 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); + + 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 based on work from Frodo Looijaard and Philip Edelbrock "); +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 Thu Feb 19 15:51:30 2004 @@ -0,0 +1,922 @@ +/* + fscscy.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2001 Martin Knoblauch + + 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 + and Philip Edelbrock +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct fscscy_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct fscscy_data *) (new_client + 1); + 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(new_client); + 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); + + 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 based on work (fscpos) from Hermann Jung "); +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 Thu Feb 19 15:51:30 2004 @@ -0,0 +1,997 @@ +/* + gl518sm.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard , + Kyösti Mälkki + + 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 +#include +#include +#include +#include +#ifdef __SMP__ +#include +#endif +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 u16 swap_bytes(u16 val); +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct gl518_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct gl518_data *) (new_client + 1); + 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(new_client); + 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); + + return 0; +} + + +static u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +/* 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 swap_bytes(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, + swap_bytes(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 and Kyösti Mälkki "); +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 Thu Feb 19 15:51:30 2004 @@ -0,0 +1,807 @@ +/* + gl520sm.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard , + Kyösti Mälkki + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + 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[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 u16 swap_bytes(u16 val); +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} +}; + +/* I choose here for semi-static GL520SM allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct gl520_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct gl520_data *) (new_client + 1); + 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->two_temps = 1; + data->alarm_mask = 0xff; + gl520_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(new_client); + ERROR0: + return err; +} + + +/* Called when we have found a new GL520SM. It should set limits, etc. */ +static void gl520_init_client(struct i2c_client *client) +{ + /* Power-on defaults (bit 7=1) */ + gl520_write_value(client, GL520_REG_CONF, 0x80); + + /* No noisy output (bit 2=1), Comparator mode (bit 3=0), two fans (bit4=0), + standby mode (bit6=0) */ + gl520_write_value(client, GL520_REG_CONF, 0x04); + + /* Never interrupts */ + gl520_write_value(client, GL520_REG_MASK, 0x00); + + /* Clear status register (bit 5=1), start (bit6=1) */ + gl520_write_value(client, GL520_REG_CONF, 0x24); + gl520_write_value(client, GL520_REG_CONF, 0x44); +} + +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); + + return 0; +} + + +static u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +/* 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 swap_bytes(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, + swap_bytes(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 */ + 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); + + 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 == 0) && (!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; + } else { + data->two_temps = 1; + } + 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 and Kyösti Mälkki "); +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 Thu Feb 19 15:51:31 2004 @@ -0,0 +1,1113 @@ +/* + 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 + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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) +{ + /* safety precaution in case we put some other super IO chip + in config mode by accident */ + outb(0xAA, REG); + + /* documented exit sequence */ + 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_it87 = 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 INVALID 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),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) + +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_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0: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 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)) goto + ERROR0; + + if (is_isa) { + if (check_region(address, IT87_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 IT87-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 it87_{read,write}_value. */ + + if (!(new_client = kmalloc((sizeof(struct i2c_client)) + + sizeof(struct it87_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct it87_data *) (new_client + 1); + 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) + goto ERROR1; + if (!is_isa + && (it87_read_value(new_client, IT87_REG_I2C_ADDR) != + address)) 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); + 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(new_client); + 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); + + 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. It should set limits, etc. */ +static void it87_init_client(struct i2c_client *client) +{ + if( reset_it87 ) { + it87_write_value(client, IT87_REG_CONFIG, 0x80); + } + /* Start monitoring */ + it87_write_value(client, IT87_REG_CONFIG, + (it87_read_value(client, IT87_REG_CONFIG) & 0xb7) + | (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] = INVALID; + } + + 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; + default: + printk + (KERN_ERR + "it87.o: Invalid sensor type %ld; must be 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 "); +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_it87, "i"); +MODULE_PARM_DESC(reset_it87, "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 Thu Feb 19 15:51:31 2004 @@ -0,0 +1,320 @@ +/* + lm75.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#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 { + 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 u16 swap_bytes(u16 val); +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, cur, conf, hyst, os; + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm75_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto error0; + } + + data = (struct lm75_data *) (new_client + 1); + 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. It is lousy. */ + if (kind < 0) { + 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); + os = i2c_smbus_read_word_data(new_client, 3); + for (i = 0; i <= 0x1f; i++) + if ( + (i2c_smbus_read_byte_data + (new_client, i * 8 + 1) != conf) + || + (i2c_smbus_read_word_data + (new_client, i * 8 + 2) != hyst) + || + (i2c_smbus_read_word_data + (new_client, i * 8 + 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(new_client); + 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); + return 0; +} + +static u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +/* 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 swap_bytes(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, + swap_bytes(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 "); +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 Thu Feb 19 15:51:31 2004 @@ -0,0 +1,49 @@ +/* + lm75.h - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2003 Mark M. Hoffman + + 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 + +/* 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 Thu Feb 19 15:51:31 2004 @@ -0,0 +1,747 @@ +/* + lm78.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 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 (!(new_client = kmalloc((sizeof(struct i2c_client)) + + sizeof(struct lm78_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct lm78_data *) (new_client + 1); + 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(new_client); + 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); + + 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. It should set limits, etc. */ +static void lm78_init_client(struct i2c_client *client) +{ + int vid; + + /* Reset all except Watchdog values and last conversion values + This sets fan-divs to 2, among others */ + lm78_write_value(client, LM78_REG_CONFIG, 0x80); + + vid = lm78_read_value(client, LM78_REG_VID_FANDIV) & 0x0f; + if (((struct lm78_data *) (client->data))->type == lm79) + vid |= + (lm78_read_value(client, LM78_REG_CHIPID) & 0x01) << 4; + else + vid |= 0x10; + vid = VID_FROM_REG(vid); + + /* Start monitoring */ + lm78_write_value(client, LM78_REG_CONFIG, + (lm78_read_value(client, LM78_REG_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 "); +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 Thu Feb 19 15:51:31 2004 @@ -0,0 +1,618 @@ +/* + lm80.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + and Philip Edelbrock + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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,nr) (SENSORS_LIMIT((val),0,255)) +#define IN_FROM_REG(val,nr) (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 = 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) + +/* For each registered LM80, we need to keep some data in memory. That + data is pointed to by lm80_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new lm80 client is + allocated. */ +struct lm80_data { + 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 register); +static int lm80_write_value(struct i2c_client *client, u8 register, + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm80_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct lm80_data *) (new_client + 1); + 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(new_client); + 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); + + 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. It should set limits, etc. */ +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], 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); + 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], nr); + 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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:31 2004 @@ -0,0 +1,505 @@ +/* + * lm83.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003 Jean Delvare + * + * 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* + * 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-256 : val) +#define TEMP_TO_REG(val) (val < 0 ? val+256 : val) + +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 +{ + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + sizeof(struct + lm83_data), GFP_KERNEL))) + { + printk("lm83.o: Out of memory in lm83_detect (new_client).\n"); + return -ENOMEM; + } + + /* + * The LM83-specific data is placed right after the common I2C + * client data, and is pointed to by the data field from the I2C + * client data. + */ + + new_client->addr = address; + new_client->data = data = (struct lm83_data *) (new_client + 1); + 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. + */ + + 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(new_client); + 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); + 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 "); +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 Thu Feb 19 15:51:32 2004 @@ -0,0 +1,2050 @@ +/* + lm85.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + Copyright (c) 2002, 2003 Philip Pokorny + Copyright (c) 2003 Margit Schubert-While + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +#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 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 (!(new_client = kmalloc((sizeof(struct i2c_client)) + + sizeof(struct lm85_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct lm85_data *) (new_client + 1); + 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(new_client); + 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); + + 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 for help with + * post 2.7.0 CVS changes + */ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Philip Pokorny + Philip Edelbrock + Stephen Rousset + Dan Eaton + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +/* 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 { + 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); + +/* I choose here for semi-static LM87 allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm87_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct lm87_data *) (new_client + 1); + 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(new_client); + 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); + + 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 , Philip Edelbrock , " + "Mark Studebaker , and Stephen Rousset "); + +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 Thu Feb 19 15:51:32 2004 @@ -0,0 +1,729 @@ +/* + * lm90.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003 Jean Delvare + * + * 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 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 + * + * 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* + * Addresses to scan + * Address is fully defined internally and cannot be changed. + */ + +static unsigned short normal_i2c[] = { 0x4c, 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(lm90, adm1032); + +/* + * 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 +{ + 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 = ""; + u8 reg_config1=0, reg_convrate=0; + +#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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm90_data), GFP_KERNEL))) + { + printk("lm90.o: Out of memory in lm90_detect (new_client).\n"); + return -ENOMEM; + } + + /* + * The LM90-specific data is placed right after the common I2C + * client data, and is pointed to by the data field from the I2C + * client data. + */ + + new_client->addr = address; + new_client->data = data = (struct lm90_data *) (new_client + 1); + 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. + */ + + if (kind < 0) /* detection */ + { + 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 ((reg_config1 & 0x2A) != 0x00 + || reg_convrate > 0x0A) + { +#ifdef DEBUG + printk(KERN_DEBUG "lm90.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, + LM90_REG_R_MAN_ID); + chip_id = i2c_smbus_read_byte_data(new_client, + LM90_REG_R_CHIP_ID); + + if (man_id == 0x01) /* National Semiconductor */ + { + if (chip_id >= 0x21 && chip_id < 0x30 /* LM90 */ + && (kind == 0 /* skip detection */ + || ((i2c_smbus_read_byte_data(new_client, + LM90_REG_R_CONFIG2) & 0xF8) == 0x00 + && reg_convrate <= 0x09))) + { + kind = lm90; + } + } + else if (man_id == 0x41) /* Analog Devices */ + { + if ((chip_id & 0xF0) == 0x40 /* ADM1032 */ + && (kind == 0 /* skip detection */ + || (reg_config1 & 0x3F) == 0x00)) + { + kind = adm1032; + } + } + } + + 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 + { + 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(new_client); + 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); + 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 "); +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 Thu Feb 19 15:51:32 2004 @@ -0,0 +1,396 @@ + +/* + * LM92 - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * + * Author: Abraham van der Merwe + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 { + 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, 0x4b, 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 = ((u16) tmp >> 8) | (u16) ((u16) tmp << 8); + + return (0); +} + +static inline int lm92_write16 (struct i2c_client *client,u8 reg,u16 value) +{ + /* convert the data to big endian format */ + u16 be = (value >> 8) | (u16) (value << 8); + return (i2c_smbus_write_word_data (client,reg,be) < 0 ? -EIO : 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 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 ((client = kmalloc (sizeof (struct i2c_client) + sizeof (lm92_t),GFP_KERNEL)) == NULL) + return (-ENOMEM); + + data = (lm92_t *) (client + 1); + 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)) { + kfree (client); + return (-ERESTARTSYS); + } + + if ((kind < 0 && lm92_read16 (client,LM92_REG_MANUFACTURER,&manufacturer) < 0) || + manufacturer != LM92_MANUFACTURER_ID) { + kfree (client); + up (&mutex); + return (-ENODEV); + } + + if ((result = i2c_attach_client (client))) { + kfree (client); + up (&mutex); + return (result); + } + + if ((result = i2c_register_entry (client,client->name,lm92_dir_table)) < 0) { + i2c_detach_client (client); + kfree (client); + up (&mutex); + return (result); + } + data->sysctl_id = result; + + if ((result = lm92_init_client (client)) < 0) { + i2c_deregister_entry (data->sysctl_id); + i2c_detach_client (client); + kfree (client); + up (&mutex); + return (result); + } + + client->id = id++; + + up (&mutex); + + return (0); +} + +static int lm92_attach_adapter (struct i2c_adapter *adapter) +{ + int result; + struct i2c_client_address_data lm92_client_data = { + normal_i2c: addr_data.normal_i2c, + normal_i2c_range: addr_data.normal_i2c_range, + probe: addr_data.probe, + probe_range: addr_data.probe_range, + ignore: addr_data.ignore, + ignore_range: addr_data.ignore_range, + force: addr_data.forces->force + }; + + if (!(result = i2c_probe (adapter,&lm92_client_data,lm92_detect))) + result = i2c_detect (adapter,&addr_data,lm92_detect); + + return (result); +} + +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); + + 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 "); +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 Thu Feb 19 15:51:32 2004 @@ -0,0 +1,287 @@ +/* + matorb.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + and Philip Edelbrock + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct matorb_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct matorb_data *) (new_client + 1); + 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(new_client); + 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); + + 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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:32 2004 @@ -0,0 +1,540 @@ +/* + * max6650.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring. + * + * Author: John Morris + * + * 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 +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* + * 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 +{ + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + sizeof(struct + max6650_data), GFP_KERNEL))) { + printk("max6650.o: Out of memory in max6650_detect (new_client).\n"); + return -ENOMEM; + } + + /* + * The max6650-specific data is placed right after the common I2C + * client data, and is pointed to by the data field from the I2C + * client data. + */ + + new_client->addr = address; + new_client->data = data = (struct max6650_data *) (new_client + 1); + 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(new_client); + 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); + 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 Thu Feb 19 15:51:33 2004 @@ -0,0 +1,1394 @@ +/* + maxilife.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1999-2000 Fons Rademakers + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +MODULE_LICENSE("GPL"); + +#undef AUTODETECT /* try to autodetect MaxiLife version */ +/*#define AUTODETECT*/ +#define NOWRITE /* don't allow writing to MaxiLife registers */ + +#ifdef AUTODETECT +#include +#include +#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 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct maxi_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + /* Fill the new client structure with data */ + data = (struct maxi_data *) (new_client + 1); + 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(new_client); + 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); + 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 "); +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 Thu Feb 19 15:51:33 2004 @@ -0,0 +1,1107 @@ +/* + mtp008.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2001 Kris Van Hees + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct mtp008_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + data = (struct mtp008_data *) (new_client + 1); + 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(new_client); + + 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); + + 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_TEMP_MAX, + data->in_max[nr]); + } + if (*nrels_mag >= 2) { + data->in_min[nr] = TEMP_TO_REG(results[1]); + mtp008_write_value(client, MTP008_REG_TEMP_MIN, + 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 , " + "Philip Edelbrock , " + "and Kris Van Hees "); +MODULE_DESCRIPTION("MTP008 driver"); + +module_init(sm_mtp008_init); +module_exit(sm_mtp008_exit); --- linux-old/drivers/sensors/pcf8574.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/pcf8574.c Thu Feb 19 15:51:33 2004 @@ -0,0 +1,331 @@ +/* + pcf8574.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2000 Frodo Looijaard , + Philip Edelbrock , + Dan Eaton + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 { + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + 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} +}; + +/* I choose here for semi-static PCF8574 allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +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. + But it allows us to access pcf8574_{read,write}_value. */ + if (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct pcf8574_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct pcf8574_data *) (new_client + 1); + 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 >= 0x20 && address <= 0x27) + kind = pcf8574; + else if (address >= 0x38 && address <= 0x3f) + kind = pcf8574a; + else goto ERROR1; + } + + if (kind == pcf8574) { + type_name = "pcf8574"; + client_name = "PCF8574 chip"; + } else + if (kind == pcf8574a) { + type_name = "pcf8574a"; + client_name = "PCF8574A chip"; + } else { +#ifdef DEBUG + printk("pcf8574.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 = pcf8574_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, + pcf8574_dir_table_template)) < 0) { + err = i; + goto ERROR4; + } + 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. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(new_client); + 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); + + 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); + + if ((jiffies - data->last_updated > 5*HZ) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting pcf8574 update\n"); +#endif + + data->read = i2c_smbus_read_byte(client); + data->last_updated = jiffies; + data->valid = 1; + } + + 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 , Philip Edelbrock , Dan Eaton and Aurelien Jarno "); +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 Thu Feb 19 15:51:33 2004 @@ -0,0 +1,459 @@ +/* + pcf8591.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2001 Aurelien Jarno + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +/* 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 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 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)) + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct pcf8591_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct pcf8591_data *) (new_client + 1); + 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; + + if (kind == pcf8591) { + type_name = "pcf8591"; + client_name = "PCF8591 chip"; + } else { +#ifdef DEBUG + printk(KERN_ERR "pcf8591.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 = 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: + ERROR1: + kfree(new_client); + 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); + + 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 "); +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 Thu Feb 19 15:51:33 2004 @@ -0,0 +1,740 @@ +/* + sis5595.c - Part of lm_sensors, Linux kernel modules + for hardware monitoring + + Copyright (c) 1998 - 2001 Frodo Looijaard , + Kyösti Mälkki , and + Mark D. Studebaker + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct sis5595_data), + GFP_KERNEL))) { + return -ENOMEM; + } + + data = (struct sis5595_data *) (new_client + 1); + 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(new_client); + 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); + + 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 "); +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 Thu Feb 19 15:51:33 2004 @@ -0,0 +1,509 @@ +/* + smsc47m1.c - Part of lm_sensors, Linux kernel modules + for hardware monitoring + + Copyright (c) 2002 Mark D. Studebaker + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +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) 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) == 0x59 || (id) == 0x5F) + +#define SMSC_ACT_REG 0x20 +#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) +#define PWM_FROM_REG(val) ((((val) & 0x7f) * 1005) / 640) +#define PWM_TO_REG(val) SENSORS_LIMIT((((val) * 645) / 1000), 0, 63) + +struct smsc47m1_data { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct smsc47m1_data), + GFP_KERNEL))) { + return -ENOMEM; + } + + data = (struct smsc47m1_data *) (new_client + 1); + 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(new_client); + 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); + + return 0; +} + +static int smsc47m1_read_value(struct i2c_client *client, u8 reg) +{ + int res; + + res = inb_p(client->addr + reg); + return res; +} + +static int smsc47m1_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + outb_p(value, client->addr + reg); + 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] >> 7; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->pwm[nr - 1] &= 0x80; + data->pwm[nr - 1] |= PWM_TO_REG(results[0]); + if (*nrels_mag >= 2) { + if(results[1] && (!(data->pwm[nr-1] & 0x80))) { + /* output PWM */ + smsc47m1_write_value(client, + SMSC47M1_REG_PPIN(nr), 0x04); + data->pwm[nr - 1] |= 0x80; + } + } + 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 "); +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 Thu Feb 19 15:51:34 2004 @@ -0,0 +1,499 @@ +/* + thmc50.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 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 +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2D, 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(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 { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct thmc50_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = + (struct thmc50_data *) (((struct i2c_client *) new_client) + + 1); + 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(new_client); + 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); + + 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 and Philip Edelbrock "); +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 Thu Feb 19 15:51:34 2004 @@ -0,0 +1,874 @@ +/* + via686a.c - Part of lm_sensors, Linux kernel modules + for hardware monitoring + + Copyright (c) 1998 - 2002 Frodo Looijaard , + Kyösti Mälkki , + Mark Studebaker , + and Bob Dougherty + (Some conversion-factor data were contributed by Jonathan Teh Soon Yew + and Alex van Kaam .) + + 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 +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" + + +/* 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. Rounding and 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 +// ) +// +// These get us close, but they don't completely agree with what my BIOS +// says- they are all a bit low. But, it all we have to go on... +static inline u8 IN_TO_REG(long val, int inNum) +{ + // to avoid floating point, we multiply everything by 100. + // val is guaranteed to be positive, so we can achieve the effect of + // rounding by (...*10+5)/10. Note that the *10 is hidden in the + // /250 (which should really be /2500). + // At the end, we need to /100 because we *100 everything and we need + // to /10 because of the rounding thing, so we /1000. + if (inNum <= 1) + return (u8) + SENSORS_LIMIT(((val * 210240 - 13300) / 250 + 5) / 1000, + 0, 255); + else if (inNum == 2) + return (u8) + SENSORS_LIMIT(((val * 157370 - 13300) / 250 + 5) / 1000, + 0, 255); + else if (inNum == 3) + return (u8) + SENSORS_LIMIT(((val * 101080 - 13300) / 250 + 5) / 1000, + 0, 255); + else + return (u8) SENSORS_LIMIT(((val * 41714 - 13300) / 250 + 5) + / 1000, 0, 255); +} + +static inline long IN_FROM_REG(u8 val, int inNum) +{ + // to avoid floating point, we multiply everything by 100. + // val is guaranteed to be positive, so we can achieve the effect of + // rounding by adding 0.5. Or, to avoid fp math, we do (...*10+5)/10. + // We need to scale with *100 anyway, so no need to /100 at the end. + if (inNum <= 1) + return (long) (((250000 * val + 13300) / 210240 * 10 + 5) /10); + else if (inNum == 2) + return (long) (((250000 * val + 13300) / 157370 * 10 + 5) /10); + else if (inNum == 3) + return (long) (((250000 * val + 13300) / 101080 * 10 + 5) /10); + else + return (long) (((250000 * val + 13300) / 41714 * 10 + 5) /10); +} + +/********* 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 ) 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 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 + (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. Just check the limits and go. +// The +5 effectively rounds off properly and the +50 is because +// the temps start at -50 +static inline u8 TEMP_TO_REG(long val) +{ + return (u8) + SENSORS_LIMIT(viaLUT[((val <= -500) ? 0 : (val >= 1100) ? 160 : + ((val + 5) / 10 + 50))], 0, 255); +} + +/* for 8-bit temperature hyst and over registers */ +// The temp values are already *10, so we don't need to do that. +// But we _will_ round these off to the nearest degree with (...*10+5)/10 +#define TEMP_FROM_REG(val) ((tempLUT[(val)]*10+5)/10) + +/* 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; + + // handle the extremes first (they won't interpolate well! ;-) + if (val == 0) + return (long) tempLUT[0]; + if (val == 1023) + return (long) tempLUT[255]; + + if (twoBits == 0) + return (long) tempLUT[eightBits]; + else { + // do some interpolation by multipying the lower and upper + // bounds by 25, 50 or 75, then /100. + temp = ((25 * (4 - twoBits)) * tempLUT[eightBits] + + (25 * twoBits) * tempLUT[eightBits + 1]); + // increase the magnitude by 50 to achieve rounding. + if (temp > 0) + temp += 50; + else + temp -= 50; + return (temp / 100); + } +} + +#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. That + data is pointed to by via686a_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new via686a client is + allocated. */ +struct via686a_data { + 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 (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct via686a_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct via686a_data *) (new_client + 1); + 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(new_client); + 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); + + 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 , " + "Mark Studebaker " + "and Bob Dougherty "); +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 Thu Feb 19 15:51:34 2004 @@ -0,0 +1,825 @@ +/* + vt1211.c - Part of lm_sensors, Linux kernel modules + for hardware monitoring + + Copyright (c) 2002 Mark D. Studebaker + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20040207" +#define LM_VERSION "2.8.4" +#include + +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 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}, + {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();