--- linux-old/drivers/i2c/i2c-ali1535.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-ali1535.c Sun Feb 26 11:18:36 2006 @@ -0,0 +1,535 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + + +/* 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 struct pci_driver ali1535_driver; +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_driver.name); + +#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; +} + + +#define MAX_TIMEOUT_HEROS 100 +#define MAX_TIMEOUT_BLOCK_HEROS 500 +#define MAX_TRY_HEROS 3 + +/* believe it or not this is the delay technique recommended by ALI */ +static void ali1535_delay_loop(void) +{ + int i; + + for(i=0;i<30;i++) + outb_p(0,0xEB); +} + +static int ali1535_wait_for_status(int count,int status) +{ + int i; + int dat; + + for (i = 0; i < count; i++) { + ali1535_delay_loop(); + dat = inb_p(SMBHSTSTS); + if (dat == status) + break; + } + + return i == count ? 1 : 0; +} + +/* 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; + s32 result = 0; + int timeout = 0; + int oldsize = size; + + down(&i2c_ali1535_sem); + +repeat: + if(timeout++ > MAX_TRY_HEROS) { + result = -1; + goto EXIT; + } + + /* clear status */ + outb_p(0xFF, SMBHSTSTS); + + if (ali1535_wait_for_status(MAX_TIMEOUT_HEROS,ALI1535_STS_IDLE)) + goto repeat; + + outb_p(ALI1535_KILL,SMBHSTTYP); + + if (ali1535_wait_for_status(MAX_TIMEOUT_HEROS,ALI1535_STS_FAIL)) + goto repeat; + + /* clear status */ + outb_p(0xFF, SMBHSTSTS); + + if (ali1535_wait_for_status(MAX_TIMEOUT_HEROS,ALI1535_STS_IDLE)) + goto repeat; + + switch (size) { + case I2C_SMBUS_QUICK: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD); + ali1535_delay_loop(); + size = ALI1535_QUICK; + outb_p(size, SMBHSTTYP); /* output command */ + ali1535_delay_loop(); + break; + case I2C_SMBUS_BYTE: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD); + ali1535_delay_loop(); + size = ALI1535_BYTE; + outb_p(size, SMBHSTTYP); /* output command */ + ali1535_delay_loop(); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(command, SMBHSTCMD); + ali1535_delay_loop(); + } + break; + case I2C_SMBUS_BYTE_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD); + ali1535_delay_loop(); + size = ALI1535_BYTE_DATA; + outb_p(size, SMBHSTTYP); /* output command */ + ali1535_delay_loop(); + outb_p(command, SMBHSTCMD); + ali1535_delay_loop(); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->byte, SMBHSTDAT0); + ali1535_delay_loop(); + } + break; + case I2C_SMBUS_WORD_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD); + ali1535_delay_loop(); + size = ALI1535_WORD_DATA; + outb_p(size, SMBHSTTYP); /* output command */ + outb_p(command, SMBHSTCMD); + ali1535_delay_loop(); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT0); + ali1535_delay_loop(); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); + ali1535_delay_loop(); + } + break; + case I2C_SMBUS_BLOCK_DATA: + outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD); + ali1535_delay_loop(); + size = ALI1535_BLOCK_DATA; + outb_p(size, SMBHSTTYP); /* output command */ + outb_p(command, SMBHSTCMD); + ali1535_delay_loop(); + 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); + ali1535_delay_loop(); + { + int val; + + val = inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR; + ali1535_delay_loop(); + outb_p(val, SMBHSTTYP); /* Reset SMBBLKDAT */ + ali1535_delay_loop(); + } + for (i = 1; i <= len; i++) { + outb_p(data->block[i], SMBBLKDAT); + ali1535_delay_loop(); + } + } + break; + default: + printk + (KERN_WARNING "i2c-ali1535.o: Unsupported transaction %d\n", size); + result = -1; + goto EXIT; + } + + /* start the transaction */ + outb_p(0xFF, SMBHSTPORT); + ali1535_delay_loop(); + + if (ali1535_wait_for_status(size == ALI1535_BLOCK_DATA ? + MAX_TIMEOUT_BLOCK_HEROS : + MAX_TIMEOUT_HEROS , + ALI1535_STS_IDLE | ALI1535_STS_DONE)) { + size = oldsize; + goto repeat; + } + + /* clear status */ + outb_p(0xFF, SMBHSTSTS); + ali1535_delay_loop(); + + if(inb_p(SMBHSTSTS) != ALI1535_STS_IDLE) { + size = oldsize; + goto repeat; + } + + 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); + ali1535_delay_loop(); + break; + case ALI1535_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT0); + ali1535_delay_loop(); + break; + case ALI1535_WORD_DATA: + data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); + ali1535_delay_loop(); + break; + case ALI1535_BLOCK_DATA: + len = inb_p(SMBHSTDAT0); + ali1535_delay_loop(); + if (len > 32) + len = 32; + data->block[0] = len; + { + int val; + + val = inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR; + ali1535_delay_loop(); + outb_p(val, SMBHSTTYP); /* Reset SMBBLKDAT */ + ali1535_delay_loop(); + } + for (i = 1; i <= data->block[0]; i++) { + data->block[i] = inb_p(SMBBLKDAT); + ali1535_delay_loop(); +#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; +} + +static void ali1535_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void ali1535_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_ALI1535, + .algo = &smbus_algorithm, + .inc_use = ali1535_inc, + .dec_use = ali1535_dec, +}; + + +static struct pci_device_id ali1535_ids[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_AL, + .device = PCI_DEVICE_ID_AL_M7101, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, } +}; + +static int __devinit ali1535_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + if (ali1535_setup(dev)) { + printk + ("i2c-ali1535.o: ALI1535 not detected, module not inserted.\n"); + return -ENODEV; + } + + sprintf(ali1535_adapter.name, "SMBus ALI1535 adapter at %04x", + ali1535_smba); + return i2c_add_adapter(&ali1535_adapter); +} + +static void __devexit ali1535_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&ali1535_adapter); + release_region(ali1535_smba, ALI1535_SMB_IOSIZE); +} + + +static struct pci_driver ali1535_driver = { + .name = "ali1535 smbus", + .id_table = ali1535_ids, + .probe = ali1535_probe, + .remove = __devexit_p(ali1535_remove), +}; + +static int __init i2c_ali1535_init(void) +{ + printk("i2c-ali1535.o version %s (%s)\n", LM_VERSION, LM_DATE); + return pci_module_init(&ali1535_driver); +} + +static void __exit i2c_ali1535_exit(void) +{ + pci_unregister_driver(&ali1535_driver); +} + +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-ali1563.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-ali1563.c Sun Feb 26 11:18:37 2006 @@ -0,0 +1,467 @@ +/** + * i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge + * + * Copyright (C) 2004, 2005 Patrick Mochel, Chunhao Huang + * + * The 1563 southbridge is deceptively similar to the 1533, with a + * few notable exceptions. One of those happens to be the fact they + * upgraded the i2c core to be 2.0 compliant, and happens to be almost + * identical to the i2c controller found in the Intel 801 south + * bridges. + * + * This driver is based on a mix of the 15x3, 1535, and i801 drivers, + * with a little help from the ALi 1563 spec. + * + * Chunhao Huang / Winbond + * Backport to linux-2.4 from linux-2.6 on Mar.2005 + * + * This file is released under the GPLv2 + */ + +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +#ifndef PCI_DEVICE_ID_AL_M1563 +#define PCI_DEVICE_ID_AL_M1563 0x1563 +#endif + +#define ALI1563_MAX_TIMEOUT 500 +#define ALI1563_SMBBA 0x80 +#define ALI1563_SMB_IOEN 1 +#define ALI1563_SMB_HOSTEN 2 +#define ALI1563_SMB_IOSIZE 16 + +#define SMB_HST_STS (ali1563_smba + 0) +#define SMB_HST_CNTL1 (ali1563_smba + 1) +#define SMB_HST_CNTL2 (ali1563_smba + 2) +#define SMB_HST_CMD (ali1563_smba + 3) +#define SMB_HST_ADD (ali1563_smba + 4) +#define SMB_HST_DAT0 (ali1563_smba + 5) +#define SMB_HST_DAT1 (ali1563_smba + 6) +#define SMB_BLK_DAT (ali1563_smba + 7) + +#define HST_STS_BUSY 0x01 +#define HST_STS_INTR 0x02 +#define HST_STS_DEVERR 0x04 +#define HST_STS_BUSERR 0x08 +#define HST_STS_FAIL 0x10 +#define HST_STS_DONE 0x80 +#define HST_STS_BAD 0x1c + + +#define HST_CNTL1_TIMEOUT 0x80 +#define HST_CNTL1_LAST 0x40 + +#define HST_CNTL2_KILL 0x04 +#define HST_CNTL2_START 0x40 +#define HST_CNTL2_QUICK 0x00 +#define HST_CNTL2_BYTE 0x01 +#define HST_CNTL2_BYTE_DATA 0x02 +#define HST_CNTL2_WORD_DATA 0x03 +#define HST_CNTL2_BLOCK 0x05 +#define HST_CNTL2_SIZEMASK 0x38 + +static struct pci_driver ali1563_pci_driver; +static unsigned short ali1563_smba; + +static int ali1563_transaction(struct i2c_adapter * a) +{ + u32 data; + int timeout; + + printk(KERN_DEBUG "ali1563: Transaction (pre): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD) { + printk(KERN_WARNING "ali1563: Trying to reset busy device\n"); + outb_p(data | HST_STS_BAD,SMB_HST_STS); + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD){ + return -EBUSY; + } + } + outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_START, SMB_HST_CNTL2); + + timeout = ALI1563_MAX_TIMEOUT; + do + i2c_delay(1); + while (((data = inb_p(SMB_HST_STS)) & HST_STS_BUSY) && --timeout); + + printk(KERN_DEBUG "ali1563: Transaction (post): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + if (timeout && !(data & HST_STS_BAD)) { + return 0; + } + + /* modified by Rudolf */ + if (!timeout) { + printk(KERN_ERR "ali1563: Timeout - Trying to KILL transaction!\n"); + /* Issue 'kill' to host controller */ + outb_p(HST_CNTL2_KILL,SMB_HST_CNTL2); + data = inb_p(SMB_HST_STS); + } + + /* device error - probably missing ACK */ + if (data & HST_STS_DEVERR) { + printk(KERN_DEBUG "ali1563: Device error!\n"); + } + + /* bus collision */ + if (data & HST_STS_BUSERR) { + printk(KERN_ERR "ali1563: Bus collision!\n"); + /* Issue timeout, hoping it helps */ + outb_p(HST_CNTL1_TIMEOUT,SMB_HST_CNTL1); + } + + if (data & HST_STS_FAIL) { + printk(KERN_ERR "ali1563: Cleaning fail after KILL!\n"); + outb_p(0x0,SMB_HST_CNTL2); + } + + return -1; +} + +static int ali1563_block_start(struct i2c_adapter * a) +{ + u32 data; + int timeout; + + printk(KERN_DEBUG "ali1563: Block (pre): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD) { + printk(KERN_WARNING "ali1563: Trying to reset busy device\n"); + outb_p(data | HST_STS_BAD,SMB_HST_STS); + data = inb_p(SMB_HST_STS); + if (data & HST_STS_BAD){ + return -EBUSY; + } + } + + /* Clear byte-ready bit */ + outb_p(data | HST_STS_DONE, SMB_HST_STS); + + /* Start transaction and wait for byte-ready bit to be set */ + outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_START, SMB_HST_CNTL2); + + timeout = ALI1563_MAX_TIMEOUT; + do + i2c_delay(1); + while (!((data = inb_p(SMB_HST_STS)) & HST_STS_DONE) && --timeout); + + printk(KERN_DEBUG "ali1563: Block (post): STS=%02x, CNTL1=%02x, " + "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", + inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), + inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), + inb_p(SMB_HST_DAT1)); + + if (timeout && !(data & HST_STS_BAD)){ + return 0; + } + printk(KERN_WARNING "ali1563: SMBus Error: %s%s%s%s%s\n", + timeout ? "Timeout " : "", + data & HST_STS_FAIL ? "Transaction Failed " : "", + data & HST_STS_BUSERR ? "No response or Bus Collision " : "", + data & HST_STS_DEVERR ? "Device Error " : "", + !(data & HST_STS_DONE) ? "Transaction Never Finished " : ""); + return -1; +} + +static int ali1563_block(struct i2c_adapter * a, union i2c_smbus_data * data, u8 rw) +{ + int i, len; + int error = 0; + + /* Do we need this? */ + outb_p(HST_CNTL1_LAST,SMB_HST_CNTL1); + + if (rw == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 1) + len = 1; + else if (len > 32) + len = 32; + outb_p(len,SMB_HST_DAT0); + outb_p(data->block[1],SMB_BLK_DAT); + } else + len = 32; + + outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_BLOCK, SMB_HST_CNTL2); + + for (i = 0; i < len; i++) { + if (rw == I2C_SMBUS_WRITE) { + outb_p(data->block[i + 1], SMB_BLK_DAT); + if ((error = ali1563_block_start(a))) + break; + } else { + if ((error = ali1563_block_start(a))) + break; + if (i == 0) { + len = inb_p(SMB_HST_DAT0); + if (len < 1) + len = 1; + else if (len > 32) + len = 32; + } + data->block[i+1] = inb_p(SMB_BLK_DAT); + } + } + /* Do we need this? */ + outb_p(HST_CNTL1_LAST,SMB_HST_CNTL1); + return error; +} + +static s32 ali1563_access(struct i2c_adapter * a, u16 addr, + unsigned short flags, char rw, u8 cmd, + int size, union i2c_smbus_data * data) +{ + int error = 0; + int timeout; + u32 reg; + + for (timeout = ALI1563_MAX_TIMEOUT; timeout; timeout--) { + if (!(reg = inb_p(SMB_HST_STS) & HST_STS_BUSY)) + break; + } + if (!timeout) + printk(KERN_WARNING "ali1563: SMBus not idle. HST_STS = %02x\n",reg); + outb_p(0xff,SMB_HST_STS); + + /* Map the size to what the chip understands */ + switch (size) { + case I2C_SMBUS_PROC_CALL: + printk(KERN_ERR "ali1563: I2C_SMBUS_PROC_CALL not supported!\n"); + error = -EINVAL; + break; + case I2C_SMBUS_QUICK: + size = HST_CNTL2_QUICK; + break; + case I2C_SMBUS_BYTE: + size = HST_CNTL2_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + size = HST_CNTL2_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + size = HST_CNTL2_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + size = HST_CNTL2_BLOCK; + break; + } + + outb_p(((addr & 0x7f) << 1) | (rw & 0x01), SMB_HST_ADD); + outb_p( (inb_p(SMB_HST_CNTL2)&~HST_CNTL2_SIZEMASK) | (size << 3), SMB_HST_CNTL2); + + /* Write the command register */ + switch(size) { + case HST_CNTL2_BYTE: + if (rw== I2C_SMBUS_WRITE) + /* outb_p(cmd, SMB_HST_CMD); */ + outb_p(cmd, SMB_HST_DAT0); /* modify by Rudolf */ + break; + case HST_CNTL2_BYTE_DATA: + outb_p(cmd, SMB_HST_CMD); + if (rw == I2C_SMBUS_WRITE) + outb_p(data->byte, SMB_HST_DAT0); + break; + case HST_CNTL2_WORD_DATA: + outb_p(cmd, SMB_HST_CMD); + if (rw == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMB_HST_DAT0); + outb_p((data->word & 0xff00) >> 8, SMB_HST_DAT1); + } + break; + case HST_CNTL2_BLOCK: + outb_p(cmd, SMB_HST_CMD); + error = ali1563_block(a,data,rw); + goto Done; + } + + if ((error = ali1563_transaction(a))){ + goto Done; + } + + if ((rw == I2C_SMBUS_WRITE) || (size == HST_CNTL2_QUICK)){ + goto Done; + } + + switch (size) { + case HST_CNTL2_BYTE: /* Result put in SMBHSTDAT0 */ + data->byte = inb_p(SMB_HST_DAT0); + break; + case HST_CNTL2_BYTE_DATA: + data->byte = inb_p(SMB_HST_DAT0); + break; + case HST_CNTL2_WORD_DATA: + data->word = inb_p(SMB_HST_DAT0) + (inb_p(SMB_HST_DAT1) << 8); + break; + } +Done: + return error; +} + +static u32 ali1563_func(struct i2c_adapter * a) +{ + 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 void ali1563_enable(struct pci_dev * dev) +{ + u16 ctrl; + + pci_read_config_word(dev,ALI1563_SMBBA,&ctrl); + ctrl |= 0x7; + pci_write_config_word(dev,ALI1563_SMBBA,ctrl); +} + +static int __devinit ali1563_setup(struct pci_dev * dev) +{ + u16 ctrl; + + pci_read_config_word(dev,ALI1563_SMBBA,&ctrl); + printk(KERN_DEBUG "ali1563: SMBus control = %04x\n",ctrl); + + /* Check if device is even enabled first */ + if (!(ctrl & ALI1563_SMB_IOEN)) { + printk(KERN_WARNING "ali1563: I/O space not enabled, trying manually\n"); + ali1563_enable(dev); + } + if (!(ctrl & ALI1563_SMB_IOEN)) { + printk(KERN_WARNING "ali1563: I/O space still not enabled, giving up\n"); + goto Err; + } + if (!(ctrl & ALI1563_SMB_HOSTEN)) { + printk(KERN_WARNING "ali1563: Host Controller not enabled\n"); + goto Err; + } + + /* SMB I/O Base in high 12 bits and must be aligned with the + * size of the I/O space. */ + ali1563_smba = ctrl & ~(ALI1563_SMB_IOSIZE - 1); + if (!ali1563_smba) { + printk(KERN_WARNING "ali1563: ali1563_smba Uninitialized\n"); + goto Err; + } + if (!request_region(ali1563_smba, ALI1563_SMB_IOSIZE, + ali1563_pci_driver.name)) { + printk(KERN_WARNING "ali1563: Could not allocate I/O space"); + goto Err; + } + + return 0; +Err: + return -ENODEV; +} + +static void ali1563_shutdown(struct pci_dev *dev) +{ + release_region(ali1563_smba,ALI1563_SMB_IOSIZE); +} + +static void ali1563_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void ali1563_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +static struct i2c_algorithm ali1563_algorithm = { + .name = "Non-i2c SMBus adapter", + .id = I2C_ALGO_SMBUS, + .smbus_xfer = ali1563_access, + .functionality = ali1563_func, +}; + +static struct i2c_adapter ali1563_adapter = { + .algo = &ali1563_algorithm, + .inc_use = ali1563_inc, + .dec_use = ali1563_dec, +}; + +static int __devinit ali1563_probe(struct pci_dev * dev, + const struct pci_device_id * id_table) +{ + int error; + + if ((error = ali1563_setup(dev))){ + return error; + } + sprintf(ali1563_adapter.name,"SMBus ALi 1563 Adapter @ %04x", + ali1563_smba); + if ((error = i2c_add_adapter(&ali1563_adapter))) + ali1563_shutdown(dev); + printk(KERN_DEBUG "%s: Returning %d\n",__FUNCTION__,error); + return error; +} + +static void __devexit ali1563_remove(struct pci_dev * dev) +{ + i2c_del_adapter(&ali1563_adapter); + ali1563_shutdown(dev); +} + +static struct pci_device_id ali1563_ids[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_AL, + .device = PCI_DEVICE_ID_AL_M1563, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, } +}; + +static struct pci_driver ali1563_pci_driver = { + .name = "ali1563 smbus", + .id_table = ali1563_ids, + .probe = ali1563_probe, + .remove = __devexit_p(ali1563_remove), +}; + +static int __init ali1563_init(void) +{ + printk("i2c-ali1563.o version %s (%s)\n", LM_VERSION, LM_DATE); + return pci_module_init(&ali1563_pci_driver); +} + +module_init(ali1563_init); + +static void __exit ali1563_exit(void) +{ + pci_unregister_driver(&ali1563_pci_driver); +} + +module_exit(ali1563_exit); + +MODULE_AUTHOR("Patrick Mochel , Chunhao Huang"); +MODULE_DESCRIPTION("ALi M1563 southbridge driver for linux-2.4, backported by Chunhao Huang from linux-2.6"); +MODULE_LICENSE("GPL"); + --- linux-old/drivers/i2c/i2c-ali15x3.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-ali15x3.c Sun Feb 26 11:18:37 2006 @@ -0,0 +1,549 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#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 struct pci_driver ali15x3_driver; +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_driver.name)) { + 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 void ali15x3_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void ali15x3_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_ALI15X3, + .algo = &smbus_algorithm, + .inc_use = ali15x3_inc, + .dec_use = ali15x3_dec, +}; + +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-s4882.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-amd756-s4882.c Sun Feb 26 11:18:37 2006 @@ -0,0 +1,265 @@ +/* + * i2c-amd756-s4882.c - i2c-amd756 extras for the Tyan S4882 motherboard + * + * Copyright (C) 2004 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. + */ + +/* + * We select the channels by sending commands to the Philips + * PCA9556 chip at I2C address 0x18. The main adapter is used for + * the non-multiplexed part of the bus, and 4 virtual adapters + * are defined for the multiplexed addresses: 0x50-0x53 (memory + * module EEPROM) located on channels 1-4, and 0x4c (LM63) + * located on multiplexed channels 0 and 5-7. We define one + * virtual adapter per CPU, which corresponds to two multiplexed + * channels: + * CPU0: virtual adapter 1, channels 1 and 0 + * CPU1: virtual adapter 2, channels 2 and 5 + * CPU2: virtual adapter 3, channels 3 and 6 + * CPU3: virtual adapter 4, channels 4 and 7 + */ + +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "i2c-amd756-s4882" + +extern struct i2c_adapter amd756_smbus; + +static struct i2c_adapter *s4882_adapter; +static struct i2c_algorithm *s4882_algo; + +/* Wrapper access functions for multiplexed SMBus */ +static struct semaphore amd756_lock; + +static s32 amd756_access_virt0(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + int error; + + /* We exclude the multiplexed addresses */ + if (addr == 0x4c || (addr & 0xfc) == 0x50 || (addr & 0xfc) == 0x30 + || addr == 0x18) + return -1; + + down(&amd756_lock); + + error = amd756_smbus.algo->smbus_xfer(adap, addr, flags, read_write, + command, size, data); + + up(&amd756_lock); + + return error; +} + +/* We remember the last used channels combination so as to only switch + channels when it is really needed. This greatly reduces the SMBus + overhead, but also assumes that nobody will be writing to the PCA9556 + in our back. */ +static u8 last_channels; + +static inline s32 amd756_access_channel(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data, + u8 channels) +{ + int error; + + /* We exclude the non-multiplexed addresses */ + if (addr != 0x4c && (addr & 0xfc) != 0x50 && (addr & 0xfc) != 0x30) + return -1; + + down(&amd756_lock); + + if (last_channels != channels) { + union i2c_smbus_data mplxdata; + mplxdata.byte = channels; + + error = amd756_smbus.algo->smbus_xfer(adap, 0x18, 0, + I2C_SMBUS_WRITE, 0x01, + I2C_SMBUS_BYTE_DATA, + &mplxdata); + if (error) + goto UNLOCK; + last_channels = channels; + } + error = amd756_smbus.algo->smbus_xfer(adap, addr, flags, read_write, + command, size, data); + +UNLOCK: + up(&amd756_lock); + return error; +} + +static s32 amd756_access_virt1(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU0: channels 1 and 0 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x03); +} + +static s32 amd756_access_virt2(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU1: channels 2 and 5 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x24); +} + +static s32 amd756_access_virt3(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU2: channels 3 and 6 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x48); +} + +static s32 amd756_access_virt4(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data * data) +{ + /* CPU3: channels 4 and 7 enabled */ + return amd756_access_channel(adap, addr, flags, read_write, command, + size, data, 0x90); +} + +static int __init amd756_s4882_init(void) +{ + int i, error; + union i2c_smbus_data ioconfig; + + /* Unregister physical bus */ + error = i2c_del_adapter(&amd756_smbus); + if (error) { + if (error != -ENODEV) + printk(KERN_ERR DRV_NAME ": Physical bus removal " + "failed\n"); + goto ERROR0; + } + + printk(KERN_INFO "Enabling SMBus multiplexing for Tyan S4882\n"); + init_MUTEX(&amd756_lock); + + /* Define the 5 virtual adapters and algorithms structures */ + if (!(s4882_adapter = kmalloc(5 * sizeof(struct i2c_adapter), + GFP_KERNEL))) { + error = -ENOMEM; + goto ERROR1; + } + if (!(s4882_algo = kmalloc(5 * sizeof(struct i2c_algorithm), + GFP_KERNEL))) { + error = -ENOMEM; + goto ERROR2; + } + + /* Fill in the new structures */ + s4882_algo[0] = *(amd756_smbus.algo); + s4882_algo[0].smbus_xfer = amd756_access_virt0; + s4882_adapter[0] = amd756_smbus; + s4882_adapter[0].algo = s4882_algo; + for (i = 1; i < 5; i++) { + s4882_algo[i] = *(amd756_smbus.algo); + s4882_adapter[i] = amd756_smbus; + sprintf(s4882_adapter[i].name, + "SMBus 8111 adapter (CPU%d)", i-1); + s4882_adapter[i].algo = s4882_algo+i; + } + s4882_algo[1].smbus_xfer = amd756_access_virt1; + s4882_algo[2].smbus_xfer = amd756_access_virt2; + s4882_algo[3].smbus_xfer = amd756_access_virt3; + s4882_algo[4].smbus_xfer = amd756_access_virt4; + + /* Configure the PCA9556 multiplexer */ + ioconfig.byte = 0x00; /* All I/O to output mode */ + error = amd756_smbus.algo->smbus_xfer(&amd756_smbus, 0x18, 0, + I2C_SMBUS_WRITE, 0x03, + I2C_SMBUS_BYTE_DATA, &ioconfig); + if (error) { + printk(KERN_ERR DRV_NAME ": PCA9556 configuration failed\n"); + error = -EIO; + goto ERROR3; + } + + /* Register virtual adapters */ + for (i = 0; i < 5; i++) { + error = i2c_add_adapter(s4882_adapter+i); + if (error) { + printk(KERN_ERR DRV_NAME + ": Virtual adapter %d registration " + "failed, module not inserted\n", i); + for (i--; i >= 0; i--) + i2c_del_adapter(s4882_adapter+i); + goto ERROR3; + } + } + + return 0; + +ERROR3: + kfree(s4882_algo); + s4882_algo = NULL; +ERROR2: + kfree(s4882_adapter); + s4882_adapter = NULL; +ERROR1: + i2c_del_adapter(&amd756_smbus); +ERROR0: + return error; +} + +static void __exit amd756_s4882_exit(void) +{ + if (s4882_adapter) { + int i; + + for (i = 0; i < 5; i++) + i2c_del_adapter(s4882_adapter+i); + kfree(s4882_adapter); + s4882_adapter = NULL; + } + if (s4882_algo) { + kfree(s4882_algo); + s4882_algo = NULL; + } + + /* Restore physical bus */ + if (i2c_add_adapter(&amd756_smbus)) + printk(KERN_ERR DRV_NAME ": Physical bus restoration " + "failed\n"); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("S4882 SMBus multiplexing"); +MODULE_LICENSE("GPL"); + +module_init(amd756_s4882_init); +module_exit(amd756_s4882_exit); --- linux-old/drivers/i2c/i2c-amd756.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-amd756.c Sun Feb 26 11:18:37 2006 @@ -0,0 +1,445 @@ +/* + 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) + 2004-02-15: Don't register driver to avoid driver conflicts. + (Daniel Rune Jensen) +*/ + +/* + Supports AMD756, AMD766, AMD768, AMD8111 and nVidia nForce + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +#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 void amd756_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void amd756_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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, +}; + +struct i2c_adapter amd756_smbus = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_AMD756, + .algo = &smbus_algorithm, + .inc_use = amd756_inc, + .dec_use = amd756_dec, +}; + +enum chiptype { AMD756, AMD766, AMD768, NFORCE, AMD8111 }; +static const char* chipname[] = { + "AMD756", "AMD766", "AMD768", + "nVidia 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_smbus.name, "SMBus %s adapter at %04x", + chipname[id->driver_data], amd756_ioport); + + error = i2c_add_adapter(&amd756_smbus); + if (error) { + printk(KERN_ERR DRV_NAME + ": Adapter registration failed, module not inserted.\n"); + goto out_err; + } + + return 0; + + out_err: + release_region(amd756_ioport, SMB_IOSIZE); + return error; +} + + +static int __init i2c_amd756_init(void) +{ + struct pci_dev *dev; + const struct pci_device_id *id; + + printk(KERN_INFO "i2c-amd756.o version %s (%s)\n", LM_VERSION, LM_DATE); + + pci_for_each_dev(dev) { + id = pci_match_device(amd756_ids, dev); + if (id && amd756_probe(dev, id) >= 0) + return 0; + } + + return -ENODEV; +} + + +static void __exit i2c_amd756_exit(void) +{ + i2c_del_adapter(&amd756_smbus); + release_region(amd756_ioport, SMB_IOSIZE); +} + +MODULE_AUTHOR("Merlin Hughes "); +MODULE_DESCRIPTION("AMD756/766/768/8111 and nVidia nForce SMBus driver"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(amd756_smbus); + +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,425 @@ +/* + * 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +#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; +}; + +static struct pci_driver amd8111_driver; + +/* + * 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: + 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; + + 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; +} + +static void amd8111_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void amd8111_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = 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_driver.name)) { + kfree(smbus); + return -1; + } + + 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; + smbus->adapter.inc_use = amd8111_inc; + smbus->adapter.dec_use = amd8111_dec; + + 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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,191 @@ +/* + 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 */ +#include + +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 void bit_hydra_inc(struct i2c_adapter *adap) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void bit_hydra_dec(struct i2c_adapter *adap) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +/* ------------------------------------------------------------------------ */ + +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 = { + .name = "Hydra i2c", + .id = I2C_HW_B_HYDRA, + .algo_data = &bit_hydra_data, + .inc_use = bit_hydra_inc, + .dec_use = bit_hydra_dec, +}; + +static struct pci_device_id hydra_ids[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_APPLE, + .device = PCI_DEVICE_ID_APPLE_HYDRA, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, } +}; + +static int __devinit hydra_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + unsigned int base_addr; + + base_addr = dev->resource[0].start; + hydra_base = (unsigned long) ioremap(base_addr, 0x100); + + pdregw(0); /* clear SCLK_OE and SDAT_OE */ + return i2c_bit_add_bus(&bit_hydra_ops); +} + +static void __devexit hydra_remove(struct pci_dev *dev) +{ + pdregw(0); /* clear SCLK_OE and SDAT_OE */ + i2c_bit_del_bus(&bit_hydra_ops); + iounmap((void *) hydra_base); +} + + +static struct pci_driver hydra_driver = { + .name = "hydra smbus", + .id_table = hydra_ids, + .probe = hydra_probe, + .remove = __devexit_p(hydra_remove), +}; + +static int __init i2c_hydra_init(void) +{ + return pci_module_init(&hydra_driver); +} + + +static void __exit i2c_hydra_exit(void) +{ + pci_unregister_driver(&hydra_driver); +} + + + +MODULE_AUTHOR("Geert Uytterhoeven "); +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,712 @@ +/* + 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) + 6300ESB 25A4 ("") + ICH6 266A ("") + ICH7 27DA ("") + ESB2 269B ("") + 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 supported for ICH5 and higher. + Block 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +/* 82801CA is undefined before kernel 2.4.13 */ +#ifndef PCI_DEVICE_ID_INTEL_82801CA_3 +#define PCI_DEVICE_ID_INTEL_82801CA_3 0x2483 +#endif + +/* 82801DB is undefined before kernel 2.4.19 */ +#ifndef PCI_DEVICE_ID_INTEL_82801DB_3 +#define PCI_DEVICE_ID_INTEL_82801DB_3 0x24c3 +#endif + +/* 82801EB is undefined before kernel 2.4.21 */ +#ifndef PCI_DEVICE_ID_INTEL_82801EB_3 +#define PCI_DEVICE_ID_INTEL_82801EB_3 0x24d3 +#endif + +/* ESB is undefined before kernel 2.4.22 */ +#ifndef PCI_DEVICE_ID_INTEL_ESB_4 +#define PCI_DEVICE_ID_INTEL_ESB_4 0x25a4 +#endif + +/* ESB2 - Enterprise Southbridge is undefined */ +#ifndef PCI_DEVICE_ID_INTEL_ESB2_17 +#define PCI_DEVICE_ID_INTEL_ESB2_17 0x269b +#endif + +/* ICH6 is undefined */ +#ifndef PCI_DEVICE_ID_INTEL_ICH6_16 +#define PCI_DEVICE_ID_INTEL_ICH6_16 0x266a +#endif + +/* ICH7 is undefined */ +#ifndef PCI_DEVICE_ID_INTEL_ICH7_17 +#define PCI_DEVICE_ID_INTEL_ICH7_17 0x27da +#endif + +#ifdef I2C_CLIENT_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 /* ich4 and later */ +#define I801_BLOCK_LAST 0x34 +#define I801_I2C_BLOCK_LAST 0x38 /* unimplemented */ +#define I801_START 0x40 +#define I801_PEC_EN 0x80 /* ich4 and later */ + +/* 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, int hwpec); + +static unsigned short i801_smba; +static struct pci_driver i801_driver; +static struct pci_dev *I801_dev; +static int isich4; /* is PEC supported? */ +static int isich5; /* is i2c block read supported? */ + +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 == PCI_DEVICE_ID_INTEL_82801EB_3 || + dev->device == PCI_DEVICE_ID_INTEL_ESB_4 || + dev->device == PCI_DEVICE_ID_INTEL_ESB2_17 || + dev->device == PCI_DEVICE_ID_INTEL_ICH6_16 || + dev->device == PCI_DEVICE_ID_INTEL_ICH7_17) + isich4 = 1; + else + isich4 = 0; + isich5 = isich4 && dev->device != PCI_DEVICE_ID_INTEL_82801DB_3; + + /* 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_driver.name)) { + 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 hwpec) +{ + 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 if (!isich5) { + dev_err(I801_dev, + "I2C_SMBUS_I2C_BLOCK_READ unsupported!\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 if (command == I2C_SMBUS_I2C_BLOCK_DATA && + read_write == I2C_SMBUS_READ) + smbcmd = I801_I2C_BLOCK_DATA; + 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) { + if (command != I2C_SMBUS_I2C_BLOCK_DATA) { + len = inb_p(SMBHSTDAT0); + if (len < 1) + len = 1; + if (len > 32) + len = 32; + data->block[0] = len; + } else { + /* if slave returns < 32 bytes transaction will fail */ + data->block[0] = 32; + } + } + + /* 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; + } + + if (hwpec) { + /* 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); + } + result = 0; +END: + if (command == I2C_SMBUS_I2C_BLOCK_DATA && + read_write == I2C_SMBUS_WRITE) { + /* 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 + hwpec = isich4 && (flags & I2C_CLIENT_PEC) + && size != I2C_SMBUS_QUICK + && size != I2C_SMBUS_I2C_BLOCK_DATA; +#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: + 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; + } + + outb_p(hwpec, SMBAUXCTL); /* enable/disable hardware PEC */ + + if(block) + ret = i801_block_transaction(data, read_write, size, hwpec); + else { + outb_p(xact | ENABLE_INT9, SMBHSTCNT); + ret = i801_transaction(); + } + + 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 void i801_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void i801_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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_HWPEC_CALC : 0) +#endif +#if 0 + | (isich5 ? I2C_FUNC_SMBUS_READ_I2C_BLOCK + : 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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_I801, + .algo = &smbus_algorithm, + .inc_use = i801_inc, + .dec_use = i801_dec, +}; + +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 = PCI_DEVICE_ID_INTEL_82801EB_3, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_ESB_4, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_ESB2_17, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_ICH6_16, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_ICH7_17, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, } +}; + +static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + + if (i801_setup(dev)) { + dev_warn(dev, + "I801 not detected, module not inserted.\n"); + return -ENODEV; + } + + snprintf(i801_adapter.name, 32, + "SMBus I801 adapter at %04x", i801_smba); + return i2c_add_adapter(&i801_adapter); +} + +static void __devexit i801_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&i801_adapter); + release_region(i801_smba, (isich4 ? 16 : 8)); +} + +static struct pci_driver i801_driver = { + .name = "i801 smbus", + .id_table = i801_ids, + .probe = i801_probe, + .remove = __devexit_p(i801_remove), +}; + +static int __init i2c_i801_init(void) +{ + printk(KERN_INFO "i2c-i801 version %s (%s)\n", LM_VERSION, LM_DATE); + return pci_module_init(&i801_driver); +} + +static void __exit i2c_i801_exit(void) +{ + pci_unregister_driver(&i801_driver); +} + +MODULE_AUTHOR ("Frodo Looijaard , " + "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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,328 @@ +/* + 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 + i845G 2562 +*/ + + +#include +#include +#include +#include +#include +#include +#include /* for HZ */ +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +MODULE_LICENSE("GPL"); + +/* Not defined by any Linux 2.4 kernel */ +#define PCI_DEVICE_ID_INTEL_82810E_IG 0x7125 +#define PCI_DEVICE_ID_INTEL_82815_CGC 0x1132 +#define PCI_DEVICE_ID_INTEL_82845G_IG 0x2562 + +/* 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 void i810_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void i810_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .name = "I810/I815 I2C Adapter", + .id = I2C_HW_B_I810, + .algo_data = &i810_i2c_bit_data, + .inc_use = i810_inc, + .dec_use = i810_dec, +}; + +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 = { + .name = "I810/I815 DDC Adapter", + .id = I2C_HW_B_I810, + .algo_data = &i810_ddc_bit_data, + .inc_use = i810_inc, + .dec_use = i810_dec, +}; + + +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 = PCI_DEVICE_ID_INTEL_82810E_IG, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_82815_CGC, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_82845G_IG, + .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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,89 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +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, +}; + +static void isa_inc_use(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void isa_dec_use(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +/* There can only be one... */ +static struct i2c_adapter isa_adapter = { + .name = "ISA main adapter", + .id = I2C_ALGO_ISA | I2C_HW_ISA, + .algo = &isa_algorithm, + .inc_use = isa_inc_use, + .dec_use = isa_dec_use, +}; + +/* 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-nforce2.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-nforce2.c Sun Feb 26 11:18:37 2006 @@ -0,0 +1,414 @@ +/* + SMBus driver for nVidia nForce2 MCP + + Copyright (c) 2003 Hans-Frieder Vogt , + Based on + SMBus 2.0 driver for AMD-8111 IO-Hub + Copyright (c) 2002 Vojtech Pavlik + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + SUPPORTED DEVICES PCI ID + nForce2 MCP 0064 + nForce2 Ultra 400 MCP 0084 + nForce3 Pro150 MCP 00D4 + nForce3 250Gb MCP 00E4 + nForce4 MCP 0052 + nForce4 MCP-04 0034 + + This driver supports the 2 SMBuses that are included in the MCP of the + nForce2/3/4 chipsets. +*/ + +/* Note: we assume there can only be one nForce2, with two SMBus interfaces */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR ("Hans-Frieder Vogt "); +MODULE_DESCRIPTION("nForce2 SMBus driver"); + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS +#define PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS 0x0064 +#endif + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE2S_SMBUS +#define PCI_DEVICE_ID_NVIDIA_NFORCE2S_SMBUS 0x0084 +#endif + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE3_SMBUS +#define PCI_DEVICE_ID_NVIDIA_NFORCE3_SMBUS 0x00D4 +#endif + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE3S_SMBUS +#define PCI_DEVICE_ID_NVIDIA_NFORCE3S_SMBUS 0x00E4 +#endif + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE4_SMBUS +#define PCI_DEVICE_ID_NVIDIA_NFORCE4_SMBUS 0x0052 +#endif + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE_MPC04_SMBUS +#define PCI_DEVICE_ID_NVIDIA_NFORCE_MPC04_SMBUS 0x0034 +#endif + + +struct nforce2_smbus { + struct pci_dev *dev; + struct i2c_adapter adapter; + int base; + int size; +}; + + +/* + * nVidia nForce2 SMBus control register definitions + */ +#define NFORCE_PCI_SMB1 0x50 +#define NFORCE_PCI_SMB2 0x54 + + +/* + * ACPI 2.0 chapter 13 SMBus 2.0 EC register model + */ +#define NVIDIA_SMB_PRTCL (smbus->base + 0x00) /* protocol, PEC */ +#define NVIDIA_SMB_STS (smbus->base + 0x01) /* status */ +#define NVIDIA_SMB_ADDR (smbus->base + 0x02) /* address */ +#define NVIDIA_SMB_CMD (smbus->base + 0x03) /* command */ +#define NVIDIA_SMB_DATA (smbus->base + 0x04) /* 32 data registers */ +#define NVIDIA_SMB_BCNT (smbus->base + 0x24) /* number of data bytes */ +#define NVIDIA_SMB_ALRM_A (smbus->base + 0x25) /* alarm address */ +#define NVIDIA_SMB_ALRM_D (smbus->base + 0x26) /* 2 bytes alarm data */ + +#define NVIDIA_SMB_STS_DONE 0x80 +#define NVIDIA_SMB_STS_ALRM 0x40 +#define NVIDIA_SMB_STS_RES 0x20 +#define NVIDIA_SMB_STS_STATUS 0x1f + +#define NVIDIA_SMB_PRTCL_WRITE 0x00 +#define NVIDIA_SMB_PRTCL_READ 0x01 +#define NVIDIA_SMB_PRTCL_QUICK 0x02 +#define NVIDIA_SMB_PRTCL_BYTE 0x04 +#define NVIDIA_SMB_PRTCL_BYTE_DATA 0x06 +#define NVIDIA_SMB_PRTCL_WORD_DATA 0x08 +#define NVIDIA_SMB_PRTCL_BLOCK_DATA 0x0a +#define NVIDIA_SMB_PRTCL_PROC_CALL 0x0c +#define NVIDIA_SMB_PRTCL_BLOCK_PROC_CALL 0x0d +#define NVIDIA_SMB_PRTCL_I2C_BLOCK_DATA 0x4a +#define NVIDIA_SMB_PRTCL_PEC 0x80 + +static struct pci_driver nforce2_driver; + +static s32 nforce2_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data); +/* +static int nforce2_block_transaction(union i2c_smbus_data *data, + char read_write, int i2c_enable); + */ +static u32 nforce2_func(struct i2c_adapter *adapter); + + +static struct i2c_algorithm smbus_algorithm = { + .name = "Non-I2C SMBus adapter", + .id = I2C_ALGO_SMBUS, + .smbus_xfer = nforce2_access, + .functionality = nforce2_func, +}; + +/* Return -1 on error. See smbus.h for more information */ +s32 nforce2_access(struct i2c_adapter * adap, u16 addr, unsigned short flags, + char read_write, u8 command, int size, + union i2c_smbus_data * data) +{ + struct nforce2_smbus *smbus = adap->algo_data; + unsigned char protocol, pec, temp; + unsigned char len = 0; /* to keep the compiler quiet */ + int i; + + protocol = (read_write == I2C_SMBUS_READ) ? NVIDIA_SMB_PRTCL_READ : NVIDIA_SMB_PRTCL_WRITE; + pec = (flags & I2C_CLIENT_PEC) ? NVIDIA_SMB_PRTCL_PEC : 0; + + switch (size) { + + case I2C_SMBUS_QUICK: + protocol |= NVIDIA_SMB_PRTCL_QUICK; + read_write = I2C_SMBUS_WRITE; + break; + + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, NVIDIA_SMB_CMD); + protocol |= NVIDIA_SMB_PRTCL_BYTE; + break; + + case I2C_SMBUS_BYTE_DATA: + outb_p(command, NVIDIA_SMB_CMD); + if (read_write == I2C_SMBUS_WRITE) + outb_p(data->byte, NVIDIA_SMB_DATA); + protocol |= NVIDIA_SMB_PRTCL_BYTE_DATA; + break; + + case I2C_SMBUS_WORD_DATA: + outb_p(command, NVIDIA_SMB_CMD); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word, NVIDIA_SMB_DATA); + outb_p(data->word >> 8, NVIDIA_SMB_DATA+1); + } + protocol |= NVIDIA_SMB_PRTCL_WORD_DATA | pec; + break; + + case I2C_SMBUS_BLOCK_DATA: + outb_p(command, NVIDIA_SMB_CMD); + if (read_write == I2C_SMBUS_WRITE) { + len = min_t(u8, data->block[0], 32); + outb_p(len, NVIDIA_SMB_BCNT); + for (i = 0; i < len; i++) + outb_p(data->block[i + 1], NVIDIA_SMB_DATA+i); + } + protocol |= NVIDIA_SMB_PRTCL_BLOCK_DATA | pec; + break; + + case I2C_SMBUS_I2C_BLOCK_DATA: + len = min_t(u8, data->block[0], 32); + outb_p(command, NVIDIA_SMB_CMD); + outb_p(len, NVIDIA_SMB_BCNT); + if (read_write == I2C_SMBUS_WRITE) + for (i = 0; i < len; i++) + outb_p(data->block[i + 1], NVIDIA_SMB_DATA+i); + protocol |= NVIDIA_SMB_PRTCL_I2C_BLOCK_DATA; + break; + + case I2C_SMBUS_PROC_CALL: + printk(KERN_WARNING "i2c-nforce2.o: I2C_SMBUS_PROC_CALL not supported!\n"); + return -1; + + case I2C_SMBUS_BLOCK_PROC_CALL: + printk(KERN_WARNING "i2c-nforce2.o: I2C_SMBUS_BLOCK_PROC_CALL not supported!\n"); + return -1; + + default: + printk(KERN_WARNING "i2c-nforce2.c: Unsupported transaction %d\n", size); + return -1; + } + + outb_p((addr & 0x7f) << 1, NVIDIA_SMB_ADDR); + outb_p(protocol, NVIDIA_SMB_PRTCL); + + temp = inb_p(NVIDIA_SMB_STS); + + if (~temp & NVIDIA_SMB_STS_DONE) { + udelay(500); + temp = inb_p(NVIDIA_SMB_STS); + } + if (~temp & NVIDIA_SMB_STS_DONE) { + i2c_delay(HZ/100); + temp = inb_p(NVIDIA_SMB_STS); + } + + if ((~temp & NVIDIA_SMB_STS_DONE) || (temp & NVIDIA_SMB_STS_STATUS)) { + printk(KERN_DEBUG "i2c-nforce2.o: SMBus Timeout! (0x%02x)\n", + temp); + return -1; + } + + if (read_write == I2C_SMBUS_WRITE) + return 0; + + switch (size) { + + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + data->byte = inb_p(NVIDIA_SMB_DATA); + break; + + case I2C_SMBUS_WORD_DATA: + /* case I2C_SMBUS_PROC_CALL: not supported */ + data->word = inb_p(NVIDIA_SMB_DATA) | (inb_p(NVIDIA_SMB_DATA+1) << 8); + break; + + case I2C_SMBUS_BLOCK_DATA: + /* case I2C_SMBUS_BLOCK_PROC_CALL: not supported */ + len = inb_p(NVIDIA_SMB_BCNT); + len = min_t(u8, len, 32); + case I2C_SMBUS_I2C_BLOCK_DATA: + for (i = 0; i < len; i++) + data->block[i+1] = inb_p(NVIDIA_SMB_DATA + i); + data->block[0] = len; + break; + } + + return 0; +} + + +u32 nforce2_func(struct i2c_adapter *adapter) +{ + /* other functionality might be possible, but is not tested */ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA /* | + I2C_FUNC_SMBUS_BLOCK_DATA */; +} + +static void nforce2_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void nforce2_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +static struct pci_device_id nforce2_ids[] = { + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2S_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE3_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE3S_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE4_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MPC04_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0 } +}; + + +static int __devinit nforce2_probe_smb (struct pci_dev *dev, int reg, struct nforce2_smbus *smbus, char *name) +{ + u16 iobase; + int error; + + if (pci_read_config_word(dev, reg, &iobase) != PCIBIOS_SUCCESSFUL) { + printk (KERN_ERR "i2c-nforce2.o: Error reading PCI config for %s\n", name); + return -1; + } + smbus->dev = dev; + smbus->base = iobase & 0xfffc; + smbus->size = 8; + + if (!request_region(smbus->base, smbus->size, nforce2_driver.name)) { + printk (KERN_ERR "i2c-nforce2.o: Error requesting region %02x .. %02X for %s\n", smbus->base, smbus->base+smbus->size-1, name); + return -1; + } + + sprintf(smbus->adapter.name, "SMBus nForce2 adapter at %04x", smbus->base); + smbus->adapter.id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_NFORCE2; + smbus->adapter.algo = &smbus_algorithm; + smbus->adapter.algo_data = smbus; + smbus->adapter.inc_use = nforce2_inc; + smbus->adapter.dec_use = nforce2_dec; + + error = i2c_add_adapter(&smbus->adapter); + if (error) { + printk(KERN_WARNING "i2c-nforce2.o: Failed to register adapter.\n"); + release_region(smbus->base, smbus->size); + return -1; + } + printk(KERN_INFO "i2c-nforce2.o: nForce2 SMBus adapter at %#x\n", smbus->base); + return 0; +} + + +static int __devinit nforce2_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct nforce2_smbus *smbuses; + int res1, res2; + + /* we support 2 SMBus adapters */ + if (!(smbuses = kmalloc(2*sizeof(struct nforce2_smbus), GFP_KERNEL))) + return -ENOMEM; + memset (smbuses, 0, 2*sizeof(struct nforce2_smbus)); + pci_set_drvdata(dev, smbuses); + + /* SMBus adapter 1 */ + res1 = nforce2_probe_smb (dev, NFORCE_PCI_SMB1, &smbuses[0], "SMB1"); + if (res1 < 0) { + printk (KERN_ERR "i2c-nforce2.o: Error probing SMB1.\n"); + smbuses[0].base = 0; /* to have a check value */ + } + res2 = nforce2_probe_smb (dev, NFORCE_PCI_SMB2, &smbuses[1], "SMB2"); + if (res2 < 0) { + printk (KERN_ERR "i2c-nforce2.o: Error probing SMB2.\n"); + smbuses[1].base = 0; /* to have a check value */ + } + if ((res1 < 0) && (res2 < 0)) { + /* we did not find even one of the SMBuses, so we give up */ + kfree(smbuses); + return -ENODEV; + } + + return 0; +} + + +static void __devexit nforce2_remove(struct pci_dev *dev) +{ + struct nforce2_smbus *smbuses = (void*) pci_get_drvdata(dev); + + if (smbuses[0].base) { + i2c_del_adapter(&smbuses[0].adapter); + release_region(smbuses[0].base, smbuses[0].size); + } + if (smbuses[1].base) { + i2c_del_adapter(&smbuses[1].adapter); + release_region(smbuses[1].base, smbuses[1].size); + } + kfree(smbuses); +} + +static struct pci_driver nforce2_driver = { + .name = "nForce2 SMBus", + .id_table = nforce2_ids, + .probe = nforce2_probe, + .remove = __devexit_p(nforce2_remove), +}; + +int __init nforce2_init(void) +{ + printk(KERN_INFO "i2c-nforce2.o version %s (%s)\n", LM_VERSION, LM_DATE); + return pci_module_init(&nforce2_driver); +} + +void __exit nforce2_exit(void) +{ + pci_unregister_driver(&nforce2_driver); +} + +module_init(nforce2_init); +module_exit(nforce2_exit); + --- linux-old/drivers/i2c/i2c-piix4.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/i2c/i2c-piix4.c Sun Feb 26 11:12:13 2006 @@ -0,0 +1,563 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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; +static struct pci_driver piix4_driver; + +#ifdef CONFIG_X86 +/* + * Get DMI information. + */ + +static int __devinit ibm_dmi_probe(void) +{ + extern int is_unsafe_smbus; + return is_unsafe_smbus; +} +#endif + +/* Detect whether a PIIX4 can be found, and initialize it, where necessary. + Note the differences between kernels with the old PCI BIOS interface and + newer kernels with the real PCI interface. In compat.h some things are + defined to make the transition easier. */ +static int __devinit piix4_setup(struct pci_dev *PIIX4_dev, + const struct pci_device_id *id) +{ + unsigned char temp; + + /* match up the function */ + if (PCI_FUNC(PIIX4_dev->devfn) != id->driver_data) + return -ENODEV; + + printk(KERN_INFO "Found %s device\n", PIIX4_dev->name); + +#ifdef CONFIG_X86 + if(ibm_dmi_probe() && PIIX4_dev->vendor == PCI_VENDOR_ID_INTEL) { + printk(KERN_ERR "i2c-piix4.o: IBM Laptop detected; this module " + "may corrupt your serial eeprom! Refusing to load " + "module!\n"); + return -EPERM; + } +#endif + + /* Determine the address of the SMBus areas */ + if (force_addr) { + piix4_smba = force_addr & 0xfff0; + force = 0; + } else { + pci_read_config_word(PIIX4_dev, SMBBA, &piix4_smba); + piix4_smba &= 0xfff0; + if(piix4_smba == 0) { + printk(KERN_ERR "i2c-piix4.o: SMB base address " + "uninitialized - upgrade BIOS or use " + "force_addr=0xaddr\n"); + return -ENODEV; + } + } + + if (!request_region(piix4_smba, SMBIOSIZE, piix4_driver.name)) { + 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; +} + +static void piix4_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void piix4_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_PIIX4, + .algo = &smbus_algorithm, + .inc_use = piix4_inc, + .dec_use = piix4_dec, +}; + +#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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,244 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +/* 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 void savage4_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void savage4_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .name = "I2C Savage4 adapter", + .id = I2C_HW_B_SAVG, + .algo_data = &sav_i2c_bit_data, + .inc_use = savage4_inc, + .dec_use = savage4_dec, +}; + +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,496 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +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 struct pci_driver sis5595_driver; +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_driver.name); + 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; +} + +static void sis5595_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void sis5595_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS5595, + .algo = &smbus_algorithm, + .inc_use = sis5595_inc, + .dec_use = sis5595_dec, +}; + + +static struct pci_device_id sis5595_ids[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_SI, + .device = PCI_DEVICE_ID_SI_503, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, } +}; + +static int __devinit sis5595_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + + if (sis5595_setup(dev)) { + printk + ("i2c-sis5595.o: SIS5595 not detected, module not inserted.\n"); + + return -ENODEV; + } + + sprintf(sis5595_adapter.name, "SMBus SIS5595 adapter at %04x", + sis5595_base + SMB_INDEX); + i2c_add_adapter(&sis5595_adapter); + + return 0; +} + +static void __devexit sis5595_remove(struct pci_dev *dev) +{ + i2c_del_adapter(&sis5595_adapter); + release_region(sis5595_base + SMB_INDEX, 2); +} + + +static struct pci_driver sis5595_driver = { + .name = "sis5595 smbus", + .id_table = sis5595_ids, + .probe = sis5595_probe, + .remove = __devexit_p(sis5595_remove), +}; + +static int __init i2c_sis5595_init(void) +{ + printk("i2c-sis5595.o version %s (%s)\n", LM_VERSION, LM_DATE); + return pci_module_init(&sis5595_driver); +} + + +static void __exit i2c_sis5595_exit(void) +{ + pci_unregister_driver(&sis5595_driver); +} + + + +MODULE_AUTHOR("Frodo Looijaard "); +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,545 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + + +#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 + +static struct pci_driver sis630_driver; + +/* 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 void sis630_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void sis630_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + + +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_driver.name)){ + 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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS630, + .algo = &smbus_algorithm, + .inc_use = sis630_inc, + .dec_use = sis630_dec, +}; + + +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,606 @@ +/* + 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 +*/ + +/* 25th March 2004 + Support for Sis655 chipsets added by Ken Healy +*/ + +/* + Note: we assume there can only be one SiS645 with one SMBus interface +*/ + +/* #define DEBUG 1 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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 + +/* SiS655 north bridge (defined in 2.4.22)*/ +#ifndef PCI_DEVICE_ID_SI_655 +#define PCI_DEVICE_ID_SI_655 0x0655 +#endif + +/* SiS746 north bridge (defined in 2.4.21) */ +#ifndef PCI_DEVICE_ID_SI_746 +#define PCI_DEVICE_ID_SI_746 0x0746 +#endif + +/* SiS85C503/5513 (LPC Bridge) */ +#ifndef PCI_DEVICE_ID_SI_LPC +#define PCI_DEVICE_ID_SI_LPC 0x0018 +#endif + +/* SiS961 south bridge */ +#ifndef PCI_DEVICE_ID_SI_961 +#define PCI_DEVICE_ID_SI_961 0x0961 +#endif + +/* SiS962 south bridge */ +#ifndef PCI_DEVICE_ID_SI_962 +#define PCI_DEVICE_ID_SI_962 0x0962 +#endif + +/* SiS963 south bridge */ +#ifndef PCI_DEVICE_ID_SI_963 +#define PCI_DEVICE_ID_SI_963 0x0963 +#endif + +/* SMBus ID */ +#ifndef PCI_DEVICE_ID_SI_SMBUS +#define PCI_DEVICE_ID_SI_SMBUS 0x16 +#endif + +/* base address register in PCI config space */ +#define SIS645_BAR 0x04 + +/* SiS645 SMBus registers */ +#define SMB_STS 0x00 +#define SMB_EN 0x01 +#define SMB_CNT 0x02 +#define SMB_HOST_CNT 0x03 +#define SMB_ADDR 0x04 +#define SMB_CMD 0x05 +#define SMB_PCOUNT 0x06 +#define SMB_COUNT 0x07 +#define SMB_BYTE 0x08 +#define SMB_DEV_ADDR 0x10 +#define SMB_DB0 0x11 +#define SMB_DB1 0x12 +#define SMB_SAA 0x13 + +/* register count for request_region */ +#define SMB_IOSIZE 0x20 + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* SiS645 SMBus constants */ +#define SIS645_QUICK 0x00 +#define SIS645_BYTE 0x01 +#define SIS645_BYTE_DATA 0x02 +#define SIS645_WORD_DATA 0x03 +#define SIS645_PROC_CALL 0x04 +#define SIS645_BLOCK_DATA 0x05 + +static struct pci_driver sis645_driver; +static struct i2c_adapter sis645_adapter; +static u16 sis645_smbus_base = 0; + +static inline u8 sis645_read(u8 reg) +{ + return inb(sis645_smbus_base + reg) ; +} + +static inline void sis645_write(u8 reg, u8 data) +{ + outb(data, sis645_smbus_base + reg) ; +} + +#ifdef CONFIG_HOTPLUG + +/* Turns on SMBus device if it is not; return 0 iff successful + */ +static int __devinit sis645_enable_smbus(struct pci_dev *dev) +{ + u8 val = 0; + + pci_read_config_byte(dev, 0x77, &val); + +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Config byte was 0x%02x.\n", val); +#endif + + pci_write_config_byte(dev, 0x77, val & ~0x10); + + pci_read_config_byte(dev, 0x77, &val); + + if (val & 0x10) { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Config byte stuck!\n"); +#endif + return -1; + } + + return 0; +} + +/* Builds the basic pci_dev for SiS645 SMBus + */ +static int __devinit sis645_build_dev(struct pci_dev **smbus_dev, + struct pci_dev *bridge_dev) +{ + struct pci_dev temp_dev; + u16 vid = 0, did = 0; + int ret; + + /* fill in the device structure for search */ + memset(&temp_dev, 0, sizeof(temp_dev)); + temp_dev.bus = bridge_dev->bus; + temp_dev.sysdata = bridge_dev->bus->sysdata; + temp_dev.hdr_type = PCI_HEADER_TYPE_NORMAL; + + /* the SMBus device is function 1 on the same unit as the ISA bridge */ + temp_dev.devfn = bridge_dev->devfn + 1; + + /* query to make sure */ + ret = pci_read_config_word(&temp_dev, PCI_VENDOR_ID, &vid); + if (ret || PCI_VENDOR_ID_SI != vid) { + printk(KERN_ERR DRV_NAME ": Couldn't find SMBus device!\n"); + return ret; + } + temp_dev.vendor = vid; + + ret = pci_read_config_word(&temp_dev, PCI_DEVICE_ID, &did); + if (ret || PCI_DEVICE_ID_SI_SMBUS != did) { + printk(KERN_ERR DRV_NAME ": Couldn't find SMBus device!\n"); + return ret; + } + temp_dev.device = did; + + /* ok, we've got it... request some memory and finish it off */ + *smbus_dev = kmalloc(sizeof(**smbus_dev), GFP_ATOMIC); + if (NULL == *smbus_dev) { + printk(KERN_ERR DRV_NAME ": Out of memory!\n"); + return -ENOMEM; + } + + **smbus_dev = temp_dev; + + ret = pci_setup_device(*smbus_dev); + if (ret) { + printk(KERN_ERR DRV_NAME ": pci_setup_device failed (0x%08x)\n",ret); + } + return ret; +} + +/* See if a SMBus can be found, and enable it if possible. + */ +static int __devinit sis645_hotplug_smbus(void) +{ + int ret; + struct pci_dev *smbus_dev, *bridge_dev ; + + if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_961, NULL))) + printk(KERN_INFO DRV_NAME ": Found SiS961 south bridge.\n"); + + else if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_962, NULL))) + printk(KERN_INFO DRV_NAME ": Found SiS962 [MuTIOL Media IO].\n"); + + else if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_963, NULL))) + printk(KERN_INFO DRV_NAME ": Found SiS963 [MuTIOL Media IO].\n"); + + else if ((bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_503, NULL)) || + (bridge_dev = pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_LPC, NULL))) { + + printk(KERN_INFO DRV_NAME ": Found SiS south bridge in compatability mode(?)\n"); + + /* look for known compatible north bridges */ + if ((NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_645, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_646, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_648, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_650, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_651, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_655, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_735, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_745, NULL)) + && (NULL == pci_find_device(PCI_VENDOR_ID_SI, + PCI_DEVICE_ID_SI_746, NULL))) { + printk(KERN_ERR DRV_NAME ": Can't find suitable host bridge!\n"); + return -ENODEV; + } + } else { + printk(KERN_ERR DRV_NAME ": Can't find suitable south bridge!\n"); + return -ENODEV; + } + + /* if we get this far, we think the smbus device is present */ + + if ((ret = sis645_enable_smbus(bridge_dev))) + return ret; + + if ((ret = sis645_build_dev(&smbus_dev, bridge_dev))) + return ret; + + if ((ret = pci_enable_device(smbus_dev))) { + printk(KERN_ERR DRV_NAME ": Can't pci_enable SMBus device!" + " (0x%08x)\n", ret); + return ret; + } + + pci_insert_device(smbus_dev, smbus_dev->bus); + + return 0; +} +#endif /* CONFIG_HOTPLUG */ + +/* Execute a SMBus transaction. + int size is from SIS645_QUICK to SIS645_BLOCK_DATA + */ +static int sis645_transaction(int size) +{ + int temp; + int result = 0; + int timeout = 0; + + /* Make sure the SMBus host is ready to start transmitting */ + if (((temp = sis645_read(SMB_CNT)) & 0x03) != 0x00) { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": SMBus busy (0x%02x). Resetting...\n", + temp); +#endif + + /* kill the transaction */ + sis645_write(SMB_HOST_CNT, 0x20); + + /* check it again */ + if (((temp = sis645_read(SMB_CNT)) & 0x03) != 0x00) { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Failed! (0x%02x)\n", temp); +#endif + return -1; + } else { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Successful!\n"); +#endif + } + } + + /* Turn off timeout interrupts, set fast host clock */ + sis645_write(SMB_CNT, 0x20); + + /* clear all (sticky) status flags */ + temp = sis645_read(SMB_STS); + sis645_write(SMB_STS, temp & 0x1e); + + /* start the transaction by setting bit 4 and size bits */ + sis645_write(SMB_HOST_CNT, 0x10 | (size & 0x07)); + + /* We will always wait for a fraction of a second! */ + do { + i2c_delay(1); + temp = sis645_read(SMB_STS); + } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { + printk(KERN_DEBUG DRV_NAME ": SMBus Timeout! (0x%02x)\n",temp); + result = -1; + } + + /* device error - probably missing ACK */ + if (temp & 0x02) { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Failed bus transaction!\n"); +#endif + result = -1; + } + + /* bus collision */ + if (temp & 0x04) { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Bus collision!\n"); +#endif + result = -1; + } + + /* Finish up by resetting the bus */ + sis645_write(SMB_STS, temp); + if ((temp = sis645_read(SMB_STS))) { +#ifdef DEBUG + printk(KERN_DEBUG DRV_NAME ": Failed reset at end of transaction!" + " (0x%02x)\n", temp); +#endif + } + + return result; +} + +/* Return -1 on error. */ +static s32 sis645_access(struct i2c_adapter * adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + + switch (size) { + case I2C_SMBUS_QUICK: + sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + size = SIS645_QUICK; + break; + + case I2C_SMBUS_BYTE: + sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + if (read_write == I2C_SMBUS_WRITE) + sis645_write(SMB_CMD, command); + size = SIS645_BYTE; + break; + + case I2C_SMBUS_BYTE_DATA: + sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis645_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) + sis645_write(SMB_BYTE, data->byte); + size = SIS645_BYTE_DATA; + break; + + case I2C_SMBUS_PROC_CALL: + case I2C_SMBUS_WORD_DATA: + sis645_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); + sis645_write(SMB_CMD, command); + if (read_write == I2C_SMBUS_WRITE) { + sis645_write(SMB_BYTE, data->word & 0xff); + sis645_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8); + } + size = (size == I2C_SMBUS_PROC_CALL ? SIS645_PROC_CALL : SIS645_WORD_DATA); + break; + + case I2C_SMBUS_BLOCK_DATA: + /* TO DO: */ + printk(KERN_INFO DRV_NAME ": SMBus block not implemented!\n"); + return -1; + break; + + default: + printk(KERN_INFO DRV_NAME ": Unsupported I2C size\n"); + return -1; + break; + } + + if (sis645_transaction(size)) + return -1; + + if ((size != SIS645_PROC_CALL) && + ((read_write == I2C_SMBUS_WRITE) || (size == SIS645_QUICK))) + return 0; + + switch (size) { + case SIS645_BYTE: + case SIS645_BYTE_DATA: + data->byte = sis645_read(SMB_BYTE); + break; + + case SIS645_WORD_DATA: + case SIS645_PROC_CALL: + data->word = sis645_read(SMB_BYTE) + + (sis645_read(SMB_BYTE + 1) << 8); + break; + } + return 0; +} + +static void sis645_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void sis645_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS645, + .algo = &smbus_algorithm, + .inc_use = sis645_inc, + .dec_use = sis645_dec, +}; + +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_driver.name)) { + 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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,171 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +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 void i2c_tsunami_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void i2c_tsunami_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .name = "I2C Tsunami/Typhoon adapter", + .id = I2C_HW_B_TSUNA, + .algo_data = &tsunami_i2c_bit_data, + .inc_use = i2c_tsunami_inc, + .dec_use = i2c_tsunami_dec, +}; + + +#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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,200 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +/* 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 void bit_via_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void bit_via_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .name = "VIA i2c", + .id = I2C_HW_B_VIA, + .algo_data = &bit_data, + .inc_use = bit_via_inc, + .dec_use = bit_via_dec, +}; + + +static struct pci_device_id vt586b_ids[] __initdata = { + { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +static int __init 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 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); + 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) +{ + i2c_bit_del_bus(&vt586b_adapter); + release_region(I2C_DIR, IOSPACE); +} + + +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,525 @@ +/* + 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 + Copyright (C) 2005 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. +*/ + +/* + Supports the following VIA south bridges: + + Chip name PCI ID REV I2C block + VT82C596A 0x3050 no + VT82C596B 0x3051 no + VT82C686A 0x3057 0x30 no + VT82C686B 0x3057 0x40 yes + VT8231 0x8235 no? + VT8233 0x3074 yes + VT8233A 0x3147 yes? + VT8235 0x3177 yes + VT8237R 0x3227 yes + + Note: we assume there can only be one device, with one SMBus interface. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +#define SMBBA1 0x90 +#define SMBBA2 0x80 +#define SMBBA3 0xD0 + +/* SMBus address offsets */ +static unsigned short vt596_smba; +#define SMBHSTSTS (vt596_smba + 0) +#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) + +/* PCI Address Constants */ + +/* SMBus data in configuration space can be found in two places, + We try to select the better one */ + +static unsigned short SMBHSTCFG = 0xD2; + +/* Other settings */ +#define MAX_TIMEOUT 500 + +/* 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 +#define VT596_I2C_BLOCK_DATA 0x34 + + +/* 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; + +#define FEATURE_I2CBLOCK (1<<0) +static unsigned int vt596_features; + +#ifdef DEBUG +static void vt596_dump_regs(const char *msg, u8 size) +{ + dev_dbg(&vt596_adapter, "%s: STS=%02x CNT=%02x CMD=%02x ADD=%02x " + "DAT=%02x,%02x\n", msg, inb_p(SMBHSTSTS), inb_p(SMBHSTCNT), + inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), + inb_p(SMBHSTDAT1)); + + if (size == VT596_BLOCK_DATA + || size == VT596_I2C_BLOCK_DATA) { + int i; + + dev_dbg(&vt596_adapter, "BLK="); + for (i = 0; i < I2C_SMBUS_BLOCK_MAX / 2; i++) + printk("%02x,", inb_p(SMBBLKDAT)); + printk("\n"); + dev_dbg(&vt596_adapter, " "); + for (; i < I2C_SMBUS_BLOCK_MAX - 1; i++) + printk("%02x,", inb_p(SMBBLKDAT)); + printk("%02x\n", inb_p(SMBBLKDAT)); + } +} +#else +static inline void vt596_dump_regs(const char *msg, u8 size) { } +#endif + +/* Return -1 on error, 0 on success */ +static int vt596_transaction(u8 size) +{ + int temp; + int result = 0; + int timeout = 0; + + vt596_dump_regs("Transaction (pre)", size); + + /* Make sure the SMBus host is ready to start transmitting */ + if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { + dev_dbg(&vt596_adapter, "SMBus busy (0x%02x). " + "Resetting...\n", temp); + + outb_p(temp, SMBHSTSTS); + if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { + dev_err(&vt596_adapter, "SMBus reset failed! " + "(0x%02x)\n", temp); + return -1; + } + } + + /* Start the transaction by setting bit 6 */ + outb_p(0x40 | size, 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) { + result = -1; + dev_err(&vt596_adapter, "SMBus Timeout!\n"); + } + + if (temp & 0x10) { + result = -1; + dev_err(&vt596_adapter, "Transaction failed (0x%02x)\n", size); + } + + if (temp & 0x08) { + result = -1; + dev_err(&vt596_adapter, "SMBus collision!\n"); + } + + if (temp & 0x04) { + int read = inb_p(SMBHSTADD) & 0x01; + result = -1; + /* The quick and receive byte commands are used to probe + for chips, so errors are expected, and we don't want + to frighten the user. */ + if (!((size == VT596_QUICK && !read) || + (size == VT596_BYTE && read))) + dev_err(&vt596_adapter, "Transaction error!\n"); + } + + /* Resetting status register */ + if (temp & 0x1F) + outb_p(temp, SMBHSTSTS); + + vt596_dump_regs("Transaction (post)", size); + + return result; +} + +/* Return -1 on error, 0 on success */ +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; + + switch (size) { + case I2C_SMBUS_QUICK: + size = VT596_QUICK; + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) + outb_p(command, SMBHSTCMD); + size = VT596_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + 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(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_I2C_BLOCK_DATA: + if (!(vt596_features & FEATURE_I2CBLOCK)) + goto exit_unsupported; + if (read_write == I2C_SMBUS_READ) + outb_p(I2C_SMBUS_BLOCK_MAX, SMBHSTDAT0); + /* Fall through */ + case I2C_SMBUS_BLOCK_DATA: + outb_p(command, SMBHSTCMD); + if (read_write == I2C_SMBUS_WRITE) { + u8 len = data->block[0]; + if (len > I2C_SMBUS_BLOCK_MAX) + len = I2C_SMBUS_BLOCK_MAX; + outb_p(len, SMBHSTDAT0); + inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + for (i = 1; i <= len; i++) + outb_p(data->block[i], SMBBLKDAT); + } + size = (size == I2C_SMBUS_I2C_BLOCK_DATA) ? + VT596_I2C_BLOCK_DATA : VT596_BLOCK_DATA; + break; + default: + goto exit_unsupported; + } + + outb_p(((addr & 0x7f) << 1) | read_write, SMBHSTADD); + + if (vt596_transaction(size)) /* Error in transaction */ + return -1; + + if ((read_write == I2C_SMBUS_WRITE) || (size == VT596_QUICK)) + return 0; + + switch (size) { + case VT596_BYTE: + 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_I2C_BLOCK_DATA: + case VT596_BLOCK_DATA: + data->block[0] = inb_p(SMBHSTDAT0); + if (data->block[0] > I2C_SMBUS_BLOCK_MAX) + data->block[0] = I2C_SMBUS_BLOCK_MAX; + inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + for (i = 1; i <= data->block[0]; i++) + data->block[i] = inb_p(SMBBLKDAT); + break; + } + return 0; + +exit_unsupported: + dev_warn(&vt596_adapter, "Unsupported command invoked! (0x%02x)\n", + size); + return -1; +} + +static void vt596_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +static void vt596_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +static u32 vt596_func(struct i2c_adapter *adapter) +{ + u32 func = I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; + + if (vt596_features & FEATURE_I2CBLOCK) + func |= I2C_FUNC_SMBUS_I2C_BLOCK; + return func; +} + +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 = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_VIA2, + .algo = &smbus_algorithm, + .inc_use = vt596_inc, + .dec_use = vt596_dec, +}; + +static int __init 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 & 0x0001)) { + /* 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 & 0x0001)) { + SMBHSTCFG = 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 & 0x01)) { + 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 | 0x01); + 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; + } + } + + dev_dbg(pdev, "VT596_smba = 0x%X\n", vt596_smba); + + switch (id->device) { + case PCI_DEVICE_ID_VIA_8237: + case PCI_DEVICE_ID_VIA_8235: + case PCI_DEVICE_ID_VIA_8233A: + case PCI_DEVICE_ID_VIA_8233_0: + vt596_features |= FEATURE_I2CBLOCK; + break; + case PCI_DEVICE_ID_VIA_82C686_4: + /* The VT82C686B (rev 0x40) does support I2C block + transactions, but the VT82C686A (rev 0x30) doesn't */ + if (!pci_read_config_byte(pdev, PCI_REVISION_ID, &temp) + && temp >= 0x40) + vt596_features |= FEATURE_I2CBLOCK; + break; + } + + 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; +} + +/* 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[] __initdata = { + { + .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, } +}; + +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); + 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) +{ + i2c_del_adapter(&vt596_adapter); + release_region(vt596_smba, 8); +} + +MODULE_AUTHOR("Kyosti Malkki , " + "Mark D. Studebaker and " + "Jean Delvare "); +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 Sun Feb 26 11:18:37 2006 @@ -0,0 +1,297 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include + +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 void voodoo3_inc(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +void voodoo3_dec(struct i2c_adapter *adapter) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +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 = { + .name = "I2C Voodoo3/Banshee adapter", + .id = I2C_HW_B_VOO, + .algo_data = &voo_i2c_bit_data, + .inc_use = voodoo3_inc, + .dec_use = voodoo3_dec, +}; + +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 = { + .name = "DDC Voodoo3/Banshee adapter", + .id = I2C_HW_B_VOO, + .algo_data = &voo_ddc_bit_data, + .inc_use = voodoo3_inc, + .dec_use = voodoo3_dec, +}; + + +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; + + 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,588 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x18, 0x1a, 0x29, 0x2b, + 0x4c, 0x4e, SENSORS_I2C_END +}; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_8(adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066); + +/* adm1021 constants specified below */ + +/* The adm1021 registers */ +/* Read-only */ +#define ADM1021_REG_TEMP 0x00 +#define ADM1021_REG_REMOTE_TEMP 0x01 +#define ADM1021_REG_STATUS 0x02 +#define ADM1021_REG_MAN_ID 0x0FE /* 0x41 = Analog Devices, 0x49 = TI, + 0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi*/ +#define ADM1021_REG_DEV_ID 0x0FF /* ADM1021 = 0x0X, ADM1021A/ADM1023 = 0x3X */ +#define ADM1021_REG_DIE_CODE 0x0FF /* MAX1617A */ +/* These use different addresses for reading/writing */ +#define ADM1021_REG_CONFIG_R 0x03 +#define ADM1021_REG_CONFIG_W 0x09 +#define ADM1021_REG_CONV_RATE_R 0x04 +#define ADM1021_REG_CONV_RATE_W 0x0A +/* These are for the ADM1023's additional precision on the remote temp sensor */ +#define ADM1021_REG_REM_TEMP_PREC 0x010 +#define ADM1021_REG_REM_OFFSET 0x011 +#define ADM1021_REG_REM_OFFSET_PREC 0x012 +#define ADM1021_REG_REM_TOS_PREC 0x013 +#define ADM1021_REG_REM_THYST_PREC 0x014 +/* limits */ +#define ADM1021_REG_TOS_R 0x05 +#define ADM1021_REG_TOS_W 0x0B +#define ADM1021_REG_REMOTE_TOS_R 0x07 +#define ADM1021_REG_REMOTE_TOS_W 0x0D +#define ADM1021_REG_THYST_R 0x06 +#define ADM1021_REG_THYST_W 0x0C +#define ADM1021_REG_REMOTE_THYST_R 0x08 +#define ADM1021_REG_REMOTE_THYST_W 0x0E +/* write-only */ +#define ADM1021_REG_ONESHOT 0x0F + +#define ADM1021_ALARM_TEMP (ADM1021_ALARM_TEMP_HIGH | ADM1021_ALARM_TEMP_LOW) +#define ADM1021_ALARM_RTEMP (ADM1021_ALARM_RTEMP_HIGH | ADM1021_ALARM_RTEMP_LOW\ + | ADM1021_ALARM_RTEMP_NA) +#define ADM1021_ALARM_ALL (ADM1021_ALARM_TEMP | ADM1021_ALARM_RTEMP) + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +/* Conversions note: 1021 uses normal integer signed-byte format*/ +#define TEMP_FROM_REG(val) (val > 127 ? val-256 : val) +#define TEMP_TO_REG(val) (SENSORS_LIMIT((val < 0 ? val+256 : val),0,255)) + +/* Each client has this additional data */ +struct adm1021_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 temp, temp_os, temp_hyst; /* Register values */ + u8 remote_temp, remote_temp_os, remote_temp_hyst, alarms, die_code; + u8 fail; + /* Special values for ADM1023 only */ + u8 remote_temp_prec, remote_temp_os_prec, remote_temp_hyst_prec, + remote_temp_offset, remote_temp_offset_prec; +}; + +static int adm1021_attach_adapter(struct i2c_adapter *adapter); +static int adm1021_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void adm1021_init_client(struct i2c_client *client); +static int adm1021_detach_client(struct i2c_client *client); +static int adm1021_read_value(struct i2c_client *client, u8 reg); +static int adm1021_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask); +static int adm1021_write_value(struct i2c_client *client, u8 reg, + u16 value); +static void adm1021_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1021_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, + long *results); +static void adm1021_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1021_die_code(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1021_update_client(struct i2c_client *client); + +/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */ +static int read_only = 0; + + +/* This is the driver that will be inserted */ +static struct i2c_driver adm1021_driver = { + .name = "ADM1021, MAX1617 sensor driver", + .id = I2C_DRIVERID_ADM1021, + .flags = I2C_DF_NOTIFY, + .attach_adapter = adm1021_attach_adapter, + .detach_client = adm1021_detach_client, +}; + +/* -- SENSORS SYSCTL START -- */ + +#define ADM1021_SYSCTL_TEMP 1200 +#define ADM1021_SYSCTL_REMOTE_TEMP 1201 +#define ADM1021_SYSCTL_DIE_CODE 1202 +#define ADM1021_SYSCTL_ALARMS 1203 + +#define ADM1021_ALARM_TEMP_HIGH 0x40 +#define ADM1021_ALARM_TEMP_LOW 0x20 +#define ADM1021_ALARM_RTEMP_HIGH 0x10 +#define ADM1021_ALARM_RTEMP_LOW 0x08 +#define ADM1021_ALARM_RTEMP_NA 0x04 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected adm1021. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table adm1021_dir_table_template[] = { + {ADM1021_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_temp}, + {ADM1021_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_remote_temp}, + {ADM1021_SYSCTL_DIE_CODE, "die_code", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_die_code}, + {ADM1021_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_alarms}, + {0} +}; + +static ctl_table adm1021_max_dir_table_template[] = { + {ADM1021_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_temp}, + {ADM1021_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_remote_temp}, + {ADM1021_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1021_alarms}, + {0} +}; + +static int adm1021_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm1021_detect); +} + +static int adm1021_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct adm1021_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("adm1021.o: adm1021_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto error0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access adm1021_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct adm1021_data), GFP_KERNEL))) { + err = -ENOMEM; + goto error0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &adm1021_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if ((adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0x03) != 0x00 + || (adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x3F) != 0x00 + || (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) & 0xF8) != 0x00) + goto error1; + } + + /* Determine the chip type. */ + + if (kind <= 0) { + i = adm1021_read_value(new_client, ADM1021_REG_MAN_ID); + if (i == 0x41) + if ((adm1021_read_value (new_client, ADM1021_REG_DEV_ID) & 0xF0) == 0x30) + kind = adm1023; + else + kind = adm1021; + else if (i == 0x49) + kind = thmc10; + else if (i == 0x23) + kind = gl523sm; + else if ((i == 0x4d) && + (adm1021_read_value + (new_client, ADM1021_REG_DEV_ID) == 0x01)) + kind = max1617a; + else if (i == 0x54) + kind = mc1066; + /* LM84 Mfr ID in a different place, and it has more unused bits */ + else if (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) == 0x00 + && (kind == 0 /* skip extra detection */ + || ((adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x7F) == 0x00 + && (adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0xAB) == 0x00))) + kind = lm84; + else + kind = max1617; + } + + if (kind == max1617) { + type_name = "max1617"; + client_name = "MAX1617 chip"; + } else if (kind == max1617a) { + type_name = "max1617a"; + client_name = "MAX1617A chip"; + } else if (kind == adm1021) { + type_name = "adm1021"; + client_name = "ADM1021 chip"; + } else if (kind == adm1023) { + type_name = "adm1023"; + client_name = "ADM1023 chip"; + } else if (kind == thmc10) { + type_name = "thmc10"; + client_name = "THMC10 chip"; + } else if (kind == lm84) { + type_name = "lm84"; + client_name = "LM84 chip"; + } else if (kind == gl523sm) { + type_name = "gl523sm"; + client_name = "GL523SM chip"; + } else if (kind == mc1066) { + type_name = "mc1066"; + client_name = "MC1066 chip"; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = i; + goto error4; + } + data->sysctl_id = i; + + /* Initialize the ADM1021 chip */ + if (kind != lm84) + adm1021_init_client(new_client); + return 0; + + error4: + i2c_detach_client(new_client); + error3: + error1: + kfree(data); + error0: + return err; +} + +static void adm1021_init_client(struct i2c_client *client) +{ + /* Enable ADC and disable suspend mode */ + adm1021_write_value(client, ADM1021_REG_CONFIG_W, + adm1021_read_value(client, ADM1021_REG_CONFIG_R) & 0xBF); + /* Set Conversion rate to 1/sec (this can be tinkered with) */ + adm1021_write_value(client, ADM1021_REG_CONV_RATE_W, 0x04); +} + +static int adm1021_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct adm1021_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("adm1021.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* All registers are byte-sized */ +static int adm1021_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* only update value if read succeeded; set fail bit if failed */ +static int adm1021_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask) +{ + int i; + struct adm1021_data *data = client->data; + + i = i2c_smbus_read_byte_data(client, reg); + if (i < 0) { + data->fail |= mask; + return i; + } + *val = i; + return 0; +} + +static int adm1021_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if (read_only > 0) + return 0; + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void adm1021_update_client(struct i2c_client *client) +{ + struct adm1021_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting adm1021 update\n"); +#endif + + data->fail = 0; + adm1021_rd_good(&(data->temp), client, ADM1021_REG_TEMP, + ADM1021_ALARM_TEMP); + adm1021_rd_good(&(data->temp_os), client, ADM1021_REG_TOS_R, + ADM1021_ALARM_TEMP); + adm1021_rd_good(&(data->temp_hyst), client, + ADM1021_REG_THYST_R, ADM1021_ALARM_TEMP); + adm1021_rd_good(&(data->remote_temp), client, + ADM1021_REG_REMOTE_TEMP, ADM1021_ALARM_RTEMP); + adm1021_rd_good(&(data->remote_temp_os), client, + ADM1021_REG_REMOTE_TOS_R, ADM1021_ALARM_RTEMP); + adm1021_rd_good(&(data->remote_temp_hyst), client, + ADM1021_REG_REMOTE_THYST_R, + ADM1021_ALARM_RTEMP); + data->alarms = ADM1021_ALARM_ALL; + if (!adm1021_rd_good(&(data->alarms), client, + ADM1021_REG_STATUS, 0)) + data->alarms &= ADM1021_ALARM_ALL; + if (data->type == adm1021) + adm1021_rd_good(&(data->die_code), client, + ADM1021_REG_DIE_CODE, 0); + if (data->type == adm1023) { + adm1021_rd_good(&(data->remote_temp_prec), client, + ADM1021_REG_REM_TEMP_PREC, + ADM1021_ALARM_TEMP); + adm1021_rd_good(&(data->remote_temp_os_prec), client, + ADM1021_REG_REM_TOS_PREC, + ADM1021_ALARM_RTEMP); + adm1021_rd_good(&(data->remote_temp_hyst_prec), client, + ADM1021_REG_REM_THYST_PREC, + ADM1021_ALARM_RTEMP); + adm1021_rd_good(&(data->remote_temp_offset), client, + ADM1021_REG_REM_OFFSET, + ADM1021_ALARM_RTEMP); + adm1021_rd_good(&(data->remote_temp_offset_prec), + client, ADM1021_REG_REM_OFFSET_PREC, + ADM1021_ALARM_RTEMP); + } + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void adm1021_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1021_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1021_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_os); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_os = TEMP_TO_REG(results[0]); + adm1021_write_value(client, ADM1021_REG_TOS_W, + data->temp_os); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + adm1021_write_value(client, ADM1021_REG_THYST_W, + data->temp_hyst); + } + } +} + +void adm1021_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1021_data *data = client->data; + int prec = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + if (data->type == adm1023) { *nrels_mag = 3; } + else { *nrels_mag = 0; } + else if (operation == SENSORS_PROC_REAL_READ) { + adm1021_update_client(client); + results[0] = TEMP_FROM_REG(data->remote_temp_os); + results[1] = TEMP_FROM_REG(data->remote_temp_hyst); + results[2] = TEMP_FROM_REG(data->remote_temp); + if (data->type == adm1023) { + results[0]=results[0]*1000 + + ((data->remote_temp_os_prec >> 5) * 125); + results[1]=results[1]*1000 + + ((data->remote_temp_hyst_prec >> 5) * 125); + results[2]=(TEMP_FROM_REG(data->remote_temp_offset)*1000) + + ((data->remote_temp_offset_prec >> 5) * 125); + results[3]=TEMP_FROM_REG(data->remote_temp)*1000 + + ((data->remote_temp_prec >> 5) * 125); + *nrels_mag = 4; + } else { + *nrels_mag = 3; + } + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + if (data->type == adm1023) { + prec=((results[0]-((results[0]/1000)*1000))/125)<<5; + adm1021_write_value(client, + ADM1021_REG_REM_TOS_PREC, + prec); + results[0]=results[0]/1000; + data->remote_temp_os_prec=prec; + } + data->remote_temp_os = TEMP_TO_REG(results[0]); + adm1021_write_value(client, + ADM1021_REG_REMOTE_TOS_W, + data->remote_temp_os); + } + if (*nrels_mag >= 2) { + if (data->type == adm1023) { + prec=((results[1]-((results[1]/1000)*1000))/125)<<5; + adm1021_write_value(client, + ADM1021_REG_REM_THYST_PREC, + prec); + results[1]=results[1]/1000; + data->remote_temp_hyst_prec=prec; + } + data->remote_temp_hyst = TEMP_TO_REG(results[1]); + adm1021_write_value(client, + ADM1021_REG_REMOTE_THYST_W, + data->remote_temp_hyst); + } + if (*nrels_mag >= 3) { + if (data->type == adm1023) { + prec=((results[2]-((results[2]/1000)*1000))/125)<<5; + adm1021_write_value(client, + ADM1021_REG_REM_OFFSET_PREC, + prec); + results[2]=results[2]/1000; + data->remote_temp_offset_prec=prec; + data->remote_temp_offset=results[2]; + adm1021_write_value(client, + ADM1021_REG_REM_OFFSET, + data->remote_temp_offset); + } + } + } +} + +void adm1021_die_code(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1021_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1021_update_client(client); + results[0] = data->die_code; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + /* Can't write to it */ + } +} + +void adm1021_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1021_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1021_update_client(client); + results[0] = data->alarms | data->fail; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + /* Can't write to it */ + } +} + +static int __init sm_adm1021_init(void) +{ + printk(KERN_INFO "adm1021.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&adm1021_driver); +} + +static void __exit sm_adm1021_exit(void) +{ + i2c_del_driver(&adm1021_driver); +} + +MODULE_AUTHOR + ("Frodo Looijaard 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,778 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(adm1024); + +/* Many ADM1024 constants specified below */ + +#define ADM1024_REG_IN_MAX(nr) (0x2b + (nr) * 2) +#define ADM1024_REG_IN_MIN(nr) (0x2c + (nr) * 2) +#define ADM1024_REG_IN(nr) (0x20 + (nr)) + +/* The ADM1024 registers */ +#define ADM1024_REG_INT_TEMP_TRIP_SET 0x13 +#define ADM1024_REG_EXT_TEMP_TRIP_SET 0x14 +#define ADM1024_REG_TEST 0x15 +#define ADM1024_REG_CHANNEL_MODE 0x16 +#define ADM1024_REG_INT_TEMP_TRIP 0x17 /* read only */ +#define ADM1024_REG_EXT_TEMP_TRIP 0x18 /* read only */ +#define ADM1024_REG_ANALOG_OUT 0x19 +#define ADM1024_REG_AIN1_LOW_LIMIT 0x1A +#define ADM1024_REG_AIN2_LOW_LIMIT 0x1B +/* These are all read-only */ +#define ADM1024_REG_2_5V 0x20 /* 2.5V Measured Value/EXT Temp 2 */ +#define ADM1024_REG_VCCP1 0x21 +#define ADM1024_REG_3_3V 0x22 /* VCC Measured Value */ +#define ADM1024_REG_5V 0x23 +#define ADM1024_REG_12V 0x24 +#define ADM1024_REG_VCCP2 0x25 +#define ADM1024_REG_EXT_TEMP1 0x26 +#define ADM1024_REG_TEMP 0x27 +#define ADM1024_REG_FAN1 0x28 /* FAN1/AIN1 Value */ +#define ADM1024_REG_FAN2 0x29 /* FAN2/AIN2 Value */ +#define ADM1024_REG_COMPANY_ID 0x3E /* 0x41 for ADM1024 */ +#define ADM1024_REG_DIE_REV 0x3F +/* These are read/write */ +#define ADM1024_REG_2_5V_HIGH 0x2B /* 2.5V/Ext Temp2 High Limit */ +#define ADM1024_REG_2_5V_LOW 0x2C /* 2.5V/Ext Temp2 Low Limit */ +#define ADM1024_REG_VCCP1_HIGH 0x2D +#define ADM1024_REG_VCCP1_LOW 0x2E +#define ADM1024_REG_3_3V_HIGH 0x2F /* VCC High Limit */ +#define ADM1024_REG_3_3V_LOW 0x30 /* VCC Low Limit */ +#define ADM1024_REG_5V_HIGH 0x31 +#define ADM1024_REG_5V_LOW 0x32 +#define ADM1024_REG_12V_HIGH 0x33 +#define ADM1024_REG_12V_LOW 0x34 +#define ADM1024_REG_VCCP2_HIGH 0x35 +#define ADM1024_REG_VCCP2_LOW 0x36 +#define ADM1024_REG_EXT_TEMP1_HIGH 0x37 +#define ADM1024_REG_EXT_TEMP1_LOW 0x38 +#define ADM1024_REG_TOS 0x39 +#define ADM1024_REG_THYST 0x3A +#define ADM1024_REG_FAN1_MIN 0x3B +#define ADM1024_REG_FAN2_MIN 0x3C + +#define ADM1024_REG_CONFIG 0x40 +#define ADM1024_REG_INT1_STAT 0x41 +#define ADM1024_REG_INT2_STAT 0x42 +#define ADM1024_REG_INT1_MASK 0x43 +#define ADM1024_REG_INT2_MASK 0x44 + +#define ADM1024_REG_CHASSIS_CLEAR 0x46 +#define ADM1024_REG_VID_FAN_DIV 0x47 +#define ADM1024_REG_I2C_ADDR 0x48 +#define ADM1024_REG_VID4 0x49 +#define ADM1024_REG_CONFIG2 0x4A +#define ADM1024_REG_TEMP_CONFIG 0x4B +#define ADM1024_REG_EXTMODE1 0x4C /* Interupt Status Register Mirror No. 1 */ +#define ADM1024_REG_EXTMODE2 0x4D /* Interupt Status Register Mirror No. 2 */ + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define IN_TO_REG(val,nr) (SENSORS_LIMIT(((val) & 0xff),0,255)) +#define IN_FROM_REG(val,nr) (val) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:\ + (val)==255?0:1350000/((div)*(val))) + +#define TEMP_FROM_REG(temp) \ + ((temp)<256?((((temp)&0x1fe) >> 1) * 10) + ((temp) & 1) * 5: \ + ((((temp)&0x1fe) >> 1) -255) * 10 - ((temp) & 1) * 5) \ + +#define EXT_TEMP_FROM_REG(temp) (((temp)>0x80?(temp)-0x100:(temp))*10) + + +#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) + +#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ + ((val)+5)/10), \ + 0,255) + +#define ALARMS_FROM_REG(val) (val) + +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1))) + +#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ + 205-(val)*5) + +/* For each registered ADM1024, we need to keep some data in memory. That + data is pointed to by adm1024_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new adm1024 client is + allocated. */ +struct adm1024_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[6]; /* Register value */ + u8 in_max[6]; /* Register value */ + u8 in_min[6]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + int temp; /* Temp, shifted right */ + u8 temp_os_max; /* Register value */ + u8 temp_os_hyst; /* Register value */ + int temp1; /* Ext Temp 1 */ + u8 temp1_os_max; + u8 temp1_os_hyst; + int temp2; /* Ext Temp 2 */ + u8 temp2_os_max; + u8 temp2_os_hyst; + u16 alarms; /* Register encoding, combined */ + u8 analog_out; /* Register value */ + u8 vid; /* Register value combined */ +}; + + + +static int adm1024_attach_adapter(struct i2c_adapter *adapter); +static int adm1024_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int adm1024_detach_client(struct i2c_client *client); + +static int adm1024_read_value(struct i2c_client *client, u8 register); +static int adm1024_write_value(struct i2c_client *client, u8 register, + u8 value); +static void adm1024_update_client(struct i2c_client *client); +static void adm1024_init_client(struct i2c_client *client); + + +static void adm1024_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_temp1(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_temp2(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1024_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, + long *results); +static void adm1024_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver adm1024_driver = { + .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 Celsius * 100 */ +#define ADM1024_SYSCTL_TEMP1 1290 /* Degrees Celsius */ +#define ADM1024_SYSCTL_TEMP2 1295 /* Degrees Celsius */ +#define ADM1024_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define ADM1024_SYSCTL_ALARMS 2001 /* bitvector */ +#define ADM1024_SYSCTL_ANALOG_OUT 2002 +#define ADM1024_SYSCTL_VID 2003 + +#define ADM1024_ALARM_IN0 0x0001 +#define ADM1024_ALARM_IN1 0x0002 +#define ADM1024_ALARM_IN2 0x0004 +#define ADM1024_ALARM_IN3 0x0008 +#define ADM1024_ALARM_IN4 0x0100 +#define ADM1024_ALARM_IN5 0x0200 +#define ADM1024_ALARM_FAN1 0x0040 +#define ADM1024_ALARM_FAN2 0x0080 +#define ADM1024_ALARM_TEMP 0x0010 +#define ADM1024_ALARM_TEMP1 0x0020 +#define ADM1024_ALARM_TEMP2 0x0001 +#define ADM1024_ALARM_CHAS 0x1000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected ADM1024. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table adm1024_dir_table_template[] = { + {ADM1024_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_in}, + {ADM1024_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_in}, + {ADM1024_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_in}, + {ADM1024_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_in}, + {ADM1024_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_in}, + {ADM1024_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_in}, + {ADM1024_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_fan}, + {ADM1024_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_fan}, + {ADM1024_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_temp}, + {ADM1024_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_temp1}, + {ADM1024_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_temp2}, + {ADM1024_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_fan_div}, + {ADM1024_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_alarms}, + {ADM1024_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_analog_out}, + {ADM1024_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1024_vid}, + {0} +}; + +static int adm1024_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm1024_detect); +} + +static int adm1024_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct adm1024_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("adm1024.o: adm1024_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access adm1024_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct adm1024_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &adm1024_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if((adm1024_read_value(new_client, ADM1024_REG_CONFIG) & 0x80) != 0x00) + goto ERROR1; + } + + /* Determine the chip type. */ + if (kind <= 0) { + i = adm1024_read_value(new_client, ADM1024_REG_COMPANY_ID); + if (i == 0x41) + kind = adm1024; + else { + if (kind == 0) + printk + ("adm1024.o: Ignoring 'force' parameter for unknown chip at " + "adapter %d, address 0x%02x\n", + i2c_adapter_id(adapter), address); + goto ERROR1; + } + } + + if (kind == adm1024) { + type_name = "adm1024"; + client_name = "ADM1024 chip"; + } else { +#ifdef DEBUG + printk("adm1024.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the ADM1024 chip */ + adm1024_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int adm1024_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct adm1024_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("adm1024.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +static int adm1024_read_value(struct i2c_client *client, u8 reg) +{ + return 0xFF & i2c_smbus_read_byte_data(client, reg); +} + +static int adm1024_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void adm1024_init_client(struct i2c_client *client) +{ + /* Enable temperature channel 2 */ + adm1024_write_value(client, ADM1024_REG_CHANNEL_MODE, adm1024_read_value(client, ADM1024_REG_CHANNEL_MODE) | 0x04); + + /* Start monitoring */ + adm1024_write_value(client, ADM1024_REG_CONFIG, 0x07); +} + +static void adm1024_update_client(struct i2c_client *client) +{ + struct adm1024_data *data = client->data; + u8 i; + + down(&data->update_lock); + + if ( + (jiffies - data->last_updated > + (data->type == adm1024 ? HZ / 2 : HZ * 2)) + || (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting adm1024 update\n"); +#endif + for (i = 0; i <= 5; i++) { + data->in[i] = + adm1024_read_value(client, ADM1024_REG_IN(i)); + data->in_min[i] = + adm1024_read_value(client, + ADM1024_REG_IN_MIN(i)); + data->in_max[i] = + adm1024_read_value(client, + ADM1024_REG_IN_MAX(i)); + } + data->fan[0] = + adm1024_read_value(client, ADM1024_REG_FAN1); + data->fan_min[0] = + adm1024_read_value(client, ADM1024_REG_FAN1_MIN); + data->fan[1] = + adm1024_read_value(client, ADM1024_REG_FAN2); + data->fan_min[1] = + adm1024_read_value(client, ADM1024_REG_FAN2_MIN); + data->temp = + (adm1024_read_value(client, ADM1024_REG_TEMP) << 1) + + ((adm1024_read_value + (client, ADM1024_REG_TEMP_CONFIG) & 0x80) >> 7); + data->temp_os_max = + adm1024_read_value(client, ADM1024_REG_TOS); + data->temp_os_hyst = + adm1024_read_value(client, ADM1024_REG_THYST); + data->temp1 = + adm1024_read_value(client, ADM1024_REG_EXT_TEMP1); + data->temp1_os_max = + adm1024_read_value(client, ADM1024_REG_EXT_TEMP1_HIGH); + data->temp1_os_hyst = + adm1024_read_value(client, ADM1024_REG_EXT_TEMP1_LOW); + data->temp2 = + adm1024_read_value(client, ADM1024_REG_2_5V); + data->temp2_os_max = + adm1024_read_value(client, ADM1024_REG_2_5V_HIGH); + data->temp2_os_hyst = + adm1024_read_value(client, ADM1024_REG_2_5V_LOW); + + i = adm1024_read_value(client, ADM1024_REG_VID_FAN_DIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + data->vid = i & 0x0f; + data->vid |= + (adm1024_read_value(client, ADM1024_REG_VID4) & 0x01) + << 4; + + data->alarms = + adm1024_read_value(client, + ADM1024_REG_INT1_STAT) + + (adm1024_read_value(client, ADM1024_REG_INT2_STAT) << + 8); + data->analog_out = + adm1024_read_value(client, ADM1024_REG_ANALOG_OUT); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +void adm1024_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + + int scales[6] = { 250, 225, 330, 500, 1200, 270 }; + + struct adm1024_data *data = client->data; + int nr = ctl_name - ADM1024_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = + IN_FROM_REG(data->in_min[nr], nr) * scales[nr] / 192; + results[1] = + IN_FROM_REG(data->in_max[nr], nr) * scales[nr] / 192; + results[2] = + IN_FROM_REG(data->in[nr], nr) * scales[nr] / 192; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = + IN_TO_REG((results[0] * 192) / scales[nr], nr); + adm1024_write_value(client, ADM1024_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = + IN_TO_REG((results[1] * 192) / scales[nr], nr); + adm1024_write_value(client, ADM1024_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void adm1024_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + int nr = ctl_name - ADM1024_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data-> + fan_div[nr - 1])); + results[1] = + FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = FAN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr - + 1])); + adm1024_write_value(client, + nr == + 1 ? ADM1024_REG_FAN1_MIN : + ADM1024_REG_FAN2_MIN, + data->fan_min[nr - 1]); + } + } +} + + +void adm1024_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = TEMP_LIMIT_FROM_REG(data->temp_os_max); + results[1] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_os_max = TEMP_LIMIT_TO_REG(results[0]); + adm1024_write_value(client, ADM1024_REG_TOS, + data->temp_os_max); + } + if (*nrels_mag >= 2) { + data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[1]); + adm1024_write_value(client, ADM1024_REG_THYST, + data->temp_os_hyst); + } + } +} + +void adm1024_temp1(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = TEMP_LIMIT_FROM_REG(data->temp1_os_max); + results[1] = TEMP_LIMIT_FROM_REG(data->temp1_os_hyst); + results[2] = EXT_TEMP_FROM_REG(data->temp1); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp1_os_max = TEMP_LIMIT_TO_REG(results[0]); + adm1024_write_value(client, ADM1024_REG_EXT_TEMP1_HIGH, + data->temp1_os_max); + } + if (*nrels_mag >= 2) { + data->temp1_os_hyst = TEMP_LIMIT_TO_REG(results[1]); + adm1024_write_value(client, ADM1024_REG_EXT_TEMP1_LOW, + data->temp1_os_hyst); + } + } +} + +void adm1024_temp2(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = TEMP_LIMIT_FROM_REG(data->temp2_os_max); + results[1] = TEMP_LIMIT_FROM_REG(data->temp2_os_hyst); + results[2] = EXT_TEMP_FROM_REG(data->temp2); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp2_os_max = TEMP_LIMIT_TO_REG(results[0]); + adm1024_write_value(client, ADM1024_REG_2_5V_HIGH, + data->temp2_os_max); + } + if (*nrels_mag >= 2) { + data->temp2_os_hyst = TEMP_LIMIT_TO_REG(results[1]); + adm1024_write_value(client, ADM1024_REG_2_5V_LOW, + data->temp2_os_hyst); + } + } +} + +void adm1024_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void adm1024_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = adm1024_read_value(client, ADM1024_REG_VID_FAN_DIV); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0x3f) | (data->fan_div[1] << 6); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xcf) | (data->fan_div[0] << 4); + adm1024_write_value(client, + ADM1024_REG_VID_FAN_DIV, old); + } + } +} + +void adm1024_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = data->analog_out; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->analog_out = results[0]; + adm1024_write_value(client, ADM1024_REG_ANALOG_OUT, + data->analog_out); + } + } +} + +void adm1024_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1024_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1024_update_client(client); + results[0] = VID_FROM_REG(data->vid); + *nrels_mag = 1; + } +} + +static int __init sm_adm1024_init(void) +{ + printk("adm1024.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&adm1024_driver); +} + +static void __exit sm_adm1024_exit(void) +{ + i2c_del_driver(&adm1024_driver); +} + + + +MODULE_AUTHOR + ("Frodo Looijaard 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,590 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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, 0, 255) +#define IN_FROM_REG(val) (val) + +#define TEMP_FROM_REG(val) (((val)>=0x80?(val)-0x100:(val))*10) +#define TEMP_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ + ((val)+5)/10),-128,127) + +#define ALARMS_FROM_REG(val) (val) + +/* For each registered ADM1025, we need to keep some data in memory. That + data is pointed to by adm1025_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new adm1025 client is + allocated. */ +struct adm1025_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[6]; /* Register value */ + u8 in_max[6]; /* Register value */ + u8 in_min[6]; /* Register value */ + u8 temp[2]; /* Register value */ + u8 temp_high[2]; /* Register value */ + u8 temp_low[2]; /* Register value */ + u16 alarms; /* Register encoding, combined */ + u8 vid; /* Register value combined */ + u8 vrm; +}; + + +static int adm1025_attach_adapter(struct i2c_adapter *adapter); +static int adm1025_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int adm1025_detach_client(struct i2c_client *client); +static void adm1025_update_client(struct i2c_client *client); +static void adm1025_init_client(struct i2c_client *client); + + +static void adm1025_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1025_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1025_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1025_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1025_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver adm1025_driver = { + .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 Celsius * 10 */ +#define ADM1025_SYSCTL_TEMP 1251 + +#define ADM1025_SYSCTL_ALARMS 2001 /* bitvector */ +#define ADM1025_SYSCTL_VID 2003 /* Volts * 1000 */ +#define ADM1025_SYSCTL_VRM 2004 + +#define ADM1025_ALARM_IN0 0x0001 +#define ADM1025_ALARM_IN1 0x0002 +#define ADM1025_ALARM_IN2 0x0004 +#define ADM1025_ALARM_IN3 0x0008 +#define ADM1025_ALARM_IN4 0x0100 +#define ADM1025_ALARM_IN5 0x0200 +#define ADM1025_ALARM_RTEMP 0x0020 +#define ADM1025_ALARM_TEMP 0x0010 +#define ADM1025_ALARM_RFAULT 0x4000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected ADM1025. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table adm1025_dir_table_template[] = { + {ADM1025_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_in}, + {ADM1025_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_in}, + {ADM1025_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_in}, + {ADM1025_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_in}, + {ADM1025_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_in}, + {ADM1025_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_in}, + {ADM1025_SYSCTL_RTEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_temp}, + {ADM1025_SYSCTL_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_temp}, + {ADM1025_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_alarms}, + {ADM1025_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_vid}, + {ADM1025_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1025_vrm}, + {0} +}; + +static int adm1025_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm1025_detect); +} + +static int adm1025_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct adm1025_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("adm1025.o: adm1025_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access adm1025_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct adm1025_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &adm1025_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if ((i2c_smbus_read_byte_data(new_client, + ADM1025_REG_CONFIG) & 0x80) != 0x00 + || (i2c_smbus_read_byte_data(new_client, + ADM1025_REG_STATUS1) & 0xC0) != 0x00 + || (i2c_smbus_read_byte_data(new_client, + ADM1025_REG_STATUS2) & 0xBC) != 0x00) + goto ERROR1; + } + + /* Determine the chip type. */ + if (kind <= 0) { + u8 man_id, chip_id; + + man_id = i2c_smbus_read_byte_data(new_client, + ADM1025_REG_COMPANY_ID); + chip_id = i2c_smbus_read_byte_data(new_client, + ADM1025_REG_DIE_REV); + + if (man_id == 0x41) { /* Analog Devices */ + if ((chip_id & 0xF0) == 0x20) /* ADM1025 */ + kind = adm1025; + } else if (man_id == 0xA1) { /* Philips */ + if (address != 0x2E + && (chip_id & 0xF0) == 0x20) /* NE1619 */ + kind = ne1619; + } + } + + if (kind <= 0) { /* Identification failed */ + printk("adm1025.o: Unsupported chip.\n"); + goto ERROR1; + } + + if (kind == adm1025) { + type_name = "adm1025"; + client_name = "ADM1025 chip"; + } else if (kind == ne1619) { + type_name = "ne1619"; + client_name = "NE1619 chip"; + } else { +#ifdef DEBUG + printk("adm1025.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the ADM1025 chip */ + adm1025_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int adm1025_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct adm1025_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("adm1025.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +/* Called when we have found a new ADM1025. */ +static void adm1025_init_client(struct i2c_client *client) +{ + struct adm1025_data *data = client->data; + u8 reg; + int i; + + data->vrm = 82; + + /* Set high limits + Usually we avoid setting limits on driver init, but it happens + that the ADM1025 comes with stupid default limits (all registers + set to 0). In case the chip has not gone through any limit + setting yet, we better set the high limits to the max so that + no alarm triggers. */ + for (i=0; i<6; i++) { + reg = i2c_smbus_read_byte_data(client, + ADM1025_REG_IN_MAX(i)); + if (reg == 0) + i2c_smbus_write_byte_data(client, + ADM1025_REG_IN_MAX(i), + 0xFF); + } + for (i=0; i<2; i++) { + reg = i2c_smbus_read_byte_data(client, + ADM1025_REG_TEMP_HIGH(i)); + if (reg == 0) + i2c_smbus_write_byte_data(client, + ADM1025_REG_TEMP_HIGH(i), + 0x7F); + } + + /* Start monitoring */ + reg = i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG); + i2c_smbus_write_byte_data(client, ADM1025_REG_CONFIG, (reg|0x01)&0x7F); +} + +static void adm1025_update_client(struct i2c_client *client) +{ + struct adm1025_data *data = client->data; + u8 nr; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > 2 * HZ) + || (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk("Starting adm1025 update\n"); +#endif + + /* Voltages */ + for (nr = 0; nr < 6; nr++) { + data->in[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN(nr)); + data->in_min[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN_MIN(nr)); + data->in_max[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN_MAX(nr)); + } + + /* Temperatures */ + for (nr = 0; nr < 2; nr++) { + data->temp[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP(nr)); + data->temp_high[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP_HIGH(nr)); + data->temp_low[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP_LOW(nr)); + } + + /* VID */ + data->vid = (i2c_smbus_read_byte_data(client, ADM1025_REG_VID) & 0x0f) + + ((i2c_smbus_read_byte_data(client, ADM1025_REG_VID4) & 0x01) << 4); + + /* Alarms */ + data->alarms = (i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS1) & 0x3f) + + ((i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS2) & 0x43) << 8); + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the data + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +void adm1025_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int scales[6] = { 250, 225, 330, 500, 1200, 330 }; + + struct adm1025_data *data = client->data; + int nr = ctl_name - ADM1025_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1025_update_client(client); + results[0] = (IN_FROM_REG(data->in_min[nr]) * scales[nr] + 96) / 192; + results[1] = (IN_FROM_REG(data->in_max[nr]) * scales[nr] + 96) / 192; + results[2] = (IN_FROM_REG(data->in[nr]) * scales[nr] + 96) / 192; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG((results[0] * 192 + scales[nr] / 2) + / scales[nr]); + i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG((results[1] * 192 + scales[nr] / 2) + / scales[nr]); + i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void adm1025_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1025_data *data = client->data; + int nr = ctl_name - ADM1025_SYSCTL_RTEMP; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1025_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_high[nr]); + results[1] = TEMP_FROM_REG(data->temp_low[nr]); + results[2] = TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_high[nr] = TEMP_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_HIGH(nr), + data->temp_high[nr]); + } + if (*nrels_mag >= 2) { + data->temp_low[nr] = TEMP_TO_REG(results[1]); + i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_LOW(nr), + data->temp_low[nr]); + } + } +} + +void adm1025_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1025_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1025_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void adm1025_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1025_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1025_update_client(client); + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void adm1025_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct adm1025_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +static int __init sm_adm1025_init(void) +{ + printk("adm1025.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&adm1025_driver); +} + +static void __exit sm_adm1025_exit(void) +{ + i2c_del_driver(&adm1025_driver); +} + + + +MODULE_AUTHOR("Chen-Yuan Wu " + " 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,1743 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +#ifndef I2C_DRIVERID_ADM1026 +#define I2C_DRIVERID_ADM1026 1048 +#endif + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(adm1026); + +static int gpio_input[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_output[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_inverted[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_normal[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_fan[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; +MODULE_PARM(gpio_input,"1-17i"); +MODULE_PARM_DESC(gpio_input,"List of GPIO pins (0-16) to program as inputs"); +MODULE_PARM(gpio_output,"1-17i"); +MODULE_PARM_DESC(gpio_output,"List of GPIO pins (0-16) to program as outputs"); +MODULE_PARM(gpio_inverted,"1-17i"); +MODULE_PARM_DESC(gpio_inverted,"List of GPIO pins (0-16) to program as inverted"); +MODULE_PARM(gpio_normal,"1-17i"); +MODULE_PARM_DESC(gpio_normal,"List of GPIO pins (0-16) to program as normal/non-inverted"); +MODULE_PARM(gpio_fan,"1-8i"); +MODULE_PARM_DESC(gpio_fan,"List of GPIO pins (0-7) to program as fan tachs"); + +/* Many ADM1026 constants specified below */ + +/* The ADM1026 registers */ +#define ADM1026_REG_CONFIG1 (0x00) +#define CFG1_MONITOR (0x01) +#define CFG1_INT_ENABLE (0x02) +#define CFG1_INT_CLEAR (0x04) +#define CFG1_AIN8_9 (0x08) +#define CFG1_THERM_HOT (0x10) +#define CFG1_DAC_AFC (0x20) +#define CFG1_PWM_AFC (0x40) +#define CFG1_RESET (0x80) +#define ADM1026_REG_CONFIG2 (0x01) +/* CONFIG2 controls FAN0/GPIO0 through FAN7/GPIO7 */ +#define ADM1026_REG_CONFIG3 (0x07) +#define CFG3_GPIO16_ENABLE (0x01) +#define CFG3_CI_CLEAR (0x02) +#define CFG3_VREF_250 (0x04) +#define CFG3_GPIO16_DIR (0x40) +#define CFG3_GPIO16_POL (0x80) +#define ADM1026_REG_E2CONFIG (0x13) +#define E2CFG_READ (0x01) +#define E2CFG_WRITE (0x02) +#define E2CFG_ERASE (0x04) +#define E2CFG_ROM (0x08) +#define E2CFG_CLK_EXT (0x80) + +/* There are 10 general analog inputs and 7 dedicated inputs + * They are: + * 0 - 9 = AIN0 - AIN9 + * 10 = Vbat + * 11 = 3.3V Standby + * 12 = 3.3V Main + * 13 = +5V + * 14 = Vccp (CPU core voltage) + * 15 = +12V + * 16 = -12V + */ +static u16 REG_IN[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x27, 0x29, 0x26, 0x2a, + 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + }; +static u16 REG_IN_MIN[] = { + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x6d, 0x49, 0x6b, 0x4a, + 0x4b, 0x4c, 0x4d, 0x4e, 0x4f + }; +static u16 REG_IN_MAX[] = { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x6c, 0x41, 0x6a, 0x42, + 0x43, 0x44, 0x45, 0x46, 0x47 + }; +#define ADM1026_REG_IN(nr) (REG_IN[(nr)]) +#define ADM1026_REG_IN_MIN(nr) (REG_IN_MIN[(nr)]) +#define ADM1026_REG_IN_MAX(nr) (REG_IN_MAX[(nr)]) + +/* Temperatures are: + * 0 - Internal + * 1 - External 1 + * 2 - External 2 + */ +static u16 REG_TEMP[] = { 0x1f, 0x28, 0x29 }; +static u16 REG_TEMP_MIN[] = { 0x69, 0x48, 0x49 }; +static u16 REG_TEMP_MAX[] = { 0x68, 0x40, 0x41 }; +static u16 REG_TEMP_TMIN[] = { 0x10, 0x11, 0x12 }; +static u16 REG_TEMP_THERM[] = { 0x0d, 0x0e, 0x0f }; +static u16 REG_TEMP_OFFSET[] = { 0x1e, 0x6e, 0x6f }; +#define ADM1026_REG_TEMP(nr) (REG_TEMP[(nr)]) +#define ADM1026_REG_TEMP_MIN(nr) (REG_TEMP_MIN[(nr)]) +#define ADM1026_REG_TEMP_MAX(nr) (REG_TEMP_MAX[(nr)]) +#define ADM1026_REG_TEMP_TMIN(nr) (REG_TEMP_TMIN[(nr)]) +#define ADM1026_REG_TEMP_THERM(nr) (REG_TEMP_THERM[(nr)]) +#define ADM1026_REG_TEMP_OFFSET(nr) (REG_TEMP_OFFSET[(nr)]) + +#define ADM1026_REG_FAN(nr) (0x38 + (nr)) +#define ADM1026_REG_FAN_MIN(nr) (0x60 + (nr)) +#define ADM1026_REG_FAN_DIV_0_3 (0x02) +#define ADM1026_REG_FAN_DIV_4_7 (0x03) + +#define ADM1026_REG_DAC (0x04) +#define ADM1026_REG_PWM (0x05) + +#define ADM1026_REG_GPIO_CFG_0_3 (0x08) +#define ADM1026_REG_GPIO_CFG_4_7 (0x09) +#define ADM1026_REG_GPIO_CFG_8_11 (0x0a) +#define ADM1026_REG_GPIO_CFG_12_15 (0x0b) +/* CFG_16 in REG_CFG3 */ +#define ADM1026_REG_GPIO_STATUS_0_7 (0x24) +#define ADM1026_REG_GPIO_STATUS_8_15 (0x25) +/* STATUS_16 in REG_STATUS4 */ +#define ADM1026_REG_GPIO_MASK_0_7 (0x1c) +#define ADM1026_REG_GPIO_MASK_8_15 (0x1d) +/* MASK_16 in REG_MASK4 */ + +#define ADM1026_REG_COMPANY 0x16 +#define ADM1026_REG_VERSTEP 0x17 +/* These are the recognized values for the above regs */ +#define ADM1026_COMPANY_ANALOG_DEV 0x41 +#define ADM1026_VERSTEP_GENERIC 0x40 +#define ADM1026_VERSTEP_ADM1026 0x44 + +#define ADM1026_REG_MASK1 0x18 +#define ADM1026_REG_MASK2 0x19 +#define ADM1026_REG_MASK3 0x1a +#define ADM1026_REG_MASK4 0x1b + +#define ADM1026_REG_STATUS1 0x20 +#define ADM1026_REG_STATUS2 0x21 +#define ADM1026_REG_STATUS3 0x22 +#define ADM1026_REG_STATUS4 0x23 + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + */ + +/* IN are scaled acording to built-in resistors. These are the + * voltages corresponding to 3/4 of full scale (192 or 0xc0) + * NOTE: The -12V input needs an additional factor to account + * for the Vref pullup resistor. + * NEG12_OFFSET = SCALE * Vref / V-192 - Vref + * = 13875 * 2.50 / 1.875 - 2500 + * = 16000 + */ +#if 1 +/* The values in this table are based on Table II, page 15 of the + * datasheet. + */ +static int adm1026_scaling[] = { /* .001 Volts */ + 2250, 2250, 2250, 2250, 2250, 2250, + 1875, 1875, 1875, 1875, 3000, 3330, + 3330, 4995, 2250, 12000, 13875 + }; +#define NEG12_OFFSET 16000 +#else +/* The values in this table are based on the resistors in + * Figure 5 on page 16. But the 3.3V inputs are not in + * the figure and the values for the 5V input are wrong. + * For 5V, I'm guessing that R2 at 55.2k is right, but + * the total resistance should be 1400 or 1449 like the + * other inputs. Using 1449, gives 4.922V at 192. + */ +static int adm1026_scaling[] = { /* .001 Volts */ + 2249, 2249, 2249, 2249, 2249, 2249, + 1875, 1875, 1875, 1875, 3329, 3329, + 3329, 4922, 2249, 11969, 13889 + }; +#define NEG12_OFFSET 16019 +#endif + +#define SCALE(val,from,to) (((val)*(to) + ((from)/2))/(from)) +#define INS_TO_REG(n,val) (SENSORS_LIMIT(SCALE(val,adm1026_scaling[n],192),0,255)) +#if 0 /* If we have extended A/D bits */ +#define INSEXT_FROM_REG(n,val,ext) (SCALE((val)*4 + (ext),192*4,adm1026_scaling[n])) +#define INS_FROM_REG(n,val) (INSEXT_FROM_REG(n,val,0)) +#else +#define INS_FROM_REG(n,val) (SCALE(val,192,adm1026_scaling[n])) +#endif + +/* FAN speed is measured using 22.5kHz clock and counts for 2 pulses + * and we assume a 2 pulse-per-rev fan tach signal + * 22500 kHz * 60 (sec/min) * 2 (pulse) / 2 (pulse/rev) == 1350000 + */ +#define FAN_TO_REG(val,div) ((val)<=0 ? 0xff : SENSORS_LIMIT(1350000/((val)*(div)),1,254)) +#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==0xff ? 0 : 1350000/((val)*(div))) +#define DIV_FROM_REG(val) (1<<(val)) +#define DIV_TO_REG(val) ((val)>=8 ? 3 : (val)>=4 ? 2 : (val)>=2 ? 1 : 0) + +/* Temperature is reported in 1 degC increments */ +#define TEMP_TO_REG(val) (SENSORS_LIMIT(val,-127,127)) +#define TEMP_FROM_REG(val) (val) +#define OFFSET_TO_REG(val) (SENSORS_LIMIT(val,-127,127)) +#define OFFSET_FROM_REG(val) (val) + +#define PWM_TO_REG(val) (SENSORS_LIMIT(val,0,255)) +#define PWM_FROM_REG(val) (val) + +/* Analog output is a voltage, but it's used like a PWM + * Seems like this should be scaled, but to be consistent + * with other drivers, we do it this way. + */ +#define DAC_TO_REG(val) (SENSORS_LIMIT(val,0,255)) +#define DAC_FROM_REG(val) (val) + +/* sensors_vid.h defines vid_from_reg() */ +#define VID_FROM_REG(val,vrm) (vid_from_reg(val,vrm)) + +#define ALARMS_FROM_REG(val) (val) + +/* Unlike some other drivers we DO NOT set initial limits. Use + * the config file to set limits. + */ + +/* Typically used with systems using a v9.1 VRM spec ? */ +#define ADM1026_INIT_VRM 91 +#define ADM1026_INIT_VID -1 + +/* Chip sampling rates + * + * Some sensors are not updated more frequently than once per second + * so it doesn't make sense to read them more often than that. + * We cache the results and return the saved data if the driver + * is called again before a second has elapsed. + * + * Also, there is significant configuration data for this chip + * So, we keep the config data up to date in the cache + * when it is written and only sample it once every 5 *minutes* + */ +#define ADM1026_DATA_INTERVAL (1 * HZ) +#define ADM1026_CONFIG_INTERVAL (5 * 60 * HZ) + +/* We allow for multiple chips in a single system. + * + * For each registered ADM1026, we need to keep state information + * at client->data. The adm1026_data structure is dynamically + * allocated, when a new client structure is allocated. */ + +struct adm1026_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_reading; /* In jiffies */ + unsigned long last_config; /* In jiffies */ + + u8 in[17]; /* Register value */ + u8 in_max[17]; /* Register value */ + u8 in_min[17]; /* Register value */ + s8 temp[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + s8 temp_tmin[3]; /* Register value */ + s8 temp_therm[3]; /* Register value */ + s8 temp_offset[3]; /* Register value */ + u8 fan[8]; /* Register value */ + u8 fan_min[8]; /* Register value */ + u8 fan_div[8]; /* Decoded value */ + u8 pwm; /* Register value */ + u8 analog_out; /* Register value */ + int vid; /* Decoded value */ + u8 vrm; /* VRM version */ + long alarms; /* Register encoding, combined */ + long alarm_mask; /* Register encoding, combined */ + long gpio; /* Register encoding, combined */ + long gpio_mask; /* Register encoding, combined */ + u8 gpio_config[17]; /* Decoded value */ + u8 config1; /* Register value */ + u8 config2; /* Register value */ + u8 config3; /* Register value */ +}; + +static int adm1026_attach_adapter(struct i2c_adapter *adapter); +static int adm1026_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int adm1026_detach_client(struct i2c_client *client); + +static int adm1026_read_value(struct i2c_client *client, u8 register); +static int adm1026_write_value(struct i2c_client *client, u8 register, int value); +static void adm1026_print_gpio(struct i2c_client *client); +static void adm1026_fixup_gpio(struct i2c_client *client); +static void adm1026_update_client(struct i2c_client *client); +static void adm1026_init_client(struct i2c_client *client); + + +static void adm1026_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void adm1026_in16(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void adm1026_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_fixup_fan_min(struct i2c_client *client, + int fan, int old_div); +static void adm1026_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_temp_offset(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_temp_tmin(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_temp_therm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_alarm_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_gpio(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_gpio_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1026_afc(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver adm1026_driver = { + .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 + */ +static int adm1026_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm1026_detect); +} + +/* This function is called by i2c_detect */ +static int adm1026_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + int company, verstep ; + struct i2c_client *new_client; + struct adm1026_data *data; + int err = 0; + const char *type_name = ""; + struct ctl_table template[CTLTBL_MAX] ; + struct ctl_table * template_next = template ; + + if (i2c_is_isa_adapter(adapter)) { + /* This chip has no ISA interface */ + goto ERROR0 ; + } + + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + /* We need to be able to do byte I/O */ + goto ERROR0 ; + } + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access adm1026_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct adm1026_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &adm1026_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + company = adm1026_read_value(new_client, ADM1026_REG_COMPANY); + verstep = adm1026_read_value(new_client, ADM1026_REG_VERSTEP); + +#ifdef DEBUG + printk("adm1026: Detecting device at %d,0x%02x with" + " COMPANY: 0x%02x and VERSTEP: 0x%02x\n", + i2c_adapter_id(new_client->adapter), new_client->addr, + company, verstep + ); +#endif + + /* If auto-detecting, Determine the chip type. */ + if (kind <= 0) { +#ifdef DEBUG + printk("adm1026: Autodetecting device at %d,0x%02x ...\n", + i2c_adapter_id(adapter), address ); +#endif + if( company == ADM1026_COMPANY_ANALOG_DEV + && verstep == ADM1026_VERSTEP_ADM1026 ) { + kind = adm1026 ; + } else if( company == ADM1026_COMPANY_ANALOG_DEV + && (verstep & 0xf0) == ADM1026_VERSTEP_GENERIC ) { + printk("adm1026: Unrecgonized stepping 0x%02x" + " Defaulting to ADM1026.\n", verstep ); + kind = adm1026 ; + } else if( (verstep & 0xf0) == ADM1026_VERSTEP_GENERIC ) { + printk("adm1026: Found version/stepping 0x%02x" + " Assuming generic ADM1026.\n", verstep ); + kind = any_chip ; + } else { +#ifdef DEBUG + printk("adm1026: Autodetection failed\n"); +#endif + /* Not an ADM1026 ... */ + if( kind == 0 ) { /* User used force=x,y */ + printk("adm1026: Generic ADM1026 Version 6 not" + " found at %d,0x%02x. Try force_adm1026.\n", + i2c_adapter_id(adapter), address ); + } + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR2; + } + data->sysctl_id = i; + + return 0; + + /* Error out and cleanup code */ + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int adm1026_detach_client(struct i2c_client *client) +{ + int err; + int id ; + + id = client->id; + i2c_deregister_entry(((struct adm1026_data *)(client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("adm1026(%d): Client deregistration failed," + " client not detached.\n", id ); + return err; + } + + kfree(client->data); + + return 0; +} + +static 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 ; +} + +static 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. */ +static 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); + +} + +static 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" ); + } +} + +static 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); +} + +static 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 + 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 "20060214" +#define LM_VERSION "2.10.0" + +/* Following macros take channel parameter starting from 0 */ +#define ADM1031_REG_FAN_SPEED(nr) (0x08 + (nr)) + +#define ADM1031_REG_FAN_DIV(nr) (0x20 + (nr)) +#define ADM1031_REG_FAN_MIN(nr) (0x10 + (nr)) + +#define ADM1031_REG_TEMP_MAX(nr) (0x14 + 4 * (nr)) +#define ADM1031_REG_TEMP_MIN(nr) (0x15 + 4 * (nr)) +#define ADM1031_REG_TEMP_CRIT(nr) (0x16 + 4 * (nr)) + +#define ADM1031_REG_TEMP(nr) (0x0a + (nr)) +#define ADM1031_REG_AUTO_TEMP(nr) (0x24 + (nr)) + +#define ADM1031_REG_STATUS(nr) (0x02 + (nr)) +#define ADM1031_REG_FAN_PWM 0x22 + +#define ADM1031_REG_CONF1 0x00 +#define ADM1031_REG_CONF2 0x01 +#define ADM1031_REG_EXT_TEMP 0x06 + +#define ADM1031_CONF1_MONITOR_ENABLE 0x01 /* Monitoring enable */ +#define ADM1031_CONF1_PWM_INVERT 0x08 /* PWM Invert (unused) */ +#define ADM1031_CONF1_AUTO_MODE 0x80 /* Auto fan mode */ + +#define ADM1031_CONF2_PWM1_ENABLE 0x01 +#define ADM1031_CONF2_PWM2_ENABLE 0x02 +#define ADM1031_CONF2_TACH1_ENABLE 0x04 +#define ADM1031_CONF2_TACH2_ENABLE 0x08 +#define ADM1031_CONF2_TEMP_ENABLE(chan) (0x10 << (chan)) + +/* 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(adm1030, adm1031); + +/* + * Proc entries + * These files are created for each detected ADM1031. + */ + +/* -- SENSORS SYSCTL START -- */ + +#define ADM1031_SYSCTL_TEMP1 1200 +#define ADM1031_SYSCTL_TEMP2 1201 +#define ADM1031_SYSCTL_TEMP3 1202 + +#define ADM1031_SYSCTL_FAN1 1210 +#define ADM1031_SYSCTL_FAN2 1211 + +#define ADM1031_SYSCTL_FAN_DIV 1220 + +#define ADM1031_SYSCTL_ALARMS 1250 + +#define ADM1031_ALARM_FAN1_MIN 0x0001 +#define ADM1031_ALARM_FAN1_FLT 0x0002 +#define ADM1031_ALARM_TEMP2_HIGH 0x0004 +#define ADM1031_ALARM_TEMP2_LOW 0x0008 +#define ADM1031_ALARM_TEMP2_CRIT 0x0010 +#define ADM1031_ALARM_TEMP2_DIODE 0x0020 +#define ADM1031_ALARM_TEMP1_HIGH 0x0040 +#define ADM1031_ALARM_TEMP1_LOW 0x0080 +#define ADM1031_ALARM_FAN2_MIN 0x0100 +#define ADM1031_ALARM_FAN2_FLT 0x0200 +#define ADM1031_ALARM_TEMP3_HIGH 0x0400 +#define ADM1031_ALARM_TEMP3_LOW 0x0800 +#define ADM1031_ALARM_TEMP3_CRIT 0x1000 +#define ADM1031_ALARM_TEMP3_DIODE 0x2000 +#define ADM1031_ALARM_TEMP1_CRIT 0x4000 +#define ADM1031_ALARM_THERMAL 0x8000 + + +/* -- SENSORS SYSCTL END -- */ +static void adm1031_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1031_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1031_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1031_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static ctl_table adm1031_dir_table_template[] = { + {ADM1031_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan}, + {ADM1031_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan_div}, + {ADM1031_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan}, + {ADM1031_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_alarms}, + {0} +}; + +static ctl_table adm1030_dir_table_template[] = { + {ADM1031_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan}, + {ADM1031_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan_div}, + {ADM1031_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_alarms}, + {0} +}; + + +/* Each client has this additional data */ +struct adm1031_data { + struct i2c_client client; + int sysctl_id; + struct semaphore update_lock; + int chip_type; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 alarms; + u8 conf1; + u8 conf2; + u8 fan[2]; + u8 fan_min[2]; + u8 fan_pwm[2]; + u8 fan_div[2]; + s8 temp[3]; + u8 auto_temp[3]; + u8 ext_temp[3]; + s8 temp_min[3]; + s8 temp_max[3]; + s8 temp_crit[3]; +}; + +static int adm1031_attach_adapter(struct i2c_adapter *adapter); +static int adm1031_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void adm1031_init_client(struct i2c_client *client); +static int adm1031_detach_client(struct i2c_client *client); + +static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg); +static inline int adm1031_write_value(struct i2c_client *client, u8 reg, + unsigned int value); + +static void adm1031_update_client(struct i2c_client *client); + +/* This is the driver that will be inserted */ +static struct i2c_driver adm1031_driver = { + .name = "ADM1031/ADM1030 sensor driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = adm1031_attach_adapter, + .detach_client = adm1031_detach_client, +}; + +#define TEMP_TO_REG(val) ((val) < 0 ? (((val) - 500) / 1000) : \ + (((val) + 500) / 1000)) + +#define TEMP_FROM_REG(reg) ((reg) * 1000) + +#define TEMP_FROM_REG_EXT(val,ext) (TEMP_FROM_REG(val) + (ext) * 125) + +#define FAN_FROM_REG(reg,div) ((reg) ? 675000 / ((reg) * (div)) : 0) + +#define FAN_TO_REG(val,div) ((val) <= 0 ? 0 : \ + (val) * (div) >= 675000 ? 1 : \ + (val) * (div) <= 2647 ? 255 : \ + 675000 / ((val) * (div))) + +#define FAN_DIV_TO_REG(val) ((val) == 8 ? 0xc0 : \ + (val) == 4 ? 0x80 : \ + (val) == 1 ? 0x00 : 0x40) + +#define FAN_DIV_FROM_REG(reg) (1 << (((reg)&0xc0) >> 6)) + +#define AUTO_TEMP_MIN_FROM_REG(reg) (((reg) >> 3) * 4) + + +static int adm1031_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm1031_detect); +} + +/* This function is called by i2c_detect */ +static int +adm1031_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *new_client; + struct adm1031_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto exit; + + if (!(data = kmalloc(sizeof(struct adm1031_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + memset(data, 0, sizeof(struct adm1031_data)); + + new_client = &data->client; + new_client->data = data; + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &adm1031_driver; + new_client->flags = 0; + + if (kind < 0) { + int id, co; + id = i2c_smbus_read_byte_data(new_client, 0x3d); + co = i2c_smbus_read_byte_data(new_client, 0x3e); + + if (((id != 0x31) || (id != 0x30)) && (co != 0x41)) + goto exit_free; + kind = (id == 0x30) ? adm1030 : adm1031; + } + + if (kind <= 0) + kind = adm1031; + + /* Given the detected chip type, set the chip name and the + * auto fan control helper table. */ + if (kind == adm1030) { + type_name = "adm1030"; + client_name = "ADM1030 chip"; + + } else if (kind == adm1031) { + type_name = "adm1031"; + client_name = "ADM1031 chip"; + } + data->chip_type = kind; + + strcpy(new_client->name, client_name); + 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 exit_free; + + + if (kind == adm1030) { + if ((err = i2c_register_entry(new_client, type_name, + adm1030_dir_table_template, + THIS_MODULE)) < 0) + goto exit_detach; + } else if (kind == adm1031) { + if ((err = i2c_register_entry(new_client, type_name, + adm1031_dir_table_template, + THIS_MODULE)) < 0) + goto exit_detach; + } + + data->sysctl_id = err; + + /* Initialize the ADM1031 chip */ + adm1031_init_client(new_client); + + return 0; + +exit_detach: + i2c_detach_client(new_client); +exit_free: + kfree(data); +exit: + return err; +} + +static int adm1031_detach_client(struct i2c_client *client) +{ + int ret; + struct adm1031_data *data = client->data; + + i2c_deregister_entry(data->sysctl_id); + if ((ret = i2c_detach_client(client)) != 0) { + return ret; + } + + kfree(data); + return 0; +} + +static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int adm1031_write_value(struct i2c_client *client, u8 reg, + unsigned int value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void adm1031_init_client(struct i2c_client *client) +{ + unsigned int read_val; + unsigned int mask; + struct adm1031_data *data = client->data; + + mask = (ADM1031_CONF2_PWM1_ENABLE | ADM1031_CONF2_TACH1_ENABLE); + if (data->chip_type == adm1031) { + mask |= (ADM1031_CONF2_PWM2_ENABLE | + ADM1031_CONF2_TACH2_ENABLE); + } + /* Initialize the ADM1031 chip (enable fan speed reading) */ + read_val = adm1031_read_value(client, ADM1031_REG_CONF2); + if ((read_val | mask) != read_val) { + adm1031_write_value(client, ADM1031_REG_CONF2, + read_val | mask); + } + + read_val = adm1031_read_value(client, ADM1031_REG_CONF1); + if ((read_val | ADM1031_CONF1_MONITOR_ENABLE) != read_val) { + adm1031_write_value(client, ADM1031_REG_CONF1, read_val | + ADM1031_CONF1_MONITOR_ENABLE); + } +} + +static void adm1031_update_client(struct i2c_client *client) +{ + struct adm1031_data *data = client->data; + int chan; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk(KERN_INFO "adm1031.o: Starting chip update\n"); +#endif + for (chan = 0; + chan < ((data->chip_type == adm1031) ? 3 : 2); + chan++) { + u8 oldh, newh; + + oldh = adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + data->ext_temp[chan] = adm1031_read_value(client, + ADM1031_REG_EXT_TEMP); + newh = adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + if (newh != oldh) { + data->ext_temp[chan] = adm1031_read_value(client, + ADM1031_REG_EXT_TEMP); +#ifdef DEBUG + oldh = adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + + /* oldh is actually newer */ + if (newh != oldh) + printk(KERN_INFO "adm1031.o: Remote " + "temperature may be wrong.\n"); +#endif + } + data->temp[chan] = newh; + + data->temp_min[chan] = adm1031_read_value(client, + ADM1031_REG_TEMP_MIN(chan)); + data->temp_max[chan] = adm1031_read_value(client, + ADM1031_REG_TEMP_MAX(chan)); + data->temp_crit[chan] = adm1031_read_value(client, + ADM1031_REG_TEMP_CRIT(chan)); + data->auto_temp[chan] = adm1031_read_value(client, + ADM1031_REG_AUTO_TEMP(chan)); + } + + data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1); + + data->alarms = adm1031_read_value(client, ADM1031_REG_STATUS(0)) + | (adm1031_read_value(client, ADM1031_REG_STATUS(1)) + << 8); + if (data->chip_type == adm1030) { + data->alarms &= 0xc0ff; + } + + for (chan = 0; + chan < (data->chip_type == adm1030 ? 1 : 2); + chan++) { + data->fan_div[chan] = adm1031_read_value(client, + ADM1031_REG_FAN_DIV(chan)); + data->fan_min[chan] = adm1031_read_value(client, + ADM1031_REG_FAN_MIN(chan)); + data->fan[chan] = adm1031_read_value(client, + ADM1031_REG_FAN_SPEED(chan)); + data->fan_pwm[chan] = (adm1031_read_value(client, + ADM1031_REG_FAN_PWM) >> (4 * chan)) + & 0x0f; + } + + data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1); + data->conf2 = adm1031_read_value(client, ADM1031_REG_CONF2); + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static int __init sensors_adm1031_init(void) +{ + printk(KERN_INFO "adm1031.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&adm1031_driver); +} + +static void __exit sensors_adm1031_exit(void) +{ + i2c_del_driver(&adm1031_driver); +} + + +static void +adm1031_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int ext; + struct adm1031_data *data = client->data; + int tmp; + int nr = ctl_name - ADM1031_SYSCTL_TEMP1; + + ext = nr == 0 ? + ((data->ext_temp[nr] >> 6) & 0x3) * 2 : + (((data->ext_temp[nr] >> ((nr - 1) * 3)) & 7)); + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_max[nr]); + results[1] = TEMP_FROM_REG(data->temp_min[nr]); + results[2] = TEMP_FROM_REG(data->temp_crit[nr]); + results[3] = TEMP_FROM_REG_EXT(data->temp[nr], ext); + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + tmp = SENSORS_LIMIT(results[0], -55000, 127000); + data->temp_max[nr] = TEMP_TO_REG(tmp); + adm1031_write_value(client, + ADM1031_REG_TEMP_MAX(nr), + data->temp_max[nr]); + } + if (*nrels_mag >= 2) { + tmp = SENSORS_LIMIT(results[1], -55000, 127000); + data->temp_min[nr] = TEMP_TO_REG(tmp); + adm1031_write_value(client, + ADM1031_REG_TEMP_MIN(nr), + data->temp_min[nr]); + } + if (*nrels_mag >= 3) { + tmp = SENSORS_LIMIT(results[2], -55000, 127000); + data->temp_crit[nr] = TEMP_TO_REG(tmp); + adm1031_write_value(client, + ADM1031_REG_TEMP_CRIT(nr), + data->temp_crit[nr]); + } + } +} + +/* + * That function checks the cases where the fan reading is not + * relevant. It is used to provide 0 as fan reading when the fan is + * not supposed to run. + */ +static int trust_fan_readings(struct adm1031_data * data, int chan) +{ + int res = 0; + + if (data->conf1 & ADM1031_CONF1_AUTO_MODE) { + switch(data->conf1 & 0x60) { + case 0x00: /* temp2 controls fan1, temp3 controls fan2 */ + res = data->temp[chan+1] >= + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[chan+1]); + break; + case 0x20: /* temp2 controls both fans */ + res = data->temp[1] >= + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[1]); + break; + case 0x40: /* temp3 controls both fans */ + res = data->temp[2] >= + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[2]); + break; + case 0x60: /* max of temp1, temp2 and temp3 controls both + fans */ + res = data->temp[0] >= + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[0]) + || data->temp[1] >= + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[1]) + || (data->chip_type == adm1031 + && data->temp[2] >= + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[2])); + break; + } + } else { + res = data->fan_pwm[chan] > 0; + } + + return res; +} + +static void adm1031_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1031_data *data = client->data; + int nr = ctl_name - ADM1031_SYSCTL_FAN1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr], + FAN_DIV_FROM_REG(data->fan_div[nr])); + results[1] = trust_fan_readings(data, nr) ? + FAN_FROM_REG(data->fan[nr], + FAN_DIV_FROM_REG(data->fan_div[nr])) : 0; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr] = FAN_TO_REG(results[0], + FAN_DIV_FROM_REG(data->fan_div[nr])); + adm1031_write_value(client, + ADM1031_REG_FAN_MIN(nr), + data->fan_min[nr]); + } + } +} + +static void adm1031_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1031_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = FAN_DIV_FROM_REG(data->fan_div[0]); + *nrels_mag = 1; + if(data->chip_type == adm1031) { + results[1] = FAN_DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } + } else if (operation == SENSORS_PROC_REAL_WRITE) { + int old_div, new_min; + if (*nrels_mag >= 1) { + old_div = FAN_DIV_FROM_REG(data->fan_div[0]); + data->fan_div[0] = FAN_DIV_TO_REG(results[0]); + adm1031_write_value(client, + ADM1031_REG_FAN_DIV(0), + data->fan_div[0]); + new_min = data->fan_min[0] * old_div / + FAN_DIV_FROM_REG(data->fan_div[0]); + data->fan_min[0] = new_min > 0xff ? 0xff : new_min; + adm1031_write_value(client, + ADM1031_REG_FAN_MIN(0), + data->fan_min[0]); + } + if (*nrels_mag >= 2) { + old_div = FAN_DIV_FROM_REG(data->fan_div[1]); + data->fan_div[1] = FAN_DIV_TO_REG(results[1]); + adm1031_write_value(client, + ADM1031_REG_FAN_DIV(1), + data->fan_div[1]); + new_min = data->fan_min[1] * old_div / + FAN_DIV_FROM_REG(data->fan_div[1]); + data->fan_min[1] = new_min > 0xff ? 0xff : new_min; + adm1031_write_value(client, + ADM1031_REG_FAN_MIN(1), + data->fan_min[1]); + } + } +} + +static void adm1031_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1031_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +MODULE_AUTHOR("Alexandre d'Alton "); +MODULE_DESCRIPTION("ADM1031/ADM1030 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_adm1031_init); +module_exit(sensors_adm1031_exit); --- linux-old/drivers/sensors/adm9240.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/adm9240.c Sun Feb 26 11:12:14 2006 @@ -0,0 +1,701 @@ +/* + adm9240.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + 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 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 + supposedly 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2c, 0x2f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_3(adm9240, ds1780, lm81); + +/* Many ADM9240 constants specified below */ + +#define ADM9240_REG_IN_MAX(nr) (0x2b + (nr) * 2) +#define ADM9240_REG_IN_MIN(nr) (0x2c + (nr) * 2) +#define ADM9240_REG_IN(nr) (0x20 + (nr)) + +/* The ADM9240 registers */ +#define ADM9240_REG_TEST 0x15 +#define ADM9240_REG_ANALOG_OUT 0x19 +/* These are all read-only */ +#define ADM9240_REG_2_5V 0x20 +#define ADM9240_REG_VCCP1 0x21 +#define ADM9240_REG_3_3V 0x22 +#define ADM9240_REG_5V 0x23 +#define ADM9240_REG_12V 0x24 +#define ADM9240_REG_VCCP2 0x25 +#define ADM9240_REG_TEMP 0x27 +#define ADM9240_REG_FAN1 0x28 +#define ADM9240_REG_FAN2 0x29 +#define ADM9240_REG_COMPANY_ID 0x3E /* 0x23 for ADM9240; 0xDA for DS1780 */ + /* 0x01 for LM81 */ +#define ADM9240_REG_DIE_REV 0x3F +/* These are read/write */ +#define ADM9240_REG_2_5V_HIGH 0x2B +#define ADM9240_REG_2_5V_LOW 0x2C +#define ADM9240_REG_VCCP1_HIGH 0x2D +#define ADM9240_REG_VCCP1_LOW 0x2E +#define ADM9240_REG_3_3V_HIGH 0x2F +#define ADM9240_REG_3_3V_LOW 0x30 +#define ADM9240_REG_5V_HIGH 0x31 +#define ADM9240_REG_5V_LOW 0x32 +#define ADM9240_REG_12V_HIGH 0x33 +#define ADM9240_REG_12V_LOW 0x34 +#define ADM9240_REG_VCCP2_HIGH 0x35 +#define ADM9240_REG_VCCP2_LOW 0x36 +#define ADM9240_REG_TCRIT_LIMIT 0x37 /* LM81 only - not supported */ +#define ADM9240_REG_LOW_LIMIT 0x38 /* LM81 only - not supported */ +#define ADM9240_REG_TOS 0x39 +#define ADM9240_REG_THYST 0x3A +#define ADM9240_REG_FAN1_MIN 0x3B +#define ADM9240_REG_FAN2_MIN 0x3C + +#define ADM9240_REG_CONFIG 0x40 +#define ADM9240_REG_INT1_STAT 0x41 +#define ADM9240_REG_INT2_STAT 0x42 +#define ADM9240_REG_INT1_MASK 0x43 +#define ADM9240_REG_INT2_MASK 0x44 + +#define ADM9240_REG_COMPAT 0x45 /* dummy compat. register for other drivers? */ +#define ADM9240_REG_CHASSIS_CLEAR 0x46 +#define ADM9240_REG_VID_FAN_DIV 0x47 +#define ADM9240_REG_I2C_ADDR 0x48 +#define ADM9240_REG_VID4 0x49 +#define ADM9240_REG_TEMP_CONFIG 0x4B +#define ADM9240_REG_EXTMODE1 0x4C /* LM81 only - not supported */ +#define ADM9240_REG_EXTMODE2 0x4D /* LM81 only - not supported */ + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val), 0, 255)) +#define IN_FROM_REG(val,nr) (val) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:\ + (val)==255?0:1350000/((div)*(val))) + +#define TEMP_FROM_REG(temp) ((temp)<256 ? (temp) * 5 : \ + ((temp) - 512) * 5) + +#define TEMP_LIMIT_FROM_REG(val) (((val)>=0x80?(val)-0x100:(val))*10) + +#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10)+256:\ + ((val)+5)/10), \ + 0,255) + +#define ALARMS_FROM_REG(val) (val) + +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1))) + +#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ + 205-(val)*5) + +/* For each registered ADM9240, we need to keep some data in memory. */ +struct adm9240_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[6]; /* Register value */ + u8 in_max[6]; /* Register value */ + u8 in_min[6]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + int temp; /* Temp, shifted right */ + u8 temp_os_max; /* Register value */ + u8 temp_os_hyst; /* Register value */ + u16 alarms; /* Register encoding, combined */ + u8 analog_out; /* Register value */ + u8 vid; /* Register value combined */ +}; + + +static int adm9240_attach_adapter(struct i2c_adapter *adapter); +static int adm9240_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int adm9240_detach_client(struct i2c_client *client); + +static void adm9240_update_client(struct i2c_client *client); +static void adm9240_init_client(struct i2c_client *client); + + +static void adm9240_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm9240_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm9240_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm9240_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm9240_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm9240_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, + long *results); +static void adm9240_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver adm9240_driver = { + .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 Celsius * 100 */ +#define ADM9240_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define ADM9240_SYSCTL_ALARMS 2001 /* bitvector */ +#define ADM9240_SYSCTL_ANALOG_OUT 2002 +#define ADM9240_SYSCTL_VID 2003 + +#define ADM9240_ALARM_IN0 0x0001 +#define ADM9240_ALARM_IN1 0x0002 +#define ADM9240_ALARM_IN2 0x0004 +#define ADM9240_ALARM_IN3 0x0008 +#define ADM9240_ALARM_IN4 0x0100 +#define ADM9240_ALARM_IN5 0x0200 +#define ADM9240_ALARM_FAN1 0x0040 +#define ADM9240_ALARM_FAN2 0x0080 +#define ADM9240_ALARM_TEMP 0x0010 +#define ADM9240_ALARM_CHAS 0x1000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected ADM9240. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table adm9240_dir_table_template[] = { + {ADM9240_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_in}, + {ADM9240_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_in}, + {ADM9240_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_in}, + {ADM9240_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_in}, + {ADM9240_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_in}, + {ADM9240_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_in}, + {ADM9240_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_fan}, + {ADM9240_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_fan}, + {ADM9240_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_temp}, + {ADM9240_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_fan_div}, + {ADM9240_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_alarms}, + {ADM9240_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_analog_out}, + {ADM9240_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm9240_vid}, + {0} +}; + +static int adm9240_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm9240_detect); +} + +static int adm9240_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct adm9240_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access adm9240_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct adm9240_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &adm9240_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if ( + ((i2c_smbus_read_byte_data + (new_client, ADM9240_REG_CONFIG) & 0x80) != 0x00) + || + (i2c_smbus_read_byte_data(new_client, ADM9240_REG_I2C_ADDR) + != address)) + goto ERROR1; + } + + /* Determine the chip type. */ + if (kind <= 0) { + i = i2c_smbus_read_byte_data(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; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the ADM9240 chip */ + adm9240_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int adm9240_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct adm9240_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("adm9240.o: Client deregistration failed, " + "client not detached\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* Called when we have found a new ADM9240. */ +static void adm9240_init_client(struct i2c_client *client) +{ + /* Start monitoring */ + i2c_smbus_write_byte_data(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] = + i2c_smbus_read_byte_data(client, + ADM9240_REG_IN(i)); + data->in_min[i] = + i2c_smbus_read_byte_data(client, + ADM9240_REG_IN_MIN(i)); + data->in_max[i] = + i2c_smbus_read_byte_data(client, + ADM9240_REG_IN_MAX(i)); + } + data->fan[0] = + i2c_smbus_read_byte_data(client, ADM9240_REG_FAN1); + data->fan_min[0] = + i2c_smbus_read_byte_data(client, ADM9240_REG_FAN1_MIN); + data->fan[1] = + i2c_smbus_read_byte_data(client, ADM9240_REG_FAN2); + data->fan_min[1] = + i2c_smbus_read_byte_data(client, ADM9240_REG_FAN2_MIN); + data->temp = + (i2c_smbus_read_byte_data(client, ADM9240_REG_TEMP) << 1) + + ((i2c_smbus_read_byte_data + (client, ADM9240_REG_TEMP_CONFIG) & 0x80) >> 7); + data->temp_os_max = + i2c_smbus_read_byte_data(client, ADM9240_REG_TOS); + data->temp_os_hyst = + i2c_smbus_read_byte_data(client, ADM9240_REG_THYST); + + i = i2c_smbus_read_byte_data(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 |= + (i2c_smbus_read_byte_data(client, ADM9240_REG_VID4) & 0x01) + << 4; + + data->alarms = + i2c_smbus_read_byte_data(client, + ADM9240_REG_INT1_STAT) + + (i2c_smbus_read_byte_data(client, ADM9240_REG_INT2_STAT) << + 8); + data->analog_out = + i2c_smbus_read_byte_data(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); + i2c_smbus_write_byte_data(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); + i2c_smbus_write_byte_data(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])); + i2c_smbus_write_byte_data(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]); + i2c_smbus_write_byte_data(client, ADM9240_REG_TOS, + data->temp_os_max); + } + if (*nrels_mag >= 2) { + data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[1]); + i2c_smbus_write_byte_data(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 = i2c_smbus_read_byte_data(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); + i2c_smbus_write_byte_data(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]; + i2c_smbus_write_byte_data(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_LICENSE("GPL"); +MODULE_DESCRIPTION("ADM9240 driver"); + +module_init(sm_adm9240_init); +module_exit(sm_adm9240_exit); --- linux-old/drivers/sensors/asb100.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/asb100.c Sun Feb 26 11:18:38 2006 @@ -0,0 +1,1018 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include +#include "lm75.h" + +#ifndef I2C_DRIVERID_ASB100 +#define I2C_DRIVERID_ASB100 1043 +#endif + +/* I2C addresses to scan */ +static unsigned short normal_i2c[] = { 0x2d, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 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 */ +#define ASB100_DEFAULT_VRM 90 + +/* Insmod parameters */ +SENSORS_INSMOD_1(asb100); +SENSORS_MODULE_PARM(force_subclients, "List of subclient addresses: " \ + "{bus, clientaddr, subclientaddr1, subclientaddr2}"); + +/* Voltage IN registers 0-6 */ +#define ASB100_REG_IN(nr) (0x20 + (nr)) +#define ASB100_REG_IN_MAX(nr) (0x2b + (nr * 2)) +#define ASB100_REG_IN_MIN(nr) (0x2c + (nr * 2)) + +/* FAN IN registers 1-3 */ +#define ASB100_REG_FAN(nr) (0x27 + (nr)) +#define ASB100_REG_FAN_MIN(nr) (0x3a + (nr)) + +/* TEMPERATURE registers 1-4 */ +static const u16 asb100_reg_temp[] = {0, 0x27, 0x150, 0x250, 0x17}; +static const u16 asb100_reg_temp_max[] = {0, 0x39, 0x155, 0x255, 0x18}; +static const u16 asb100_reg_temp_hyst[] = {0, 0x3a, 0x153, 0x253, 0x19}; + +#define ASB100_REG_TEMP(nr) (asb100_reg_temp[nr]) +#define ASB100_REG_TEMP_MAX(nr) (asb100_reg_temp_max[nr]) +#define ASB100_REG_TEMP_HYST(nr) (asb100_reg_temp_hyst[nr]) + +#define ASB100_REG_TEMP2_CONFIG 0x0152 +#define ASB100_REG_TEMP3_CONFIG 0x0252 + + +#define ASB100_REG_CONFIG 0x40 +#define ASB100_REG_ALARM1 0x41 +#define ASB100_REG_ALARM2 0x42 +#define ASB100_REG_SMIM1 0x43 +#define ASB100_REG_SMIM2 0x44 +#define ASB100_REG_VID_FANDIV 0x47 +#define ASB100_REG_I2C_ADDR 0x48 +#define ASB100_REG_CHIPID 0x49 +#define ASB100_REG_I2C_SUBADDR 0x4a +#define ASB100_REG_PIN 0x4b +#define ASB100_REG_IRQ 0x4c +#define ASB100_REG_BANK 0x4e +#define ASB100_REG_CHIPMAN 0x4f + +#define ASB100_REG_WCHIPID 0x58 + +/* bit 7 -> enable, bits 0-3 -> duty cycle */ +#define ASB100_REG_PWM1 0x59 + +/* CONVERSIONS + Rounding and limit checking is only done on the TO_REG variants. */ + +/* These constants are a guess, consistent w/ w83781d */ +#define ASB100_IN_MIN ( 0) +#define ASB100_IN_MAX (408) + +/* IN: 1/100 V (0V to 4.08V) + REG: 16mV/bit */ +static u8 IN_TO_REG(unsigned val) +{ + unsigned nval = SENSORS_LIMIT(val, ASB100_IN_MIN, ASB100_IN_MAX); + return (nval * 10 + 8) / 16; +} + +static unsigned IN_FROM_REG(u8 reg) +{ + return (reg * 16 + 5) / 10; +} + +static u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +static int FAN_FROM_REG(u8 val, int div) +{ + return val==0 ? -1 : val==255 ? 0 : 1350000/(val*div); +} + +/* These constants are a guess, consistent w/ w83781d */ +#define ASB100_TEMP_MIN (-1280) +#define ASB100_TEMP_MAX ( 1270) + +/* TEMP: 1/10 degrees C (-128C to +127C) + REG: 1C/bit, two's complement */ +static u8 TEMP_TO_REG(int temp) +{ + int ntemp = SENSORS_LIMIT(temp, ASB100_TEMP_MIN, ASB100_TEMP_MAX); + ntemp += (ntemp<0 ? -5 : 5); + return (u8)(ntemp / 10); +} + +static int TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 10; +} + +/* PWM: 0 - 255 per sensors documentation + REG: (6.25% duty cycle per bit) */ +static u8 ASB100_PWM_TO_REG(int pwm) +{ + pwm = SENSORS_LIMIT(pwm, 0, 255); + return (u8)(pwm / 16); +} + +static int ASB100_PWM_FROM_REG(u8 reg) +{ + return reg * 16; +} + +#define ALARMS_FROM_REG(val) (val) + +#define DIV_FROM_REG(val) (1 << (val)) + +/* FAN DIV: 1, 2, 4, or 8 (defaults to 2) + REG: 0, 1, 2, or 3 (respectively) (defaults to 1) */ +static u8 DIV_TO_REG(long val) +{ + return val==8 ? 3 : val==4 ? 2 : val==1 ? 0 : 1; +} + +/* For each registered client, we need to keep some data in memory. That + data is pointed to by client->data. The structure itself is + dynamically allocated, at the same time the client itself is allocated. */ +struct asb100_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + unsigned long last_updated; /* In jiffies */ + + /* array of 2 pointers to subclients */ + struct i2c_client *lm75[2]; + + char valid; /* !=0 if following fields are valid */ + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u16 temp[4]; /* Register value (0 and 3 are u8 only) */ + u16 temp_max[4]; /* Register value (0 and 3 are u8 only) */ + u16 temp_hyst[4]; /* Register value (0 and 3 are u8 only) */ + u8 fan_div[3]; /* Register encoding, right justified */ + u8 pwm; /* Register encoding */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u8 vrm; +}; + +static int asb100_attach_adapter(struct i2c_adapter *adapter); +static int asb100_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int asb100_detach_client(struct i2c_client *client); + +static int asb100_read_value(struct i2c_client *client, u16 reg); +static void asb100_write_value(struct i2c_client *client, u16 reg, u16 val); +static void asb100_update_client(struct i2c_client *client); +static void asb100_init_client(struct i2c_client *client); + +static void asb100_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void asb100_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver asb100_driver = { + .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 Celsius * 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 = 0; + 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]); + 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); + 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 = 0; + 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"); + goto ERROR0; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_debug("asb100.o: detect failed, " + "smbus byte data not supported!\n"); + goto ERROR0; + } + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access asb100_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct asb100_data), GFP_KERNEL))) { + pr_debug("asb100.o: detect failed, kmalloc failed!\n"); + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &asb100_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + /* The chip may be stuck in some other bank than bank 0. This may + make reading other information impossible. Specify a force=... or + force_*=... parameter, and the chip will be reset to the right + bank. */ + if (kind < 0) { + + int val1 = asb100_read_value(new_client, ASB100_REG_BANK); + int val2 = asb100_read_value(new_client, ASB100_REG_CHIPMAN); + + /* If we're in bank 0 */ + if ( (!(val1 & 0x07)) && + /* Check for ASB100 ID (low byte) */ + ( ((!(val1 & 0x80)) && (val2 != 0x94)) || + /* Check for ASB100 ID (high byte ) */ + ((val1 & 0x80) && (val2 != 0x06)) ) ) { + pr_debug("asb100.o: detect failed, " + "bad chip id 0x%02x!\n", val2); + 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); + 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, THIS_MODULE)) < 0) { + err = data->sysctl_id; + goto ERROR3; + } + + return 0; + +ERROR3: + i2c_detach_client(data->lm75[0]); + kfree(data->lm75[1]); + kfree(data->lm75[0]); +ERROR2: + i2c_detach_client(new_client); +ERROR1: + kfree(data); +ERROR0: + return err; +} + +static int asb100_detach_client(struct i2c_client *client) +{ + int err; + struct asb100_data *data = client->data; + + /* remove sysctl table (primary client only) */ + if ((data)) + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk (KERN_ERR "asb100.o: Client deregistration failed; " + "client not detached.\n"); + return err; + } + + if (data) { + /* primary client */ + kfree(data); + } else { + /* subclients */ + kfree(client); + } + + return 0; +} + +/* The SMBus locks itself, usually, but nothing may access the Winbond between + bank switches. ISA access must always be locked explicitly! + We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, + would slow down the W83781D access and should not be necessary. + There are some ugly typecasts here, but the good news is - they should + nowhere else be necessary! */ +static int asb100_read_value(struct i2c_client *client, u16 reg) +{ + struct asb100_data *data = client->data; + struct i2c_client *cl; + int res, bank; + + down(&data->lock); + + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank); + + if (bank == 0 || bank > 2) { + res = i2c_smbus_read_byte_data(client, reg & 0xff); + } else { + /* switch to subclient */ + cl = data->lm75[bank - 1]; + + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x50: /* TEMP */ + res = swab16(i2c_smbus_read_word_data (cl, 0)); + break; + case 0x52: /* CONFIG */ + res = i2c_smbus_read_byte_data(cl, 1); + break; + case 0x53: /* HYST */ + res = swab16(i2c_smbus_read_word_data (cl, 2)); + break; + case 0x55: /* MAX */ + default: + res = swab16(i2c_smbus_read_word_data (cl, 3)); + break; + } + } + + if (bank > 2) + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0); + + up(&data->lock); + + return res; +} + +static void asb100_write_value(struct i2c_client *client, u16 reg, u16 value) +{ + struct asb100_data *data = client->data; + struct i2c_client *cl; + int bank; + + down(&data->lock); + + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank); + + if (bank == 0 || bank > 2) { + i2c_smbus_write_byte_data(client, reg & 0xff, value & 0xff); + } else { + /* switch to subclient */ + cl = data->lm75[bank - 1]; + + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x52: /* CONFIG */ + i2c_smbus_write_byte_data(cl, 1, value & 0xff); + break; + case 0x53: /* HYST */ + i2c_smbus_write_word_data(cl, 2, swab16(value)); + break; + case 0x55: /* MAX */ + i2c_smbus_write_word_data(cl, 3, swab16(value)); + break; + } + } + + if (bank > 2) + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0); + + up(&data->lock); +} + +static void asb100_init_client(struct i2c_client *client) +{ + struct asb100_data *data = client->data; + int vid = 0; + + vid = asb100_read_value(client, ASB100_REG_VID_FANDIV) & 0x0f; + vid |= (asb100_read_value(client, ASB100_REG_CHIPID) & 0x01) << 4; + data->vrm = ASB100_DEFAULT_VRM; + vid = vid_from_reg(vid, data->vrm); + + /* Start monitoring */ + asb100_write_value(client, ASB100_REG_CONFIG, + (asb100_read_value(client, ASB100_REG_CONFIG) & 0xf7) | 0x01); +} + +static void asb100_update_client(struct i2c_client *client) +{ + struct asb100_data *data = client->data; + int i; + + down(&data->update_lock); + + if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || + time_before(jiffies, data->last_updated) || !data->valid) { + + pr_debug("asb100.o: starting device update...\n"); + + /* 7 voltage inputs */ + for (i = 0; i < 7; i++) { + data->in[i] = asb100_read_value(client, + ASB100_REG_IN(i)); + data->in_min[i] = asb100_read_value(client, + ASB100_REG_IN_MIN(i)); + data->in_max[i] = asb100_read_value(client, + ASB100_REG_IN_MAX(i)); + } + + /* 3 fan inputs */ + for (i = 1; i <= 3; i++) { + data->fan[i-1] = asb100_read_value(client, + ASB100_REG_FAN(i)); + data->fan_min[i-1] = asb100_read_value(client, + ASB100_REG_FAN_MIN(i)); + } + + /* 4 temperature inputs */ + for (i = 1; i <= 4; i++) { + data->temp[i-1] = asb100_read_value(client, + ASB100_REG_TEMP(i)); + data->temp_max[i-1] = asb100_read_value(client, + ASB100_REG_TEMP_MAX(i)); + data->temp_hyst[i-1] = asb100_read_value(client, + ASB100_REG_TEMP_HYST(i)); + } + + /* VID and fan divisors */ + i = asb100_read_value(client, ASB100_REG_VID_FANDIV); + data->vid = i & 0x0f; + data->vid |= (asb100_read_value(client, + ASB100_REG_CHIPID) & 0x01) << 4; + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + data->fan_div[2] = (asb100_read_value(client, + ASB100_REG_PIN) >> 6) & 0x03; + + /* PWM */ + data->pwm = asb100_read_value(client, ASB100_REG_PWM1); + + /* alarms */ + data->alarms = asb100_read_value(client, ASB100_REG_ALARM1) + + (asb100_read_value(client, ASB100_REG_ALARM2) << 8); + + data->last_updated = jiffies; + data->valid = 1; + + pr_debug("asb100.o: ... update complete.\n"); + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +static void asb100_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + int nr = ctl_name - ASB100_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + asb100_write_value(client, ASB100_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + asb100_write_value(client, ASB100_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void asb100_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + int nr = ctl_name - ASB100_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = + FAN_TO_REG(results[0], + DIV_FROM_REG(data->fan_div[nr-1])); + asb100_write_value(client, + ASB100_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + +void asb100_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + int nr = ctl_name - ASB100_SYSCTL_TEMP1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_max[nr]); + results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); + results[2] = TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 3; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_max[nr] = TEMP_TO_REG(results[0]); + asb100_write_value(client, ASB100_REG_TEMP_MAX(nr+1), + data->temp_max[nr]); + } + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = TEMP_TO_REG(results[1]); + asb100_write_value(client, ASB100_REG_TEMP_HYST(nr+1), + data->temp_hyst[nr]); + } + } +} + +void asb100_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + int nr = ctl_name - ASB100_SYSCTL_TEMP1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + + results[0] = LM75_TEMP_FROM_REG(data->temp_max[nr]); + results[1] = LM75_TEMP_FROM_REG(data->temp_hyst[nr]); + results[2] = LM75_TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 3; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_max[nr] = + LM75_TEMP_TO_REG(results[0]); + asb100_write_value(client, ASB100_REG_TEMP_MAX(nr+1), + data->temp_max[nr]); + } + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = + LM75_TEMP_TO_REG(results[1]); + asb100_write_value(client, ASB100_REG_TEMP_HYST(nr+1), + data->temp_hyst[nr]); + } + } +} + +void asb100_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void asb100_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +void asb100_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void asb100_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + int old, old2; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + results[2] = DIV_FROM_REG(data->fan_div[2]); + *nrels_mag = 3; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = asb100_read_value(client, ASB100_REG_VID_FANDIV); + if (*nrels_mag >= 3) { + data->fan_div[2] = DIV_TO_REG(results[2]); + old2 = asb100_read_value(client, ASB100_REG_PIN); + old2 = (old2 & 0x3f) | ((data->fan_div[2] & 0x03) << 6); + asb100_write_value(client, ASB100_REG_PIN, old2); + } + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0x3f) | ((data->fan_div[1] & 0x03) << 6); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xcf) | ((data->fan_div[0] & 0x03) << 4); + asb100_write_value(client, ASB100_REG_VID_FANDIV, old); + } + } +} + +void asb100_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct asb100_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + asb100_update_client(client); + results[0] = ASB100_PWM_FROM_REG(data->pwm & 0x0f); + results[1] = (data->pwm & 0x80) ? 1 : 0; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + u8 val = data->pwm; + if (*nrels_mag >= 1) { + val = 0x0f & ASB100_PWM_TO_REG(results[0]); + if (*nrels_mag >= 2) { + if (results[1]) + val |= 0x80; + else + val &= ~0x80; + } + asb100_write_value(client, ASB100_REG_PWM1, val); + } + } +} + +static int __init asb100_init(void) +{ + printk(KERN_INFO "asb100.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&asb100_driver); +} + +static void __exit asb100_exit(void) +{ + i2c_del_driver(&asb100_driver); +} + +MODULE_AUTHOR( "Mark M. Hoffman , " + "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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,887 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; + +/* found only at 0x44 or 0x45 */ +static unsigned short normal_i2c_range[] = { 0x44, 0x45, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(bt869); + +/* Many bt869 constants specified below */ + +/* The bt869 registers */ +/* Coming soon: Many, many registers */ + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ + + /*none */ + +/* Initial values */ +/*none*/ + +/* Each client has this additional data */ +struct bt869_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 status[3]; /* Register values */ + u16 res[2]; /* Resolution XxY */ + u8 ntsc; /* 1=NTSC, 0=PAL */ + u8 half; /* go half res */ + u8 depth; /* screen depth */ + u8 colorbars; /* turn on/off colorbar calibration screen */ + u8 svideo; /* output format: (2=RGB) 1=SVIDEO, 0=Composite */ +}; + +static int bt869_attach_adapter(struct i2c_adapter *adapter); +static int bt869_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void bt869_init_client(struct i2c_client *client); +static int bt869_detach_client(struct i2c_client *client); +static int bt869_read_value(struct i2c_client *client, u8 reg); +static int bt869_write_value(struct i2c_client *client, u8 reg, u16 value); +static void bt869_write_values(struct i2c_client *client, u16 *values); +static void bt869_status(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_ntsc(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_res(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_half(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_colorbars(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_svideo(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_depth(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void bt869_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver bt869_driver = { + .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 + }; + + +static int bt869_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, bt869_detect); +} + +/* This function is called by i2c_detect */ +int bt869_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, cur; + struct i2c_client *new_client; + struct bt869_data *data; + int err = 0; + const char *type_name, *client_name; + + + printk("bt869.o: probing address %d .\n", address); + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("bt869.o: bt869_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access bt869_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct bt869_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &bt869_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. It is lousy. */ + i2c_smbus_write_byte_data(new_client, 0xC4, 0); /* set status bank 0 */ + cur = i2c_smbus_read_byte(new_client); + printk("bt869.o: address 0x%X testing-->0x%X\n", address, cur); + if ((cur & 0xE0) != 0x20) + goto ERROR1; + + /* Determine the chip type */ + kind = ((cur & 0x20) >> 5); + + if (kind) { + type_name = "bt869"; + client_name = "bt869 chip"; + printk("bt869.o: BT869 detected\n"); + } else { + type_name = "bt868"; + client_name = "bt868 chip"; + printk("bt869.o: BT868 detected\n"); + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + bt869_init_client((struct i2c_client *) new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int bt869_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct bt869_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("bt869.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* All registers are byte-sized. + bt869 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int bt869_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte(client); +} + +/* All registers are byte-sized. + bt869 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int bt869_write_value(struct i2c_client *client, u8 reg, u16 value) +{ +#ifdef DEBUG + printk("bt869.o: write_value(0x%X, 0x%X)\n", reg, value); +#endif + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void bt869_write_values(struct i2c_client *client, u16 *values) +{ + /* writes set of registers from array. 0,0 marks end of table */ + while (*values) { + bt869_write_value(client, values[0], values[1]); + values += 2; + } +} + +static void bt869_init_client(struct i2c_client *client) +{ + struct bt869_data *data = client->data; + + /* Initialize the bt869 chip */ + bt869_write_value(client, 0x0ba, 0x80); + // bt869_write_value(client,0x0D6, 0x00); + /* Be a slave to the clock on the Voodoo3 */ + bt869_write_value(client, 0xa0, 0x80); + bt869_write_value(client, 0xba, 0x20); + /* depth =16bpp */ + bt869_write_value(client, 0x0C6, 0x001); + bt869_write_value(client, 0xC4, 1); + /* Flicker free enable and config */ + bt869_write_value(client, 0xC8, 0); + data->res[0] = 640; + data->res[1] = 480; + data->ntsc = 1; + data->half = 0; + data->colorbars = 0; + data->svideo = 0; + data->depth = 16; + +} + +static void bt869_update_client(struct i2c_client *client) +{ + struct bt869_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk("Starting bt869 update\n"); +#endif + if ((data->res[0] == 800) && (data->res[1] == 600)) { + /* 800x600 built-in mode */ + bt869_write_value(client, 0xB8, + (2 + (!data->ntsc))); + bt869_write_value(client, 0xa0, 0x80 + 0x11); + printk("bt869.o: writing into config -->0x%X\n", + (2 + (!data->ntsc))); + } + else if ((data->res[0] == 720) && (data->res[1] == 576)) { + /* 720x576 no-overscan-compensation mode suitable for PAL DVD playback */ + data->ntsc = 0; /* This mode always PAL */ + bt869_write_values(client, registers_720_576); + } + else if ((data->res[0] == 720) && (data->res[1] == 480)) { + /* 720x480 no-overscan-compensation mode suitable for NTSC DVD playback */ + data->ntsc = 1; /* This mode always NTSC */ + bt869_write_values(client, registers_720_480); + } + else { + /* 640x480 built-in mode */ + bt869_write_value(client, 0xB8, (!data->ntsc)); + bt869_write_value(client, 0xa0, 0x80 + 0x0C); + printk("bt869.o: writing into config -->0x%X\n", + (0 + (!data->ntsc))); + if ((data->res[0] != 640) || (data->res[1] != 480)) { + printk + ("bt869.o: Warning: arbitrary resolutions not supported yet. Using 640x480.\n"); + data->res[0] = 640; + data->res[1] = 480; + } + } + /* Set colour depth */ + if ((data->depth != 24) && (data->depth != 16)) + data->depth = 16; + if (data->depth == 16) + bt869_write_value(client, 0x0C6, 0x001); + if (data->depth == 24) + bt869_write_value(client, 0x0C6, 0x000); + /* set "half" resolution mode */ + bt869_write_value(client, 0xd4, data->half << 6); + /* Set composite/svideo mode, also enable the right dacs */ + switch (data->svideo) { + case 2: /* RGB */ + /* requires hardware mod on Voodoo3 to get all outputs, + untested in practice... Feedback to steve@daviesfam.org please */ + bt869_write_value(client, 0xd6, 0x0c); + bt869_write_value(client, 0xce, 0x24); + bt869_write_value(client, 0xba, 0x20); + break; + case 1: /* Svideo*/ + bt869_write_value(client, 0xce, 0x24); + bt869_write_value(client, 0xba, 0x21); + break; + default: /* Composite */ + bt869_write_value(client, 0xce, 0x0); + bt869_write_value(client, 0xba, 0x21); + break; + } + /* Enable outputs */ + bt869_write_value(client, 0xC4, 1); + /* Issue timing reset */ + bt869_write_value(client, 0x6c, 0x80); + +/* Read back status registers */ + bt869_write_value(client, 0xC4, + 1 | (data->colorbars << 2)); + data->status[0] = bt869_read_value(client, 1); + bt869_write_value(client, 0xC4, + 0x41 | (data->colorbars << 2)); + data->status[1] = bt869_read_value(client, 1); + bt869_write_value(client, 0xC4, + 0x81 | (data->colorbars << 2)); + data->status[2] = bt869_read_value(client, 1); + bt869_write_value(client, 0xC4, + 0x0C1 | (data->colorbars << 2)); + data->last_updated = jiffies; + data->valid = 1; + } + up(&data->update_lock); +} + + +void bt869_status(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->status[0]; + results[1] = data->status[1]; + results[2] = data->status[2]; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + printk + ("bt869.o: Warning: write was requested on read-only proc file: status\n"); + } +} + + +void bt869_ntsc(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->ntsc; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->ntsc = (results[0] > 0); + } + bt869_update_client(client); + } +} + + +void bt869_svideo(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->svideo; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->svideo = results[0]; + } + bt869_update_client(client); + } +} + + +void bt869_res(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->res[0]; + results[1] = data->res[1]; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->res[0] = results[0]; + } + if (*nrels_mag >= 2) { + data->res[1] = results[1]; + } + bt869_update_client(client); + } +} + + +void bt869_half(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->half; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->half = (results[0] > 0); + bt869_update_client(client); + } + } +} + +void bt869_colorbars(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->colorbars; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->colorbars = (results[0] > 0); + bt869_update_client(client); + } + } +} + +void bt869_depth(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct bt869_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + bt869_update_client(client); + results[0] = data->depth; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->depth = results[0]; + bt869_update_client(client); + } + } +} + +static int __init sm_bt869_init(void) +{ + printk("bt869.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&bt869_driver); +} + +static void __exit sm_bt869_exit(void) +{ + i2c_del_driver(&bt869_driver); +} + + + +MODULE_AUTHOR + ("Frodo Looijaard , 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,587 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x50, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(ddcmon); + +static int checksum = 0; +MODULE_PARM(checksum, "i"); +MODULE_PARM_DESC(checksum, "Only accept eeproms whose checksum is correct"); + +/* Many constants specified below */ + +/* DDCMON registers */ +/* vendor section */ +#define DDCMON_REG_MAN_ID 0x08 +#define DDCMON_REG_PROD_ID 0x0A +#define DDCMON_REG_SERIAL 0x0C +#define DDCMON_REG_WEEK 0x10 +#define DDCMON_REG_YEAR 0x11 +/* EDID version */ +#define DDCMON_REG_EDID_VER 0x12 +#define DDCMON_REG_EDID_REV 0x13 +/* display information */ +#define DDCMON_REG_HORSIZE 0x15 +#define DDCMON_REG_VERSIZE 0x16 +#define DDCMON_REG_GAMMA 0x17 +#define DDCMON_REG_DPMS_FLAGS 0x18 +/* supported timings */ +#define DDCMON_REG_ESTABLISHED_TIMINGS 0x23 +#define DDCMON_REG_STANDARD_TIMINGS 0x26 +#define DDCMON_REG_TIMBASE 0x36 +#define DDCMON_REG_TIMINCR 18 +#define DDCMON_REG_TIMNUM 4 + +#define DDCMON_REG_CHECKSUM 0x7f + +/* Size of DDCMON in bytes */ +#define DDCMON_SIZE 128 + +/* Each client has this additional data */ +struct ddcmon_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 data[DDCMON_SIZE]; /* Register values */ +}; + + +static int ddcmon_attach_adapter(struct i2c_adapter *adapter); +static int ddcmon_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int ddcmon_detach_client(struct i2c_client *client); + +static void ddcmon_idcall(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_size(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_sync(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_maxclock(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_timings(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_serial(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_time(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_edid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_gamma(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_dpms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_standard_timing(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ddcmon_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver ddcmon_driver = { + .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_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, ddcmon_detect); +} + +/* This function is called by i2c_detect */ +int ddcmon_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, cs; + struct i2c_client *new_client; + struct ddcmon_data *data; + int err = 0; + const char *type_name, *client_name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access ddcmon_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct ddcmon_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + memset(data->data, 0xff, DDCMON_SIZE); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &ddcmon_driver; + new_client->flags = 0; + + /* prevent 24RF08 corruption (just in case) */ + i2c_smbus_write_quick(new_client, 0); + + /* Now, we do the remaining detection. */ + if (checksum) { + int cs = 0; + for (i = 0; i < 0x80; i++) + cs += i2c_smbus_read_byte_data(new_client, i); + if ((cs & 0xff) != 0) + goto ERROR1; + } + + /* Verify the first 8 locations 0x00FFFFFFFFFFFF00 */ + /* Allow force and force_ddcmon arguments */ + if(kind < 0) + { + for(i = 0; i < 8; i++) { + cs = i2c_smbus_read_byte_data(new_client, i); + if(i == 0 || i == 7) { + if(cs != 0) + goto ERROR1; + } else if(cs != 0xff) + goto ERROR1; + } + } + + type_name = "ddcmon"; + client_name = "DDC Monitor"; + + /* Fill in the remaining client fields and put it in the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int ddcmon_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct ddcmon_data *) (client->data))-> + sysctl_id); + if ((err = i2c_detach_client(client))) { + printk + ("ddcmon.o: Client deregistration failed, client not detached.\n"); + return err; + } + kfree(client->data); + return 0; +} + +static void ddcmon_update_client(struct i2c_client *client) +{ + struct ddcmon_data *data = client->data; + int i, j; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > 300 * HZ) || + (jiffies < data->last_updated) || !data->valid) { + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + { + for (i=0; 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,538 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +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 | X | X |POL |1SHOT| */ +#define DS1621_REG_CONFIG_NVB 0x10 +#define DS1621_REG_CONFIG_POLARITY 0x02 +#define DS1621_REG_CONFIG_1SHOT 0x01 +#define DS1621_REG_CONFIG_DONE 0x80 + +/* Note: the done bit is always unset if continuous conversion is in progress. + We need to stop the continuous conversion or switch to single shot + before this bit becomes available! + */ + +/* The DS1621 registers */ +#define DS1621_REG_TEMP 0xAA /* word, RO */ +#define DS1621_REG_TEMP_OVER 0xA1 /* word, RW */ +#define DS1621_REG_TEMP_HYST 0xA2 /* word, RW -- it's a low temp trigger */ +#define DS1621_REG_CONF 0xAC /* byte, RW */ +#define DS1621_REG_TEMP_COUNTER 0xA8 /* byte, RO */ +#define DS1621_REG_TEMP_SLOPE 0xA9 /* byte, RO */ +#define DS1621_COM_START 0xEE /* no data */ +#define DS1621_COM_STOP 0x22 /* no data */ + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define TEMP_FROM_REG(val) ((((val & 0x7fff) >> 7) * 5) | \ + ((val & 0x8000)?-256:0)) +#define TEMP_TO_REG(val) (SENSORS_LIMIT((val<0 ? (0x200+((val)/5))<<7 : \ + (((val) + 2) / 5) << 7),0,0xffff)) +#define ALARMS_FROM_REG(val) ((val) & \ + (DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW)) +#define ITEMP_FROM_REG(val) ((((val & 0x7fff) >> 8)) | \ + ((val & 0x8000)?-256:0)) + +/* Each client has this additional data */ +struct ds1621_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 temp, temp_over, temp_hyst; /* Register values, word */ + u8 conf; /* Register encoding, combined */ + + char enable; /* !=0 if we're expected to restart the conversion */ + u8 temp_int, temp_counter, temp_slope; /* Register values, byte */ +}; + +static int ds1621_attach_adapter(struct i2c_adapter *adapter); +static int ds1621_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void ds1621_init_client(struct i2c_client *client); +static int ds1621_detach_client(struct i2c_client *client); + +static int ds1621_read_value(struct i2c_client *client, u8 reg); +static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value); +static void ds1621_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ds1621_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ds1621_enable(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ds1621_continuous(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ds1621_polarity(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ds1621_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver ds1621_driver = { + .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 Celsius * 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_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, ds1621_detect); +} + +/* This function is called by i2c_detect */ +static int ds1621_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, conf, temp; + struct i2c_client *new_client; + struct ds1621_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("ds1621.o: ds1621_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access ds1621_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct ds1621_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &ds1621_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. It is lousy. */ + if (kind < 0) { + /* The NVB bit should be low if no EEPROM write has been + requested during the latest 10ms, which is highly + improbable in our case. */ + conf = i2c_smbus_read_byte_data(new_client, + DS1621_REG_CONF); + if (conf & DS1621_REG_CONFIG_NVB) + goto ERROR1; + /* The 7 lowest bits of a temperature should always be 0. */ + temp = ds1621_read_value(new_client, + DS1621_REG_TEMP); + if (temp & 0x007f) + goto ERROR1; + temp = ds1621_read_value(new_client, + DS1621_REG_TEMP_HYST); + if (temp & 0x007f) + goto ERROR1; + temp = ds1621_read_value(new_client, + DS1621_REG_TEMP_OVER); + if (temp & 0x007f) + 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); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + ds1621_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int ds1621_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct ds1621_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("ds1621.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* All registers are word-sized, except for the configuration register. + DS1621 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int ds1621_read_value(struct i2c_client *client, u8 reg) +{ + if ((reg == DS1621_REG_CONF) || (reg == DS1621_REG_TEMP_COUNTER) + || (reg == DS1621_REG_TEMP_SLOPE)) + return i2c_smbus_read_byte_data(client, reg); + else + return swab16(i2c_smbus_read_word_data(client, reg)); +} + +/* All registers are word-sized, except for the configuration register. + DS1621 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if ( (reg == DS1621_COM_START) || (reg == DS1621_COM_STOP) ) + return i2c_smbus_write_byte(client, reg); + else + if ((reg == DS1621_REG_CONF) || (reg == DS1621_REG_TEMP_COUNTER) + || (reg == DS1621_REG_TEMP_SLOPE)) + return i2c_smbus_write_byte_data(client, reg, value); + else + return i2c_smbus_write_word_data(client, reg, swab16(value)); +} + +static void ds1621_init_client(struct i2c_client *client) +{ + int reg; + + reg = ds1621_read_value(client, DS1621_REG_CONF); + /* start the continuous 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 necessary */ + 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 continuous 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 Sun Feb 26 11:18:38 2006 @@ -0,0 +1,371 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +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); + + +/* possible natures */ +#define NATURE_UNKNOWN 0 +#define NATURE_VAIO 1 + +/* Size of EEPROM in bytes */ +#define EEPROM_SIZE 256 + +/* Each client has this additional data */ +struct eeprom_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + u8 valid; /* bitfield, bit!=0 if slice is valid */ + unsigned long last_updated[8]; /* In jiffies, 8 slices */ + + u8 data[EEPROM_SIZE]; /* Register values */ + u8 nature; +}; + + +static int eeprom_attach_adapter(struct i2c_adapter *adapter); +static int eeprom_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int eeprom_detach_client(struct i2c_client *client); + +#if 0 +static int eeprom_write_value(struct i2c_client *client, u8 reg, + u8 value); +#endif + +static void eeprom_contents(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void eeprom_update_client(struct i2c_client *client, u8 slice); + + +/* This is the driver that will be inserted */ +static struct i2c_driver eeprom_driver = { + .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_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, eeprom_detect); +} + +/* This function is called by i2c_detect */ +static 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; + + /* There are three ways we can read the EEPROM data: + (1) I2C block reads (faster, but unsupported by most adapters) + (2) Consecutive byte reads (100% overhead) + (3) Regular byte data reads (200% overhead) + The third method is not implemented by this driver because all + known adapters support at least the second. */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BYTE)) + goto ERROR0; + + if (!(data = kmalloc(sizeof(struct eeprom_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + memset(data->data, 0xff, EEPROM_SIZE); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &eeprom_driver; + new_client->flags = 0; + + /* prevent 24RF08 corruption */ + i2c_smbus_write_quick(new_client, 0); + + type_name = "eeprom"; + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, "EEPROM chip"); + data->valid = 0; + init_MUTEX(&data->update_lock); + data->nature = NATURE_UNKNOWN; + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + /* 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(new_client) == 'C' + && i2c_smbus_read_byte(new_client) == 'G' + && i2c_smbus_read_byte(new_client) == '-') { + printk(KERN_INFO "Vaio EEPROM detected, " + "enabling password protection\n"); + data->nature = NATURE_VAIO; + } + } + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, type_name, + eeprom_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + kfree(data); + ERROR0: + return err; +} + +static int eeprom_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct eeprom_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("eeprom.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +#if 0 +/* No writes yet (PAE) */ +static int eeprom_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} +#endif + +static void eeprom_update_client(struct i2c_client *client, u8 slice) +{ + struct eeprom_data *data = client->data; + int i, j; + + down(&data->update_lock); + + if (!(data->valid & (1 << slice)) + || (jiffies - data->last_updated[slice] > 300 * HZ) + || (jiffies < data->last_updated[slice])) { + +#ifdef DEBUG + printk(KERN_DEBUG "eeprom.o: Starting 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(KERN_DEBUG "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/f71805f.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/f71805f.c Sun Feb 26 11:18:39 2006 @@ -0,0 +1,757 @@ +/* + * f71805f.c - driver for the Fintek F71805F Super-I/O chip integrated + * hardware monitoring features + * Copyright (C) 2005 Jean Delvare + * + * The F71805F is a LPC Super-I/O chip made by Fintek. It integrates + * complete hardware monitoring features: voltage, fan and temperature + * sensors, and manual and automatic fan speed control. + * + * 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +/* ISA address is read from Super-I/O configuration space */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +SENSORS_INSMOD_1(f71805f); + +#define DRVNAME "f71805f" + +/* + * Super-I/O constants and functions + */ + +#define F71805F_LD_HWM 0x04 + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_DEVREV 0x22 /* Device revision */ +#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_FINTEK_ID 0x1934 +#define SIO_F71805F_ID 0x0406 + +static inline int +superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static int +superio_inw(int base, int reg) +{ + int val; + outb(reg++, base); + val = inb(base + 1) << 8; + outb(reg, base); + val |= inb(base + 1); + return val; +} + +static inline void +superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void +superio_enter(int base) +{ + outb(0x87, base); + outb(0x87, base); +} + +static inline void +superio_exit(int base) +{ + outb(0xaa, base); +} + +/* + * ISA constants + */ + +#define REGION_LENGTH 2 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 + +/* + * Registers + */ + +/* in nr from 0 to 8 (8-bit values) */ +#define F71805F_REG_IN(nr) (0x10 + (nr)) +#define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr)) +#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr)) +/* fan nr from 0 to 2 (12-bit values, two registers) */ +#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr)) +#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr)) +#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr)) +/* temp nr from 0 to 2 (8-bit values) */ +#define F71805F_REG_TEMP(nr) (0x1B + (nr)) +#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr)) +#define F71805F_REG_TEMP_HYST(nr) (0x55 + 2 * (nr)) +#define F71805F_REG_TEMP_MODE 0x01 + +#define F71805F_REG_START 0x00 +/* status nr from 0 to 2 */ +#define F71805F_REG_STATUS(nr) (0x36 + (nr)) + +/* + * Data structures and manipulation thereof + */ + +struct f71805f_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned long last_limits; /* In jiffies */ + + /* Register values */ + u8 in[9]; + u8 in_high[9]; + u8 in_low[9]; + u16 fan[3]; + u16 fan_low[3]; + u8 fan_enabled; /* Read once at init time */ + u8 temp[3]; + u8 temp_high[3]; + u8 temp_hyst[3]; + u8 temp_mode; + u8 alarms[3]; +}; + +static inline long in_from_reg(u8 reg) +{ + return (reg * 8); +} + +/* The 2 least significant bits are not used */ +static inline u8 in_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 2016) + return 0xfc; + return (((val + 16) / 32) << 2); +} + +/* in0 is downscaled by a factor 2 internally */ +static inline long in0_from_reg(u8 reg) +{ + return (reg * 16); +} + +static inline u8 in0_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 4032) + return 0xfc; + return (((val + 32) / 64) << 2); +} + +/* The 4 most significant bits are not used */ +static inline long fan_from_reg(u16 reg) +{ + reg &= 0xfff; + if (!reg || reg == 0xfff) + return 0; + return (1500000 / reg); +} + +static inline u16 fan_to_reg(long rpm) +{ + /* If the low limit is set below what the chip can measure, + store the largest possible 12-bit value in the registers, + so that no alarm will ever trigger. */ + if (rpm < 367) + return 0xfff; + return (1500000 / rpm); +} + +static inline u8 temp_to_reg(long val) +{ + if (val < 0) + val = 0; + else if (val > 0xff) + val = 0xff; + return val; +} + +/* + * Driver and client management + */ + +static u8 f71805f_read8(struct i2c_client *client, u8 reg) +{ + struct f71805f_data *data = client->data; + u8 val; + + down(&data->lock); + outb_p(reg, client->addr + ADDR_REG_OFFSET); + val = inb_p(client->addr + DATA_REG_OFFSET); + up(&data->lock); + + return val; +} + +static void f71805f_write8(struct i2c_client *client, u8 reg, u8 val) +{ + struct f71805f_data *data = client->data; + + down(&data->lock); + outb_p(reg, client->addr + ADDR_REG_OFFSET); + outb_p(val, client->addr + DATA_REG_OFFSET); + up(&data->lock); +} + +/* It is important to read the MSB first, because doing so latches the + value of the LSB, so we are sure both bytes belong to the same value. */ +static u16 f71805f_read16(struct i2c_client *client, u8 reg) +{ + struct f71805f_data *data = client->data; + u16 val; + + down(&data->lock); + outb_p(reg, client->addr + ADDR_REG_OFFSET); + val = inb_p(client->addr + DATA_REG_OFFSET) << 8; + outb_p(++reg, client->addr + ADDR_REG_OFFSET); + val |= inb_p(client->addr + DATA_REG_OFFSET); + up(&data->lock); + + return val; +} + +static void f71805f_write16(struct i2c_client *client, u8 reg, u16 val) +{ + struct f71805f_data *data = client->data; + + down(&data->lock); + outb_p(reg, client->addr + ADDR_REG_OFFSET); + outb_p(val >> 8, client->addr + DATA_REG_OFFSET); + outb_p(++reg, client->addr + ADDR_REG_OFFSET); + outb_p(val & 0xff, client->addr + DATA_REG_OFFSET); + up(&data->lock); +} + +static struct i2c_driver f71805f_driver; +static ctl_table f71805f_dir_table_template[]; + +static void f71805f_init_client(struct i2c_client *client) +{ + struct f71805f_data *data = client->data; + u8 reg; + int i; + + reg = f71805f_read8(client, F71805F_REG_START); + if ((reg & 0x41) != 0x01) { + printk(KERN_DEBUG DRVNAME ": Starting monitoring " + "operations\n"); + f71805f_write8(client, + F71805F_REG_START, (reg | 0x01) & ~0x40); + } + + /* Fan monitoring can be disabled. If it is, we won't be polling + the register values, and instead set the cache to return 0 RPM. */ + for (i = 0; i < 3; i++) { + reg = f71805f_read8(client, F71805F_REG_FAN_CTRL(i)); + if (!(reg & 0x80)) + data->fan_enabled |= (1 << i); + else + data->fan[i] = data->fan_low[i] = 0xfff; + } +} + +static int f71805f_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *client; + struct f71805f_data *data; + int err; + + if (!request_region(address, REGION_LENGTH, f71805f_driver.name)) { + err = -EBUSY; + goto exit; + } + + if (!(data = kmalloc(sizeof(struct f71805f_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit_release; + } + memset(data, 0, sizeof(struct f71805f_data)); + + /* Fill in the client fields */ + client = &data->client; + client->addr = address; + client->data = data; + client->adapter = adapter; + client->driver = &f71805f_driver; + strcpy(client->name, "F71805F chip"); + + init_MUTEX(&data->lock); + init_MUTEX(&data->update_lock); + + /* Tell the i2c core a new client has arrived */ + if ((err = i2c_attach_client(client))) + goto exit_free; + + /* Initialize the F71805F chip */ + f71805f_init_client(client); + + /* Register a new directory entry in /proc */ + err = i2c_register_entry(client, "f71805f", + f71805f_dir_table_template, THIS_MODULE); + if (err < 0) + goto exit_detach; + data->sysctl_id = err; + + return 0; + +exit_detach: + i2c_detach_client(client); +exit_free: + kfree(data); +exit_release: + release_region(address, REGION_LENGTH); +exit: + return err; +} + +static int f71805f_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, f71805f_detect); +} + +static int f71805f_detach_client(struct i2c_client *client) +{ + int err; + struct f71805f_data *data = client->data; + + i2c_deregister_entry(data->sysctl_id); + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR DRVNAME ": Client deregistration failed, " + "client not detached\n"); + return err; + } + + release_region(client->addr, REGION_LENGTH); + kfree(client->data); + + return 0; +} + +static void f71805f_update_client(struct i2c_client *client) +{ + struct f71805f_data *data = client->data; + int nr; + + down(&data->update_lock); + + /* Limit registers cache is refreshed after 60 seconds */ + if ((jiffies - data->last_limits > 60 * HZ) + || (jiffies < data->last_limits) + || !data->valid) { + for (nr = 0; nr < 9; nr++) { + data->in_high[nr] = f71805f_read8(client, + F71805F_REG_IN_HIGH(nr)); + data->in_low[nr] = f71805f_read8(client, + F71805F_REG_IN_LOW(nr)); + } + for (nr = 0; nr < 3; nr++) { + if (data->fan_enabled & (1 << nr)) + data->fan_low[nr] = f71805f_read16(client, + F71805F_REG_FAN_LOW(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->temp_high[nr] = f71805f_read8(client, + F71805F_REG_TEMP_HIGH(nr)); + data->temp_hyst[nr] = f71805f_read8(client, + F71805F_REG_TEMP_HYST(nr)); + } + data->temp_mode = f71805f_read8(client, F71805F_REG_TEMP_MODE); + + data->last_limits = jiffies; + } + + /* Measurement registers cache is refreshed after 1 second */ + if ((jiffies - data->last_updated > HZ) + || (jiffies < data->last_updated) + || !data->valid) { + for (nr = 0; nr < 9; nr++) { + data->in[nr] = f71805f_read8(client, + F71805F_REG_IN(nr)); + } + for (nr = 0; nr < 3; nr++) { + if (data->fan_enabled & (1 << nr)) + data->fan[nr] = f71805f_read16(client, + F71805F_REG_FAN(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->temp[nr] = f71805f_read8(client, + F71805F_REG_TEMP(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->alarms[nr] = f71805f_read8(client, + F71805F_REG_STATUS(nr)); + } + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +/* -- SENSORS SYSCTL START -- */ +#define F71805F_SYSCTL_IN0 1000 +#define F71805F_SYSCTL_IN1 1001 +#define F71805F_SYSCTL_IN2 1002 +#define F71805F_SYSCTL_IN3 1003 +#define F71805F_SYSCTL_IN4 1004 +#define F71805F_SYSCTL_IN5 1005 +#define F71805F_SYSCTL_IN6 1006 +#define F71805F_SYSCTL_IN7 1007 +#define F71805F_SYSCTL_IN8 1008 +#define F71805F_SYSCTL_FAN1 1101 +#define F71805F_SYSCTL_FAN2 1102 +#define F71805F_SYSCTL_FAN3 1103 +#define F71805F_SYSCTL_TEMP1 1201 +#define F71805F_SYSCTL_TEMP2 1202 +#define F71805F_SYSCTL_TEMP3 1203 +#define F71805F_SYSCTL_SENSOR1 1211 +#define F71805F_SYSCTL_SENSOR2 1212 +#define F71805F_SYSCTL_SENSOR3 1213 +#define F71805F_SYSCTL_ALARMS_IN 1090 +#define F71805F_SYSCTL_ALARMS_FAN 1190 +#define F71805F_SYSCTL_ALARMS_TEMP 1290 +/* -- SENSORS SYSCTL END -- */ + +static void f71805f_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + int nr = ctl_name - F71805F_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = in_from_reg(data->in_low[nr]); + results[1] = in_from_reg(data->in_high[nr]); + results[2] = in_from_reg(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag < 1) + return; + + down(&data->update_lock); + data->in_low[nr] = in_to_reg(results[0]); + f71805f_write8(client, F71805F_REG_IN_LOW(nr), + data->in_low[nr]); + + if (*nrels_mag >= 2) { + data->in_high[nr] = in_to_reg(results[1]); + f71805f_write8(client, F71805F_REG_IN_HIGH(nr), + data->in_high[nr]); + } + up(&data->update_lock); + } +} + +static void f71805f_in0(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = in0_from_reg(data->in_low[0]); + results[1] = in0_from_reg(data->in_high[0]); + results[2] = in0_from_reg(data->in[0]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag < 1) + return; + + down(&data->update_lock); + data->in_low[0] = in0_to_reg(results[0]); + f71805f_write8(client, F71805F_REG_IN_LOW(0), + data->in_low[0]); + + if (*nrels_mag >= 2) { + data->in_high[0] = in0_to_reg(results[1]); + f71805f_write8(client, F71805F_REG_IN_HIGH(0), + data->in_high[0]); + } + up(&data->update_lock); + } +} + +static void f71805f_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + int nr = ctl_name - F71805F_SYSCTL_FAN1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = fan_from_reg(data->fan_low[nr]); + results[1] = fan_from_reg(data->fan[nr]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag < 1) + return; + + down(&data->update_lock); + data->fan_low[nr] = fan_to_reg(results[0]); + f71805f_write16(client, F71805F_REG_FAN_LOW(nr), + data->fan_low[nr]); + up(&data->update_lock); + } +} + +static void f71805f_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + int nr = ctl_name - F71805F_SYSCTL_TEMP1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = data->temp_high[nr]; + results[1] = data->temp_hyst[nr]; + results[2] = data->temp[nr]; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag < 1) + return; + + down(&data->update_lock); + data->temp_high[nr] = temp_to_reg(results[0]); + f71805f_write8(client, F71805F_REG_TEMP_HIGH(nr), + data->temp_high[nr]); + + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = temp_to_reg(results[1]); + f71805f_write8(client, F71805F_REG_TEMP_HYST(nr), + data->temp_hyst[nr]); + } + up(&data->update_lock); + } +} + +static void f71805f_sensor(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + int nr = ctl_name - F71805F_SYSCTL_SENSOR1; + + f71805f_update_client(client); + results[0] = (data->temp_mode & (1 << nr)) ? 3 : 4; + *nrels_mag = 1; + } +} + +static void f71805f_alarms_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = data->alarms[0] + | ((data->alarms[1] & 0x01) << 8); + *nrels_mag = 1; + } +} + +static void f71805f_alarms_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = data->alarms[2] & 0x07; + *nrels_mag = 1; + } +} + +static void f71805f_alarms_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct f71805f_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + f71805f_update_client(client); + results[0] = (data->alarms[1] >> 3) & 0x07; + *nrels_mag = 1; + } +} + +static ctl_table f71805f_dir_table_template[] = { + { F71805F_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in0 }, + { F71805F_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in }, + { F71805F_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_fan }, + { F71805F_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_fan }, + { F71805F_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_fan }, + { F71805F_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_temp }, + { F71805F_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_temp }, + { F71805F_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_temp }, + { F71805F_SYSCTL_SENSOR1, "sensor1", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_sensor }, + { F71805F_SYSCTL_SENSOR2, "sensor2", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_sensor }, + { F71805F_SYSCTL_SENSOR3, "sensor3", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_sensor }, + { F71805F_SYSCTL_ALARMS_IN, "alarms_in", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_alarms_in }, + { F71805F_SYSCTL_ALARMS_FAN, "alarms_fan", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_alarms_fan }, + { F71805F_SYSCTL_ALARMS_TEMP, "alarms_temp", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_alarms_temp }, + { 0 } +}; + +static struct i2c_driver f71805f_driver = { + .name = "F71805F sensor driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = f71805f_attach_adapter, + .detach_client = f71805f_detach_client, +}; + +static int __init f71805f_find(int sioaddr, unsigned int *address) +{ + int err = -ENODEV; + u16 devid; + + superio_enter(sioaddr); + + devid = superio_inw(sioaddr, SIO_REG_MANID); + if (devid != SIO_FINTEK_ID) + goto exit; + + devid = superio_inw(sioaddr, SIO_REG_DEVID); + if (devid != SIO_F71805F_ID) { + printk(KERN_INFO DRVNAME ": Unsupported Fintek device, " + "skipping\n"); + goto exit; + } + + superio_select(sioaddr, F71805F_LD_HWM); + if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { + printk(KERN_WARNING DRVNAME ": Device not activated, " + "skipping\n"); + goto exit; + } + + *address = superio_inw(sioaddr, SIO_REG_ADDR); + if (*address == 0) { + printk(KERN_WARNING DRVNAME ": Base address not set, " + "skipping\n"); + goto exit; + } + + err = 0; + printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n", + *address, superio_inb(sioaddr, SIO_REG_DEVREV)); + +exit: + superio_exit(sioaddr); + return err; +} + +static int __init f71805f_init(void) +{ + printk("%s: Driver version %s (%s)\n", DRVNAME, LM_VERSION, LM_DATE); + + if (f71805f_find(0x2e, &normal_isa[0]) + && f71805f_find(0x4e, &normal_isa[0])) + return -ENODEV; + + return i2c_add_driver(&f71805f_driver); +} + +static void __exit f71805f_exit(void) +{ + i2c_del_driver(&f71805f_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("F71805F hardware monitoring driver"); + +module_init(f71805f_init); +module_exit(f71805f_exit); --- linux-old/drivers/sensors/fscher.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/fscher.c Sun Feb 26 11:18:39 2006 @@ -0,0 +1,731 @@ +/* + fscher.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (C) 2003, 2004 Reinhard Nissl + + 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 hermes chip, + module based on fscpos.c + Copyright (C) 2000 Hermann Jung + Copyright (C) 1998, 1999 Frodo Looijaard + and Philip Edelbrock +*/ + +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +#ifndef I2C_DRIVERID_FSCHER +#define I2C_DRIVERID_FSCHER 1046 +#endif + +/* 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(fscher); + +/* The FSCHER registers */ + +/* chip identification */ +#define FSCHER_REG_IDENT_0 0x00 +#define FSCHER_REG_IDENT_1 0x01 +#define FSCHER_REG_IDENT_2 0x02 +#define FSCHER_REG_REVISION 0x03 + +/* global control and status */ +#define FSCHER_REG_EVENT_STATE 0x04 +#define FSCHER_REG_CONTROL 0x05 + +/* watchdog */ +#define FSCHER_REG_WDOG_PRESET 0x28 +#define FSCHER_REG_WDOG_STATE 0x23 +#define FSCHER_REG_WDOG_CONTROL 0x21 + +/* fan 0 */ +#define FSCHER_REG_FAN0_MIN 0x55 +#define FSCHER_REG_FAN0_ACT 0x0e +#define FSCHER_REG_FAN0_STATE 0x0d +#define FSCHER_REG_FAN0_RIPPLE 0x0f + +/* fan 1 */ +#define FSCHER_REG_FAN1_MIN 0x65 +#define FSCHER_REG_FAN1_ACT 0x6b +#define FSCHER_REG_FAN1_STATE 0x62 +#define FSCHER_REG_FAN1_RIPPLE 0x6f + +/* fan 2 */ +#define FSCHER_REG_FAN2_MIN 0xb5 +#define FSCHER_REG_FAN2_ACT 0xbb +#define FSCHER_REG_FAN2_STATE 0xb2 +#define FSCHER_REG_FAN2_RIPPLE 0xbf + +/* voltage supervision */ +#define FSCHER_REG_VOLT_12 0x45 +#define FSCHER_REG_VOLT_5 0x42 +#define FSCHER_REG_VOLT_BATT 0x48 + +/* temperatures */ +/* sensor 0 */ +#define FSCHER_REG_TEMP0_ACT 0x64 +#define FSCHER_REG_TEMP0_STATE 0x71 + +/* sensor 1 */ +#define FSCHER_REG_TEMP1_ACT 0x32 +#define FSCHER_REG_TEMP1_STATE 0x81 + +/* sensor 2 */ +#define FSCHER_REG_TEMP2_ACT 0x35 +#define FSCHER_REG_TEMP2_STATE 0x91 + + + +/* Initial limits */ + +/* For each registered FSCHER, we need to keep some data in memory. That + data is pointed to by fscher_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new fscher client is + allocated. */ +struct fscher_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 revision; /* revision of chip */ + u8 global_event; /* global event status */ + u8 global_control; /* global control register */ + u8 watchdog[3]; /* watchdog */ + u8 volt[3]; /* 12, 5, battery current */ + u8 temp_act[3]; /* temperature */ + u8 temp_status[3]; /* status of sensor */ + u8 fan_act[3]; /* fans revolutions per second */ + u8 fan_status[3]; /* fan status */ + u8 fan_min[3]; /* fan min value for rps */ + u8 fan_ripple[3]; /* divider for rps */ +}; + + +static int fscher_attach_adapter(struct i2c_adapter *adapter); +static int fscher_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int fscher_detach_client(struct i2c_client *client); + +static int fscher_read_value(struct i2c_client *client, u8 register); +static int fscher_write_value(struct i2c_client *client, u8 register, + u8 value); +static void fscher_update_client(struct i2c_client *client); +static void fscher_init_client(struct i2c_client *client); + + +static void fscher_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void fscher_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscher_pwm_internal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results, + int nr, int reg_min); +static void fscher_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscher_fan_internal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results, + int nr, int reg_state, int res_ripple); +static void fscher_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscher_volt(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscher_wdog(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver fscher_driver = { + .name = "FSCHER sensor driver", + .id = I2C_DRIVERID_FSCHER, + .flags = I2C_DF_NOTIFY, + .attach_adapter = fscher_attach_adapter, + .detach_client = fscher_detach_client, +}; + +/* -- SENSORS SYSCTL START -- */ +#define FSCHER_SYSCTL_VOLT0 1000 /* 12 volt supply */ +#define FSCHER_SYSCTL_VOLT1 1001 /* 5 volt supply */ +#define FSCHER_SYSCTL_VOLT2 1002 /* batterie voltage */ +#define FSCHER_SYSCTL_FAN0 1101 /* state, ripple, actual value + fan 0 */ +#define FSCHER_SYSCTL_FAN1 1102 /* state, ripple, actual value + fan 1 */ +#define FSCHER_SYSCTL_FAN2 1103 /* state, ripple, actual value + fan 2 */ +#define FSCHER_SYSCTL_TEMP0 1201 /* state and value of sensor 0, + cpu die */ +#define FSCHER_SYSCTL_TEMP1 1202 /* state and value of sensor 1, + motherboard */ +#define FSCHER_SYSCTL_TEMP2 1203 /* state and value of sensor 2, + chassis */ +#define FSCHER_SYSCTL_PWM0 1301 /* min fan 0 */ +#define FSCHER_SYSCTL_PWM1 1302 /* min fan 1 */ +#define FSCHER_SYSCTL_PWM2 1303 /* min fan 2 */ +#define FSCHER_SYSCTL_REV 2000 /* revision */ +#define FSCHER_SYSCTL_EVENT 2001 /* global event status */ +#define FSCHER_SYSCTL_CONTROL 2002 /* global control byte */ +#define FSCHER_SYSCTL_WDOG 2003 /* watch dog preset, state and + control */ +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected FSCHER. 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 fscher_dir_table_template[] = { + {FSCHER_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_in}, + {FSCHER_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_in}, + {FSCHER_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_in}, + {FSCHER_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_temp}, + {FSCHER_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_temp}, + {FSCHER_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_temp}, + {FSCHER_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_volt}, + {FSCHER_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_volt}, + {FSCHER_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_volt}, + {FSCHER_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_fan}, + {FSCHER_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_fan}, + {FSCHER_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_fan}, + {FSCHER_SYSCTL_PWM0, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_pwm}, + {FSCHER_SYSCTL_PWM1, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_pwm}, + {FSCHER_SYSCTL_PWM2, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_pwm}, + {FSCHER_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscher_wdog}, + {0} +}; + +static int fscher_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, fscher_detect); +} + +int fscher_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct fscher_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("fscher.o: fscher_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 fscher_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct fscher_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &fscher_driver; + new_client->flags = 0; + + /* Do the remaining detection unless force or force_fscher parameter */ + if (kind < 0) { + if (fscher_read_value(new_client, FSCHER_REG_IDENT_0) != 0x48) /* 'H' */ + goto ERROR1; + if (fscher_read_value(new_client, FSCHER_REG_IDENT_1) != 0x45) /* 'E' */ + goto ERROR1; + if (fscher_read_value(new_client, FSCHER_REG_IDENT_2) != 0x52) /* 'R' */ + goto ERROR1; + } + + kind = fscher; + + type_name = "fscher"; + client_name = "fsc hermes chip"; + + /* Fill in the remaining client fields and put it into the + global list */ + strcpy(new_client->name, client_name); + 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, + fscher_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + fscher_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int fscher_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct fscher_data *) (client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("fscher.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +static int fscher_read_value(struct i2c_client *client, u8 reg) +{ +#ifdef DEBUG + printk("fscher: read reg 0x%02x\n",reg); +#endif + return i2c_smbus_read_byte_data(client, reg); +} + +static int fscher_write_value(struct i2c_client *client, u8 reg, u8 value) +{ +#ifdef DEBUG + printk("fscher: 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 FSCHER. */ +static void fscher_init_client(struct i2c_client *client) +{ + struct fscher_data *data = client->data; + + /* read revision from chip */ + data->revision = fscher_read_value(client,FSCHER_REG_REVISION); +} + +static void fscher_update_client(struct i2c_client *client) +{ + struct fscher_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 fscher update\n"); +#endif + data->temp_act[0] = fscher_read_value(client, FSCHER_REG_TEMP0_ACT); + data->temp_act[1] = fscher_read_value(client, FSCHER_REG_TEMP1_ACT); + data->temp_act[2] = fscher_read_value(client, FSCHER_REG_TEMP2_ACT); + data->temp_status[0] = fscher_read_value(client, FSCHER_REG_TEMP0_STATE); + data->temp_status[1] = fscher_read_value(client, FSCHER_REG_TEMP1_STATE); + data->temp_status[2] = fscher_read_value(client, FSCHER_REG_TEMP2_STATE); + + data->volt[0] = fscher_read_value(client, FSCHER_REG_VOLT_12); + data->volt[1] = fscher_read_value(client, FSCHER_REG_VOLT_5); + data->volt[2] = fscher_read_value(client, FSCHER_REG_VOLT_BATT); + + data->fan_act[0] = fscher_read_value(client, FSCHER_REG_FAN0_ACT); + data->fan_act[1] = fscher_read_value(client, FSCHER_REG_FAN1_ACT); + data->fan_act[2] = fscher_read_value(client, FSCHER_REG_FAN2_ACT); + data->fan_status[0] = fscher_read_value(client, FSCHER_REG_FAN0_STATE); + data->fan_status[1] = fscher_read_value(client, FSCHER_REG_FAN1_STATE); + data->fan_status[2] = fscher_read_value(client, FSCHER_REG_FAN2_STATE); + data->fan_min[0] = fscher_read_value(client, FSCHER_REG_FAN0_MIN); + data->fan_min[1] = fscher_read_value(client, FSCHER_REG_FAN1_MIN); + data->fan_min[2] = fscher_read_value(client, FSCHER_REG_FAN2_MIN); + data->fan_ripple[0] = fscher_read_value(client, FSCHER_REG_FAN0_RIPPLE); + data->fan_ripple[1] = fscher_read_value(client, FSCHER_REG_FAN1_RIPPLE); + data->fan_ripple[2] = fscher_read_value(client, FSCHER_REG_FAN2_RIPPLE); + + data->watchdog[0] = fscher_read_value(client, FSCHER_REG_WDOG_PRESET); + data->watchdog[1] = fscher_read_value(client, FSCHER_REG_WDOG_STATE); + data->watchdog[2] = fscher_read_value(client, FSCHER_REG_WDOG_CONTROL); + + data->global_event = fscher_read_value(client, FSCHER_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 fscher_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct fscher_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + fscher_update_client(client); + switch(ctl_name) { + case FSCHER_SYSCTL_REV: + results[0] = data->revision ; + break; + case FSCHER_SYSCTL_EVENT: + /* bits 6, 5 and 2 are reserved => mask with 0x9b */ + results[0] = data->global_event & 0x9b; + break; + case FSCHER_SYSCTL_CONTROL: + results[0] = data->global_control & 0x01; + break; + default: + printk("fscher: 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 == FSCHER_SYSCTL_CONTROL) && (*nrels_mag >= 1)) { + data->global_control = (results[0] & 0x01); + printk("fscher: writing 0x%02x to global_control\n", + data->global_control); + fscher_write_value(client,FSCHER_REG_CONTROL, + data->global_control); + } + else + printk("fscher: writing to chip not supported\n"); + } +} + + +#define TEMP_FROM_REG(val) (val-128) + +void fscher_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct fscher_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + fscher_update_client(client); + switch(ctl_name) { + case FSCHER_SYSCTL_TEMP0: + results[0] = data->temp_status[0] & 0x03; + results[1] = TEMP_FROM_REG(data->temp_act[0]); + break; + case FSCHER_SYSCTL_TEMP1: + results[0] = data->temp_status[1] & 0x03; + results[1] = TEMP_FROM_REG(data->temp_act[1]); + break; + case FSCHER_SYSCTL_TEMP2: + results[0] = data->temp_status[2] & 0x03; + results[1] = TEMP_FROM_REG(data->temp_act[2]); + break; + default: + printk("fscher: 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 FSCHER_SYSCTL_TEMP0: + data->temp_status[0] = (data->temp_status[0] & ~0x02) + | (results[0] & 0x02); + printk("fscher: writing value 0x%02x to temp0_status\n", + data->temp_status[0]); + fscher_write_value(client, FSCHER_REG_TEMP0_STATE, + data->temp_status[0] & 0x02); + break; + case FSCHER_SYSCTL_TEMP1: + data->temp_status[1] = (data->temp_status[1] & ~0x02) + | (results[0] & 0x02); + printk("fscher: writing value 0x%02x to temp1_status\n", + data->temp_status[1]); + fscher_write_value(client, FSCHER_REG_TEMP1_STATE, + data->temp_status[1] & 0x02); + break; + case FSCHER_SYSCTL_TEMP2: + data->temp_status[2] = (data->temp_status[2] & ~0x02) + | (results[0] & 0x02); + printk("fscher: writing value 0x%02x to temp2_status\n", + data->temp_status[2]); + fscher_write_value(client, FSCHER_REG_TEMP2_STATE, + data->temp_status[2] & 0x02); + break; + default: + printk("fscher: ctl_name %d not supported\n",ctl_name); + } + } + else + printk("fscher: writing to chip not supported\n"); + } +} + +/* + * The final conversion is specified in sensors.conf, as it depends on + * mainboard specific values. We export the registers contents as + * pseudo-hundredths-of-Volts (range 0V - 2.55V). Not that it makes much + * sense per se, but it minimizes the conversions count and keeps the + * values within a usual range. + */ +void fscher_volt(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct fscher_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + fscher_update_client(client); + switch(ctl_name) { + case FSCHER_SYSCTL_VOLT0: + results[0] = data->volt[0]; + break; + case FSCHER_SYSCTL_VOLT1: + results[0] = data->volt[1]; + break; + case FSCHER_SYSCTL_VOLT2: + results[0] = data->volt[2]; + break; + default: + printk("fscher: ctl_name %d not supported\n", ctl_name); + *nrels_mag = 0; + return; + } + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + printk("fscher: writing to chip not supported\n"); + } +} + +void fscher_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + + switch(ctl_name) { + case FSCHER_SYSCTL_PWM0: + fscher_pwm_internal(client,operation,ctl_name,nrels_mag,results, + 0,FSCHER_REG_FAN0_MIN); + break; + case FSCHER_SYSCTL_PWM1: + fscher_pwm_internal(client,operation,ctl_name,nrels_mag,results, + 1,FSCHER_REG_FAN1_MIN); + break; + case FSCHER_SYSCTL_PWM2: + fscher_pwm_internal(client,operation,ctl_name,nrels_mag,results, + 2,FSCHER_REG_FAN2_MIN); + break; + default: + printk("fscher: illegal pwm nr %d\n",ctl_name); + } +} + +void fscher_pwm_internal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results, + int nr, int reg_min) +{ + struct fscher_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + fscher_update_client(client); + results[0] = data->fan_min[nr]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if(*nrels_mag >= 1) { + data->fan_min[nr] = results[0]; + printk("fscher: writing value 0x%02x to fan%d_min\n", + data->fan_min[nr],nr); + fscher_write_value(client,reg_min,data->fan_min[nr]); + } + } +} + +void fscher_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + switch(ctl_name) { + case FSCHER_SYSCTL_FAN0: + fscher_fan_internal(client,operation,ctl_name,nrels_mag,results, + 0,FSCHER_REG_FAN0_STATE, FSCHER_REG_FAN0_RIPPLE); + break; + case FSCHER_SYSCTL_FAN1: + fscher_fan_internal(client,operation,ctl_name,nrels_mag,results, + 1,FSCHER_REG_FAN1_STATE, FSCHER_REG_FAN1_RIPPLE); + break; + case FSCHER_SYSCTL_FAN2: + fscher_fan_internal(client,operation,ctl_name,nrels_mag,results, + 2,FSCHER_REG_FAN2_STATE, FSCHER_REG_FAN2_RIPPLE); + break; + default: + printk("fscher: illegal fan nr %d\n",ctl_name); + } +} + +#define RPM_FROM_REG(val) (val*60) + +void fscher_fan_internal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results, + int nr, int reg_state, int reg_ripple) +{ + struct fscher_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + fscher_update_client(client); + results[0] = data->fan_status[nr] & 0x04; + results[1] = data->fan_ripple[nr] & 0x03; + results[2] = RPM_FROM_REG(data->fan_act[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if(*nrels_mag >= 1) { + data->fan_status[nr] = results[0] & 0x04; + printk("fscher: writing value 0x%02x to fan%d_status\n", + data->fan_status[nr],nr); + fscher_write_value(client,reg_state,data->fan_status[nr]); + } + if(*nrels_mag >= 2) { + if((results[1] & 0x03) == 0) { + printk("fscher: fan%d ripple 0 not allowed\n",nr); + return; + } + data->fan_ripple[nr] = results[1] & 0x03; + printk("fscher: writing value 0x%02x to fan%d_ripple\n", + data->fan_ripple[nr],nr); + fscher_write_value(client,reg_ripple,data->fan_ripple[nr]); + } + } +} + +void fscher_wdog(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct fscher_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + fscher_update_client(client); + results[0] = data->watchdog[0] ; + results[1] = data->watchdog[1] & 0x02; + results[2] = data->watchdog[2] & 0xd0; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->watchdog[0] = results[0] & 0xff; + printk("fscher: writing value 0x%02x to wdog_preset\n", + data->watchdog[0]); + fscher_write_value(client,FSCHER_REG_WDOG_PRESET,data->watchdog[0]); + } + if (*nrels_mag >= 2) { + data->watchdog[1] = results[1] & 0x02; + printk("fscher: writing value 0x%02x to wdog_state\n", + data->watchdog[1]); + fscher_write_value(client,FSCHER_REG_WDOG_STATE,data->watchdog[1]); + } + if (*nrels_mag >= 3) { + data->watchdog[2] = results[2] & 0xf0; + printk("fscher: writing value 0x%02x to wdog_control\n", + data->watchdog[2]); + fscher_write_value(client,FSCHER_REG_WDOG_CONTROL,data->watchdog[2]); + } + } +} + +static int __init sm_fscher_init(void) +{ + printk("fscher.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&fscher_driver); +} + +static void __exit sm_fscher_exit(void) +{ + i2c_del_driver(&fscher_driver); +} + + + +MODULE_AUTHOR("Reinhard Nissl based on work from Hermann" + " Jung , Frodo Looijaard and" + " Philip Edelbrock "); +MODULE_DESCRIPTION("fujitsu siemens hermes chip driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_fscher_init); +module_exit(sm_fscher_exit); --- linux-old/drivers/sensors/fscpos.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/fscpos.c Sun Feb 26 11:18:39 2006 @@ -0,0 +1,686 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(fscpos); + +/* The FSCPOS registers */ + +/* chip identification */ +#define FSCPOS_REG_IDENT_0 0x00 +#define FSCPOS_REG_IDENT_1 0x01 +#define FSCPOS_REG_IDENT_2 0x02 +#define FSCPOS_REG_REVISION 0x03 + +/* global control and status */ +#define FSCPOS_REG_EVENT_STATE 0x04 +#define FSCPOS_REG_CONTROL 0x05 + +/* watchdog */ +#define FSCPOS_REG_WDOG_PRESET 0x28 +#define FSCPOS_REG_WDOG_STATE 0x23 +#define FSCPOS_REG_WDOG_CONTROL 0x21 + +/* fan 0 */ +#define FSCPOS_REG_FAN0_MIN 0x55 +#define FSCPOS_REG_FAN0_ACT 0x0e +#define FSCPOS_REG_FAN0_STATE 0x0d +#define FSCPOS_REG_FAN0_RIPPLE 0x0f + +/* fan 1 */ +#define FSCPOS_REG_FAN1_MIN 0x65 +#define FSCPOS_REG_FAN1_ACT 0x6b +#define FSCPOS_REG_FAN1_STATE 0x62 +#define FSCPOS_REG_FAN1_RIPPLE 0x6f + +/* fan 2 */ +/* min speed fan2 not supported */ +#define FSCPOS_REG_FAN2_ACT 0xab +#define FSCPOS_REG_FAN2_STATE 0xa2 +#define FSCPOS_REG_FAN2_RIPPLE 0x0af + +/* voltage supervision */ +#define FSCPOS_REG_VOLT_12 0x45 +#define FSCPOS_REG_VOLT_5 0x42 +#define FSCPOS_REG_VOLT_BATT 0x48 + +/* temperatures */ +/* sensor 0 */ +#define FSCPOS_REG_TEMP0_ACT 0x64 +#define FSCPOS_REG_TEMP0_STATE 0x71 + +/* sensor 1 */ +#define FSCPOS_REG_TEMP1_ACT 0x32 +#define FSCPOS_REG_TEMP1_STATE 0x81 + +/* sensor 2 */ +#define FSCPOS_REG_TEMP2_ACT 0x35 +#define FSCPOS_REG_TEMP2_STATE 0x91 + + + + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ + +#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val),0,255)) +#define IN_FROM_REG(val,nr) (val) + +/* Initial limits */ + +/* For each registered FSCPOS, we need to keep some data in memory. That + data is pointed to by fscpos_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new fscpos client is + allocated. */ +struct fscpos_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 revision; /* revision of chip */ + u8 global_event; /* global event status */ + u8 global_control; /* global control register */ + u8 watchdog[3]; /* watchdog */ + u8 volt[3]; /* 12, 5, battery current */ + u8 temp_act[3]; /* temperature */ + u8 temp_status[3]; /* status of sensor */ + u8 fan_act[3]; /* fans revolutions per second */ + u8 fan_status[3]; /* fan status */ + u8 fan_min[3]; /* fan min value for rps */ + u8 fan_ripple[3]; /* divider for rps */ +}; + + +static int fscpos_attach_adapter(struct i2c_adapter *adapter); +static int fscpos_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int fscpos_detach_client(struct i2c_client *client); + +static int fscpos_read_value(struct i2c_client *client, u8 register); +static int fscpos_write_value(struct i2c_client *client, u8 register, + u8 value); +static void fscpos_update_client(struct i2c_client *client); +static void fscpos_init_client(struct i2c_client *client); + + +static void fscpos_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void fscpos_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscpos_fan_internal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results, + int nr, int reg_state, int reg_min, int res_ripple); +static void fscpos_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscpos_volt(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscpos_wdog(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver fscpos_driver = { + .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); +} + +static int fscpos_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct fscpos_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("fscpos.o: fscpos_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access fscpos_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct fscpos_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &fscpos_driver; + new_client->flags = 0; + + /* Do the remaining detection unless force or force_fscpos parameter */ + if (kind < 0) { + if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_0) != 0x50) + goto ERROR1; + if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_1) != 0x45) + goto ERROR1; + if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_2) != 0x47) + goto ERROR1; + } + + kind = fscpos; + + type_name = "fscpos"; + client_name = "fsc poseidon chip"; + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + fscpos_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int fscpos_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct fscpos_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("fscpos.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +static int fscpos_read_value(struct i2c_client *client, u8 reg) +{ +#ifdef DEBUG + printk("fscpos: read reg 0x%02x\n",reg); +#endif + return i2c_smbus_read_byte_data(client, reg); +} + +static int fscpos_write_value(struct i2c_client *client, u8 reg, u8 value) +{ +#ifdef DEBUG + printk("fscpos: write reg 0x%02x, val 0x%02x\n",reg, value); +#endif + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new FSCPOS. */ +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 Sun Feb 26 11:18:39 2006 @@ -0,0 +1,911 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(fscscy); + +/* The FSCSCY registers */ + +/* chip identification */ +#define FSCSCY_REG_IDENT_0 0x00 +#define FSCSCY_REG_IDENT_1 0x01 +#define FSCSCY_REG_IDENT_2 0x02 +#define FSCSCY_REG_REVISION 0x03 + +/* global control and status */ +#define FSCSCY_REG_EVENT_STATE 0x04 +#define FSCSCY_REG_CONTROL 0x05 + +/* watchdog */ +#define FSCSCY_REG_WDOG_PRESET 0x28 +#define FSCSCY_REG_WDOG_STATE 0x23 +#define FSCSCY_REG_WDOG_CONTROL 0x21 + +/* +** Fan definitions +** +** _RPMMIN: Minimum speed. Can be set via interface, but only for three of the fans +** FAN1_RPMMIN is wired to Fan 0 (CPU Fans) +** FAN4_RPMMIN is wired to Fan 2 (PS Fans ??) +** FAN5_RPMMIN is wired to Fan 3 (AUX Fans ??) +** _ACT: Actual Fan Speed +** _STATE: Fan status register +** _RIPPLE: Fan speed multiplier +*/ + +/* fan 0 */ +#define FSCSCY_REG_FAN0_RPMMIN 0x65 +#define FSCSCY_REG_FAN0_ACT 0x6b +#define FSCSCY_REG_FAN0_STATE 0x62 +#define FSCSCY_REG_FAN0_RIPPLE 0x6f + +/* fan 1 */ +#define FSCSCY_REG_FAN1_RPMMIN FSCSCY_REG_FAN0_RPMMIN +#define FSCSCY_REG_FAN1_ACT 0x6c +#define FSCSCY_REG_FAN1_STATE 0x61 +#define FSCSCY_REG_FAN1_RIPPLE 0x6f + +/* fan 2 */ +#define FSCSCY_REG_FAN2_RPMMIN 0x55 +#define FSCSCY_REG_FAN2_ACT 0x0e +#define FSCSCY_REG_FAN2_STATE 0x0d +#define FSCSCY_REG_FAN2_RIPPLE 0x0f + +/* fan 3 */ +#define FSCSCY_REG_FAN3_RPMMIN 0xa5 +#define FSCSCY_REG_FAN3_ACT 0xab +#define FSCSCY_REG_FAN3_STATE 0xa2 +#define FSCSCY_REG_FAN3_RIPPLE 0xaf + +/* fan 4 */ +#define FSCSCY_REG_FAN4_RPMMIN FSCSCY_REG_FAN2_RPMMIN +#define FSCSCY_REG_FAN4_ACT 0x5c +#define FSCSCY_REG_FAN4_STATE 0x52 +#define FSCSCY_REG_FAN4_RIPPLE 0x0f + +/* fan 5 */ +#define FSCSCY_REG_FAN5_RPMMIN FSCSCY_REG_FAN3_RPMMIN +#define FSCSCY_REG_FAN5_ACT 0xbb +#define FSCSCY_REG_FAN5_STATE 0xb2 +#define FSCSCY_REG_FAN5_RIPPLE 0xbf + +/* voltage supervision */ +#define FSCSCY_REG_VOLT_12 0x45 +#define FSCSCY_REG_VOLT_5 0x42 +#define FSCSCY_REG_VOLT_BATT 0x48 + +/* temperatures */ +/* sensor 0 */ +#define FSCSCY_REG_TEMP0_ACT 0x64 +#define FSCSCY_REG_TEMP0_STATE 0x71 +#define FSCSCY_REG_TEMP0_LIM 0x76 + +/* sensor 1 */ +#define FSCSCY_REG_TEMP1_ACT 0xD0 +#define FSCSCY_REG_TEMP1_STATE 0xD1 +#define FSCSCY_REG_TEMP1_LIM 0xD6 + +/* sensor 2 */ +#define FSCSCY_REG_TEMP2_ACT 0x32 +#define FSCSCY_REG_TEMP2_STATE 0x81 +#define FSCSCY_REG_TEMP2_LIM 0x86 + +/* sensor3 */ +#define FSCSCY_REG_TEMP3_ACT 0x35 +#define FSCSCY_REG_TEMP3_STATE 0x91 +#define FSCSCY_REG_TEMP3_LIM 0x96 + +/* PCI Load */ +#define FSCSCY_REG_PCILOAD 0x1a + +/* Intrusion Sensor */ +#define FSCSCY_REG_INTR_STATE 0x13 +#define FSCSCY_REG_INTR_CTRL 0x12 + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ + +#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val),0,255)) +#define IN_FROM_REG(val,nr) (val) + +/* Initial limits */ + +/* For each registered FSCSCY, we need to keep some data in memory. That + data is pointed to by fscscy_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new fscscy client is + allocated. */ +struct fscscy_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 revision; /* revision of chip */ + u8 global_event; /* global event status */ + u8 global_control; /* global control register */ + u8 watchdog[3]; /* watchdog */ + u8 volt[3]; /* 12, 5, battery current */ + u8 volt_min[3]; /* minimum voltages over module "lifetime" */ + u8 volt_max[3]; /* maximum voltages over module "lifetime" */ + u8 temp_act[4]; /* temperature */ + u8 temp_status[4]; /* status of temp. sensor */ + u8 temp_lim[4]; /* limit temperature of temp. sensor */ + u8 temp_min[4]; /* minimum of temp. sensor, this is just calculated by the module */ + u8 temp_max[4]; /* maximum of temp. sensor, this is just calculsted by the module */ + u8 fan_act[6]; /* fans revolutions per second */ + u8 fan_status[6]; /* fan status */ + u8 fan_rpmmin[6]; /* fan min value for rps */ + u8 fan_ripple[6]; /* divider for rps */ + u8 fan_min[6]; /* minimum RPM over module "lifetime" */ + u8 fan_max[6]; /* maximum RPM over module "lifetime" */ + u8 pciload; /* PCILoad value */ + u8 intr_status; /* Intrusion Status */ + u8 intr_control; /* Intrusion Control */ +}; + + +static int fscscy_attach_adapter(struct i2c_adapter *adapter); +static int fscscy_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int fscscy_detach_client(struct i2c_client *client); + +static int fscscy_read_value(struct i2c_client *client, u8 register); +static int fscscy_write_value(struct i2c_client *client, u8 register, + u8 value); +static void fscscy_update_client(struct i2c_client *client); +static void fscscy_init_client(struct i2c_client *client); + + +static void fscscy_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void fscscy_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscscy_fan_internal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results, + int nr, int reg_state, int reg_min, int res_ripple); +static void fscscy_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscscy_volt(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscscy_wdog(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscscy_pciload(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void fscscy_intrusion(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver fscscy_driver = { + .name = "FSCSCY sensor driver", + .id = I2C_DRIVERID_FSCSCY, + .flags = I2C_DF_NOTIFY, + .attach_adapter = fscscy_attach_adapter, + .detach_client = fscscy_detach_client, +}; + +/* The /proc/sys entries */ + +/* -- SENSORS SYSCTL START -- */ +#define FSCSCY_SYSCTL_VOLT0 1000 /* 12 volt supply */ +#define FSCSCY_SYSCTL_VOLT1 1001 /* 5 volt supply */ +#define FSCSCY_SYSCTL_VOLT2 1002 /* batterie voltage*/ +#define FSCSCY_SYSCTL_FAN0 1101 /* state, min, ripple, actual value fan 0 */ +#define FSCSCY_SYSCTL_FAN1 1102 /* state, min, ripple, actual value fan 1 */ +#define FSCSCY_SYSCTL_FAN2 1103 /* state, min, ripple, actual value fan 2 */ +#define FSCSCY_SYSCTL_FAN3 1104 /* state, min, ripple, actual value fan 3 */ +#define FSCSCY_SYSCTL_FAN4 1105 /* state, min, ripple, actual value fan 4 */ +#define FSCSCY_SYSCTL_FAN5 1106 /* state, min, ripple, actual value fan 5 */ +#define FSCSCY_SYSCTL_TEMP0 1201 /* state and value of sensor 0, cpu die */ +#define FSCSCY_SYSCTL_TEMP1 1202 /* state and value of sensor 1, motherboard */ +#define FSCSCY_SYSCTL_TEMP2 1203 /* state and value of sensor 2, chassis */ +#define FSCSCY_SYSCTL_TEMP3 1204 /* state and value of sensor 3, chassis */ +#define FSCSCY_SYSCTL_REV 2000 /* Revision */ +#define FSCSCY_SYSCTL_EVENT 2001 /* global event status */ +#define FSCSCY_SYSCTL_CONTROL 2002 /* global control byte */ +#define FSCSCY_SYSCTL_WDOG 2003 /* state, min, ripple, actual value fan 2 */ +#define FSCSCY_SYSCTL_PCILOAD 2004 /* PCILoad value */ +#define FSCSCY_SYSCTL_INTRUSION 2005 /* state, control for intrusion sensor */ + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected FSCSCY. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table fscscy_dir_table_template[] = { + {FSCSCY_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_in}, + {FSCSCY_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_in}, + {FSCSCY_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_in}, + {FSCSCY_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_temp}, + {FSCSCY_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_temp}, + {FSCSCY_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_temp}, + {FSCSCY_SYSCTL_TEMP3, "temp4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_temp}, + {FSCSCY_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_volt}, + {FSCSCY_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_volt}, + {FSCSCY_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_volt}, + {FSCSCY_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_fan}, + {FSCSCY_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_fan}, + {FSCSCY_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_fan}, + {FSCSCY_SYSCTL_FAN3, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_fan}, + {FSCSCY_SYSCTL_FAN4, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_fan}, + {FSCSCY_SYSCTL_FAN5, "fan6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_fan}, + {FSCSCY_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_wdog}, + {FSCSCY_SYSCTL_PCILOAD, "pciload", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_pciload}, + {FSCSCY_SYSCTL_INTRUSION, "intrusion", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &fscscy_intrusion}, + {0} +}; + +static int fscscy_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, fscscy_detect); +} + +int fscscy_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct fscscy_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("fscscy.o: fscscy_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access fscscy_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct fscscy_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &fscscy_driver; + new_client->flags = 0; + + /* Do the remaining detection unless force or force_fscscy parameter */ + if (kind < 0) { + if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_0) != 0x53) + goto ERROR1; + if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_1) != 0x43) + goto ERROR1; + if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_2) != 0x59) + goto ERROR1; + } + + kind = fscscy; + + type_name = "fscscy"; + client_name = "fsc scylla chip"; + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + fscscy_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int fscscy_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct fscscy_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("fscscy.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +static int fscscy_read_value(struct i2c_client *client, u8 reg) +{ +#ifdef DEBUG + printk("fscscy: read reg 0x%02x\n",reg); +#endif + return i2c_smbus_read_byte_data(client, reg); +} + +static int fscscy_write_value(struct i2c_client *client, u8 reg, u8 value) +{ +#ifdef DEBUG + printk("fscscy: write reg 0x%02x, val 0x%02x\n",reg, value); +#endif + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new FSCSCY. */ +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 Sun Feb 26 11:18:39 2006 @@ -0,0 +1,988 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2d, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_2(gl518sm_r00, gl518sm_r80); + +/* Defining this will enable debug messages for the voltage iteration + code used with rev 0 ICs */ +#undef DEBUG_VIN + +/* Many GL518 constants specified below */ + +/* The GL518 registers */ +#define GL518_REG_CHIP_ID 0x00 +#define GL518_REG_REVISION 0x01 +#define GL518_REG_VENDOR_ID 0x02 +#define GL518_REG_CONF 0x03 +#define GL518_REG_TEMP 0x04 +#define GL518_REG_TEMP_OVER 0x05 +#define GL518_REG_TEMP_HYST 0x06 +#define GL518_REG_FAN_COUNT 0x07 +#define GL518_REG_FAN_LIMIT 0x08 +#define GL518_REG_VIN1_LIMIT 0x09 +#define GL518_REG_VIN2_LIMIT 0x0a +#define GL518_REG_VIN3_LIMIT 0x0b +#define GL518_REG_VDD_LIMIT 0x0c +#define GL518_REG_VIN3 0x0d +#define GL518_REG_MISC 0x0f +#define GL518_REG_ALARM 0x10 +#define GL518_REG_MASK 0x11 +#define GL518_REG_INT 0x12 +#define GL518_REG_VIN2 0x13 +#define GL518_REG_VIN1 0x14 +#define GL518_REG_VDD 0x15 + + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ + +#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-5:(val)+5) / 10)+119),\ + 0,255)) +#define TEMP_FROM_REG(val) (((val) - 119) * 10) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((960000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) \ + ( (val)==0 ? 0 : (val)==255 ? 0 : (960000/((val)*(div))) ) + +#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*10+8)/19),0,255)) +#define IN_FROM_REG(val) (((val)*19)/10) + +#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*10+11)/23),0,255)) +#define VDD_FROM_REG(val) (((val)*23)/10) + +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) +#define DIV_FROM_REG(val) (1 << (val)) + +#define ALARMS_FROM_REG(val) val + +#define BEEP_ENABLE_TO_REG(val) ((val)?0:1) +#define BEEP_ENABLE_FROM_REG(val) ((val)?0:1) + +#define BEEPS_TO_REG(val) ((val) & 0x7f) +#define BEEPS_FROM_REG(val) ((val) & 0x7f) + +/* Each client has this additional data */ +struct gl518_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + + int iterate_lock; + int quit_thread; + struct task_struct *thread; + wait_queue_head_t wq; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned long last_updated_v00; + /* In jiffies (used only by rev00 chips) */ + + u8 voltage[4]; /* Register values; [0] = VDD */ + u8 voltage_min[4]; /* Register values; [0] = VDD */ + u8 voltage_max[4]; /* Register values; [0] = VDD */ + u8 iter_voltage[4]; /* Register values; [0] = VDD */ + u8 fan[2]; + u8 fan_min[2]; + u8 temp; /* Register values */ + u8 temp_over; /* Register values */ + u8 temp_hyst; /* Register values */ + u8 alarms, beeps; /* Register value */ + u8 alarm_mask; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u8 beep_enable; /* Boolean */ + u8 iterate; /* Voltage iteration mode */ +}; + +static int gl518_attach_adapter(struct i2c_adapter *adapter); +static int gl518_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void gl518_init_client(struct i2c_client *client); +static int gl518_detach_client(struct i2c_client *client); + +static int gl518_read_value(struct i2c_client *client, u8 reg); +static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value); +static void gl518_update_client(struct i2c_client *client); + +static void gl518_update_client_rev00(struct i2c_client *client); +static void gl518_update_iterate(struct i2c_client *client); + +static void gl518_vin(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_beep(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_fan1off(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl518_iterate(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +/* This is the driver that will be inserted */ +static struct i2c_driver gl518_driver = { + .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 Celsius * 10 */ +#define GL518_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define GL518_SYSCTL_ALARMS 2001 /* bitvector */ +#define GL518_SYSCTL_BEEP 2002 /* bitvector */ +#define GL518_SYSCTL_FAN1OFF 2003 +#define GL518_SYSCTL_ITERATE 2004 + +#define GL518_ALARM_VDD 0x01 +#define GL518_ALARM_VIN1 0x02 +#define GL518_ALARM_VIN2 0x04 +#define GL518_ALARM_VIN3 0x08 +#define GL518_ALARM_TEMP 0x10 +#define GL518_ALARM_FAN1 0x20 +#define GL518_ALARM_FAN2 0x40 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected GL518. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table gl518_dir_table_template[] = { + {GL518_SYSCTL_VIN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_vin}, + {GL518_SYSCTL_VIN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_vin}, + {GL518_SYSCTL_VIN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_vin}, + {GL518_SYSCTL_VDD, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_vin}, + {GL518_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_fan}, + {GL518_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_fan}, + {GL518_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_temp}, + {GL518_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_fan_div}, + {GL518_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_alarms}, + {GL518_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_beep}, + {GL518_SYSCTL_FAN1OFF, "fan1off", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_fan1off}, + {GL518_SYSCTL_ITERATE, "iterate", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl518_iterate}, + {0} +}; + +/* I choose here for semi-static GL518SM allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +#define MAX_GL518_NR 4 +static struct i2c_client *gl518_list[MAX_GL518_NR]; + +static int gl518_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, gl518_detect); +} + +static int gl518_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct gl518_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("gl518sm.o: gl518_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access gl518_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct gl518_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &gl518_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if ( + (gl518_read_value(new_client, GL518_REG_CHIP_ID) != + 0x80) + || (gl518_read_value(new_client, GL518_REG_CONF) & + 0x80)) goto ERROR1; + } + + /* Determine the chip type. */ + if (kind <= 0) { + i = gl518_read_value(new_client, GL518_REG_REVISION); + if (i == 0x00) + kind = gl518sm_r00; + else if (i == 0x80) + kind = gl518sm_r80; + else { + if (kind == 0) + printk + ("gl518sm.o: Ignoring 'force' parameter for unknown chip at " + "adapter %d, address 0x%02x\n", + i2c_adapter_id(adapter), address); + goto ERROR1; + } + } + + type_name = "gl518sm"; + if (kind == gl518sm_r00) { + client_name = "GL518SM Revision 0x00 chip"; + } else if (kind == gl518sm_r80) { + client_name = "GL518SM Revision 0x80 chip"; + } else { +#ifdef DEBUG + printk("gl518sm.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + + for (i = 0; i < MAX_GL518_NR; i++) + if (!gl518_list[i]) + break; + if (i == MAX_GL518_NR) { + printk + ("gl518sm.o: No empty slots left, recompile and heighten " + "MAX_GL518_NR!\n"); + err = -ENOMEM; + goto ERROR2; + } + gl518_list[i] = new_client; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the GL518SM chip */ + if (kind == gl518sm_r00) + data->iterate = 0; + else + data->iterate = 3; + data->iterate_lock = 0; + data->quit_thread = 0; + data->thread = NULL; + data->alarm_mask = 0xff; + data->voltage[0]=data->voltage[1]=data->voltage[2]=0; + gl518_init_client((struct i2c_client *) new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + for (i = 0; i < MAX_GL518_NR; i++) + if (new_client == gl518_list[i]) + gl518_list[i] = NULL; + ERROR2: + ERROR1: + kfree(data); + ERROR0: + return err; +} + + +/* Called when we have found a new GL518SM. */ +static void gl518_init_client(struct i2c_client *client) +{ + /* Power-on defaults (bit 7=1) */ + gl518_write_value(client, GL518_REG_CONF, 0x80); + + /* No noisy output (bit 2=1), Comparator mode (bit 3=0), two fans (bit4=0), + standby mode (bit6=0) */ + gl518_write_value(client, GL518_REG_CONF, 0x04); + + /* Never interrupts */ + gl518_write_value(client, GL518_REG_MASK, 0x00); + + /* Clear status register (bit 5=1), start (bit6=1) */ + gl518_write_value(client, GL518_REG_CONF, 0x24); + gl518_write_value(client, GL518_REG_CONF, 0x44); +} + +static int gl518_detach_client(struct i2c_client *client) +{ + int err, i; + struct gl518_data *data = client->data; + + i2c_deregister_entry(((struct gl518_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("gl518sm.o: Client deregistration failed, client not detached.\n"); + return err; + } + + for (i = 0; i < MAX_GL518_NR; i++) + if (client == gl518_list[i]) + break; + if ((i == MAX_GL518_NR)) { + printk("gl518sm.o: Client to detach not found.\n"); + return -ENOENT; + } + gl518_list[i] = NULL; + + if (data->thread) { + data->quit_thread = 1; + wake_up_interruptible(&data->wq); + } + + kfree(client->data); + + return 0; +} + + +/* Registers 0x07 to 0x0c are word-sized, others are byte-sized + GL518 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int gl518_read_value(struct i2c_client *client, u8 reg) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return swab16(i2c_smbus_read_word_data(client, reg)); + else + return i2c_smbus_read_byte_data(client, reg); +} + +/* Registers 0x07 to 0x0c are word-sized, others are byte-sized + GL518 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return i2c_smbus_write_word_data(client, reg, swab16(value)); + else + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void gl518_update_client(struct i2c_client *client) +{ + struct gl518_data *data = client->data; + int val; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting gl518 update\n"); +#endif + + data->alarms = gl518_read_value(client, GL518_REG_INT); + data->beeps = gl518_read_value(client, GL518_REG_ALARM); + + val = gl518_read_value(client, GL518_REG_VDD_LIMIT); + data->voltage_min[0] = val & 0xff; + data->voltage_max[0] = (val >> 8) & 0xff; + val = gl518_read_value(client, GL518_REG_VIN1_LIMIT); + data->voltage_min[1] = val & 0xff; + data->voltage_max[1] = (val >> 8) & 0xff; + val = gl518_read_value(client, GL518_REG_VIN2_LIMIT); + data->voltage_min[2] = val & 0xff; + data->voltage_max[2] = (val >> 8) & 0xff; + val = gl518_read_value(client, GL518_REG_VIN3_LIMIT); + data->voltage_min[3] = val & 0xff; + data->voltage_max[3] = (val >> 8) & 0xff; + + val = gl518_read_value(client, GL518_REG_FAN_COUNT); + data->fan[0] = (val >> 8) & 0xff; + data->fan[1] = val & 0xff; + + val = gl518_read_value(client, GL518_REG_FAN_LIMIT); + data->fan_min[0] = (val >> 8) & 0xff; + data->fan_min[1] = val & 0xff; + + data->temp = gl518_read_value(client, GL518_REG_TEMP); + data->temp_over = + gl518_read_value(client, GL518_REG_TEMP_OVER); + data->temp_hyst = + gl518_read_value(client, GL518_REG_TEMP_HYST); + + val = gl518_read_value(client, GL518_REG_MISC); + data->fan_div[0] = (val >> 6) & 0x03; + data->fan_div[1] = (val >> 4) & 0x03; + + data->alarms &= data->alarm_mask; + + val = gl518_read_value(client, GL518_REG_CONF); + data->beep_enable = (val >> 2) & 1; + +#ifndef DEBUG_VIN + if (data->type != gl518sm_r00) { + data->voltage[0] = + gl518_read_value(client, GL518_REG_VDD); + data->voltage[1] = + gl518_read_value(client, GL518_REG_VIN1); + data->voltage[2] = + gl518_read_value(client, GL518_REG_VIN2); + data->voltage[3] = + gl518_read_value(client, GL518_REG_VIN3); + } else + gl518_update_client_rev00(client); +#else + gl518_update_client_rev00(client); +#endif + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +/* Here we decide how to run the iteration code. + When called, we trigger the iteration and report the last + measured voltage. No delay for user apps */ +static void gl518_update_client_rev00(struct i2c_client *client) +{ + struct gl518_data *data = client->data; + int i; + + if (data->iterate == 1) { /* 10 sec delay */ + /* as that update is slow, we consider the data valid for 30 seconds */ + if ( + ((jiffies - data->last_updated_v00 > 30 * HZ) + || (data->alarms & 7) + || (!data->valid)) && (!data->iterate_lock)) { + data->iterate_lock = 1; + gl518_update_iterate(client); + data->iterate_lock = 0; + } + for (i = 0; i < 4; i++) + data->voltage[i] = data->iter_voltage[i]; + } else if (data->iterate == 2) { /* show results of last iteration */ + for (i = 0; i < 4; i++) + data->voltage[i] = data->iter_voltage[i]; + wake_up_interruptible(&data->wq); + } else { /* no iteration */ + data->voltage[3] = + gl518_read_value(client, GL518_REG_VIN3); + } +} + +static int gl518_update_thread(void *c) +{ + struct i2c_client *client = c; + struct gl518_data *data = client->data; + +#ifdef __SMP__ + lock_kernel(); +#endif + exit_mm(current); + current->session = 1; + current->pgrp = 1; + sigfillset(¤t->blocked); + current->fs->umask = 0; + strcpy(current->comm, "gl518sm"); + + init_waitqueue_head(&(data->wq)); + data->thread = current; + +#ifdef __SMP__ + unlock_kernel(); +#endif + + for (;;) { + if (!data->iterate_lock) { + data->iterate_lock = 1; + gl518_update_iterate(client); + data->iterate_lock = 0; + } + + if ((data->quit_thread) || signal_pending(current)) + break; + interruptible_sleep_on(&data->wq); + } + + data->thread = NULL; + data->quit_thread = 0; + return 0; +} + +/* This updates vdd, vin1, vin2 values by doing slow and multiple + comparisons for the GL518SM rev 00 that lacks support for direct + reading of these values. Values are kept in iter_voltage */ + +static void gl518_update_iterate(struct i2c_client *client) +{ + struct gl518_data *data = client->data; + int i, j, loop_more = 1, min[3], max[3], delta[3]; + int alarm, beeps, irqs; + +#define VIN_REG(c) c==0?GL518_REG_VDD_LIMIT:\ + c==1?GL518_REG_VIN1_LIMIT:\ + GL518_REG_VIN2_LIMIT + + /* disable beeps & irqs for vin0-2 */ + beeps = gl518_read_value(client, GL518_REG_ALARM); + irqs = gl518_read_value(client, GL518_REG_MASK); + gl518_write_value(client, GL518_REG_ALARM, beeps & ~0x7); + gl518_write_value(client, GL518_REG_MASK, irqs & ~0x7); + + alarm = data->alarms; + + for (i = 0; i < 3; i++) { + if (alarm & (1 << i)) { + min[i] = 0; + max[i] = 127; + } else { + min[i] = data->voltage_min[i]; + max[i] = + (data->voltage_max[i] + + data->voltage_min[i]) / 2; + } + delta[i] = (max[i] - min[i]) / 2; + } + + for (j = 0; (j < 10 && loop_more); j++) { + + for (i = 0; i < 3; i++) + gl518_write_value(client, VIN_REG(i), + max[i] << 8 | min[i]); + + if ((data->thread) && + ((data->quit_thread) || signal_pending(current))) + goto finish; + + /* we wait now 1.5 seconds before comparing */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ + HZ / 2); + + alarm = gl518_read_value(client, GL518_REG_INT); + +#ifdef DEBUG_VIN + printk("gl518sm: iteration %2d: %4d%c %4d%c %4d%c\n", j, + max[0], (alarm & 1) ? '!' : ' ', + max[1], (alarm & 2) ? '!' : ' ', + max[2], (alarm & 4) ? '!' : ' '); +#endif + + for (loop_more = 0, i = 0; i < 3; i++) { + if (alarm & (1 << i)) + max[i] += delta[i]; + else + max[i] -= delta[i]; + + if (delta[i]) + loop_more++; + delta[i] >>= 1; + } + + } + + for (i = 0; i < 3; i++) + if (alarm & (1 << i)) + max[i]++; + +#ifdef DEBUG_VIN + printk("gl518sm: final :%5d %5d %5d\n", max[0], max[1], + max[2]); + printk("gl518sm: meter :%5d %5d %5d\n", data->voltage[0], + data->voltage[1], data->voltage[2]); +#endif + + /* update values, including vin3 */ + for (i = 0; i < 3; i++) { + data->iter_voltage[i] = max[i]; + } + data->iter_voltage[3] = gl518_read_value(client, GL518_REG_VIN3); + data->last_updated_v00 = jiffies; + + finish: + + /* reset values */ + for (i = 0; i < 3; i++) { + gl518_write_value(client, VIN_REG(i), + data->voltage_max[i] << 8 | data-> + voltage_min[i]); + } + + gl518_write_value(client, GL518_REG_ALARM, beeps); + gl518_write_value(client, GL518_REG_MASK, irqs); + +#undef VIN_REG +} + +void gl518_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + gl518_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + gl518_write_value(client, GL518_REG_TEMP_OVER, + data->temp_over); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + gl518_write_value(client, GL518_REG_TEMP_HYST, + data->temp_hyst); + } + } +} + +void gl518_vin(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + int nr = ctl_name - GL518_SYSCTL_VDD; + int regnr, old = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + gl518_update_client(client); + results[0] = nr ? IN_FROM_REG(data->voltage_min[nr]) : + VDD_FROM_REG(data->voltage_min[nr]); + results[1] = nr ? IN_FROM_REG(data->voltage_max[nr]) : + VDD_FROM_REG(data->voltage_max[nr]); + results[2] = nr ? IN_FROM_REG(data->voltage[nr]) : + VDD_FROM_REG(data->voltage[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + regnr = + nr == 0 ? GL518_REG_VDD_LIMIT : nr == + 1 ? GL518_REG_VIN1_LIMIT : nr == + 2 ? GL518_REG_VIN2_LIMIT : GL518_REG_VIN3_LIMIT; + if (*nrels_mag == 1) + old = gl518_read_value(client, regnr) & 0xff00; + if (*nrels_mag >= 2) { + data->voltage_max[nr] = + nr ? IN_TO_REG(results[1]) : + VDD_TO_REG(results[1]); + old = data->voltage_max[nr] << 8; + } + if (*nrels_mag >= 1) { + data->voltage_min[nr] = + nr ? IN_TO_REG(results[0]) : + VDD_TO_REG(results[0]); + old |= data->voltage_min[nr]; + gl518_write_value(client, regnr, old); + } + } +} + + +void gl518_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + int nr = ctl_name - GL518_SYSCTL_FAN1; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl518_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + results[1] = + FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr] = FAN_TO_REG(results[0], + DIV_FROM_REG(data-> + fan_div + [nr])); + old = + gl518_read_value(client, GL518_REG_FAN_LIMIT); + + if (nr == 0) { + old = + (old & 0x00ff) | (data-> + fan_min[0] << 8); + if (results[0] == 0) + data->alarm_mask &= ~0x20; + else + data->alarm_mask |= 0x20; + } else { + old = (old & 0xff00) | data->fan_min[1]; + if (results[0] == 0) + data->alarm_mask &= ~0x40; + else + data->alarm_mask |= 0x40; + } + gl518_write_value(client, GL518_REG_FAN_LIMIT, + old); + } + } +} + + +void gl518_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl518_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void gl518_beep(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl518_update_client(client); + results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); + results[1] = BEEPS_FROM_REG(data->beeps); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); + gl518_write_value(client, GL518_REG_CONF, + (gl518_read_value(client, + GL518_REG_CONF) + & 0xfb) | (data-> + beep_enable << 2)); + } + if (*nrels_mag >= 2) { + data->beeps = + BEEPS_TO_REG(results[1]) & data->alarm_mask; + gl518_write_value(client, GL518_REG_ALARM, + data->beeps); + } + } +} + + +void gl518_fan_div(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + int old; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl518_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = gl518_read_value(client, GL518_REG_MISC); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0xcf) | (data->fan_div[1] << 4); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0x3f) | (data->fan_div[0] << 6); + } + gl518_write_value(client, GL518_REG_MISC, old); + } +} + +void gl518_fan1off(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int old; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = + ((gl518_read_value(client, GL518_REG_MISC) & 0x08) != + 0); + results[1] = + ((gl518_read_value(client, GL518_REG_CONF) & 0x10) != + 0); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + old = + gl518_read_value(client, + GL518_REG_MISC) & 0xf7; + if (results[0]) + old |= 0x08; + gl518_write_value(client, GL518_REG_MISC, old); + } + if (*nrels_mag >= 2) { + old = + gl518_read_value(client, + GL518_REG_CONF) & 0xef; + if (results[1]) + old |= 0x10; + gl518_write_value(client, GL518_REG_CONF, old); + } + } +} + +void gl518_iterate(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl518_data *data = client->data; + int i; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->iterate; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE && + data->type == gl518sm_r00 ) { + if ((*nrels_mag >= 1) && (data->iterate != results[0])) { + data->iterate = results[0]; + for (i = 0; i < 4; i++) { + data->voltage[i] = 0; + data->iter_voltage[i] = 0; + } + data->valid = 0; + + if ((data->iterate != 2) && (data->thread)) { + data->quit_thread = 1; + wake_up_interruptible(&data->wq); + } else if ((data->iterate == 2) && (!data->thread)) { + init_waitqueue_head(&(data->wq)); + kernel_thread(gl518_update_thread, + (void *) client, 0); + } + } + } +} + +static int __init sm_gl518sm_init(void) +{ + printk("gl518sm.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&gl518_driver); +} + +static void __exit sm_gl518sm_exit(void) +{ + i2c_del_driver(&gl518_driver); +} + + + +MODULE_AUTHOR + ("Frodo Looijaard 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 Sun Feb 26 11:18:39 2006 @@ -0,0 +1,805 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2d, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(gl520sm); + +/* Many GL520 constants specified below +One of the inputs can be configured as either temp or voltage. +That's why _TEMP2 and _VIN4 access the same register +*/ + +/* The GL520 registers */ +#define GL520_REG_CHIP_ID 0x00 +#define GL520_REG_REVISION 0x01 +#define GL520_REG_VID 0x02 +#define GL520_REG_CONF 0x03 +#define GL520_REG_TEMP1 0x04 +#define GL520_REG_TEMP1_OVER 0x05 +#define GL520_REG_TEMP1_HYST 0x06 +#define GL520_REG_FAN_COUNT 0x07 +#define GL520_REG_FAN_LIMIT 0x08 +#define GL520_REG_VIN1_LIMIT 0x09 +#define GL520_REG_VIN2_LIMIT 0x0a +#define GL520_REG_VIN3_LIMIT 0x0b +#define GL520_REG_VDD_LIMIT 0x0c +#define GL520_REG_VIN3 0x0d +#define GL520_REG_VIN4 0x0e +#define GL520_REG_TEMP2 0x0e +#define GL520_REG_MISC 0x0f +#define GL520_REG_ALARM 0x10 +#define GL520_REG_MASK 0x11 +#define GL520_REG_INT 0x12 +#define GL520_REG_VIN2 0x13 +#define GL520_REG_VIN1 0x14 +#define GL520_REG_VDD 0x15 +#define GL520_REG_TEMP2_OVER 0x17 +#define GL520_REG_VIN4_MAX 0x17 +#define GL520_REG_TEMP2_HYST 0x18 +#define GL520_REG_VIN4_MIN 0x18 + + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ + +#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-5:(val)+5) / 10)+130),\ + 0,255)) +#define TEMP_FROM_REG(val) (((val) - 130) * 10) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((960000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) \ + ( (val)==0 ? 0 : (val)==255 ? 0 : (960000/((val)*(div))) ) + +#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*10+8)/19),0,255)) +#define IN_FROM_REG(val) (((val)*19)/10) + +#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*10+11)/23),0,255)) +#define VDD_FROM_REG(val) (((val)*23)/10) + +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) +#define DIV_FROM_REG(val) (1 << (val)) + +#define ALARMS_FROM_REG(val) val + +#define BEEP_ENABLE_TO_REG(val) ((val)?0:1) +#define BEEP_ENABLE_FROM_REG(val) ((val)?0:1) + +#define BEEPS_TO_REG(val) (val) +#define BEEPS_FROM_REG(val) (val) + +#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ + 205-(val)*5) + +/* Each client has this additional data */ +struct gl520_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 voltage[5]; /* Register values; [0] = VDD */ + u8 voltage_min[5]; /* Register values; [0] = VDD */ + u8 voltage_max[5]; /* Register values; [0] = VDD */ + u8 fan[2]; + u8 fan_min[2]; + u8 temp[2]; /* Register values */ + u8 temp_over[2]; /* Register values */ + u8 temp_hyst[2]; /* Register values */ + u8 alarms, beeps, vid; /* Register value */ + u8 alarm_mask; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u8 beep_enable; /* Boolean */ + u8 two_temps; /* Boolean */ +}; + +static int gl520_attach_adapter(struct i2c_adapter *adapter); +static int gl520_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void gl520_init_client(struct i2c_client *client); +static int gl520_detach_client(struct i2c_client *client); + +static int gl520_read_value(struct i2c_client *client, u8 reg); +static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value); +static void gl520_update_client(struct i2c_client *client); + +static void gl520_vin(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_beep(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_fan1off(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void gl520_config(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +/* This is the driver that will be inserted */ +static struct i2c_driver gl520_driver = { + .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 Celsius * 10 */ +#define GL520_SYSCTL_TEMP2 1201 /* Degrees Celsius * 10 */ +#define GL520_SYSCTL_VID 1300 +#define GL520_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define GL520_SYSCTL_ALARMS 2001 /* bitvector */ +#define GL520_SYSCTL_BEEP 2002 /* bitvector */ +#define GL520_SYSCTL_FAN1OFF 2003 +#define GL520_SYSCTL_CONFIG 2004 + +#define GL520_ALARM_VDD 0x01 +#define GL520_ALARM_VIN1 0x02 +#define GL520_ALARM_VIN2 0x04 +#define GL520_ALARM_VIN3 0x08 +#define GL520_ALARM_TEMP1 0x10 +#define GL520_ALARM_FAN1 0x20 +#define GL520_ALARM_FAN2 0x40 +#define GL520_ALARM_TEMP2 0x80 +#define GL520_ALARM_VIN4 0x80 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected GL520. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table gl520_dir_table_template[] = { + {GL520_SYSCTL_VIN1, "vin1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_vin}, + {GL520_SYSCTL_VIN2, "vin2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_vin}, + {GL520_SYSCTL_VIN3, "vin3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_vin}, + {GL520_SYSCTL_VIN4, "vin4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_vin}, + {GL520_SYSCTL_VDD, "vdd", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_vin}, + {GL520_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_vid}, + {GL520_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_fan}, + {GL520_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_fan}, + {GL520_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_temp}, + {GL520_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_temp}, + {GL520_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_fan_div}, + {GL520_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_alarms}, + {GL520_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_beep}, + {GL520_SYSCTL_FAN1OFF, "fan1off", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_fan1off}, + {GL520_SYSCTL_CONFIG, "config", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &gl520_config}, + {0} +}; + +static int gl520_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, gl520_detect); +} + +static int gl520_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct gl520_data *data; + int err = 0; + const char *type_name = ""; + char client_name[32]; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("gl520sm.o: gl520_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access gl520_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct gl520_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &gl520_driver; + new_client->flags = 0; + + /* Determine the chip type. */ + + if (gl520_read_value(new_client, GL520_REG_CHIP_ID) != 0x20) { + printk + ("gl520sm.o: Ignoring 'force' parameter for unknown chip at " + "adapter %d, address 0x%02x\n", + i2c_adapter_id(adapter), address); + goto ERROR1; + } else { + kind = gl520sm; + } + + i = gl520_read_value(new_client, GL520_REG_REVISION); + if (kind == gl520sm) { + type_name = "gl520sm"; + sprintf(client_name, "GL520SM Revision %02x chip", i); + } else { +#ifdef DEBUG + printk("gl520sm.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the GL520SM chip */ + data->alarm_mask = 0xff; + gl520_init_client(new_client); + if (data->two_temps) + data->voltage_max[4] = data->voltage_min[4] = + data->voltage[4] = 0; + else + data->temp_hyst[1] = data->temp_over[1] = + data->temp[1] = 0; + + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + + +/* Called when we have found a new GL520SM. */ +static void gl520_init_client(struct i2c_client *client) +{ + struct gl520_data *data = (struct gl520_data *)(client->data); + u8 oldconf, conf; + + conf = oldconf = gl520_read_value(client, GL520_REG_CONF); + data->two_temps = !(conf & 0x10); + + /* If IRQ# is disabled, we can safely force comparator mode */ + if (!(conf & 0x20)) + conf &= 0xf7; + + /* Enable monitoring if needed */ + conf |= 0x40; + + if (conf != oldconf) + gl520_write_value(client, GL520_REG_CONF, conf); +} + +static int gl520_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct gl520_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("gl520sm.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* Registers 0x07 to 0x0c are word-sized, others are byte-sized + GL520 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int gl520_read_value(struct i2c_client *client, u8 reg) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return swab16(i2c_smbus_read_word_data(client, reg)); + else + return i2c_smbus_read_byte_data(client, reg); +} + +/* Registers 0x07 to 0x0c are word-sized, others are byte-sized + GL520 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return i2c_smbus_write_word_data(client, reg, swab16(value)); + else + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void gl520_update_client(struct i2c_client *client) +{ + struct gl520_data *data = client->data; + int val; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting gl520 update\n"); +#endif + + data->alarms = gl520_read_value(client, GL520_REG_INT); + data->beeps = gl520_read_value(client, GL520_REG_ALARM); + data->vid = gl520_read_value(client, GL520_REG_VID) & 0x1f; + + val = gl520_read_value(client, GL520_REG_VDD_LIMIT); + data->voltage_min[0] = val & 0xff; + data->voltage_max[0] = (val >> 8) & 0xff; + val = gl520_read_value(client, GL520_REG_VIN1_LIMIT); + data->voltage_min[1] = val & 0xff; + data->voltage_max[1] = (val >> 8) & 0xff; + val = gl520_read_value(client, GL520_REG_VIN2_LIMIT); + data->voltage_min[2] = val & 0xff; + data->voltage_max[2] = (val >> 8) & 0xff; + val = gl520_read_value(client, GL520_REG_VIN3_LIMIT); + data->voltage_min[3] = val & 0xff; + data->voltage_max[3] = (val >> 8) & 0xff; + + val = gl520_read_value(client, GL520_REG_FAN_COUNT); + data->fan[0] = (val >> 8) & 0xff; + data->fan[1] = val & 0xff; + + val = gl520_read_value(client, GL520_REG_FAN_LIMIT); + data->fan_min[0] = (val >> 8) & 0xff; + data->fan_min[1] = val & 0xff; + + data->temp[0] = gl520_read_value(client, GL520_REG_TEMP1); + data->temp_over[0] = + gl520_read_value(client, GL520_REG_TEMP1_OVER); + data->temp_hyst[0] = + gl520_read_value(client, GL520_REG_TEMP1_HYST); + + val = gl520_read_value(client, GL520_REG_MISC); + data->fan_div[0] = (val >> 6) & 0x03; + data->fan_div[1] = (val >> 4) & 0x03; + + data->alarms &= data->alarm_mask; + + val = gl520_read_value(client, GL520_REG_CONF); + data->beep_enable = (val >> 2) & 1; + + data->voltage[0] = gl520_read_value(client, GL520_REG_VDD); + data->voltage[1] = + gl520_read_value(client, GL520_REG_VIN1); + data->voltage[2] = + gl520_read_value(client, GL520_REG_VIN2); + data->voltage[3] = + gl520_read_value(client, GL520_REG_VIN3); + + /* Temp1 and Vin4 are the same input */ + if (data->two_temps) { + data->temp[1] = + gl520_read_value(client, GL520_REG_TEMP2); + data->temp_over[1] = + gl520_read_value(client, GL520_REG_TEMP2_OVER); + data->temp_hyst[1] = + gl520_read_value(client, GL520_REG_TEMP2_HYST); + } else { + data->voltage[4] = + gl520_read_value(client, GL520_REG_VIN4); + data->voltage_min[4] = + gl520_read_value(client, GL520_REG_VIN4_MIN); + data->voltage_max[4] = + gl520_read_value(client, GL520_REG_VIN4_MAX); + } + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +void gl520_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + int nr = ctl_name - GL520_SYSCTL_TEMP1; + int regnr; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over[nr]); + results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); + results[2] = TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if ((nr == 1) && (!data->two_temps)) + return; + regnr = + nr == 0 ? GL520_REG_TEMP1_OVER : GL520_REG_TEMP2_OVER; + if (*nrels_mag >= 1) { + data->temp_over[nr] = TEMP_TO_REG(results[0]); + gl520_write_value(client, regnr, + data->temp_over[nr]); + } + regnr = + nr == 0 ? GL520_REG_TEMP1_HYST : GL520_REG_TEMP2_HYST; + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = TEMP_TO_REG(results[1]); + gl520_write_value(client, regnr, + data->temp_hyst[nr]); + } + } +} + +void gl520_vin(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + int nr = ctl_name - GL520_SYSCTL_VDD; + int regnr, old = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = nr ? IN_FROM_REG(data->voltage_min[nr]) : + VDD_FROM_REG(data->voltage_min[nr]); + results[1] = nr ? IN_FROM_REG(data->voltage_max[nr]) : + VDD_FROM_REG(data->voltage_max[nr]); + results[2] = nr ? IN_FROM_REG(data->voltage[nr]) : + VDD_FROM_REG(data->voltage[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (nr != 4) { + regnr = + nr == 0 ? GL520_REG_VDD_LIMIT : nr == + 1 ? GL520_REG_VIN1_LIMIT : nr == + 2 ? GL520_REG_VIN2_LIMIT : + GL520_REG_VIN3_LIMIT; + if (*nrels_mag == 1) + old = + gl520_read_value(client, + regnr) & 0xff00; + if (*nrels_mag >= 2) { + data->voltage_max[nr] = + nr ? IN_TO_REG(results[1]) : + VDD_TO_REG(results[1]); + old = data->voltage_max[nr] << 8; + } + if (*nrels_mag >= 1) { + data->voltage_min[nr] = + nr ? IN_TO_REG(results[0]) : + VDD_TO_REG(results[0]); + old |= data->voltage_min[nr]; + gl520_write_value(client, regnr, old); + } + } else if (!data->two_temps) { + if (*nrels_mag == 1) + gl520_write_value(client, + GL520_REG_VIN4_MIN, + IN_TO_REG(results[0])); + if (*nrels_mag >= 2) + gl520_write_value(client, + GL520_REG_VIN4_MAX, + IN_TO_REG(results[1])); + } + } +} + + +void gl520_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + int nr = ctl_name - GL520_SYSCTL_FAN1; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + results[1] = + FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr] = FAN_TO_REG(results[0], + DIV_FROM_REG(data-> + fan_div + [nr])); + old = + gl520_read_value(client, GL520_REG_FAN_LIMIT); + + if (nr == 0) { + old = + (old & 0x00ff) | (data-> + fan_min[nr] << 8); + if (results[0] == 0) + data->alarm_mask &= ~0x20; + else + data->alarm_mask |= 0x20; + } else { + old = (old & 0xff00) | data->fan_min[nr]; + if (results[0] == 0) + data->alarm_mask &= ~0x40; + else + data->alarm_mask |= 0x40; + } + gl520_write_value(client, GL520_REG_FAN_LIMIT, + old); + } + } +} + + +void gl520_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void gl520_beep(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); + results[1] = BEEPS_FROM_REG(data->beeps); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); + gl520_write_value(client, GL520_REG_CONF, + (gl520_read_value(client, + GL520_REG_CONF) + & 0xfb) | (data-> + beep_enable << 2)); + } + if (*nrels_mag >= 2) { + data->beeps = + BEEPS_TO_REG(results[1]) & data->alarm_mask; + gl520_write_value(client, GL520_REG_ALARM, + data->beeps); + } + } +} + + +void gl520_fan_div(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + int old; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = gl520_read_value(client, GL520_REG_MISC); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0xcf) | (data->fan_div[1] << 4); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0x3f) | (data->fan_div[0] << 6); + } + gl520_write_value(client, GL520_REG_MISC, old); + } +} + +void gl520_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + gl520_update_client(client); + results[0] = VID_FROM_REG(data->vid); + *nrels_mag = 1; + } +} + +void gl520_fan1off(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int old; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = + ((gl520_read_value(client, GL520_REG_MISC) & 0x04) != + 0); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + old = + gl520_read_value(client, + GL520_REG_MISC) & 0xfb; + if (results[0]) + old |= 0x04; + gl520_write_value(client, GL520_REG_MISC, old); + } + } +} + +void gl520_config(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct gl520_data *data = client->data; + int old; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = + ((gl520_read_value(client, GL520_REG_CONF) & 0x10) == + 0); + data->two_temps = results[0]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + old = + gl520_read_value(client, + GL520_REG_CONF) & 0xef; + if (!results[1]) { + old |= 0x10; + data->two_temps = 0; + data->temp_hyst[1] = data->temp_over[1] = + data->temp[1] = 0; + } else { + data->two_temps = 1; + data->voltage_max[4] = data->voltage_min[4] = + data->voltage[4] = 0; + } + gl520_write_value(client, GL520_REG_CONF, old); + } + } +} + +static int __init sm_gl520sm_init(void) +{ + printk("gl520sm.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&gl520_driver); +} + +static void __exit sm_gl520sm_exit(void) +{ + i2c_del_driver(&gl520_driver); +} + + + +MODULE_AUTHOR + ("Frodo Looijaard 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 Sun Feb 26 11:18:39 2006 @@ -0,0 +1,1132 @@ +/* + 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 chip 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x28, 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_2(it87, it8712); + + +#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 */ +#define DEVREV 0x22 /* Register: Device Revision */ + +static inline int +superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +static int superio_inw(int reg) +{ + int val; + outb(reg++, REG); + val = inb(VAL) << 8; + outb(reg, REG); + val |= inb(VAL); + return val; +} + +static inline void +superio_select(void) +{ + outb(DEV, REG); + outb(PME, VAL); +} + +static inline void +superio_enter(void) +{ + outb(0x87, REG); + outb(0x01, REG); + outb(0x55, REG); + outb(0x55, REG); +} + +static inline void +superio_exit(void) +{ + outb(0x02, REG); + outb(0x02, VAL); +} + +#define IT87_DEVID_MATCH(id) ((id) == 0x8712 || (id) == 0x8705) + +#define IT87_ACT_REG 0x30 +#define IT87_BASE_REG 0x60 + +/* Update battery voltage after every reading if true */ +static int update_vbat = 0; + +/* Reset the registers on init */ +static int reset = 0; + +/* Many IT87 constants specified below */ + +/* Length of ISA address segment */ +#define IT87_EXTENT 8 + +/* Where are the ISA address/data registers relative to the base address */ +#define IT87_ADDR_REG_OFFSET 5 +#define IT87_DATA_REG_OFFSET 6 + +/*----- The IT87 registers -----*/ + +#define IT87_REG_CONFIG 0x00 + +#define IT87_REG_ALARM1 0x01 +#define IT87_REG_ALARM2 0x02 +#define IT87_REG_ALARM3 0x03 + +#define IT87_REG_VID 0x0a +#define IT87_REG_FAN_DIV 0x0b + +#define IT87_REG_FAN(nr) (0x0c + (nr)) +#define IT87_REG_FAN_MIN(nr) (0x0f + (nr)) +#define IT87_REG_FAN_CTRL 0x13 + +/* pwm and smart guardian registers */ + +#define IT87_REG_FAN_ONOFF 0x14 +#define IT87_REG_PWM(nr) (0x14 + (nr)) +#define IT87_REG_SG_TL_OFF(nr) (0x58 + (nr)*8) +#define IT87_REG_SG_TL_LOW(nr) (0x59 + (nr)*8) +#define IT87_REG_SG_TL_MED(nr) (0x5a + (nr)*8) +#define IT87_REG_SG_TL_HI(nr) (0x5b + (nr)*8) +#define IT87_REG_SG_TL_OVR(nr) (0x5c + (nr)*8) +#define IT87_REG_SG_PWM_LOW(nr) (0x5d + (nr)*8) +#define IT87_REG_SG_PWM_MED(nr) (0x5e + (nr)*8) +#define IT87_REG_SG_PWM_HI(nr) (0x5f + (nr)*8) + +/* Monitors: 9 voltage (0 to 7, battery), 3 temp (1 to 3), 3 fan (1 to 3) */ + +#define IT87_REG_VIN(nr) (0x20 + (nr)) +#define IT87_REG_TEMP(nr) (0x28 + (nr)) + +#define IT87_REG_VIN_MAX(nr) (0x30 + (nr) * 2) +#define IT87_REG_VIN_MIN(nr) (0x31 + (nr) * 2) +#define IT87_REG_TEMP_HIGH(nr) (0x3e + (nr) * 2) +#define IT87_REG_TEMP_LOW(nr) (0x3f + (nr) * 2) + +#define IT87_REG_I2C_ADDR 0x48 + +#define IT87_REG_VIN_ENABLE 0x50 +#define IT87_REG_TEMP_ENABLE 0x51 + +#define IT87_REG_CHIPID 0x58 +#define IT87_REG_CHIPID2 0x5b /* IT8712F only */ + +/* sensor pin types */ +#define UNUSED 0 +#define THERMISTOR 2 +#define PIIDIODE 3 + +/* Conversions. Limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) +#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) + +#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ + ((val)+5)/10),-127,127)) +#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) + +#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ + 205-(val)*5) +#define ALARMS_FROM_REG(val) (val) + +extern inline u8 DIV_TO_REG(long val) +{ + u8 i; + for( i = 0; i <= 7; i++ ) + { + if( val>>i == 1 ) + return i; + } + return 1; +} +#define DIV_FROM_REG(val) (1 << (val)) + +/* For each registered IT87, we need to keep some data in memory. That + data is pointed to by it87_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new it87 client is + allocated. */ +struct it87_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[9]; /* Register value */ + u8 in_max[9]; /* Register value */ + u8 in_min[9]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u8 temp[3]; /* Register value */ + u8 temp_high[3]; /* Register value */ + u8 temp_low[3]; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u8 pwm[3]; /* Register value */ + u8 fan_ctl[2]; /* Register encoding */ + u8 sg_tl[3][5]; /* Register value */ + u8 sg_pwm[3][3]; /* Register value */ + u8 sens[3]; /* 2 = Thermistor, + 3 = PII/Celeron diode */ +}; + + +static int it87_attach_adapter(struct i2c_adapter *adapter); +static int it87_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 = { + .name = "IT87xx sensor driver", + .id = I2C_DRIVERID_IT87, + .flags = I2C_DF_NOTIFY, + .attach_adapter = it87_attach_adapter, + .detach_client = it87_detach_client, +}; + +/* 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 Celsius * 10 */ +#define IT87_SYSCTL_TEMP2 1201 /* Degrees Celsius * 10 */ +#define IT87_SYSCTL_TEMP3 1202 /* Degrees Celsius * 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 __init it87_find(int *address) +{ + int err = -ENODEV; + u16 devid; + + superio_enter(); + devid = superio_inw(DEVID); + if (!IT87_DEVID_MATCH(devid)) + goto exit; + + superio_select(); + if (!(superio_inb(IT87_ACT_REG) & 0x01)) { + printk(KERN_INFO "it87: Device not activated, skipping\n"); + goto exit; + } + + *address = superio_inw(IT87_BASE_REG) & ~(IT87_EXTENT - 1); + if (*address == 0) { + printk(KERN_INFO "it87: Base address not set, skipping\n"); + goto exit; + } + + err = 0; + printk(KERN_INFO "it87: Found IT%04xF chip at 0x%x, revision %d\n", + devid, *address, superio_inb(DEVREV) & 0x0f); + +exit: + superio_exit(); + return err; +} + +/* This function is called by i2c_detect */ +static int it87_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct it87_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + int is_isa = i2c_is_isa_adapter(adapter); + + if (!is_isa + && !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + if (is_isa + && check_region(address, IT87_EXTENT)) + return 0; + + /* Probe whether there is anything available on this address. Already + done for SMBus clients */ + if (is_isa && kind < 0) { +#define REALLY_SLOW_IO + /* We need the timeouts for at least some IT87-like chips. + But only if we read 'undefined' registers. */ + i = inb_p(address + 1); + if (inb_p(address + 2) != i + || inb_p(address + 3) != i + || inb_p(address + 7) != i) + return 0; +#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 (!(data = kmalloc(sizeof(struct it87_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + if (is_isa) + init_MUTEX(&data->lock); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &it87_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80) + || (!is_isa + && it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) + goto ERROR1; + } + + /* Determine the chip type. */ + if (kind <= 0) { + i = it87_read_value(new_client, IT87_REG_CHIPID); + if (i == 0x90) { + kind = it87; + i = it87_read_value(new_client, IT87_REG_CHIPID2); + if (i == 0x12) + kind = it8712; + } + 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 = "IT8712 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; + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* The IT8705F doesn't have VID capability */ + data->vid = 0x1f; + + /* 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the IT87 chip */ + it87_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + if (is_isa) + release_region(address, IT87_EXTENT); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int it87_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct it87_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("it87.o: Client deregistration failed, client not detached.\n"); + return err; + } + + if(i2c_is_isa_client(client)) + release_region(client->addr, IT87_EXTENT); + kfree(client->data); + + return 0; +} + +/* The SMBus locks itself, but ISA access must be locked explicitly! + 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 explicitly! + We don't want to lock the whole ISA bus, so we lock each client + separately. + We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, + would slow down the IT87 access and should not be necessary. + There are some ugly typecasts here, but the good new is - they should + nowhere else be necessary! */ +static int it87_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + if (i2c_is_isa_client(client)) { + down(&(((struct it87_data *) (client->data))->lock)); + outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); + outb_p(value, client->addr + IT87_DATA_REG_OFFSET); + up(&(((struct it87_data *) (client->data))->lock)); + return 0; + } else + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new IT87. */ +static void it87_init_client(struct i2c_client *client) +{ + int tmp; + + if (reset) { + /* Reset all except Watchdog values and last conversion values + This sets fan-divs to 2, among others */ + it87_write_value(client, IT87_REG_CONFIG, 0x80); + } + + /* Check if temperature channnels are reset manually or by some reason */ + tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); + if ((tmp & 0x3f) == 0) { + /* Temp1,Temp3=thermistor; Temp2=thermal diode */ + tmp = (tmp & 0xc0) | 0x2a; + it87_write_value(client, IT87_REG_TEMP_ENABLE, tmp); + } + + /* Check if voltage monitors are reset manually or by some reason */ + tmp = it87_read_value(client, IT87_REG_VIN_ENABLE); + if ((tmp & 0xff) == 0) { + /* Enable all voltage monitors */ + it87_write_value(client, IT87_REG_VIN_ENABLE, 0xff); + } + + /* Check if tachometers are reset manually or by some reason */ + tmp = it87_read_value(client, IT87_REG_FAN_CTRL); + if ((tmp & 0x70) == 0) { + /* Enable all fan tachometers */ + tmp = (tmp & 0x8f) | 0x70; + it87_write_value(client, IT87_REG_FAN_CTRL, tmp); + } + + /* Start monitoring */ + it87_write_value(client, IT87_REG_CONFIG, + (it87_read_value(client, IT87_REG_CONFIG) & 0x36) + | (update_vbat ? 0x41 : 0x01)); +} + +static void it87_update_client(struct i2c_client *client) +{ + struct it87_data *data = client->data; + int i, tmp, tmp2; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + + if (update_vbat) { + /* Cleared after each update, so reenable. Value + returned by this read will be previous value */ + it87_write_value(client, IT87_REG_CONFIG, + it87_read_value(client, IT87_REG_CONFIG) | 0x40); + } + for (i = 0; i <= 7; i++) { + data->in[i] = + it87_read_value(client, IT87_REG_VIN(i)); + data->in_min[i] = + it87_read_value(client, IT87_REG_VIN_MIN(i)); + data->in_max[i] = + it87_read_value(client, IT87_REG_VIN_MAX(i)); + } + data->in[8] = + it87_read_value(client, IT87_REG_VIN(8)); + /* VBAT sensor doesn't have limit registers, set + to min and max value */ + data->in_min[8] = 0; + data->in_max[8] = 255; + + for (i = 1; i <= 3; i++) { + data->fan[i - 1] = + it87_read_value(client, IT87_REG_FAN(i)); + data->fan_min[i - 1] = + it87_read_value(client, IT87_REG_FAN_MIN(i)); + } + for (i = 1; i <= 3; i++) { + data->temp[i - 1] = + it87_read_value(client, IT87_REG_TEMP(i)); + data->temp_high[i - 1] = + it87_read_value(client, IT87_REG_TEMP_HIGH(i)); + data->temp_low[i - 1] = + it87_read_value(client, IT87_REG_TEMP_LOW(i)); + } + + if (data->type == it8712) { + data->vid = it87_read_value(client, IT87_REG_VID); + data->vid &= 0x1f; + } + + i = it87_read_value(client, IT87_REG_FAN_DIV); + data->fan_div[0] = i & 0x07; + data->fan_div[1] = (i >> 3) & 0x07; + data->fan_div[2] = ( (i&0x40)==0x40 ? 3 : 1 ); + + for( i = 1; i <= 3; i++ ) { + data->pwm[i-1] = it87_read_value(client, IT87_REG_PWM(i)); + data->sg_tl[i-1][0] = it87_read_value(client, IT87_REG_SG_TL_OFF(i)); + data->sg_tl[i-1][1] = it87_read_value(client, IT87_REG_SG_TL_LOW(i)); + data->sg_tl[i-1][2] = it87_read_value(client, IT87_REG_SG_TL_MED(i)); + data->sg_tl[i-1][3] = it87_read_value(client, IT87_REG_SG_TL_HI(i)); + data->sg_tl[i-1][4] = it87_read_value(client, IT87_REG_SG_TL_OVR(i)); + data->sg_pwm[i-1][0] = it87_read_value(client, IT87_REG_SG_PWM_LOW(i)); + data->sg_pwm[i-1][1] = it87_read_value(client, IT87_REG_SG_PWM_MED(i)); + data->sg_pwm[i-1][2] = it87_read_value(client, IT87_REG_SG_PWM_HI(i)); + } + data->alarms = + it87_read_value(client, IT87_REG_ALARM1) | + (it87_read_value(client, IT87_REG_ALARM2) << 8) | + (it87_read_value(client, IT87_REG_ALARM3) << 16); + data->fan_ctl[0] = it87_read_value(client, IT87_REG_FAN_CTRL); + data->fan_ctl[1] = it87_read_value(client, IT87_REG_FAN_ONOFF); + + tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); + for(i = 0; i < 3; i++) { + tmp2 = (tmp >> i) & 0x09; + if(tmp2 == 0x01) + data->sens[i] = PIIDIODE; + else if(tmp2 == 0x08) + data->sens[i] = THERMISTOR; + else + data->sens[i] = UNUSED; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + - Each function must return the magnitude (power of 10 to divide the + data with) if it is called with operation==SENSORS_PROC_REAL_INFO. + - It must put a maximum of *nrels elements in results reflecting the + data of this file, and set *nrels to the number it actually put + in it, if operation==SENSORS_PROC_REAL_READ. + - Finally, it must get upto *nrels elements from results and write them + to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +void it87_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = ctl_name - IT87_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + it87_write_value(client, IT87_REG_VIN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + it87_write_value(client, IT87_REG_VIN_MAX(nr), + data->in_max[nr]); + } + } +} + +void it87_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = ctl_name - IT87_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = FAN_TO_REG(results[0], + DIV_FROM_REG(data->fan_div[nr - 1])); + it87_write_value(client, IT87_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + + +void it87_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = ctl_name - IT87_SYSCTL_TEMP1 + 1; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_high[nr - 1]); + results[1] = TEMP_FROM_REG(data->temp_low[nr - 1]); + results[2] = TEMP_FROM_REG(data->temp[nr - 1]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_high[nr - 1] = TEMP_TO_REG(results[0]); + it87_write_value(client, IT87_REG_TEMP_HIGH(nr), + data->temp_high[nr - 1]); + } + if (*nrels_mag >= 2) { + data->temp_low[nr - 1] = TEMP_TO_REG(results[1]); + it87_write_value(client, IT87_REG_TEMP_LOW(nr), + data->temp_low[nr - 1]); + } + } +} + +void it87_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = ctl_name - IT87_SYSCTL_PWM1 + 1; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = data->pwm[nr - 1]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->pwm[nr - 1] = results[0]; + it87_write_value(client, IT87_REG_PWM(nr), data->pwm[nr - 1]); + } + } +} + +void it87_sgpwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = ctl_name - IT87_SYSCTL_PWM1 + 1; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = data->sg_pwm[nr - 1][0]; + results[1] = data->sg_pwm[nr - 1][1]; + results[2] = data->sg_pwm[nr - 1][2]; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->sg_pwm[nr - 1][0] = results[0]; + it87_write_value(client, IT87_REG_SG_PWM_LOW(nr), data->sg_pwm[nr - 1][0]); + } + if (*nrels_mag >= 2) { + data->sg_pwm[nr - 1][1] = results[1]; + it87_write_value(client, IT87_REG_SG_PWM_MED(nr), data->sg_pwm[nr - 1][1]); + } + if (*nrels_mag >= 3) { + data->sg_pwm[nr - 1][2] = results[2]; + it87_write_value(client, IT87_REG_SG_PWM_HI(nr), data->sg_pwm[nr - 1][2]); + } + } +} + +void it87_sgtl(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = ctl_name - IT87_SYSCTL_PWM1 + 1; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = TEMP_FROM_REG(data->sg_tl[nr - 1][0]); + results[1] = TEMP_FROM_REG(data->sg_tl[nr - 1][1]); + results[2] = TEMP_FROM_REG(data->sg_tl[nr - 1][2]); + results[3] = TEMP_FROM_REG(data->sg_tl[nr - 1][3]); + results[4] = TEMP_FROM_REG(data->sg_tl[nr - 1][4]); + *nrels_mag = 5; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->sg_tl[nr - 1][0] = TEMP_TO_REG(results[0]); + it87_write_value(client, IT87_REG_SG_TL_OFF(nr), data->sg_tl[nr - 1][0]); + } + if (*nrels_mag >= 2) { + data->sg_tl[nr - 1][1] = TEMP_TO_REG(results[1]); + it87_write_value(client, IT87_REG_SG_TL_LOW(nr), data->sg_tl[nr - 1][1]); + } + if (*nrels_mag >= 3) { + data->sg_tl[nr - 1][2] = TEMP_TO_REG(results[2]); + it87_write_value(client, IT87_REG_SG_TL_MED(nr), data->sg_tl[nr - 1][2]); + } + if (*nrels_mag >= 4) { + data->sg_tl[nr - 1][3] = TEMP_TO_REG(results[3]); + it87_write_value(client, IT87_REG_SG_TL_HI(nr), data->sg_tl[nr - 1][3]); + } + if (*nrels_mag >= 5) { + data->sg_tl[nr - 1][4] = TEMP_TO_REG(results[4]); + it87_write_value(client, IT87_REG_SG_TL_OVR(nr), data->sg_tl[nr - 1][4]); + } + } +} + +void it87_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = VID_FROM_REG(data->vid); + *nrels_mag = 1; + } +} + +void it87_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void it87_fan_div(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + results[2] = DIV_FROM_REG(data->fan_div[2]);; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = it87_read_value(client, IT87_REG_FAN_DIV); + if (*nrels_mag >= 3) { + data->fan_div[2] = DIV_TO_REG(results[2]); + if (data->fan_div[2] != 3) { + data->fan_div[2] = 1; + old = (old & 0xbf); + } else { + old = (old | 0x40); + } + } + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0xc3) | (data->fan_div[1] << 3); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xf8) | data->fan_div[0]; + it87_write_value(client, IT87_REG_FAN_DIV, old); + } + } +} + +void it87_fan_ctl(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int index = ctl_name - IT87_SYSCTL_FAN_CTL; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + it87_update_client(client); + results[0] = data->fan_ctl[index]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_ctl[index] = results[0]; + if( index == 0 ) + it87_write_value(client, IT87_REG_FAN_CTRL, data->fan_ctl[index] ); + else + it87_write_value(client, IT87_REG_FAN_ONOFF, data->fan_ctl[index] ); + } + } +} + +void it87_sens(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct it87_data *data = client->data; + int nr = 1 + ctl_name - IT87_SYSCTL_SENS1; + u8 tmp, val1, val2; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->sens[nr - 1]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + val1 = 0x01 << (nr - 1); + val2 = 0x08 << (nr - 1); + tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); + switch (results[0]) { + case PIIDIODE: + tmp &= ~ val2; + tmp |= val1; + break; + case THERMISTOR: + tmp &= ~ val1; + tmp |= val2; + break; + case UNUSED: + tmp &= ~ val1; + tmp &= ~ val2; + break; + default: + printk(KERN_ERR "it87.o: Invalid sensor type %ld; " + "must be 0 (unused), 2 (thermistor) " + "or 3 (diode)\n", results[0]); + return; + } + it87_write_value(client, + IT87_REG_TEMP_ENABLE, tmp); + data->sens[nr - 1] = results[0]; + } + } +} + +static int __init sm_it87_init(void) +{ + int addr; + + printk("it87.o version %s (%s)\n", LM_VERSION, LM_DATE); + if (!it87_find(&addr)) { + normal_isa[0] = addr; + } + return i2c_add_driver(&it87_driver); +} + +static void __exit sm_it87_exit(void) +{ + i2c_del_driver(&it87_driver); +} + + +MODULE_AUTHOR("Chris Gauthron "); +MODULE_DESCRIPTION("IT8705F, IT8712F, Sis950 driver"); +MODULE_PARM(update_vbat, "i"); +MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); +MODULE_PARM(reset, "i"); +MODULE_PARM_DESC(reset, "Reset the chip's registers, default no"); + +module_init(sm_it87_init); +module_exit(sm_it87_exit); --- linux-old/drivers/sensors/lm63.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/lm63.c Sun Feb 26 11:18:39 2006 @@ -0,0 +1,646 @@ +/* + * lm63.c - driver for the National Semiconductor LM63 temperature sensor + * with integrated fan control + * Copyright (C) 2004 Jean Delvare + * Based on the lm90 driver. + * + * The LM63 is a sensor chip made by National Semiconductor. It measures + * two temperatures (its own and one external one) and the speed of one + * fan, those speed it can additionally control. Complete datasheet can be + * obtained from National's website at: + * http://www.national.com/pf/LM/LM63.html + * + * The LM63 is basically an LM86 with fan speed monitoring and control + * capabilities added. It misses some of the LM86 features though: + * - No low limit for local temperature. + * - No critical limit for local temperature. + * - Critical limit for remote temperature can be changed only once. We + * will consider that the critical limit is read-only. + * + * The datasheet isn't very clear about what the tachometer reading is. + * I had a explanation from National Semiconductor though. The two lower + * bits of the read value have to be masked out. The value is still 16 bit + * in width. + * + * 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 "20060214" +#define LM_VERSION "2.10.0" + +/* + * 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_1(lm63); + +/* + * The LM63 registers + */ + +#define LM63_REG_CONFIG1 0x03 +#define LM63_REG_CONFIG2 0xBF +#define LM63_REG_CONFIG_FAN 0x4A + +#define LM63_REG_TACH_COUNT_MSB 0x47 +#define LM63_REG_TACH_COUNT_LSB 0x46 +#define LM63_REG_TACH_LIMIT_MSB 0x49 +#define LM63_REG_TACH_LIMIT_LSB 0x48 + +#define LM63_REG_PWM_VALUE 0x4C +#define LM63_REG_PWM_FREQ 0x4D + +#define LM63_REG_LOCAL_TEMP 0x00 +#define LM63_REG_LOCAL_HIGH 0x05 + +#define LM63_REG_REMOTE_TEMP_MSB 0x01 +#define LM63_REG_REMOTE_TEMP_LSB 0x10 +#define LM63_REG_REMOTE_OFFSET_MSB 0x11 +#define LM63_REG_REMOTE_OFFSET_LSB 0x12 +#define LM63_REG_REMOTE_HIGH_MSB 0x07 +#define LM63_REG_REMOTE_HIGH_LSB 0x13 +#define LM63_REG_REMOTE_LOW_MSB 0x08 +#define LM63_REG_REMOTE_LOW_LSB 0x14 +#define LM63_REG_REMOTE_TCRIT 0x19 +#define LM63_REG_REMOTE_TCRIT_HYST 0x21 + +#define LM63_REG_ALERT_STATUS 0x02 +#define LM63_REG_ALERT_MASK 0x16 + +#define LM63_REG_MAN_ID 0xFE +#define LM63_REG_CHIP_ID 0xFF + +/* + * Conversions and various macros + * For tachometer counts, the LM63 uses 16-bit values. + * For local temperature and high limit, remote critical limit and hysteresis + * value, it uses signed 8-bit values with LSB = 1 degree Celsius. + * For remote temperature, low and high limits, it uses signed 11-bit values + * with LSB = 0.125 degree Celsius, left-justified in 16-bit registers. + */ + +#define FAN_FROM_REG(reg) ((reg) == 0xFFFC || (reg) == 0 ? 0 : \ + 5400000 / (reg)) +#define FAN_TO_REG(val) ((val) <= 82 ? 0xFFFF : \ + (5400000 / (val)) & 0xFFFC) +#define TEMP8_FROM_REG(reg) (reg) +#define TEMP8_TO_REG(val) ((val) <= -128 ? -128 : \ + (val) >= 127 ? 127 : \ + val) +#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125) +#define TEMP11_TO_REG(val) ((val) <= -128000 ? 0x8000 : \ + (val) >= 127875 ? 0x7FE0 : \ + (val) < 0 ? ((val) - 62) / 125 * 32 : \ + ((val) + 62) / 125 * 32) +#define HYST_TO_REG(val) ((val) <= 0 ? 0 : \ + (val) >= 127 ? 127 : \ + val) + +/* + * Functions declaration + */ + +static int lm63_attach_adapter(struct i2c_adapter *adapter); +static int lm63_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void lm63_init_client(struct i2c_client *client); +static int lm63_detach_client(struct i2c_client *client); + +static void lm63_local_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm63_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm63_remote_tcrit(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm63_remote_tcrit_hyst(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm63_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm63_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm63_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +/* + * Driver data (common to all clients) + */ + +static struct i2c_driver lm63_driver = { + .name = "LM63 sensor driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = lm63_attach_adapter, + .detach_client = lm63_detach_client, +}; + +/* + * Client data (each client gets its own) + */ + +struct lm63_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u8 config, config_fan; + u16 fan1_input; + u16 fan1_low; + u8 pwm1_freq; + u8 pwm1_value; + s8 temp1_input; + s8 temp1_high; + s16 temp2_input; + s16 temp2_high; + s16 temp2_low; + s8 temp2_crit; + u8 temp2_crit_hyst; + u8 alarms; +}; + +/* + * Proc entries + * These files are created for each detected LM63. + */ + +/* -- SENSORS SYSCTL START -- */ + +#define LM63_SYSCTL_TEMP1 1200 +#define LM63_SYSCTL_TEMP2 1201 +#define LM63_SYSCTL_TEMP2_TCRIT 1205 +#define LM63_SYSCTL_TEMP2_TCRIT_HYST 1208 +#define LM63_SYSCTL_ALARMS 1210 +#define LM63_SYSCTL_FAN1 1220 +#define LM63_SYSCTL_PWM1 1230 + +#define LM63_ALARM_LOCAL_HIGH 0x40 +#define LM63_ALARM_REMOTE_HIGH 0x10 +#define LM63_ALARM_REMOTE_LOW 0x08 +#define LM63_ALARM_REMOTE_CRIT 0x02 +#define LM63_ALARM_REMOTE_OPEN 0x04 +#define LM63_ALARM_FAN_LOW 0x01 + +/* -- SENSORS SYSCTL END -- */ + +static ctl_table lm63_dir_table_template[] = +{ + {LM63_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_local_temp}, + {LM63_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_remote_temp}, + {LM63_SYSCTL_TEMP2_TCRIT, "temp2_crit", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_remote_tcrit}, + {LM63_SYSCTL_TEMP2_TCRIT_HYST, "temp2_crit_hyst", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_remote_tcrit_hyst}, + {LM63_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_alarms}, + {LM63_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_fan}, + {LM63_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_pwm}, + {0} +}; + +/* + * Real code + */ + +static int lm63_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, lm63_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int lm63_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *new_client; + struct lm63_data *data; + int err = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { +#ifdef DEBUG + printk(KERN_DEBUG "lm63: adapter doesn't support SMBus byte " + "data mode, skipping.\n"); +#endif + return 0; + } + + if (!(data = kmalloc(sizeof(struct lm63_data), GFP_KERNEL))) { + printk(KERN_ERR "lm63: Out of memory in lm63_detect\n"); + return -ENOMEM; + } + + /* + * The common I2C client data is placed right before the + * LM63-specific data. The LM63-specific data is pointed to by the + * data field from the i2c_client structure. + */ + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm63_driver; + new_client->flags = 0; + + /* Default to an LM63 if forced */ + if (kind == 0) + kind = lm63; + + if (kind < 0) { /* must identify */ + u8 man_id, chip_id, reg_config1, reg_config2; + u8 reg_alert_status, reg_alert_mask; + + man_id = i2c_smbus_read_byte_data(new_client, + LM63_REG_MAN_ID); + chip_id = i2c_smbus_read_byte_data(new_client, + LM63_REG_CHIP_ID); + reg_config1 = i2c_smbus_read_byte_data(new_client, + LM63_REG_CONFIG1); + reg_config2 = i2c_smbus_read_byte_data(new_client, + LM63_REG_CONFIG2); + reg_alert_status = i2c_smbus_read_byte_data(new_client, + LM63_REG_ALERT_STATUS); + reg_alert_mask = i2c_smbus_read_byte_data(new_client, + LM63_REG_ALERT_MASK); + + if (man_id == 0x01 /* National Semiconductor */ + && chip_id == 0x41 /* LM63 */ + && (reg_config1 & 0x18) == 0x00 + && (reg_config2 & 0xF8) == 0x00 + && (reg_alert_status & 0x20) == 0x00 + && (reg_alert_mask & 0xA4) == 0xA4) { + kind = lm63; + } else { +#ifdef DEBUG + printk(KERN_DEBUG "lm63: Unsupported chip " + "(man_id=0x%02X, chip_id=0x%02X)\n", + man_id, chip_id); +#endif + goto ERROR1; + } + } + + /* Fill in the remaining client fields */ + strcpy(new_client->name, "LM63 chip"); + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) { + printk(KERN_ERR "lm63: Failed attaching client\n"); + goto ERROR1; + } + + /* Register a new directory entry */ + if ((err = i2c_register_entry(new_client, "lm63", + lm63_dir_table_template, THIS_MODULE)) < 0) { + printk(KERN_ERR "lm63: Failed registering directory entry\n"); + goto ERROR2; + } + data->sysctl_id = err; + + + /* Initialize the LM63 chip */ + lm63_init_client(new_client); + + return 0; + +ERROR2: + i2c_detach_client(new_client); +ERROR1: + kfree(data); + return err; +} + +/* Idealy we shouldn't have to initialize anything, since the BIOS + should have taken care of everything */ +static void lm63_init_client(struct i2c_client *client) +{ + struct lm63_data *data = client->data; + + data->config = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1); + data->config_fan = i2c_smbus_read_byte_data(client, + LM63_REG_CONFIG_FAN); + + /* Start converting if needed */ + if (data->config & 0x40) { /* standby */ +#ifdef DEBUG + printk(KERN_DEBUG "lm63: Switching to operational mode"); +#endif + data->config &= 0xA7; + i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1, + data->config); + } + + /* We may need pwm1_freq before ever updating the client data */ + data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ); + if (data->pwm1_freq == 0) + data->pwm1_freq = 1; + +#ifdef DEBUG + /* Show some debug info about the LM63 configuration */ + printk(KERN_DEBUG "lm63: Alert/tach pin configured for %s\n", + (data->config & 0x04) ? "tachometer input" : + "alert output"); + printk(KERN_DEBUG "lm63: PWM clock %s kHz, output frequency %u Hz\n", + (data->config_fan & 0x08) ? "1.4" : "360", + ((data->config_fan & 0x08) ? 700 : 180000) / data->pwm1_freq); + printk(KERN_DEBUG "lm63: PWM output active %s, %s mode\n", + (data->config_fan & 0x10) ? "low" : "high", + (data->config_fan & 0x20) ? "manual" : "auto"); +#endif +} + +static int lm63_detach_client(struct i2c_client *client) +{ + struct lm63_data *data = client->data; + int err; + + i2c_deregister_entry(data->sysctl_id); + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "lm63: Client deregistration failed, client " + "not detached.\n"); + return err; + } + + kfree(data); + return 0; +} + +static void lm63_update_client(struct i2c_client *client) +{ + struct lm63_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ) || + (jiffies < data->last_updated) || + !data->valid) { + if (data->config & 0x04) { /* tachometer enabled */ + /* order matters for fan1_input */ + data->fan1_input = i2c_smbus_read_byte_data(client, + LM63_REG_TACH_COUNT_LSB) & 0xFC; + data->fan1_input |= i2c_smbus_read_byte_data(client, + LM63_REG_TACH_COUNT_MSB) << 8; + data->fan1_low = (i2c_smbus_read_byte_data(client, + LM63_REG_TACH_LIMIT_LSB) & 0xFC) + | (i2c_smbus_read_byte_data(client, + LM63_REG_TACH_LIMIT_MSB) << 8); + } + + data->pwm1_freq = i2c_smbus_read_byte_data(client, + LM63_REG_PWM_FREQ); + if (data->pwm1_freq == 0) + data->pwm1_freq = 1; + data->pwm1_value = i2c_smbus_read_byte_data(client, + LM63_REG_PWM_VALUE); + + data->temp1_input = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_TEMP); + data->temp1_high = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_HIGH); + + /* order matters for temp2_input */ + data->temp2_input = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TEMP_MSB) << 8; + data->temp2_input |= i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TEMP_LSB); + data->temp2_high = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_HIGH_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_HIGH_LSB); + data->temp2_low = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_LOW_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_LOW_LSB); + data->temp2_crit = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TCRIT); + data->temp2_crit_hyst = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TCRIT_HYST); + + /* Mask out Busy bit in status register */ + data->alarms = i2c_smbus_read_byte_data(client, + LM63_REG_ALERT_STATUS) & 0x7F; + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static void lm63_local_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + lm63_update_client(client); + results[0] = TEMP8_FROM_REG(data->temp1_high); + results[1] = TEMP8_FROM_REG(data->temp1_input); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp1_high = TEMP8_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, LM63_REG_LOCAL_HIGH, + data->temp1_high); + } + } +} + +static void lm63_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + lm63_update_client(client); + results[0] = TEMP11_FROM_REG(data->temp2_high); + results[1] = TEMP11_FROM_REG(data->temp2_low); + results[2] = TEMP11_FROM_REG(data->temp2_input); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp2_high = TEMP11_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, + LM63_REG_REMOTE_HIGH_MSB, + data->temp2_high >> 8); + i2c_smbus_write_byte_data(client, + LM63_REG_REMOTE_HIGH_LSB, + data->temp2_high & 0xFF); + } + if (*nrels_mag >= 2) { + data->temp2_low = TEMP11_TO_REG(results[1]); + i2c_smbus_write_byte_data(client, + LM63_REG_REMOTE_LOW_MSB, + data->temp2_low >> 8); + i2c_smbus_write_byte_data(client, + LM63_REG_REMOTE_LOW_LSB, + data->temp2_low & 0xFF); + } + } +} + +static void lm63_remote_tcrit(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + lm63_update_client(client); + results[0] = TEMP8_FROM_REG(data->temp2_crit); + *nrels_mag = 1; + } +} + +static void lm63_remote_tcrit_hyst(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + lm63_update_client(client); + results[0] = TEMP8_FROM_REG(data->temp2_crit) - + TEMP8_FROM_REG(data->temp2_crit_hyst); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp2_crit_hyst = HYST_TO_REG(data->temp2_crit - + results[0]); + i2c_smbus_write_byte_data(client, + LM63_REG_REMOTE_TCRIT_HYST, + data->temp2_crit_hyst); + } + } +} + +static void lm63_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + lm63_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +static void lm63_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + if (!(data->config & 0x04)) { /* tachometer disabled */ + results[0] = 0; + *nrels_mag = 1; + return; + } + + lm63_update_client(client); + results[0] = FAN_FROM_REG(data->fan1_low); + results[1] = FAN_FROM_REG(data->fan1_input); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (!(data->config & 0x04)) /* tachometer disabled */ + return; + + if (*nrels_mag >= 1) { + data->fan1_low = FAN_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, + LM63_REG_TACH_LIMIT_LSB, + data->fan1_low & 0xFF); + i2c_smbus_write_byte_data(client, + LM63_REG_TACH_LIMIT_MSB, + data->fan1_low >> 8); + } + } +} + +static void lm63_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm63_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + lm63_update_client(client); + results[0] = data->pwm1_value >= 2 * data->pwm1_freq ? 255 : + (data->pwm1_value * 255 + data->pwm1_freq) / + (2 * data->pwm1_freq); + results[1] = data->config_fan & 0x20 ? 1 : 2; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1 && (data->config_fan & 0x20)) { + data->pwm1_value = results[0] <= 0 ? 0 : + results[0] >= 255 ? 2 * data->pwm1_freq : + (results[0] * data->pwm1_freq * 2 + 127) / 255; + i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, + data->pwm1_value); + } + } +} + +static int __init lm63_init(void) +{ + printk(KERN_INFO "lm63 version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&lm63_driver); +} + +static void __exit lm63_exit(void) +{ + i2c_del_driver(&lm63_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("LM63 sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(lm63_init); +module_exit(lm63_exit); --- linux-old/drivers/sensors/lm75.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/lm75.c Sun Feb 26 11:18:39 2006 @@ -0,0 +1,331 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" +#include "lm75.h" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(lm75); + +/* Many LM75 constants specified below */ + +/* The LM75 registers */ +#define LM75_REG_TEMP 0x00 +#define LM75_REG_CONF 0x01 +#define LM75_REG_TEMP_HYST 0x02 +#define LM75_REG_TEMP_OS 0x03 + +/* Each client has this additional data */ +struct lm75_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 temp, temp_os, temp_hyst; /* Register values */ +}; + +static int lm75_attach_adapter(struct i2c_adapter *adapter); +static int lm75_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void lm75_init_client(struct i2c_client *client); +static int lm75_detach_client(struct i2c_client *client); + +static int lm75_read_value(struct i2c_client *client, u8 reg); +static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value); +static void lm75_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm75_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver lm75_driver = { + .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 Celsius * 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_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, lm75_detect); +} + +/* This function is called by i2c_detect */ +static int lm75_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct lm75_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("lm75.o: lm75_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + goto error0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access lm75_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct lm75_data), GFP_KERNEL))) { + err = -ENOMEM; + goto error0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm75_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. There is no identification- + dedicated register so we have to rely on several tricks: + unused bits, registers cycling over 8-address boundaries, + addresses 0x04-0x07 returning the last read value. + The cycling+unused addresses combination is not tested, + since it would significantly slow the detection down and would + hardly add any value. */ + if (kind < 0) { + int cur, conf, hyst, os; + + /* Unused addresses */ + cur = i2c_smbus_read_word_data(new_client, 0); + conf = i2c_smbus_read_byte_data(new_client, 1); + hyst = i2c_smbus_read_word_data(new_client, 2); + if (i2c_smbus_read_word_data(new_client, 4) != hyst + || i2c_smbus_read_word_data(new_client, 5) != hyst + || i2c_smbus_read_word_data(new_client, 6) != hyst + || i2c_smbus_read_word_data(new_client, 7) != hyst) + goto error1; + os = i2c_smbus_read_word_data(new_client, 3); + if (i2c_smbus_read_word_data(new_client, 4) != os + || i2c_smbus_read_word_data(new_client, 5) != os + || i2c_smbus_read_word_data(new_client, 6) != os + || i2c_smbus_read_word_data(new_client, 7) != os) + goto error1; + + /* Unused bits */ + if (conf & 0xe0) + goto error1; + + /* Addresses cycling */ + for (i = 8; i < 0xff; i += 8) + if (i2c_smbus_read_byte_data(new_client, i + 1) != conf + || i2c_smbus_read_word_data(new_client, i + 2) != hyst + || i2c_smbus_read_word_data(new_client, i + 3) != os) + goto error1; + } + + /* Determine the chip type - only one kind supported! */ + if (kind <= 0) + kind = lm75; + + if (kind == lm75) { + type_name = "lm75"; + client_name = "LM75 chip"; + } else { + pr_debug("lm75.o: Internal error: unknown kind (%d)?!?", kind); + goto error1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto error4; + } + data->sysctl_id = i; + + lm75_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + error4: + i2c_detach_client(new_client); + error3: + error1: + kfree(data); + error0: + return err; +} + +static int lm75_detach_client(struct i2c_client *client) +{ + struct lm75_data *data = client->data; + + i2c_deregister_entry(data->sysctl_id); + i2c_detach_client(client); + kfree(client->data); + return 0; +} + +/* All registers are word-sized, except for the configuration register. + LM75 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int lm75_read_value(struct i2c_client *client, u8 reg) +{ + if (reg == LM75_REG_CONF) + return i2c_smbus_read_byte_data(client, reg); + else + return swab16(i2c_smbus_read_word_data(client, reg)); +} + +/* All registers are word-sized, except for the configuration register. + LM75 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if (reg == LM75_REG_CONF) + return i2c_smbus_write_byte_data(client, reg, value); + else + return i2c_smbus_write_word_data(client, reg, swab16(value)); +} + +static void lm75_init_client(struct i2c_client *client) +{ + int i; + + /* Enable if in shutdown mode */ + i = lm75_read_value(client, LM75_REG_CONF); + if (i >= 0 && (i & 0x01)) + lm75_write_value(client, LM75_REG_CONF, i & 0xfe); +} + +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 Sun Feb 26 11:18:39 2006 @@ -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 Sun Feb 26 11:18:39 2006 @@ -0,0 +1,723 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +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; + 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), -128, 127)) +#define TEMP_FROM_REG(val) ((val)*10) + +#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\ + 205-(val)*5) + +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) +#define DIV_FROM_REG(val) (1 << (val)) + +/* There are some complications in a module like this. First off, LM78 chips + may be both present on the SMBus and the ISA bus, and we have to handle + those cases separately at some places. Second, there might be several + LM78 chips available (well, actually, that is probably never done; but + it is a clean illustration of how to handle a case like that). Finally, + a specific chip may be attached to *both* ISA and SMBus, and we would + not like to detect it double. Fortunately, in the case of the LM78 at + least, a register tells us what SMBus address we are on, so that helps + a bit - except if there could be more than one SMBus. Groan. No solution + for this yet. */ + +/* This module may seem overly long and complicated. In fact, it is not so + bad. Quite a lot of bookkeeping is done. A real driver can often cut + some corners. */ + +/* For each registered LM78, we need to keep some data in memory. That + data is pointed to by lm78_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new lm78 client is + allocated. */ +struct lm78_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + s8 temp; /* Register value */ + s8 temp_over; /* Register value */ + s8 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 = { + .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, +}; + +/* 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 Celsius * 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 */ +static int lm78_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct lm78_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + int is_isa = i2c_is_isa_adapter(adapter); + + if (!is_isa + && !i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) goto + ERROR0; + + if (is_isa) { + if (check_region(address, LM78_EXTENT)) + goto ERROR0; + } + + /* Probe whether there is anything available on this address. Already + done for SMBus clients */ + if (kind < 0) { + if (is_isa) { + +#define REALLY_SLOW_IO + /* We need the timeouts for at least some LM78-like chips. But only + if we read 'undefined' registers. */ + i = inb_p(address + 1); + if (inb_p(address + 2) != i) + goto ERROR0; + if (inb_p(address + 3) != i) + goto ERROR0; + if (inb_p(address + 7) != i) + goto ERROR0; +#undef REALLY_SLOW_IO + + /* Let's just hope nothing breaks here */ + i = inb_p(address + 5) & 0x7f; + outb_p(~i & 0x7f, address + 5); + if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) { + outb_p(i, address + 5); + return 0; + } + } + } + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access lm78_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct lm78_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + if (is_isa) + init_MUTEX(&data->lock); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm78_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if (lm78_read_value(new_client, LM78_REG_CONFIG) & 0x80) + goto ERROR1; + if (!is_isa + && (lm78_read_value(new_client, LM78_REG_I2C_ADDR) != + address)) goto ERROR1; + } + + /* Determine the chip type. */ + if (kind <= 0) { + i = lm78_read_value(new_client, LM78_REG_CHIPID); + if (i == 0x00 || i == 0x20) + kind = lm78; + else if (i == 0x40) + kind = lm78j; + else if ((i & 0xfe) == 0xc0) + kind = lm79; + else { + if (kind == 0) + printk + ("lm78.o: Ignoring 'force' parameter for unknown chip at " + "adapter %d, address 0x%02x\n", + i2c_adapter_id(adapter), address); + goto ERROR1; + } + } + + if (kind == lm78) { + type_name = "lm78"; + client_name = "LM78 chip"; + } else if (kind == lm78j) { + type_name = "lm78-j"; + client_name = "LM78-J chip"; + } else if (kind == lm79) { + type_name = "lm79"; + client_name = "LM79 chip"; + } else { +#ifdef DEBUG + printk("lm78.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Reserve the ISA region */ + if (is_isa) + request_region(address, LM78_EXTENT, type_name); + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the LM78 chip */ + lm78_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + if (is_isa) + release_region(address, LM78_EXTENT); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int lm78_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct lm78_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("lm78.o: Client deregistration failed, client not detached.\n"); + return err; + } + + if(i2c_is_isa_client(client)) + release_region(client->addr, LM78_EXTENT); + kfree(client->data); + + return 0; +} + +/* The SMBus locks itself, but ISA access must be locked explicitly! + 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 explicitly! + We don't want to lock the whole ISA bus, so we lock each client + separately. + We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks, + would slow down the LM78 access and should not be necessary. + There are some ugly typecasts here, but the good new is - they should + nowhere else be necessary! */ +static int lm78_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + if (i2c_is_isa_client(client)) { + down(&(((struct lm78_data *) (client->data))->lock)); + outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET); + outb_p(value, client->addr + LM78_DATA_REG_OFFSET); + up(&(((struct lm78_data *) (client->data))->lock)); + return 0; + } else + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new LM78. */ +static void lm78_init_client(struct i2c_client *client) +{ + u8 config = lm78_read_value(client, LM78_REG_CONFIG); + + /* Start monitoring */ + if (!(config & 0x01)) + lm78_write_value(client, LM78_REG_CONFIG, + (config & 0xf7) | 0x01); +} + +static void lm78_update_client(struct i2c_client *client) +{ + struct lm78_data *data = client->data; + int i; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting lm78 update\n"); +#endif + for (i = 0; i <= 6; i++) { + data->in[i] = + lm78_read_value(client, LM78_REG_IN(i)); + data->in_min[i] = + lm78_read_value(client, LM78_REG_IN_MIN(i)); + data->in_max[i] = + lm78_read_value(client, LM78_REG_IN_MAX(i)); + } + for (i = 1; i <= 3; i++) { + data->fan[i - 1] = + lm78_read_value(client, LM78_REG_FAN(i)); + data->fan_min[i - 1] = + lm78_read_value(client, LM78_REG_FAN_MIN(i)); + } + data->temp = lm78_read_value(client, LM78_REG_TEMP); + data->temp_over = + lm78_read_value(client, LM78_REG_TEMP_OVER); + data->temp_hyst = + lm78_read_value(client, LM78_REG_TEMP_HYST); + i = lm78_read_value(client, LM78_REG_VID_FANDIV); + data->vid = i & 0x0f; + if (data->type == lm79) + data->vid |= + (lm78_read_value(client, LM78_REG_CHIPID) & + 0x01) << 4; + else + data->vid |= 0x10; + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = lm78_read_value(client, LM78_REG_ALARM1) + + (lm78_read_value(client, LM78_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + + data->fan_div[2] = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +void lm78_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm78_data *data = client->data; + int nr = ctl_name - LM78_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm78_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + lm78_write_value(client, LM78_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + lm78_write_value(client, LM78_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void lm78_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm78_data *data = client->data; + int nr = ctl_name - LM78_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm78_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data-> + fan_div[nr - 1])); + results[1] = + FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = FAN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr - + 1])); + lm78_write_value(client, LM78_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + + +void lm78_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm78_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm78_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + lm78_write_value(client, LM78_REG_TEMP_OVER, + data->temp_over); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + lm78_write_value(client, LM78_REG_TEMP_HYST, + data->temp_hyst); + } + } +} + +void lm78_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm78_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm78_update_client(client); + results[0] = VID_FROM_REG(data->vid); + *nrels_mag = 1; + } +} + +void lm78_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm78_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm78_update_client(client); + results[0] = 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 Sun Feb 26 11:18:40 2006 @@ -0,0 +1,600 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(lm80); + +/* Many LM80 constants specified below */ + +/* The LM80 registers */ +#define LM80_REG_IN_MAX(nr) (0x2a + (nr) * 2) +#define LM80_REG_IN_MIN(nr) (0x2b + (nr) * 2) +#define LM80_REG_IN(nr) (0x20 + (nr)) + +#define LM80_REG_FAN1_MIN 0x3c +#define LM80_REG_FAN2_MIN 0x3d +#define LM80_REG_FAN1 0x28 +#define LM80_REG_FAN2 0x29 + +#define LM80_REG_TEMP 0x27 +#define LM80_REG_TEMP_HOT_MAX 0x38 +#define LM80_REG_TEMP_HOT_HYST 0x39 +#define LM80_REG_TEMP_OS_MAX 0x3a +#define LM80_REG_TEMP_OS_HYST 0x3b + +#define LM80_REG_CONFIG 0x00 +#define LM80_REG_ALARM1 0x01 +#define LM80_REG_ALARM2 0x02 +#define LM80_REG_MASK1 0x03 +#define LM80_REG_MASK2 0x04 +#define LM80_REG_FANDIV 0x05 +#define LM80_REG_RES 0x06 + + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ + +#define IN_TO_REG(val) (SENSORS_LIMIT((val),0,255)) +#define IN_FROM_REG(val) (val) + +static inline unsigned char FAN_TO_REG(unsigned rpm, unsigned div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:\ + (val)==255?0:1350000/((div)*(val))) + +static inline long TEMP_FROM_REG(u16 temp) +{ + long res; + + temp >>= 4; + if (temp < 0x0800) + res = 625 * (long) temp; + else + res = ((long) temp - 0x01000) * 625; + + return res / 100; +} + +#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*100) + +#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-50)/100):\ + ((val)+50)/100), \ + 0,255) + +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) + +struct lm80_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 temp; /* Register values, shifted right */ + u8 temp_hot_max; /* Register value */ + u8 temp_hot_hyst; /* Register value */ + u8 temp_os_max; /* Register value */ + u8 temp_os_hyst; /* Register value */ + u16 alarms; /* Register encoding, combined */ +}; + + + +static int lm80_attach_adapter(struct i2c_adapter *adapter); +static int lm80_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int lm80_detach_client(struct i2c_client *client); + +static int lm80_read_value(struct i2c_client *client, u8 reg); +static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value); +static void lm80_update_client(struct i2c_client *client); +static void lm80_init_client(struct i2c_client *client); + + +static void lm80_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void lm80_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm80_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm80_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm80_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver lm80_driver = { + .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 Celsius * 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); +} + +static int lm80_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, cur; + struct i2c_client *new_client; + struct lm80_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("lm80.o: lm80_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access lm80_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct lm80_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm80_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. It is lousy. */ + if (lm80_read_value(new_client, LM80_REG_ALARM2) & 0xc0) + goto ERROR1; + for (i = 0x2a; i <= 0x3d; i++) { + cur = i2c_smbus_read_byte_data(new_client, i); + if ((i2c_smbus_read_byte_data(new_client, i + 0x40) != cur) + || (i2c_smbus_read_byte_data(new_client, i + 0x80) != + cur) + || (i2c_smbus_read_byte_data(new_client, i + 0xc0) != + cur)) goto ERROR1; + } + + /* Determine the chip type - only one kind supported! */ + if (kind <= 0) + kind = lm80; + + if (kind == lm80) { + type_name = "lm80"; + client_name = "LM80 chip"; + } else { +#ifdef DEBUG + printk("lm80.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + lm80_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int lm80_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct lm80_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("lm80.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +static int lm80_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new LM80. */ +static void lm80_init_client(struct i2c_client *client) +{ + /* Reset all except Watchdog values and last conversion values + This sets fan-divs to 2, among others. This makes most other + initializations unnecessary */ + lm80_write_value(client, LM80_REG_CONFIG, 0x80); + /* Set 11-bit temperature resolution */ + lm80_write_value(client, LM80_REG_RES, 0x08); + + /* Start monitoring */ + lm80_write_value(client, LM80_REG_CONFIG, 0x01); +} + +static void lm80_update_client(struct i2c_client *client) +{ + struct lm80_data *data = client->data; + int i; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > 2 * HZ) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting lm80 update\n"); +#endif + for (i = 0; i <= 6; i++) { + data->in[i] = + lm80_read_value(client, LM80_REG_IN(i)); + data->in_min[i] = + lm80_read_value(client, LM80_REG_IN_MIN(i)); + data->in_max[i] = + lm80_read_value(client, LM80_REG_IN_MAX(i)); + } + data->fan[0] = lm80_read_value(client, LM80_REG_FAN1); + data->fan_min[0] = + lm80_read_value(client, LM80_REG_FAN1_MIN); + data->fan[1] = lm80_read_value(client, LM80_REG_FAN2); + data->fan_min[1] = + lm80_read_value(client, LM80_REG_FAN2_MIN); + + data->temp = + (lm80_read_value(client, LM80_REG_TEMP) << 8) | + (lm80_read_value(client, LM80_REG_RES) & 0xf0); + data->temp_os_max = + lm80_read_value(client, LM80_REG_TEMP_OS_MAX); + data->temp_os_hyst = + lm80_read_value(client, LM80_REG_TEMP_OS_HYST); + data->temp_hot_max = + lm80_read_value(client, LM80_REG_TEMP_HOT_MAX); + data->temp_hot_hyst = + lm80_read_value(client, LM80_REG_TEMP_HOT_HYST); + + i = lm80_read_value(client, LM80_REG_FANDIV); + data->fan_div[0] = (i >> 2) & 0x03; + data->fan_div[1] = (i >> 4) & 0x03; + data->alarms = lm80_read_value(client, LM80_REG_ALARM1) + + (lm80_read_value(client, LM80_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +void lm80_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm80_data *data = client->data; + int nr = ctl_name - LM80_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm80_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + lm80_write_value(client, LM80_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + lm80_write_value(client, LM80_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void lm80_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm80_data *data = client->data; + int nr = ctl_name - LM80_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm80_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data-> + fan_div[nr - 1])); + results[1] = + FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = FAN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr - + 1])); + lm80_write_value(client, + nr == + 1 ? LM80_REG_FAN1_MIN : + LM80_REG_FAN2_MIN, + data->fan_min[nr - 1]); + } + } +} + + +void lm80_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm80_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm80_update_client(client); + results[0] = TEMP_LIMIT_FROM_REG(data->temp_hot_max); + results[1] = TEMP_LIMIT_FROM_REG(data->temp_hot_hyst); + results[2] = TEMP_LIMIT_FROM_REG(data->temp_os_max); + results[3] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst); + results[4] = TEMP_FROM_REG(data->temp); + *nrels_mag = 5; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_hot_max = TEMP_LIMIT_TO_REG(results[0]); + lm80_write_value(client, LM80_REG_TEMP_HOT_MAX, + data->temp_hot_max); + } + if (*nrels_mag >= 2) { + data->temp_hot_hyst = + TEMP_LIMIT_TO_REG(results[1]); + lm80_write_value(client, LM80_REG_TEMP_HOT_HYST, + data->temp_hot_hyst); + } + if (*nrels_mag >= 3) { + data->temp_os_max = TEMP_LIMIT_TO_REG(results[2]); + lm80_write_value(client, LM80_REG_TEMP_OS_MAX, + data->temp_os_max); + } + if (*nrels_mag >= 4) { + data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[3]); + lm80_write_value(client, LM80_REG_TEMP_OS_HYST, + data->temp_os_hyst); + } + } +} + +void lm80_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm80_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm80_update_client(client); + results[0] = 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 Sun Feb 26 11:18:40 2006 @@ -0,0 +1,503 @@ +/* + * 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 "20060214" +#define LM_VERSION "2.10.0" + +/* + * 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 with LSB = 1 degree Celsius. + */ + +#define TEMP_FROM_REG(val) (val) +#define TEMP_TO_REG(val) ((val) <= -50 ? -50 : \ + (val) >= 127 ? 127 : (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 = { + .name = "LM83 sensor driver", + .id = I2C_DRIVERID_LM83, + .flags = I2C_DF_NOTIFY, + .attach_adapter = lm83_attach_adapter, + .detach_client = lm83_detach_client, +}; + +/* + * Client data (each client gets its own) + */ + +struct lm83_data +{ + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + s8 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} +}; + +/* + * Real code + */ + +static int lm83_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, lm83_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int lm83_detect(struct i2c_adapter *adapter, int address, unsigned + short flags, int kind) +{ + struct i2c_client *new_client; + struct lm83_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) + { + printk("lm83.o: Called for an ISA bus adapter, aborting.\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + { +#ifdef DEBUG + printk("lm83.o: I2C bus doesn't support byte read mode, " + "skipping.\n"); +#endif + return 0; + } + + if (!(data = kmalloc(sizeof(struct lm83_data), GFP_KERNEL))) + { + printk("lm83.o: Out of memory in lm83_detect (new_client).\n"); + return -ENOMEM; + } + + /* + * The common I2C client data is placed right before the + * LM83-specific data. The LM83-specific data is pointed to by the + * data field from the I2C client data. + */ + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm83_driver; + new_client->flags = 0; + + /* + * Now we do the remaining detection. A negative kind means that + * the driver was loaded with no force parameter (default), so we + * must both detect and identify the chip (actually there is only + * one possible kind of chip for now, LM83). A zero kind means that + * the driver was loaded with the force parameter, the detection + * step shall be skipped. A positive kind means that the driver + * was loaded with the force parameter and a given kind of chip is + * requested, so both the detection and the identification steps + * are skipped. + */ + + /* Default to an LM83 if forced */ + if (kind == 0) + kind = lm83; + + if (kind < 0) /* detection */ + { + if (((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) + & 0xA8) != 0x00) + || ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) + & 0x48) != 0x00) + || ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) + & 0x41) != 0x00)) + { +#ifdef DEBUG + printk(KERN_DEBUG "lm83.o: Detection failed at 0x%02x.\n", + address); +#endif + goto ERROR1; + } + } + + if (kind <= 0) /* identification */ + { + u8 man_id, chip_id; + + man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID); + chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID); + if (man_id == 0x01) /* National Semiconductor */ + { + if (chip_id == 0x03) + kind = lm83; + } + } + + if (kind <= 0) /* identification failed */ + { + printk("lm83.o: Unsupported chip.\n"); + goto ERROR1; + } + + if (kind == lm83) + { + type_name = "lm83"; + client_name = "LM83 chip"; + } + else + { + printk("lm83.o: Unknown kind %d.\n", kind); + goto ERROR1; + } + + /* + * OK, we got a valid chip so we can fill in the remaining client + * fields. + */ + + strcpy(new_client->name, client_name); + 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, THIS_MODULE)) < 0) + { +#ifdef DEBUG + printk("lm83.o: Failed registering directory entry.\n"); +#endif + goto ERROR2; + } + data->sysctl_id = err; + + /* + * Initialize the LM83 chip + * (Nothing to do for this one.) + */ + + return 0; + + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + return err; +} + +static int lm83_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct lm83_data *) (client->data))->sysctl_id); + if ((err = i2c_detach_client(client))) + { + printk("lm83.o: Client deregistration failed, client not " + "detached.\n"); + return err; + } + + kfree(client->data); + return 0; +} + +static void lm83_update_client(struct i2c_client *client) +{ + struct lm83_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ * 2) || + (jiffies < data->last_updated) || !data->valid) + { + int nr; +#ifdef DEBUG + printk("lm83.o: Updating LM83 data.\n"); +#endif + for (nr = 0; nr < 4 ; nr++) + { + data->temp[nr] = + i2c_smbus_read_byte_data(client, LM83_REG_R_TEMP[nr]); + data->temp_high[nr] = + i2c_smbus_read_byte_data(client, LM83_REG_R_HIGH[nr]); + } + data->tcrit = i2c_smbus_read_byte_data(client, LM83_REG_R_TCRIT); + data->alarms = + i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) + + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) << 8); + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static void lm83_temp(struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results) +{ + struct lm83_data *data = client->data; + int nr = ctl_name - LM83_SYSCTL_LOCAL_TEMP; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) + { + lm83_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_high[nr]); + results[1] = TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 2; + } + else if (operation == SENSORS_PROC_REAL_WRITE) + { + if (*nrels_mag >= 1) + { + data->temp_high[nr] = TEMP_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr], + data->temp_high[nr]); + } + } +} + +static void lm83_tcrit(struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results) +{ + struct lm83_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) + { + lm83_update_client(client); + results[0] = TEMP_FROM_REG(data->tcrit); + *nrels_mag = 1; + } + else if (operation == SENSORS_PROC_REAL_WRITE) + { + if (*nrels_mag >= 1) + { + data->tcrit = TEMP_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, LM83_REG_W_TCRIT, + data->tcrit); + } + } +} + +static void lm83_alarms(struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results) +{ + struct lm83_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) + { + lm83_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +static int __init sm_lm83_init(void) +{ + printk(KERN_INFO "lm83.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&lm83_driver); +} + +static void __exit sm_lm83_exit(void) +{ + i2c_del_driver(&lm83_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +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 Sun Feb 26 11:18:40 2006 @@ -0,0 +1,2039 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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_6(lm85b, lm85c, adm1027, adt7463, emc6d100, emc6d102); + +/* 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_ADT7463C 0x6A +#define LM85_VERSTEP_EMC6D100_A0 0x60 +#define LM85_VERSTEP_EMC6D100_A1 0x61 +#define LM85_VERSTEP_EMC6D102 0x65 + +#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) + +/* 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)) + +/* Typically used with Pentium 4 systems v9.1 VRM spec */ +#define LM85_INIT_VRM 91 + +/* Chip sampling rates + * + * Some sensors are not updated more frequently than once per second + * so it doesn't make sense to read them more often than that. + * We cache the results and return the saved data if the driver + * is called again before a second has elapsed. + * + * Also, there is significant configuration data for this chip + * given the automatic PWM fan control that is possible. There + * are about 47 bytes of config data to only 22 bytes of actual + * readings. So, we keep the config data up to date in the cache + * when it is written and only sample it once every 5 *minutes* + */ +#define LM85_DATA_INTERVAL (1 * HZ) +#define LM85_CONFIG_INTERVAL (5 * 60 * HZ) + +/* For each registered LM85, we need to keep some data in memory. That + data is pointed to by client->data. The structure itself is + dynamically allocated, when a new lm85 client is allocated. */ + +/* LM85 can automatically adjust fan speeds based on temperature + * This structure encapsulates an entire Zone config. There are + * three zones (one for each temperature input) on the lm85 + */ +struct lm85_zone { + s8 limit; /* Low temp limit */ + u8 hyst; /* Low limit hysteresis. (0-15) */ + u8 range; /* Temp range, encoded */ + s8 critical; /* "All fans ON" temp limit */ +}; + +struct lm85_autofan { + u8 config; /* Register value */ + u8 freq; /* PWM frequency, encoded */ + u8 min_pwm; /* Minimum PWM value, encoded */ + u8 min_off; /* Min PWM or OFF below "limit", flag */ +}; + +struct lm85_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_reading; /* In jiffies */ + unsigned long last_config; /* In jiffies */ + + u8 in[8]; /* Register value */ + u8 in_max[8]; /* Register value */ + u8 in_min[8]; /* Register value */ + s8 temp[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + s8 temp_offset[3]; /* Register value */ + u16 fan[4]; /* Register value */ + u16 fan_min[4]; /* Register value */ + u8 pwm[3]; /* Register value */ + u8 spinup_ctl; /* Register encoding, combined */ + u8 tach_mode; /* Register encoding, combined */ + u16 extend_adc; /* Register value */ + u8 fan_ppr; /* Register value */ + u8 smooth[3]; /* Register encoding */ + u8 vid; /* Register value */ + u8 vrm; /* VRM version */ + u8 syncpwm3; /* Saved PWM3 for TACH 2,3,4 config */ + s8 oppoint[3]; /* Register value */ + u16 tmin_ctl; /* Register value */ + long therm_total; /* Cummulative therm count */ + long therm_ovfl; /* Count of therm overflows */ + u8 therm_limit; /* Register value */ + u32 alarms; /* Register encoding, combined */ + u32 alarm_mask; /* Register encoding, combined */ + struct lm85_autofan autofan[3]; + struct lm85_zone zone[3]; +}; + +static int lm85_attach_adapter(struct i2c_adapter *adapter); +static int lm85_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int lm85_detach_client(struct i2c_client *client); +static int lm85_read_value(struct i2c_client *client, u16 register); +static int lm85_write_value(struct i2c_client *client, u16 register, int value); +static void lm85_update_client(struct i2c_client *client); +static void lm85_init_client(struct i2c_client *client); + + +static void lm85_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void lm85_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_zone(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_pwm_config(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_pwm_zone(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_smooth(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void lm85_spinup_ctl(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_tach_mode(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void adm1027_tach_mode(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1027_temp_offset(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1027_fan_ppr(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1027_alarm_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void adt7463_tmin_ctl(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adt7463_therm_signal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void emc6d100_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver lm85_driver = { + .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) */ +static int lm85_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, lm85_detect); +} + +/* This function is called by i2c_detect */ +static int lm85_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + int company, verstep ; + struct i2c_client *new_client; + struct lm85_data *data; + int err = 0; + const char *type_name = ""; + struct ctl_table template[CTLTBL_MAX] ; + int template_used ; + + if (i2c_is_isa_adapter(adapter)) { + /* This chip has no ISA interface */ + goto ERROR0 ; + }; + + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + /* We need to be able to do byte I/O */ + goto ERROR0 ; + }; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access lm85_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct lm85_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm85_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + company = lm85_read_value(new_client, LM85_REG_COMPANY); + verstep = lm85_read_value(new_client, LM85_REG_VERSTEP); + +#ifdef DEBUG + printk("lm85: Detecting device at %d,0x%02x with" + " COMPANY: 0x%02x and VERSTEP: 0x%02x\n", + i2c_adapter_id(new_client->adapter), new_client->addr, + company, verstep + ); +#endif + + /* If auto-detecting, Determine the chip type. */ + if (kind <= 0) { +#ifdef DEBUG + printk("lm85: Autodetecting device at %d,0x%02x ...\n", + i2c_adapter_id(adapter), address ); +#endif + if( company == LM85_COMPANY_NATIONAL + && verstep == LM85_VERSTEP_LM85C ) { + kind = lm85c ; + } else if( company == LM85_COMPANY_NATIONAL + && verstep == LM85_VERSTEP_LM85B ) { + kind = lm85b ; + } else if( company == LM85_COMPANY_NATIONAL + && (verstep & LM85_VERSTEP_VMASK) == LM85_VERSTEP_GENERIC) { + printk("lm85: Detected National Semiconductor chip\n"); + printk("lm85: Unrecgonized version/stepping 0x%02x" + " Defaulting to Generic LM85.\n", verstep ); + kind = any_chip ; + } else if( company == LM85_COMPANY_ANALOG_DEV + && verstep == LM85_VERSTEP_ADM1027 ) { + kind = adm1027 ; + } else if( company == LM85_COMPANY_ANALOG_DEV + && (verstep == LM85_VERSTEP_ADT7463 + || verstep == LM85_VERSTEP_ADT7463C) ) { + 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_EMC6D102) { + kind = emc6d102; + } 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 ); + } + 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 ; + case emc6d102 : + type_name = "emc6d102"; + strcpy(new_client->name, "SMSC EMC6D102"); + template_used = 0; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR2; + } + data->sysctl_id = i; + + return 0; + + /* Error out and cleanup code */ + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int lm85_detach_client(struct i2c_client *client) +{ + int err; + int id ; + + id = client->id; + i2c_deregister_entry(((struct lm85_data *)(client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("lm85(%d): Client deregistration failed," + " client not detached.\n", id ); + return err; + } + + kfree(client->data); + + return 0; +} + +static 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 ; +} + +static 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. */ +static 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); + +} + +static 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] = 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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. */ + +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(val) ((val)*10) + +#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(val)-5:\ + (val)+5)/10,-128,127) + +#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. The + structure is dynamically allocated whenever a new LM87 client is + found. */ +struct lm87_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[6]; /* Scaled Register value */ + u8 in_max[6]; /* Scaled Register value */ + u8 in_min[6]; /* Scaled Register value */ + u8 ain1; /* Register value */ + u8 ain1_min; /* Register value */ + u8 ain1_max; /* Register value */ + u8 ain2; /* Register value */ + u8 ain2_min; /* Register value */ + u8 ain2_max; /* Register value */ + u8 fan; /* Register value */ + u8 fan_min; /* Register value */ + u8 fan_div; /* Register encoding, shifted right */ + u8 fan2; /* Register value */ + u8 fan2_min; /* Register value */ + u8 fan2_div; /* Register encoding, shifted right */ + s8 ext2_temp; /* Register value */ + s8 ext_temp; /* Register value */ + s8 int_temp; /* Register value */ + u8 ext_temp_max; /* Register value */ + u8 ext_temp_min; /* Register value */ + u8 ext2_temp_max; /* Register value */ + u8 ext2_temp_min; /* Register value */ + u8 int_temp_max; /* Register value */ + u8 int_temp_min; /* Register value */ + u16 alarms; /* Register encoding, combined */ + u8 analog_out; /* Register value */ + u8 vid; /* Register value combined */ + u8 vrm; /* VRM version * 10 */ +}; + +static int lm87_attach_adapter(struct i2c_adapter *adapter); +static int lm87_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int lm87_detach_client(struct i2c_client *client); + +static int lm87_read_value(struct i2c_client *client, u8 register); +static int lm87_write_value(struct i2c_client *client, u8 register, + u8 value); +static void lm87_update_client(struct i2c_client *client); +static void lm87_init_client(struct i2c_client *client); + + +static void lm87_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +#if defined (LM87_AIN1) || defined (LM87_AIN2) +static void lm87_ain(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +#endif +static void lm87_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm87_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm87_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm87_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm87_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, + long *results); +static void lm87_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm87_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver LM87_driver = { + .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 Celsius * 10 */ +#define LM87_SYSCTL_TEMP2 1251 /* Degrees Celsius * 10 */ +#define LM87_SYSCTL_TEMP3 1252 /* Degrees Celsius * 10 */ +#define LM87_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define LM87_SYSCTL_ALARMS 2001 /* bitvector */ +#define LM87_SYSCTL_ANALOG_OUT 2002 +#define LM87_SYSCTL_VID 2003 +#define LM87_SYSCTL_VRM 2004 + +#define LM87_ALARM_IN0 0x0001 +#define LM87_ALARM_IN1 0x0002 +#define LM87_ALARM_IN2 0x0004 +#define LM87_ALARM_IN3 0x0008 +#define LM87_ALARM_TEMP1 0x0010 +#define LM87_ALARM_TEMP2 0x0020 +#define LM87_ALARM_TEMP3 0x0020 /* same?? */ +#define LM87_ALARM_FAN1 0x0040 +#define LM87_ALARM_FAN2 0x0080 +#define LM87_ALARM_IN4 0x0100 +#define LM87_ALARM_IN5 0x0200 +#define LM87_ALARM_RESERVED1 0x0400 +#define LM87_ALARM_RESERVED2 0x0800 +#define LM87_ALARM_CHAS 0x1000 +#define LM87_ALARM_THERM_SIG 0x2000 +#define LM87_ALARM_TEMP2_FAULT 0x4000 +#define LM87_ALARM_TEMP3_FAULT 0x08000 + +/* -- SENSORS SYSCTL END -- */ + +/* The /proc/sys entries */ +/* These files are created for each detected LM87. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ + +static ctl_table LM87_dir_table_template[] = { +#ifdef LM87_AIN1 + {LM87_SYSCTL_AIN1, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_ain}, +#endif +#ifdef LM87_AIN2 + {LM87_SYSCTL_AIN2, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_ain}, +#endif +#ifndef LM87_EXT2 + {LM87_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_in}, + {LM87_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_in}, +#endif + {LM87_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_in}, + {LM87_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_in}, + {LM87_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_in}, + {LM87_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_in}, +#ifndef LM87_AIN1 + {LM87_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_fan}, + {LM87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_fan_div}, +#define LM87_FANDIV_FLAG +#endif +#ifndef LM87_AIN2 + {LM87_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_fan}, +#ifndef LM87_FANDIV_FLAG + {LM87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_fan_div}, +#endif /* LM87_FANDIV_FLAG */ +#endif /* LM87_AIN2 */ +#ifdef LM87_EXT2 + {LM87_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_temp}, +#endif + {LM87_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_temp}, + {LM87_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_temp}, + {LM87_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_alarms}, + {LM87_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_analog_out}, + {LM87_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_vid}, + {LM87_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm87_vrm}, + {0} +}; + +static int lm87_attach_adapter(struct i2c_adapter *adapter) +{ + int error; + struct i2c_client_address_data lm87_client_data; + + lm87_client_data.normal_i2c = addr_data.normal_i2c; + lm87_client_data.normal_i2c_range = addr_data.normal_i2c_range; + lm87_client_data.probe = addr_data.probe; + lm87_client_data.probe_range = addr_data.probe_range; + lm87_client_data.ignore = addr_data.ignore; + lm87_client_data.ignore_range = addr_data.ignore_range; + lm87_client_data.force = addr_data.forces->force; + + error = i2c_probe(adapter, &lm87_client_data, lm87_detect); + i2c_detect(adapter, &addr_data, lm87_detect); + + return error; +} + +static int lm87_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct lm87_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access LM87_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct lm87_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &LM87_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if (((lm87_read_value(new_client, LM87_REG_CONFIG) & 0x80) + != 0x00) || + (lm87_read_value(new_client, LM87_REG_COMPANY_ID) != 0x02)) + goto ERROR1; + } + + /* Fill in the remaining client fields and put into the global list */ + type_name = "lm87"; + client_name = "LM87 chip"; + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the LM87 chip */ + lm87_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int lm87_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct lm87_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "lm87.o: Client deregistration failed, " + "client not detached\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +#define MAX_RETRIES 5 + +static int lm87_read_value(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* Retry in case of read errors */ + for (i = 1; i <= MAX_RETRIES; i++) { + if ((value = i2c_smbus_read_byte_data(client, reg)) >= 0) + return value; + + printk(KERN_WARNING "lm87.o: Read byte data failed, " + "address 0x%02x\n", reg); + mdelay(i + 3); + } + + /* what to return in case of error? */ + printk(KERN_ERR "lm87.o: All read byte retries failed!!\n"); + return 0; +} + +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. */ +static void lm87_init_client(struct i2c_client *client) +{ + struct lm87_data *data = client->data; + u8 reg; + + /* 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 + */ + + /* Preserve 4 MSB */ + reg = lm87_read_value(client, LM87_REG_CHANNEL_MODE); +/* I know, not clean, but it works. :'p */ + lm87_write_value(client, LM87_REG_CHANNEL_MODE, (reg & 0xf0) | +#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 = 82; + + /* Start monitoring */ + reg = lm87_read_value(client, LM87_REG_CONFIG); + if (!(reg & 0x01)) { + printk(KERN_INFO "lm87.o: Monitoring starts\n"); + lm87_write_value(client, LM87_REG_CONFIG, + (reg & 0x7e) | 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] = (data->in_min[nr] * scales[nr] + 96) / 192; + results[1] = (data->in_max[nr] * scales[nr] + 96) / 192; + results[2] = (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] = (results[0] * 192 + scales[nr] / 2) + / 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] / 2) + / 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_FROM_REG(data->ext2_temp_max); + results[1] = TEMP_FROM_REG(data->ext2_temp_min); + results[2] = TEMP_FROM_REG(data->ext2_temp); + } + else if(ctl_name == LM87_SYSCTL_TEMP2) + { + results[0] = TEMP_FROM_REG(data->ext_temp_max); + results[1] = TEMP_FROM_REG(data->ext_temp_min); + results[2] = TEMP_FROM_REG(data->ext_temp); + } + else if(ctl_name == LM87_SYSCTL_TEMP1) + { + results[0] = TEMP_FROM_REG(data->int_temp_max); + results[1] = TEMP_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] = 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(KERN_INFO "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 Sun Feb 26 11:18:40 2006 @@ -0,0 +1,859 @@ +/* + * lm90.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2005 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 LM89 and LM99, two other sensor chips + * made by National Semiconductor. Both have an increased remote + * temperature measurement accuracy (1 degree), and the LM99 + * additionally shifts remote temperatures (measured and limits) by 16 + * degrees, which allows for higher temperatures measurement. The + * driver doesn't handle it since it can be done easily in user-space. + * Complete datasheets can be obtained from National's website at: + * http://www.national.com/pf/LM/LM89.html + * http://www.national.com/pf/LM/LM99.html + * Note that there is no way to differentiate between both chips. + * + * This driver also supports the LM86, another sensor chip made by + * National Semiconductor. It is exactly similar to the LM90 except it + * has a higher accuracy. + * Complete datasheet can be obtained from National's website at: + * http://www.national.com/pf/LM/LM86.html + * + * This driver also supports the ADM1032, a sensor chip made by Analog + * Devices. That chip is similar to the LM90, with a few differences + * that are not handled by this driver. Complete datasheet can be + * obtained from Analog's website at: + * http://products.analog.com/products/info.asp?product=ADM1032 + * Among others, it has a higher accuracy than the LM90, much like the + * LM86 does. + * + * This driver also supports the MAX6657, MAX6658 and MAX6659 sensor + * chips made by Maxim. These chips are similar to the LM86. Complete + * datasheet can be obtained at Maxim's website at: + * http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578 + * Note that there is no easy way to differentiate between the three + * variants. The extra address and features of the MAX6659 are not + * supported by this driver. + * + * This driver also supports the ADT7461 chip from Analog Devices but + * only in its "compatability mode". If an ADT7461 chip is found but + * is configured in non-compatible mode (where its temperature + * register values are decoded differently) it is ignored by this + * driver. Complete datasheet can be obtained from Analog's website + * at: + * http://products.analog.com/products/info.asp?product=ADT7461 + * + * Since the LM90 was the first chipset supported by this driver, most + * comments will refer to this chipset, but are actually general and + * concern all supported chipsets, unless mentioned otherwise. + * + * 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 "20060214" +#define LM_VERSION "2.10.0" + +#ifndef I2C_DRIVERID_LM90 +#define I2C_DRIVERID_LM90 1042 +#endif + +/* + * Addresses to scan + * Address is fully defined internally and cannot be changed except for + * MAX6659. + * LM86, LM89, LM90, LM99, ADM1032, ADM1032-1, ADT7461, MAX6657 and MAX6658 + * have address 0x4c. + * ADM1032-2, ADT7461-2, LM89-1, and LM99-1 have address 0x4d. + * MAX6659 can have address 0x4c, 0x4d or 0x4e (unsupported). + */ + +static unsigned short normal_i2c[] = { 0x4c, 0x4d, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* + * Insmod parameters + */ + +SENSORS_INSMOD_6(lm90, adm1032, lm99, lm86, max6657, adt7461); + +/* + * 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 + * For local temperatures and limits, critical limits and the hysteresis + * value, the LM90 uses signed 8-bit values with LSB = 1 degree Celsius. + * For remote temperatures and limits, it uses signed 11-bit values with + * LSB = 0.125 degree Celsius, left-justified in 16-bit registers. + */ + +#define TEMP1_FROM_REG(val) (val) +#define TEMP1_TO_REG(val) ((val) <= -128 ? -128 : \ + (val) >= 127 ? 127 : (val)) +#define TEMP2_FROM_REG(val) ((val) / 32 * 125 / 100) +#define TEMP2_TO_REG(val) ((val) <= -1280 ? 0x8000 : \ + (val) >= 1270 ? 0x7FE0 : \ + ((val) * 100 / 125 * 32)) +#define HYST_TO_REG(val) ((val) <= 0 ? 0 : \ + (val) >= 31 ? 31 : (val)) + +/* + * ADT7461 is almost identical to LM90 except that attempts to write + * values that are outside the range 0 < temp < 127 are treated as + * the boundary value. + */ + +#define TEMP1_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \ + (val) >= 127 ? 127 : (val)) +#define TEMP2_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \ + (val) >= 1277 ? 0x7FC0 : \ + ((val) * 100 / 250 * 64)) + +/* + * 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); +static void adm1032_pec(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 = { + .name = "LM90/ADM1032 sensor driver", + .id = I2C_DRIVERID_LM90, + .flags = I2C_DF_NOTIFY, + .attach_adapter = lm90_attach_adapter, + .detach_client = lm90_detach_client +}; + +/* + * Client data (each client gets its own) + */ + +struct lm90_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + int kind; + + /* registers values */ + s8 local_temp, local_high, local_low; + s16 remote_temp, remote_high, remote_low; /* combined */ + s8 local_crit, remote_crit; + u8 hyst; /* linked to two sysctl files (hyst1 RW, hyst2 RO) */ + u8 alarms; /* bitvector */ +}; + +/* + * 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_SYSCTL_PEC 1214 + +#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} +}; + +static ctl_table adm1032_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}, + {LM90_SYSCTL_PEC, "pec", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1032_pec}, + {0} +}; + +/* + * Real code + */ + +/* The ADM1032 supports PEC but not on write byte transactions, so we need + to explicitely ask for a transaction without PEC. */ +static inline s32 adm1032_write_byte(struct i2c_client *client, u8 value) +{ + return i2c_smbus_xfer(client->adapter, client->addr, + client->flags & ~I2C_CLIENT_PEC, + I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL); +} + +/* It is assumed that client->update_lock is held (unless we are in + detection or initialization steps). This matters when PEC is enabled, + because we don't want the address pointer to change between the write + byte and the read byte transactions. */ +static int lm90_read_reg(struct i2c_client* client, u8 reg, u8 *value) +{ + int err; + + if (client->flags & I2C_CLIENT_PEC) { + err = adm1032_write_byte(client, reg); + if (err >= 0) + err = i2c_smbus_read_byte(client); + } else + err = i2c_smbus_read_byte_data(client, reg); + + if (err < 0) { + printk(KERN_WARNING "lm90: Register 0x%02x read failed (%d)\n", + reg, err); + return err; + } + *value = err; + + return 0; +} + +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 = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { +#ifdef DEBUG + printk(KERN_DEBUG "lm90: adapter doesn't support byte mode, " + "skipping\n"); +#endif + return 0; + } + + if (!(data = kmalloc(sizeof(struct lm90_data), GFP_KERNEL))) { + printk(KERN_ERR "lm90: Out of memory in lm90_detect\n"); + return -ENOMEM; + } + + /* + * The common I2C client data is placed right before the + * LM90-specific data. The LM90-specific data is pointed to by the + * data field from the I2C client data. + */ + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm90_driver; + new_client->flags = 0; + + /* + * Now we do the remaining detection. A negative kind means that + * the driver was loaded with no force parameter (default), so we + * must both detect and identify the chip. A zero kind means that + * the driver was loaded with the force parameter, the detection + * step shall be skipped. A positive kind means that the driver + * was loaded with the force parameter and a given kind of chip is + * requested, so both the detection and the identification steps + * are skipped. + */ + + /* Default to an LM90 if forced */ + if (kind == 0) + kind = lm90; + + if (kind < 0) { /* detection and identification */ + u8 man_id, chip_id, reg_config1, reg_convrate; + + if (lm90_read_reg(new_client, LM90_REG_R_MAN_ID, + &man_id) < 0 + || lm90_read_reg(new_client, LM90_REG_R_CHIP_ID, + &chip_id) < 0 + || lm90_read_reg(new_client, LM90_REG_R_CONFIG1, + ®_config1) < 0 + || lm90_read_reg(new_client, LM90_REG_R_CONVRATE, + ®_convrate) < 0) + goto exit_free; + + if (man_id == 0x01) { /* National Semiconductor */ + u8 reg_config2; + + if (lm90_read_reg(new_client, LM90_REG_R_CONFIG2, + ®_config2) < 0) + goto exit_free; + + if ((reg_config1 & 0x2A) == 0x00 + && (reg_config2 & 0xF8) == 0x00 + && reg_convrate <= 0x09) { + if (address == 0x4C + && (chip_id & 0xF0) == 0x20) /* LM90 */ + kind = lm90; + else if ((chip_id & 0xF0) == 0x30) /* LM89/LM99 */ + kind = lm99; + else if (address == 0x4C + && (chip_id & 0xF0) == 0x10) /* LM86 */ + kind = lm99; + } + } else + if (man_id == 0x41) { /* Analog Devices */ + if ((chip_id & 0xF0) == 0x40 /* ADM1032 */ + && (reg_config1 & 0x3F) == 0x00 + && reg_convrate <= 0x0A) + kind = adm1032; + else + if (chip_id == 0x51 /* ADT7461 */ + && (reg_config1 & 0x1F) == 0x00 /* check compat mode */ + && reg_convrate <= 0x0A) + kind = adt7461; + } else + if (man_id == 0x4D) { /* Maxim */ + /* + * The Maxim variants do NOT have a chip_id register. + * Reading from that address will return the last read + * value, which in our case is those of the man_id + * register. Likewise, the config1 register seems to + * lack a low nibble, so the value will be those of the + * previous read, so in our case those of the man_id + * register. + */ + if (chip_id == man_id + && (reg_config1 & 0x1F) == (man_id & 0x0F) + && reg_convrate <= 0x09) + kind = max6657; + } + } + + if (kind <= 0) { /* identification failed */ + printk(KERN_INFO "lm90: Unsupported chip\n"); + goto exit_free; + } + + if (kind == lm90) { + type_name = "lm90"; + client_name = "LM90 chip"; + } else if (kind == adm1032) { + type_name = "adm1032"; + client_name = "ADM1032 chip"; + /* The ADM1032 supports PEC, but only if combined + transactions are not used. */ + if (i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { + new_client->flags |= I2C_CLIENT_PEC; + printk(KERN_DEBUG "lm90: Enabling PEC for ADM1032\n"); + } + } else if (kind == lm99) { + type_name = "lm99"; + client_name = "LM99 chip"; + } else if (kind == lm86) { + type_name = "lm86"; + client_name = "LM86 chip"; + } else if (kind == max6657) { + type_name = "max6657"; + client_name = "MAX6657 chip"; + } else if (kind == adt7461) { + type_name = "adt7461"; + client_name = "ADT7561 chip"; + } else { + printk(KERN_ERR "lm90: Unknown kind %d\n", kind); + goto exit_free; + } + + /* + * OK, we got a valid chip so we can fill in the remaining client + * fields. + */ + + strcpy(new_client->name, client_name); + data->valid = 0; + data->kind = kind; + init_MUTEX(&data->update_lock); + + /* + * Tell the I2C layer a new client has arrived. + */ + + if ((err = i2c_attach_client(new_client))) { + printk(KERN_ERR "lm90: Failed to attach client (%d)\n", err); + goto exit_free; + } + + /* + * Register a new directory entry. + */ + + if ((err = i2c_register_entry(new_client, type_name, + (new_client->flags & I2C_CLIENT_PEC) ? + adm1032_dir_table_template : + lm90_dir_table_template, THIS_MODULE)) < 0) { + printk(KERN_ERR "lm90: Failed to register directory entry " + "(%d)\n", err); + goto exit_detach; + } + data->sysctl_id = err; + + /* + * Initialize the LM90 chip. + */ + + lm90_init_client(new_client); + return 0; + +exit_detach: + i2c_detach_client(new_client); +exit_free: + kfree(data); + return err; +} + +static void lm90_init_client(struct i2c_client *client) +{ + u8 config; + + /* + * Start the conversions. + */ + + i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, + 5); /* 2 Hz */ + if (lm90_read_reg(client, LM90_REG_R_CONFIG1, &config) < 0) { + printk(KERN_ERR "lm90: Initialization failed!\n"); + return; + } + 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(KERN_ERR "lm90: Client deregistration failed, client " + "not detached (%d)\n", err); + return err; + } + + kfree(client->data); + return 0; +} + +static void lm90_update_client(struct i2c_client *client) +{ + struct lm90_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ * 2) || + (jiffies < data->last_updated) || !data->valid) { + u8 oldh, newh, l; +#ifdef DEBUG + printk(KERN_DEBUG "lm90: Updating register values\n"); +#endif + + lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP, + &data->local_temp); + lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH, + &data->local_high); + lm90_read_reg(client, LM90_REG_R_LOCAL_LOW, + &data->local_low); + lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT, + &data->local_crit); + lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT, + &data->remote_crit); + lm90_read_reg(client, LM90_REG_R_TCRIT_HYST, + &data->hyst); + + /* + * 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. + */ + + if (lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPH, &oldh) == 0 + && lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPL, &l) == 0 + && lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPH, &newh) == 0 + && (newh == oldh + || lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPL, &l) == 0)) + data->remote_temp = (newh << 8) | l; + + if (lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &newh) == 0 + && lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL, &l) == 0) + data->remote_low = (newh << 8) | l; + if (lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &newh) == 0 + && lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL, &l) == 0) + data->remote_high = (newh << 8) | l; + lm90_read_reg(client, LM90_REG_R_STATUS, &data->alarms); + + 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) { + if (data->kind == adt7461) + data->local_high = TEMP1_TO_REG_ADT7461(results[0]); + else + 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) { + if (data->kind == adt7461) + data->local_low = TEMP1_TO_REG_ADT7461(results[1]); + else + 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) { + if (data->kind == adt7461) + data->remote_high = TEMP2_TO_REG_ADT7461(results[0]); + else + 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) { + if (data->kind == adt7461) + data->remote_low = TEMP2_TO_REG_ADT7461(results[1]); + else + 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) { + if (data->kind == adt7461) + data->local_crit = TEMP1_TO_REG_ADT7461(results[0]); + else + 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) { + if (data->kind == adt7461) + data->remote_crit = TEMP1_TO_REG_ADT7461(results[0]); + else + 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; + } +} + +/* pec used for ADM1032 only */ +static void adm1032_pec(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = !!(client->flags & I2C_CLIENT_PEC); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + switch (results[0]) { + case 0: + client->flags &= ~I2C_CLIENT_PEC; + break; + case 1: + client->flags |= I2C_CLIENT_PEC; + break; + } + } + } +} + +static int __init sm_lm90_init(void) +{ + printk(KERN_INFO "lm90 driver 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 Sun Feb 26 11:18:40 2006 @@ -0,0 +1,417 @@ + +/* + * 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 "20060214" +#define LM_VERSION "2.10.0" + +/* if defined, 4 faults must occur consecutively to set alarm flags */ +/* #define ENABLE_FAULT_QUEUE */ + +#define LM92_REG_TEMPERATURE 0x00 /* ro, 16-bit */ +#define LM92_REG_CONFIGURATION 0x01 /* rw, 8-bit */ +#define LM92_REG_TRIP_HYSTERESIS 0x02 /* rw, 16-bit */ +#define LM92_REG_TRIP_CRITICAL 0x03 /* rw, 16-bit */ +#define LM92_REG_TRIP_LOW 0x04 /* rw, 16-bit */ +#define LM92_REG_TRIP_HIGH 0x05 /* rw, 16-bit */ +#define LM92_REG_MANUFACTURER 0x07 /* ro, 16-bit */ + +#define LM92_MANUFACTURER_ID 0x8001 + +#define TEMP_MIN (-4096) +#define TEMP_MAX 4095 + +#define LIMIT(x) do { \ + if ((x) < TEMP_MIN) (x) = TEMP_MIN; \ + if ((x) > TEMP_MAX) (x) = TEMP_MAX; \ + } while (0) + +#define PROC_TO_NATIVE(x) ((x) / 625) +#define NATIVE_TO_PROC(x) ((x) * 625) +#define CELSIUS(x) ((x) * 16) + +static void lm92_temp (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results); +static void lm92_alarms (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results); + +/* -- SENSORS SYSCTL START -- */ +#define LM92_SYSCTL_ALARMS 2001 /* high, low, critical */ +#define LM92_SYSCTL_TEMP 1200 /* high, low, critical, hysteresis, input */ + +#define LM92_ALARM_TEMP_HIGH 0x01 +#define LM92_ALARM_TEMP_LOW 0x02 +#define LM92_ALARM_TEMP_CRIT 0x04 +#define LM92_TEMP_HIGH 0x08 +#define LM92_TEMP_LOW 0x10 +#define LM92_TEMP_CRIT 0x20 +#define LM92_TEMP_HYST 0x40 +#define LM92_TEMP_INPUT 0x80 + +/* -- SENSORS SYSCTL END -- */ + +static ctl_table lm92_dir_table[] = { + {LM92_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm92_temp, NULL}, + {LM92_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm92_alarms, NULL}, + {0} +}; + +/* NOTE: all temperatures are degrees centigrade * 16 */ +typedef struct { + struct i2c_client client; + int sysctl_id; + unsigned long timestamp; + struct { + long high; + long low; + long crit; + long hyst; + long input; + } temp; + struct { + long low; + long high; + long crit; + } alarms; +} lm92_t; + +/* this is needed for each client driver method */ +static struct i2c_driver lm92_driver; + +/* ensure exclusive access to chip and static variables */ +static DECLARE_MUTEX (mutex); + +/* addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* insmod parameters */ +SENSORS_INSMOD_1 (lm92); + +static inline int lm92_write8 (struct i2c_client *client,u8 reg,u8 value) +{ + return (i2c_smbus_write_byte_data (client,reg,value) < 0 ? -EIO : 0); +} + +static inline int lm92_read16 (struct i2c_client *client,u8 reg,u16 *value) +{ + s32 tmp = i2c_smbus_read_word_data (client,reg); + + if (tmp < 0) return (-EIO); + + /* convert the data to little endian format */ + *value = swab16((u16) tmp); + + return (0); +} + +static inline int lm92_write16 (struct i2c_client *client,u8 reg,u16 value) +{ + /* convert the data to big endian format */ + if (i2c_smbus_write_word_data(client, reg, swab16(value)) < 0) + return -EIO; + + return 0; +} + +static int lm92_read (struct i2c_client *client) +{ + lm92_t *data = (lm92_t *) client->data; + u16 value[5]; + + if ((jiffies - data->timestamp) > HZ) { + if (lm92_read16 (client,LM92_REG_TEMPERATURE,value) < 0 || + lm92_read16 (client,LM92_REG_TRIP_HYSTERESIS,value + 1) < 0 || + lm92_read16 (client,LM92_REG_TRIP_CRITICAL,value + 2) < 0 || + lm92_read16 (client,LM92_REG_TRIP_LOW,value + 3) < 0 || + lm92_read16 (client,LM92_REG_TRIP_HIGH,value + 4) < 0) + return (-EIO); + + data->temp.input = (s16) value[0] >> 3; + data->temp.hyst = (s16) value[1] >> 3; + data->temp.crit = (s16) value[2] >> 3; + data->temp.low = (s16) value[3] >> 3; + data->temp.high = (s16) value[4] >> 3; + + data->alarms.low = value[0] & 1; + data->alarms.high = (value[0] & 2) >> 1; + data->alarms.crit = (value[0] & 4) >> 2; + + data->timestamp = jiffies; + } + + return (0); +} + +static int lm92_write (struct i2c_client *client) +{ + lm92_t *data = (lm92_t *) client->data; + + LIMIT (data->temp.hyst); + LIMIT (data->temp.crit); + LIMIT (data->temp.low); + LIMIT (data->temp.high); + + if (lm92_write16 (client,LM92_REG_TRIP_HYSTERESIS,((s16) data->temp.hyst << 3)) < 0 || + lm92_write16 (client,LM92_REG_TRIP_CRITICAL,((s16) data->temp.crit << 3)) < 0 || + lm92_write16 (client,LM92_REG_TRIP_LOW,((s16) data->temp.low << 3)) < 0 || + lm92_write16 (client,LM92_REG_TRIP_HIGH,((s16) data->temp.high << 3)) < 0) + return (-EIO); + + return (0); +} + +static void lm92_temp (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results) +{ + if (!down_interruptible (&mutex)) { + lm92_t *data = (lm92_t *) client->data; + + if (operation == SENSORS_PROC_REAL_READ) { + lm92_read (client); + results[0] = NATIVE_TO_PROC (data->temp.input); + results[1] = NATIVE_TO_PROC (data->temp.high); + results[2] = NATIVE_TO_PROC (data->temp.low); + results[3] = NATIVE_TO_PROC (data->temp.crit); + results[4] = NATIVE_TO_PROC (data->temp.hyst); + *nrels_mag = 5; + } else if (operation == SENSORS_PROC_REAL_WRITE && *nrels_mag == 4) { + data->temp.high = PROC_TO_NATIVE (results[0]); + data->temp.low = PROC_TO_NATIVE (results[1]); + data->temp.crit = PROC_TO_NATIVE (results[2]); + data->temp.hyst = PROC_TO_NATIVE (results[3]); + lm92_write (client); + } else if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 4; + } + + up (&mutex); + } +} + +static void lm92_alarms (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results) +{ + if (!down_interruptible (&mutex)) { + lm92_t *data = (lm92_t *) client->data; + + if (operation == SENSORS_PROC_REAL_READ) { + lm92_read (client); + results[0] = data->alarms.high || (data->alarms.low << 1) || (data->alarms.crit << 2); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 0; + } + + up (&mutex); + } +} + +static int max6635_check(struct i2c_client *client) +{ + int i; + u16 temp_low, temp_high, temp_hyst, temp_crit; + u8 conf; + + temp_low = i2c_smbus_read_word_data(client, LM92_REG_TRIP_LOW); + temp_high = i2c_smbus_read_word_data(client, LM92_REG_TRIP_HIGH); + temp_hyst = i2c_smbus_read_word_data(client, LM92_REG_TRIP_HYSTERESIS); + temp_crit = i2c_smbus_read_word_data(client, LM92_REG_TRIP_CRITICAL); + + if ((temp_low & 0x7f00) || (temp_high & 0x7f00) + || (temp_hyst & 0x7f00) || (temp_crit & 0x7f00)) + return 0; + + conf = i2c_smbus_read_byte_data(client, LM92_REG_CONFIGURATION); + + for (i=0; i<128; i+=16) { + if (temp_low != i2c_smbus_read_word_data(client, LM92_REG_TRIP_LOW + i) + || temp_high != i2c_smbus_read_word_data(client, LM92_REG_TRIP_HIGH + i) + || temp_hyst != i2c_smbus_read_word_data(client, LM92_REG_TRIP_HYSTERESIS + i) + || temp_crit != i2c_smbus_read_word_data(client, LM92_REG_TRIP_CRITICAL + i) + || conf != i2c_smbus_read_byte_data(client, LM92_REG_CONFIGURATION + i)) + return 0; + } + + return 1; +} + +static int lm92_init_client (struct i2c_client *client) +{ + lm92_t *data = (lm92_t *) client->data; + u8 value = 0; + int result; + + /* force reads to query the chip */ + data->timestamp = 0; + + /* setup the configuration register */ + +#ifdef ENABLE_FAULT_QUEUE + value |= 0x10; +#endif /* #ifdef ENABLE_FAULT_QUEUE */ + + if (lm92_write8 (client,LM92_REG_CONFIGURATION,value) < 0) + return (-ENODEV); + + /* set default alarm trigger values */ + + data->temp.high = CELSIUS (64); + data->temp.low = CELSIUS (10); + data->temp.crit = CELSIUS (80); + data->temp.hyst = CELSIUS (2); + + if ((result = lm92_write (client)) < 0) + return (result); + + /* read everything once so that our cached data is updated */ + + if ((result = lm92_read (client)) < 0) + return (result); + + return (0); +} + +static int lm92_detect (struct i2c_adapter *adapter,int address,unsigned short flags,int kind) +{ + struct i2c_client *client; + lm92_t *data; + int result = 0; + u16 manufacturer; + + if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + if (!(data = kmalloc(sizeof(lm92_t), GFP_KERNEL))) + return (-ENOMEM); + + client = &data->client; + client->addr = address; + client->data = data; + client->adapter = adapter; + client->driver = &lm92_driver; + client->flags = 0; + strcpy (client->name,lm92_driver.name); + + if (down_interruptible (&mutex)) { + result = -ERESTARTSYS; + goto ERROR1; + } + + if (kind < 0) { + /* Is it an lm92? */ + if (address < 0x4c + && (lm92_read16(client,LM92_REG_MANUFACTURER,&manufacturer) < 0 + || manufacturer != LM92_MANUFACTURER_ID)) { + /* Is it a MAX6635/MAX6635/MAX6635? */ + if (!max6635_check(client)) { + goto ERROR2; + } + } + } + + if ((result = i2c_attach_client (client))) { + goto ERROR2; + } + + if ((result = i2c_register_entry(client, client->name, lm92_dir_table, + THIS_MODULE)) < 0) { + goto ERROR3; + } + data->sysctl_id = result; + + if ((result = lm92_init_client (client)) < 0) { + goto ERROR4; + } + + up (&mutex); + + return (0); + +ERROR4: + i2c_deregister_entry(data->sysctl_id); +ERROR3: + i2c_detach_client(client); +ERROR2: + up(&mutex); +ERROR1: + kfree(data); + return result; +} + +static int lm92_attach_adapter (struct i2c_adapter *adapter) +{ + return i2c_detect (adapter,&addr_data,lm92_detect); +} + +static int lm92_detach_client (struct i2c_client *client) +{ + int result; + + i2c_deregister_entry (((lm92_t *) (client->data))->sysctl_id); + + if ((result = i2c_detach_client (client))) + return (result); + + kfree(client->data); + + return (0); +} + + +static struct i2c_driver lm92_driver = { + .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/lm93.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/lm93.c Sun Feb 26 11:18:40 2006 @@ -0,0 +1,2344 @@ +/* + lm93.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + + Author/Maintainer: Mark M. Hoffman + Copyright (c) 2004 Utilitek Systems, Inc. + + derived in part from lm78.c: + Copyright (c) 1998, 1999 Frodo Looijaard + + derived in part from lm85.c: + Copyright (c) 2002, 2003 Philip Pokorny + Copyright (c) 2003 Margit Schubert-While + + derived in part from w83l785ts.c: + Copyright (c) 2003-2004 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. +*/ + +#define DEBUG 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include +#include "lm93.h" + +#ifndef I2C_DRIVERID_LM93 +#define I2C_DRIVERID_LM93 1049 +#endif + +/* I2C addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, 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 }; + +/* module parameters */ +SENSORS_INSMOD_1(lm93); + +static int disable_block /* = 0 */ ; +MODULE_PARM(disable_block, "i"); +MODULE_PARM_DESC(disable_block, + "Set to non-zero to disable SMBus block data transactions."); + +static int init = 1; +MODULE_PARM(init, "i"); +MODULE_PARM_DESC(init, "Set to zero to bypass most chip initialization"); + +static int vccp_limit_type[2] /* = {0,0} */ ; +MODULE_PARM(vccp_limit_type, "2-2i"); +MODULE_PARM_DESC(vccp_limit_type, "Configures in7 and in8 limit modes"); + +static int vid_agtl = 0; +MODULE_PARM(vid_agtl, "i"); +MODULE_PARM_DESC(vid_agtl, "Configures VID pin input thresholds"); + +/* -- SENSORS SYSCTL START -- */ +/* volts * 100 */ +#define LM93_SYSCTL_IN1 1001 +#define LM93_SYSCTL_IN2 1002 +#define LM93_SYSCTL_IN3 1003 +#define LM93_SYSCTL_IN4 1004 +#define LM93_SYSCTL_IN5 1005 +#define LM93_SYSCTL_IN6 1006 +#define LM93_SYSCTL_IN7 1007 +#define LM93_SYSCTL_IN8 1008 +#define LM93_SYSCTL_IN9 1009 +#define LM93_SYSCTL_IN10 1010 +#define LM93_SYSCTL_IN11 1011 +#define LM93_SYSCTL_IN12 1012 +#define LM93_SYSCTL_IN13 1013 +#define LM93_SYSCTL_IN14 1014 +#define LM93_SYSCTL_IN15 1015 +#define LM93_SYSCTL_IN16 1016 + +/* degrees Celsius * 10 */ +#define LM93_SYSCTL_TEMP1 1101 +#define LM93_SYSCTL_TEMP2 1102 +#define LM93_SYSCTL_TEMP3 1103 + +#define LM93_SYSCTL_TEMP1_AUTO_BASE 1111 +#define LM93_SYSCTL_TEMP2_AUTO_BASE 1112 +#define LM93_SYSCTL_TEMP3_AUTO_BASE 1113 + +#define LM93_SYSCTL_TEMP1_AUTO_OFFSETS 1121 +#define LM93_SYSCTL_TEMP2_AUTO_OFFSETS 1122 +#define LM93_SYSCTL_TEMP3_AUTO_OFFSETS 1123 + +#define LM93_SYSCTL_TEMP1_AUTO_BOOST 1131 +#define LM93_SYSCTL_TEMP2_AUTO_BOOST 1132 +#define LM93_SYSCTL_TEMP3_AUTO_BOOST 1133 + +#define LM93_SYSCTL_TEMP1_AUTO_BOOST_HYST 1141 +#define LM93_SYSCTL_TEMP2_AUTO_BOOST_HYST 1142 +#define LM93_SYSCTL_TEMP3_AUTO_BOOST_HYST 1143 + +/* 0 => off, 255 => 100% */ +#define LM93_SYSCTL_TEMP1_AUTO_PWM_MIN 1151 +#define LM93_SYSCTL_TEMP2_AUTO_PWM_MIN 1152 +#define LM93_SYSCTL_TEMP3_AUTO_PWM_MIN 1153 + +/* degrees Celsius * 10 */ +#define LM93_SYSCTL_TEMP1_AUTO_OFFSET_HYST 1161 +#define LM93_SYSCTL_TEMP2_AUTO_OFFSET_HYST 1162 +#define LM93_SYSCTL_TEMP3_AUTO_OFFSET_HYST 1163 + +/* rotations/minute */ +#define LM93_SYSCTL_FAN1 1201 +#define LM93_SYSCTL_FAN2 1202 +#define LM93_SYSCTL_FAN3 1203 +#define LM93_SYSCTL_FAN4 1204 + +/* 1-2 => enable smart tach mode associated with this pwm #, or disable */ +#define LM93_SYSCTL_FAN1_SMART_TACH 1205 +#define LM93_SYSCTL_FAN2_SMART_TACH 1206 +#define LM93_SYSCTL_FAN3_SMART_TACH 1207 +#define LM93_SYSCTL_FAN4_SMART_TACH 1208 + +/* volts * 1000 */ +#define LM93_SYSCTL_VID1 1301 +#define LM93_SYSCTL_VID2 1302 + +/* 0 => off, 255 => 100% */ +#define LM93_SYSCTL_PWM1 1401 +#define LM93_SYSCTL_PWM2 1402 + +/* Hz */ +#define LM93_SYSCTL_PWM1_FREQ 1411 +#define LM93_SYSCTL_PWM2_FREQ 1412 + +/* bitvector */ +#define LM93_SYSCTL_PWM1_AUTO_CHANNELS 1421 +#define LM93_SYSCTL_PWM2_AUTO_CHANNELS 1422 + +/* Hz */ +#define LM93_SYSCTL_PWM1_AUTO_SPINUP_MIN 1431 +#define LM93_SYSCTL_PWM2_AUTO_SPINUP_MIN 1432 + +/* seconds */ +#define LM93_SYSCTL_PWM1_AUTO_SPINUP_TIME 1441 +#define LM93_SYSCTL_PWM2_AUTO_SPINUP_TIME 1442 + +/* seconds */ +#define LM93_SYSCTL_PWM_AUTO_PROCHOT_RAMP 1451 +#define LM93_SYSCTL_PWM_AUTO_VRDHOT_RAMP 1452 + +/* 0 => 0%, 255 => > 99.6% */ +#define LM93_SYSCTL_PROCHOT1 1501 +#define LM93_SYSCTL_PROCHOT2 1502 + +/* !0 => enable #PROCHOT logical short */ +#define LM93_SYSCTL_PROCHOT_SHORT 1503 + +/* 2 boolean enable/disable, 3rd value indicates duty cycle */ +#define LM93_SYSCTL_PROCHOT_OVERRIDE 1504 + +/* 2 values, 0-9 */ +#define LM93_SYSCTL_PROCHOT_INTERVAL 1505 + +/* GPIO input (bitmask) */ +#define LM93_SYSCTL_GPIO 1601 + +/* #VRDHOT input (boolean) */ +#define LM93_SYSCTL_VRDHOT1 1701 +#define LM93_SYSCTL_VRDHOT2 1702 + +/* alarms (bitmask) */ +#define LM93_SYSCTL_ALARMS 2001 + +/* alarm bitmask definitions + The LM93 has nearly 64 bits of error status... I've pared that down to + what I think is a useful subset in order to fit it into 32 bits. + + Especially note that the #VRD_HOT alarms are missing because we provide + that information as values in another /proc file. + + If libsensors is extended to support 64 bit values, this could be revisited. +*/ +#define LM93_ALARM_IN1 0x00000001 +#define LM93_ALARM_IN2 0x00000002 +#define LM93_ALARM_IN3 0x00000004 +#define LM93_ALARM_IN4 0x00000008 +#define LM93_ALARM_IN5 0x00000010 +#define LM93_ALARM_IN6 0x00000020 +#define LM93_ALARM_IN7 0x00000040 +#define LM93_ALARM_IN8 0x00000080 +#define LM93_ALARM_IN9 0x00000100 +#define LM93_ALARM_IN10 0x00000200 +#define LM93_ALARM_IN11 0x00000400 +#define LM93_ALARM_IN12 0x00000800 +#define LM93_ALARM_IN13 0x00001000 +#define LM93_ALARM_IN14 0x00002000 +#define LM93_ALARM_IN15 0x00004000 +#define LM93_ALARM_IN16 0x00008000 +#define LM93_ALARM_FAN1 0x00010000 +#define LM93_ALARM_FAN2 0x00020000 +#define LM93_ALARM_FAN3 0x00040000 +#define LM93_ALARM_FAN4 0x00080000 +#define LM93_ALARM_PH1_ERR 0x00100000 +#define LM93_ALARM_PH2_ERR 0x00200000 +#define LM93_ALARM_SCSI1_ERR 0x00400000 +#define LM93_ALARM_SCSI2_ERR 0x00800000 +#define LM93_ALARM_DVDDP1_ERR 0x01000000 +#define LM93_ALARM_DVDDP2_ERR 0x02000000 +#define LM93_ALARM_D1_ERR 0x04000000 +#define LM93_ALARM_D2_ERR 0x08000000 +#define LM93_ALARM_TEMP1 0x10000000 +#define LM93_ALARM_TEMP2 0x20000000 +#define LM93_ALARM_TEMP3 0x40000000 + +/* -- SENSORS SYSCTL END -- */ + +/* SMBus capabilities */ +#define LM93_SMBUS_FUNC_FULL (I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA) +#define LM93_SMBUS_FUNC_MIN (I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA) + +/* LM93 BLOCK READ COMMANDS */ +static const struct { u8 cmd; u8 len; } lm93_block_read_cmds[12] = { + { 0xf2, 8 }, + { 0xf3, 8 }, + { 0xf4, 6 }, + { 0xf5, 16 }, + { 0xf6, 4 }, + { 0xf7, 8 }, + { 0xf8, 12 }, + { 0xf9, 32 }, + { 0xfa, 8 }, + { 0xfb, 8 }, + { 0xfc, 16 }, + { 0xfd, 9 }, +}; + +/* ALARMS: SYSCTL format described further below + REG: 64 bits in 8 registers, as immediately below */ +struct block1_t { + u8 host_status_1; + u8 host_status_2; + u8 host_status_3; + u8 host_status_4; + u8 p1_prochot_status; + u8 p2_prochot_status; + u8 gpi_status; + u8 fan_status; +}; + +/* unique ID for each LM93 detected */ +static int lm93_id = 0; + +/* 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 lm93_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + unsigned long last_updated; /* In jiffies */ + + /* client update function */ + void (*update)(struct lm93_data *, struct i2c_client *); + + char valid; /* !=0 if following fields are valid */ + + /* register values, arranged by block read groups */ + struct block1_t block1; + + /* temp1 - temp4: unfiltered readings + temp1 - temp2: filtered readings */ + u8 block2[6]; + + /* vin1 - vin16: readings */ + u8 block3[16]; + + /* prochot1 - prochot2: readings */ + struct { u8 cur; u8 avg; } block4[2]; + + /* fan counts 1-4 => 14-bits, LE, *left* justified */ + u16 block5[4]; + + /* block6 has a lot of data we don't need */ + struct { u8 min; u8 max; } temp_lim[3]; + + /* vin1 - vin16: low and high limits */ + struct { u8 min; u8 max; } block7[16]; + + /* fan count limits 1-4 => same format as block5 */ + u16 block8[4]; + + /* pwm control registers (2 pwms, 4 regs) */ + u8 block9[2][4]; + + /* auto/pwm base temp and offset temp registers */ + struct { u8 base[4]; u8 offset[12]; } block10; + + /* master config register */ + u8 config; + + /* VID1 & VID2 => register format, 6-bits, right justified */ + u8 vid[2]; + + /* prochot1 - prochot2: limits */ + u8 prochot_max[2]; + + /* vccp1 & vccp2 (in7 & in8): VID relative limits (register format) */ + u8 vccp_limits[2]; + + /* GPIO input state (register format, i.e. inverted) */ + u8 gpi; + + /* #PROCHOT override (register format) */ + u8 prochot_override; + + /* #PROCHOT intervals (register format) */ + u8 prochot_interval; + + /* Fan Boost Temperatures (register format) */ + u8 boost[4]; + + /* Fan Boost Hysteresis (register format) */ + u8 boost_hyst[2]; + + /* Temperature Zone Min. PWM & Hysteresis (register format) */ + u8 auto_pwm_min_hyst[2]; + + /* #PROCHOT & #VRDHOT PWM Ramp Control */ + u8 pwm_ramp_ctl; + + /* miscellaneous setup regs */ + u8 sfc1; + u8 sfc2; + u8 sf_tach_to_pwm; + + /* The two PWM CTL2 registers can read something other than what was + last written for the OVR_DC field (duty cycle override). So, we + save the user-commanded value here. */ + u8 pwm_override[2]; +}; + +#define MAX_RETRIES 5 + +static u8 lm93_read_byte(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* retry in case of read errors */ + for (i=1; i<=MAX_RETRIES; i++) { + if ((value = i2c_smbus_read_byte_data(client, reg)) >= 0) { + return value; + } else { + printk(KERN_WARNING "lm93.o: read byte data failed, " + "address 0x%02x.\n", reg); + mdelay(i + 3); + } + + } + + /* what to return in case of error? */ + printk(KERN_ERR "lm93.o: All read byte retries failed!!\n"); + return 0; +} + +static int lm93_write_byte(struct i2c_client *client, u8 reg, u8 value) +{ + int result; + + /* how to handle write errors? */ + result = i2c_smbus_write_byte_data(client, reg, value); + + if (result < 0) + printk(KERN_WARNING "lm93.o: write byte data failed, " + "0x%02x at address 0x%02x.\n", value, reg); + + return result; +} + +static u16 lm93_read_word(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* retry in case of read errors */ + for (i=1; i<=MAX_RETRIES; i++) { + if ((value = i2c_smbus_read_word_data(client, reg)) >= 0) { + return value; + } else { + printk(KERN_WARNING "lm93.o: read word data failed, " + "address 0x%02x.\n", reg); + mdelay(i + 3); + } + + } + + /* what to return in case of error? */ + printk(KERN_ERR "lm93.o: All read word retries failed!!\n"); + return 0; +} + +static int lm93_write_word(struct i2c_client *client, u8 reg, u16 value) +{ + int result; + + /* how to handle write errors? */ + result = i2c_smbus_write_word_data(client, reg, value); + + if (result < 0) + printk(KERN_WARNING "lm93.o: write word data failed, " + "0x%04x at address 0x%02x.\n", value, reg); + + return result; +} + +static u8 lm93_block_buffer[I2C_SMBUS_BLOCK_MAX]; + +/* + read block data into values, retry if not expected length + fbn => index to lm93_block_read_cmds table + (Fixed Block Number - section 14.5.2 of LM93 datasheet) +*/ +static void lm93_read_block(struct i2c_client *client, u8 fbn, u8 *values) +{ + int i, result=0; + + for (i = 1; i <= MAX_RETRIES; i++) { + result = i2c_smbus_read_block_data(client, + lm93_block_read_cmds[fbn].cmd, lm93_block_buffer); + + if (result == lm93_block_read_cmds[fbn].len) { + break; + } else { + printk(KERN_WARNING "lm93.o: block read data failed, " + "command 0x%02x.\n", + lm93_block_read_cmds[fbn].cmd); + mdelay(i + 3); + } + } + + if (result == lm93_block_read_cmds[fbn].len) { + memcpy(values,lm93_block_buffer,lm93_block_read_cmds[fbn].len); + } else { + /* what to do in case of error? */ + } +} + +static void lm93_update_client(struct i2c_client *client) +{ + struct lm93_data *data = client->data; + + down(&data->update_lock); + + if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || + time_before(jiffies, data->last_updated) || !data->valid) { + + data->update(data, client); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +/* update routine for data that has no corresponding SMBus block data command */ +static void lm93_update_client_common(struct lm93_data *data, + struct i2c_client *client) +{ + int i; + u8 *ptr; + + /* temp1 - temp4: limits */ + for (i = 0; i < 4; i++) { + data->temp_lim[i].min = + lm93_read_byte(client, LM93_REG_TEMP_MIN(i)); + data->temp_lim[i].max = + lm93_read_byte(client, LM93_REG_TEMP_MAX(i)); + } + + /* config register */ + data->config = lm93_read_byte(client, LM93_REG_CONFIG); + + /* vid1 - vid2: values */ + for (i = 0; i < 2; i++) + data->vid[i] = lm93_read_byte(client, LM93_REG_VID(i)); + + /* prochot1 - prochot2: limits */ + for (i = 0; i < 2; i++) + data->prochot_max[i] = lm93_read_byte(client, + LM93_REG_PROCHOT_MAX(i)); + + /* vccp1 - vccp2: VID relative limits */ + for (i = 0; i < 2; i++) + data->vccp_limits[i] = lm93_read_byte(client, + LM93_REG_VCCP_LIMIT_OFF(i)); + + /* GPIO input state */ + data->gpi = lm93_read_byte(client, LM93_REG_GPI); + + /* #PROCHOT override state */ + data->prochot_override = lm93_read_byte(client, + LM93_REG_PROCHOT_OVERRIDE); + + /* #PROCHOT intervals */ + data->prochot_interval = lm93_read_byte(client, + LM93_REG_PROCHOT_INTERVAL); + + /* Fan Boost Termperature registers */ + for (i = 0; i < 4; i++) + data->boost[i] = lm93_read_byte(client, LM93_REG_BOOST(i)); + + /* Fan Boost Temperature Hyst. registers */ + data->boost_hyst[0] = lm93_read_byte(client, LM93_REG_BOOST_HYST_12); + data->boost_hyst[1] = lm93_read_byte(client, LM93_REG_BOOST_HYST_34); + + /* Temperature Zone Min. PWM & Hysteresis registers */ + data->auto_pwm_min_hyst[0] = + lm93_read_byte(client, LM93_REG_PWM_MIN_HYST_12); + data->auto_pwm_min_hyst[1] = + lm93_read_byte(client, LM93_REG_PWM_MIN_HYST_34); + + /* #PROCHOT & #VRDHOT PWM Ramp Control register */ + data->pwm_ramp_ctl = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + + /* misc setup registers */ + data->sfc1 = lm93_read_byte(client, LM93_REG_SFC1); + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sf_tach_to_pwm = lm93_read_byte(client, + LM93_REG_SF_TACH_TO_PWM); + + /* write back alarm values to clear */ + for (i = 0, ptr = (u8 *)(&data->block1); i < 8; i++) + lm93_write_byte(client, LM93_REG_HOST_ERROR_1 + i, *(ptr + i)); +} + +/* update routine which uses SMBus block data commands */ +static void lm93_update_client_full(struct lm93_data *data, + struct i2c_client *client) +{ + pr_debug("lm93.o: starting device update (block data enabled)\n"); + + /* in1 - in16: values & limits */ + lm93_read_block(client, 3, (u8 *)(data->block3)); + lm93_read_block(client, 7, (u8 *)(data->block7)); + + /* temp1 - temp4: values */ + lm93_read_block(client, 2, (u8 *)(data->block2)); + + /* prochot1 - prochot2: values */ + lm93_read_block(client, 4, (u8 *)(data->block4)); + + /* fan1 - fan4: values & limits */ + lm93_read_block(client, 5, (u8 *)(data->block5)); + lm93_read_block(client, 8, (u8 *)(data->block8)); + + /* pmw control registers */ + lm93_read_block(client, 9, (u8 *)(data->block9)); + + /* alarm values */ + lm93_read_block(client, 1, (u8 *)(&data->block1)); + + /* auto/pwm registers */ + lm93_read_block(client, 10, (u8 *)(&data->block10)); + + lm93_update_client_common(data, client); +} + +/* update routine which uses SMBus byte/word data commands only */ +static void lm93_update_client_min(struct lm93_data *data, + struct i2c_client *client) +{ + int i,j; + u8 *ptr; + + pr_debug("lm93.o: starting device update (block data disabled)\n"); + + /* in1 - in16: values & limits */ + for (i = 0; i < 16; i++) { + data->block3[i] = + lm93_read_byte(client, LM93_REG_IN(i)); + data->block7[i].min = + lm93_read_byte(client, LM93_REG_IN_MIN(i)); + data->block7[i].max = + lm93_read_byte(client, LM93_REG_IN_MAX(i)); + } + + /* temp1 - temp4: values */ + for (i = 0; i < 4; i++) { + data->block2[i] = + lm93_read_byte(client, LM93_REG_TEMP(i)); + } + + /* prochot1 - prochot2: values */ + for (i = 0; i < 2; i++) { + data->block4[i].cur = + lm93_read_byte(client, LM93_REG_PROCHOT_CUR(i)); + data->block4[i].avg = + lm93_read_byte(client, LM93_REG_PROCHOT_AVG(i)); + } + + /* fan1 - fan4: values & limits */ + for (i = 0; i < 4; i++) { + data->block5[i] = + lm93_read_word(client, LM93_REG_FAN(i)); + data->block8[i] = + lm93_read_word(client, LM93_REG_FAN_MIN(i)); + } + + /* pwm control registers */ + for (i = 0; i < 2; i++) { + for (j = 0; j < 4; j++) { + data->block9[i][j] = + lm93_read_byte(client, LM93_REG_PWM_CTL(i,j)); + } + } + + /* alarm values */ + for (i = 0, ptr = (u8 *)(&data->block1); i < 8; i++) { + *(ptr + i) = + lm93_read_byte(client, LM93_REG_HOST_ERROR_1 + i); + } + + /* auto/pwm (base temp) registers */ + for (i = 0; i < 4; i++) { + data->block10.base[i] = + lm93_read_byte(client, LM93_REG_TEMP_BASE(i)); + } + + /* auto/pwm (offset temp) registers */ + for (i = 0; i < 12; i++) { + data->block10.offset[i] = + lm93_read_byte(client, LM93_REG_TEMP_OFFSET(i)); + } + + lm93_update_client_common(data, client); +} + +/* VID: mV + REG: 6-bits, right justified, *always* using Intel VRM/VRD 10 */ +static int LM93_VID_FROM_REG(u8 reg) +{ + return vid_from_reg((reg & 0x3f), 100); +} + +static void lm93_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_VID1; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_VID_FROM_REG(data->vid[nr]); + *nrels_mag = 1; + } +} + +/* min, max, and nominal voltage readings, per channel (mV)*/ +static const unsigned long lm93_vin_val_min[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3000, +}; +static const unsigned long lm93_vin_val_max[16] = { + 1236, 1236, 1236, 1600, 2000, 2000, 1600, 1600, + 4400, 6500, 3333, 2625, 1312, 1312, 1236, 3600, +}; +/* +static const unsigned long lm93_vin_val_nom[16] = { + 927, 927, 927, 1200, 1500, 1500, 1200, 1200, + 3300, 5000, 2500, 1969, 984, 984, 309, 3300, +}; +*/ + +/* min, max, and nominal register values, per channel (u8) */ +static const u8 lm93_vin_reg_min[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, +}; +static const u8 lm93_vin_reg_max[16] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, +}; +/* +static const u8 lm93_vin_reg_nom[16] = { + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0xc0, +}; +*/ + +/* IN: 1/100 V, limits determined by channel nr + REG: scaling determined by channel nr */ +static u8 LM93_IN_TO_REG(int nr, unsigned val) +{ + /* range limit */ + const long mV = SENSORS_LIMIT(val * 10, + lm93_vin_val_min[nr], lm93_vin_val_max[nr]); + + /* try not to lose too much precision here */ + const long uV = mV * 1000; + const long uV_max = lm93_vin_val_max[nr] * 1000; + const long uV_min = lm93_vin_val_min[nr] * 1000; + + /* convert */ + const long slope = (uV_max - uV_min) / + (lm93_vin_reg_max[nr] - lm93_vin_reg_min[nr]); + const long intercept = uV_min - slope * lm93_vin_reg_min[nr]; + + u8 result = ((uV - intercept + (slope/2)) / slope); + result = SENSORS_LIMIT(result, + lm93_vin_reg_min[nr], lm93_vin_reg_max[nr]); + return result; +} + +static unsigned LM93_IN_FROM_REG(int nr, u8 reg) +{ + const long uV_max = lm93_vin_val_max[nr] * 1000; + const long uV_min = lm93_vin_val_min[nr] * 1000; + + const long slope = (uV_max - uV_min) / + (lm93_vin_reg_max[nr] - lm93_vin_reg_min[nr]); + const long intercept = uV_min - slope * lm93_vin_reg_min[nr]; + + return (slope * reg + intercept + 5000) / 10000; +} + +/* vid in mV , upper == 0 indicates low limit, otherwise upper limit + upper also determines which nibble of the register is returned + (the other nibble will be 0x0) */ +static u8 LM93_IN_REL_TO_REG(unsigned val, int upper, int vid) +{ + long uV_offset = vid * 1000 - val * 10000; + if (upper) { + uV_offset = SENSORS_LIMIT(uV_offset, 12500, 200000); + return (u8)((uV_offset / 12500 - 1) << 4); + } else { + uV_offset = SENSORS_LIMIT(uV_offset, -400000, -25000); + return (u8)((uV_offset / -25000 - 1) << 0); + } +} + +/* vid in mV, upper == 0 indicates low limit, otherwise upper limit */ +static unsigned LM93_IN_REL_FROM_REG(u8 reg, int upper, int vid) +{ + const long uV_offset = upper ? (((reg >> 4 & 0x0f) + 1) * 12500) : + (((reg >> 0 & 0x0f) + 1) * -25000); + const long uV_vid = vid * 1000; + return (uV_vid + uV_offset + 5000) / 10000; +} + +static void lm93_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_IN1; /* 0 <= nr <= 15 */ + int vccp = ctl_name - LM93_SYSCTL_IN7; /* 0 <= vccp <= 1 if relevant */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + + lm93_update_client(client); + + /* for limits, check in7 and in8 for VID relative mode */ + if ((ctl_name==LM93_SYSCTL_IN7 || ctl_name==LM93_SYSCTL_IN8) && + (vccp_limit_type[vccp])) { + long vid = LM93_VID_FROM_REG(data->vid[vccp]); + results[0] = LM93_IN_REL_FROM_REG( + data->vccp_limits[vccp], 0, vid); + results[1] = LM93_IN_REL_FROM_REG( + data->vccp_limits[vccp], 1, vid); + + /* otherwise, use absolute limits */ + } else { + results[0] = LM93_IN_FROM_REG(nr, + data->block7[nr].min); + results[1] = LM93_IN_FROM_REG(nr, + data->block7[nr].max); + } + + results[2] = LM93_IN_FROM_REG(nr, data->block3[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + + /* for limits, check in7 and in8 for VID relative mode */ + if ((ctl_name==LM93_SYSCTL_IN7 || ctl_name==LM93_SYSCTL_IN8) && + (vccp_limit_type[vccp])) { + + long vid = LM93_VID_FROM_REG(data->vid[vccp]); + if (*nrels_mag >= 2) { + data->vccp_limits[vccp] = + (data->vccp_limits[vccp] & 0x0f) | + LM93_IN_REL_TO_REG(results[1], 1, vid); + } + if (*nrels_mag >= 1) { + data->vccp_limits[vccp] = + (data->vccp_limits[vccp] & 0xf0) | + LM93_IN_REL_TO_REG(results[0], 0, vid); + lm93_write_byte(client, + LM93_REG_VCCP_LIMIT_OFF(vccp), + data->vccp_limits[vccp]); + } + + /* otherwise, use absolute limits */ + } else { + if (*nrels_mag >= 1) { + data->block7[nr].min = LM93_IN_TO_REG(nr, + results[0]); + lm93_write_byte(client, LM93_REG_IN_MIN(nr), + data->block7[nr].min); + } + if (*nrels_mag >= 2) { + data->block7[nr].max = LM93_IN_TO_REG(nr, + results[1]); + lm93_write_byte(client, LM93_REG_IN_MAX(nr), + data->block7[nr].max); + } + } + up(&data->update_lock); + } +} + +static unsigned LM93_ALARMS_FROM_REG(struct block1_t b1) +{ + unsigned result; + result = b1.host_status_2 & 0x3f; + + if (vccp_limit_type[0]) + result |= (b1.host_status_4 & 0x10) << 2; + else + result |= b1.host_status_2 & 0x40; + + if (vccp_limit_type[1]) + result |= (b1.host_status_4 & 0x20) << 2; + else + result |= b1.host_status_2 & 0x80; + + result |= b1.host_status_3 << 8; + result |= (b1.fan_status & 0x0f) << 16; + result |= (b1.p1_prochot_status & 0x80) << 13; + result |= (b1.p2_prochot_status & 0x80) << 14; + result |= (b1.host_status_4 & 0xfc) << 20; + result |= (b1.host_status_1 & 0x07) << 28; + return result; +} + +static void lm93_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_ALARMS_FROM_REG(data->block1); + *nrels_mag = 1; + } +} + +#define LM93_TEMP_MIN (-1280) +#define LM93_TEMP_MAX ( 1270) + +/* TEMP: 1/10 degrees C (-128C to +127C) + REG: 1C/bit, two's complement */ +static u8 LM93_TEMP_TO_REG(int temp) +{ + int ntemp = SENSORS_LIMIT(temp, LM93_TEMP_MIN, LM93_TEMP_MAX); + ntemp += (ntemp<0 ? -5 : 5); + return (u8)(ntemp / 10); +} + +static int LM93_TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 10; +} + +static void lm93_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_TEMP_FROM_REG(data->temp_lim[nr].max); + results[1] = LM93_TEMP_FROM_REG(data->temp_lim[nr].min); + results[2] = LM93_TEMP_FROM_REG(data->block2[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + data->temp_lim[nr].max = LM93_TEMP_TO_REG(results[0]); + lm93_write_byte(client, LM93_REG_TEMP_MAX(nr), + data->temp_lim[nr].max); + } + if (*nrels_mag >= 2) { + data->temp_lim[nr].min = LM93_TEMP_TO_REG(results[1]); + lm93_write_byte(client, LM93_REG_TEMP_MIN(nr), + data->temp_lim[nr].min); + } + up(&data->update_lock); + } +} + +/* Determine 4-bit temperature offset resolution */ +static int LM93_TEMP_OFFSET_MODE_FROM_REG(u8 sfc2, int nr) +{ + /* mode: 0 => 1C/bit, nonzero => 0.5C/bit */ + return sfc2 & (nr < 2 ? 0x10 : 0x20); +} + +#define LM93_TEMP_OFFSET_MIN ( 0) +#define LM93_TEMP_OFFSET_MAX0 (150) +#define LM93_TEMP_OFFSET_MAX1 ( 75) + +/* This function is common to all 4-bit temperature offsets + returns 4 bits right justified + mode 0 => 1C/bit, mode !0 => 0.5C/bit */ +static u8 LM93_TEMP_OFFSET_TO_REG(int off, int mode) +{ + int factor = mode ? 5 : 10; + + off = SENSORS_LIMIT(off, LM93_TEMP_OFFSET_MIN, + mode ? LM93_TEMP_OFFSET_MAX1 : LM93_TEMP_OFFSET_MAX0); + return (u8)((off + factor/2) / factor); +} + +/* This function is common to all 4-bit temperature offsets + reg is 4 bits right justified + mode 0 => 1C/bit, mode !0 => 0.5C/bit */ +static int LM93_TEMP_OFFSET_FROM_REG(u8 reg, int mode) +{ + return (reg & 0x0f) * (mode ? 5 : 10); +} + +/* TEMP: 1/10 degrees C (0C to +15C (mode 0) or +7.5C (mode non-zero)) + REG: 1.0C/bit (mode 0) or 0.5C/bit (mode non-zero) + 0 <= nr <= 3 */ +static u8 LM93_TEMP_AUTO_OFFSET_TO_REG(u8 old, int off, int nr, int mode) +{ + u8 new = LM93_TEMP_OFFSET_TO_REG(off, mode); + + /* temp1-temp2 (nr=0,1) use lower nibble */ + if (nr < 2) + return (old & 0xf0) | (new & 0x0f); + + /* temp3-temp4 (nr=2,3) use upper nibble */ + else + return (new << 4 & 0xf0) | (old & 0x0f); +} + +/* 0 <= nr <= 3 */ +static int LM93_TEMP_AUTO_OFFSET_FROM_REG(u8 reg, int nr, int mode) +{ + /* temp1-temp2 (nr=0,1) use lower nibble */ + if (nr < 2) + return LM93_TEMP_OFFSET_FROM_REG(reg & 0x0f, mode); + + /* temp3-temp4 (nr=2,3) use upper nibble */ + else + return LM93_TEMP_OFFSET_FROM_REG(reg >> 4 & 0x0f, mode); +} + +static void lm93_temp_auto_offsets(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1_AUTO_OFFSETS; + int ii; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + int mode; + lm93_update_client(client); + + /* mode: 0 => 1C/bit, nonzero => 0.5C/bit */ + mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + + for (ii = 0; ii < 12; ii++) { + results[ii] = LM93_TEMP_AUTO_OFFSET_FROM_REG( + data->block10.offset[ii], nr, mode); + } + *nrels_mag = 12; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + /* we only care about the first 12 values */ + int nrels = *nrels_mag > 12 ? 12 : *nrels_mag; + + down(&data->update_lock); + + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + + for (ii = 0; ii < nrels; ii++) { + data->block10.offset[ii] = LM93_TEMP_AUTO_OFFSET_TO_REG( + data->block10.offset[ii], results[ii], nr, 1); + lm93_write_byte(client, LM93_REG_TEMP_OFFSET(ii), + data->block10.offset[ii]); + } + up(&data->update_lock); + } +} + +/* RPM: (82.5 to 1350000) + REG: 14-bits, LE, *left* justified */ +static u16 LM93_FAN_TO_REG(long rpm) +{ + u16 count, regs; + + if (rpm == 0) { + count = 0x3fff; + } else { + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + count = SENSORS_LIMIT((1350000 + rpm) / rpm, 1, 0x3ffe); + } + + regs = count << 2; + return cpu_to_le16(regs); +} + +static int LM93_FAN_FROM_REG(u16 regs) +{ + const u16 count = le16_to_cpu(regs) >> 2; + return count==0 ? -1 : count==0x3fff ? 0: 1350000 / count; +} + +static void lm93_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_FAN1; + + if (0 > nr || nr > 3) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_FAN_FROM_REG(data->block8[nr]); /* min */ + results[1] = LM93_FAN_FROM_REG(data->block5[nr]); /* val */ + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + data->block8[nr] = LM93_FAN_TO_REG(results[0]); + lm93_write_word(client, LM93_REG_FAN_MIN(nr), + data->block8[nr]); + up(&data->update_lock); + } + } +} + +/* PROCHOT: 0-255, 0 => 0%, 255 => > 96.6% + * REG: (same) */ +static u8 LM93_PROCHOT_TO_REG(long prochot) +{ + prochot = SENSORS_LIMIT(prochot, 0, 255); + return (u8)prochot; +} + +static void lm93_prochot(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_PROCHOT1; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = data->prochot_max[nr]; + results[1] = data->block4[nr].avg; + results[2] = data->block4[nr].avg; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + data->prochot_max[nr] = LM93_PROCHOT_TO_REG(results[0]); + lm93_write_byte(client, LM93_REG_PROCHOT_MAX(nr), + data->prochot_max[nr]); + } + up(&data->update_lock); + } +} + +/* PROCHOT-OVERRIDE; 0-15, 0 is 6.25%, 15 is 99.88% + * REG: (same) */ +static u8 LM93_PROCHOT_OVERRIDE_TO_REG(int force1, int force2, long prochot) +{ + u8 result = 0; + + result |= force1 ? 0x80 : 0x00; + result |= force2 ? 0x40 : 0x00; + prochot = SENSORS_LIMIT(prochot, 0, 15); + result |= prochot; + return result; +} + +static void lm93_prochot_override(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + + if (ctl_name != LM93_SYSCTL_PROCHOT_OVERRIDE) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = (data->prochot_override & 0x80) ? 1 : 0; + results[1] = (data->prochot_override & 0x40) ? 1 : 0; + results[2] = data->prochot_override & 0x0f; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + + /* grab old values */ + int force2 = (data->prochot_override & 0x40) ? 1 : 0; + int prochot = data->prochot_override & 0x0f; + + down(&data->update_lock); + if (*nrels_mag >= 3) { + data->prochot_override = LM93_PROCHOT_OVERRIDE_TO_REG( + results[0], results[1], results[2]); + } + if (*nrels_mag == 2) { + data->prochot_override = LM93_PROCHOT_OVERRIDE_TO_REG( + results[0], results[1], prochot); + } + if (*nrels_mag == 1) { + data->prochot_override = LM93_PROCHOT_OVERRIDE_TO_REG( + results[0], force2, prochot); + } + if (*nrels_mag >= 1) { + lm93_write_byte(client, LM93_REG_PROCHOT_OVERRIDE, + data->prochot_override); + } + up(&data->update_lock); + } +} + +/* PROCHOT-INTERVAL: 73 - 37200 (1/100 seconds) + * REG: 0-9 as mapped below */ +static int lm93_interval_map[10] = { + 73, 146, 290, 580, 1170, 2330, 4660, 9320, 18600, 37200, +}; + +static int LM93_INTERVAL_FROM_REG(u8 reg) +{ + return lm93_interval_map[reg & 0x0f]; +} + +/* round up to nearest match */ +static u8 LM93_INTERVAL_TO_REG(long interval) +{ + int i; + for (i = 0; i < 9; i++) + if (interval <= lm93_interval_map[i]) + break; + + /* can fall through with i==9 */ + return (u8)i; +} + +static void lm93_prochot_interval(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + + if (ctl_name != LM93_SYSCTL_PROCHOT_INTERVAL) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_INTERVAL_FROM_REG( + data->prochot_interval & 0x0f); + results[1] = LM93_INTERVAL_FROM_REG( + (data->prochot_interval & 0xf0) >> 4); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + data->prochot_interval = lm93_read_byte(client, + LM93_REG_PROCHOT_INTERVAL); + if (*nrels_mag >= 2) { + data->prochot_interval = + (LM93_INTERVAL_TO_REG(results[1]) << 4) | + (data->prochot_interval & 0x0f); + } + if (*nrels_mag >= 1) { + data->prochot_interval = + (data->prochot_interval & 0xf0) | + LM93_INTERVAL_TO_REG(results[0]); + lm93_write_byte(client, LM93_REG_PROCHOT_INTERVAL, + data->prochot_interval); + } + up(&data->update_lock); + } +} + +/* PWM: 0-255 per sensors documentation + REG: 0-13 as mapped below... right justified */ +typedef enum { LM93_PWM_MAP_HI_FREQ, LM93_PWM_MAP_LO_FREQ } pwm_freq_t; +static int lm93_pwm_map[2][14] = { + { + 0x00, /* 0.00% */ 0x40, /* 25.00% */ + 0x50, /* 31.25% */ 0x60, /* 37.50% */ + 0x70, /* 43.75% */ 0x80, /* 50.00% */ + 0x90, /* 56.25% */ 0xa0, /* 62.50% */ + 0xb0, /* 68.75% */ 0xc0, /* 75.00% */ + 0xd0, /* 81.25% */ 0xe0, /* 87.50% */ + 0xf0, /* 93.75% */ 0xff, /* 100.00% */ + }, + { + 0x00, /* 0.00% */ 0x40, /* 25.00% */ + 0x49, /* 28.57% */ 0x52, /* 32.14% */ + 0x5b, /* 35.71% */ 0x64, /* 39.29% */ + 0x6d, /* 42.86% */ 0x76, /* 46.43% */ + 0x80, /* 50.00% */ 0x89, /* 53.57% */ + 0x92, /* 57.14% */ 0xb6, /* 71.43% */ + 0xdb, /* 85.71% */ 0xff, /* 100.00% */ + }, +}; + +static int LM93_PWM_FROM_REG(u8 reg, pwm_freq_t freq) +{ + return lm93_pwm_map[freq][reg & 0x0f]; +} + +/* round up to nearest match */ +static u8 LM93_PWM_TO_REG(int pwm, pwm_freq_t freq) +{ + int i; + for (i = 0; i < 13; i++) + if (pwm <= lm93_pwm_map[freq][i]) + break; + + /* can fall through with i==13 */ + return (u8)i; +} + +static void lm93_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_PWM1; + u8 ctl2, ctl4; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + ctl2 = data->block9[nr][LM93_PWM_CTL2]; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + results[1] = (ctl2 & 0x01) ? 1 : 0; + ctl2 = ctl2 >> 4 & 0x0f; + if (results[1]) /* show user commanded value if enabled */ + results[0] = data->pwm_override[nr]; + else /* show present h/w value if manual pwm disabled */ + results[0] = LM93_PWM_FROM_REG(ctl2, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ); + *nrels_mag = 2; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + ctl2 = lm93_read_byte( + client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL2)); + ctl4 = lm93_read_byte( + client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4)); + ctl2 = (ctl2 & 0x0f) | + LM93_PWM_TO_REG(results[0], (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ) << 4; + if (*nrels_mag >= 2) { + if (results[1]) + ctl2 |= 0x01; + else + ctl2 &= ~0x01; + } + /* save user commanded value */ + data->pwm_override[nr] = + LM93_PWM_FROM_REG(ctl2 >> 4 & 0x0f, + (ctl4 & 0x07) ? LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + lm93_write_byte(client, + LM93_REG_PWM_CTL(nr,LM93_PWM_CTL2), ctl2); + up(&data->update_lock); + } + } +} + +/* PWM FREQ: HZ + REG: 0-7 as mapped below */ +static int lm93_pwm_freq_map[8] = { + 22500, 96, 84, 72, 60, 48, 36, 12 +}; + +static int LM93_PWM_FREQ_FROM_REG(u8 reg) +{ + return lm93_pwm_freq_map[reg & 0x07]; +} + +/* round up to nearest match */ +static u8 LM93_PWM_FREQ_TO_REG(int freq) +{ + int i; + for (i = 7; i > 0; i--) + if (freq <= lm93_pwm_freq_map[i]) + break; + + /* can fall through with i==0 */ + return (u8)i; +} + +/* helper function - must grab data->update_lock before calling + fan is 0-3, indicating fan1-fan4 */ +static void lm93_write_fan_smart_tach(struct i2c_client *client, + struct lm93_data *data, int fan, long value) +{ + /* insert the new mapping and write it out */ + data->sf_tach_to_pwm = lm93_read_byte(client, LM93_REG_SF_TACH_TO_PWM); + data->sf_tach_to_pwm &= ~(0x3 << fan * 2); + data->sf_tach_to_pwm |= value << fan * 2; + lm93_write_byte(client, LM93_REG_SF_TACH_TO_PWM, data->sf_tach_to_pwm); + + /* insert the enable bit and write it out */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + if (value) + data->sfc2 |= 1 << fan; + else + data->sfc2 &= ~(1 << fan); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); +} + +/* helper function - must grab data->update_lock before calling + pwm is 0-1, indicating pwm1-pwm2 + this disables smart tach for all tach channels bound to the given pwm */ +static void lm93_disable_fan_smart_tach(struct i2c_client *client, + struct lm93_data *data, int pwm) +{ + int mapping = lm93_read_byte(client, LM93_REG_SF_TACH_TO_PWM); + int mask; + + /* collapse the mapping into a mask of enable bits */ + mapping = (mapping >> pwm) & 0x55; + mask = mapping & 0x01; + mask |= (mapping & 0x04) >> 1; + mask |= (mapping & 0x10) >> 2; + mask |= (mapping & 0x40) >> 3; + + /* disable smart tach according to the mask */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 &= ~mask; + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); +} + +static void lm93_pwm_freq(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_PWM1_FREQ; + u8 ctl4; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + results[0] = LM93_PWM_FREQ_FROM_REG(ctl4); + *nrels_mag = 1; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + ctl4 = lm93_read_byte( client, + LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4)); + ctl4 = (ctl4 & 0xf8) | LM93_PWM_FREQ_TO_REG(results[0]); + data->block9[nr][LM93_PWM_CTL4] = ctl4; + + /* ctl4 == 0 -> 22.5KHz -> disable smart tach */ + if (!ctl4) + lm93_disable_fan_smart_tach(client, data, nr); + + lm93_write_byte(client, + LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4), ctl4); + up(&data->update_lock); + } + } +} + +/* GPIO: 0-255, GPIO0 is LSB + * REG: inverted */ +static unsigned LM93_GPI_FROM_REG(u8 reg) +{ + return ~reg & 0xff; +} + +static void lm93_gpio(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + + if (ctl_name != LM93_SYSCTL_GPIO) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_GPI_FROM_REG(data->gpi); + *nrels_mag = 1; + } +} + +static void lm93_temp_auto_base(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1_AUTO_BASE; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_TEMP_FROM_REG(data->block10.base[nr]); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + data->block10.base[nr] = LM93_TEMP_TO_REG(results[0]); + lm93_write_byte(client, LM93_REG_TEMP_BASE(nr), + data->block10.base[nr]); + } + up(&data->update_lock); + } +} + +static void lm93_temp_auto_boost(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1_AUTO_BOOST; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_TEMP_FROM_REG(data->boost[nr]); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + data->boost[nr] = LM93_TEMP_TO_REG(results[0]); + lm93_write_byte(client, LM93_REG_BOOST(nr), + data->boost[nr]); + } + up(&data->update_lock); + } + +} + +static u8 LM93_AUTO_BOOST_HYST_TO_REG(struct lm93_data *data, long hyst, + int nr, int mode) +{ + u8 reg = LM93_TEMP_OFFSET_TO_REG( + (LM93_TEMP_FROM_REG(data->boost[nr]) - hyst), mode); + + switch (nr) { + case 0: + reg = (data->boost_hyst[0] & 0xf0) | (reg & 0x0f); + break; + case 1: + reg = (reg << 4 & 0xf0) | (data->boost_hyst[0] & 0x0f); + break; + case 2: + reg = (data->boost_hyst[1] & 0xf0) | (reg & 0x0f); + break; + case 3: + default: + reg = (reg << 4 & 0xf0) | (data->boost_hyst[1] & 0x0f); + break; + } + + return reg; +} + +static int LM93_AUTO_BOOST_HYST_FROM_REGS(struct lm93_data *data, int nr, + int mode) +{ + u8 reg; + + switch (nr) { + case 0: + reg = data->boost_hyst[0] & 0x0f; + break; + case 1: + reg = data->boost_hyst[0] >> 4 & 0x0f; + break; + case 2: + reg = data->boost_hyst[1] & 0x0f; + break; + case 3: + default: + reg = data->boost_hyst[1] >> 4 & 0x0f; + break; + } + + return LM93_TEMP_FROM_REG(data->boost[nr]) - + LM93_TEMP_OFFSET_FROM_REG(reg, mode); +} + +static void lm93_temp_auto_boost_hyst(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1_AUTO_BOOST_HYST; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + int mode; + lm93_update_client(client); + + /* mode: 0 => 1C/bit, nonzero => 0.5C/bit */ + mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + + results[0] = LM93_AUTO_BOOST_HYST_FROM_REGS(data, nr, mode); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + + data->boost_hyst[nr/2] = + LM93_AUTO_BOOST_HYST_TO_REG(data, + results[0], nr, 1); + lm93_write_byte(client, LM93_REG_BOOST_HYST(nr), + data->boost_hyst[nr/2]); + up(&data->update_lock); + } + } +} + +static void lm93_temp_auto_pwm_min(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1_AUTO_PWM_MIN; + u8 reg, ctl4; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + reg = data->auto_pwm_min_hyst[nr/2] >> 4 & 0x0f; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + results[0] = LM93_PWM_FROM_REG(reg, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + reg = lm93_read_byte(client, LM93_REG_PWM_MIN_HYST(nr)); + ctl4 = lm93_read_byte( + client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4)); + reg = (reg & 0x0f) | + LM93_PWM_TO_REG(results[0], (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ) << 4; + + data->auto_pwm_min_hyst[nr/2] = reg; + lm93_write_byte(client, LM93_REG_PWM_MIN_HYST(nr), reg); + up(&data->update_lock); + } + } +} + +static void lm93_temp_auto_offset_hyst(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_TEMP1_AUTO_OFFSET_HYST; + + if (0 > nr || nr > 2) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + int mode; + lm93_update_client(client); + + /* mode: 0 => 1C/bit, nonzero => 0.5C/bit */ + mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + + results[0] = LM93_TEMP_OFFSET_FROM_REG( + data->auto_pwm_min_hyst[nr/2], mode); + + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + u8 reg; + down(&data->update_lock); + + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + + reg = data->auto_pwm_min_hyst[nr/2]; + reg = (reg & 0xf0) | + (LM93_TEMP_OFFSET_TO_REG(results[0], 1) & 0x0f); + + data->auto_pwm_min_hyst[nr/2] = reg; + lm93_write_byte(client, LM93_REG_PWM_MIN_HYST(nr), reg); + up(&data->update_lock); + } + } +} + +/* some tedious bit-twiddling here to deal with the register format: + + data->sf_tach_to_pwm: (tach to pwm mapping bits) + + bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + T4:P2 T4:P1 T3:P2 T3:P1 T2:P2 T2:P1 T1:P2 T1:P1 + + data->sfc2: (enable bits) + + bit | 3 | 2 | 1 | 0 + T4 T3 T2 T1 +*/ + +static void lm93_fan_smart_tach(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_FAN1_SMART_TACH; + + if (0 > nr || nr > 3) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + int mapping; + + lm93_update_client(client); + + /* extract the relevant mapping */ + mapping = (data->sf_tach_to_pwm >> (nr * 2)) & 0x03; + + /* if there's a mapping and it's enabled */ + if (mapping && ((data->sfc2 >> nr) & 0x01)) + results[0] = mapping; + else + results[0] = 0; + + *nrels_mag = 1; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + + /* sanity test, ignore the write otherwise */ + if (0 <= results[0] && results[0] <= 2) { + + /* can't enable if pwm freq is 22.5KHz */ + if (results[0]) { + u8 ctl4 = lm93_read_byte(client, + LM93_REG_PWM_CTL(results[0]-1, + LM93_PWM_CTL4)); + if ((ctl4 & 0x07) == 0) + results[0] = 0; + } + + lm93_write_fan_smart_tach(client, data, nr, + results[0]); + } + up(&data->update_lock); + } + } +} + +static void lm93_prochot_short(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + + if (ctl_name != LM93_SYSCTL_PROCHOT_SHORT) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = (data->config & 0x10) ? 1 : 0; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + if (results[0]) + data->config |= 0x10; + else + data->config &= ~0x10; + lm93_write_byte(client, LM93_REG_CONFIG, data->config); + } + up(&data->update_lock); + } +} + +static void lm93_vrdhot(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_VRDHOT1; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = data->block1.host_status_1 & (1 << (nr+4)) ? 1 : 0; + *nrels_mag = 1; + } +} + +static void lm93_pwm_auto_chan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_PWM1_AUTO_CHANNELS; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = data->block9[nr][LM93_PWM_CTL1]; + *nrels_mag = 1; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + data->block9[nr][LM93_PWM_CTL1] = + SENSORS_LIMIT(results[0], 0, 255); + lm93_write_byte(client, + LM93_REG_PWM_CTL(nr,LM93_PWM_CTL1), + data->block9[nr][LM93_PWM_CTL1]); + up(&data->update_lock); + } + } +} + +static void lm93_pwm_auto_spinup_min(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_PWM1_AUTO_SPINUP_MIN; + u8 ctl3, ctl4; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + ctl3 = data->block9[nr][LM93_PWM_CTL3]; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + results[0] = LM93_PWM_FROM_REG(ctl3 & 0x0f, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ); + *nrels_mag = 1; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + down(&data->update_lock); + ctl3 = lm93_read_byte(client, + LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3)); + ctl4 = lm93_read_byte(client, + LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + ctl3 = (ctl3 & 0xf0) | + LM93_PWM_TO_REG(results[0], (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + data->block9[nr][LM93_PWM_CTL3] = ctl3; + lm93_write_byte(client, + LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3), ctl3); + up(&data->update_lock); + } + } +} + +/* TIME: 1/100 seconds + * REG: 0-7 as mapped below */ +static int lm93_spinup_time_map[8] = { + 0, 10, 25, 40, 70, 100, 200, 400, +}; + +static int LM93_SPINUP_TIME_FROM_REG(u8 reg) +{ + return lm93_spinup_time_map[reg >> 5 & 0x07]; +} + +/* round up to nearest match */ +static u8 LM93_SPINUP_TIME_TO_REG(int time) +{ + int i; + for (i = 0; i < 7; i++) + if (time <= lm93_spinup_time_map[i]) + break; + + /* can fall through with i==8 */ + return (u8)i; +} + +static void lm93_pwm_auto_spinup_time(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + int nr = ctl_name - LM93_SYSCTL_PWM1_AUTO_SPINUP_TIME; + + if (0 > nr || nr > 1) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + results[0] = LM93_SPINUP_TIME_FROM_REG( + data->block9[nr][LM93_PWM_CTL3]); + *nrels_mag = 1; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + u8 ctl3; + down(&data->update_lock); + ctl3 = lm93_read_byte(client, + LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3)); + ctl3 = (ctl3 & 0x1f) | (LM93_SPINUP_TIME_TO_REG( + results[0]) << 5 & 0xe0); + data->block9[nr][LM93_PWM_CTL3] = ctl3; + lm93_write_byte(client, + LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3), ctl3); + up(&data->update_lock); + } + } +} + +#define LM93_RAMP_MIN 0 +#define LM93_RAMP_MAX 75 + +/* RAMP: 1/100 seconds + REG: 50mS/bit 4-bits right justified */ +static u8 LM93_RAMP_TO_REG(int ramp) +{ + ramp = SENSORS_LIMIT(ramp, LM93_RAMP_MIN, LM93_RAMP_MAX); + return (u8)((ramp + 2) / 5); +} + +static int LM93_RAMP_FROM_REG(u8 reg) +{ + return (reg & 0x0f) * 5; +} + +static void lm93_pwm_auto_ramp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm93_data *data = client->data; + + if (!(ctl_name == LM93_SYSCTL_PWM_AUTO_PROCHOT_RAMP || + ctl_name == LM93_SYSCTL_PWM_AUTO_VRDHOT_RAMP)) + return; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm93_update_client(client); + if (ctl_name == LM93_SYSCTL_PWM_AUTO_PROCHOT_RAMP) + results[0] = LM93_RAMP_FROM_REG( + data->pwm_ramp_ctl >> 4 & 0x0f); + else + results[0] = LM93_RAMP_FROM_REG( + data->pwm_ramp_ctl & 0x0f); + *nrels_mag = 1; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + u8 ramp; + down(&data->update_lock); + ramp = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + if (ctl_name == LM93_SYSCTL_PWM_AUTO_PROCHOT_RAMP) + ramp = (ramp & 0x0f) | (LM93_RAMP_TO_REG( + results[0]) << 4 & 0xf0); + else + ramp = (ramp & 0xf0) | (LM93_RAMP_TO_REG( + results[0]) & 0x0f); + lm93_write_byte(client, LM93_REG_PWM_RAMP_CTL, ramp); + up(&data->update_lock); + } + } +} + +/* These files are created for each detected LM93. 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. */ + +#define LM93_SYSCTL_IN(nr) {LM93_SYSCTL_IN##nr, "in" #nr, NULL, 0, \ + 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_in} +#define LM93_SYSCTL_TEMP(nr) {LM93_SYSCTL_TEMP##nr, "temp" #nr, NULL, 0, \ + 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_temp} +#define LM93_SYSCTL_TEMP_AUTO_BASE(nr) {LM93_SYSCTL_TEMP##nr##_AUTO_BASE, \ + "temp" #nr "_auto_base", NULL, 0, 0644, NULL, &i2c_proc_real, \ + &i2c_sysctl_real, NULL, &lm93_temp_auto_base} +#define LM93_SYSCTL_TEMP_AUTO_OFFSETS(nr) {LM93_SYSCTL_TEMP##nr##_AUTO_OFFSETS,\ + "temp" #nr "_auto_offsets", NULL, 0, 0644, NULL, &i2c_proc_real, \ + &i2c_sysctl_real, NULL, &lm93_temp_auto_offsets} +#define LM93_SYSCTL_TEMP_AUTO_BOOST(nr) { LM93_SYSCTL_TEMP##nr##_AUTO_BOOST, \ + "temp" #nr "_auto_boost", NULL, 0, 0644, NULL, &i2c_proc_real, \ + &i2c_sysctl_real, NULL, &lm93_temp_auto_boost} +#define LM93_SYSCTL_TEMP_AUTO_BOOST_HYST(nr) { \ + LM93_SYSCTL_TEMP##nr##_AUTO_BOOST_HYST, "temp" #nr "_auto_boost_hyst", \ + NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, \ + &lm93_temp_auto_boost_hyst} +#define LM93_SYSCTL_TEMP_AUTO_PWM_MIN(nr) { \ + LM93_SYSCTL_TEMP##nr##_AUTO_PWM_MIN, "temp" #nr "_auto_pwm_min", \ + NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, \ + &lm93_temp_auto_pwm_min} +#define LM93_SYSCTL_TEMP_AUTO_OFFSET_HYST(nr) { \ + LM93_SYSCTL_TEMP##nr##_AUTO_OFFSET_HYST,"temp" #nr "_auto_offset_hyst",\ + NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, \ + &lm93_temp_auto_offset_hyst} +#define LM93_SYSCTL_PWM(nr) {LM93_SYSCTL_PWM##nr, "pwm" #nr, NULL, 0, \ + 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_pwm} +#define LM93_SYSCTL_PWM_FREQ(nr) {LM93_SYSCTL_PWM##nr##_FREQ, \ + "pwm" #nr "_freq", NULL, 0, 0644, NULL, &i2c_proc_real, \ + &i2c_sysctl_real, NULL, &lm93_pwm_freq} +#define LM93_SYSCTL_PWM_AUTO_CHAN(nr) {LM93_SYSCTL_PWM##nr##_AUTO_CHANNELS, \ + "pwm" #nr "_auto_channels", NULL, 0, 0644, NULL, &i2c_proc_real, \ + &i2c_sysctl_real, NULL, &lm93_pwm_auto_chan} +#define LM93_SYSCTL_PWM_AUTO_SPINUP_MIN(nr) { \ + LM93_SYSCTL_PWM##nr##_AUTO_SPINUP_MIN, "pwm" #nr "_auto_spinup_min", \ + NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, \ + &lm93_pwm_auto_spinup_min} +#define LM93_SYSCTL_PWM_AUTO_SPINUP_TIME(nr) { \ + LM93_SYSCTL_PWM##nr##_AUTO_SPINUP_TIME, "pwm" #nr "_auto_spinup_time", \ + NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, \ + &lm93_pwm_auto_spinup_time} +#define LM93_SYSCTL_FAN(nr) {LM93_SYSCTL_FAN##nr, "fan" #nr, NULL, 0, \ + 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_fan} +#define LM93_SYSCTL_VID(nr) {LM93_SYSCTL_VID##nr, "vid" #nr, NULL, 0, \ + 0444, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_vid} +#define LM93_SYSCTL_PROCHOT(nr) {LM93_SYSCTL_PROCHOT##nr, "prochot" #nr, NULL, \ + 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_prochot} +#define LM93_SYSCTL_VRDHOT(nr) {LM93_SYSCTL_VRDHOT##nr, "vrdhot" #nr, NULL, \ + 0, 0444, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_vrdhot} +#define LM93_SYSCTL_FAN_SMART_TACH(nr) {LM93_SYSCTL_FAN##nr##_SMART_TACH, \ + "fan" #nr "_smart_tach", NULL, 0, 0644, NULL, &i2c_proc_real, \ + &i2c_sysctl_real, NULL, &lm93_fan_smart_tach} +static ctl_table lm93_dir_table_template[] = { + LM93_SYSCTL_IN(1), + LM93_SYSCTL_IN(2), + LM93_SYSCTL_IN(3), + LM93_SYSCTL_IN(4), + LM93_SYSCTL_IN(5), + LM93_SYSCTL_IN(6), + LM93_SYSCTL_IN(7), + LM93_SYSCTL_IN(8), + LM93_SYSCTL_IN(9), + LM93_SYSCTL_IN(10), + LM93_SYSCTL_IN(11), + LM93_SYSCTL_IN(12), + LM93_SYSCTL_IN(13), + LM93_SYSCTL_IN(14), + LM93_SYSCTL_IN(15), + LM93_SYSCTL_IN(16), + + LM93_SYSCTL_TEMP(1), + LM93_SYSCTL_TEMP(2), + LM93_SYSCTL_TEMP(3), + + LM93_SYSCTL_TEMP_AUTO_BASE(1), + LM93_SYSCTL_TEMP_AUTO_BASE(2), + LM93_SYSCTL_TEMP_AUTO_BASE(3), + + LM93_SYSCTL_TEMP_AUTO_OFFSETS(1), + LM93_SYSCTL_TEMP_AUTO_OFFSETS(2), + LM93_SYSCTL_TEMP_AUTO_OFFSETS(3), + + LM93_SYSCTL_TEMP_AUTO_BOOST(1), + LM93_SYSCTL_TEMP_AUTO_BOOST(2), + LM93_SYSCTL_TEMP_AUTO_BOOST(3), + + LM93_SYSCTL_TEMP_AUTO_BOOST_HYST(1), + LM93_SYSCTL_TEMP_AUTO_BOOST_HYST(2), + LM93_SYSCTL_TEMP_AUTO_BOOST_HYST(3), + + LM93_SYSCTL_TEMP_AUTO_PWM_MIN(1), + LM93_SYSCTL_TEMP_AUTO_PWM_MIN(2), + LM93_SYSCTL_TEMP_AUTO_PWM_MIN(3), + + LM93_SYSCTL_TEMP_AUTO_OFFSET_HYST(1), + LM93_SYSCTL_TEMP_AUTO_OFFSET_HYST(2), + LM93_SYSCTL_TEMP_AUTO_OFFSET_HYST(3), + + LM93_SYSCTL_FAN(1), + LM93_SYSCTL_FAN(2), + LM93_SYSCTL_FAN(3), + LM93_SYSCTL_FAN(4), + + LM93_SYSCTL_FAN_SMART_TACH(1), + LM93_SYSCTL_FAN_SMART_TACH(2), + LM93_SYSCTL_FAN_SMART_TACH(3), + LM93_SYSCTL_FAN_SMART_TACH(4), + + LM93_SYSCTL_PWM(1), + LM93_SYSCTL_PWM(2), + + LM93_SYSCTL_PWM_FREQ(1), + LM93_SYSCTL_PWM_FREQ(2), + + LM93_SYSCTL_PWM_AUTO_CHAN(1), + LM93_SYSCTL_PWM_AUTO_CHAN(2), + + LM93_SYSCTL_PWM_AUTO_SPINUP_MIN(1), + LM93_SYSCTL_PWM_AUTO_SPINUP_MIN(2), + + LM93_SYSCTL_PWM_AUTO_SPINUP_TIME(1), + LM93_SYSCTL_PWM_AUTO_SPINUP_TIME(2), + + {LM93_SYSCTL_PWM_AUTO_PROCHOT_RAMP, "pwm_auto_prochot_ramp", NULL, 0, + 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, + &lm93_pwm_auto_ramp}, + + {LM93_SYSCTL_PWM_AUTO_VRDHOT_RAMP, "pwm_auto_vrdhot_ramp", NULL, 0, + 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, + &lm93_pwm_auto_ramp}, + + LM93_SYSCTL_VID(1), + LM93_SYSCTL_VID(2), + + LM93_SYSCTL_PROCHOT(1), + LM93_SYSCTL_PROCHOT(2), + + {LM93_SYSCTL_PROCHOT_SHORT, "prochot_short", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_prochot_short}, + + {LM93_SYSCTL_PROCHOT_OVERRIDE, "prochot_override", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_prochot_override}, + + {LM93_SYSCTL_PROCHOT_INTERVAL, "prochot_interval", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_prochot_interval}, + + LM93_SYSCTL_VRDHOT(1), + LM93_SYSCTL_VRDHOT(2), + + {LM93_SYSCTL_GPIO, "gpio", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm93_gpio}, + + {LM93_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm93_alarms}, + + {0} +}; + +static void lm93_init_client(struct i2c_client *client) +{ + int i; + u8 reg; + + /* configure VID pin input thresholds */ + reg = lm93_read_byte(client, LM93_REG_GPI_VID_CTL); + lm93_write_byte(client, LM93_REG_GPI_VID_CTL, + reg | (vid_agtl ? 0x03 : 0x00)); + + if (init) { + /* enable #ALERT pin */ + reg = lm93_read_byte(client, LM93_REG_CONFIG); + lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x08); + + /* enable ASF mode for BMC status registers */ + reg = lm93_read_byte(client, LM93_REG_STATUS_CONTROL); + lm93_write_byte(client, LM93_REG_STATUS_CONTROL, reg | 0x02); + + /* set sleep state to S0 */ + lm93_write_byte(client, LM93_REG_SLEEP_CONTROL, 0); + + /* unmask #VRDHOT and dynamic VCCP (if nec) error events */ + reg = lm93_read_byte(client, LM93_REG_MISC_ERR_MASK); + reg &= ~0x03; + reg &= ~(vccp_limit_type[0] ? 0x10 : 0); + reg &= ~(vccp_limit_type[1] ? 0x20 : 0); + lm93_write_byte(client, LM93_REG_MISC_ERR_MASK, reg); + } + + /* start monitoring */ + reg = lm93_read_byte(client, LM93_REG_CONFIG); + lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x01); + + /* spin until ready */ + for (i=0; i<20; i++) { + mdelay(10); + if ((lm93_read_byte(client, LM93_REG_CONFIG) & 0x80) == 0x80) + return; + } + + printk(KERN_WARNING "lm93.o: timed out waiting for sensor " + "chip to signal ready!\n"); +} + +/* forward declaration */ +static struct i2c_driver lm93_driver; + +/* This function is called by i2c_detect */ +static int lm93_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int err = 0, func; + struct lm93_data *data; + struct i2c_client *client; + void (*update)(struct lm93_data *, struct i2c_client *); + + /* lm93 is SMBus only */ + if (i2c_is_isa_adapter(adapter)) { + pr_debug("lm93.o: detect failed, " + "cannot attach to legacy adapter!\n"); + goto ERROR0; + } + + /* choose update routine based on bus capabilities */ + func = i2c_get_functionality(adapter); + + if ( ((LM93_SMBUS_FUNC_FULL & func) == LM93_SMBUS_FUNC_FULL) && + (!disable_block) ) { + pr_debug("lm93.o: using SMBus block data transactions\n"); + update = lm93_update_client_full; + } else if ((LM93_SMBUS_FUNC_MIN & func) == LM93_SMBUS_FUNC_MIN) { + pr_debug("lm93.o: disabled SMBus block data transactions\n"); + update = lm93_update_client_min; + } else { + pr_debug("lm93.o: detect failed, " + "smbus byte and/or word data not supported!\n"); + 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 lm78_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct lm93_data), GFP_KERNEL))) { + pr_debug("lm93.o: detect failed, kmalloc failed!\n"); + err = -ENOMEM; + goto ERROR0; + } + + client = &data->client; + client->addr = address; + client->data = data; + client->adapter = adapter; + client->driver = &lm93_driver; + client->flags = 0; + + /* detection */ + if (kind < 0) { + int mfr = lm93_read_byte(client, LM93_REG_MFR_ID); + + if (mfr != 0x01) { + pr_debug("lm93.o: detect failed, " + "bad manufacturer id 0x%02x!\n", mfr); + goto ERROR1; + } + } + + if (kind <= 0) { + int ver = lm93_read_byte(client, LM93_REG_VER); + + if ((ver == LM93_MFR_ID) || (ver == LM93_MFR_ID_PROTOTYPE)) { + kind = lm93; + } else { + pr_debug("lm93.o: detect failed, " + "bad version id 0x%02x!\n", ver); + if (kind == 0) + pr_debug("lm93.o: " + "(ignored 'force' parameter)\n"); + goto ERROR1; + } + } + + /* fill in remaining client fields */ + strcpy(client->name, "LM93 chip"); + client->id = lm93_id++; + pr_debug("lm93.o: assigning ID %d to %s at %d,0x%02x\n", client->id, + client->name, i2c_adapter_id(client->adapter), client->addr); + + /* housekeeping */ + data->valid = 0; + data->update = update; + init_MUTEX(&data->update_lock); + + /* tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(client))) + goto ERROR1; + + /* initialize the chip */ + lm93_init_client(client); + + /* register a new directory entry with module sensors */ + if ((data->sysctl_id = i2c_register_entry(client, "lm93", + lm93_dir_table_template, THIS_MODULE)) < 0) { + err = data->sysctl_id; + goto ERROR2; + } + + return 0; + +ERROR2: + i2c_detach_client(client); +ERROR1: + kfree(data); +ERROR0: + return err; +} + +/* This function is called when: + * lm93_driver is inserted (when this module is loaded), for each + available adapter + * when a new adapter is inserted (and lm93_driver is still present) */ +static int lm93_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, lm93_detect); +} + +static int lm93_detach_client(struct i2c_client *client) +{ + int err; + struct lm93_data *data = client->data; + + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk (KERN_ERR "lm93.o: Client deregistration failed; " + "client not detached.\n"); + return err; + } + kfree(data); + return 0; +} + +static struct i2c_driver lm93_driver = { + .name = "LM93 sensor driver", + .id = I2C_DRIVERID_LM93, + .flags = I2C_DF_NOTIFY, + .attach_adapter = lm93_attach_adapter, + .detach_client = lm93_detach_client, +}; + +static int __init lm93_init(void) +{ + printk(KERN_INFO "lm93.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&lm93_driver); +} + +static void __exit lm93_exit(void) +{ + i2c_del_driver(&lm93_driver); +} + +MODULE_AUTHOR("Mark M. Hoffman "); +MODULE_DESCRIPTION("LM93 driver"); +MODULE_LICENSE("GPL"); + +module_init(lm93_init); +module_exit(lm93_exit); --- linux-old/drivers/sensors/lm93.h Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/lm93.h Sun Feb 26 11:18:40 2006 @@ -0,0 +1,113 @@ +/* + lm93.h - Part of lm_sensors, Linux kernel modules for hardware monitoring + + Author/Maintainer: Mark M. Hoffman + Copyright (c) 2004 Utilitek Systems, Inc. + + 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. +*/ + +#ifndef LM93_H +#define LM93_H + +/* LM93 REGISTER ADDRESSES */ + +/* miscellaneous */ +#define LM93_REG_MFR_ID 0x3e +#define LM93_REG_VER 0x3f +#define LM93_REG_STATUS_CONTROL 0xe2 +#define LM93_REG_CONFIG 0xe3 +#define LM93_REG_SLEEP_CONTROL 0xe4 + +/* alarm values start here */ +#define LM93_REG_HOST_ERROR_1 0x48 + +/* voltage inputs: in1-in16 (nr => 0-15) */ +#define LM93_REG_IN(nr) (0x56 + (nr)) +#define LM93_REG_IN_MIN(nr) (0x90 + (nr) * 2) +#define LM93_REG_IN_MAX(nr) (0x91 + (nr) * 2) + +/* temperature inputs: temp1-temp4 (nr => 0-3) */ +#define LM93_REG_TEMP(nr) (0x50 + (nr)) +#define LM93_REG_TEMP_MIN(nr) (0x78 + (nr) * 2) +#define LM93_REG_TEMP_MAX(nr) (0x79 + (nr) * 2) + +/* temp[1-4]_auto_boost (nr => 0-3) */ +#define LM93_REG_BOOST(nr) (0x80 + (nr)) + +/* #PROCHOT inputs: prochot1-prochot2 (nr => 0-1) */ +#define LM93_REG_PROCHOT_CUR(nr) (0x67 + (nr) * 2) +#define LM93_REG_PROCHOT_AVG(nr) (0x68 + (nr) * 2) +#define LM93_REG_PROCHOT_MAX(nr) (0xb0 + (nr)) + +/* fan tach inputs: fan1-fan4 (nr => 0-3) */ +#define LM93_REG_FAN(nr) (0x6e + (nr) * 2) +#define LM93_REG_FAN_MIN(nr) (0xb4 + (nr) * 2) + +/* pwm outputs: pwm1-pwm2 (nr => 0-1, reg => 0-3) */ +#define LM93_REG_PWM_CTL(nr,reg) (0xc8 + (reg) + (nr) * 4) +#define LM93_PWM_CTL1 0 +#define LM93_PWM_CTL2 1 +#define LM93_PWM_CTL3 2 +#define LM93_PWM_CTL4 3 + +/* GPIO input state */ +#define LM93_REG_GPI 0x6b + +/* vid inputs: vid1-vid2 (nr => 0-1) */ +#define LM93_REG_VID(nr) (0x6c + (nr)) + +/* vccp1 & vccp2: VID relative inputs (nr => 0-1) */ +#define LM93_REG_VCCP_LIMIT_OFF(nr) (0xb2 + (nr)) + +/* temp[1-4]_auto_boost_hyst */ +#define LM93_REG_BOOST_HYST_12 0xc0 +#define LM93_REG_BOOST_HYST_34 0xc1 +#define LM93_REG_BOOST_HYST(nr) (0xc0 + (nr)/2) + +/* temp[1-4]_auto_pwm_[min|hyst] */ +#define LM93_REG_PWM_MIN_HYST_12 0xc3 +#define LM93_REG_PWM_MIN_HYST_34 0xc4 +#define LM93_REG_PWM_MIN_HYST(nr) (0xc3 + (nr)/2) + +/* prochot_override & prochot_interval */ +#define LM93_REG_PROCHOT_OVERRIDE 0xc6 +#define LM93_REG_PROCHOT_INTERVAL 0xc7 + +/* temp[1-4]_auto_base (nr => 0-3) */ +#define LM93_REG_TEMP_BASE(nr) (0xd0 + (nr)) + +/* temp[1-4]_auto_offsets (step => 0-11) */ +#define LM93_REG_TEMP_OFFSET(step) (0xd4 + (step)) + +/* #PROCHOT & #VRDHOT PWM ramp control */ +#define LM93_REG_PWM_RAMP_CTL 0xbf + +/* miscellaneous */ +#define LM93_REG_SFC1 0xbc +#define LM93_REG_SFC2 0xbd +#define LM93_REG_GPI_VID_CTL 0xbe +#define LM93_REG_SF_TACH_TO_PWM 0xe0 + +/* error masks */ +#define LM93_REG_GPI_ERR_MASK 0xec +#define LM93_REG_MISC_ERR_MASK 0xed + +/* LM93 REGISTER VALUES */ +#define LM93_MFR_ID 0x73 +#define LM93_MFR_ID_PROTOTYPE 0x72 + +#endif /* !defined LM93_H */ + --- linux-old/drivers/sensors/matorb.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/matorb.c Sun Feb 26 11:18:40 2006 @@ -0,0 +1,282 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2E, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(matorb); + +/* Many MATORB constants specified below */ + + +/* Each client has this additional data */ +struct matorb_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + +}; + +static int matorb_attach_adapter(struct i2c_adapter *adapter); +static int matorb_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void matorb_init_client(struct i2c_client *client); +static int matorb_detach_client(struct i2c_client *client); + +static int matorb_write_value(struct i2c_client *client, u8 reg, + u16 value); +static void matorb_disp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void matorb_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver matorb_driver = { + .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_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, matorb_detect); +} + +/* This function is called by i2c_detect */ +int matorb_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, cur; + struct i2c_client *new_client; + struct matorb_data *data; + int err = 0; + const char *type_name = "matorb"; + const char *client_name = "matorb"; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("matorb.o: matorb_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + goto ERROR0; + + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access matorb_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct matorb_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &matorb_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. It is lousy. */ + cur = i2c_smbus_write_byte_data(new_client, 0x0FE, 0x58); /* clear screen */ + + printk("matorb.o: debug detect 0x%X\n", cur); + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + matorb_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + kfree(data); + ERROR0: + return err; +} + +static int matorb_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct matorb_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("matorb.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +#if 0 +/* All registers are word-sized, except for the configuration register. + MATORB uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int matorb_read_value(struct i2c_client *client, u8 reg) +{ + return -1; /* Doesn't support reads */ +} +#endif + +/* All registers are word-sized, except for the configuration register. + MATORB uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int matorb_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if (reg == 0) { + return i2c_smbus_write_byte(client, value); + } else { + return i2c_smbus_write_byte_data(client, reg, value); + } +} + +static void matorb_init_client(struct i2c_client *client) +{ + /* Initialize the MATORB chip */ +} + +static void matorb_update_client(struct i2c_client *client) +{ + struct matorb_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting matorb update\n"); +#endif + +/* nothing yet */ + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void matorb_disp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int i; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + matorb_update_client(client); + results[0] = 0; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + for (i = 1; i <= *nrels_mag; i++) { + matorb_write_value(client, 0, results[i - 1]); + } + } +} + +static int __init sm_matorb_init(void) +{ + printk("matorb.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&matorb_driver); +} + +static void __exit sm_matorb_exit(void) +{ + i2c_del_driver(&matorb_driver); +} + + + +MODULE_AUTHOR + ("Frodo Looijaard and Philip Edelbrock "); +MODULE_DESCRIPTION("MATORB driver"); + +module_init(sm_matorb_init); +module_exit(sm_matorb_exit); --- linux-old/drivers/sensors/max1619.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/max1619.c Sun Feb 26 11:18:40 2006 @@ -0,0 +1,492 @@ +/* + * max1619.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2004 Alexey Fisher + * Jean Delvare + * + * Copied from lm90.c: + * Copyright (C) 2003-2004 Jean Delvare + * + * The MAX1619 is a sensor chip made by Maxim. It reports up to two + * temperatures (its own plus up to one external one). + * Complete datasheet can be obtained from Maxim's website at: + * http://pdfserv.maxim-ic.com/en/ds/MAX1619.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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +/* + * 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_1(max1619); + +/* + * The max1619 registers + */ + +#define MAX1619_REG_R_CONFIG 0x03 +#define MAX1619_REG_W_CONFIG 0x09 +#define MAX1619_REG_R_CONVRATE 0x04 +#define MAX1619_REG_W_CONVRATE 0x0A +#define MAX1619_REG_R_STATUS 0x02 +#define MAX1619_REG_R_LOCAL_TEMP 0x00 +#define MAX1619_REG_R_REMOTE_TEMP 0x01 +#define MAX1619_REG_R_REMOTE_THIGH 0x07 +#define MAX1619_REG_W_REMOTE_THIGH 0x0d +#define MAX1619_REG_R_REMOTE_TLOW 0x08 +#define MAX1619_REG_W_REMOTE_TLOW 0x0E +#define MAX1619_REG_R_REMOTE_TMAX 0x10 +#define MAX1619_REG_W_REMOTE_TMAX 0x12 +#define MAX1619_REG_R_REMOTE_THYST 0x11 +#define MAX1619_REG_W_REMOTE_THYST 0x13 +#define MAX1619_REG_R_MAN_ID 0xFE +#define MAX1619_REG_R_CHIP_ID 0xFF + +/* + * Conversions and various macros + */ + +#define TEMP_FROM_REG(val) ((val) & 0x80 ? (val)-0x100 : (val)) +#define TEMP_TO_REG(val) ((val) < 0 ? (val)+0x100 : (val)) + +/* + * Functions declaration + */ + +static int max1619_attach_adapter(struct i2c_adapter *adapter); +static int max1619_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void max1619_init_client(struct i2c_client *client); +static int max1619_detach_client(struct i2c_client *client); +static void max1619_local_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void max1619_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void max1619_remote_crit(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void max1619_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 max1619_driver = { + .name = "MAX1619 sensor driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = max1619_attach_adapter, + .detach_client = max1619_detach_client +}; + +/* + * Client data (each client gets its own) + */ + +struct max1619_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + u8 local_temp; + u8 remote_temp, remote_high, remote_low; + u8 remote_hyst, remote_max; + u8 alarms; +}; + +/* + * Proc entries + * These files are created for each detected max1619. + */ + +/* -- SENSORS SYSCTL START -- */ + +#define MAX1619_SYSCTL_LOCAL_TEMP 1200 +#define MAX1619_SYSCTL_REMOTE_TEMP 1201 +#define MAX1619_SYSCTL_REMOTE_CRIT 1202 +#define MAX1619_SYSCTL_ALARMS 1203 + +#define MAX1619_ALARM_REMOTE_THIGH 0x10 +#define MAX1619_ALARM_REMOTE_TLOW 0x08 +#define MAX1619_ALARM_REMOTE_OPEN 0x04 +#define MAX1619_ALARM_REMOTE_OVERT 0x02 + +/* -- SENSORS SYSCTL END -- */ + + +static ctl_table max1619_dir_table_template[] = +{ + {MAX1619_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max1619_local_temp}, + {MAX1619_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max1619_remote_temp}, + {MAX1619_SYSCTL_REMOTE_CRIT,"temp2_crit", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max1619_remote_crit}, + {MAX1619_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max1619_alarms}, + {0} +}; + +/* + * Real code + */ + +static int max1619_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, max1619_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int max1619_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *new_client; + struct max1619_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk(KERN_DEBUG "max1619.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(KERN_DEBUG "max1619.o: I2C bus doesn't support " + "byte read mode, skipping.\n"); +#endif + return 0; + } + + if (!(data = kmalloc(sizeof(struct max1619_data), GFP_KERNEL))) { + printk(KERN_ERR "max1619.o: Out of memory in " + "max1619_detect (new_client).\n"); + return -ENOMEM; + } + + /* + * The common I2C client data is placed right before the + * MAX1619-specific data. The MAX1619-specific data is pointed to + * by the data field from the I2C client da1ta. + */ + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &max1619_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) { + u8 reg_config, reg_convrate, reg_status; + + reg_config = i2c_smbus_read_byte_data(new_client, + MAX1619_REG_R_CONFIG); + reg_convrate = i2c_smbus_read_byte_data(new_client, + MAX1619_REG_R_CONVRATE); + reg_status = i2c_smbus_read_byte_data(new_client, + MAX1619_REG_R_STATUS); + + if ((reg_config & 0x03) != 0x00 + || reg_convrate > 0x07 + || (reg_status & 0x61) != 0x00) { +#ifdef DEBUG + printk(KERN_DEBUG "max1619.o: Detection failed at " + "0x%02x.\n", address); +#endif + goto ERROR1; + } + } + + if (kind <= 0) { + u8 man_id, chip_id; + + man_id = i2c_smbus_read_byte_data(new_client, + MAX1619_REG_R_MAN_ID); + chip_id = i2c_smbus_read_byte_data(new_client, + MAX1619_REG_R_CHIP_ID); + + if ((man_id == 0x4D) && (chip_id == 0x04)) { + kind = max1619; + } + } + + if (kind <= 0) { + printk(KERN_INFO "max1619.o: Unsupported chip.\n"); + goto ERROR1; + } + + if (kind == max1619) { + type_name = "max1619"; + client_name = "MAX1619 chip"; + } else { + printk(KERN_ERR "max1619.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); + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* + * Tell the I2C layer a new client has arrived. + */ + + if ((err = i2c_attach_client(new_client))) { + printk(KERN_ERR "max1619.o: Failed attaching client.\n"); + goto ERROR1; + } + + /* + * Register a new directory entry. + */ + + if ((err = i2c_register_entry(new_client, type_name, + max1619_dir_table_template, THIS_MODULE)) < 0) { + printk(KERN_ERR "max1619.o: Failed registering directory " + "entry.\n"); + goto ERROR2; + } + data->sysctl_id = err; + + /* + * Initialize the MAX1619 chip. + */ + + max1619_init_client(new_client); + return 0; + +ERROR2: + i2c_detach_client(new_client); +ERROR1: + kfree(data); + return err; +} + +static void max1619_init_client(struct i2c_client *client) +{ + u8 config; + + /* + * Start the conversions. + */ + + /* Set conversion rate to 2 Hz */ + i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONVRATE, 5); + + /* Start monitoring */ + config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); + if (config & 0x40) + i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONFIG, + config & 0xBF); +} + + +static int max1619_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct max1619_data *) + (client->data))->sysctl_id); + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "max1619.o: Client deregistration failed, " + "client not detached.\n"); + return err; + } + + kfree(client->data); + return 0; +} + +static void max1619_update_client(struct i2c_client *client) +{ + struct max1619_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ * 2) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk(KERN_DEBUG "max1619.o: Updating data.\n"); +#endif + + data->local_temp = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_LOCAL_TEMP); + data->remote_temp = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_REMOTE_TEMP); + data->remote_high = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_REMOTE_THIGH); + data->remote_low = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_REMOTE_TLOW); + data->remote_max = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_REMOTE_TMAX); + data->remote_hyst = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_REMOTE_THYST); + data->alarms = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_STATUS); + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static void max1619_local_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct max1619_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + max1619_update_client(client); + results[0] = TEMP_FROM_REG(data->local_temp); + *nrels_mag = 1; + } + +} + +static void max1619_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct max1619_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + max1619_update_client(client); + results[0] = TEMP_FROM_REG(data->remote_high); + results[1] = TEMP_FROM_REG(data->remote_low); + results[2] = TEMP_FROM_REG(data->remote_temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->remote_high = TEMP_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, + MAX1619_REG_W_REMOTE_THIGH, data->remote_high); + } + if (*nrels_mag >= 2) { + data->remote_low = TEMP_TO_REG(results[1]); + i2c_smbus_write_byte_data(client, + MAX1619_REG_W_REMOTE_TLOW, data->remote_low); + + } + } +} + +static void max1619_remote_crit(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct max1619_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + max1619_update_client(client); + results[0] = TEMP_FROM_REG(data->remote_max); + results[1] = TEMP_FROM_REG(data->remote_hyst); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->remote_max = TEMP_TO_REG(results[0]); + i2c_smbus_write_byte_data(client, + MAX1619_REG_W_REMOTE_TMAX, data->remote_max); + } + if (*nrels_mag >= 2) { + data->remote_hyst = TEMP_TO_REG(results[1]); + i2c_smbus_write_byte_data(client, + MAX1619_REG_W_REMOTE_THYST, data->remote_hyst); + } + } +} + +static void max1619_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct max1619_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; /* magnitude */ + else if (operation == SENSORS_PROC_REAL_READ) { + max1619_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + + +static int __init sm_max1619_init(void) +{ + printk(KERN_INFO "max1619.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&max1619_driver); +} + +static void __exit sm_max1619_exit(void) +{ + i2c_del_driver(&max1619_driver); +} + +MODULE_AUTHOR("Alexey Fisher "); +MODULE_DESCRIPTION("MAX1619 sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_max1619_init); +module_exit(sm_max1619_exit); --- linux-old/drivers/sensors/max6650.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/max6650.c Sun Feb 26 11:18:41 2006 @@ -0,0 +1,538 @@ +/* + * 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 "20060214" +#define LM_VERSION "2.10.0" + +#ifndef I2C_DRIVERID_MAX6650 +#define I2C_DRIVERID_MAX6650 1044 +#endif + +/* + * Addresses to scan. There are four disjoint possibilities, by pin config. + */ + +static unsigned short normal_i2c[] = {0x1b, 0x1f, 0x48, 0x4b, SENSORS_I2C_END}; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* + * Insmod parameters + */ + +SENSORS_INSMOD_1(max6650); + +/* + * MAX 6650/6651 registers + */ + +#define MAX6650_REG_SPEED 0x00 +#define MAX6650_REG_CONFIG 0x02 +#define MAX6650_REG_GPIO_DEF 0x04 +#define MAX6650_REG_DAC 0x06 +#define MAX6650_REG_ALARM_EN 0x08 +#define MAX6650_REG_ALARM 0x0A +#define MAX6650_REG_TACH0 0x0C +#define MAX6650_REG_TACH1 0x0E +#define MAX6650_REG_TACH2 0x10 +#define MAX6650_REG_TACH3 0x12 +#define MAX6650_REG_GPIO_STAT 0x14 +#define MAX6650_REG_COUNT 0x16 + +/* + * Config register bits + */ + +#define MAX6650_CFG_MODE_MASK 0x30 +#define MAX6650_CFG_MODE_ON 0x00 +#define MAX6650_CFG_MODE_OFF 0x10 +#define MAX6650_CFG_MODE_CLOSED_LOOP 0x20 +#define MAX6650_CFG_MODE_OPEN_LOOP 0x30 + +static const u8 tach_reg[] = +{ + MAX6650_REG_TACH0, MAX6650_REG_TACH1, + MAX6650_REG_TACH2, MAX6650_REG_TACH3 +}; + +#define MAX6650_INT_CLK 254000 /* Default clock speed - 254 kHz */ + +/* + * Functions declaration + */ + +static void max6650_fan (struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results); +static void max6650_speed (struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results); +static void max6650_xdump (struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results); +static int max6650_detect(struct i2c_adapter *adapter, int address, unsigned + short flags, int kind); +static int max6650_attach_adapter(struct i2c_adapter *adapter); +static int max6650_detach_client(struct i2c_client *client); +static void max6650_init_client(struct i2c_client *client); +static int max6650_read(struct i2c_client *client, u8 reg); + +/* + * Driver data (common to all clients) + */ + + +static struct i2c_driver max6650_driver = { + .name = "MAX6650/1 sensor driver", + .id = I2C_DRIVERID_MAX6650, + .flags = I2C_DF_NOTIFY, + .attach_adapter = max6650_attach_adapter, + .detach_client = max6650_detach_client +}; + +/* + * Client data (each client gets its own) + */ + +struct max6650_data +{ + struct i2c_client client; + int sysctl_id; + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + + u8 speed; + u8 config; + u8 tach[4]; + u8 count; +}; + +/* + * Proc entries + * These files are created for each detected max6650. + */ + +/* -- SENSORS SYSCTL START -- */ + +#define MAX6650_SYSCTL_FAN1 1101 +#define MAX6650_SYSCTL_FAN2 1102 +#define MAX6650_SYSCTL_FAN3 1103 +#define MAX6650_SYSCTL_FAN4 1104 +#define MAX6650_SYSCTL_SPEED 1105 +#define MAX6650_SYSCTL_XDUMP 1106 + + +/* -- SENSORS SYSCTL END -- */ + + +static ctl_table max6650_dir_table_template[] = +{ + {MAX6650_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, + {MAX6650_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, + {MAX6650_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, + {MAX6650_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan}, + {MAX6650_SYSCTL_SPEED, "speed", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_speed}, + {MAX6650_SYSCTL_XDUMP, "xdump", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_xdump}, + {0} +}; + +/* + * Real code + */ + +static int max6650_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, max6650_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ + +static int max6650_detect(struct i2c_adapter *adapter, int address, unsigned + short flags, int kind) +{ + struct i2c_client *new_client; + struct max6650_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk("max6650.o: Called for an ISA bus adapter, aborting.\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { +#ifdef DEBUG + printk("max6650.o: I2C bus doesn't support byte read mode, skipping.\n"); +#endif + return 0; + } + + if (!(data = kmalloc(sizeof(struct max6650_data), GFP_KERNEL))) { + printk("max6650.o: Out of memory in max6650_detect (new_client).\n"); + return -ENOMEM; + } + + /* + * The common I2C client data is placed right before the + * max6650-specific data. The max6650-specific data is pointed to by the + * data field from the I2C client data. + */ + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &max6650_driver; + new_client->flags = 0; + + /* + * Now we do the remaining detection. A negative kind means that + * the driver was loaded with no force parameter (default), so we + * must both detect and identify the chip (actually there is only + * one possible kind of chip for now, max6650). A zero kind means that + * the driver was loaded with the force parameter, the detection + * step shall be skipped. A positive kind means that the driver + * was loaded with the force parameter and a given kind of chip is + * requested, so both the detection and the identification steps + * are skipped. + * + * Currently I can find no way to distinguish between a MAX6650 and + * a MAX6651. This driver has only been tried on the latter. + */ + + if (kind < 0) { /* detection */ + if ( + (max6650_read(new_client, MAX6650_REG_CONFIG) & 0xC0) || + (max6650_read(new_client, MAX6650_REG_GPIO_STAT) & 0xE0) || + (max6650_read(new_client, MAX6650_REG_ALARM_EN) & 0xE0) || + (max6650_read(new_client, MAX6650_REG_ALARM) & 0xE0) || + (max6650_read(new_client, MAX6650_REG_COUNT) & 0xFC) + ) + { +#ifdef DEBUG + printk("max6650.o: max6650 detection failed at 0x%02x.\n", + address); +#endif + goto ERROR1; + } + } + + if (kind <= 0) { /* identification */ + kind = max6650; + } + + if (kind <= 0) { /* identification failed */ + printk("max6650.o: Unsupported chip.\n"); + goto ERROR1; + } + + if (kind == max6650) { + type_name = "max6650"; + client_name = "max6650 chip"; + } else { + printk("max6650.o: Unknown kind %d.\n", kind); + goto ERROR1; + } + + /* + * OK, we got a valid chip so we can fill in the remaining client + * fields. + */ + + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { +#ifdef DEBUG + printk("max6650.o: Failed registering directory entry.\n"); +#endif + goto ERROR2; + } + data->sysctl_id = err; + + /* + * Initialize the max6650 chip + */ + max6650_init_client(new_client); + return 0; + +ERROR2: + i2c_detach_client(new_client); +ERROR1: + kfree(data); + return err; +} + +static void max6650_init_client(struct i2c_client *client) +{ + /* Nothing to do here - assume the BIOS has initialized the chip */ +} + +static int max6650_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct max6650_data *) (client->data))->sysctl_id); + if ((err = i2c_detach_client(client))) { + printk("max6650.o: Client deregistration failed, " + "client not detached.\n"); + return err; + } + + kfree(client->data); + return 0; +} + +static int max6650_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int max6650_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void max6650_update_client(struct i2c_client *client) +{ + int i; + struct max6650_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk("max6650.o: Updating max6650 data.\n"); +#endif + data->speed = max6650_read (client, MAX6650_REG_SPEED); + data->config = max6650_read (client, MAX6650_REG_CONFIG); + for (i = 0; i < 4; i++) { + data->tach[i] = max6650_read(client, tach_reg[i]); + } + data->count = max6650_read (client, MAX6650_REG_COUNT); + data->last_updated = jiffies; + data->valid = 1; + } + up(&data->update_lock); +} + +static void max6650_fan (struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results) +{ + int index = ctl_name - MAX6650_SYSCTL_FAN1; + struct max6650_data *data = client->data; + int tcount; /* Tachometer count time, 0.25 second units */ + + if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 0; + } else if (operation == SENSORS_PROC_REAL_READ) { + max6650_update_client(client); + + /* + * Calculation details: + * + * Each tachometer counts over an interval given by the "count" + * register (0.25, 0.5, 1 or 2 seconds). This module assumes + * that the fans produce two pulses per revolution (this seems + * to be the most common). + */ + + tcount = 1 << data->count; /* 0.25 second units */ + results[0] = (data->tach[index] * 240) / tcount; /* counts per min */ + results[0] /= 2; /* Assume two counts per rev */ + *nrels_mag = 1; + } +} + +/* + * Set the fan speed to the specified RPM (or read back the RPM setting). + * + * The MAX6650/1 will automatically control fan speed when in closed loop + * mode. + * + * Assumptions: + * + * 1) The MAX6650/1 is running from its internal 254kHz clock (perhaps + * this should be made a module parameter). + * + * 2) The prescaler (low three bits of the config register) has already + * been set to an appropriate value. + * + * The relevant equations are given on pages 21 and 22 of the datasheet. + * + * From the datasheet, the relevant equation when in regulation is: + * + * [fCLK / (128 x (KTACH + 1))] = 2 x FanSpeed / KSCALE + * + * where: + * + * fCLK is the oscillator frequency (either the 254kHz internal + * oscillator or the externally applied clock) + * + * KTACH is the value in the speed register + * + * FanSpeed is the speed of the fan in rps + * + * KSCALE is the prescaler value (1, 2, 4, 8, or 16) + * + * When reading, we need to solve for FanSpeed. When writing, we need to + * solve for KTACH. + * + * Note: this tachometer is completely separate from the tachometers + * used to measure the fan speeds. Only one fan's speed (fan1) is + * controlled. + */ + +static void max6650_speed (struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results) +{ + struct max6650_data *data = client->data; + int kscale, ktach, fclk, rpm; + + if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 0; + } else if (operation == SENSORS_PROC_REAL_READ) { + /* + * Use the datasheet equation: + * + * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)] + * + * then multiply by 60 to give rpm. + */ + + max6650_update_client(client); + + kscale = 1 << (data->config & 7); + ktach = data->speed; + fclk = MAX6650_INT_CLK; + rpm = 60 * kscale * fclk / (256 * (ktach + 1)); + + results[0] = rpm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE && *nrels_mag >= 1) { + /* + * Divide the required speed by 60 to get from rpm to rps, then + * use the datasheet equation: + * + * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1 + */ + + max6650_update_client(client); + + rpm = results[0]; + kscale = 1 << (data->config & 7); + fclk = MAX6650_INT_CLK; + ktach = ((fclk * kscale) / (256 * rpm / 60)) - 1; + + data->speed = ktach; + data->config = (data->config & ~MAX6650_CFG_MODE_MASK) | + MAX6650_CFG_MODE_CLOSED_LOOP; + max6650_write (client, MAX6650_REG_CONFIG, data->config); + max6650_write (client, MAX6650_REG_SPEED, data->speed); + } +} + +/* + * Debug - dump all registers except the tach counts. + */ + +static void max6650_xdump (struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results) +{ + if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 0; + } else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = max6650_read (client, MAX6650_REG_SPEED); + results[1] = max6650_read (client, MAX6650_REG_CONFIG); + results[2] = max6650_read (client, MAX6650_REG_GPIO_DEF); + results[3] = max6650_read (client, MAX6650_REG_DAC); + results[4] = max6650_read (client, MAX6650_REG_ALARM_EN); + results[5] = max6650_read (client, MAX6650_REG_ALARM); + results[6] = max6650_read (client, MAX6650_REG_GPIO_STAT); + results[7] = max6650_read (client, MAX6650_REG_COUNT); + *nrels_mag = 8; + } +} + +static int __init sm_max6650_init(void) +{ + printk(KERN_INFO "max6650.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&max6650_driver); +} + +static void __exit sm_max6650_exit(void) +{ + i2c_del_driver(&max6650_driver); +} + +MODULE_AUTHOR("john.morris@spirentcom.com"); +MODULE_DESCRIPTION("max6650 sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_max6650_init); +module_exit(sm_max6650_exit); --- linux-old/drivers/sensors/maxilife.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/maxilife.c Sun Feb 26 11:18:41 2006 @@ -0,0 +1,1381 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +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 i2c_client client; + 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 = { + .name = "HP MaxiLife driver", + .id = I2C_DRIVERID_MAXILIFE, + .flags = I2C_DF_NOTIFY, + .attach_adapter = maxi_attach_adapter, + .detach_client = maxi_detach_client, +}; + +/* 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 Celsius */ +#define MAXI_SYSCTL_TEMP2 1202 /* Degrees Celsius */ +#define MAXI_SYSCTL_TEMP3 1203 /* Degrees Celsius */ +#define MAXI_SYSCTL_TEMP4 1204 /* Degrees Celsius */ +#define MAXI_SYSCTL_TEMP5 1205 /* Degrees Celsius */ +#define MAXI_SYSCTL_TEMP6 1206 /* Degrees Celsius */ +#define MAXI_SYSCTL_PLL 1301 /* MHz */ +#define MAXI_SYSCTL_VID1 1401 /* Volts / 6.337, for nba just Volts */ +#define MAXI_SYSCTL_VID2 1402 /* Volts */ +#define MAXI_SYSCTL_VID3 1403 /* Volts */ +#define MAXI_SYSCTL_VID4 1404 /* Volts */ +#define MAXI_SYSCTL_VID5 1405 /* Volts */ +#define MAXI_SYSCTL_LCD1 1501 /* Line 1 of LCD */ +#define MAXI_SYSCTL_LCD2 1502 /* Line 2 of LCD */ +#define MAXI_SYSCTL_LCD3 1503 /* Line 3 of LCD */ +#define MAXI_SYSCTL_LCD4 1504 /* Line 4 of LCD */ +#define MAXI_SYSCTL_ALARMS 2001 /* Bitvector (see below) */ + +#define MAXI_ALARM_VID4 0x0001 +#define MAXI_ALARM_TEMP2 0x0002 +#define MAXI_ALARM_VID1 0x0004 +#define MAXI_ALARM_VID2 0x0008 +#define MAXI_ALARM_VID3 0x0010 +#define MAXI_ALARM_PLL 0x0080 +#define MAXI_ALARM_TEMP4 0x0100 +#define MAXI_ALARM_TEMP5 0x0200 +#define MAXI_ALARM_FAN1 0x1000 +#define MAXI_ALARM_FAN2 0x2000 +#define MAXI_ALARM_FAN3 0x4000 + +#define MAXI_ALARM_FAN 0x0100 /* To be used with MaxiLife'99 */ +#define MAXI_ALARM_VID 0x0200 /* The MSB specifies which sensor */ +#define MAXI_ALARM_TEMP 0x0400 /* in the alarm group failed, i.e.: */ +#define MAXI_ALARM_VADD 0x0800 /* 0x0402 = TEMP2 failed = CPU2 temp */ + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected MaxiLife processor. + This is just a template; though at first sight, you might think we + could use a statically allocated list, we need some way to get back + to the parent - which is done through one of the 'extra' fields + which are initialized when a new copy is allocated. */ +static ctl_table maxi_dir_table_template[] = { + {MAXI_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_fan}, + {MAXI_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_fan}, + {MAXI_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_fan}, + {MAXI_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_fan}, + {MAXI_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_temp}, + {MAXI_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_temp}, + {MAXI_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_temp}, + {MAXI_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_temp}, + {MAXI_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_temp}, + {MAXI_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_temp}, + {MAXI_SYSCTL_PLL, "pll", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_pll}, + {MAXI_SYSCTL_VID1, "vid1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_vid}, + {MAXI_SYSCTL_VID2, "vid2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_vid}, + {MAXI_SYSCTL_VID3, "vid3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_vid}, + {MAXI_SYSCTL_VID4, "vid4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_vid}, + {MAXI_SYSCTL_VID5, "vid5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_vid}, + {MAXI_SYSCTL_LCD1, "lcd1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_lcd}, + {MAXI_SYSCTL_LCD2, "lcd2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_lcd}, + {MAXI_SYSCTL_LCD3, "lcd3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_lcd}, + {MAXI_SYSCTL_LCD4, "lcd4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_lcd}, + {MAXI_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &maxi_alarms}, + {0} +}; + +/* This function is called when: + - maxi_driver is inserted (when this module is loaded), for each + available adapter + - when a new adapter is inserted (and maxi_driver is still present) */ +static int maxi_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, maxi_detect); +} + +/* This function is called by i2c_detect */ +int maxi_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *new_client; + struct maxi_data *data; + enum maxi_type type = 0; + int i, j, err = 0; + const char *type_name = NULL, *client_name = NULL; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access maxi_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct maxi_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + /* Fill the new client structure with data */ + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &maxi_driver; + new_client->flags = 0; + + /* Now we do the remaining detection. */ + if (kind < 0) { + if (i2c_smbus_read_byte_data + (new_client, MAXI_REG_MBX_STATUS) < 0) + goto ERROR2; + } + + /* Determine the chip type - only one kind supported */ + if (kind <= 0) + kind = maxilife; + + if (kind == maxilife) { + /* Detect if the machine has a MaxiLife NBA controller. + The right way to perform this check is to do a read/modify/write + on register MbxStatus (5A): + - Read 5A (value 0 for non-NBA firmware, FF (MbxIdle on NBA-firmware) + - Write 55 on 5A, then read back 5A + Non-NBA firmware: value is 55 (reg 5A is a standard writable reg) + NBA firmaware: value is FF (write-protect on MbxStatus active) */ + int stat; + i2c_smbus_write_byte_data(new_client, MAXI_REG_MBX_STATUS, + 0x55); + stat = + i2c_smbus_read_byte_data(new_client, + MAXI_REG_MBX_STATUS); + + /*if (stat == MAXI_STAT_IDLE || stat == MAXI_STAT_OK) */ + if (stat != 0x55) + maxi_version = nba; +#ifdef AUTODETECT + else { + /* The right way to get the platform info is to read the firmware + revision from serial EEPROM (addr=0x54), at offset 0x0045. + This is a string as: + "CG 00.04" -> Cristal [XU] / Geronimo [XAs] + "CO 00.03" -> Cognac [XU] + "AS 00.01" -> Ashaki [XA] */ +#if 0 + int biosctl; + biosctl = + i2c_smbus_read_byte_data(new_client, + MAXI_REG_BIOS_CTRL); + i2c_smbus_write_byte_data(new_client, + MAXI_REG_BIOS_CTRL, + biosctl | 4); + err = eeprom_read_byte_data(adapter, 0x54, 0x45); + i2c_smbus_write_byte_data(new_client, + MAXI_REG_BIOS_CTRL, + biosctl); +#endif + int i; + char *biosmem, *bm; + bm = biosmem = ioremap(0xe0000, 0x20000); + if (biosmem) { + printk("begin of bios search\n"); + for (i = 0; i < 0x20000; i++) { + if (*bm == 'C') { + char *s = bm; + while (s && isprint(*s)) { + printk("%c", *s); + s++; + } + printk("\n"); + if (!strncmp + (bm, "CG 00.04", 8)) { + maxi_version = + cristal; + printk + ("maxilife: found MaxiLife Rev CG 00.04\n"); + break; + } + if (!strncmp + (bm, "CO 00.03", 8)) { + maxi_version = + cognac; + printk + ("maxilife: found MaxiLife Rev CO 00.03\n"); + break; + } + } + if (*bm == 'A' && *(bm + 1) == 'S') { + char *s = bm; + while (s && isprint(*s)) { + printk("%c", *s); + s++; + } + printk("\n"); + if (!strncmp + (bm, "AS 00.01", 8)) { + maxi_version = + ashaki; + printk + ("maxilife: found MaxiLife Rev AS 00.01\n"); + break; + } + } + bm++; + } + printk("end of bios search\n"); + } else + printk("could not map bios memory\n"); + } +#endif + + if (maxi_version == cristal) { + type = cristal; + type_name = "maxilife-cg"; + client_name = "HP MaxiLife Rev CG 00.04"; + printk + ("maxilife: HP KAYAK XU/XAs (Dual Pentium II Slot 1)\n"); + } else if (maxi_version == cognac) { + type = cognac; + type_name = "maxilife-co"; + client_name = "HP MaxiLife Rev CO 00.03"; + printk + ("maxilife: HP KAYAK XU (Dual Xeon Slot 2 400/450 Mhz)\n"); + } else if (maxi_version == ashaki) { + type = ashaki; + type_name = "maxilife-as"; + client_name = "HP MaxiLife Rev AS 00.01"; + printk + ("maxilife: HP KAYAK XA (Pentium II Slot 1, monoprocessor)\n"); + } else if (maxi_version == nba) { + type = nba; + type_name = "maxilife-nba"; + client_name = "HP MaxiLife NBA"; + printk("maxilife: HP KAYAK XU800/XM600\n"); + } else { +#ifdef AUTODETECT + printk + ("maxilife: Warning: probed non-maxilife chip?!? (%x)\n", + err); +#else + printk + ("maxilife: Error: specified wrong maxi_version (%d)\n", + maxi_version); +#endif + goto ERROR2; + } + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + ((struct maxi_data *) (new_client->data))->type = type; + + for (i = 0; i < 4; i++) + for (j = 0; j < 17; j++) + ((struct maxi_data *) (new_client->data))-> + lcd[i][j] = (u8) 0; + + data->valid = 0; + 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, + THIS_MODULE)) < 0) + goto ERROR4; + data->sysctl_id = err; + + /* Initialize the MaxiLife chip */ + maxi_init_client(new_client); + return 0; + + /* OK, this is not exactly good programming practice, usually. + But it is very code-efficient in this case. */ + ERROR4: + i2c_detach_client(new_client); + ERROR2: + kfree(data); + ERROR0: + return err; +} + +/* This function is called whenever a client should be removed: + - maxi_driver is removed (when this module is unloaded) + - when an adapter is removed which has a maxi client (and maxi_driver + is still present). */ +static int maxi_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct maxi_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("maxilife: Client deregistration failed, client not detached.\n"); + return err; + } + kfree(client->data); + return 0; +} + +/* Read byte from specified register (-1 in case of error, value otherwise). */ +static int maxi_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* Read the byte value for a MaxiLife token (-1 in case of error, value otherwise */ +static int maxi_read_token(struct i2c_client *client, u16 token) +{ + u8 lowToken, highToken; + int error, value; + + lowToken = LOW(token); + highToken = HIGH(token); + + /* Set mailbox status register to idle state. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, + MAXI_STAT_IDLE); + if (error < 0) + return error; + + /* Check for mailbox idle state. */ + error = i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); + if (error != MAXI_STAT_IDLE) + return -1; + + /* Write the most significant byte of the token we want to read. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_H, + highToken); + if (error < 0) + return error; + + /* Write the least significant byte of the token we want to read. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_L, + lowToken); + if (error < 0) + return error; + + /* Write the read token opcode to the mailbox. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_CMD, + MAXI_CMD_READ); + if (error < 0) + return error; + + /* Check for transaction completion */ + do { + error = + i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); + } while (error == MAXI_STAT_BUSY); + if (error != MAXI_STAT_OK) + return -1; + + /* Read the value of the token. */ + value = i2c_smbus_read_byte_data(client, MAXI_REG_MBX_DATA); + if (value == -1) + return -1; + + /* set mailbox status to idle to complete transaction. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, + MAXI_STAT_IDLE); + if (error < 0) + return error; + + return value; +} + +#ifndef NOWRITE +/* Write byte to specified register (-1 in case of error, 0 otherwise). */ +static int maxi_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} +#endif + +/* Write a set of len byte values to MaxiLife token (-1 in case of error, 0 otherwise). */ +int maxi_write_token_loop(struct i2c_client *client, u16 token, u8 len, + u8 * values) +{ + u8 lowToken, highToken, bCounter; + int error; + + lowToken = LOW(token); + highToken = HIGH(token); + + /* Set mailbox status register to idle state. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, + MAXI_STAT_IDLE); + if (error < 0) + return error; + + /* Check for mailbox idle state. */ + error = i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); + if (error != MAXI_STAT_IDLE) + return -1; + + for (bCounter = 0; (bCounter < len && bCounter < 32); bCounter++) { + error = + i2c_smbus_write_byte_data(client, + (u8) (MAXI_REG_MBX_DATA + + bCounter), + values[bCounter]); + if (error < 0) + return error; + } + + /* Write the most significant byte of the token we want to read. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_H, + highToken); + if (error < 0) + return error; + + /* Write the least significant byte of the token we want to read. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_TOKEN_L, + lowToken); + if (error < 0) + return error; + + /* Write the write token opcode to the mailbox. */ + error = + i2c_smbus_write_byte_data(client, MAXI_REG_MBX_CMD, + MAXI_CMD_WRITE); + if (error < 0) + return error; + + /* Check for transaction completion */ + do { + error = + i2c_smbus_read_byte_data(client, MAXI_REG_MBX_STATUS); + } while (error == MAXI_STAT_BUSY); + if (error != MAXI_STAT_OK) + return -1; + + /* set mailbox status to idle to complete transaction. */ + return i2c_smbus_write_byte_data(client, MAXI_REG_MBX_STATUS, + MAXI_STAT_IDLE); +} + +/* Called when we have found a new MaxiLife. */ +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 Sun Feb 26 11:18:41 2006 @@ -0,0 +1,1099 @@ +/* + mtp008.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (C) 2001, 2004 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {SENSORS_I2C_END}; +static unsigned short normal_i2c_range[] = {0x2c, 0x2e, SENSORS_I2C_END}; +static unsigned int normal_isa[] = {SENSORS_ISA_END}; +static unsigned int normal_isa_range[] = {SENSORS_ISA_END}; + +/* Insmod parameters */ +SENSORS_INSMOD_1(mtp008); + +/* The MTP008 registers */ +/* in0 .. in6 */ +#define MTP008_REG_IN(nr) (0x20 + (nr)) +#define MTP008_REG_IN_MAX(nr) (0x2b + (nr) * 2) +#define MTP008_REG_IN_MIN(nr) (0x2c + (nr) * 2) + +/* temp1 */ +#define MTP008_REG_TEMP 0x27 +#define MTP008_REG_TEMP_MAX 0x39 +#define MTP008_REG_TEMP_MIN 0x3a + +/* fan1 .. fan3 */ +#define MTP008_REG_FAN(nr) (0x27 + (nr)) +#define MTP008_REG_FAN_MIN(nr) (0x3a + (nr)) + +#define MTP008_REG_CONFIG 0x40 +#define MTP008_REG_INT_STAT1 0x41 +#define MTP008_REG_INT_STAT2 0x42 + +#define MTP008_REG_SMI_MASK1 0x43 +#define MTP008_REG_SMI_MASK2 0x44 + +#define MTP008_REG_NMI_MASK1 0x45 +#define MTP008_REG_NMI_MASK2 0x46 + +#define MTP008_REG_VID_FANDIV 0x47 + +#define MTP008_REG_I2C_ADDR 0x48 + +#define MTP008_REG_RESET_VID4 0x49 + +#define MTP008_REG_OVT_PROP 0x50 + +#define MTP008_REG_BEEP_CTRL1 0x51 +#define MTP008_REG_BEEP_CTRL2 0x52 + +/* pwm1 .. pwm3 nr range 1-3 */ +#define MTP008_REG_PWM_CTRL(nr) (0x52 + (nr)) + +#define MTP008_REG_PIN_CTRL1 0x56 +#define MTP008_REG_PIN_CTRL2 0x57 + +#define MTP008_REG_CHIPID 0x58 + +/* + * Pin control register configuration constants. + */ +#define MTP008_CFG_VT1_PII 0x08 +#define MTP008_CFG_VT2_AIN 0x00 +#define MTP008_CFG_VT2_VT 0x03 +#define MTP008_CFG_VT2_PII 0x04 +#define MTP008_CFG_VT2_MASK 0x06 +#define MTP008_CFG_VT3_VT 0x01 + +/* sensor pin types */ +#define VOLTAGE 1 +#define THERMISTOR 2 +#define PIIDIODE 3 + +/* + * Conversion routines and macros. Limit checking is only done on + * the TO_REG variants. + * + * Note that IN values are expressed as 100 times the actual voltage to avoid + * having to use floating point values. As such, IN values are between 0 and + * 409 (0V to 4.096V). + */ +#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8) / 16), 0, 255)) +#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) + +/* + * The fan cotation count (as stored in the register) is calculated using the + * following formula: + * count = (22.5K * 60) / (rpm * div) = 1350000 / (rpm * div) + * and the rpm is therefore: + * rpm = 1350000 / (count * div) + */ +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + + return SENSORS_LIMIT( + (1350000 + rpm * div / 2) / (rpm * div), + 1, 254 + ); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 \ + : (val) == 255 ? 0 \ + : 1350000 / \ + ((val) * (div)) \ + ) + +/* + * Temperatures are stored as two's complement values of the Celsius value. It + * actually uses 10 times the Celsius value to avoid using floating point + * values. + */ +#define TEMP_TO_REG(val) ( \ + (val) < 0 \ + ? SENSORS_LIMIT(((val) - 5) / 10, 0, 255) \ + : SENSORS_LIMIT(((val) + 5) / 10, 0, 255) \ + ) +#define TEMP_FROM_REG(val) ( \ + ( \ + (val) > 0x80 ? (val) - 0x100 \ + : (val) \ + ) * 10 \ + ) + +/* + * VCORE voltage: + * 0x00 to 0x0f = 2.05 to 1.30 (0.05 per unit) + * 0x10 to 0x1e = 3.50 to 2.10 (0.10 per unit) + * 0x1f = No CPU + */ +#define VID_FROM_REG(val) ((val) == 0x1f \ + ? 0 \ + : (val) < 0x10 ? 205 - (val) * 5 \ + : 510 - (val) * 10) + +/* + * Fan divider. + */ +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val) == 8 ? 3 \ + : (val) == 4 ? 2 \ + : (val) == 2 ? 1 \ + : 0) + +/* + * Alarms (interrupt status). + */ +#define ALARMS_FROM_REG(val) (val) + +/* + * Beep controls. + */ +#define BEEPS_FROM_REG(val) (val) +#define BEEPS_TO_REG(val) (val) + +/* + * PWM control. nr range 1 to 3 + */ +#define PWM_FROM_REG(val) (val) +#define PWM_TO_REG(val) (val) +#define PWMENABLE_FROM_REG(nr, val) (((val) >> ((nr) + 3)) & 1) + +/* + * For each registered MTP008, we need to keep some data in memory. The + * structure itself is dynamically allocated, at the same time when a new + * mtp008 client is allocated. + */ +struct mtp008_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u8 temp; /* Register value */ + u8 temp_max; /* Register value */ + u8 temp_min; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u8 vid; /* Register encoding */ + u8 fan_div[3]; /* Register encoding */ + u16 alarms; /* Register encoding */ + u16 beeps; /* Register encoding */ + u8 pwm[4]; /* Register value */ + u8 sens[3]; /* 1 = Analog input, + 2 = Thermistor, + 3 = PII/Celeron diode */ + u8 pwmenable; /* Register 0x57 value */ +}; + +static int mtp008_attach_adapter(struct i2c_adapter *adapter); +static int mtp008_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int mtp008_detach_client(struct i2c_client *client); + +static int mtp008_read_value(struct i2c_client *client, u8 register); +static int mtp008_write_value(struct i2c_client *client, u8 register, u8 value); +static void mtp008_update_client(struct i2c_client *client); +static void mtp008_init_client(struct i2c_client *client); + +static void mtp008_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_beep(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_sens(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void mtp008_getsensortype(struct mtp008_data *data, u8 inp); + +static struct i2c_driver mtp008_driver = +{ + .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 Celsius * 10 */ +#define MTP008_SYSCTL_TEMP2 1201 /* Degrees Celsius * 10 */ +#define MTP008_SYSCTL_TEMP3 1202 /* Degrees Celsius * 10 */ +#define MTP008_SYSCTL_VID 1300 /* Volts * 100 */ +#define MTP008_SYSCTL_PWM1 1401 +#define MTP008_SYSCTL_PWM2 1402 +#define MTP008_SYSCTL_PWM3 1403 +#define MTP008_SYSCTL_SENS1 1501 /* 1, 2, or Beta (3000-5000) */ +#define MTP008_SYSCTL_SENS2 1502 +#define MTP008_SYSCTL_SENS3 1503 +#define MTP008_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define MTP008_SYSCTL_ALARMS 2001 /* bitvector */ +#define MTP008_SYSCTL_BEEP 2002 /* bitvector */ + +#define MTP008_ALARM_IN0 0x0001 +#define MTP008_ALARM_IN1 0x0002 +#define MTP008_ALARM_IN2 0x0004 +#define MTP008_ALARM_IN3 0x0008 +#define MTP008_ALARM_IN4 0x0100 +#define MTP008_ALARM_IN5 0x0200 +#define MTP008_ALARM_IN6 0x0400 +#define MTP008_ALARM_FAN1 0x0040 +#define MTP008_ALARM_FAN2 0x0080 +#define MTP008_ALARM_FAN3 0x0800 +#define MTP008_ALARM_TEMP1 0x0010 +#define MTP008_ALARM_TEMP2 0x0100 +#define MTP008_ALARM_TEMP3 0x0200 + +/* -- SENSORS SYSCTL END -- */ + +/* The /proc/sys entries */ +/* These files are created for each detected chip. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ + +static ctl_table mtp008_dir_table_template[] = +{ + {MTP008_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_in}, + {MTP008_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan}, + {MTP008_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan}, + {MTP008_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan}, + {MTP008_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_temp}, + {MTP008_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_temp_add}, + {MTP008_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_temp_add}, + {MTP008_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_vid}, + {MTP008_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_fan_div}, + {MTP008_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_alarms}, + {MTP008_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_beep}, + {MTP008_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_pwm}, + {MTP008_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_pwm}, + {MTP008_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_pwm}, + {MTP008_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_sens}, + {MTP008_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_sens}, + {MTP008_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &mtp008_sens}, + {0} +}; + +/* This function is called when: + * mtp008_driver is inserted (when this module is loaded), for each available + * adapter when a new adapter is inserted (and mtp008_driver is still present) + */ +static int mtp008_attach_adapter(struct i2c_adapter *adapter) +{ + struct i2c_client_address_data mtp008_addr_data; + + mtp008_addr_data.normal_i2c = addr_data.normal_i2c; + mtp008_addr_data.normal_i2c_range = addr_data.normal_i2c_range; + mtp008_addr_data.probe = addr_data.probe; + mtp008_addr_data.probe_range = addr_data.probe_range; + mtp008_addr_data.ignore = addr_data.ignore; + mtp008_addr_data.ignore_range = addr_data.ignore_range; + mtp008_addr_data.force = addr_data.forces->force; + + return i2c_probe(adapter, &mtp008_addr_data, mtp008_detect); +} + +int mtp008_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + const char *type_name = ""; + const char *client_name = ""; + int is_isa, err, sysid; + struct i2c_client *new_client; + struct mtp008_data *data; + + err = 0; + + is_isa = i2c_is_isa_adapter(adapter); + if (is_isa || + !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* + * We presume we have a valid client. We now create the client + * structure, even though we cannot fill it completely yet. But it + * allows us to use mtp008_(read|write)_value(). + */ + if (!(data = kmalloc(sizeof(struct mtp008_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &mtp008_driver; + new_client->flags = 0; + + /* + * Remaining detection. + */ + if (kind < 0) { + if (mtp008_read_value(new_client, MTP008_REG_CHIPID) != 0xac) + goto ERROR1; + } + /* + * Fill in the remaining client fields and put it into the global list. + */ + type_name = "mtp008"; + client_name = "MTP008 chip"; + strcpy(new_client->name, client_name); + data->type = kind; + 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, + THIS_MODULE)) < 0) { + err = sysid; + goto ERROR2; + } + data->sysctl_id = sysid; + + /* + * Initialize the MTP008 chip. + */ + mtp008_init_client(new_client); + + return 0; + + /* + * Error handling. Bad programming practise but very code efficient. + */ + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + + ERROR0: + return err; +} + +static int mtp008_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry( + ((struct mtp008_data *) (client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("mtp008.o: Deregistration failed, " + "client not detached.\n"); + return err; + } + kfree(client->data); + + return 0; +} + + +static int mtp008_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg) & 0xff; +} + +static int mtp008_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new MTP008. */ +static void mtp008_init_client(struct i2c_client *client) +{ + u8 save1, save2; + struct mtp008_data *data; + + data = client->data; + + /* + * Initialize the Myson MTP008 hardware monitoring chip. + * Save the pin settings that the BIOS hopefully set. + */ + save1 = mtp008_read_value(client, MTP008_REG_PIN_CTRL1); + save2 = mtp008_read_value(client, MTP008_REG_PIN_CTRL2); + mtp008_write_value(client, MTP008_REG_CONFIG, + (mtp008_read_value(client, MTP008_REG_CONFIG) & 0x7f) | 0x80); + mtp008_write_value(client, MTP008_REG_PIN_CTRL1, save1); + mtp008_write_value(client, MTP008_REG_PIN_CTRL2, save2); + + mtp008_getsensortype(data, save2); + + + /* + * Start monitoring. + */ + mtp008_write_value( + client, MTP008_REG_CONFIG, + (mtp008_read_value(client, MTP008_REG_CONFIG) & 0xf7) | 0x01 + ); +} + +static void mtp008_update_client(struct i2c_client *client) +{ + int i; + u8 inp; + struct mtp008_data *data; + + data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk("Starting MTP008 update\n"); +#endif + + /* + * Read in the analog inputs. We're reading AIN4 and AIN5 as + * regular analog inputs, even though they may have been + * configured as temperature readings instead. Interpretation + * of these values is done elsewhere. + */ + for (i = 0; i < 7; i++) { + data->in[i] = + mtp008_read_value(client, MTP008_REG_IN(i)); + data->in_max[i] = + mtp008_read_value(client, MTP008_REG_IN_MAX(i)); + data->in_min[i] = + mtp008_read_value(client, MTP008_REG_IN_MIN(i)); + } + + /* + * Read the temperature sensor. + */ + data->temp = mtp008_read_value(client, MTP008_REG_TEMP); + data->temp_max = mtp008_read_value(client, MTP008_REG_TEMP_MAX); + data->temp_min = mtp008_read_value(client, MTP008_REG_TEMP_MIN); + + /* + * Read the first 2 fan dividers and the VID setting. Read the + * third fan divider from a different register. + */ + inp = mtp008_read_value(client, MTP008_REG_VID_FANDIV); + data->vid = inp & 0x0f; + data->vid |= (mtp008_read_value(client, + MTP008_REG_RESET_VID4) & 0x01) << 4; + + data->fan_div[0] = (inp >> 4) & 0x03; + data->fan_div[1] = inp >> 6; + data->fan_div[2] = + mtp008_read_value(client, MTP008_REG_PIN_CTRL1) >> 6; + + /* + * Read the interrupt status registers. + */ + data->alarms = + (mtp008_read_value(client, + MTP008_REG_INT_STAT1) & 0xdf) | + (mtp008_read_value(client, + MTP008_REG_INT_STAT2) & 0x0f) << 8; + + /* + * Read the beep control registers. + */ + data->beeps = + (mtp008_read_value(client, + MTP008_REG_BEEP_CTRL1) & 0xdf) | + (mtp008_read_value(client, + MTP008_REG_BEEP_CTRL2) & 0x8f) << 8; + + /* + * Read the sensor configuration. + */ + inp = mtp008_read_value(client, MTP008_REG_PIN_CTRL2); + mtp008_getsensortype(data, inp); + data->pwmenable = inp; + + /* + * Read the PWM registers if enabled. + */ + for (i = 1; i <= 3; i++) + { + if(PWMENABLE_FROM_REG(i, inp)) + data->pwm[i-1] = mtp008_read_value(client, + MTP008_REG_PWM_CTRL(i)); + else + data->pwm[i-1] = 255; + } + + /* + * Read the fan sensors. Skip 3 if PWM1 enabled. + */ + for (i = 1; i <= 3; i++) { + if(i == 3 && PWMENABLE_FROM_REG(1, inp)) { + data->fan[2] = 0; + data->fan_min[2] = 0; + } else { + data->fan[i-1] = mtp008_read_value(client, + MTP008_REG_FAN(i)); + data->fan_min[i-1] = mtp008_read_value(client, + MTP008_REG_FAN_MIN(i)); + } + } + + data->last_updated = jiffies; + data->valid = 1; + } + up(&data->update_lock); +} + +static void mtp008_getsensortype(struct mtp008_data *data, u8 inp) +{ + inp &= 0x0f; + data->sens[0] = (inp >> 3) + 2; /* 2 or 3 */ + data->sens[1] = ((inp >> 1) & 0x03) + 1; /* 1, 2 or 3 */ + data->sens[2] = (inp & 0x01) + 1; /* 1 or 2 */ +} + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +void mtp008_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int nr; + struct mtp008_data *data; + + nr = ctl_name - MTP008_SYSCTL_IN0; + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 2; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + if((nr != 4 && nr != 5) || data->sens[nr - 3] == VOLTAGE) { + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + } else { + results[0] = 0; + results[1] = 0; + results[2] = 0; + } + + *nrels_mag = 3; + + break; + case SENSORS_PROC_REAL_WRITE: + if((nr != 4 && nr != 5) || data->sens[nr - 3] == VOLTAGE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + mtp008_write_value(client, MTP008_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + mtp008_write_value(client, MTP008_REG_IN_MAX(nr), + data->in_max[nr]); + } + } + } +} + +void mtp008_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int nr; + struct mtp008_data *data; + + nr = ctl_name - MTP008_SYSCTL_FAN1; + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 0; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + results[1] = FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr])); + + *nrels_mag = 2; + + break; + case SENSORS_PROC_REAL_WRITE: + if (*nrels_mag >= 1) { + data->fan_min[nr] = + FAN_TO_REG(results[0], + DIV_FROM_REG(data->fan_div[nr])); + mtp008_write_value(client, MTP008_REG_FAN_MIN(nr + 1), + data->fan_min[nr]); + } + } +} + +void mtp008_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct mtp008_data *data; + + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 1; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = TEMP_FROM_REG(data->temp_max); + results[1] = TEMP_FROM_REG(data->temp_min); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + + break; + case SENSORS_PROC_REAL_WRITE: + if (*nrels_mag >= 1) { + data->temp_max = TEMP_TO_REG(results[0]); + mtp008_write_value(client, MTP008_REG_TEMP_MAX, + data->temp_max); + } + if (*nrels_mag >= 2) { + data->temp_min = TEMP_TO_REG(results[1]); + mtp008_write_value(client, MTP008_REG_TEMP_MIN, + data->temp_min); + } + } +} + +void mtp008_temp_add(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int nr; + struct mtp008_data *data; + + nr = 3 + ctl_name - MTP008_SYSCTL_TEMP1; /* AIN4 or AIN5 */ + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 1; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + if(data->sens[nr - 3] != VOLTAGE) { + results[0] = TEMP_FROM_REG(data->in_max[nr]); + results[1] = TEMP_FROM_REG(data->in_min[nr]); + results[2] = TEMP_FROM_REG(data->in[nr]); + } else { + results[0] = 0; + results[1] = 0; + results[2] = 0; + } + *nrels_mag = 3; + + break; + case SENSORS_PROC_REAL_WRITE: + if(data->sens[nr - 3] != VOLTAGE) { + if (*nrels_mag >= 1) { + data->in_max[nr] = TEMP_TO_REG(results[0]); + mtp008_write_value(client, + MTP008_REG_IN_MAX(nr), + data->in_max[nr]); + } + if (*nrels_mag >= 2) { + data->in_min[nr] = TEMP_TO_REG(results[1]); + mtp008_write_value(client, + MTP008_REG_IN_MIN(nr), + data->in_min[nr]); + } + } + } +} + +void mtp008_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct mtp008_data *data; + + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 2; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = VID_FROM_REG(data->vid); + + *nrels_mag = 1; + } +} + +void mtp008_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct mtp008_data *data; + u8 val; + + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 0; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + results[2] = DIV_FROM_REG(data->fan_div[2]); + + *nrels_mag = 3; + + break; + case SENSORS_PROC_REAL_WRITE: + if (*nrels_mag >= 3) { + data->fan_div[2] = DIV_TO_REG(results[2]); + val = mtp008_read_value(client, MTP008_REG_PIN_CTRL1); + val = (val & 0x3f) | (data->fan_div[2] & 0x03) << 6; + mtp008_write_value(client, MTP008_REG_PIN_CTRL1, val); + } + if (*nrels_mag >= 1) { + val = mtp008_read_value(client, MTP008_REG_VID_FANDIV); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + val = (val & 0x3f) | + (data->fan_div[1] & 0x03) << 6; + } + data->fan_div[0] = DIV_TO_REG(results[0]); + val = (val & 0xcf) | (data->fan_div[0] & 0x03) << 4; + mtp008_write_value(client, MTP008_REG_VID_FANDIV, val); + } + } +} + +void mtp008_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct mtp008_data *data; + + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 0; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = ALARMS_FROM_REG(data->alarms); + + *nrels_mag = 1; + } +} + +void mtp008_beep(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct mtp008_data *data; + + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 0; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = BEEPS_FROM_REG(data->beeps); + + *nrels_mag = 1; + + break; + case SENSORS_PROC_REAL_WRITE: + if (*nrels_mag >= 1) { + data->beeps = BEEPS_TO_REG(results[0]) & 0xdf8f; + + mtp008_write_value(client, MTP008_REG_BEEP_CTRL1, + data->beeps & 0xff); + mtp008_write_value(client, MTP008_REG_BEEP_CTRL2, + data->beeps >> 8); + } + } +} + +void mtp008_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int nr; + struct mtp008_data *data; + + nr = ctl_name - MTP008_SYSCTL_PWM1; + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 0; + + break; + case SENSORS_PROC_REAL_READ: + mtp008_update_client(client); + + results[0] = PWM_FROM_REG(data->pwm[nr]); + results[1] = PWMENABLE_FROM_REG(nr + 1, data->pwmenable); + *nrels_mag = 2; + + break; + case SENSORS_PROC_REAL_WRITE: + if (*nrels_mag >= 1) { + if (*nrels_mag >= 2) { + if(results[1]) + data->pwmenable |= 0x10 << nr; + else + data->pwmenable &= ~(0x10 << nr); + mtp008_write_value(client, MTP008_REG_PIN_CTRL2, + data->pwmenable); + } + data->pwm[nr] = PWM_TO_REG(results[0]); + mtp008_write_value(client, MTP008_REG_PWM_CTRL(nr), + data->pwm[nr]); + } + } +} + +void mtp008_sens(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + const char *opts = ""; + int nr; + u8 tmp; + struct mtp008_data *data; + + nr = 1 + ctl_name - MTP008_SYSCTL_SENS1; + data = client->data; + + switch (operation) { + case SENSORS_PROC_REAL_INFO: + *nrels_mag = 0; + + break; + case SENSORS_PROC_REAL_READ: + results[0] = data->sens[nr - 1]; + + *nrels_mag = 1; + + break; + case SENSORS_PROC_REAL_WRITE: + if (*nrels_mag >= 1) { + tmp = mtp008_read_value(client, MTP008_REG_PIN_CTRL2); + + switch (nr) { + case 1: /* VT or PII */ + opts = "2 or 3"; + + switch (results[0]) { + case THERMISTOR: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp & ~MTP008_CFG_VT1_PII); + data->sens[0] = 2; + return; + case PIIDIODE: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp | MTP008_CFG_VT1_PII); + data->sens[0] = 3; + return; + } + + break; + case 2: /* AIN, VT or PII */ + tmp &= ~MTP008_CFG_VT2_MASK; + opts = "1, 2 or 3"; + + switch (results[0]) { + case VOLTAGE: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp | MTP008_CFG_VT2_AIN); + data->sens[1] = 1; + return; + case THERMISTOR: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp | MTP008_CFG_VT2_VT); + data->sens[1] = 2; + return; + case PIIDIODE: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp | MTP008_CFG_VT2_PII); + data->sens[1] = 3; + return; + } + + break; + case 3: /* AIN or VT */ + opts = "1 or 2"; + + switch (results[0]) { + case VOLTAGE: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp & ~MTP008_CFG_VT3_VT); + data->sens[2] = 1; + return; + case THERMISTOR: + mtp008_write_value( + client, MTP008_REG_PIN_CTRL2, + tmp | MTP008_CFG_VT3_VT); + data->sens[2] = 2; + return; + } + + break; + } + + printk("mtp008.o: Invalid sensor type %ld " + "for sensor %d; must be %s.\n", + results[0], nr, opts); + } + } +} + +static int __init sm_mtp008_init(void) +{ + printk("mtp008.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&mtp008_driver); +} + +static void __exit sm_mtp008_exit(void) +{ + i2c_del_driver(&mtp008_driver); +} + + + +MODULE_AUTHOR("Frodo Looijaard , " + "Philip Edelbrock , " + "and Kris Van Hees "); +MODULE_DESCRIPTION("MTP008 driver"); + +module_init(sm_mtp008_init); +module_exit(sm_mtp008_exit); --- linux-old/drivers/sensors/pc87360.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/pc87360.c Sun Feb 26 11:18:41 2006 @@ -0,0 +1,1351 @@ +/* + * pc87360.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * Copyright (C) 2004 Jean Delvare + * + * Copied from smsc47m1.c: + * 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 the following chips: + * + * Chip #vin #fan #pwm #temp devid + * PC87360 - 2 2 - 0xE1 + * PC87363 - 2 2 - 0xE8 + * PC87364 - 3 3 - 0xE4 + * PC87365 11 3 3 2 0xE5 + * PC87366 11 3 3 3-4 0xE9 + * + * This driver assumes that no more than one chip is present, and the + * standard Super-I/O address is used (0x2E/0x2F). + */ + +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; +static struct i2c_force_data forces[] = {{NULL}}; +static u8 devid; +static unsigned int extra_isa[] = { 0x0000, 0x0000, 0x0000 }; +static u8 confreg[4]; + +enum chips { any_chip, pc87360, pc87363, pc87364, pc87365, pc87366 }; +static struct i2c_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .normal_isa = normal_isa, + .normal_isa_range = normal_isa_range, + .probe = normal_i2c, /* cheat */ + .probe_range = normal_i2c_range, /* cheat */ + .ignore = normal_i2c, /* cheat */ + .ignore_range = normal_i2c_range, /* cheat */ + .forces = forces, +}; + +static int init = 1; +MODULE_PARM(init, "i"); +MODULE_PARM_DESC(init, + "Chip initialization level:\n" + " 0: None\n" + "*1: Forcibly enable internal voltage and temperature channels, except in9\n" + " 2: Forcibly enable all voltage and temperature channels, except in9\n" + " 3: Forcibly enable all voltage and temperature channels, including in9"); + +/* + * Super-I/O registers and operations + */ + +#define DEV 0x07 /* Register: Logical device select */ +#define DEVID 0x20 /* Register: Device ID */ +#define ACT 0x30 /* Register: Device activation */ +#define BASE 0x60 /* Register: Base address */ + +#define FSCM 0x09 /* Logical device: fans */ +#define VLM 0x0d /* Logical device: voltages */ +#define TMS 0x0e /* Logical device: temperatures */ +static const u8 logdev[3] = { FSCM, VLM, TMS }; + +#define LD_FAN 0 +#define LD_IN 1 +#define LD_TEMP 2 + +static inline void superio_outb(int sioaddr, int reg, int val) +{ + outb(reg, sioaddr); + outb(val, sioaddr+1); +} + +static inline int superio_inb(int sioaddr, int reg) +{ + outb(reg, sioaddr); + return inb(sioaddr+1); +} + +static inline void superio_exit(int sioaddr) +{ + outb(0x02, sioaddr); + outb(0x02, sioaddr+1); +} + +/* + * Logical devices + */ + +#define PC87360_EXTENT 0x10 +#define PC87365_REG_BANK 0x09 +#define NO_BANK 0xff + +/* + * Fan registers and conversions + */ + +/* nr has to be 0 or 1 (PC87360/87363) or 2 (PC87364/87365/87366) */ +#define PC87360_REG_PRESCALE(nr) (0x00 + 2 * (nr)) +#define PC87360_REG_PWM(nr) (0x01 + 2 * (nr)) +#define PC87360_REG_FAN_MIN(nr) (0x06 + 3 * (nr)) +#define PC87360_REG_FAN(nr) (0x07 + 3 * (nr)) +#define PC87360_REG_FAN_STATUS(nr) (0x08 + 3 * (nr)) + +#define FAN_FROM_REG(val,div) ((val)==0?0: \ + 480000/((val)*(div))) +#define FAN_TO_REG(val,div) ((val)<=100?0: \ + 480000/((val)*(div))) +#define FAN_DIV_FROM_REG(val) (1 << ((val >> 5) & 0x03)) +#define FAN_DIV_TO_REG(val) ((val)==8?0x60:(val)==4?0x40: \ + (val)==1?0x00:0x20) +#define FAN_STATUS_FROM_REG(val) ((val) & 0x07) + +#define FAN_CONFIG_MONITOR(val,nr) (((val) >> (2 + nr * 3)) & 1) +#define FAN_CONFIG_CONTROL(val,nr) (((val) >> (3 + nr * 3)) & 1) +#define FAN_CONFIG_INVERT(val,nr) (((val) >> (4 + nr * 3)) & 1) + +#define PWM_FROM_REG(val,inv) ((inv) ? 255 - (val) : (val)) +static inline u8 PWM_TO_REG(int val, int inv) +{ + if (inv) + val = 255 - val; + if (val < 0) + return 0; + if (val > 255) + return 255; + return val; +} + +/* + * Voltage registers and conversions + */ + +#define PC87365_REG_IN_CONVRATE 0x07 +#define PC87365_REG_IN_CONFIG 0x08 +#define PC87365_REG_IN 0x0B +#define PC87365_REG_IN_MIN 0x0D +#define PC87365_REG_IN_MAX 0x0C +#define PC87365_REG_IN_STATUS 0x0A +#define PC87365_REG_IN_ALARMS1 0x00 +#define PC87365_REG_IN_ALARMS2 0x01 +#define PC87365_REG_VID 0x06 + +#define IN_FROM_REG(val,ref) (((val) * (ref) + 128) / 256) +#define IN_TO_REG(val,ref) ((val)<0 ? 0 : \ + (val)*256>=(ref)*255 ? 255: \ + ((val) * 256 + (ref) / 2) / (ref)) + +/* + * Temperature registers and conversions + */ + +#define PC87365_REG_TEMP_CONFIG 0x08 +#define PC87365_REG_TEMP 0x0B +#define PC87365_REG_TEMP_MIN 0x0D +#define PC87365_REG_TEMP_MAX 0x0C +#define PC87365_REG_TEMP_CRIT 0x0E +#define PC87365_REG_TEMP_STATUS 0x0A +#define PC87365_REG_TEMP_ALARMS 0x00 + +#define TEMP_FROM_REG(val) ((val)&0x80 ? (val) - 0x100 : (val)) +#define TEMP_TO_REG(val) ((val)<-55 ? 201 : (val)>127 ? 0x7F : \ + (val)<0 ? (val) + 0x100 : (val)) + +struct pc87360_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + int address[3]; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 fannr, innr, tempnr; + + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u8 fan_status[3]; /* Register value */ + u8 pwm[3]; /* Register value */ + u16 fan_conf; /* Configuration register values, combined */ + + u16 in_vref; /* 10mV/bit */ + u8 in[14]; /* Register value */ + u8 in_min[14]; /* Register value */ + u8 in_max[14]; /* Register value */ + u8 in_crit[3]; /* Register value */ + u8 in_status[14]; /* Register value */ + u16 in_alarms; /* Register values, combined, masked */ + u8 vid_conf; /* Configuration register value */ + u8 vrm; + u8 vid; /* Register value */ + + u8 temp[3]; /* Register value */ + u8 temp_min[3]; /* Register value */ + u8 temp_max[3]; /* Register value */ + u8 temp_crit[3]; /* Register value */ + u8 temp_status[3]; /* Register value */ + u8 temp_alarms; /* Register value, masked */ +}; + + +static int pc87360_attach_adapter(struct i2c_adapter *adapter); +static int pc87360_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int pc87360_detach_client(struct i2c_client *client); + +static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg); +static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg, u8 value); +static void pc87360_init_client(struct i2c_client *client, int use_thermistors); +static void pc87360_update_client(struct i2c_client *client); + + +void pc87365_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); + +static void pc87360_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pc87360_fan_status(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pc87360_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pc87360_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +void pc87365_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +void pc87365_in_status(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +void pc87365_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +void pc87365_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); + +void pc87365_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +void pc87365_temp_status(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); + +static struct i2c_driver pc87360_driver = { + .name = "PC8736x hardware monitor", + .flags = I2C_DF_NOTIFY, + .attach_adapter = pc87360_attach_adapter, + .detach_client = pc87360_detach_client, +}; + +/* -- SENSORS SYSCTL START -- */ + +#define PC87365_SYSCTL_ALARMS 100 /* bit field */ + +#define PC87360_SYSCTL_FAN1 1101 /* Rotations/min */ +#define PC87360_SYSCTL_FAN2 1102 +#define PC87360_SYSCTL_FAN3 1103 /* not for PC87360/PC87363 */ +#define PC87360_SYSCTL_FAN_DIV 1201 /* 1, 2, 4 or 8 */ +#define PC87360_SYSCTL_FAN1_STATUS 1301 /* bit field */ +#define PC87360_SYSCTL_FAN2_STATUS 1302 +#define PC87360_SYSCTL_FAN3_STATUS 1303 /* not for PC87360/PC87363 */ +#define PC87360_SYSCTL_PWM1 1401 /* 0-255 */ +#define PC87360_SYSCTL_PWM2 1402 +#define PC87360_SYSCTL_PWM3 1403 /* not for PC87360/PC87363 */ + +#define PC87360_STATUS_FAN_READY 0x01 +#define PC87360_STATUS_FAN_LOW 0x02 +#define PC87360_STATUS_FAN_OVERFLOW 0x04 + +#define PC87365_SYSCTL_IN0 2100 /* mV */ +#define PC87365_SYSCTL_IN1 2101 +#define PC87365_SYSCTL_IN2 2102 +#define PC87365_SYSCTL_IN3 2103 +#define PC87365_SYSCTL_IN4 2104 +#define PC87365_SYSCTL_IN5 2105 +#define PC87365_SYSCTL_IN6 2106 +#define PC87365_SYSCTL_IN7 2107 +#define PC87365_SYSCTL_IN8 2108 +#define PC87365_SYSCTL_IN9 2109 +#define PC87365_SYSCTL_IN10 2110 +#define PC87365_SYSCTL_TEMP4 2111 /* not for PC87365 */ +#define PC87365_SYSCTL_TEMP5 2112 /* not for PC87365 */ +#define PC87365_SYSCTL_TEMP6 2113 /* not for PC87365 */ +#define PC87365_SYSCTL_IN0_STATUS 2300 /* bit field */ +#define PC87365_SYSCTL_IN1_STATUS 2301 +#define PC87365_SYSCTL_IN2_STATUS 2302 +#define PC87365_SYSCTL_IN3_STATUS 2303 +#define PC87365_SYSCTL_IN4_STATUS 2304 +#define PC87365_SYSCTL_IN5_STATUS 2305 +#define PC87365_SYSCTL_IN6_STATUS 2306 +#define PC87365_SYSCTL_IN7_STATUS 2307 +#define PC87365_SYSCTL_IN8_STATUS 2308 +#define PC87365_SYSCTL_IN9_STATUS 2309 +#define PC87365_SYSCTL_IN10_STATUS 2310 +#define PC87365_SYSCTL_TEMP4_STATUS 2311 /* not for PC87365 */ +#define PC87365_SYSCTL_TEMP5_STATUS 2312 /* not for PC87365 */ +#define PC87365_SYSCTL_TEMP6_STATUS 2313 /* not for PC87365 */ + +#define PC87365_SYSCTL_VID 2400 +#define PC87365_SYSCTL_VRM 2401 + +#define PC87365_STATUS_IN_MIN 0x02 +#define PC87365_STATUS_IN_MAX 0x04 + +#define PC87365_SYSCTL_TEMP1 3101 /* degrees Celsius */ +#define PC87365_SYSCTL_TEMP2 3102 +#define PC87365_SYSCTL_TEMP3 3103 /* not for PC87365 */ +#define PC87365_SYSCTL_TEMP1_STATUS 3301 /* bit field */ +#define PC87365_SYSCTL_TEMP2_STATUS 3302 +#define PC87365_SYSCTL_TEMP3_STATUS 3303 /* not for PC87365 */ + +#define PC87365_STATUS_TEMP_MIN 0x02 +#define PC87365_STATUS_TEMP_MAX 0x04 +#define PC87365_STATUS_TEMP_CRIT 0x08 +#define PC87365_STATUS_TEMP_OPEN 0x40 + +/* -- SENSORS SYSCTL END -- */ + +static ctl_table pc87360_dir_table_template[] = { /* PC87363 and PC87364 too */ + {PC87360_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, + {PC87360_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, + {PC87360_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, + {PC87360_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_div}, + {PC87360_SYSCTL_FAN1_STATUS, "fan1_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, + {PC87360_SYSCTL_FAN2_STATUS, "fan2_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, + {PC87360_SYSCTL_FAN3_STATUS, "fan3_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, + {PC87360_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, + {PC87360_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, + {PC87360_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, + {0} +}; + +static ctl_table pc87365_dir_table_template[] = { /* PC87366 too */ + {PC87365_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_alarms}, + {PC87360_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, + {PC87360_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, + {PC87360_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan}, + {PC87360_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_div}, + {PC87360_SYSCTL_FAN1_STATUS, "fan1_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, + {PC87360_SYSCTL_FAN2_STATUS, "fan2_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, + {PC87360_SYSCTL_FAN3_STATUS, "fan3_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_fan_status}, + {PC87360_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, + {PC87360_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, + {PC87360_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87360_pwm}, + {PC87365_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN9, "in9", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN10, "in10", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_IN0_STATUS, "in0_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN1_STATUS, "in1_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN2_STATUS, "in2_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN3_STATUS, "in3_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN4_STATUS, "in4_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN5_STATUS, "in5_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN6_STATUS, "in6_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN7_STATUS, "in7_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN8_STATUS, "in8_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN9_STATUS, "in9_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_IN10_STATUS, "in10_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp}, + {PC87365_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp}, + {PC87365_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp}, + {PC87365_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in}, + {PC87365_SYSCTL_TEMP1_STATUS, "temp1_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp_status}, + {PC87365_SYSCTL_TEMP2_STATUS, "temp2_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp_status}, + {PC87365_SYSCTL_TEMP3_STATUS, "temp3_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_temp_status}, + {PC87365_SYSCTL_TEMP4_STATUS, "temp4_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_TEMP5_STATUS, "temp5_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_TEMP6_STATUS, "temp6_status", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_in_status}, + {PC87365_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_vid}, + {PC87365_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pc87365_vrm}, + {0} +}; + +static int pc87360_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, pc87360_detect); +} + +static int __init pc87360_find(int sioaddr, u8 *devid, int *address) +{ + u16 val; + int i; + int nrdev; /* logical device count */ + + /* No superio_enter */ + + /* Identify device */ + val = superio_inb(sioaddr, DEVID); + switch (val) { + case 0xE1: /* PC87360 */ + case 0xE8: /* PC87363 */ + case 0xE4: /* PC87364 */ + nrdev = 1; + break; + case 0xE5: /* PC87365 */ + case 0xE9: /* PC87366 */ + nrdev = 3; + break; + default: + superio_exit(sioaddr); + return -ENODEV; + } + /* Remember the device id */ + *devid = val; + + for (i = 0; i < nrdev; i++) { + /* select logical device */ + superio_outb(sioaddr, DEV, logdev[i]); + + val = superio_inb(sioaddr, ACT); + if (!(val & 0x01)) { + printk(KERN_INFO "pc87360.o: Device 0x%02x not " + "activated\n", logdev[i]); + continue; + } + + val = (superio_inb(sioaddr, BASE) << 8) + | superio_inb(sioaddr, BASE + 1); + if (!val) { + printk(KERN_INFO "pc87360.o: Base address not set for " + "device 0x%02x\n", logdev[i]); + continue; + } + + address[i] = val; + + if (i==0) { /* Fans */ + confreg[0] = superio_inb(sioaddr, 0xF0); + confreg[1] = superio_inb(sioaddr, 0xF1); + +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Fan 1: mon=%d " + "ctrl=%d inv=%d\n", (confreg[0]>>2)&1, + (confreg[0]>>3)&1, (confreg[0]>>4)&1); + printk(KERN_DEBUG "pc87360.o: Fan 2: mon=%d " + "ctrl=%d inv=%d\n", (confreg[0]>>5)&1, + (confreg[0]>>6)&1, (confreg[0]>>7)&1); + printk(KERN_DEBUG "pc87360.o: Fan 3: mon=%d " + "ctrl=%d inv=%d\n", confreg[1]&1, + (confreg[1]>>1)&1, (confreg[1]>>2)&1); +#endif + } else if (i==1) { /* Voltages */ + /* Are we using thermistors? */ + if (*devid == 0xE9) { /* PC87366 */ + /* These registers are not logical-device + specific, just that we won't need them if + we don't use the VLM device */ + confreg[2] = superio_inb(sioaddr, 0x2B); + confreg[3] = superio_inb(sioaddr, 0x25); + + if (confreg[2] & 0x40) { + printk(KERN_INFO "pc87360.o: Using " + "thermistors for temperature " + "monitoring\n"); + } + if (confreg[3] & 0xE0) { + printk(KERN_INFO "pc87360.o: VID " + "inputs routed (mode %u)\n", + confreg[3] >> 5); + } + } + } + } + + superio_exit(sioaddr); + return 0; +} + +/* We don't really care about the address. + Read from extra_isa instead. */ +int pc87360_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct pc87360_data *data; + int err = 0; + const char *type_name = "pc87360"; + const char *client_name = "PC8736x chip"; + ctl_table *template = pc87360_dir_table_template; + int use_thermistors = 0; + + if (!i2c_is_isa_adapter(adapter)) { + return 0; + } + + for (i = 0; i < 3; i++) { + if (extra_isa[i] + && check_region(extra_isa[i], PC87360_EXTENT)) { + printk(KERN_ERR "pc87360.o: Region 0x%x-0x%x already " + "in use!\n", extra_isa[i], + extra_isa[i]+PC87360_EXTENT-1); + return -ENODEV; + } + } + + if (!(data = kmalloc(sizeof(struct pc87360_data), GFP_KERNEL))) { + return -ENOMEM; + } + memset(data, 0x00, sizeof(struct pc87360_data)); + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &pc87360_driver; + new_client->flags = 0; + + data->fannr = 2; + data->innr = 0; + data->tempnr = 0; + + switch (devid) { + case 0xe8: + type_name = "pc87363"; + break; + case 0xe4: + type_name = "pc87364"; + data->fannr = 3; + break; + case 0xe5: + type_name = "pc87365"; + template = pc87365_dir_table_template; + data->fannr = extra_isa[0] ? 3 : 0; + data->innr = extra_isa[1] ? 11 : 0; + data->tempnr = extra_isa[2] ? 2 : 0; + break; + case 0xe9: + type_name = "pc87366"; + template = pc87365_dir_table_template; + data->fannr = extra_isa[0] ? 3 : 0; + data->innr = extra_isa[1] ? 14 : 0; + data->tempnr = extra_isa[2] ? 3 : 0; + break; + } + + /* Retrieve the fans configuration from Super-I/O space */ + if (data->fannr) + data->fan_conf = confreg[0] | (confreg[1] << 8); + + for (i = 0; i < 3; i++) { + if ((data->address[i] = extra_isa[i])) { + request_region(extra_isa[i], PC87360_EXTENT, "pc87360"); + } + } + strcpy(new_client->name, client_name); + data->valid = 0; + init_MUTEX(&data->update_lock); + + if ((err = i2c_attach_client(new_client))) + goto ERROR1; + + /* Use the correct reference voltage + Unless both the VLM and the TMS logical devices agree to + use an external Vref, the internal one is used. */ + if (data->innr) { + i = pc87360_read_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONFIG); + if (data->tempnr) { + i &= pc87360_read_value(data, LD_TEMP, NO_BANK, + PC87365_REG_TEMP_CONFIG); + } + data->in_vref = (i&0x02) ? 3025 : 2966; +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Using %s reference voltage\n", + (i&0x02) ? "external" : "internal"); +#endif + + data->vid_conf = confreg[3]; + data->vrm = 90; + } + + /* Fan clock dividers may be needed before any data is read */ + for (i = 0; i < data->fannr; i++) { + if (FAN_CONFIG_MONITOR(data->fan_conf, i)) + data->fan_status[i] = pc87360_read_value(data, + LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(i)); + } + + if (init > 0) { + if (devid == 0xe9 && data->address[1]) /* PC87366 */ + use_thermistors = confreg[2] & 0x40; + + pc87360_init_client(new_client, use_thermistors); + } + + if ((i = i2c_register_entry((struct i2c_client *) new_client, + type_name, template, THIS_MODULE)) < 0) { + err = i; + goto ERROR2; + } + data->sysctl_id = i; + + return 0; + +ERROR2: + i2c_detach_client(new_client); +ERROR1: + for (i = 0; i < 3; i++) { + if (data->address[i]) { + release_region(data->address[i], PC87360_EXTENT); + } + } + kfree(data); + return err; +} + +static int pc87360_detach_client(struct i2c_client *client) +{ + struct pc87360_data *data = client->data; + int i, err; + + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "pc87360.o: Client deregistration failed, " + "client not detached.\n"); + return err; + } + + for (i = 0; i < 3; i++) { + if (data->address[i]) { + release_region(data->address[i], PC87360_EXTENT); + } + } + kfree(client->data); + + return 0; +} + +/* ldi is the logical device index + bank is for voltages and temperatures only */ +static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg) +{ + int res; + + down(&(data->lock)); + if (bank != NO_BANK) { + outb_p(bank, data->address[ldi] + PC87365_REG_BANK); + } + res = inb_p(data->address[ldi] + reg); + up(&(data->lock)); + return res; +} + +static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg, u8 value) +{ + down(&(data->lock)); + if (bank != NO_BANK) { + outb_p(bank, data->address[ldi] + PC87365_REG_BANK); + } + outb_p(value, data->address[ldi] + reg); + up(&(data->lock)); +} + +static void pc87360_init_client(struct i2c_client *client, int use_thermistors) +{ + struct pc87360_data *data = client->data; + int i, nr; + const u8 init_in[14] = { 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 1, 2, 2, 2 }; + const u8 init_temp[3] = { 2, 2, 1 }; + u8 reg; + + if (init >= 2 && data->innr) { + reg = pc87360_read_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONVRATE); + printk(KERN_INFO "pc87360.o: VLM conversion set to " + "1s period, 160us delay\n"); + pc87360_write_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONVRATE, + (reg & 0xC0) | 0x11); + } + + nr = data->innr < 11 ? data->innr : 11; + for (i=0; i= init_in[i]) { + /* Forcibly enable voltage channel */ + reg = pc87360_read_value(data, LD_IN, i, + PC87365_REG_IN_STATUS); + if (!(reg & 0x01)) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Forcibly " + "enabling in%d\n", i); +#endif + pc87360_write_value(data, LD_IN, i, + PC87365_REG_IN_STATUS, + (reg & 0x68) | 0x87); + } + } + } + + /* We can't blindly trust the Super-I/O space configuration bit, + most BIOS won't set it properly */ + for (i=11; iinnr; i++) { + reg = pc87360_read_value(data, LD_IN, i, + PC87365_REG_TEMP_STATUS); + use_thermistors = use_thermistors || (reg & 0x01); + } + + i = use_thermistors ? 2 : 0; + for (; itempnr; i++) { + if (init >= init_temp[i]) { + /* Forcibly enable temperature channel */ + reg = pc87360_read_value(data, LD_TEMP, i, + PC87365_REG_TEMP_STATUS); + if (!(reg & 0x01)) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Forcibly " + "enabling temp%d\n", i+1); +#endif + pc87360_write_value(data, LD_TEMP, i, + PC87365_REG_TEMP_STATUS, + 0xCF); + } + } + } + + if (use_thermistors) { + for (i=11; iinnr; i++) { + if (init >= init_in[i]) { + /* The pin may already be used by thermal + diodes */ + reg = pc87360_read_value(data, LD_TEMP, (i-11)/2, + PC87365_REG_TEMP_STATUS); + if (reg & 0x01) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Skipping " + "temp%d, pin already in use by " + "temp%d\n", i-7, (i-11)/2); +#endif + continue; + } + + /* Forcibly enable thermistor channel */ + reg = pc87360_read_value(data, LD_IN, i, + PC87365_REG_IN_STATUS); + if (!(reg & 0x01)) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Forcibly " + "enabling temp%d\n", i-7); +#endif + pc87360_write_value(data, LD_IN, i, + PC87365_REG_TEMP_STATUS, + (reg & 0x60) | 0x8F); + } + } + } + } + + if (data->innr) { + reg = pc87360_read_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONFIG); + if (reg & 0x01) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Forcibly " + "enabling monitoring (VLM)\n"); +#endif + pc87360_write_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONFIG, + reg & 0xFE); + } + } + + if (data->tempnr) { + reg = pc87360_read_value(data, LD_TEMP, NO_BANK, + PC87365_REG_TEMP_CONFIG); + if (reg & 0x01) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Forcibly " + "enabling monitoring (TMS)\n"); +#endif + pc87360_write_value(data, LD_TEMP, NO_BANK, + PC87365_REG_TEMP_CONFIG, + reg & 0xFE); + } + + if (init >= 2) { + /* Chip config as documented by National Semi. */ + pc87360_write_value(data, LD_TEMP, 0xF, 0xA, 0x08); + /* We voluntarily omit the bank here, in case the + sequence itself matters. It shouldn't be a problem, + since nobody else is supposed to access the + device at that point. */ + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xB, 0x04); + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xC, 0x35); + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xD, 0x05); + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xE, 0x05); + } + } +} + +static void pc87360_autodiv(struct pc87360_data *data, int nr) +{ + u8 old_min = data->fan_min[nr]; + + /* Increase clock divider if needed and possible */ + if ((data->fan_status[nr] & 0x04) /* overflow flag */ + || (data->fan[nr] >= 224)) { /* next to overflow */ + if ((data->fan_status[nr] & 0x60) != 0x60) { + data->fan_status[nr] += 0x20; + data->fan_min[nr] >>= 1; + data->fan[nr] >>= 1; +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Increasing " + "clock divider to %d for fan %d\n", + FAN_DIV_FROM_REG(data->fan_status[nr]), + nr+1); +#endif + } + } else { + /* Decrease clock divider if possible */ + while (!(data->fan_min[nr] & 0x80) /* fan min "nails" divider */ + && data->fan[nr] < 85 /* bad accuracy */ + && (data->fan_status[nr] & 0x60) != 0x00) { + data->fan_status[nr] -= 0x20; + data->fan_min[nr] <<= 1; + data->fan[nr] <<= 1; +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Decreasing " + "clock divider to %d for fan %d\n", + FAN_DIV_FROM_REG(data->fan_status[nr]), + nr+1); +#endif + } + } + + /* Write new fan min if it changed */ + if (old_min != data->fan_min[nr]) { + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_MIN(nr), + data->fan_min[nr]); + } +} + +static void pc87360_update_client(struct i2c_client *client) +{ + struct pc87360_data *data = client->data; + u8 i; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ * 2) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk(KERN_DEBUG "pc87360.o: Data update\n"); +#endif + + /* Fans */ + for (i = 0; i < data->fannr; i++) { + if (FAN_CONFIG_MONITOR(data->fan_conf, i)) { + data->fan_status[i] = pc87360_read_value(data, + LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(i)); + data->fan[i] = pc87360_read_value(data, LD_FAN, + NO_BANK, PC87360_REG_FAN(i)); + data->fan_min[i] = pc87360_read_value(data, + LD_FAN, NO_BANK, + PC87360_REG_FAN_MIN(i)); + /* Change clock divider if needed */ + pc87360_autodiv(data, i); + /* Clear bits and write new divider */ + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(i), + data->fan_status[i]); + } + data->pwm[i] = pc87360_read_value(data, LD_FAN, + NO_BANK, PC87360_REG_PWM(i)); + } + + /* Voltages */ + for (i = 0; i < data->innr; i++) { + data->in_status[i] = pc87360_read_value(data, LD_IN, i, + PC87365_REG_IN_STATUS); + /* Clear bits */ + pc87360_write_value(data, LD_IN, i, + PC87365_REG_IN_STATUS, + data->in_status[i]); + if ((data->in_status[i] & 0x81) == 0x81) { + data->in[i] = pc87360_read_value(data, LD_IN, + i, PC87365_REG_IN); + } + if (data->in_status[i] & 0x01) { + data->in_min[i] = pc87360_read_value(data, + LD_IN, i, + PC87365_REG_IN_MIN); + data->in_max[i] = pc87360_read_value(data, + LD_IN, i, + PC87365_REG_IN_MAX); + if (i >= 11) + data->in_crit[i-11] = + pc87360_read_value(data, LD_IN, + i, PC87365_REG_TEMP_CRIT); + } + } + if (data->innr) { + data->in_alarms = pc87360_read_value(data, LD_IN, + NO_BANK, PC87365_REG_IN_ALARMS1) + | ((pc87360_read_value(data, LD_IN, + NO_BANK, PC87365_REG_IN_ALARMS2) + & 0x07) << 8); + data->vid = (data->vid_conf & 0xE0) ? + pc87360_read_value(data, LD_IN, + NO_BANK, PC87365_REG_VID) : 0x1F; + } + + /* Temperatures */ + for (i = 0; i < data->tempnr; i++) { + data->temp_status[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_STATUS); + /* Clear bits */ + pc87360_write_value(data, LD_TEMP, i, + PC87365_REG_TEMP_STATUS, + data->temp_status[i]); + if ((data->temp_status[i] & 0x81) == 0x81) { + data->temp[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP); + } + if (data->temp_status[i] & 0x01) { + data->temp_min[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_MIN); + data->temp_max[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_MAX); + data->temp_crit[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_CRIT); + } + } + if (data->tempnr) { + data->temp_alarms = pc87360_read_value(data, LD_TEMP, + NO_BANK, PC87365_REG_TEMP_ALARMS) + & 0x3F; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void pc87365_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = data->in_alarms; + results[1] = data->temp_alarms; + *nrels_mag = 2; + } +} + +void pc87360_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87360_SYSCTL_FAN1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr], + FAN_DIV_FROM_REG(data->fan_status[nr])); + results[1] = FAN_FROM_REG(data->fan[nr], + FAN_DIV_FROM_REG(data->fan_status[nr])); + *nrels_mag = 2; + } + /* We ignore National's recommendation */ + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (nr >= data->fannr) + return; + if (*nrels_mag >= 1) { + int fan_min = FAN_TO_REG(results[0], + FAN_DIV_FROM_REG(data->fan_status[nr])); + /* If it wouldn't fit, change clock divisor */ + while (fan_min > 255 + && (data->fan_status[nr] & 0x60) != 0x60) { + fan_min >>= 1; + data->fan[nr] >>= 1; + data->fan_status[nr] += 0x20; + } + data->fan_min[nr] = fan_min > 255 ? 255 : fan_min; + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_MIN(nr), + data->fan_min[nr]); + /* Write new divider, preserve alarm bits */ + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(nr), + data->fan_status[nr] & 0xF9); + } + } +} + +void pc87360_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int i; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + for (i = 0; i < data->fannr; i++) { + results[i] = FAN_DIV_FROM_REG(data->fan_status[i]); + } + for (; i < 3; i++) { + results[i] = 0; + } + *nrels_mag = 3; + } +} + +void pc87360_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87360_SYSCTL_PWM1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = PWM_FROM_REG(data->pwm[nr], + FAN_CONFIG_INVERT(data->fan_conf, nr)); + results[1] = FAN_CONFIG_CONTROL(data->fan_conf, nr); + *nrels_mag = 2; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (nr >= data->fannr) + return; + if (*nrels_mag >= 1) { + data->pwm[nr] = PWM_TO_REG(results[0], + FAN_CONFIG_INVERT(data->fan_conf, nr)); + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_PWM(nr), + data->pwm[nr]); + } + } +} + +void pc87360_fan_status(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87360_SYSCTL_FAN1_STATUS; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = FAN_STATUS_FROM_REG(data->fan_status[nr]); + *nrels_mag = 1; + } +} + +void pc87365_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87365_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr], data->in_vref); + results[1] = IN_FROM_REG(data->in_max[nr], data->in_vref); + if (nr < 11) { + *nrels_mag = 3; + } else { + results[2] = IN_FROM_REG(data->in_crit[nr-11], + data->in_vref); + *nrels_mag = 4; + } + results[(*nrels_mag)-1] = IN_FROM_REG(data->in[nr], + data->in_vref); + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0], + data->in_vref); + pc87360_write_value(data, LD_IN, nr, + PC87365_REG_IN_MIN, + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1], + data->in_vref); + pc87360_write_value(data, LD_IN, nr, + PC87365_REG_IN_MAX, + data->in_max[nr]); + } + if (*nrels_mag >= 3 && nr >= 11) { + data->in_crit[nr-11] = IN_TO_REG(results[2], + data->in_vref); + pc87360_write_value(data, LD_IN, nr, + PC87365_REG_TEMP_CRIT, + data->in_crit[nr-11]); + } + } +} + +void pc87365_in_status(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87365_SYSCTL_IN0_STATUS; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = data->in_status[nr]; + *nrels_mag = 1; + } +} + +void pc87365_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = vid_from_reg(data->vid & 0x1f, data->vrm); + *nrels_mag = 1; + } +} + +void pc87365_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +void pc87365_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87365_SYSCTL_TEMP1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_max[nr]); + results[1] = TEMP_FROM_REG(data->temp_min[nr]); + results[2] = TEMP_FROM_REG(data->temp_crit[nr]); + results[3] = TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 4; + } + else if (operation == SENSORS_PROC_REAL_WRITE) { + if (nr >= data->tempnr) + return; + if (*nrels_mag >= 1) { + data->temp_max[nr] = TEMP_TO_REG(results[0]); + pc87360_write_value(data, LD_TEMP, nr, + PC87365_REG_TEMP_MAX, + data->temp_max[nr]); + } + if (*nrels_mag >= 2) { + data->temp_min[nr] = TEMP_TO_REG(results[1]); + pc87360_write_value(data, LD_TEMP, nr, + PC87365_REG_TEMP_MIN, + data->temp_min[nr]); + } + if (*nrels_mag >= 3) { + data->temp_crit[nr] = TEMP_TO_REG(results[2]); + pc87360_write_value(data, LD_TEMP, nr, + PC87365_REG_TEMP_CRIT, + data->temp_crit[nr]); + } + } +} + +void pc87365_temp_status(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pc87360_data *data = client->data; + int nr = ctl_name - PC87365_SYSCTL_TEMP1_STATUS; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pc87360_update_client(client); + results[0] = data->temp_status[nr]; + *nrels_mag = 1; + } +} + + +static int __init pc87360_init(void) +{ + int i; + + printk(KERN_INFO "pc87360.o version %s (%s)\n", LM_VERSION, LM_DATE); + + if (pc87360_find(0x2e, &devid, extra_isa) + && pc87360_find(0x4e, &devid, extra_isa)) { + printk(KERN_WARNING "pc87360.o: PC8736x not detected, " + "module not inserted.\n"); + return -ENODEV; + } + + /* Arbitrarily pick one of the addresses */ + for (i = 0; i < 3; i++) { + if (extra_isa[i] != 0x0000) { + normal_isa[0] = extra_isa[i]; + break; + } + } + + if (normal_isa[0] == 0x0000) { + printk(KERN_WARNING "pc87360.o: No active logical device, " + "module not inserted.\n"); + return -ENODEV; + + } + + return i2c_add_driver(&pc87360_driver); +} + +static void __exit pc87360_exit(void) +{ + i2c_del_driver(&pc87360_driver); +} + + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("PC8736x hardware monitor"); +MODULE_LICENSE("GPL"); + +module_init(pc87360_init); +module_exit(pc87360_exit); --- linux-old/drivers/sensors/pcf8574.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/pcf8574.c Sun Feb 26 11:18:41 2006 @@ -0,0 +1,305 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x20, 0x27, 0x38, 0x3f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_2(pcf8574, pcf8574a); + +/* The PCF8574 registers */ + +/* (No registers. [Wow! This thing is SIMPLE!] ) */ + +/* Initial values */ +#define PCF8574_INIT 255 /* All outputs on (input mode) */ + +/* Each client has this additional data */ +struct pcf8574_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + + u8 read, write; /* Register values */ +}; + +static int pcf8574_attach_adapter(struct i2c_adapter *adapter); +static int pcf8574_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int pcf8574_detach_client(struct i2c_client *client); + +static void pcf8574_read(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pcf8574_write(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pcf8574_update_client(struct i2c_client *client); +static void pcf8574_init_client(struct i2c_client *client); + +/* This is the driver that will be inserted */ +static struct i2c_driver pcf8574_driver = { + .name = "PCF8574 sensor chip driver", + .id = I2C_DRIVERID_PCF8574, + .flags = I2C_DF_NOTIFY, + .attach_adapter = pcf8574_attach_adapter, + .detach_client = pcf8574_detach_client, +}; + + +/* -- SENSORS SYSCTL START -- */ +#define PCF8574_SYSCTL_READ 1000 +#define PCF8574_SYSCTL_WRITE 1001 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected PCF8574. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table pcf8574_dir_table_template[] = { + {PCF8574_SYSCTL_READ, "read", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &pcf8574_read}, + {PCF8574_SYSCTL_WRITE, "write", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &pcf8574_write}, + {0} +}; + +static int pcf8574_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, pcf8574_detect); +} + +/* This function is called by i2c_detect */ +static int pcf8574_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct pcf8574_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("pcf8574.o: pcf8574_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. */ + if (!(data = kmalloc(sizeof(struct pcf8574_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &pcf8574_driver; + new_client->flags = 0; + + /* Now, we would do the remaining detection. But the PCF8574 is plainly + impossible to detect! Stupid chip. */ + + /* Determine the chip type */ + if (kind <= 0) { + if (address >= 0x38 && address <= 0x3f) + kind = pcf8574a; + else + kind = pcf8574; + } + + if (kind == pcf8574a) { + type_name = "pcf8574a"; + client_name = "PCF8574A chip"; + } else { + type_name = "pcf8574"; + client_name = "PCF8574 chip"; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR1; + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, type_name, + pcf8574_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR2; + } + data->sysctl_id = i; + + /* Initialize the PCF8574 chip */ + pcf8574_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int pcf8574_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct pcf8574_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("pcf8574.o: Client deregistration failed, " + "client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +/* Called when we have found a new PCF8574. */ +static void pcf8574_init_client(struct i2c_client *client) +{ + struct pcf8574_data *data = client->data; + data->write = PCF8574_INIT; + i2c_smbus_write_byte(client, data->write); +} + + +static void pcf8574_update_client(struct i2c_client *client) +{ + struct pcf8574_data *data = client->data; + + down(&data->update_lock); + +#ifdef DEBUG + printk("Starting pcf8574 update\n"); +#endif + + data->read = i2c_smbus_read_byte(client); + + up(&data->update_lock); +} + + +void pcf8574_read(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pcf8574_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pcf8574_update_client(client); + results[0] = data->read; + *nrels_mag = 1; + } +} +void pcf8574_write(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pcf8574_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->write; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->write = results[0]; + i2c_smbus_write_byte(client, data->write); + } + } +} + + +static int __init sm_pcf8574_init(void) +{ + printk("pcf8574.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&pcf8574_driver); +} + +static void __exit sm_pcf8574_exit(void) +{ + i2c_del_driver(&pcf8574_driver); +} + + +MODULE_AUTHOR("Frodo Looijaard , " + "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 Sun Feb 26 11:18:41 2006 @@ -0,0 +1,444 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(pcf8591); + +/* The PCF8591 control byte */ +/* 7 6 5 4 3 2 1 0 */ +/* | 0 |AOEF| AIP | 0 |AINC| AICH | */ + +#define PCF8591_CONTROL_BYTE_AOEF 0x40 /* Analog Output Enable Flag */ + /* (analog output active if 1) */ + +#define PCF8591_CONTROL_BYTE_AIP 0x30 /* Analog Input Programming */ + /* 0x00 = four single ended inputs */ + /* 0x10 = three differential inputs */ + /* 0x20 = single ended and differential mixed */ + /* 0x30 = two differential inputs */ + +#define PCF8591_CONTROL_BYTE_AINC 0x04 /* Autoincrement Flag */ + /* (switch on if 1) */ + +#define PCF8591_CONTROL_BYTE_AICH 0x03 /* Analog Output Enable Flag */ + /* 0x00 = channel 0 */ + /* 0x01 = channel 1 */ + /* 0x02 = channel 2 */ + /* 0x03 = channel 3 */ + + +/* Initial values */ +#define PCF8591_INIT_CONTROL_BYTE (PCF8591_CONTROL_BYTE_AOEF | PCF8591_CONTROL_BYTE_AINC) + /* DAC out enabled, four single ended inputs, autoincrement */ + +#define PCF8591_INIT_AOUT 0 /* DAC out = 0 */ + + +/* Conversions. */ +#define REG_TO_SIGNED(reg) (reg & 0x80)?(reg - 256):(reg) + /* Convert signed 8 bit value to signed value */ + + +struct pcf8591_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 control_byte; + u8 ch[4]; + u8 aout; +}; + +static int pcf8591_attach_adapter(struct i2c_adapter *adapter); +static int pcf8591_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int pcf8591_detach_client(struct i2c_client *client); + +static void pcf8591_update_client(struct i2c_client *client); +static void pcf8591_init_client(struct i2c_client *client); + +static void pcf8591_ain_conf(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pcf8591_ain(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pcf8591_aout_enable(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pcf8591_aout(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + + +/* This is the driver that will be inserted */ +static struct i2c_driver pcf8591_driver = { + .name = "PCF8591 sensor chip driver", + .id = I2C_DRIVERID_PCF8591, + .flags = I2C_DF_NOTIFY, + .attach_adapter = pcf8591_attach_adapter, + .detach_client = pcf8591_detach_client, +}; + +/* 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 */ +static int pcf8591_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct pcf8591_data *data; + int err = 0; + + const char *type_name = ""; + const char *client_name = ""; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + (KERN_ERR "pcf8591.o: pcf8591_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE + | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. */ + if (!(data = kmalloc(sizeof(struct pcf8591_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &pcf8591_driver; + new_client->flags = 0; + + /* Now, we would do the remaining detection. But the PCF8591 is plainly + impossible to detect! Stupid chip. */ + + /* Determine the chip type - only one kind supported! */ + if (kind <= 0) + kind = pcf8591; + + type_name = "pcf8591"; + client_name = "PCF8591 chip"; + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the PCF8591 chip */ + pcf8591_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + kfree(data); + ERROR0: + return err; +} + +static int pcf8591_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct pcf8591_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + (KERN_ERR "pcf8591.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + +/* Called when we have found a new PCF8591. */ +static void pcf8591_init_client(struct i2c_client *client) +{ + struct pcf8591_data *data = client->data; + data->control_byte = PCF8591_INIT_CONTROL_BYTE; + data->aout = PCF8591_INIT_AOUT; + + i2c_smbus_write_byte_data(client, data->control_byte, data->aout); +} + +static void pcf8591_update_client(struct i2c_client *client) +{ + struct pcf8591_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk(KERN_DEBUG "Starting pcf8591 update\n"); +#endif + + i2c_smbus_write_byte(client, data->control_byte); + i2c_smbus_read_byte(client); /* The first byte transmitted contains the */ + /* conversion code of the previous read cycled */ + /* FLUSH IT ! */ + + + /* Number of byte to read to signed depend on the analog input mode */ + data->ch[0] = i2c_smbus_read_byte(client); + data->ch[1] = i2c_smbus_read_byte(client); + /* In all case, read at least two values */ + + if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) != 0x30) + data->ch[2] = i2c_smbus_read_byte(client); + /* Read the third value if not in "two differential inputs" mode */ + + if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0x00) + data->ch[3] = i2c_smbus_read_byte(client); + /* Read the fourth value only in "four single ended inputs" mode */ + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. */ +void pcf8591_ain_conf(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pcf8591_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = (data->control_byte & PCF8591_CONTROL_BYTE_AIP) >> 4; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + if (results[0] >= 0 && results[0] <= 3) + { + data->control_byte &= ~PCF8591_CONTROL_BYTE_AIP; + data->control_byte |= (results[0] << 4); + i2c_smbus_write_byte(client, data->control_byte); + data->valid = 0; + } + } + } +} + +void pcf8591_ain(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pcf8591_data *data = client->data; + int nr = ctl_name - PCF8591_SYSCTL_CH0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + pcf8591_update_client(client); + + /* Number of data to show and conversion to signed depend on */ + /* the analog input mode */ + + switch(nr) { + case 0: + if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) + | ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2)) + results[0] = data->ch[0]; /* single ended */ + else + results[0] = REG_TO_SIGNED(data->ch[0]);/* differential */ + break; + case 1: + if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) + | ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2)) + results[0] = data->ch[1]; /* single ended */ + else + results[0] = REG_TO_SIGNED(data->ch[1]);/* differential */ + break; + case 2: + if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 3) + results[0] = 0; /* channel not used */ + else if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) + results[0] = data->ch[2]; /* single ended */ + else + results[0] = REG_TO_SIGNED(data->ch[2]);/* differential */ + break; + case 3: + if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0) + | ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2)) + results[0] = data->ch[3]; /* single ended */ + else + results[0] = 0; /* channel not used */ + break; + } + *nrels_mag = 1; + } +} + +void pcf8591_aout_enable(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pcf8591_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = !(!(data->control_byte & PCF8591_CONTROL_BYTE_AOEF)); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + if (results[0]) + data->control_byte |= PCF8591_CONTROL_BYTE_AOEF; + else + data->control_byte &= ~PCF8591_CONTROL_BYTE_AOEF; + + i2c_smbus_write_byte(client, data->control_byte); + } + } +} + +void pcf8591_aout(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct pcf8591_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->aout; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + if (results[0] >= 0 && results[0] <= 255) /* ignore values outside DAC range */ + { + data->aout = results[0]; + i2c_smbus_write_byte_data(client, data->control_byte, data->aout); + } + } + } +} + +static int __init sm_pcf8591_init(void) +{ + printk(KERN_INFO "pcf8591.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&pcf8591_driver); +} + +static void __exit sm_pcf8591_exit(void) +{ + i2c_del_driver(&pcf8591_driver); +} + + + +MODULE_AUTHOR("Aurelien Jarno "); +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 Sun Feb 26 11:18:41 2006 @@ -0,0 +1,723 @@ +/* + 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. +*/ + +/* + SiS southbridge has a LM78-like chip integrated on the same IC. + This driver is a customized copy of lm78.c + + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +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_645, + PCI_DEVICE_ID_SI_730, + PCI_DEVICE_ID_SI_735, + 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, + 0 }; + +/* 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; + 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 DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) + +/* For the SIS5595, we need to keep some data in memory. That + data is pointed to by sis5595_list[NR]->data. The structure itself is + dynamically allocated, at the time when the new sis5595 client is + allocated. */ +struct sis5595_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + char maxins; /* == 3 if temp enabled, otherwise == 4 */ + u8 revision; /* Reg. value */ + + u8 in[5]; /* Register value */ + u8 in_max[5]; /* Register value */ + u8 in_min[5]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 temp; /* Register value */ + u8 temp_over; /* Register value */ + u8 temp_hyst; /* 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) 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 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); + +/* 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 = { + .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 Celsius * 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 __init sis5595_find_sis(int *address) +{ + u16 val; + int *i; + + if (!pci_present()) + return -ENODEV; + + if (!(s_bridge = + pci_find_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503, + NULL))) + return -ENODEV; + + /* Look for imposters */ + for(i = blacklist; *i != 0; i++) { + if (pci_find_device(PCI_VENDOR_ID_SI, *i, NULL)) { + printk("sis5595.o: Error: Looked for SIS5595 but found unsupported device %.4X\n", *i); + return -ENODEV; + } + } + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(s_bridge, SIS5595_BASE_REG, &val)) + return -ENODEV; + + *address = val & ~(SIS5595_EXTENT - 1); + if (*address == 0 && force_addr == 0) { + printk("sis5595.o: base address not set - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + if (force_addr) + *address = force_addr; /* so detect will get called */ + + return 0; +} + +int sis5595_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct sis5595_data *data; + int err = 0; + const char *type_name = "sis5595"; + const char *client_name = "SIS5595 chip"; + char val; + u16 a; + + /* Make sure we are probing the ISA bus!! */ + if (!i2c_is_isa_adapter(adapter)) { + printk + ("sis5595.o: sis5595_detect called for an I2C bus adapter?!?\n"); + return 0; + } + + if(force_addr) + address = force_addr & ~(SIS5595_EXTENT - 1); + if (check_region(address, SIS5595_EXTENT)) { + printk("sis5595.o: region 0x%x already in use!\n", address); + return -ENODEV; + } + if(force_addr) { + printk("sis5595.o: forcing ISA address 0x%04X\n", address); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(s_bridge, SIS5595_BASE_REG, address)) + return -ENODEV; + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(s_bridge, SIS5595_BASE_REG, &a)) + return -ENODEV; + if ((a & ~(SIS5595_EXTENT - 1)) != address) { + /* doesn't work for some chips? */ + printk("sis5595.o: force address failed\n"); + return -ENODEV; + } + } + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_byte(s_bridge, SIS5595_ENABLE_REG, &val)) + return -ENODEV; + if((val & 0x80) == 0) { + printk("sis5595.o: enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_byte(s_bridge, SIS5595_ENABLE_REG, + val | 0x80)) + return -ENODEV; + if (PCIBIOS_SUCCESSFUL != + pci_read_config_byte(s_bridge, SIS5595_ENABLE_REG, &val)) + return -ENODEV; + if((val & 0x80) == 0) { /* doesn't work for some chips! */ + printk("sis5595.o: sensors enable failed - not supported?\n"); + return -ENODEV; + } + } + + if (!(data = kmalloc(sizeof(struct sis5595_data), GFP_KERNEL))) { + return -ENOMEM; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &sis5595_driver; + new_client->flags = 0; + + /* Reserve the ISA region */ + request_region(address, SIS5595_EXTENT, type_name); + + /* Check revision and pin registers to determine whether 4 or 5 voltages */ + pci_read_config_byte(s_bridge, SIS5595_REVISION_REG, &(data->revision)); + /* 4 voltages, 1 temp */ + data->maxins = 3; + if (data->revision >= REV2MIN) { + pci_read_config_byte(s_bridge, SIS5595_PIN_REG, &val); + if (!(val & 0x80)) + /* 5 voltages, no temp */ + data->maxins = 4; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the SIS5595 chip */ + sis5595_init_client(new_client); + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + release_region(address, SIS5595_EXTENT); + kfree(data); + return err; +} + +static int sis5595_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct sis5595_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("sis5595.o: Client deregistration failed, client not detached.\n"); + return err; + } + + release_region(client->addr, SIS5595_EXTENT); + kfree(client->data); + + return 0; +} + + +/* ISA access must be locked explicitly. + There are some ugly typecasts here, but the good news is - they should + nowhere else be necessary! */ +static int sis5595_read_value(struct i2c_client *client, u8 reg) +{ + int res; + + down(&(((struct sis5595_data *) (client->data))->lock)); + outb_p(reg, client->addr + SIS5595_ADDR_REG_OFFSET); + res = inb_p(client->addr + SIS5595_DATA_REG_OFFSET); + up(&(((struct sis5595_data *) (client->data))->lock)); + return res; +} + +static int sis5595_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + down(&(((struct sis5595_data *) (client->data))->lock)); + outb_p(reg, client->addr + SIS5595_ADDR_REG_OFFSET); + outb_p(value, client->addr + SIS5595_DATA_REG_OFFSET); + up(&(((struct sis5595_data *) (client->data))->lock)); + return 0; +} + +/* Called when we have found a new SIS5595. */ +static void sis5595_init_client(struct i2c_client *client) +{ + u8 reg; + + /* Start monitoring */ + reg = i2c_smbus_read_byte_data(client, SIS5595_REG_CONFIG); + sis5595_write_value(client, SIS5595_REG_CONFIG, (reg|0x01)&0x7F); +} + +static void sis5595_update_client(struct i2c_client *client) +{ + struct sis5595_data *data = client->data; + int i; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + + for (i = 0; i <= data->maxins; i++) { + data->in[i] = + sis5595_read_value(client, SIS5595_REG_IN(i)); + data->in_min[i] = + sis5595_read_value(client, + SIS5595_REG_IN_MIN(i)); + data->in_max[i] = + sis5595_read_value(client, + SIS5595_REG_IN_MAX(i)); + } + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = + sis5595_read_value(client, SIS5595_REG_FAN(i)); + data->fan_min[i - 1] = + sis5595_read_value(client, + SIS5595_REG_FAN_MIN(i)); + } + if(data->maxins == 3) { + data->temp = + sis5595_read_value(client, SIS5595_REG_TEMP); + data->temp_over = + sis5595_read_value(client, SIS5595_REG_TEMP_OVER); + data->temp_hyst = + sis5595_read_value(client, SIS5595_REG_TEMP_HYST); + } + i = sis5595_read_value(client, SIS5595_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = + sis5595_read_value(client, SIS5595_REG_ALARM1) | + (sis5595_read_value(client, SIS5595_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ + +/* Return 0 for in4 and disallow writes if pin used for temp */ +void sis5595_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct sis5595_data *data = client->data; + int nr = ctl_name - SIS5595_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + if(nr <= 3 || data->maxins == 4) { + sis5595_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + } else { + results[0] = 0; + results[1] = 0; + results[2] = 0; + } + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if(nr <= 3 || data->maxins == 4) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + sis5595_write_value(client, + SIS5595_REG_IN_MIN(nr), data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + sis5595_write_value(client, + SIS5595_REG_IN_MAX(nr), data->in_max[nr]); + } + } + } +} + +void sis5595_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct sis5595_data *data = client->data; + int nr = ctl_name - SIS5595_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + sis5595_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = FAN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr-1])); + sis5595_write_value(client, + SIS5595_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + + +/* Return 0 for temp and disallow writes if pin used for in4 */ +void sis5595_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct sis5595_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + if(data->maxins == 3) { + sis5595_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + } else { + results[0] = 0; + results[1] = 0; + results[2] = 0; + } + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if(data->maxins == 3) { + if (*nrels_mag >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + sis5595_write_value(client, + SIS5595_REG_TEMP_OVER, data->temp_over); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + sis5595_write_value(client, + SIS5595_REG_TEMP_HYST, data->temp_hyst); + } + } + } +} + +void sis5595_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct sis5595_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + sis5595_update_client(client); + results[0] = 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 Sun Feb 26 11:18:41 2006 @@ -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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +static int force_addr = 0; +MODULE_PARM(force_addr, "i"); +MODULE_PARM_DESC(force_addr, + "Initialize the base address of the sensors"); + +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +SENSORS_INSMOD_1(smsc47m1); + +/* modified from kernel/include/traps.c */ +#define REG 0x2e /* The register to read/write */ +#define DEV 0x07 /* Register: Logical device select */ +#define VAL 0x2f /* The value to read/write */ +#define PME 0x0a /* The device with the fan registers in it */ +#define DEVID 0x20 /* Register: Device ID */ + +static inline void +superio_outb(int reg, int val) +{ + outb(reg, REG); + outb(val, VAL); +} + +static inline int +superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +static inline void +superio_select(void) +{ + outb(DEV, REG); + outb(PME, VAL); +} + +static inline void +superio_enter(void) +{ + outb(0x55, REG); +} + +static inline void +superio_exit(void) +{ + outb(0xAA, REG); +} + +/* + * SMSC LPC47M10x (device id 0x59), LPC47M14x (device id 0x5F) and + * LPC47B27x (device id 0x51) have fan control. + * The 47M15x and 47M192 chips "with hardware monitoring block" + * can do much more besides (device id 0x60). + * The LPC47M997 is undocumented, but seems to be compatible with + * the LPC47M192, and has the same device id. + */ +#define SMSC_DEVID_MATCH(id) ((id) == 0x51 || (id) == 0x59 || (id) == 0x5F || (id) == 0x60) + +#define SMSC_ACT_REG 0x30 +#define SMSC_BASE_REG 0x60 + +#define SMSC_EXTENT 0x80 + +#define SMSC47M1_REG_ALARM1 0x04 +#define SMSC47M1_REG_TPIN2 0x33 +#define SMSC47M1_REG_TPIN1 0x34 +#define SMSC47M1_REG_PPIN(nr) (0x37 - (nr)) +#define SMSC47M1_REG_PWM(nr) (0x55 + (nr)) +#define SMSC47M1_REG_FANDIV 0x58 +#define SMSC47M1_REG_FAN(nr) (0x58 + (nr)) +#define SMSC47M1_REG_FAN_MIN(nr) (0x5a + (nr)) + +static inline u8 MIN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 0; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT(192 - ((983040 + rpm * div / 2) / (rpm * div)), + 0, 191); +} + +#define MIN_FROM_REG(val,div) ((val)>=192?0: \ + 983040/((192-(val))*(div))) +#define FAN_FROM_REG(val,div,preload) ((val)==0?-1:(val)==255?0: \ + 983040/(((val)-preload)*(div))) +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) +/* reg is 6 middle bits; /proc is 8 bits */ +#define PWM_FROM_REG(val) (((val) << 1) & 0xfc) +#define PWM_TO_REG(val) (((SENSORS_LIMIT((val), 0, 255)) >> 1) & 0x7e) + +struct smsc47m1_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u8 alarms; /* Register encoding */ + u8 pwm[2]; /* Register value (bit 0 is disable) */ +}; + + +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 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 struct i2c_driver smsc47m1_driver = { + .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 __init smsc47m1_find(int *address) +{ + u16 val; + + superio_enter(); + val= superio_inb(DEVID); + if (!SMSC_DEVID_MATCH(val)) { + superio_exit(); + return -ENODEV; + } + + superio_select(); + val = (superio_inb(SMSC_BASE_REG) << 8) | + superio_inb(SMSC_BASE_REG + 1); + *address = val & ~(SMSC_EXTENT - 1); + if (*address == 0 && force_addr == 0) { + printk("smsc47m1.o: base address not set - use force_addr=0xaddr\n"); + superio_exit(); + return -ENODEV; + } + if (force_addr) + *address = force_addr; /* so detect will get called */ + + superio_exit(); + return 0; +} + +int smsc47m1_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct smsc47m1_data *data; + int err = 0; + const char *type_name = "smsc47m1"; + const char *client_name = "47M1xx chip"; + + if (!i2c_is_isa_adapter(adapter)) { + return 0; + } + + if(force_addr) + address = force_addr & ~(SMSC_EXTENT - 1); + if (check_region(address, SMSC_EXTENT)) { + printk("smsc47m1.o: region 0x%x already in use!\n", address); + return -ENODEV; + } + if(force_addr) { + printk("smsc47m1.o: forcing ISA address 0x%04X\n", address); + superio_enter(); + superio_select(); + superio_outb(SMSC_BASE_REG, address >> 8); + superio_outb(SMSC_BASE_REG+1, address & 0xff); + superio_exit(); + } + + if (!(data = kmalloc(sizeof(struct smsc47m1_data), GFP_KERNEL))) { + return -ENOMEM; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &smsc47m1_driver; + new_client->flags = 0; + + request_region(address, SMSC_EXTENT, "smsc47m1-fans"); + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + smsc47m1_init_client(new_client); + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + release_region(address, SMSC_EXTENT); + kfree(data); + return err; +} + +static int smsc47m1_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct smsc47m1_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("smsc47m1.o: Client deregistration failed, client not detached.\n"); + return err; + } + + release_region(client->addr, SMSC_EXTENT); + kfree(client->data); + + return 0; +} + +static int smsc47m1_read_value(struct i2c_client *client, u8 reg) +{ + int res; + + down(&(((struct smsc47m1_data *) (client->data))->lock)); + res = inb_p(client->addr + reg); + up(&(((struct smsc47m1_data *) (client->data))->lock)); + return res; +} + +static int smsc47m1_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + down(&(((struct smsc47m1_data *) (client->data))->lock)); + outb_p(value, client->addr + reg); + up(&(((struct smsc47m1_data *) (client->data))->lock)); + return 0; +} + +static void smsc47m1_init_client(struct i2c_client *client) +{ + /* configure pins for tach function */ + smsc47m1_write_value(client, SMSC47M1_REG_TPIN1, 0x05); + smsc47m1_write_value(client, SMSC47M1_REG_TPIN2, 0x05); +} + +static void smsc47m1_update_client(struct i2c_client *client) +{ + struct smsc47m1_data *data = client->data; + int i; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = + smsc47m1_read_value(client, SMSC47M1_REG_FAN(i)); + data->fan_min[i - 1] = + smsc47m1_read_value(client, SMSC47M1_REG_FAN_MIN(i)); + data->pwm[i - 1] = + smsc47m1_read_value(client, SMSC47M1_REG_PWM(i)); + } + + i = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = + smsc47m1_read_value(client, SMSC47M1_REG_ALARM1) >> 6; + if(data->alarms) + smsc47m1_write_value(client, SMSC47M1_REG_ALARM1, 0xc0); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void smsc47m1_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smsc47m1_data *data = client->data; + int nr = ctl_name - SMSC47M1_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smsc47m1_update_client(client); + results[0] = MIN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1]), + data->fan_min[nr - 1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = MIN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr-1])); + smsc47m1_write_value(client, SMSC47M1_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + + +void smsc47m1_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smsc47m1_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smsc47m1_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +void smsc47m1_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct smsc47m1_data *data = client->data; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smsc47m1_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0x3f) | (data->fan_div[1] << 6); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xcf) | (data->fan_div[0] << 4); + smsc47m1_write_value(client, SMSC47M1_REG_FANDIV, old); + } + } +} + +void smsc47m1_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smsc47m1_data *data = client->data; + int nr = 1 + ctl_name - SMSC47M1_SYSCTL_PWM1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smsc47m1_update_client(client); + results[0] = PWM_FROM_REG(data->pwm[nr - 1]); + results[1] = !(data->pwm[nr - 1] & 0x01); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->pwm[nr - 1] = smsc47m1_read_value(client, + SMSC47M1_REG_PWM(nr)) & 0x81; + data->pwm[nr - 1] |= PWM_TO_REG(results[0]); + if (*nrels_mag >= 2) { + if (results[1]) { + /* enable PWM */ + data->pwm[nr - 1] &= 0xfe; + } else { + /* disable PWM */ + data->pwm[nr - 1] |= 0x01; + } + } + smsc47m1_write_value(client, SMSC47M1_REG_PWM(nr), + data->pwm[nr - 1]); + } + } +} + +static int __init sm_smsc47m1_init(void) +{ + int addr; + + printk("smsc47m1.o version %s (%s)\n", LM_VERSION, LM_DATE); + + if (smsc47m1_find(&addr)) { + printk("smsc47m1.o: SMSC 47M1xx not detected, module not inserted.\n"); + return -ENODEV; + } + normal_isa[0] = addr; + + return i2c_add_driver(&smsc47m1_driver); +} + +static void __exit sm_smsc47m1_exit(void) +{ + i2c_del_driver(&smsc47m1_driver); +} + + + +MODULE_AUTHOR("Mark D. Studebaker "); +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 Sun Feb 26 11:18:41 2006 @@ -0,0 +1,492 @@ +/* + 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 "20060214" +#define LM_VERSION "2.10.0" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2D, 0x2E, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(thmc50); + +/* Many THMC50 constants specified below */ + +/* The THMC50 registers */ +#define THMC50_REG_TEMP 0x27 +#define THMC50_REG_CONF 0x40 +#define THMC50_REG_TEMP_HYST 0x3A +#define THMC50_REG_TEMP_OS 0x39 + +#define THMC50_REG_TEMP_TRIP 0x13 +#define THMC50_REG_TEMP_REMOTE_TRIP 0x14 +#define THMC50_REG_TEMP_DEFAULT_TRIP 0x17 +#define THMC50_REG_TEMP_REMOTE_DEFAULT_TRIP 0x18 +#define THMC50_REG_ANALOG_OUT 0x19 +#define THMC50_REG_REMOTE_TEMP 0x26 +#define THMC50_REG_REMOTE_TEMP_HYST 0x38 +#define THMC50_REG_REMOTE_TEMP_OS 0x37 + +#define THMC50_REG_INTER 0x41 +#define THMC50_REG_INTER_MIRROR 0x4C +#define THMC50_REG_INTER_MASK 0x43 + +#define THMC50_REG_COMPANY_ID 0x3E +#define THMC50_REG_DIE_CODE 0x3F + + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define TEMP_FROM_REG(val) ((val>127)?val - 0x0100:val) +#define TEMP_TO_REG(val) ((val<0)?0x0100+val:val) + +/* Each client has this additional data */ +struct thmc50_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 temp, temp_os, temp_hyst, + remote_temp, remote_temp_os, remote_temp_hyst, + inter, inter_mask, die_code, analog_out; /* Register values */ +}; + +static int thmc50_attach_adapter(struct i2c_adapter *adapter); +static int thmc50_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void thmc50_init_client(struct i2c_client *client); +static int thmc50_detach_client(struct i2c_client *client); + +static int thmc50_read_value(struct i2c_client *client, u8 reg); +static int thmc50_write_value(struct i2c_client *client, u8 reg, + u16 value); +static void thmc50_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void thmc50_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, + long *results); +static void thmc50_inter(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void thmc50_inter_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void thmc50_die_code(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void thmc50_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void thmc50_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver thmc50_driver = { + .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 Celsius */ +#define THMC50_SYSCTL_REMOTE_TEMP 1201 /* Degrees Celsius */ +#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_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, thmc50_detect); +} + +/* This function is called by i2c_detect */ +int thmc50_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int company, i; + struct i2c_client *new_client; + struct thmc50_data *data; + int err = 0; + const char *type_name, *client_name; + +#ifdef DEBUG + printk("thmc50.o: Probing for THMC50 at 0x%2X on bus %d\n", + address, adapter->id); +#endif + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("thmc50.o: thmc50_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access thmc50_{read,write}_value. */ + if (!(data = kmalloc(sizeof(struct thmc50_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &thmc50_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + company = + i2c_smbus_read_byte_data(new_client, THMC50_REG_COMPANY_ID); + + if (company != 0x49) { +#ifdef DEBUG + printk + ("thmc50.o: Detect of THMC50 failed (reg 3E: 0x%X)\n", + company); +#endif + goto ERROR1; + } + + /* Determine the chip type - only one kind supported! */ + kind = thmc50; + + if (kind == thmc50) { + type_name = "thmc50"; + client_name = "THMC50 chip"; + } else { +#ifdef DEBUG + printk("thmc50.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + thmc50_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int thmc50_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct thmc50_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("thmc50.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* All registers are word-sized, except for the configuration register. + THMC50 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int thmc50_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* All registers are word-sized, except for the configuration register. + THMC50 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +static int thmc50_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void thmc50_init_client(struct i2c_client *client) +{ + thmc50_write_value(client, THMC50_REG_CONF, 1); +} + +static void thmc50_update_client(struct i2c_client *client) +{ + struct thmc50_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting thmc50 update\n"); +#endif + + data->temp = thmc50_read_value(client, THMC50_REG_TEMP); + data->temp_os = + thmc50_read_value(client, THMC50_REG_TEMP_OS); + data->temp_hyst = + thmc50_read_value(client, THMC50_REG_TEMP_HYST); + data->remote_temp = + thmc50_read_value(client, THMC50_REG_REMOTE_TEMP); + data->remote_temp_os = + thmc50_read_value(client, THMC50_REG_REMOTE_TEMP_OS); + data->remote_temp_hyst = + thmc50_read_value(client, THMC50_REG_REMOTE_TEMP_HYST); + data->inter = thmc50_read_value(client, THMC50_REG_INTER); + data->inter_mask = + thmc50_read_value(client, THMC50_REG_INTER_MASK); + data->die_code = + thmc50_read_value(client, THMC50_REG_DIE_CODE); + data->analog_out = + thmc50_read_value(client, THMC50_REG_ANALOG_OUT); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void thmc50_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_os); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_os = TEMP_TO_REG(results[0]); + thmc50_write_value(client, THMC50_REG_TEMP_OS, + data->temp_os); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + thmc50_write_value(client, THMC50_REG_TEMP_HYST, + data->temp_hyst); + } + } +} + + +void thmc50_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = TEMP_FROM_REG(data->remote_temp_os); + results[1] = TEMP_FROM_REG(data->remote_temp_hyst); + results[2] = TEMP_FROM_REG(data->remote_temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->remote_temp_os = TEMP_TO_REG(results[0]); + thmc50_write_value(client, + THMC50_REG_REMOTE_TEMP_OS, + data->remote_temp_os); + } + if (*nrels_mag >= 2) { + data->remote_temp_hyst = TEMP_TO_REG(results[1]); + thmc50_write_value(client, + THMC50_REG_REMOTE_TEMP_HYST, + data->remote_temp_hyst); + } + } +} + + +void thmc50_inter(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = data->inter; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + printk("thmc50.o: No writes to Interrupt register!\n"); + } +} + + +void thmc50_inter_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = data->inter_mask; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->inter_mask = results[0]; + thmc50_write_value(client, THMC50_REG_INTER_MASK, + data->inter_mask); + } + } +} + + +void thmc50_die_code(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = data->die_code; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + printk("thmc50.o: No writes to Die-Code register!\n"); + } +} + + +void thmc50_analog_out(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = data->analog_out; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->analog_out = results[0]; + thmc50_write_value(client, THMC50_REG_ANALOG_OUT, + data->analog_out); + } + } +} + + + + +static int __init sm_thmc50_init(void) +{ + printk("thmc50.o version %s (%s)\n", LM_VERSION, LM_DATE); + + return i2c_add_driver(&thmc50_driver); +} + +static void __exit sm_thmc50_exit(void) +{ + i2c_del_driver(&thmc50_driver); +} + + + +MODULE_AUTHOR + ("Frodo Looijaard 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 Sun Feb 26 11:18:41 2006 @@ -0,0 +1,841 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + + +/* If force_addr is set to anything different from 0, we forcibly enable + the device at the given address. */ +static int force_addr = 0; +MODULE_PARM(force_addr, "i"); +MODULE_PARM_DESC(force_addr, + "Initialize the base address of the sensors"); + +/* Addresses to scan. + Note that we can't determine the ISA address until we have initialized + our module */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0x0000, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(via686a); + +/* + The Via 686a southbridge has a LM78-like chip integrated on the same IC. + This driver is a customized copy of lm78.c +*/ + +/* Many VIA686A constants specified below */ + +/* Length of ISA address segment */ +#define VIA686A_EXTENT 0x80 +#define VIA686A_BASE_REG 0x70 +#define VIA686A_ENABLE_REG 0x74 + +/* The VIA686A registers */ +/* ins numbered 0-4 */ +#define VIA686A_REG_IN_MAX(nr) (0x2b + ((nr) * 2)) +#define VIA686A_REG_IN_MIN(nr) (0x2c + ((nr) * 2)) +#define VIA686A_REG_IN(nr) (0x22 + (nr)) + +/* fans numbered 1-2 */ +#define VIA686A_REG_FAN_MIN(nr) (0x3a + (nr)) +#define VIA686A_REG_FAN(nr) (0x28 + (nr)) + +// the following values are as speced by VIA: +static const u8 regtemp[] = { 0x20, 0x21, 0x1f }; +static const u8 regover[] = { 0x39, 0x3d, 0x1d }; +static const u8 reghyst[] = { 0x3a, 0x3e, 0x1e }; + +/* temps numbered 1-3 */ +#define VIA686A_REG_TEMP(nr) (regtemp[(nr) - 1]) +#define VIA686A_REG_TEMP_OVER(nr) (regover[(nr) - 1]) +#define VIA686A_REG_TEMP_HYST(nr) (reghyst[(nr) - 1]) +#define VIA686A_REG_TEMP_LOW1 0x4b // bits 7-6 +#define VIA686A_REG_TEMP_LOW23 0x49 // 2 = bits 5-4, 3 = bits 7-6 + +#define VIA686A_REG_ALARM1 0x41 +#define VIA686A_REG_ALARM2 0x42 +#define VIA686A_REG_FANDIV 0x47 +#define VIA686A_REG_CONFIG 0x40 +// The following register sets temp interrupt mode (bits 1-0 for temp1, +// 3-2 for temp2, 5-4 for temp3). Modes are: +// 00 interrupt stays as long as value is out-of-range +// 01 interrupt is cleared once register is read (default) +// 10 comparator mode- like 00, but ignores hysteresis +// 11 same as 00 +#define VIA686A_REG_TEMP_MODE 0x4b +// We'll just assume that you want to set all 3 simultaneously: +#define VIA686A_TEMP_MODE_MASK 0x3F +#define VIA686A_TEMP_MODE_CONTINUOUS (0x00) + +/* Conversions. Limit checking is only done on the TO_REG + variants. */ + +/********* VOLTAGE CONVERSIONS (Bob Dougherty) ********/ +// From HWMon.cpp (Copyright 1998-2000 Jonathan Teh Soon Yew): +// voltagefactor[0]=1.25/2628; (2628/1.25=2102.4) // Vccp +// voltagefactor[1]=1.25/2628; (2628/1.25=2102.4) // +2.5V +// voltagefactor[2]=1.67/2628; (2628/1.67=1573.7) // +3.3V +// voltagefactor[3]=2.6/2628; (2628/2.60=1010.8) // +5V +// voltagefactor[4]=6.3/2628; (2628/6.30=417.14) // +12V +// in[i]=(data[i+2]*25.0+133)*voltagefactor[i]; +// That is: +// volts = (25*regVal+133)*factor +// regVal = (volts/factor-133)/25 +// (These conversions were contributed by Jonathan Teh Soon Yew +// ) +static inline u8 IN_TO_REG(long val, int inNum) +{ + /* To avoid floating point, we multiply constants by 10 (100 for +12V). + Rounding is done (120500 is actually 133000 - 12500). + Remember that val is expressed in 0.01V/bit, which is why we divide + by an additional 1000 (10000 for +12V): 100 for val and 10 (100) + for the constants. */ + if (inNum <= 1) + return (u8) + SENSORS_LIMIT((val * 21024 - 120500) / 25000, 0, 255); + else if (inNum == 2) + return (u8) + SENSORS_LIMIT((val * 15737 - 120500) / 25000, 0, 255); + else if (inNum == 3) + return (u8) + SENSORS_LIMIT((val * 10108 - 120500) / 25000, 0, 255); + else + return (u8) + SENSORS_LIMIT((val * 41714 - 1205000) / 250000, 0, 255); +} + +static inline long IN_FROM_REG(u8 val, int inNum) +{ + /* To avoid floating point, we multiply constants by 10 (100 for +12V). + We also multiply them by 100 because we want 0.01V/bit for the + output value. Rounding is done. */ + if (inNum <= 1) + return (long) ((25000 * val + 133000 + 21024 / 2) / 21024); + else if (inNum == 2) + return (long) ((25000 * val + 133000 + 15737 / 2) / 15737); + else if (inNum == 3) + return (long) ((25000 * val + 133000 + 10108 / 2) / 10108); + else + return (long) ((250000 * val + 1330000 + 41714 / 2) / 41714); +} + +/********* FAN RPM CONVERSIONS ********/ +// Higher register values = slower fans (the fan's strobe gates a counter). +// But this chip saturates back at 0, not at 255 like all the other chips. +// So, 0 means 0 RPM +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 0; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, 255); +} + +#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1350000/((val)*(div))) + +/******** TEMP CONVERSIONS (Bob Dougherty) *********/ +// linear fits from HWMon.cpp (Copyright 1998-2000 Jonathan Teh Soon Yew) +// if(temp<169) +// return double(temp)*0.427-32.08; +// else if(temp>=169 && temp<=202) +// return double(temp)*0.582-58.16; +// else +// return double(temp)*0.924-127.33; +// +// A fifth-order polynomial fits the unofficial data (provided by Alex van +// Kaam ) 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 provide 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 s16 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. + The +50 is because the temps start at -50 */ +static inline u8 TEMP_TO_REG(long val) +{ + return viaLUT[val <= -500 ? 0 : val >= 1100 ? 160 : + (val < 0 ? val - 5 : val + 5) / 10 + 50]; +} + +/* for 8-bit temperature hyst and over registers */ +#define TEMP_FROM_REG(val) (tempLUT[(val)]) + +/* for 10-bit temperature readings */ +// You might _think_ this is too long to inline, but's it's really only +// called once... +static inline long TEMP_FROM_REG10(u16 val) +{ + // the temp values are already *10, so we don't need to do that. + long temp; + u16 eightBits = val >> 2; + u16 twoBits = val & 3; + + /* no interpolation for these */ + if (twoBits == 0 || eightBits == 255) + return (long) tempLUT[eightBits]; + + /* do some linear interpolation */ + temp = (4 - twoBits) * tempLUT[eightBits] + + twoBits * tempLUT[eightBits + 1]; + /* achieve rounding */ + return (temp < 0 ? temp - 2 : temp + 2) / 4; +} + +#define ALARMS_FROM_REG(val) (val) + +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) + +/* For the VIA686A, we need to keep some data in memory. + The structure is dynamically allocated, at the same time when a new + via686a client is allocated. */ +struct via686a_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[5]; /* Register value */ + u8 in_max[5]; /* Register value */ + u8 in_min[5]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u16 temp[3]; /* Register value 10 bit */ + u8 temp_over[3]; /* Register value */ + u8 temp_hyst[3]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 alarms; /* Register encoding, combined */ +}; + +static struct pci_dev *s_bridge; /* pointer to the (only) via686a */ + +static int via686a_attach_adapter(struct i2c_adapter *adapter); +static int via686a_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int via686a_detach_client(struct i2c_client *client); + +static int via686a_read_value(struct i2c_client *client, u8 register); +static void via686a_write_value(struct i2c_client *client, u8 register, + u8 value); +static void via686a_update_client(struct i2c_client *client); +static void via686a_init_client(struct i2c_client *client); + + +static void via686a_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void via686a_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void via686a_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void via686a_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void via686a_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +/* 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 = { + .name = "VIA 686A", + .id = I2C_DRIVERID_VIA686A, + .flags = I2C_DF_NOTIFY, + .attach_adapter = via686a_attach_adapter, + .detach_client = via686a_detach_client, +}; + + + +/* The /proc/sys entries */ + +/* -- SENSORS SYSCTL START -- */ +#define VIA686A_SYSCTL_IN0 1000 +#define VIA686A_SYSCTL_IN1 1001 +#define VIA686A_SYSCTL_IN2 1002 +#define VIA686A_SYSCTL_IN3 1003 +#define VIA686A_SYSCTL_IN4 1004 +#define VIA686A_SYSCTL_FAN1 1101 +#define VIA686A_SYSCTL_FAN2 1102 +#define VIA686A_SYSCTL_TEMP 1200 +#define VIA686A_SYSCTL_TEMP2 1201 +#define VIA686A_SYSCTL_TEMP3 1202 +#define VIA686A_SYSCTL_FAN_DIV 2000 +#define VIA686A_SYSCTL_ALARMS 2001 + +#define VIA686A_ALARM_IN0 0x01 +#define VIA686A_ALARM_IN1 0x02 +#define VIA686A_ALARM_IN2 0x04 +#define VIA686A_ALARM_IN3 0x08 +#define VIA686A_ALARM_TEMP 0x10 +#define VIA686A_ALARM_FAN1 0x40 +#define VIA686A_ALARM_FAN2 0x80 +#define VIA686A_ALARM_IN4 0x100 +#define VIA686A_ALARM_TEMP2 0x800 +#define VIA686A_ALARM_CHAS 0x1000 +#define VIA686A_ALARM_TEMP3 0x8000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected VIA686A. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table via686a_dir_table_template[] = { + {VIA686A_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_in}, + {VIA686A_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_in}, + {VIA686A_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_in}, + {VIA686A_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_in}, + {VIA686A_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_in}, + {VIA686A_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_fan}, + {VIA686A_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_fan}, + {VIA686A_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &via686a_temp}, + {VIA686A_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_temp}, + {VIA686A_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_temp}, + {VIA686A_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_fan_div}, + {VIA686A_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &via686a_alarms}, + {0} +}; + +static inline int via686a_read_value(struct i2c_client *client, u8 reg) +{ + return (inb_p(client->addr + reg)); +} + +static inline void via686a_write_value(struct i2c_client *client, u8 reg, + u8 value) +{ + outb_p(value, client->addr + reg); +} + +/* This is called when the module is loaded */ +static int via686a_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, via686a_detect); +} + +int via686a_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct via686a_data *data; + int err = 0; + const char *type_name = "via686a"; + u16 val; + + /* Make sure we are probing the ISA bus!! */ + if (!i2c_is_isa_adapter(adapter)) { + printk + ("via686a.o: via686a_detect called for an I2C bus adapter?!?\n"); + return 0; + } + + /* 8231 requires multiple of 256, we enforce that on 686 as well */ + if(force_addr) + address = force_addr & 0xFF00; + if (check_region(address, VIA686A_EXTENT)) { + printk("via686a.o: region 0x%x already in use!\n", + address); + return -ENODEV; + } + + if(force_addr) { + printk("via686a.o: forcing ISA address 0x%04X\n", address); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(s_bridge, VIA686A_BASE_REG, address)) + return -ENODEV; + } + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(s_bridge, VIA686A_ENABLE_REG, &val)) + return -ENODEV; + if (!(val & 0x0001)) { + printk("via686a.o: enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(s_bridge, VIA686A_ENABLE_REG, + val | 0x0001)) + return -ENODEV; + } + + if (!(data = kmalloc(sizeof(struct via686a_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &via686a_driver; + new_client->flags = 0; + + /* Reserve the ISA region */ + request_region(address, VIA686A_EXTENT, "via686a-sensors"); + + /* Fill in the remaining client fields and put into the global list */ + strcpy(new_client->name, "Via 686A Integrated Sensors"); + 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, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + /* Initialize the VIA686A chip */ + via686a_init_client(new_client); + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + release_region(address, VIA686A_EXTENT); + kfree(data); + ERROR0: + return err; +} + +static int via686a_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct via686a_data *) + (client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("via686a.o: Client deregistration failed, client not detached.\n"); + return err; + } + + release_region(client->addr, VIA686A_EXTENT); + kfree(client->data); + + return 0; +} + +/* Called when we have found a new VIA686A. */ +static void via686a_init_client(struct i2c_client *client) +{ + u8 reg; + + /* Start monitoring */ + reg = via686a_read_value(client, VIA686A_REG_CONFIG); + via686a_write_value(client, VIA686A_REG_CONFIG, (reg|0x01)&0x7F); + + /* Configure temp interrupt mode for continuous-interrupt operation */ + via686a_write_value(client, VIA686A_REG_TEMP_MODE, + via686a_read_value(client, VIA686A_REG_TEMP_MODE) & + !(VIA686A_TEMP_MODE_MASK | VIA686A_TEMP_MODE_CONTINUOUS)); +} + +static void via686a_update_client(struct i2c_client *client) +{ + struct via686a_data *data = client->data; + int i; + + down(&data->update_lock); + + if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || + time_before(jiffies, data->last_updated) || !data->valid) { + + for (i = 0; i <= 4; i++) { + data->in[i] = + via686a_read_value(client, VIA686A_REG_IN(i)); + data->in_min[i] = via686a_read_value(client, + VIA686A_REG_IN_MIN + (i)); + data->in_max[i] = + via686a_read_value(client, VIA686A_REG_IN_MAX(i)); + } + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = + via686a_read_value(client, VIA686A_REG_FAN(i)); + data->fan_min[i - 1] = via686a_read_value(client, + VIA686A_REG_FAN_MIN(i)); + } + for (i = 1; i <= 3; i++) { + data->temp[i - 1] = via686a_read_value(client, + VIA686A_REG_TEMP(i)) << 2; + data->temp_over[i - 1] = + via686a_read_value(client, + VIA686A_REG_TEMP_OVER(i)); + data->temp_hyst[i - 1] = + via686a_read_value(client, + VIA686A_REG_TEMP_HYST(i)); + } + /* add in lower 2 bits + temp1 uses bits 7-6 of VIA686A_REG_TEMP_LOW1 + temp2 uses bits 5-4 of VIA686A_REG_TEMP_LOW23 + temp3 uses bits 7-6 of VIA686A_REG_TEMP_LOW23 + */ + data->temp[0] |= (via686a_read_value(client, + VIA686A_REG_TEMP_LOW1) + & 0xc0) >> 6; + data->temp[1] |= + (via686a_read_value(client, VIA686A_REG_TEMP_LOW23) & + 0x30) >> 4; + data->temp[2] |= + (via686a_read_value(client, VIA686A_REG_TEMP_LOW23) & + 0xc0) >> 6; + + i = via686a_read_value(client, VIA686A_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = + via686a_read_value(client, + VIA686A_REG_ALARM1) | + (via686a_read_value(client, VIA686A_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +static void via686a_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct via686a_data *data = client->data; + int nr = ctl_name - VIA686A_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + via686a_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr], nr); + results[1] = IN_FROM_REG(data->in_max[nr], nr); + results[2] = IN_FROM_REG(data->in[nr], nr); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0], nr); + via686a_write_value(client, VIA686A_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1], nr); + via686a_write_value(client, VIA686A_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void via686a_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct via686a_data *data = client->data; + int nr = ctl_name - VIA686A_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + via686a_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div + [nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = FAN_TO_REG(results[0], + DIV_FROM_REG(data-> + fan_div[nr -1])); + via686a_write_value(client, + VIA686A_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + +void via686a_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct via686a_data *data = client->data; + int nr = ctl_name - VIA686A_SYSCTL_TEMP; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + via686a_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over[nr]); + results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); + results[2] = TEMP_FROM_REG10(data->temp[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over[nr] = TEMP_TO_REG(results[0]); + via686a_write_value(client, + VIA686A_REG_TEMP_OVER(nr + 1), + data->temp_over[nr]); + } + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = TEMP_TO_REG(results[1]); + via686a_write_value(client, + VIA686A_REG_TEMP_HYST(nr + 1), + data->temp_hyst[nr]); + } + } +} + +void via686a_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct via686a_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + via686a_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void via686a_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct via686a_data *data = client->data; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + via686a_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = via686a_read_value(client, VIA686A_REG_FANDIV); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0x3f) | (data->fan_div[1] << 6); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xcf) | (data->fan_div[0] << 4); + via686a_write_value(client, VIA686A_REG_FANDIV, + old); + } + } +} + + +static struct pci_device_id via686a_pci_ids[] __devinitdata = { + {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +static int __devinit via686a_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + u16 val; + int addr = 0; + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(dev, VIA686A_BASE_REG, &val)) + return -ENODEV; + + addr = val & ~(VIA686A_EXTENT - 1); + if (addr == 0 && force_addr == 0) { + printk("via686a.o: base address not set - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + if (force_addr) + addr = force_addr; /* so detect will get called */ + + 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 Sun Feb 26 11:18:42 2006 @@ -0,0 +1,818 @@ +/* + 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 +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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 Intel thermal diode + Reading 3 temp1 VT1211 internal thermal diode + UCH1/Reading2 in0 temp2 + UCH2 in1 temp4 + UCH3 in2 temp5 + UCH4 in3 temp6 + UCH5 in4 temp7 + 3.3V in5 + -12V in6 not in vt1211 +*/ + +/* ins numbered 0-6 */ +#define VT1211_REG_IN_MAX(nr) ((nr)==0 ? 0x3d : 0x29 + ((nr) * 2)) +#define VT1211_REG_IN_MIN(nr) ((nr)==0 ? 0x3e : 0x2a + ((nr) * 2)) +#define VT1211_REG_IN(nr) (0x21 + (nr)) + +/* fans numbered 1-2 */ +#define VT1211_REG_FAN_MIN(nr) (0x3a + (nr)) +#define VT1211_REG_FAN(nr) (0x28 + (nr)) + +static const u8 regtemp[] = { 0x20, 0x21, 0x1f, 0x22, 0x23, 0x24, 0x25 }; +static const u8 regover[] = { 0x39, 0x3d, 0x1d, 0x2b, 0x2d, 0x2f, 0x31 }; +static const u8 reghyst[] = { 0x3a, 0x3e, 0x1e, 0x2c, 0x2e, 0x30, 0x32 }; + +/* temps numbered 1-7 */ +#define VT1211_REG_TEMP(nr) (regtemp[(nr) - 1]) +#define VT1211_REG_TEMP_OVER(nr) (regover[(nr) - 1]) +#define VT1211_REG_TEMP_HYST(nr) (reghyst[(nr) - 1]) +#define VT1211_REG_TEMP_LOW3 0x4b /* bits 7-6 */ +#define VT1211_REG_TEMP_LOW2 0x49 /* bits 5-4 */ +#define VT1211_REG_TEMP_LOW47 0x4d + +#define VT1211_REG_CONFIG 0x40 +#define VT1211_REG_ALARM1 0x41 +#define VT1211_REG_ALARM2 0x42 +#define VT1211_REG_VID 0x45 +#define VT1211_REG_FANDIV 0x47 +#define VT1211_REG_UCH_CONFIG 0x4a +#define VT1211_REG_TEMP1_CONFIG 0x4b +#define VT1211_REG_TEMP2_CONFIG 0x4c + +/* temps 1-7; voltages 0-6 */ +#define ISTEMP(i, ch_config) ((i) == 1 ? 1 : \ + (i) == 3 ? 1 : \ + (i) == 2 ? ((ch_config) >> 1) & 0x01 : \ + ((ch_config) >> ((i)-1)) & 0x01) +#define ISVOLT(i, ch_config) ((i) > 4 ? 1 : !(((ch_config) >> ((i)+2)) & 0x01)) + +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1) +#define PWM_FROM_REG(val) (val) +#define PWM_TO_REG(val) SENSORS_LIMIT((val), 0, 255) + +#define TEMP_FROM_REG(val) ((val)*10) +#define TEMP_FROM_REG10(val) (((val)*10)/4) +#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ + ((val)+5)/10),0,255)) +#define IN_FROM_REG(val) /*(((val)*10+5)/10)*/ (val) +#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 5)/10),0,255)) + + +/********* FAN RPM CONVERSIONS ********/ +/* But this chip saturates back at 0, not at 255 like all the other chips. + So, 0 means 0 RPM */ +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 0; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1310720 + rpm * div / 2) / (rpm * div), 1, 255); +} + +#define MIN_TO_REG(a,b) FAN_TO_REG(a,b) +#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1310720/((val)*(div))) + +struct vt1211_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u16 temp[7]; /* Register value 10 bit */ + u8 temp_over[7]; /* Register value */ + u8 temp_hyst[7]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 alarms; /* Register encoding */ + u8 pwm[2]; /* Register value */ + u8 pwm_ctl; /* Register value */ + u8 vid; /* Register encoding */ + u8 vrm; + u8 uch_config; +}; + +static int vt1211_attach_adapter(struct i2c_adapter *adapter); +static int vt1211_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int vt1211_detach_client(struct i2c_client *client); + +static inline int vt_rdval(struct i2c_client *client, u8 register); +static inline void vt1211_write_value(struct i2c_client *client, u8 register, + u8 value); +static void vt1211_update_client(struct i2c_client *client); +static void vt1211_init_client(struct i2c_client *client); + + +static 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 struct i2c_driver vt1211_driver = { + .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_TEMP1 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_TEMP1 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_TEMP2 +#define VT1211_ALARM_TEMP4 VT1211_ALARM_IN1 +#define VT1211_ALARM_TEMP5 VT1211_ALARM_IN2 +#define VT1211_ALARM_TEMP6 VT1211_ALARM_IN3 +#define VT1211_ALARM_TEMP7 VT1211_ALARM_IN4 + +/* -- SENSORS SYSCTL END -- */ + +static ctl_table vt1211_dir_table_template[] = { + {VT1211_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, + {VT1211_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, + {VT1211_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, + {VT1211_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, + {VT1211_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, + {VT1211_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, +/* + datasheet says these are reserved + {VT1211_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt1211_in}, +*/ + {VT1211_SYSCTL_TEMP1, "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 __init vt1211_find(int *address) +{ + u16 val; + + superio_enter(); + val= superio_inb(DEVID); + if(VT1211_DEVID != val) { + superio_exit(); + return -ENODEV; + } + + superio_select(); + val = (superio_inb(VT1211_BASE_REG) << 8) | + superio_inb(VT1211_BASE_REG + 1); + *address = val & ~(VT1211_EXTENT - 1); + if (*address == 0 && force_addr == 0) { + printk("vt1211.o: base address not set - use force_addr=0xaddr\n"); + superio_exit(); + return -ENODEV; + } + if (force_addr) + *address = force_addr; /* so detect will get called */ + + superio_exit(); + return 0; +} + +int vt1211_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct vt1211_data *data; + int err = 0; + u8 val; + const char *type_name = "vt1211"; + const char *client_name = "VT1211 chip"; + + if (!i2c_is_isa_adapter(adapter)) { + return 0; + } + + if(force_addr) + address = force_addr & ~(VT1211_EXTENT - 1); + if (check_region(address, VT1211_EXTENT)) { + printk("vt1211.o: region 0x%x already in use!\n", address); + return -ENODEV; + } + if(force_addr) { + printk("vt1211.o: forcing ISA address 0x%04X\n", address); + superio_enter(); + superio_select(); + superio_outb(VT1211_BASE_REG, address >> 8); + superio_outb(VT1211_BASE_REG+1, address & 0xff); + superio_exit(); + } + + superio_enter(); + superio_select(); + if((val = 0x01 & superio_inb(VT1211_ACT_REG)) == 0) + superio_outb(VT1211_ACT_REG, 1); + superio_exit(); + + if (!(data = kmalloc(sizeof(struct vt1211_data), GFP_KERNEL))) { + return -ENOMEM; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &vt1211_driver; + new_client->flags = 0; + + request_region(address, VT1211_EXTENT, "vt1211-sensors"); + strcpy(new_client->name, client_name); + data->valid = 0; + init_MUTEX(&data->update_lock); + + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + if ((i = i2c_register_entry((struct i2c_client *) new_client, + type_name, + vt1211_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + vt1211_init_client(new_client); + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + release_region(address, VT1211_EXTENT); + kfree(data); + return err; +} + +static int vt1211_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct vt1211_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("vt1211.o: Client deregistration failed, client not detached.\n"); + return err; + } + + release_region(client->addr, VT1211_EXTENT); + kfree(client->data); + + return 0; +} + +static inline int vt_rdval(struct i2c_client *client, u8 reg) +{ + return (inb_p(client->addr + reg)); +} + +static inline void vt1211_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + outb_p(value, client->addr + reg); +} + +static void vt1211_init_client(struct i2c_client *client) +{ + struct vt1211_data *data = client->data; + + data->vrm = 91; + /* set "default" interrupt mode for alarms, which isn't the default */ + vt1211_write_value(client, VT1211_REG_TEMP1_CONFIG, 0); + vt1211_write_value(client, VT1211_REG_TEMP2_CONFIG, 0); +} + +static void vt1211_update_client(struct i2c_client *client) +{ + struct vt1211_data *data = client->data; + int i, j; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + data->uch_config = vt_rdval(client, VT1211_REG_UCH_CONFIG); + for (i = 0; i <= 5; i++) { + if(ISVOLT(i, data->uch_config)) { + data->in[i] = vt_rdval(client, VT1211_REG_IN(i)); + data->in_min[i] = vt_rdval(client, + VT1211_REG_IN_MIN(i)); + data->in_max[i] = vt_rdval(client, + VT1211_REG_IN_MAX(i)); + } else { + data->in[i] = 0; + data->in_min[i] = 0; + data->in_max[i] = 0; + } + } + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = vt_rdval(client, VT1211_REG_FAN(i)); + data->fan_min[i - 1] = vt_rdval(client, + VT1211_REG_FAN_MIN(i)); + } + for (i = 1; i <= 7; i++) { + if(ISTEMP(i, data->uch_config)) { + data->temp[i - 1] = vt_rdval(client, + VT1211_REG_TEMP(i)) << 2; + switch(i) { + case 1: + /* ? */ + j = 0; + break; + case 2: + j = (vt_rdval(client, + VT1211_REG_TEMP_LOW2) & + 0x30) >> 4; + break; + case 3: + j = (vt_rdval(client, + VT1211_REG_TEMP_LOW3) & + 0xc0) >> 6; + break; + case 4: + case 5: + case 6: + case 7: + default: + j = (vt_rdval(client, + VT1211_REG_TEMP_LOW47) >> + ((i-4)*2)) & 0x03; + break; + + } + data->temp[i - 1] |= j; + data->temp_over[i - 1] = vt_rdval(client, + VT1211_REG_TEMP_OVER(i)); + data->temp_hyst[i - 1] = vt_rdval(client, + VT1211_REG_TEMP_HYST(i)); + } else { + data->temp[i - 1] = 0; + data->temp_over[i - 1] = 0; + data->temp_hyst[i - 1] = 0; + } + } + + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = vt_rdval(client, VT1211_REG_FAN(i)); + data->fan_min[i - 1] = vt_rdval(client, + VT1211_REG_FAN_MIN(i)); + data->pwm[i - 1] = vt_rdval(client, VT1211_REG_PWM(i)); + } + + data->pwm_ctl = vt_rdval(client, VT1211_REG_PWM_CTL); + i = vt_rdval(client, VT1211_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = vt_rdval(client, VT1211_REG_ALARM1) | + (vt_rdval(client, VT1211_REG_ALARM2) << 8); + data->vid= vt_rdval(client, VT1211_REG_VID) & 0x1f; + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void vt1211_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + int nr = ctl_name - VT1211_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + vt1211_write_value(client, VT1211_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + vt1211_write_value(client, VT1211_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void vt1211_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + int nr = ctl_name - VT1211_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div + [nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = MIN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr-1])); + vt1211_write_value(client, VT1211_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + + +void vt1211_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + int nr = ctl_name - VT1211_SYSCTL_TEMP1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over[nr]); + results[1] = TEMP_FROM_REG(data->temp_hyst[nr]); + results[2] = TEMP_FROM_REG10(data->temp[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over[nr] = TEMP_TO_REG(results[0]); + vt1211_write_value(client, + VT1211_REG_TEMP_OVER(nr + 1), + data->temp_over[nr]); + } + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = TEMP_TO_REG(results[1]); + vt1211_write_value(client, + VT1211_REG_TEMP_HYST(nr + 1), + data->temp_hyst[nr]); + } + } +} + +void vt1211_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +void vt1211_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = vt_rdval(client, VT1211_REG_FANDIV); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0x3f) | (data->fan_div[1] << 6); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xcf) | (data->fan_div[0] << 4); + vt1211_write_value(client, VT1211_REG_FANDIV, old); + } + } +} + +void vt1211_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + int nr = 1 + ctl_name - VT1211_SYSCTL_PWM1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = PWM_FROM_REG(data->pwm[nr - 1]); + results[1] = (data->pwm_ctl >> (3 + (4 * (nr - 1)))) & 1; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->pwm[nr - 1] = PWM_TO_REG(results[0]); + if (*nrels_mag >= 2) { + if(results[1]) { + data->pwm_ctl |= + (0x08 << (4 * (nr - 1))); + vt1211_write_value(client, + VT1211_REG_PWM_CTL, + data->pwm_ctl); + } else { + data->pwm_ctl &= + ~ (0x08 << (4 * (nr - 1))); + vt1211_write_value(client, + VT1211_REG_PWM_CTL, + data->pwm_ctl); + } + } + vt1211_write_value(client, VT1211_REG_PWM(nr), + data->pwm[nr - 1]); + } + } +} + +void vt1211_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + vt1211_update_client(client); + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void vt1211_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +void vt1211_uch(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt1211_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->uch_config & 0x7c; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->uch_config = (data->uch_config & 0x83)|(results[0] & 0x7c); + vt1211_write_value(client, VT1211_REG_UCH_CONFIG, + data->uch_config); + } + } +} + +static int __init sm_vt1211_init(void) +{ + int addr; + + printk("vt1211.o version %s (%s)\n", LM_VERSION, LM_DATE); + + if (vt1211_find(&addr)) { + printk("vt1211.o: VT1211 not detected, module not inserted.\n"); + return -ENODEV; + } + normal_isa[0] = addr; + + return i2c_add_driver(&vt1211_driver); +} + +static void __exit sm_vt1211_exit(void) +{ + i2c_del_driver(&vt1211_driver); +} + + + +MODULE_AUTHOR("Mark D. Studebaker "); +MODULE_DESCRIPTION("VT1211 sensors"); +MODULE_LICENSE("GPL"); + +module_init(sm_vt1211_init); +module_exit(sm_vt1211_exit); --- linux-old/drivers/sensors/vt8231.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/vt8231.c Sun Feb 26 11:18:42 2006 @@ -0,0 +1,837 @@ +/* + vt8231.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 VT8231 South Bridge embedded sensors */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#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(vt8231); + +#define VIA686A_EXTENT 0x80 +#define VIA686A_BASE_REG 0x70 +#define VIA686A_ENABLE_REG 0x74 + +/* pwm numbered 1-2 */ +#define VT8231_REG_PWM(nr) (0x5f + (nr)) +#define VT8231_REG_PWM_CTL 0x51 + +/* The VT8231 registers */ +/* We define the sensors as follows. + Sensor Voltage Mode Temp Mode + -------- ------------ --------- + Reading 1 temp1 + UCH1 in0 temp2 + UCH2 in1 temp3 + UCH3 in2 temp4 + UCH4 in3 temp5 + UCH5 in4 temp6 + 3.3V in5 +*/ + +/* ins numbered 0-5 */ +#define VT8231_REG_IN_MAX(nr) ((nr)==0 ? 0x3d : 0x29 + ((nr) * 2)) +#define VT8231_REG_IN_MIN(nr) ((nr)==0 ? 0x3e : 0x2a + ((nr) * 2)) +#define VT8231_REG_IN(nr) (0x21 + (nr)) + +/* fans numbered 1-2 */ +#define VT8231_REG_FAN_MIN(nr) (0x3a + (nr)) +#define VT8231_REG_FAN(nr) (0x28 + (nr)) + +static const u8 regtemp[] = { 0x1f, 0x21, 0x22, 0x23, 0x24, 0x25 }; +static const u8 regover[] = { 0x39, 0x3d, 0x2b, 0x2d, 0x2f, 0x31 }; +static const u8 reghyst[] = { 0x3a, 0x3e, 0x2c, 0x2e, 0x30, 0x32 }; + +/* temps numbered 1-6 */ +#define VT8231_REG_TEMP(nr) (regtemp[(nr) - 1]) +#define VT8231_REG_TEMP_OVER(nr) (regover[(nr) - 1]) +#define VT8231_REG_TEMP_HYST(nr) (reghyst[(nr) - 1]) +#define VT8231_REG_TEMP_LOW12 0x49 +#define VT8231_REG_TEMP_LOW36 0x4d + +#define VT8231_REG_CONFIG 0x40 +#define VT8231_REG_ALARM1 0x41 +#define VT8231_REG_ALARM2 0x42 +#define VT8231_REG_VID 0x45 +#define VT8231_REG_FANDIV 0x47 +#define VT8231_REG_UCH_CONFIG 0x4a +#define VT8231_REG_TEMP1_CONFIG 0x4b +#define VT8231_REG_TEMP2_CONFIG 0x4c + +/* temps 1-6; voltages 0-5 */ +#define ISTEMP(i, ch_config) ((i) == 1 ? 1 : \ + ((ch_config) >> (i)) & 0x01) +#define ISVOLT(i, ch_config) ((i) == 5 ? 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) + +/* Used for temp1 (diode) */ +#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)) + +/* Used for temp2-temp6 (thermistor) */ +#define THERM_FROM_REG(reg) (((253 * 4 - (reg)) * 55 + 105) / 210) +#define THERM_TO_REG(val) (253 - ((val) * 210 + 110) / 220) + +/* Used for in0-in4 */ +#define IN_FROM_REG(val) ((((val) - 3) * 1000 + 479) / 958) +#define IN_TO_REG(val) SENSORS_LIMIT(((val) * 958 + 500) \ + / 1000 + 3, 0, 255) +/* Used for in5 (scaled internally) */ +#define IN5_FROM_REG(val) ((((val) - 3) * 54000 + 16286) / 32572) +#define IN5_TO_REG(val) SENSORS_LIMIT(((val) * 32572 + 27000) \ + / 54000 + 3, 0, 255) + + +/********* FAN RPM CONVERSIONS ********/ +/* But this chip saturates back at 0, not at 255 like all the other chips. + So, 0 means 0 RPM */ +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 0; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1310720 + rpm * div / 2) / (rpm * div), 1, 255); +} + +#define MIN_TO_REG(a,b) FAN_TO_REG(a,b) +#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1310720/((val)*(div))) + +struct vt8231_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[6]; /* Register value */ + u8 in_max[6]; /* Register value */ + u8 in_min[6]; /* Register value */ + u16 temp[6]; /* Register value 10 bit */ + u8 temp_over[6]; /* Register value */ + u8 temp_hyst[6]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 alarms; /* Register encoding */ + u8 pwm[2]; /* Register value */ + u8 pwm_ctl; /* Register value */ + u8 vid; /* Register encoding */ + u8 vrm; + u8 uch_config; +}; + +static int vt8231_attach_adapter(struct i2c_adapter *adapter); +static int vt8231_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int vt8231_detach_client(struct i2c_client *client); + +static inline int vt_rdval(struct i2c_client *client, u8 register); +static inline void vt8231_write_value(struct i2c_client *client, u8 register, + u8 value); +static void vt8231_update_client(struct i2c_client *client); +static void vt8231_init_client(struct i2c_client *client); + + +static void vt8231_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_in5(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_uch(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void vt8231_therm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver vt8231_driver = { + .name = "VT8231 sensors driver", + .id = I2C_DRIVERID_VT8231, + .flags = I2C_DF_NOTIFY, + .attach_adapter = vt8231_attach_adapter, + .detach_client = vt8231_detach_client, +}; + +/* -- SENSORS SYSCTL START -- */ +#define VT8231_SYSCTL_IN0 1000 +#define VT8231_SYSCTL_IN1 1001 +#define VT8231_SYSCTL_IN2 1002 +#define VT8231_SYSCTL_IN3 1003 +#define VT8231_SYSCTL_IN4 1004 +#define VT8231_SYSCTL_IN5 1005 +#define VT8231_SYSCTL_FAN1 1101 +#define VT8231_SYSCTL_FAN2 1102 +#define VT8231_SYSCTL_TEMP 1200 +#define VT8231_SYSCTL_TEMP2 1201 +#define VT8231_SYSCTL_TEMP3 1202 +#define VT8231_SYSCTL_TEMP4 1203 +#define VT8231_SYSCTL_TEMP5 1204 +#define VT8231_SYSCTL_TEMP6 1205 +#define VT8231_SYSCTL_VID 1300 +#define VT8231_SYSCTL_PWM1 1401 +#define VT8231_SYSCTL_PWM2 1402 +#define VT8231_SYSCTL_VRM 1600 +#define VT8231_SYSCTL_UCH 1700 +#define VT8231_SYSCTL_FAN_DIV 2000 +#define VT8231_SYSCTL_ALARMS 2001 + +#define VT8231_ALARM_IN1 0x01 +#define VT8231_ALARM_IN2 0x02 +#define VT8231_ALARM_IN5 0x04 +#define VT8231_ALARM_IN3 0x08 +#define VT8231_ALARM_TEMP 0x10 +#define VT8231_ALARM_FAN1 0x40 +#define VT8231_ALARM_FAN2 0x80 +#define VT8231_ALARM_IN4 0x100 +#define VT8231_ALARM_TEMP2 0x800 +#define VT8231_ALARM_CHAS 0x1000 +/* duplicates */ +#define VT8231_ALARM_IN0 VT8231_ALARM_TEMP2 +#define VT8231_ALARM_TEMP3 VT8231_ALARM_IN1 +#define VT8231_ALARM_TEMP4 VT8231_ALARM_IN2 +#define VT8231_ALARM_TEMP5 VT8231_ALARM_IN3 +#define VT8231_ALARM_TEMP6 VT8231_ALARM_IN4 + +/* -- SENSORS SYSCTL END -- */ + +static ctl_table vt8231_dir_table_template[] = { + {VT8231_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_in}, + {VT8231_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_in}, + {VT8231_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_in}, + {VT8231_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_in}, + {VT8231_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_in}, + {VT8231_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_in5}, + {VT8231_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_temp}, + {VT8231_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_therm}, + {VT8231_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_therm}, + {VT8231_SYSCTL_TEMP4, "temp4", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_therm}, + {VT8231_SYSCTL_TEMP5, "temp5", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_therm}, + {VT8231_SYSCTL_TEMP6, "temp6", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &vt8231_therm}, + {VT8231_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_fan}, + {VT8231_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_fan}, + {VT8231_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_fan_div}, + {VT8231_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_alarms}, + {VT8231_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_pwm}, + {VT8231_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_pwm}, + {VT8231_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_vid}, + {VT8231_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_vrm}, + {VT8231_SYSCTL_UCH, "uch_config", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &vt8231_uch}, + {0} +}; + +static struct pci_dev *s_bridge; + +static int vt8231_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, vt8231_detect); +} + +/* Locate chip and get correct base address */ +static int __init vt8231_find(int *address) +{ + u16 val; + + if (!pci_present()) + return -ENODEV; + + if (!(s_bridge = pci_find_device(PCI_VENDOR_ID_VIA, + 0x8235, NULL))) + return -ENODEV; + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(s_bridge, VIA686A_BASE_REG, &val)) + return -ENODEV; + *address = val & ~(VIA686A_EXTENT - 1); + if (*address == 0 && force_addr == 0) { + printk("vt8231.o: base address not set - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + if (force_addr) + *address = force_addr; /* so detect will get called */ + + return 0; +} + +int vt8231_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct vt8231_data *data; + int err = 0; + const char *type_name = "vt8231"; + u16 val; + + if (!i2c_is_isa_adapter(adapter)) { + return 0; + } + + /* 8231 requires multiple of 256 */ + if(force_addr) + address = force_addr & 0xFF00; + if (check_region(address, VIA686A_EXTENT)) { + printk("vt8231.o: region 0x%x already in use!\n", + address); + return -ENODEV; + } + + if(force_addr) { + printk("vt8231.o: forcing ISA address 0x%04X\n", address); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(s_bridge, VIA686A_BASE_REG, address)) + return -ENODEV; + } + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(s_bridge, VIA686A_ENABLE_REG, &val)) + return -ENODEV; + if (!(val & 0x0001)) { + printk("vt8231.o: enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(s_bridge, VIA686A_ENABLE_REG, + val | 0x0001)) + return -ENODEV; + } + + if (!(data = kmalloc(sizeof(struct vt8231_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &vt8231_driver; + new_client->flags = 0; + + /* Reserve the ISA region */ + request_region(address, VIA686A_EXTENT, "vt8231-sensors"); + + /* Fill in the remaining client fields and put into the global list */ + strcpy(new_client->name, "Via 8231 Integrated Sensors"); + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry((struct i2c_client *) new_client, + type_name, + vt8231_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + vt8231_init_client(new_client); + return 0; + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + release_region(address, VIA686A_EXTENT); + kfree(data); + ERROR0: + return err; +} + +static int vt8231_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct vt8231_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("vt8231.o: Client deregistration failed, client not detached.\n"); + return err; + } + + release_region(client->addr, VIA686A_EXTENT); + kfree(client->data); + + return 0; +} + + +static inline int vt_rdval(struct i2c_client *client, u8 reg) +{ + return (inb_p(client->addr + reg)); +} + +static inline void vt8231_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + outb_p(value, client->addr + reg); +} + +static void vt8231_init_client(struct i2c_client *client) +{ + struct vt8231_data *data = client->data; + + data->vrm = 91; + /* set "default" interrupt mode for alarms, which isn't the default */ + vt8231_write_value(client, VT8231_REG_TEMP1_CONFIG, 0); + vt8231_write_value(client, VT8231_REG_TEMP2_CONFIG, 0); +} + +static void vt8231_update_client(struct i2c_client *client) +{ + struct vt8231_data *data = client->data; + int i, j; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + data->uch_config = vt_rdval(client, VT8231_REG_UCH_CONFIG); + for (i = 0; i <= 5; i++) { + if(ISVOLT(i, data->uch_config)) { + data->in[i] = vt_rdval(client, VT8231_REG_IN(i)); + data->in_min[i] = vt_rdval(client, + VT8231_REG_IN_MIN(i)); + data->in_max[i] = vt_rdval(client, + VT8231_REG_IN_MAX(i)); + } else { + data->in[i] = 0; + data->in_min[i] = 0; + data->in_max[i] = 0; + } + } + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = vt_rdval(client, VT8231_REG_FAN(i)); + data->fan_min[i - 1] = vt_rdval(client, + VT8231_REG_FAN_MIN(i)); + } + for (i = 1; i <= 6; i++) { + if(ISTEMP(i, data->uch_config)) { + data->temp[i - 1] = vt_rdval(client, + VT8231_REG_TEMP(i)) << 2; + switch(i) { + case 1: + j = (vt_rdval(client, + VT8231_REG_TEMP_LOW12) & + 0xc0) >> 6; + break; + case 2: + j = (vt_rdval(client, + VT8231_REG_TEMP_LOW12) & + 0x30) >> 4; + break; + default: + j = (vt_rdval(client, + VT8231_REG_TEMP_LOW36) >> + ((i-3)*2)) & 0x03; + break; + + } + data->temp[i - 1] |= j; + data->temp_over[i - 1] = vt_rdval(client, + VT8231_REG_TEMP_OVER(i)); + data->temp_hyst[i - 1] = vt_rdval(client, + VT8231_REG_TEMP_HYST(i)); + } else { + data->temp[i - 1] = 0; + data->temp_over[i - 1] = 0; + data->temp_hyst[i - 1] = 0; + } + } + + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = vt_rdval(client, VT8231_REG_FAN(i)); + data->fan_min[i - 1] = vt_rdval(client, + VT8231_REG_FAN_MIN(i)); + data->pwm[i - 1] = vt_rdval(client, VT8231_REG_PWM(i)); + } + + data->pwm_ctl = vt_rdval(client, VT8231_REG_PWM_CTL); + i = vt_rdval(client, VT8231_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = vt_rdval(client, VT8231_REG_ALARM1) | + (vt_rdval(client, VT8231_REG_ALARM2) << 8); + data->vid= vt_rdval(client, VT8231_REG_VID) & 0x1f; + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void vt8231_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + int nr = ctl_name - VT8231_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + vt8231_write_value(client, VT8231_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + vt8231_write_value(client, VT8231_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void vt8231_in5(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = IN5_FROM_REG(data->in_min[5]); + results[1] = IN5_FROM_REG(data->in_max[5]); + results[2] = IN5_FROM_REG(data->in[5]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[5] = IN5_TO_REG(results[0]); + vt8231_write_value(client, VT8231_REG_IN_MIN(5), + data->in_min[5]); + } + if (*nrels_mag >= 2) { + data->in_max[5] = IN5_TO_REG(results[1]); + vt8231_write_value(client, VT8231_REG_IN_MAX(5), + data->in_max[5]); + } + } +} + +void vt8231_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + int nr = ctl_name - VT8231_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div + [nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = MIN_TO_REG(results[0], + DIV_FROM_REG + (data-> + fan_div[nr-1])); + vt8231_write_value(client, VT8231_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + + +void vt8231_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over[0]); + results[1] = TEMP_FROM_REG(data->temp_hyst[0]); + results[2] = TEMP_FROM_REG10(data->temp[0]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over[0] = TEMP_TO_REG(results[0]); + vt8231_write_value(client, + VT8231_REG_TEMP_OVER(1), + data->temp_over[0]); + } + if (*nrels_mag >= 2) { + data->temp_hyst[0] = TEMP_TO_REG(results[1]); + vt8231_write_value(client, + VT8231_REG_TEMP_HYST(1), + data->temp_hyst[0]); + } + } +} + +void vt8231_therm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + int nr = ctl_name - VT8231_SYSCTL_TEMP; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = THERM_FROM_REG(data->temp_over[nr] * 4); + results[1] = THERM_FROM_REG(data->temp_hyst[nr] * 4); + results[2] = THERM_FROM_REG(data->temp[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over[nr] = THERM_TO_REG(results[0]); + vt8231_write_value(client, + VT8231_REG_TEMP_OVER(nr + 1), + data->temp_over[nr]); + } + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = THERM_TO_REG(results[1]); + vt8231_write_value(client, + VT8231_REG_TEMP_HYST(nr + 1), + data->temp_hyst[nr]); + } + } +} + +void vt8231_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +void vt8231_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + int old; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = vt_rdval(client, VT8231_REG_FANDIV); + if (*nrels_mag >= 2) { + data->fan_div[1] = DIV_TO_REG(results[1]); + old = (old & 0x3f) | (data->fan_div[1] << 6); + } + if (*nrels_mag >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + old = (old & 0xcf) | (data->fan_div[0] << 4); + vt8231_write_value(client, VT8231_REG_FANDIV, old); + } + } +} + +void vt8231_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + int nr = 1 + ctl_name - VT8231_SYSCTL_PWM1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = PWM_FROM_REG(data->pwm[nr - 1]); + results[1] = (data->pwm_ctl >> (3 + (4 * (nr - 1)))) & 1; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->pwm[nr - 1] = PWM_TO_REG(results[0]); + if (*nrels_mag >= 2) { + if(results[1]) { + data->pwm_ctl |= + (0x08 << (4 * (nr - 1))); + vt8231_write_value(client, + VT8231_REG_PWM_CTL, + data->pwm_ctl); + } else { + data->pwm_ctl &= + ~ (0x08 << (4 * (nr - 1))); + vt8231_write_value(client, + VT8231_REG_PWM_CTL, + data->pwm_ctl); + } + } + vt8231_write_value(client, VT8231_REG_PWM(nr), + data->pwm[nr - 1]); + } + } +} + +void vt8231_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + vt8231_update_client(client); + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void vt8231_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +void vt8231_uch(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct vt8231_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->uch_config & 0x7c; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->uch_config = (data->uch_config & 0x83)|(results[0] & 0x7c); + vt8231_write_value(client, VT8231_REG_UCH_CONFIG, + data->uch_config); + } + } +} + +static int __init sm_vt8231_init(void) +{ + int addr; + + printk("vt8231.o version %s (%s)\n", LM_VERSION, LM_DATE); + + if (vt8231_find(&addr)) { + printk("vt8231.o: VT8231 not detected, module not inserted.\n"); + return -ENODEV; + } + normal_isa[0] = addr; + + return i2c_add_driver(&vt8231_driver);} + +static void __exit sm_vt8231_exit(void) +{ + i2c_del_driver(&vt8231_driver); +} + + + +MODULE_AUTHOR("Mark D. Studebaker "); +MODULE_DESCRIPTION("VT8231 sensors"); +MODULE_LICENSE("GPL"); + +module_init(sm_vt8231_init); +module_exit(sm_vt8231_exit); --- linux-old/drivers/sensors/w83627hf.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/w83627hf.c Sun Feb 26 11:18:42 2006 @@ -0,0 +1,1503 @@ +/* + w83627hf.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + 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. +*/ + +/* + Supports following chips: + + Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + w83627hf 9 3 2 3 0x20 0x5ca3 no yes(LPC) + w83627thf 7 3 3 3 0x90 0x5ca3 no yes(LPC) + w83637hf 7 3 3 3 0x80 0x5ca3 no yes(LPC) + w83687thf 7 3 3 3 0x90 0x5ca3 no yes(LPC) + w83697hf 8 2 2 2 0x60 0x5ca3 no yes(LPC) + + For other winbond chips, and for i2c support in the above chips, + use w83781d.c. + + Note: automatic ("cruise") fan control for 697, 637 & 627thf not + supported yet. +*/ + +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include +#include "lm75.h" + +static int force_addr; +MODULE_PARM(force_addr, "i"); +MODULE_PARM_DESC(force_addr, + "Initialize the base address of the sensors"); +static int force_i2c = 0x1f; +MODULE_PARM(force_i2c, "i"); +MODULE_PARM_DESC(force_i2c, + "Initialize the i2c address of the sensors"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_5(w83627hf, w83627thf, w83697hf, w83637hf, w83687thf); + +static int init = 1; +MODULE_PARM(init, "i"); +MODULE_PARM_DESC(init, "Set to zero to bypass chip initialization"); + +/* modified from kernel/include/traps.c */ +static int REG; /* The register to read/write */ +#define DEV 0x07 /* Register: Logical device select */ +static int VAL; /* The value to read/write */ + +/* logical device numbers for superio_select (below) */ +#define W83627HF_LD_FDC 0x00 +#define W83627HF_LD_PRT 0x01 +#define W83627HF_LD_UART1 0x02 +#define W83627HF_LD_UART2 0x03 +#define W83627HF_LD_KBC 0x05 +#define W83627HF_LD_CIR 0x06 /* w83627hf only */ +#define W83627HF_LD_GAME 0x07 +#define W83627HF_LD_MIDI 0x07 +#define W83627HF_LD_GPIO1 0x07 +#define W83627HF_LD_GPIO5 0x07 /* w83627thf only */ +#define W83627HF_LD_GPIO2 0x08 +#define W83627HF_LD_GPIO3 0x09 +#define W83627HF_LD_GPIO4 0x09 /* w83627thf only */ +#define W83627HF_LD_ACPI 0x0a +#define W83627HF_LD_HWM 0x0b + +#define DEVID 0x20 /* Register: Device ID */ + +#define W83627THF_GPIO5_EN 0x30 /* w83627thf only */ +#define W83627THF_GPIO5_IOSR 0xf3 /* w83627thf only */ +#define W83627THF_GPIO5_DR 0xf4 /* w83627thf only */ + +#define W83687THF_VID_EN 0x29 /* w83687thf only */ +#define W83687THF_VID_CFG 0xF0 /* w83687thf only */ +#define W83687THF_VID_DATA 0xF1 /* w83687thf only */ + +static inline void +superio_outb(int reg, int val) +{ + outb(reg, REG); + outb(val, VAL); +} + +static inline int +superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +static inline void +superio_select(int ld) +{ + outb(DEV, REG); + outb(ld, VAL); +} + +static inline void +superio_enter(void) +{ + outb(0x87, REG); + outb(0x87, REG); +} + +static inline void +superio_exit(void) +{ + outb(0xAA, REG); +} + +#define W627_DEVID 0x52 +#define W627THF_DEVID 0x82 +#define W697_DEVID 0x60 +#define W637_DEVID 0x70 +#define W687THF_DEVID 0x85 +#define WINB_ACT_REG 0x30 +#define WINB_BASE_REG 0x60 +/* Constants specified below */ + +/* Length of ISA address segment */ +#define WINB_EXTENT 8 + +/* Where are the ISA address/data registers relative to the base address */ +#define W83781D_ADDR_REG_OFFSET 5 +#define W83781D_DATA_REG_OFFSET 6 + +/* The W83781D registers */ +/* The W83782D registers for nr=7,8 are in bank 5 */ +#define W83781D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0x554 + (((nr) - 7) * 2))) +#define W83781D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0x555 + (((nr) - 7) * 2))) +#define W83781D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0x550 + (nr) - 7)) + +#define W83781D_REG_FAN_MIN(nr) (0x3a + (nr)) +#define W83781D_REG_FAN(nr) (0x27 + (nr)) + +#define W83781D_REG_TEMP2 0x0150 +#define W83781D_REG_TEMP3 0x0250 +#define W83781D_REG_TEMP2_HYST 0x153 +#define W83781D_REG_TEMP3_HYST 0x253 +#define W83781D_REG_TEMP2_CONFIG 0x152 +#define W83781D_REG_TEMP3_CONFIG 0x252 +#define W83781D_REG_TEMP2_OVER 0x155 +#define W83781D_REG_TEMP3_OVER 0x255 + +#define W83781D_REG_TEMP 0x27 +#define W83781D_REG_TEMP_OVER 0x39 +#define W83781D_REG_TEMP_HYST 0x3A +#define W83781D_REG_BANK 0x4E + +#define W83781D_REG_CONFIG 0x40 +#define W83781D_REG_ALARM1 0x459 +#define W83781D_REG_ALARM2 0x45A +#define W83781D_REG_ALARM3 0x45B + +#define W83781D_REG_BEEP_CONFIG 0x4D +#define W83781D_REG_BEEP_INTS1 0x56 +#define W83781D_REG_BEEP_INTS2 0x57 +#define W83781D_REG_BEEP_INTS3 0x453 + +#define W83781D_REG_VID_FANDIV 0x47 + +#define W83781D_REG_CHIPID 0x49 +#define W83781D_REG_WCHIPID 0x58 +#define W83781D_REG_CHIPMAN 0x4F +#define W83781D_REG_PIN 0x4B + +#define W83781D_REG_VBAT 0x5D + +#define W83627HF_REG_PWM1 0x5A +#define W83627HF_REG_PWM2 0x5B + +#define W83627THF_REG_PWM1 0x01 /* 697HF/637HF/687THF too */ +#define W83627THF_REG_PWM2 0x03 /* 697HF/637HF/687THF too */ +#define W83627THF_REG_PWM3 0x11 /* 637HF/687THF too */ + +#define W83627THF_REG_VRM_OVT_CFG 0x18 /* 637HF/687THF too */ + +static const u8 regpwm_627hf[] = { W83627HF_REG_PWM1, W83627HF_REG_PWM2 }; +static const u8 regpwm[] = { W83627THF_REG_PWM1, W83627THF_REG_PWM2, + W83627THF_REG_PWM3 }; +#define W836X7HF_REG_PWM(type, nr) (((type) == w83627hf) ? \ + regpwm_627hf[(nr) - 1] : regpwm[(nr) - 1]) + +#define W83781D_REG_I2C_ADDR 0x48 +#define W83781D_REG_I2C_SUBADDR 0x4A + +/* Sensor selection */ +#define W83781D_REG_SCFG1 0x5D +static const u8 BIT_SCFG1[] = { 0x02, 0x04, 0x08 }; +#define W83781D_REG_SCFG2 0x59 +static const u8 BIT_SCFG2[] = { 0x10, 0x20, 0x40 }; +#define W83781D_DEFAULT_BETA 3435 + +/* Conversions. Limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) +#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) + +#define IN_TO_REG_VRM9(val) \ + (SENSORS_LIMIT((((val) * 1000 - 70000 + 244) / 488), 0, 255)) +#define IN_FROM_REG_VRM9(reg) (((reg) * 488 + 70000 + 500) / 1000) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define TEMP_MIN (-1280) +#define TEMP_MAX ( 1270) + +/* TEMP: 1/10 degrees C (-128C to +127C) + REG: 1C/bit, two's complement */ +static u8 TEMP_TO_REG(int temp) +{ + int ntemp = SENSORS_LIMIT(temp, TEMP_MIN, TEMP_MAX); + ntemp += (ntemp<0 ? -5 : 5); + return (u8)(ntemp / 10); +} + +static int TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 10; +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) + +#define PWM_TO_REG(val) (SENSORS_LIMIT((val),0,255)) +#define BEEPS_TO_REG(val) ((val) & 0xffffff) + +#define BEEP_ENABLE_TO_REG(val) ((val)?1:0) +#define BEEP_ENABLE_FROM_REG(val) ((val)?1:0) + +#define DIV_FROM_REG(val) (1 << (val)) + +static inline u8 DIV_TO_REG(long val) +{ + int i; + val = SENSORS_LIMIT(val, 1, 128) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return ((u8) i); +} + +/* For each registered chip, we need to keep some data in memory. That + data is pointed to by w83627hf_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new client is allocated. */ +struct w83627hf_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[9]; /* Register value */ + u8 in_max[9]; /* Register value */ + u8 in_min[9]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u8 temp; + u8 temp_over; /* Register value */ + u8 temp_hyst; /* Register value */ + u16 temp_add[2]; /* Register value */ + u16 temp_add_over[2]; /* Register value */ + u16 temp_add_hyst[2]; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u32 beeps; /* Register encoding, combined */ + u8 beep_enable; /* Boolean */ + u8 pwm[3]; /* Register value */ + u16 sens[3]; /* 782D/783S only. + 1 = pentium diode; 2 = 3904 diode; + 3000-5000 = thermistor beta. + Default = 3435. + Other Betas unimplemented */ + u8 vrm; + u8 vrm_ovt; /* Register value, 627THF/637HF/687THF only */ +}; + + +static int w83627hf_attach_adapter(struct i2c_adapter *adapter); +static int w83627hf_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int w83627hf_detach_client(struct i2c_client *client); + +static int w83627hf_read_value(struct i2c_client *client, u16 register); +static int w83627hf_write_value(struct i2c_client *client, u16 register, + u16 value); +static void w83627hf_update_client(struct i2c_client *client); +static void w83627hf_init_client(struct i2c_client *client); + + +static void w83627hf_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_beep(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83627hf_sens(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver w83627hf_driver = { + .name = "W83627HF sensor driver", + .id = I2C_DRIVERID_W83627HF, + .flags = I2C_DF_NOTIFY, + .attach_adapter = w83627hf_attach_adapter, + .detach_client = w83627hf_detach_client, +}; + +/* The /proc/sys entries */ +/* WARNING these are copied from w83781d.c and have not been renamed. + Note that the 627hf is supported by both drivers. + Do not make incompatible changes here or we will have errors + in the generated file ../include/sensors.h !!! +*/ +/* -- SENSORS SYSCTL START -- */ + +#define W83781D_SYSCTL_IN0 1000 /* Volts * 100 */ +#define W83781D_SYSCTL_IN1 1001 +#define W83781D_SYSCTL_IN2 1002 +#define W83781D_SYSCTL_IN3 1003 +#define W83781D_SYSCTL_IN4 1004 +#define W83781D_SYSCTL_IN5 1005 +#define W83781D_SYSCTL_IN6 1006 +#define W83781D_SYSCTL_IN7 1007 +#define W83781D_SYSCTL_IN8 1008 +#define W83781D_SYSCTL_FAN1 1101 /* Rotations/min */ +#define W83781D_SYSCTL_FAN2 1102 +#define W83781D_SYSCTL_FAN3 1103 +#define W83781D_SYSCTL_TEMP1 1200 /* Degrees Celsius * 10 */ +#define W83781D_SYSCTL_TEMP2 1201 /* Degrees Celsius * 10 */ +#define W83781D_SYSCTL_TEMP3 1202 /* Degrees Celsius * 10 */ +#define W83781D_SYSCTL_VID 1300 /* Volts * 1000 */ +#define W83781D_SYSCTL_VRM 1301 +#define W83781D_SYSCTL_PWM1 1401 +#define W83781D_SYSCTL_PWM2 1402 +#define W83781D_SYSCTL_PWM3 1403 +#define W83781D_SYSCTL_SENS1 1501 /* 1, 2, or Beta (3000-5000) */ +#define W83781D_SYSCTL_SENS2 1502 +#define W83781D_SYSCTL_SENS3 1503 +#define W83781D_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define W83781D_SYSCTL_ALARMS 2001 /* bitvector */ +#define W83781D_SYSCTL_BEEP 2002 /* bitvector */ + +#define W83781D_ALARM_IN0 0x0001 +#define W83781D_ALARM_IN1 0x0002 +#define W83781D_ALARM_IN2 0x0004 +#define W83781D_ALARM_IN3 0x0008 +#define W83781D_ALARM_IN4 0x0100 +#define W83781D_ALARM_IN5 0x0200 +#define W83781D_ALARM_IN6 0x0400 +#define W83782D_ALARM_IN7 0x10000 +#define W83782D_ALARM_IN8 0x20000 +#define W83781D_ALARM_FAN1 0x0040 +#define W83781D_ALARM_FAN2 0x0080 +#define W83781D_ALARM_FAN3 0x0800 +#define W83781D_ALARM_TEMP1 0x0010 +#define W83781D_ALARM_TEMP23 0x0020 /* 781D only */ +#define W83781D_ALARM_TEMP2 0x0020 /* 782D/783S */ +#define W83781D_ALARM_TEMP3 0x2000 /* 782D only */ +#define W83781D_ALARM_CHAS 0x1000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected chip. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ + +/* without pwm3-4 */ +static ctl_table w83627hf_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {0} +}; + +/* similar to w83782d but no fan3, no vid */ +static ctl_table w83697hf_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + /* no in1 to maintain compatibility with 781d and 782d. */ + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp_add}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {0} +}; + +/* no in5 and in6 */ +/* We use this one for W83637HF and W83687THF too */ +static ctl_table w83627thf_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_pwm}, + {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627hf_sens}, + {0} +}; + + +/* This function is called when: + * w83627hf_driver is inserted (when this module is loaded), for each + available adapter + * when a new adapter is inserted (and w83627hf_driver is still present) */ +static int w83627hf_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, w83627hf_detect); +} + +static int __init w83627hf_find(int sioaddr, int *address) +{ + u16 val; + + REG = sioaddr; + VAL = sioaddr + 1; + + superio_enter(); + val= superio_inb(DEVID); + if (val != W627_DEVID && val != W627THF_DEVID && val != W697_DEVID + && val != W637_DEVID && val != W687THF_DEVID) { + superio_exit(); + return -ENODEV; + } + + superio_select(W83627HF_LD_HWM); + val = (superio_inb(WINB_BASE_REG) << 8) | + superio_inb(WINB_BASE_REG + 1); + *address = val & ~(WINB_EXTENT - 1); + if (*address == 0 && force_addr == 0) { + printk("w83627hf.o: base address not set - use force_addr=0xaddr\n"); + superio_exit(); + return -ENODEV; + } + if (force_addr) + *address = force_addr; /* so detect will get called */ + + superio_exit(); + return 0; +} + +int w83627hf_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, val; + struct i2c_client *new_client; + struct w83627hf_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_is_isa_adapter(adapter)) + return 0; + + if(force_addr) + address = force_addr & ~(WINB_EXTENT - 1); + if (check_region(address, WINB_EXTENT)) { + printk("w83627hf.o: region 0x%x already in use!\n", address); + return -ENODEV; + } + if(force_addr) { + printk("w83627hf.o: forcing ISA address 0x%04X\n", address); + superio_enter(); + superio_select(W83627HF_LD_HWM); + superio_outb(WINB_BASE_REG, address >> 8); + superio_outb(WINB_BASE_REG+1, address & 0xff); + superio_exit(); + } + + superio_enter(); + val= superio_inb(DEVID); + if(val == W627_DEVID) + kind = w83627hf; + else if(val == W697_DEVID) + kind = w83697hf; + else if(val == W627THF_DEVID) + kind = w83627thf; + else if(val == W637_DEVID) + kind = w83637hf; + else if (val == W687THF_DEVID) + kind = w83687thf; + + superio_select(W83627HF_LD_HWM); + if((val = 0x01 & superio_inb(WINB_ACT_REG)) == 0) + superio_outb(WINB_ACT_REG, 1); + superio_exit(); + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access w83627hf_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct w83627hf_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &w83627hf_driver; + new_client->flags = 0; + + + if (kind == w83627hf) { + type_name = "w83627hf"; + client_name = "W83627HF chip"; + } else if (kind == w83627thf) { + type_name = "w83627thf"; + client_name = "W83627THF chip"; + } else if (kind == w83697hf) { + type_name = "w83697hf"; + client_name = "W83697HF chip"; + } else if (kind == w83637hf) { + type_name = "w83637hf"; + client_name = "W83637HF chip"; + } else if (kind == w83687thf) { + type_name = "w83687thf"; + client_name = "W83687THF chip"; + } else { + goto ERROR1; + } + + request_region(address, WINB_EXTENT, type_name); + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + 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, + (kind == w83697hf) ? + w83697hf_dir_table_template : + (kind == w83627hf) ? + w83627hf_dir_table_template : + /* w83627thf table also used for 637HF + and 687THF */ + w83627thf_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR7; + } + data->sysctl_id = i; + + /* Initialize the chip */ + w83627hf_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + +ERROR7: + i2c_detach_client(new_client); +ERROR3: + release_region(address, WINB_EXTENT); +ERROR1: + kfree(data); +ERROR0: + return err; +} + +static int w83627hf_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct w83627hf_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + (KERN_ERR "w83627hf.o: Client deregistration failed, client not detached.\n"); + return err; + } + + release_region(client->addr, WINB_EXTENT); + kfree(client->data); + + return 0; +} + + +/* + ISA access must always be locked explicitly! + We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, + would slow down the W83781D access and should not be necessary. + There are some ugly typecasts here, but the good news is - they should + nowhere else be necessary! */ +static int w83627hf_read_value(struct i2c_client *client, u16 reg) +{ + int res, word_sized; + + down(&(((struct w83627hf_data *) (client->data))->lock)); + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x50) + || ((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, + client->addr + W83781D_DATA_REG_OFFSET); + } + outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); + res = inb_p(client->addr + W83781D_DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + client->addr + W83781D_ADDR_REG_OFFSET); + res = + (res << 8) + inb_p(client->addr + + W83781D_DATA_REG_OFFSET); + } + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); + } + up(&(((struct w83627hf_data *) (client->data))->lock)); + return res; +} + +static int w83627thf_read_gpio5(struct i2c_client *client) +{ + int res = 0xff, sel; + + superio_enter(); + superio_select(W83627HF_LD_GPIO5); + + /* Make sure these GPIO pins are enabled */ + if (!(superio_inb(W83627THF_GPIO5_EN) & (1<<3))) { +#ifdef DEBUG + printk(KERN_DEBUG "w83627hf: GPIO5 disabled, no VID " + "function\n"); +#endif + goto exit; + } + + /* Make sure the pins are configured for input + There must be at least five (VRM 9), and possibly 6 (VRM 10) */ + sel = superio_inb(W83627THF_GPIO5_IOSR) & 0x3f; + if ((sel & 0x1f) != 0x1f) { +#ifdef DEBUG + printk(KERN_DEBUG "w83627hf: GPIO5 not configured for " + "VID function\n"); +#endif + goto exit; + } + + printk(KERN_INFO "w83627hf: Reading VID from GPIO5\n"); + res = superio_inb(W83627THF_GPIO5_DR) & sel; + +exit: + superio_exit(); + return res; +} + +static int w83687thf_read_vid(struct i2c_client *client) +{ + int res = 0xff; + + superio_enter(); + superio_select(W83627HF_LD_HWM); + + /* Make sure these GPIO pins are enabled */ + if (!(superio_inb(W83687THF_VID_EN) & (1 << 2))) { +#ifdef DEBUG + printk(KERN_DEBUG "w83627hf: VID disabled, no VID " + "function\n"); +#endif + goto exit; + } + + /* Make sure the pins are configured for input */ + if (!(superio_inb(W83687THF_VID_CFG) & (1 << 4))) { +#ifdef DEBUG + printk(KERN_DEBUG "w83627hf: VID configured as output, " + "no VID function\n"); +#endif + goto exit; + } + + res = superio_inb(W83687THF_VID_DATA) & 0x3f; + +exit: + superio_exit(); + return res; +} + +static int w83627hf_write_value(struct i2c_client *client, u16 reg, u16 value) +{ + int word_sized; + + down(&(((struct w83627hf_data *) (client->data))->lock)); + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, + client->addr + W83781D_DATA_REG_OFFSET); + } + outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, + client->addr + W83781D_DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + client->addr + W83781D_ADDR_REG_OFFSET); + } + outb_p(value & 0xff, + client->addr + W83781D_DATA_REG_OFFSET); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); + } + up(&(((struct w83627hf_data *) (client->data))->lock)); + return 0; +} + +/* Called when we have found a new W83781D. */ +static void w83627hf_init_client(struct i2c_client *client) +{ + struct w83627hf_data *data = client->data; + int i; + int type = data->type; + u8 tmp; + + /* Minimize conflicts with other winbond i2c-only clients... */ + /* disable i2c subclients... how to disable main i2c client?? */ + /* force i2c address to relatively uncommon address */ + w83627hf_write_value(client, W83781D_REG_I2C_SUBADDR, 0x89); + w83627hf_write_value(client, W83781D_REG_I2C_ADDR, force_i2c); + + /* Read VID only once */ + if (w83627hf == data->type || w83637hf == data->type) { + int lo = w83627hf_read_value(client, W83781D_REG_VID_FANDIV); + int hi = w83627hf_read_value(client, W83781D_REG_CHIPID); + data->vid = (lo & 0x0f) | ((hi & 0x01) << 4); + } else if (w83627thf == data->type) { + data->vid = w83627thf_read_gpio5(client); + } else if (w83687thf == data->type) { + data->vid = w83687thf_read_vid(client); + } + + /* Read VRM & OVT Config only once */ + if (w83627thf == data->type || w83637hf == data->type + || w83687thf == data->type) + data->vrm_ovt = + w83627hf_read_value(client, W83627THF_REG_VRM_OVT_CFG); + else + data->vrm_ovt = 0; + + /* Choose VRM based on "VRM & OVT" register */ + data->vrm = (data->vrm_ovt & 0x01) ? 90 : 82; + + tmp = w83627hf_read_value(client, W83781D_REG_SCFG1); + for (i = 1; i <= 3; i++) { + if (!(tmp & BIT_SCFG1[i - 1])) { + data->sens[i - 1] = W83781D_DEFAULT_BETA; + } else { + if (w83627hf_read_value + (client, + W83781D_REG_SCFG2) & BIT_SCFG2[i - 1]) + data->sens[i - 1] = 1; + else + data->sens[i - 1] = 2; + } + if ((type == w83697hf) && (i == 2)) + break; + } + + if(init) { + /* Enable temp2 */ + tmp = w83627hf_read_value(client, W83781D_REG_TEMP2_CONFIG); + if (tmp & 0x01) { + printk(KERN_WARNING "w83627hf: temp2 was disabled, " + "readings might not make sense\n"); + w83627hf_write_value(client, W83781D_REG_TEMP2_CONFIG, + tmp & 0xfe); + } + + /* Enable temp3 */ + if (type != w83697hf) { + tmp = w83627hf_read_value(client, + W83781D_REG_TEMP3_CONFIG); + if (tmp & 0x01) { + printk(KERN_WARNING "w83627hf: temp3 was " + "disabled, readings might not make " + "sense\n"); + w83627hf_write_value(client, + W83781D_REG_TEMP3_CONFIG, tmp & 0xfe); + } + } + } + + /* Start monitoring */ + w83627hf_write_value(client, W83781D_REG_CONFIG, + (w83627hf_read_value(client, + W83781D_REG_CONFIG) & 0xf7) + | 0x01); +} + +static void w83627hf_update_client(struct i2c_client *client) +{ + struct w83627hf_data *data = client->data; + int i; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + for (i = 0; i <= 8; i++) { + /* skip missing sensors */ + if (((data->type == w83697hf) && (i == 1)) || + ((data->type == w83627thf || data->type == w83637hf + || data->type == w83687thf) && (i == 5 || i == 6))) + continue; + data->in[i] = + w83627hf_read_value(client, W83781D_REG_IN(i)); + data->in_min[i] = + w83627hf_read_value(client, + W83781D_REG_IN_MIN(i)); + data->in_max[i] = + w83627hf_read_value(client, + W83781D_REG_IN_MAX(i)); + } + for (i = 1; i <= 3; i++) { + data->fan[i - 1] = + w83627hf_read_value(client, W83781D_REG_FAN(i)); + data->fan_min[i - 1] = + w83627hf_read_value(client, + W83781D_REG_FAN_MIN(i)); + } + for (i = 1; i <= 3; i++) { + u8 tmp = w83627hf_read_value(client, + W836X7HF_REG_PWM(data->type, i)); + if (data->type == w83627thf) + tmp &= 0xf0; /* bits 0-3 are reserved in 627THF */ + data->pwm[i - 1] = tmp; + if(i == 2 && (data->type == w83627hf || data->type == w83697hf)) + break; + } + + data->temp = w83627hf_read_value(client, W83781D_REG_TEMP); + data->temp_over = + w83627hf_read_value(client, W83781D_REG_TEMP_OVER); + data->temp_hyst = + w83627hf_read_value(client, W83781D_REG_TEMP_HYST); + data->temp_add[0] = + w83627hf_read_value(client, W83781D_REG_TEMP2); + data->temp_add_over[0] = + w83627hf_read_value(client, W83781D_REG_TEMP2_OVER); + data->temp_add_hyst[0] = + w83627hf_read_value(client, W83781D_REG_TEMP2_HYST); + if (data->type != w83697hf) { + data->temp_add[1] = + w83627hf_read_value(client, W83781D_REG_TEMP3); + data->temp_add_over[1] = + w83627hf_read_value(client, W83781D_REG_TEMP3_OVER); + data->temp_add_hyst[1] = + w83627hf_read_value(client, W83781D_REG_TEMP3_HYST); + } + + i = w83627hf_read_value(client, W83781D_REG_VID_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + if (data->type != w83697hf) { + data->fan_div[2] = (w83627hf_read_value(client, + W83781D_REG_PIN) >> 6) & 0x03; + } + i = w83627hf_read_value(client, W83781D_REG_VBAT); + data->fan_div[0] |= (i >> 3) & 0x04; + data->fan_div[1] |= (i >> 4) & 0x04; + if (data->type != w83697hf) + data->fan_div[2] |= (i >> 5) & 0x04; + data->alarms = + w83627hf_read_value(client, W83781D_REG_ALARM1) | + (w83627hf_read_value(client, W83781D_REG_ALARM2) << 8) | + (w83627hf_read_value(client, W83781D_REG_ALARM3) << 16); + i = w83627hf_read_value(client, W83781D_REG_BEEP_INTS2); + data->beep_enable = i >> 7; + data->beeps = ((i & 0x7f) << 8) | + w83627hf_read_value(client, W83781D_REG_BEEP_INTS1) | + w83627hf_read_value(client, W83781D_REG_BEEP_INTS3) << 16; + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +void w83627hf_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int nr = ctl_name - W83781D_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + + if (nr == 0 && (data->vrm_ovt & 0x01)) { + /* use VRM9 calculation */ + results[0] = IN_FROM_REG_VRM9(data->in_min[0]); + results[1] = IN_FROM_REG_VRM9(data->in_max[0]); + results[2] = IN_FROM_REG_VRM9(data->in[0]); + + } else { + /* use VRM8 (standard) calculation */ + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + } + + *nrels_mag = 3; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + if (nr == 0 && (data->vrm_ovt & 0x01)) + /* use VRM9 calculation */ + data->in_min[0] = IN_TO_REG_VRM9(results[0]); + else + /* use VRM8 (standard) calculation */ + data->in_min[nr] = IN_TO_REG(results[0]); + + w83627hf_write_value(client, W83781D_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + if (nr == 0 && (data->vrm_ovt & 0x01)) + /* use VRM9 calculation */ + data->in_max[0] = IN_TO_REG_VRM9(results[1]); + else + /* use VRM8 (standard) calculation */ + data->in_max[nr] = IN_TO_REG(results[1]); + + w83627hf_write_value(client, W83781D_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void w83627hf_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int nr = ctl_name - W83781D_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = + FAN_TO_REG(results[0], + DIV_FROM_REG(data->fan_div[nr-1])); + w83627hf_write_value(client, + W83781D_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + +void w83627hf_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + w83627hf_write_value(client, W83781D_REG_TEMP_OVER, + data->temp_over); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + w83627hf_write_value(client, W83781D_REG_TEMP_HYST, + data->temp_hyst); + } + } +} + +void w83627hf_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int nr = ctl_name - W83781D_SYSCTL_TEMP2; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = + LM75_TEMP_FROM_REG(data->temp_add_over[nr]); + results[1] = + LM75_TEMP_FROM_REG(data->temp_add_hyst[nr]); + results[2] = LM75_TEMP_FROM_REG(data->temp_add[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_add_over[nr] = + LM75_TEMP_TO_REG(results[0]); + w83627hf_write_value(client, + nr ? W83781D_REG_TEMP3_OVER : + W83781D_REG_TEMP2_OVER, + data->temp_add_over[nr]); + } + if (*nrels_mag >= 2) { + data->temp_add_hyst[nr] = + LM75_TEMP_TO_REG(results[1]); + w83627hf_write_value(client, + nr ? W83781D_REG_TEMP3_HYST : + W83781D_REG_TEMP2_HYST, + data->temp_add_hyst[nr]); + } + } +} + +void w83627hf_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + if (data->vid == 0xff) + results[0] = 0; + else + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void w83627hf_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +void w83627hf_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +void w83627hf_beep(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int val; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); + results[1] = data->beeps; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 2) { + data->beeps = BEEPS_TO_REG(results[1]); + w83627hf_write_value(client, W83781D_REG_BEEP_INTS1, + data->beeps & 0xff); + w83627hf_write_value(client, + W83781D_REG_BEEP_INTS3, + ((data-> beeps) >> 16) & + 0xff); + val = (data->beeps >> 8) & 0x7f; + } else if (*nrels_mag >= 1) + val = + w83627hf_read_value(client, + W83781D_REG_BEEP_INTS2) & + 0x7f; + if (*nrels_mag >= 1) { + data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); + w83627hf_write_value(client, W83781D_REG_BEEP_INTS2, + val | data->beep_enable << 7); + } + } +} + +/* w83697hf only has two fans */ +void w83627hf_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int old, old2, old3 = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + if (data->type == w83697hf) { + *nrels_mag = 2; + } else { + results[2] = DIV_FROM_REG(data->fan_div[2]); + *nrels_mag = 3; + } + } else if (operation == SENSORS_PROC_REAL_WRITE) { + unsigned long min = 0; + + old = w83627hf_read_value(client, W83781D_REG_VID_FANDIV); + /* w83627hf doesn't have extended divisor bits */ + old3 = w83627hf_read_value(client, W83781D_REG_VBAT); + if (*nrels_mag >= 3 && data->type != w83697hf) { + min = FAN_FROM_REG(data->fan_min[2], + DIV_FROM_REG(data->fan_div[2])); + data->fan_div[2] = DIV_TO_REG(results[2]); + old2 = w83627hf_read_value(client, W83781D_REG_PIN); + old2 = (old2 & 0x3f) + | ((data->fan_div[2] & 0x03) << 6); + w83627hf_write_value(client, W83781D_REG_PIN, old2); + old3 = (old3 & 0x7f) + | ((data->fan_div[2] & 0x04) << 5); + data->fan_min[2] = FAN_TO_REG(min, + DIV_FROM_REG(data->fan_div[2])); + w83627hf_write_value(client, W83781D_REG_FAN_MIN(3), + data->fan_min[2]); + } + 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] & 0x03) << 6); + old3 = (old3 & 0xbf) + | ((data->fan_div[1] & 0x04) << 4); + data->fan_min[1] = FAN_TO_REG(min, + DIV_FROM_REG(data->fan_div[1])); + w83627hf_write_value(client, W83781D_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] & 0x03) << 4); + w83627hf_write_value(client, W83781D_REG_VID_FANDIV, + old); + old3 = (old3 & 0xdf) + | ((data->fan_div[0] & 0x04) << 3); + w83627hf_write_value(client, W83781D_REG_VBAT, + old3); + data->fan_min[0] = FAN_TO_REG(min, + DIV_FROM_REG(data->fan_div[0])); + w83627hf_write_value(client, W83781D_REG_FAN_MIN(1), + data->fan_min[0]); + } + } +} + +/* we do not currently support disabling PWM with 2nd argument; + set first argument to 255 to disable */ +void w83627hf_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int nr = 1 + ctl_name - W83781D_SYSCTL_PWM1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627hf_update_client(client); + results[0] = data->pwm[nr - 1]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + if (data->type == w83627thf) { + /* bits 0-3 are reserved in 627THF */ + data->pwm[nr - 1] = PWM_TO_REG(results[0]) & 0xf0; + w83627hf_write_value(client, + W836X7HF_REG_PWM(data->type, nr), + data->pwm[nr - 1] | + (w83627hf_read_value(client, + W836X7HF_REG_PWM(data->type, nr)) & 0x0f)); + } else { + data->pwm[nr - 1] = PWM_TO_REG(results[0]); + w83627hf_write_value(client, + W836X7HF_REG_PWM(data->type, nr), + data->pwm[nr - 1]); + } + } + } +} + +void w83627hf_sens(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627hf_data *data = client->data; + int nr = 1 + ctl_name - W83781D_SYSCTL_SENS1; + u8 tmp; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->sens[nr - 1]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + switch (results[0]) { + case 1: /* PII/Celeron diode */ + tmp = w83627hf_read_value(client, + W83781D_REG_SCFG1); + w83627hf_write_value(client, + W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr - + 1]); + tmp = w83627hf_read_value(client, + W83781D_REG_SCFG2); + w83627hf_write_value(client, + W83781D_REG_SCFG2, + tmp | BIT_SCFG2[nr - + 1]); + data->sens[nr - 1] = results[0]; + break; + case 2: /* 3904 */ + tmp = w83627hf_read_value(client, + W83781D_REG_SCFG1); + w83627hf_write_value(client, + W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr - + 1]); + tmp = w83627hf_read_value(client, + W83781D_REG_SCFG2); + w83627hf_write_value(client, + W83781D_REG_SCFG2, + tmp & ~BIT_SCFG2[nr - + 1]); + data->sens[nr - 1] = results[0]; + break; + case W83781D_DEFAULT_BETA: /* thermistor */ + tmp = w83627hf_read_value(client, + W83781D_REG_SCFG1); + w83627hf_write_value(client, + W83781D_REG_SCFG1, + tmp & ~BIT_SCFG1[nr - + 1]); + data->sens[nr - 1] = results[0]; + break; + default: + printk + (KERN_ERR "w83627hf.o: Invalid sensor type %ld; must be 1, 2, or %d\n", + results[0], W83781D_DEFAULT_BETA); + break; + } + } + } +} + +static int __init sm_w83627hf_init(void) +{ + int addr; + + printk(KERN_INFO "w83627hf.o version %s (%s)\n", LM_VERSION, LM_DATE); + if (w83627hf_find(0x2e, &addr) + && w83627hf_find(0x4e, &addr)) { + printk("w83627hf.o: W83627/697 not detected, module not inserted.\n"); + return -ENODEV; + } + normal_isa[0] = addr; + + return i2c_add_driver(&w83627hf_driver); +} + +static void __exit sm_w83627hf_exit(void) +{ + i2c_del_driver(&w83627hf_driver); +} + + + +MODULE_AUTHOR("Frodo Looijaard , " + "Philip Edelbrock , " + "and Mark Studebaker "); +MODULE_DESCRIPTION("W83627HF driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_w83627hf_init); +module_exit(sm_w83627hf_exit); --- linux-old/drivers/sensors/w83781d.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/w83781d.c Sun Feb 26 11:18:42 2006 @@ -0,0 +1,1852 @@ +/* + w83781d.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + 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. +*/ + +/* + Supports following chips: + + Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + as99127f 7 3 0 3 0x31 0x12c3 yes no + as99127f rev.2 (type name = as99127f) 0x31 0x5ca3 yes no + w83627hf 9 3 2 3 0x21 0x5ca3 yes yes(LPC) + w83781d 7 3 0 3 0x10-1 0x5ca3 yes yes + w83782d 9 3 2-4 3 0x30 0x5ca3 yes yes + w83783s 5-6 3 2 1-2 0x40 0x5ca3 yes no + w83791d 10 5 5 3 0x71 0x5ca3 yes no + +*/ + +#include +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include +#include "lm75.h" + +/* RT Table support #defined so we can take it out if it gets bothersome */ +#define W83781D_RT 1 + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0x0290, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_6(w83781d, w83782d, w83783s, w83627hf, as99127f, w83791d); +SENSORS_MODULE_PARM(force_subclients, "List of subclient addresses: " \ + "{bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static int init = 1; +MODULE_PARM(init, "i"); +MODULE_PARM_DESC(init, "Set to zero to bypass chip initialization"); + +/* Constants specified below */ + +/* Length of ISA address segment */ +#define W83781D_EXTENT 8 + +/* Where are the ISA address/data registers relative to the base address */ +#define W83781D_ADDR_REG_OFFSET 5 +#define W83781D_DATA_REG_OFFSET 6 + +/* The W83781D registers */ +/* The W83782D registers for nr=7,8 are in bank 5 */ +#define W83781D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0x554 + (((nr) - 7) * 2))) +#define W83781D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0x555 + (((nr) - 7) * 2))) +#define W83781D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0x550 + (nr) - 7)) + +#define W83791D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0xb4 + (((nr) - 7) * 2))) +#define W83791D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0xb5 + (((nr) - 7) * 2))) +#define W83791D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0xb0 + (nr) - 7)) + +#define W83781D_REG_FAN_MIN(nr) ((nr < 4) ? (0x3a + (nr)) : \ + (0xba + (nr) - 4)) +#define W83781D_REG_FAN(nr) ((nr < 4) ? (0x27 + (nr)) : \ + (0xbc + (nr) - 4)) + +#define W83781D_REG_TEMP2 0x0150 +#define W83781D_REG_TEMP3 0x0250 +#define W83781D_REG_TEMP2_HYST 0x153 +#define W83781D_REG_TEMP3_HYST 0x253 +#define W83781D_REG_TEMP2_CONFIG 0x152 +#define W83781D_REG_TEMP3_CONFIG 0x252 +#define W83781D_REG_TEMP2_OVER 0x155 +#define W83781D_REG_TEMP3_OVER 0x255 + +#define W83781D_REG_TEMP 0x27 +#define W83781D_REG_TEMP_OVER 0x39 +#define W83781D_REG_TEMP_HYST 0x3A +#define W83781D_REG_BANK 0x4E + +#define W83781D_REG_CONFIG 0x40 +#define W83781D_REG_ALARM1 0x41 +#define W83781D_REG_ALARM2 0x42 +#define W83781D_REG_ALARM3 0x450 /* not on W83781D */ + +#define W83781D_REG_IRQ 0x4C +#define W83781D_REG_BEEP_CONFIG 0x4D +#define W83781D_REG_BEEP_INTS1 0x56 +#define W83781D_REG_BEEP_INTS2 0x57 +#define W83781D_REG_BEEP_INTS3 0x453 /* not on W83781D */ + +#define W83781D_REG_VID_FANDIV 0x47 + +#define W83781D_REG_CHIPID 0x49 +#define W83781D_REG_WCHIPID 0x58 +#define W83781D_REG_CHIPMAN 0x4F +#define W83781D_REG_PIN 0x4B + +/* 782D/783S only */ +#define W83781D_REG_VBAT 0x5D + +/* PWM 782D (1-4) and 783S (1-2) only */ +#define W83781D_REG_PWM1 0x5B /* 782d and 783s/627hf datasheets disagree */ + /* on which is which; */ +#define W83781D_REG_PWM2 0x5A /* We follow the 782d convention here, */ + /* However 782d is probably wrong. */ +#define W83781D_REG_PWM3 0x5E +#define W83781D_REG_PWM4 0x5F +#define W83781D_REG_PWMCLK12 0x5C +#define W83781D_REG_PWMCLK34 0x45C + +#define W83791D_REG_PWM1 0x81 +#define W83791D_REG_PWM2 0x83 +#define W83791D_REG_PWM3 0x94 + +#define W83627HF_REG_PWM1 0x01 +#define W83627HF_REG_PWM2 0x03 +#define W83627HF_REG_PWMCLK1 0x00 +#define W83627HF_REG_PWMCLK2 0x02 + +static const u8 regpwm[] = { W83781D_REG_PWM1, W83781D_REG_PWM2, + W83781D_REG_PWM3, W83781D_REG_PWM4 +}; + +static const u8 regpwm_w83791d[] = { W83791D_REG_PWM1, W83791D_REG_PWM2, + W83791D_REG_PWM3 +}; + +#define W83781D_REG_PWM(type, nr) (((type) == w83791d) ? \ + regpwm_w83791d[(nr) - 1] : \ + regpwm[(nr) - 1]) + +#define W83781D_REG_I2C_ADDR 0x48 +#define W83781D_REG_I2C_SUBADDR 0x4A + +/* The following are undocumented in the data sheets however we + received the information in an email from Winbond tech support */ +/* Sensor selection - not on 781d */ +#define W83781D_REG_SCFG1 0x5D +static const u8 BIT_SCFG1[] = { 0x02, 0x04, 0x08 }; +#define W83781D_REG_SCFG2 0x59 +static const u8 BIT_SCFG2[] = { 0x10, 0x20, 0x40 }; +#define W83781D_DEFAULT_BETA 3435 + +/* RT Table registers */ +#define W83781D_REG_RT_IDX 0x50 +#define W83781D_REG_RT_VAL 0x51 + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255)) +#define IN_FROM_REG(val) (((val) * 16 + 5) / 10) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, + 254); +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) + +#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\ + ((val)+5)/10),0,255)) +#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) + +#define ALARMS_FROM_REG(val) (val) +#define PWM_FROM_REG(val) (val) +#define PWM_TO_REG(val) (SENSORS_LIMIT((val),0,255)) +#define BEEPS_FROM_REG(val,type) ((type)==as99127f?(val)^0x7FFF:(val)) +#define BEEPS_TO_REG(val,type) ((type)==as99127f?(~(val))&0x7FFF:(val)&0xffffff) + +#define BEEP_ENABLE_TO_REG(val) ((val)?1:0) +#define BEEP_ENABLE_FROM_REG(val) ((val)?1:0) + +#define DIV_FROM_REG(val) (1 << (val)) + +static inline u8 DIV_TO_REG(long val, enum chips type) +{ + int i; + val = SENSORS_LIMIT(val, 1, + ((type == w83781d || type == as99127f) ? 8 : 128)) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return ((u8) i); +} + +/* There are some complications in a module like this. First off, W83781D chips + may be both present on the SMBus and the ISA bus, and we have to handle + those cases separately at some places. Second, there might be several + W83781D chips available (well, actually, that is probably never done; but + it is a clean illustration of how to handle a case like that). Finally, + a specific chip may be attached to *both* ISA and SMBus, and we would + not like to detect it double. Fortunately, in the case of the W83781D at + least, a register tells us what SMBus address we are on, so that helps + a bit - except if there could be more than one SMBus. Groan. No solution + for this yet. */ + +/* This module may seem overly long and complicated. In fact, it is not so + bad. Quite a lot of bookkeeping is done. A real driver can often cut + some corners. */ + +/* For each registered W83781D, we need to keep some data in memory. That + data is pointed to by w83781d_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new w83781d client is + allocated. */ +struct w83781d_data { + struct i2c_client client; + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + struct i2c_client *lm75; /* for secondary I2C addresses */ + /* pointer to array of 2 subclients */ + + u8 in[10]; /* Register value - 8 & 9 for 782D and 791D only 10 for 791D */ + u8 in_max[10]; /* Register value - 8 & 9 for 782D and 791D only 10 for 791D */ + u8 in_min[10]; /* Register value - 8 & 9 for 782D and 791D only 10 for 791D */ + u8 fan[5]; /* Register value - 4 & 5 for 791D only */ + u8 fan_min[5]; /* Register value - 4 & 5 for 791D only */ + u8 temp; + u8 temp_over; /* Register value */ + u8 temp_hyst; /* Register value */ + u16 temp_add[2]; /* Register value */ + u16 temp_add_over[2]; /* Register value */ + u16 temp_add_hyst[2]; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u32 beeps; /* Register encoding, combined */ + u8 beep_enable; /* Boolean */ + u8 pwm[4]; /* Register value */ + u8 pwmenable[4]; /* bool */ + u16 sens[3]; /* 782D/783S only. + 1 = pentium diode; 2 = 3904 diode; + 3000-5000 = thermistor beta. + Default = 3435. + Other Betas unimplemented */ +#ifdef W83781D_RT + u8 rt[3][32]; /* Register value */ +#endif + u8 vrm; +}; + + +static int w83781d_attach_adapter(struct i2c_adapter *adapter); +static int w83781d_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int w83781d_detach_client(struct i2c_client *client); + +static int w83781d_read_value(struct i2c_client *client, u16 register); +static int w83781d_write_value(struct i2c_client *client, u16 register, + u16 value); +static void w83781d_update_client(struct i2c_client *client); +static void w83781d_init_client(struct i2c_client *client); + + +static void w83781d_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_beep(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83781d_sens(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +#ifdef W83781D_RT +static void w83781d_rt(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +#endif + +static struct i2c_driver w83781d_driver = { + .name = "W83781D sensor driver", + .id = I2C_DRIVERID_W83781D, + .flags = I2C_DF_NOTIFY, + .attach_adapter = w83781d_attach_adapter, + .detach_client = w83781d_detach_client, +}; + +/* The /proc/sys entries */ +/* -- SENSORS SYSCTL START -- */ + +#define W83781D_SYSCTL_IN0 1000 /* Volts * 100 */ +#define W83781D_SYSCTL_IN1 1001 +#define W83781D_SYSCTL_IN2 1002 +#define W83781D_SYSCTL_IN3 1003 +#define W83781D_SYSCTL_IN4 1004 +#define W83781D_SYSCTL_IN5 1005 +#define W83781D_SYSCTL_IN6 1006 +#define W83781D_SYSCTL_IN7 1007 +#define W83781D_SYSCTL_IN8 1008 +#define W83781D_SYSCTL_IN9 1009 +#define W83781D_SYSCTL_FAN1 1101 /* Rotations/min */ +#define W83781D_SYSCTL_FAN2 1102 +#define W83781D_SYSCTL_FAN3 1103 +#define W83781D_SYSCTL_FAN4 1104 +#define W83781D_SYSCTL_FAN5 1105 + +#define W83781D_SYSCTL_TEMP1 1200 /* Degrees Celsius * 10 */ +#define W83781D_SYSCTL_TEMP2 1201 /* Degrees Celsius * 10 */ +#define W83781D_SYSCTL_TEMP3 1202 /* Degrees Celsius * 10 */ +#define W83781D_SYSCTL_VID 1300 /* Volts * 1000 */ +#define W83781D_SYSCTL_VRM 1301 +#define W83781D_SYSCTL_PWM1 1401 +#define W83781D_SYSCTL_PWM2 1402 +#define W83781D_SYSCTL_PWM3 1403 +#define W83781D_SYSCTL_PWM4 1404 +#define W83781D_SYSCTL_SENS1 1501 /* 1, 2, or Beta (3000-5000) */ +#define W83781D_SYSCTL_SENS2 1502 +#define W83781D_SYSCTL_SENS3 1503 +#define W83781D_SYSCTL_RT1 1601 /* 32-entry table */ +#define W83781D_SYSCTL_RT2 1602 /* 32-entry table */ +#define W83781D_SYSCTL_RT3 1603 /* 32-entry table */ +#define W83781D_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */ +#define W83781D_SYSCTL_ALARMS 2001 /* bitvector */ +#define W83781D_SYSCTL_BEEP 2002 /* bitvector */ + +#define W83781D_ALARM_IN0 0x0001 +#define W83781D_ALARM_IN1 0x0002 +#define W83781D_ALARM_IN2 0x0004 +#define W83781D_ALARM_IN3 0x0008 +#define W83781D_ALARM_IN4 0x0100 +#define W83781D_ALARM_IN5 0x0200 +#define W83781D_ALARM_IN6 0x0400 +#define W83782D_ALARM_IN7 0x10000 +#define W83782D_ALARM_IN8 0x20000 +#define W83781D_ALARM_FAN1 0x0040 +#define W83781D_ALARM_FAN2 0x0080 +#define W83781D_ALARM_FAN3 0x0800 +#define W83781D_ALARM_TEMP1 0x0010 +#define W83781D_ALARM_TEMP23 0x0020 /* 781D only */ +#define W83781D_ALARM_TEMP2 0x0020 /* 782D/783S */ +#define W83781D_ALARM_TEMP3 0x2000 /* 782D only */ +#define W83781D_ALARM_CHAS 0x1000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected chip. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ + +/* just a guess - no datasheet */ +static ctl_table as99127f_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_beep}, + {0} +}; + +static ctl_table w83781d_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_beep}, +#ifdef W83781D_RT + {W83781D_SYSCTL_RT1, "rt1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_rt}, + {W83781D_SYSCTL_RT2, "rt2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_rt}, + {W83781D_SYSCTL_RT3, "rt3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_rt}, +#endif + {0} +}; + +/* without pwm3-4 */ +static ctl_table w83782d_isa_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {0} +}; + +/* with pwm3-4 */ +static ctl_table w83782d_i2c_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM4, "pwm4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {W83781D_SYSCTL_SENS3, "sensor3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {0} +}; + +/* w83791D has 10 voltages 5 fans and 3 temps. 2 of the temps are on other + devices. */ +static ctl_table w83791d_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN9, "in9", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN5, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vid}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM4, "pwm4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vrm}, + {0} +}; + +static ctl_table w83783s_dir_table_template[] = { + {W83781D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + /* no in1 to maintain compatibility with 781d and 782d. */ + {W83781D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_in}, + {W83781D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan}, + {W83781D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp}, + {W83781D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_temp_add}, + {W83781D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vid}, + {W83781D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_vrm}, + {W83781D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_fan_div}, + {W83781D_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_alarms}, + {W83781D_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_beep}, + {W83781D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_pwm}, + {W83781D_SYSCTL_SENS1, "sensor1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {W83781D_SYSCTL_SENS2, "sensor2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83781d_sens}, + {0} +}; + + +/* This function is called when: + * w83781d_driver is inserted (when this module is loaded), for each + available adapter + * when a new adapter is inserted (and w83781d_driver is still present) */ +static int w83781d_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, w83781d_detect); +} + +static int w83781d_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, val1 = 0, val2, id; + struct i2c_client *new_client; + struct w83781d_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + int is_isa = i2c_is_isa_adapter(adapter); + enum vendor { winbond, asus } vendid; + + if (!is_isa + && !i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) goto + ERROR0; + + if (is_isa) { + if (!request_region(address, W83781D_EXTENT, "w83781d")) + goto ERROR0; + release_region(address, W83781D_EXTENT); + } + + /* Probe whether there is anything available on this address. Already + done for SMBus clients */ + if (kind < 0) { + if (is_isa) { + +#define REALLY_SLOW_IO + /* We need the timeouts for at least some LM78-like chips. But only + if we read 'undefined' registers. */ + i = inb_p(address + 1); + if (inb_p(address + 2) != i) + goto ERROR0; + if (inb_p(address + 3) != i) + goto ERROR0; + if (inb_p(address + 7) != i) + goto ERROR0; +#undef REALLY_SLOW_IO + + /* Let's just hope nothing breaks here */ + i = inb_p(address + 5) & 0x7f; + outb_p(~i & 0x7f, address + 5); + if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) { + outb_p(i, address + 5); + return 0; + } + } + } + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access w83781d_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct w83781d_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + init_MUTEX(&data->lock); + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &w83781d_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + /* The w8378?d may be stuck in some other bank than bank 0. This may + make reading other information impossible. Specify a force=... or + force_*=... parameter, and the Winbond will be reset to the right + bank. */ + if (kind < 0) { + if (w83781d_read_value(new_client, W83781D_REG_CONFIG) & + 0x80) + goto ERROR1; + + val1 = w83781d_read_value(new_client, W83781D_REG_BANK); + val2 = w83781d_read_value(new_client, W83781D_REG_CHIPMAN); + /* Check for Winbond or Asus ID if in bank 0 */ + if ((!(val1 & 0x07)) && + (((!(val1 & 0x80)) && (val2 != 0xa3) && (val2 != 0xc3)) + || ((val1 & 0x80) && (val2 != 0x5c) && (val2 != 0x12)))) + goto ERROR1; + + /* If Winbond SMBus, check address at 0x48. + Asus doesn't support, except for the as99127f rev.2 */ + if ((!is_isa) && (((!(val1 & 0x80)) && (val2 == 0xa3)) || + ((val1 & 0x80) && (val2 == 0x5c)))) { + if (w83781d_read_value + (new_client, W83781D_REG_I2C_ADDR) != address) + goto ERROR1; + } + } + + /* We have either had a force parameter, or we have already detected the + Winbond. Put it now into bank 0 and Vendor ID High Byte */ + w83781d_write_value(new_client, W83781D_REG_BANK, + (w83781d_read_value(new_client, + W83781D_REG_BANK) & 0x78) | + 0x80); + + /* Determine the chip type. */ + if (kind <= 0) { + /* get vendor ID */ + val2 = w83781d_read_value(new_client, W83781D_REG_CHIPMAN); + if (val2 == 0x5c) + vendid = winbond; + else if (val2 == 0x12) + vendid = asus; + else + goto ERROR1; + + val1 = + w83781d_read_value(new_client, W83781D_REG_WCHIPID); + if ((val1 == 0x10 || val1 == 0x11) && vendid == winbond) + kind = w83781d; + else if (val1 == 0x30 && vendid == winbond) + kind = w83782d; + else if (val1 == 0x40 && vendid == winbond && !is_isa && address == 0x2d) + kind = w83783s; + else if (val1 == 0x21 && vendid == winbond) + kind = w83627hf; + else if (val1 == 0x71 && vendid == winbond && address >= 0x2c) + kind = w83791d; + else if (val1 == 0x31 && !is_isa && address >= 0x28) + kind = as99127f; + else { + if (kind == 0) + printk(KERN_WARNING "w83781d.o: Ignoring " + "'force' parameter for unknown chip " + "at adapter %d, address 0x%02x\n", + i2c_adapter_id(adapter), address); + goto ERROR1; + } + } + + if (kind == w83781d) { + type_name = "w83781d"; + client_name = "W83781D chip"; + } else if (kind == w83782d) { + type_name = "w83782d"; + client_name = "W83782D chip"; + } else if (kind == w83783s) { + type_name = "w83783s"; + client_name = "W83783S chip"; + } else if (kind == w83627hf) { + type_name = "w83627hf"; + client_name = "W83627HF chip"; + } else if (kind == as99127f) { + type_name = "as99127f"; + client_name = "AS99127F chip"; + } else if (kind == w83791d) { + type_name = "w83791d"; + client_name = "W83791D chip"; + } else { +#ifdef DEBUG + printk(KERN_ERR "w83781d.o: Internal error: unknown kind (%d)?!?", + kind); +#endif + err = -ENODEV; + goto ERROR1; + } + + /* Reserve the ISA region */ + if (is_isa) + request_region(address, W83781D_EXTENT, type_name); + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + /* attach secondary i2c lm75-like clients */ + if (!is_isa) { + if (!(data->lm75 = kmalloc(2 * sizeof(struct i2c_client), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR4; + } + id = i2c_adapter_id(adapter); + if(force_subclients[0] == id && force_subclients[1] == address) { + for(i = 2; i <= 3; i++) { + if(force_subclients[i] < 0x48 || + force_subclients[i] > 0x4f) { + printk(KERN_ERR "w83781d.o: Invalid subclient address %d; must be 0x48-0x4f\n", + force_subclients[i]); + goto ERROR5; + } + } + w83781d_write_value(new_client, + W83781D_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) <<4)); + data->lm75[0].addr = force_subclients[2]; + } else { + val1 = w83781d_read_value(new_client, + W83781D_REG_I2C_SUBADDR); + data->lm75[0].addr = 0x48 + (val1 & 0x07); + } + if (kind != w83783s) { + if(force_subclients[0] == id && + force_subclients[1] == address) { + data->lm75[1].addr = force_subclients[3]; + } else { + data->lm75[1].addr = 0x48 + ((val1 >> 4) & 0x07); + } + if(data->lm75[0].addr == data->lm75[1].addr) { + printk(KERN_ERR "w83781d.o: Duplicate addresses 0x%x for subclients.\n", + data->lm75[0].addr); + goto ERROR5; + } + } + if (kind == w83781d) + client_name = "W83781D subclient"; + else if (kind == w83782d) + client_name = "W83782D subclient"; + else if (kind == w83783s) + client_name = "W83783S subclient"; + else if (kind == w83627hf) + client_name = "W83627HF subclient"; + else if (kind == as99127f) + client_name = "AS99127F subclient"; + else if (kind == w83791d) + client_name = "W83791D subclient"; + + + for (i = 0; i <= 1; i++) { + data->lm75[i].data = NULL; /* store all data in w83781d */ + data->lm75[i].adapter = adapter; + data->lm75[i].driver = &w83781d_driver; + data->lm75[i].flags = 0; + strcpy(data->lm75[i].name, client_name); + if ((err = i2c_attach_client(&(data->lm75[i])))) { + printk(KERN_ERR "w83781d.o: Subclient %d registration at address 0x%x failed.\n", + i, data->lm75[i].addr); + if (i == 1) + goto ERROR6; + goto ERROR5; + } + if (kind == w83783s) + break; + } + } else { + data->lm75 = NULL; + } + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, + type_name, + (kind == as99127f) ? + as99127f_dir_table_template : + (kind == w83781d) ? + w83781d_dir_table_template : + (kind == w83783s) ? + w83783s_dir_table_template : + (kind == w83791d ) ? + w83791d_dir_table_template : + (is_isa || kind == w83627hf) ? + w83782d_isa_dir_table_template : + w83782d_i2c_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR7; + } + data->sysctl_id = i; + + /* Only PWM2 can be disabled */ + for(i = 0; i < 4; i++) + data->pwmenable[i] = 1; + + /* Initialize the chip */ + w83781d_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR7: + if (!is_isa) + i2c_detach_client(& + (((struct + w83781d_data *) (new_client->data))-> + lm75[1])); + ERROR6: + if (!is_isa) + i2c_detach_client(& + (((struct + w83781d_data *) (new_client->data))-> + lm75[0])); + ERROR5: + if (!is_isa) + kfree(((struct w83781d_data *) (new_client->data))->lm75); + ERROR4: + i2c_detach_client(new_client); + ERROR3: + if (is_isa) + release_region(address, W83781D_EXTENT); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int w83781d_detach_client(struct i2c_client *client) +{ + int err; + struct w83781d_data *data = client->data; + + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + (KERN_ERR "w83781d.o: Client deregistration failed, client not detached.\n"); + return err; + } + + if(i2c_is_isa_client(client)) { + release_region(client->addr, W83781D_EXTENT); + } else { + i2c_detach_client(&(data->lm75[0])); + if (data->type != w83783s) + i2c_detach_client(&(data->lm75[1])); + kfree(data->lm75); + } + kfree(data); + + return 0; +} + +/* The SMBus locks itself, usually, but nothing may access the Winbond between + bank switches. ISA access must always be locked explicitly! + We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, + would slow down the W83781D access and should not be necessary. + There are some ugly typecasts here, but the good news is - they should + nowhere else be necessary! */ +static int w83781d_read_value(struct i2c_client *client, u16 reg) +{ + int res, word_sized, bank; + struct i2c_client *cl; + + down(&(((struct w83781d_data *) (client->data))->lock)); + if (i2c_is_isa_client(client)) { + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x50) + || ((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, + client->addr + W83781D_DATA_REG_OFFSET); + } + outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); + res = inb_p(client->addr + W83781D_DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + client->addr + W83781D_ADDR_REG_OFFSET); + res = + (res << 8) + inb_p(client->addr + + W83781D_DATA_REG_OFFSET); + } + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); + } + } else { + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + bank); + if (bank == 0 || bank > 2) { + res = i2c_smbus_read_byte_data(client, reg & 0xff); + } else { + /* switch to subclient */ + cl = + &(((struct w83781d_data *) (client->data))-> + lm75[bank - 1]); + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x50: /* TEMP */ + res = swab16(i2c_smbus_read_word_data(cl, 0)); + break; + case 0x52: /* CONFIG */ + res = i2c_smbus_read_byte_data(cl, 1); + break; + case 0x53: /* HYST */ + res = swab16(i2c_smbus_read_word_data(cl, 2)); + break; + case 0x55: /* OVER */ + default: + res = swab16(i2c_smbus_read_word_data(cl, 3)); + break; + } + } + if (bank > 2) + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + 0); + } + up(&(((struct w83781d_data *) (client->data))->lock)); + return res; +} + +static int w83781d_write_value(struct i2c_client *client, u16 reg, u16 value) +{ + int word_sized, bank; + struct i2c_client *cl; + + down(&(((struct w83781d_data *) (client->data))->lock)); + if (i2c_is_isa_client(client)) { + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, + client->addr + W83781D_DATA_REG_OFFSET); + } + outb_p(reg & 0xff, client->addr + W83781D_ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, + client->addr + W83781D_DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + client->addr + W83781D_ADDR_REG_OFFSET); + } + outb_p(value & 0xff, + client->addr + W83781D_DATA_REG_OFFSET); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + client->addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, client->addr + W83781D_DATA_REG_OFFSET); + } + } else { + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + bank); + if (bank == 0 || bank > 2) { + i2c_smbus_write_byte_data(client, reg & 0xff, + value & 0xff); + } else { + /* switch to subclient */ + cl = &(((struct w83781d_data *) (client->data))-> + lm75[bank - 1]); + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x52: /* CONFIG */ + i2c_smbus_write_byte_data(cl, 1, value & 0xff); + break; + case 0x53: /* HYST */ + i2c_smbus_write_word_data(cl, 2, swab16(value)); + break; + case 0x55: /* OVER */ + i2c_smbus_write_word_data(cl, 3, swab16(value)); + break; + } + } + if (bank > 2) + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + 0); + } + up(&(((struct w83781d_data *) (client->data))->lock)); + return 0; +} + +/* Called when we have found a new W83781D. */ +static void w83781d_init_client(struct i2c_client *client) +{ + struct w83781d_data *data = client->data; + int i, p; + int type = data->type; + u8 tmp; + + if(init && type != as99127f) { /* this resets registers we don't have + documentation for on the as99127f */ + /* save these registers */ + i = w83781d_read_value(client, W83781D_REG_BEEP_CONFIG); + p = w83781d_read_value(client, W83781D_REG_PWMCLK12); + /* Reset all except Watchdog values and last conversion values + This sets fan-divs to 2, among others */ + w83781d_write_value(client, W83781D_REG_CONFIG, 0x80); + /* Restore the registers and disable power-on abnormal beep. + This saves FAN 1/2/3 input/output values set by BIOS. */ + w83781d_write_value(client, W83781D_REG_BEEP_CONFIG, i | 0x80); + w83781d_write_value(client, W83781D_REG_PWMCLK12, p); + /* Disable master beep-enable (reset turns it on). + Individual beeps should be reset to off but for some reason + disabling this bit helps some people not get beeped */ + w83781d_write_value(client, W83781D_REG_BEEP_INTS2, 0); + } + + data->vrm = (type == w83791d) ? 90 : 82; + + if ((type != w83781d) && (type != as99127f)) { + tmp = w83781d_read_value(client, W83781D_REG_SCFG1); + for (i = 1; i <= 3; i++) { + if (!(tmp & BIT_SCFG1[i - 1])) { + data->sens[i - 1] = W83781D_DEFAULT_BETA; + } else { + if (w83781d_read_value + (client, + W83781D_REG_SCFG2) & BIT_SCFG2[i - 1]) + data->sens[i - 1] = 1; + else + data->sens[i - 1] = 2; + } + if (type == w83783s && i == 2) + break; + } + } +#ifdef W83781D_RT +/* + Fill up the RT Tables. + We assume that they are 32 bytes long, in order for temp 1-3. + Data sheet documentation is sparse. + We also assume that it is only for the 781D although I suspect + that the others support it as well.... +*/ + + if (init && type == w83781d) { + u16 k = 0; +/* + Auto-indexing doesn't seem to work... + w83781d_write_value(client,W83781D_REG_RT_IDX,0); +*/ + for (i = 0; i < 3; i++) { + int j; + for (j = 0; j < 32; j++) { + w83781d_write_value(client, + W83781D_REG_RT_IDX, + k++); + data->rt[i][j] = + w83781d_read_value(client, + W83781D_REG_RT_VAL); + } + } + } +#endif /* W83781D_RT */ + + if (init && type != as99127f) { + w83781d_write_value(client, W83781D_REG_TEMP2_CONFIG, 0x00); + if (type != w83783s) { + w83781d_write_value(client, W83781D_REG_TEMP3_CONFIG, + 0x00); + } + if (type != w83781d) { + /* enable comparator mode for temp2 and temp3 so + alarm indication will work correctly */ + i = w83781d_read_value(client, W83781D_REG_IRQ); + if (!(i & 0x40)) + w83781d_write_value(client, W83781D_REG_IRQ, + i | 0x40); + } + } + + /* Start monitoring */ + w83781d_write_value(client, W83781D_REG_CONFIG, + (w83781d_read_value(client, + W83781D_REG_CONFIG) & 0xf7) + | 0x01); +} + +static void w83781d_update_client(struct i2c_client *client) +{ + struct w83781d_data *data = client->data; + int i; + + down(&data->update_lock); + + if (time_after(jiffies - data->last_updated, HZ + HZ / 2) || + time_before(jiffies, data->last_updated) || !data->valid) { + pr_debug(KERN_DEBUG "Starting device update\n"); + + for (i = 0; i <= 9; i++) { + if ((data->type == w83783s) + && (i == 1)) + continue; /* 783S has no in1 */ + if (data->type == w83791d) { + data->in[i] = + w83781d_read_value(client, W83791D_REG_IN(i)); + data->in_min[i] = + w83781d_read_value(client, + W83791D_REG_IN_MIN(i)); + data->in_max[i] = + w83781d_read_value(client, + W83791D_REG_IN_MAX(i)); + } else { + data->in[i] = + w83781d_read_value(client, W83781D_REG_IN(i)); + data->in_min[i] = + w83781d_read_value(client, + W83781D_REG_IN_MIN(i)); + data->in_max[i] = + w83781d_read_value(client, + W83781D_REG_IN_MAX(i)); + } + if ((data->type != w83782d) + && (data->type != w83627hf) && (i == 6) + && (data->type != w83791d)) + break; + + if (data->type != w83791d && i == 8) + break; + } + for (i = 1; i <= 5; i++) { + data->fan[i - 1] = + w83781d_read_value(client, W83781D_REG_FAN(i)); + data->fan_min[i - 1] = + w83781d_read_value(client, + W83781D_REG_FAN_MIN(i)); + if (data->type != w83791d && i == 3) break; + } + if (data->type != w83781d && data->type != as99127f) { + for (i = 1; i <= 4; i++) { + data->pwm[i - 1] = + w83781d_read_value(client, + W83781D_REG_PWM(data->type, i)); + if (((data->type == w83783s) + || (data->type == w83627hf) + || ((data->type == w83782d) + && i2c_is_isa_client(client))) + && i == 2) + break; + } + /* Only PWM2 can be disabled */ + data->pwmenable[1] = (w83781d_read_value(client, + W83781D_REG_PWMCLK12) & 0x08) >> 3; + } + + data->temp = w83781d_read_value(client, W83781D_REG_TEMP); + data->temp_over = + w83781d_read_value(client, W83781D_REG_TEMP_OVER); + data->temp_hyst = + w83781d_read_value(client, W83781D_REG_TEMP_HYST); + data->temp_add[0] = + w83781d_read_value(client, W83781D_REG_TEMP2); + data->temp_add_over[0] = + w83781d_read_value(client, W83781D_REG_TEMP2_OVER); + data->temp_add_hyst[0] = + w83781d_read_value(client, W83781D_REG_TEMP2_HYST); + if (data->type != w83783s) { + data->temp_add[1] = + w83781d_read_value(client, W83781D_REG_TEMP3); + data->temp_add_over[1] = + w83781d_read_value(client, W83781D_REG_TEMP3_OVER); + data->temp_add_hyst[1] = + w83781d_read_value(client, W83781D_REG_TEMP3_HYST); + } + i = w83781d_read_value(client, W83781D_REG_VID_FANDIV); + data->vid = i & 0x0f; + data->vid |= (w83781d_read_value(client, + W83781D_REG_CHIPID) & 0x01) << 4; + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + data->fan_div[2] = (w83781d_read_value(client, + W83781D_REG_PIN) >> 6) & 0x03; + if ((data->type != w83781d) && (data->type != as99127f)) { + i = w83781d_read_value(client, W83781D_REG_VBAT); + data->fan_div[0] |= (i >> 3) & 0x04; + data->fan_div[1] |= (i >> 4) & 0x04; + data->fan_div[2] |= (i >> 5) & 0x04; + } + data->alarms = + w83781d_read_value(client, + W83781D_REG_ALARM1) + + (w83781d_read_value(client, W83781D_REG_ALARM2) << 8); + if ((data->type == w83782d) || (data->type == w83627hf)) { + data->alarms |= + w83781d_read_value(client, + W83781D_REG_ALARM3) << 16; + } + i = w83781d_read_value(client, W83781D_REG_BEEP_INTS2); + data->beep_enable = i >> 7; + data->beeps = ((i & 0x7f) << 8) + + w83781d_read_value(client, W83781D_REG_BEEP_INTS1); + if ((data->type != w83781d) && (data->type != as99127f) + && (data->type != w83791d)) { + data->beeps |= + w83781d_read_value(client, + W83781D_REG_BEEP_INTS3) << 16; + } + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +/* The next few functions are the call-back functions of the /proc/sys and + sysctl files. Which function is used is defined in the ctl_table in + the extra1 field. + Each function must return the magnitude (power of 10 to divide the date + with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must + put a maximum of *nrels elements in results reflecting the data of this + file, and set *nrels to the number it actually put in it, if operation== + SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from + results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE. + Note that on SENSORS_PROC_REAL_READ, I do not check whether results is + large enough (by checking the incoming value of *nrels). This is not very + good practice, but as long as you put less than about 5 values in results, + you can assume it is large enough. */ +static void w83781d_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int nr = ctl_name - W83781D_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = IN_FROM_REG(data->in_min[nr]); + results[1] = IN_FROM_REG(data->in_max[nr]); + results[2] = IN_FROM_REG(data->in[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->in_min[nr] = IN_TO_REG(results[0]); + w83781d_write_value(client, W83781D_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + data->in_max[nr] = IN_TO_REG(results[1]); + w83781d_write_value(client, W83781D_REG_IN_MAX(nr), + data->in_max[nr]); + } + } +} + +void w83781d_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int nr = ctl_name - W83781D_SYSCTL_FAN1 + 1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + results[1] = FAN_FROM_REG(data->fan[nr - 1], + DIV_FROM_REG(data->fan_div[nr - 1])); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr - 1] = + FAN_TO_REG(results[0], + DIV_FROM_REG(data->fan_div[nr-1])); + w83781d_write_value(client, + W83781D_REG_FAN_MIN(nr), + data->fan_min[nr - 1]); + } + } +} + +void w83781d_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + w83781d_write_value(client, W83781D_REG_TEMP_OVER, + data->temp_over); + } + if (*nrels_mag >= 2) { + data->temp_hyst = TEMP_TO_REG(results[1]); + w83781d_write_value(client, W83781D_REG_TEMP_HYST, + data->temp_hyst); + } + } +} + +void w83781d_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int nr = ctl_name - W83781D_SYSCTL_TEMP2; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = LM75_TEMP_FROM_REG(data->temp_add_over[nr]); + results[1] = LM75_TEMP_FROM_REG(data->temp_add_hyst[nr]); + results[2] = LM75_TEMP_FROM_REG(data->temp_add[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_add_over[nr] = + LM75_TEMP_TO_REG(results[0]); + w83781d_write_value(client, + nr ? W83781D_REG_TEMP3_OVER : + W83781D_REG_TEMP2_OVER, + data->temp_add_over[nr]); + } + if (*nrels_mag >= 2) { + data->temp_add_hyst[nr] = + LM75_TEMP_TO_REG(results[1]); + w83781d_write_value(client, + nr ? W83781D_REG_TEMP3_HYST : + W83781D_REG_TEMP2_HYST, + data->temp_add_hyst[nr]); + } + } +} + + +void w83781d_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void w83781d_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->vrm; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) + data->vrm = results[0]; + } +} + +void w83781d_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void w83781d_beep(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int val; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable); + results[1] = BEEPS_FROM_REG(data->beeps, data->type); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 2) { + data->beeps = BEEPS_TO_REG(results[1], data->type); + w83781d_write_value(client, W83781D_REG_BEEP_INTS1, + data->beeps & 0xff); + if ((data->type != w83781d) && + (data->type != as99127f)) { + w83781d_write_value(client, + W83781D_REG_BEEP_INTS3, + ((data-> beeps) >> 16) & + 0xff); + } + val = (data->beeps >> 8) & 0x7f; + } else if (*nrels_mag >= 1) + val = + w83781d_read_value(client, + W83781D_REG_BEEP_INTS2) & + 0x7f; + if (*nrels_mag >= 1) { + data->beep_enable = BEEP_ENABLE_TO_REG(results[0]); + w83781d_write_value(client, W83781D_REG_BEEP_INTS2, + val | data->beep_enable << 7); + } + } +} + +void w83781d_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int old, old2, old3 = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); + results[2] = DIV_FROM_REG(data->fan_div[2]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = w83781d_read_value(client, W83781D_REG_VID_FANDIV); + /* w83781d and as99127f don't have extended divisor bits */ + if ((data->type != w83781d) && data->type != as99127f) { + old3 = + w83781d_read_value(client, W83781D_REG_VBAT); + } + if (*nrels_mag >= 3) { + data->fan_div[2] = + DIV_TO_REG(results[2], data->type); + old2 = w83781d_read_value(client, W83781D_REG_PIN); + old2 = + (old2 & 0x3f) | ((data->fan_div[2] & 0x03) << 6); + w83781d_write_value(client, W83781D_REG_PIN, old2); + if ((data->type != w83781d) && + (data->type != as99127f)) { + old3 = + (old3 & 0x7f) | + ((data->fan_div[2] & 0x04) << 5); + } + } + if (*nrels_mag >= 2) { + data->fan_div[1] = + DIV_TO_REG(results[1], data->type); + old = + (old & 0x3f) | ((data->fan_div[1] & 0x03) << 6); + if ((data->type != w83781d) && + (data->type != as99127f)) { + old3 = + (old3 & 0xbf) | + ((data->fan_div[1] & 0x04) << 4); + } + } + if (*nrels_mag >= 1) { + data->fan_div[0] = + DIV_TO_REG(results[0], data->type); + old = + (old & 0xcf) | ((data->fan_div[0] & 0x03) << 4); + w83781d_write_value(client, W83781D_REG_VID_FANDIV, + old); + if ((data->type != w83781d) && + (data->type != as99127f)) { + old3 = + (old3 & 0xdf) | + ((data->fan_div[0] & 0x04) << 3); + w83781d_write_value(client, + W83781D_REG_VBAT, + old3); + } + } + } +} + +void w83781d_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int nr = 1 + ctl_name - W83781D_SYSCTL_PWM1; + int j, k; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83781d_update_client(client); + results[0] = PWM_FROM_REG(data->pwm[nr - 1]); + results[1] = data->pwmenable[nr - 1]; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->pwm[nr - 1] = PWM_TO_REG(results[0]); + w83781d_write_value(client, + W83781D_REG_PWM(data->type, nr), + data->pwm[nr - 1]); + } + /* only PWM2 can be enabled/disabled */ + if (*nrels_mag >= 2 && nr == 2) { + j = w83781d_read_value(client, W83781D_REG_PWMCLK12); + k = w83781d_read_value(client, W83781D_REG_BEEP_CONFIG); + if(results[1]) { + if(!(j & 0x08)) + w83781d_write_value(client, + W83781D_REG_PWMCLK12, j | 0x08); + if(k & 0x10) + w83781d_write_value(client, + W83781D_REG_BEEP_CONFIG, k & 0xef); + data->pwmenable[1] = 1; + } else { + if(j & 0x08) + w83781d_write_value(client, + W83781D_REG_PWMCLK12, j & 0xf7); + if(!(k & 0x10)) + w83781d_write_value(client, + W83781D_REG_BEEP_CONFIG, j | 0x10); + data->pwmenable[1] = 0; + } + } + } +} + +void w83781d_sens(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int nr = 1 + ctl_name - W83781D_SYSCTL_SENS1; + u8 tmp; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->sens[nr - 1]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + switch (results[0]) { + case 1: /* PII/Celeron diode */ + tmp = w83781d_read_value(client, + W83781D_REG_SCFG1); + w83781d_write_value(client, + W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr - + 1]); + tmp = w83781d_read_value(client, + W83781D_REG_SCFG2); + w83781d_write_value(client, + W83781D_REG_SCFG2, + tmp | BIT_SCFG2[nr - + 1]); + data->sens[nr - 1] = results[0]; + break; + case 2: /* 3904 */ + tmp = w83781d_read_value(client, + W83781D_REG_SCFG1); + w83781d_write_value(client, + W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr - + 1]); + tmp = w83781d_read_value(client, + W83781D_REG_SCFG2); + w83781d_write_value(client, + W83781D_REG_SCFG2, + tmp & ~BIT_SCFG2[nr - + 1]); + data->sens[nr - 1] = results[0]; + break; + case W83781D_DEFAULT_BETA: /* thermistor */ + tmp = w83781d_read_value(client, + W83781D_REG_SCFG1); + w83781d_write_value(client, + W83781D_REG_SCFG1, + tmp & ~BIT_SCFG1[nr - + 1]); + data->sens[nr - 1] = results[0]; + break; + default: + printk + (KERN_ERR "w83781d.o: Invalid sensor type %ld; must be 1, 2, or %d\n", + results[0], W83781D_DEFAULT_BETA); + break; + } + } + } +} + +#ifdef W83781D_RT +static void w83781d_rt(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83781d_data *data = client->data; + int nr = 1 + ctl_name - W83781D_SYSCTL_RT1; + int i; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + for (i = 0; i < 32; i++) { + results[i] = data->rt[nr - 1][i]; + } + *nrels_mag = 32; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 32) + *nrels_mag = 32; + for (i = 0; i < *nrels_mag; i++) { + /* fixme: no bounds checking 0-255 */ + data->rt[nr - 1][i] = results[i]; + w83781d_write_value(client, W83781D_REG_RT_IDX, i); + w83781d_write_value(client, W83781D_REG_RT_VAL, + data->rt[nr - 1][i]); + } + } +} +#endif + +static int __init sm_w83781d_init(void) +{ + printk(KERN_INFO "w83781d.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&w83781d_driver); +} + +static void __exit sm_w83781d_exit(void) +{ + i2c_del_driver(&w83781d_driver); +} + + + +MODULE_AUTHOR("Frodo Looijaard , " + "Philip Edelbrock , " + "and Mark Studebaker "); +MODULE_DESCRIPTION("W83781D driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_w83781d_init); +module_exit(sm_w83781d_exit); --- linux-old/drivers/sensors/w83792d.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/w83792d.c Sun Feb 26 11:18:42 2006 @@ -0,0 +1,1489 @@ +/* + w83792d.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2004, 2005 Winbond Electronics Corp. + Chunhao Huang + + 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: + 1. This driver is only for 2.4 kernel(2.4.10 or later), 2.6 kernel + need a different driver. + 2. This driver is only for Winbond W83792D C version device, there + are also some motherboards with B version W83792D device. The + calculation method to in6-in7(measured value, limits) is a little + different between C and B version. C or B version can be identified + by CR[0x49h]. + 3. The function of chassis open detection need further test. + 4. The function of vid and vrm has not been finished, because I'm NOT + very familiar with them. If someone can finish it, that's good, + then please delete this note 4. +*/ + +/* + Supports following chips: + + Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + w83792d 9 7 3 3 0x7a 0x5ca3 yes no +*/ + +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" +#include + +/* 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_1(w83792d); +SENSORS_MODULE_PARM(force_subclients, "List of subclient addresses: " \ + "{bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static int init; +MODULE_PARM(init, "i"); +MODULE_PARM_DESC(init, "Set to one for chip initialization"); + +/* Enable/Disable w83792d debugging output */ +/* #define W83792D_DEBUG 1 */ + +/* Constants specified below */ +#define W83792D_REG_GPIO_EN 0x1A +#define W83792D_REG_CONFIG 0x40 +#define W83792D_REG_I2C_ADDR 0x48 +#define W83792D_REG_CHIPID 0x49 /* contains version ID: A/B/C */ +#define W83792D_REG_I2C_SUBADDR 0x4A +#define W83792D_REG_PIN 0x4B +#define W83792D_REG_IRQ 0x4C +#define W83792D_REG_BANK 0x4E +#define W83792D_REG_CHIPMAN 0x4F /* contains the vendor ID */ +#define W83792D_REG_WCHIPID 0x58 /* contains the chip ID */ +#define W83792D_REG_VID_IN_B 0x17 /* ctroll in0/in1 's limit modifiability */ + +static const u8 W83792D_REG_IN[9] = { + 0x20, /* Vcore A in DataSheet */ + 0x21, /* Vcore B in DataSheet */ + 0x22, /* VIN0 in DataSheet */ + 0x23, /* VIN1 in DataSheet */ + 0x24, /* VIN2 in DataSheet */ + 0x25, /* VIN3 in DataSheet */ + 0x26, /* 5VCC in DataSheet */ + 0xB0, /* 5VSB in DataSheet */ + 0xB1 /* VBAT in DataSheet */ +}; +#define W83792D_REG_LOW_BITS1 0x3E /* Low Bits I in DataSheet */ +#define W83792D_REG_LOW_BITS2 0x3F /* Low Bits II in DataSheet */ +static const u8 W83792D_REG_IN_MAX[9] = { + 0x2B, /* Vcore A High Limit in DataSheet */ + 0x2D, /* Vcore B High Limit in DataSheet */ + 0x2F, /* VIN0 High Limit in DataSheet */ + 0x31, /* VIN1 High Limit in DataSheet */ + 0x33, /* VIN2 High Limit in DataSheet */ + 0x35, /* VIN3 High Limit in DataSheet */ + 0x37, /* 5VCC High Limit in DataSheet */ + 0xB4, /* 5VSB High Limit in DataSheet */ + 0xB6 /* VBAT High Limit in DataSheet */ +}; +static const u8 W83792D_REG_IN_MIN[9] = { + 0x2C, /* Vcore A Low Limit in DataSheet */ + 0x2E, /* Vcore B Low Limit in DataSheet */ + 0x30, /* VIN0 Low Limit in DataSheet */ + 0x32, /* VIN1 Low Limit in DataSheet */ + 0x34, /* VIN2 Low Limit in DataSheet */ + 0x36, /* VIN3 Low Limit in DataSheet */ + 0x38, /* 5VCC Low Limit in DataSheet */ + 0xB5, /* 5VSB Low Limit in DataSheet */ + 0xB7 /* VBAT Low Limit in DataSheet */ +}; + +static const u8 W83792D_REG_FAN[7] = { + 0x28, /* FAN 1 Count in DataSheet */ + 0x29, /* FAN 2 Count in DataSheet */ + 0x2A, /* FAN 3 Count in DataSheet */ + 0xB8, /* FAN 4 Count in DataSheet */ + 0xB9, /* FAN 5 Count in DataSheet */ + 0xBA, /* FAN 6 Count in DataSheet */ + 0xBE /* FAN 7 Count in DataSheet */ +}; +static const u8 W83792D_REG_FAN_MIN[7] = { + 0x3B, /* FAN 1 Count Low Limit in DataSheet */ + 0x3C, /* FAN 2 Count Low Limit in DataSheet */ + 0x3D, /* FAN 3 Count Low Limit in DataSheet */ + 0xBB, /* FAN 4 Count Low Limit in DataSheet */ + 0xBC, /* FAN 5 Count Low Limit in DataSheet */ + 0xBD, /* FAN 6 Count Low Limit in DataSheet */ + 0xBF /* FAN 7 Count Low Limit in DataSheet */ +}; +#define W83792D_REG_FAN_CFG 0x84 /* FAN Configuration in DataSheet */ +static const u8 W83792D_REG_PWM[7] = { + 0x81, /* FAN 1 Duty Cycle, be used to control */ + 0x83, /* FAN 2 Duty Cycle, be used to control */ + 0x94, /* FAN 3 Duty Cycle, be used to control */ + 0xA3, /* FAN 4 Duty Cycle, be used to control */ + 0xA4, /* FAN 5 Duty Cycle, be used to control */ + 0xA5, /* FAN 6 Duty Cycle, be used to control */ + 0xA6 /* FAN 7 Duty Cycle, be used to control */ +}; + +#define W83792D_REG_TEMP1 0x27 /* TEMP 1 in DataSheet */ +#define W83792D_REG_TEMP1_OVER 0x39 /* TEMP 1 High Limit in DataSheet */ +#define W83792D_REG_TEMP1_HYST 0x3A /* TEMP 1 Low Limit in DataSheet */ +static const u8 W83792D_REG_TEMP_ADD[2][7] = { + { 0xC0, /* TEMP 2 in DataSheet */ + 0xC1, /* TEMP 2(0.5 deg) in DataSheet */ + 0xC5, /* TEMP 2 Over High part in DataSheet */ + 0xC6, /* TEMP 2 Over Low part in DataSheet */ + 0xC3, /* TEMP 2 Thyst High part in DataSheet */ + 0xC4, /* TEMP 2 Thyst Low part in DataSheet */ + 0xC2 }, /* TEMP 2 Config in DataSheet */ + { 0xC8, /* TEMP 3 in DataSheet */ + 0xC9, /* TEMP 3(0.5 deg) in DataSheet */ + 0xCD, /* TEMP 3 Over High part in DataSheet */ + 0xCE, /* TEMP 3 Over Low part in DataSheet */ + 0xCB, /* TEMP 3 Thyst High part in DataSheet */ + 0xCC, /* TEMP 3 Thyst Low part in DataSheet */ + 0xCA } /* TEMP 3 Config in DataSheet */ +}; + +static const u8 W83792D_REG_FAN_DIV[4] = { + 0x47, /* contains FAN2 and FAN1 Divisor */ + 0x5B, /* contains FAN4 and FAN3 Divisor */ + 0x5C, /* contains FAN6 and FAN5 Divisor */ + 0x9E /* contains FAN7 Divisor. */ +}; + +#define W83792D_REG_ALARM1 0xA9 /* realtime status register1 */ +#define W83792D_REG_ALARM2 0xAA /* realtime status register2 */ +#define W83792D_REG_ALARM3 0xAB /* realtime status register3 */ +#define W83792D_REG_CASE_OPEN 0x42 /* Bit 5: Case Open status bit */ +#define W83792D_REG_CASE_OPEN_CLR 0x44 /* Bit 7: Case Open CLR_CHS/Reset bit */ + +static const u8 W83792D_REG_THERMAL[3] = { + 0x85, /* SmartFanI: Fan1 target value */ + 0x86, /* SmartFanI: Fan2 target value */ + 0x96 /* SmartFanI: Fan3 target value */ +}; + +static const u8 W83792D_REG_FAN_TOL[3] = { + 0x87, /* (bit3-0)SmartFan Fan1 tolerance */ + 0x87, /* (bit7-4)SmartFan Fan2 tolerance */ + 0x97 /* (bit3-0)SmartFan Fan3 tolerance */ +}; + +static const u8 W83792D_REG_POINTS[3][4] = { + { 0x85, /* SmartFanII: Fan1 temp point 1 */ + 0xE3, /* SmartFanII: Fan1 temp point 2 */ + 0xE4, /* SmartFanII: Fan1 temp point 3 */ + 0xE5 }, /* SmartFanII: Fan1 temp point 4 */ + { 0x86, /* SmartFanII: Fan2 temp point 1 */ + 0xE6, /* SmartFanII: Fan2 temp point 2 */ + 0xE7, /* SmartFanII: Fan2 temp point 3 */ + 0xE8 }, /* SmartFanII: Fan2 temp point 4 */ + { 0x96, /* SmartFanII: Fan3 temp point 1 */ + 0xE9, /* SmartFanII: Fan3 temp point 2 */ + 0xEA, /* SmartFanII: Fan3 temp point 3 */ + 0xEB } /* SmartFanII: Fan3 temp point 4 */ +}; + +static const u8 W83792D_REG_LEVELS[3][4] = { + { 0x88, /* (bit3-0) SmartFanII: Fan1 Non-Stop */ + 0x88, /* (bit7-4) SmartFanII: Fan1 Level 1 */ + 0xE0, /* (bit7-4) SmartFanII: Fan1 Level 2 */ + 0xE0 }, /* (bit3-0) SmartFanII: Fan1 Level 3 */ + { 0x89, /* (bit3-0) SmartFanII: Fan2 Non-Stop */ + 0x89, /* (bit7-4) SmartFanII: Fan2 Level 1 */ + 0xE1, /* (bit7-4) SmartFanII: Fan2 Level 2 */ + 0xE1 }, /* (bit3-0) SmartFanII: Fan2 Level 3 */ + { 0x98, /* (bit3-0) SmartFanII: Fan3 Non-Stop */ + 0x98, /* (bit7-4) SmartFanII: Fan3 Level 1 */ + 0xE2, /* (bit7-4) SmartFanII: Fan3 Level 2 */ + 0xE2 } /* (bit3-0) SmartFanII: Fan3 Level 3 */ +}; + +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), 1, 254); +} + +#define IN_FROM_REG(nr,val) (((nr)<=1)?(val*2): \ + ((((nr)==6)||((nr)==7))?(val*6):(val*4))) +#define IN_TO_REG(nr,val) (((nr)<=1)?(val/2): \ + ((((nr)==6)||((nr)==7))?(val/6):(val/4))) +#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10) +#define TEMP_TO_REG(val) (SENSORS_LIMIT((val>=0)?((val)/10):((val)/10+256), 0, 255)) +#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) +#define DIV_FROM_REG(val) (1 << (val)) + +#ifdef W83792D_DEBUG +#define ENTER() printk(KERN_DEBUG "w83792d: ENTERING %s, line: %d\n", __FUNCTION__, __LINE__); +#define LEAVE() printk(KERN_DEBUG "w83792d: LEAVING %s, line: %d\n", __FUNCTION__, __LINE__); +#else +#define ENTER() +#define LEAVE() +#endif + +struct w83792d_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + struct i2c_client *lm75; /* for secondary I2C addresses */ + /* pointer to array of 2 subclients */ + + u8 in[9]; /* Register value */ + u8 in_max[9]; /* Register value */ + u8 in_min[9]; /* Register value */ + u16 low_bits; /* Register value */ + u8 has_fan; /* Bit vector */ + u8 fan[7]; /* Register value */ + u8 fan_min[7]; /* Register value */ + u8 fan_cfg; /* Configure Fan Mode */ + u8 temp1[3]; /* Register value */ + u8 temp_add[2][7]; /* Register value */ + u8 fan_div[7]; /* Fan Divisor */ + /*u8 vid; Register encoding, combined */ + u8 pwm[7]; /* We only consider the first 3 set of pwm, + although 792 chip has 7 set of pwm. */ + u8 pwm_flag[7]; /* indicates PWM or DC mode: 1->PWM; 0->DC */ + /* u8 vrm; VRM version */ + u32 alarms; /* realtime status register encoding,combined */ + u8 chassis[2]; /* [0]->Chassis status, [1]->CLR_CHS */ + u8 thermal_cruise[3]; /* Smart FanI: Fan1,2,3 target value */ + u8 fan_tolerance[3]; /* Fan1,2,3 tolerance(Smart Fan I/II) */ + u8 sf2_points[3][4]; /* Smart FanII: Fan1,2,3 temperature points */ + u8 sf2_levels[3][4]; /* Smart FanII: Fan1,2,3 duty cycle levels */ +}; + +/* Read the w83792d register value, only use bank 0 of the 792 chip */ +static inline int +w83792d_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* Write value into the w83792d registers, only use bank 0 of the 792 chip */ +static inline int +w83792d_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int w83792d_attach_adapter(struct i2c_adapter *adapter); +static int w83792d_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int w83792d_detach_client(struct i2c_client *client); + +static void w83792d_init_client(struct i2c_client *client); +static void w83792d_update_client(struct i2c_client *client); +#ifdef W83792D_DEBUG +static void w83792d_print_debug(struct w83792d_data *data); +#endif +static void w83792d_in(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +/*static void w83792d_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); */ +static void w83792d_set_fan_div(struct i2c_client *client, + int nr, u8 newdiv); +static void w83792d_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_chassis(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_pwm_flag(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_fan_cfg(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_thermal_cruise(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_fan_tolerance(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_sf2_points(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void w83792d_sf2_levels(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver w83792d_driver = { + .name = "W83792D sensor driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = w83792d_attach_adapter, + .detach_client = w83792d_detach_client, +}; + +/* The /proc/sys entries */ +/* -- SENSORS SYSCTL START -- */ + +#define W83792D_SYSCTL_IN0 1000 +#define W83792D_SYSCTL_IN1 1001 +#define W83792D_SYSCTL_IN2 1002 +#define W83792D_SYSCTL_IN3 1003 +#define W83792D_SYSCTL_IN4 1004 +#define W83792D_SYSCTL_IN5 1005 +#define W83792D_SYSCTL_IN6 1006 +#define W83792D_SYSCTL_IN7 1007 +#define W83792D_SYSCTL_IN8 1008 +#define W83792D_SYSCTL_FAN1 1101 +#define W83792D_SYSCTL_FAN2 1102 +#define W83792D_SYSCTL_FAN3 1103 +#define W83792D_SYSCTL_FAN4 1104 +#define W83792D_SYSCTL_FAN5 1105 +#define W83792D_SYSCTL_FAN6 1106 +#define W83792D_SYSCTL_FAN7 1107 + +#define W83792D_SYSCTL_TEMP1 1200 +#define W83792D_SYSCTL_TEMP2 1201 +#define W83792D_SYSCTL_TEMP3 1202 +/*#define W83792D_SYSCTL_VID 1300 +#define W83792D_SYSCTL_VRM 1301*/ +#define W83792D_SYSCTL_PWM_FLAG 1400 +#define W83792D_SYSCTL_PWM1 1401 +#define W83792D_SYSCTL_PWM2 1402 +#define W83792D_SYSCTL_PWM3 1403 +#define W83792D_SYSCTL_FAN_CFG 1500 /* control Fan Mode */ +#define W83792D_SYSCTL_FAN_DIV 1501 +#define W83792D_SYSCTL_CHASSIS 1502 /* control Case Open */ +#define W83792D_SYSCTL_ALARMS 1503 + +#define W83792D_SYSCTL_THERMAL_CRUISE 1600 /* Smart Fan I: target value */ +#define W83792D_SYSCTL_FAN_TOLERANCE 1601 /* Smart Fan I/II: tolerance */ +#define W83792D_SYSCTL_SF2_POINTS_FAN1 1602 /* Smart Fan II: Fan1 points */ +#define W83792D_SYSCTL_SF2_POINTS_FAN2 1603 /* Smart Fan II: Fan2 points */ +#define W83792D_SYSCTL_SF2_POINTS_FAN3 1604 /* Smart Fan II: Fan3 points */ +#define W83792D_SYSCTL_SF2_LEVELS_FAN1 1605 /* Smart Fan II: Fan1 levels */ +#define W83792D_SYSCTL_SF2_LEVELS_FAN2 1606 /* Smart Fan II: Fan2 levels */ +#define W83792D_SYSCTL_SF2_LEVELS_FAN3 1607 /* Smart Fan II: Fan3 levels */ + +#define W83792D_ALARM_IN0 0x0001 +#define W83792D_ALARM_IN1 0x0002 +#define W83792D_ALARM_IN2 0x0100 +#define W83792D_ALARM_IN3 0x0200 +#define W83792D_ALARM_IN4 0x0400 +#define W83792D_ALARM_IN5 0x0800 +#define W83792D_ALARM_IN6 0x1000 +#define W83792D_ALARM_IN7 0x80000 +#define W83792D_ALARM_IN8 0x100000 +#define W83792D_ALARM_TEMP1 0x0004 +#define W83792D_ALARM_TEMP2 0x0008 +#define W83792D_ALARM_TEMP3 0x0010 +#define W83792D_ALARM_FAN1 0x0020 +#define W83792D_ALARM_FAN2 0x0040 +#define W83792D_ALARM_FAN3 0x0080 +#define W83792D_ALARM_FAN4 0x200000 +#define W83792D_ALARM_FAN5 0x400000 +#define W83792D_ALARM_FAN6 0x800000 +#define W83792D_ALARM_FAN7 0x8000 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for detected chip, + W83792D has 9 voltages 7 fans and 3 temperatures. */ +static ctl_table w83792d_dir_table_template[] = +{ + {W83792D_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_in}, + {W83792D_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_FAN5, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_FAN6, "fan6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_FAN7, "fan7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan}, + {W83792D_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_temp}, + {W83792D_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_temp_add}, + {W83792D_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_temp_add}, + /*{W83792D_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_vid}, */ + {W83792D_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan_div}, + {W83792D_SYSCTL_ALARMS, "alarms", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_alarms}, + {W83792D_SYSCTL_CHASSIS, "chassis", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_chassis}, + {W83792D_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_pwm}, + {W83792D_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_pwm}, + {W83792D_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_pwm}, + {W83792D_SYSCTL_PWM_FLAG, "pwm_flag", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_pwm_flag}, + {W83792D_SYSCTL_FAN_CFG, "fan_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_fan_cfg}, + /*{W83792D_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83792d_vrm}, */ + {W83792D_SYSCTL_THERMAL_CRUISE, "thermal_cruise", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_thermal_cruise}, + {W83792D_SYSCTL_FAN_TOLERANCE, "fan_tolerance", NULL, 0, 0644, + NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_fan_tolerance}, + {W83792D_SYSCTL_SF2_POINTS_FAN1, "sf2_points_fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_sf2_points}, + {W83792D_SYSCTL_SF2_POINTS_FAN2, "sf2_points_fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_sf2_points}, + {W83792D_SYSCTL_SF2_POINTS_FAN3, "sf2_points_fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_sf2_points}, + {W83792D_SYSCTL_SF2_LEVELS_FAN1, "sf2_levels_fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_sf2_levels}, + {W83792D_SYSCTL_SF2_LEVELS_FAN2, "sf2_levels_fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_sf2_levels}, + {W83792D_SYSCTL_SF2_LEVELS_FAN3, "sf2_levels_fan3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83792d_sf2_levels}, + {0} +}; + +/* This function is called when: + * w83792d_driver is inserted (when this module is loaded), for each + available adapter + * when a new adapter is inserted (and w83792d_driver is still present) */ +static int w83792d_attach_adapter(struct i2c_adapter *adapter) +{ + int i_tmp; + ENTER() + i_tmp = i2c_detect(adapter, &addr_data, w83792d_detect); + LEAVE() + return i_tmp; +} + +static int w83792d_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, val1 = 0, val2 = 0, id; + struct i2c_client *new_client; + struct w83792d_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + ENTER() + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + LEAVE() + 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 w83792d_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct w83792d_data), GFP_KERNEL))) { + printk(KERN_ERR "w83792d: Out of memory in w83792d_detect (new_client).\n"); + err = -ENOMEM; + LEAVE() + goto ERROR0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &w83792d_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + if (kind < 0) { + if (w83792d_read_value(new_client, W83792D_REG_CONFIG)&0x80) { + LEAVE() + goto ERROR1; + } + val1 = w83792d_read_value(new_client, W83792D_REG_BANK); + val2 = w83792d_read_value(new_client, W83792D_REG_CHIPMAN); +#ifdef W83792D_DEBUG + printk(KERN_DEBUG "w83792d: val1 is: %d, val2 is: %d\n", + val1, val2); +#endif + /* Check for Winbond ID if in bank 0 */ + if (!(val1 & 0x07)) { /* is Bank0 */ + if (((!(val1 & 0x80)) && (val2 != 0xa3)) || + ((val1 & 0x80) && (val2 != 0x5c))) { + LEAVE() + goto ERROR1; + } + } + /* check address at 0x48. */ + if (w83792d_read_value(new_client, W83792D_REG_I2C_ADDR) + != address) { + LEAVE() + goto ERROR1; + } + } + + /* We have either had a force parameter, or we have already detected + the Winbond. Put it now into bank 0 and Vendor ID High Byte */ + w83792d_write_value(new_client, W83792D_REG_BANK, + (w83792d_read_value(new_client, + W83792D_REG_BANK) & 0x78) | + 0x80); + + /* Determine the chip type. */ + if (kind <= 0) { + /* get vendor ID */ + val2 = w83792d_read_value(new_client, W83792D_REG_CHIPMAN); + if (val2 != 0x5c) { /* the vendor is NOT Winbond */ + LEAVE() + goto ERROR1; + } + val1 = w83792d_read_value(new_client, W83792D_REG_WCHIPID); + if (val1 == 0x7a) { + kind = w83792d; + } else { + if (kind == 0) + printk(KERN_WARNING "w83792d: Ignoring " + "'force' parameter for unknown chip " + "at adapter %d, address 0x%02x\n", + i2c_adapter_id(adapter), address); + LEAVE() + goto ERROR1; + } + } + + if (kind == w83792d) { + type_name = "w83792d"; + client_name = "W83792D chip"; + } else { + printk(KERN_ERR "w83792d: Internal error: unknown kind (%d)?!?", + kind); + LEAVE() + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + data->valid = 0; + data->has_fan = 0x07; /* at least 3 fan inputs */ + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) { + LEAVE() + goto ERROR1; + } + + /* attach secondary i2c lm75-like clients */ + if (!(data->lm75 = kmalloc(2 * sizeof(struct i2c_client), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR2; + } + id = i2c_adapter_id(adapter); + if(force_subclients[0] == id && force_subclients[1] == address) { + if(force_subclients[2] < 0x48 || force_subclients[2] > 0x4b) { + printk(KERN_ERR "w83792d.o: Invalid subclient address %d; must be 0x48-0x4b\n", + force_subclients[2]); + goto ERROR5; + } + if(force_subclients[3] < 0x4c || force_subclients[3] > 0x4f) { + printk(KERN_ERR "w83792d.o: Invalid subclient address %d; must be 0x4c-0x4f\n", + force_subclients[3]); + goto ERROR5; + } + w83792d_write_value(new_client, + W83792D_REG_I2C_SUBADDR, + 0x40 | (force_subclients[2] & 0x03) | + ((force_subclients[3] & 0x03) <<4)); + data->lm75[0].addr = force_subclients[2]; + data->lm75[1].addr = force_subclients[3]; + } else { + val1 = w83792d_read_value(new_client, + W83792D_REG_I2C_SUBADDR); + data->lm75[0].addr = 0x48 + (val1 & 0x07); + data->lm75[1].addr = 0x48 + ((val1 >> 4) & 0x07); + if (data->lm75[0].addr == data->lm75[1].addr) + printk(KERN_WARNING "w83792d: Subclients have the same " + "address (0x%02x)! Use force_subclients.\n", + data->lm75[0].addr); + } + client_name = "W83792D subclient"; + + + for (i = 0; i <= 1; i++) { + data->lm75[i].data = NULL; /* store all data in w83792d */ + data->lm75[i].adapter = adapter; + data->lm75[i].driver = &w83792d_driver; + data->lm75[i].flags = 0; + strcpy(data->lm75[i].name, client_name); + if ((err = i2c_attach_client(&(data->lm75[i])))) { + printk(KERN_ERR "w83792d.o: Subclient %d registration at address 0x%x failed.\n", + i, data->lm75[i].addr); + if (i == 1) + goto ERROR6; + goto ERROR5; + } + } + + /* Read GPIO enable register to check if pins for fan 4,5 are used as + GPIO */ + val1 = w83792d_read_value(new_client, W83792D_REG_GPIO_EN); + if (!(val1 & 0x40)) + data->has_fan |= 0x08; /* fan 4 */ + if (!(val1 & 0x20)) + data->has_fan |= 0x10; /* fan 5 */ + + val1 = w83792d_read_value(new_client, W83792D_REG_PIN); + if (val1 & 0x40) + data->has_fan |= 0x20; /* fan 6 */ + if (val1 & 0x04) + data->has_fan |= 0x40; /* fan 7 */ + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, type_name, + w83792d_dir_table_template, THIS_MODULE)) < 0) { + err = i; + goto ERROR7; + } + data->sysctl_id = i; + + /* Initialize the chip */ + w83792d_init_client(new_client); + LEAVE() + return 0; + + ERROR7: + i2c_detach_client(& + (((struct + w83792d_data *) (new_client->data))-> + lm75[1])); + ERROR6: + i2c_detach_client(& + (((struct + w83792d_data *) (new_client->data))-> + lm75[0])); + ERROR5: + kfree(((struct w83792d_data *) (new_client->data))->lm75); + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + ERROR0: + + LEAVE() + return err; +} + +static int w83792d_detach_client(struct i2c_client *client) +{ + int err; + struct w83792d_data *data = client->data; + ENTER() + + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "w83792d: Client deregistration failed, client not detached.\n"); + LEAVE() + return err; + } + i2c_detach_client(&(data->lm75[0])); + i2c_detach_client(&(data->lm75[1])); + kfree(data->lm75); + kfree(data); + + LEAVE() + return 0; +} + +/* Called when we have found a new W83792D. */ +static void w83792d_init_client(struct i2c_client *client) +{ + int temp2_cfg, temp3_cfg; + u8 vid_in_b; + + ENTER() + + if (init) { + w83792d_write_value(client, W83792D_REG_CONFIG, 0x80); + } + /* data->vrm = 90; */ /* maybe need to be modified! */ + + /* Clear the bit6 of W83792D_REG_VID_IN_B(set it into 0): + W83792D_REG_VID_IN_B bit6 = 0: the high/low limit of + vin0/vin1 can be modified by user; + W83792D_REG_VID_IN_B bit6 = 1: the high/low limit of + vin0/vin1 auto-updated, can NOT be modified by user. */ + vid_in_b = w83792d_read_value(client, W83792D_REG_VID_IN_B); + w83792d_write_value(client, W83792D_REG_VID_IN_B, + vid_in_b & 0xbf); + + temp2_cfg = w83792d_read_value(client, W83792D_REG_TEMP_ADD[0][6]); + temp3_cfg = w83792d_read_value(client, W83792D_REG_TEMP_ADD[1][6]); + w83792d_write_value(client, W83792D_REG_TEMP_ADD[0][6], + temp2_cfg & 0xe6); + w83792d_write_value(client, W83792D_REG_TEMP_ADD[1][6], + temp3_cfg & 0xe6); + + /* Start monitoring */ + w83792d_write_value(client, W83792D_REG_CONFIG, (w83792d_read_value( + client, W83792D_REG_CONFIG) & 0xf7) | 0x01); + LEAVE() +} + +static void w83792d_update_client(struct i2c_client *client) +{ + struct w83792d_data *data = client->data; + int i, j; + u8 reg_array_tmp[4], pwm_array_tmp[7], reg_tmp; + + down(&data->update_lock); + + if (time_after(jiffies - data->last_updated, HZ * 3) || + time_before(jiffies, data->last_updated) || !data->valid) { + pr_debug(KERN_DEBUG "Starting device update\n"); + + /* Update the voltages measured value and limits */ + for (i = 0; i < 9; i++) { + data->in[i] = w83792d_read_value(client, + W83792D_REG_IN[i]); + data->in_max[i] = w83792d_read_value(client, + W83792D_REG_IN_MAX[i]); + data->in_min[i] = w83792d_read_value(client, + W83792D_REG_IN_MIN[i]); + } + data->low_bits = w83792d_read_value(client, + W83792D_REG_LOW_BITS1) + + (w83792d_read_value(client, + W83792D_REG_LOW_BITS2) << 8); + + for (i = 0; i < 7; i++) { + /* Update the Fan measured value and limits */ + data->fan[i] = w83792d_read_value(client, + W83792D_REG_FAN[i]); + data->fan_min[i] = w83792d_read_value(client, + W83792D_REG_FAN_MIN[i]); + /* Update the PWM/DC Value and PWM/DC flag */ + pwm_array_tmp[i] = w83792d_read_value(client, + W83792D_REG_PWM[i]); + data->pwm[i] = pwm_array_tmp[i] & 0x0f; + data->pwm_flag[i] = pwm_array_tmp[i] >> 7; + } + data->fan_cfg = w83792d_read_value(client, W83792D_REG_FAN_CFG); + + /* Update the Fan Divisor */ + for (i = 0; i < 4; i++) { + reg_array_tmp[i] = w83792d_read_value(client, W83792D_REG_FAN_DIV[i]); + } + data->fan_div[0] = reg_array_tmp[0] & 0x07; + data->fan_div[1] = (reg_array_tmp[0] >> 4) & 0x07; + data->fan_div[2] = reg_array_tmp[1] & 0x07; + data->fan_div[3] = (reg_array_tmp[1] >> 4) & 0x07; + data->fan_div[4] = reg_array_tmp[2] & 0x07; + data->fan_div[5] = (reg_array_tmp[2] >> 4) & 0x07; + data->fan_div[6] = reg_array_tmp[3] & 0x07; + + for (i = 0; i < 7; i++) { + if (!(data->has_fan & (1 << i))) + continue; + if (data->fan[i] == 0xff && data->fan_div[i] < 7) + w83792d_set_fan_div(client, i, 7); + else if (data->fan[i] < 0x70 && data->fan_div[i] > 0) { + w83792d_set_fan_div(client, i, + data->fan_div[i] - 1); + } else if (data->fan[i] > 0xf8 && + data->fan_div[i] < 7) { + w83792d_set_fan_div(client, i, + data->fan_div[i] + 1); + } + } + + /* Update the Temperature1 measured value and limits */ + data->temp1[0] = w83792d_read_value(client, W83792D_REG_TEMP1); + data->temp1[1] = w83792d_read_value(client, W83792D_REG_TEMP1_OVER); + data->temp1[2] = w83792d_read_value(client, W83792D_REG_TEMP1_HYST); + + /* Update the Temperature2/3 measured value and limits */ + for (i = 0; i < 7; i++) { + data->temp_add[0][i] = w83792d_read_value(client, + W83792D_REG_TEMP_ADD[0][i]); + data->temp_add[1][i] = w83792d_read_value(client, + W83792D_REG_TEMP_ADD[1][i]); + } + + /* Update the VID */ + /* i = w83792d_read_value(client, W83792D_REG_FAN_DIV[0]); + data->vid = i & 0x0f; + data->vid |= + (w83792d_read_value(client, W83792D_REG_CHIPID) & 0x01) + << 4; */ + + /* Update the realtime status */ + data->alarms = w83792d_read_value(client, W83792D_REG_ALARM1) + + (w83792d_read_value(client, W83792D_REG_ALARM2) << 8) + + (w83792d_read_value(client, W83792D_REG_ALARM3) << 16); + + /* Update CaseOpen status and it's CLR_CHS. */ + data->chassis[0] = (w83792d_read_value(client, + W83792D_REG_CASE_OPEN) + >> 5) & 0x01; + data->chassis[1] = (w83792d_read_value(client, + W83792D_REG_CASE_OPEN_CLR) + >> 7) & 0x01; + + /* Update Thermal Cruise/Smart Fan I target value */ + for (i = 0; i < 3; i++) { + data->thermal_cruise[i] = + w83792d_read_value(client, + W83792D_REG_THERMAL[i]) & 0x7f; + } + + /* Update Smart Fan I/II tolerance */ + reg_tmp = w83792d_read_value(client, W83792D_REG_FAN_TOL[0]); + data->fan_tolerance[0] = reg_tmp & 0x0f; + data->fan_tolerance[1] = (reg_tmp >> 4) & 0x0f; + data->fan_tolerance[2] = + w83792d_read_value(client, W83792D_REG_FAN_TOL[2]) & 0x0f; + + /* Update Smart Fan II temperature points */ + for (i = 0; i < 3; i++) { + for (j = 0; j < 4; j++) { + data->sf2_points[i][j] = w83792d_read_value( + client,W83792D_REG_POINTS[i][j]) & 0x7f; + } + } + + /* Update Smart Fan II duty cycle levels */ + for (i = 0; i < 3; i++) { + reg_tmp = w83792d_read_value(client, + W83792D_REG_LEVELS[i][0]); + data->sf2_levels[i][0] = reg_tmp & 0x0f; + data->sf2_levels[i][1] = (reg_tmp >> 4) & 0x0f; + reg_tmp = w83792d_read_value(client, + W83792D_REG_LEVELS[i][2]); + data->sf2_levels[i][2] = (reg_tmp >> 4) & 0x0f; + data->sf2_levels[i][3] = reg_tmp & 0x0f; + } + data->last_updated = jiffies; + data->valid = 1; +#ifdef W83792D_DEBUG + w83792d_print_debug(data); +#endif + } + up(&data->update_lock); +} + +/* This is a function used to debug the message. */ +#ifdef W83792D_DEBUG +static void w83792d_print_debug(struct w83792d_data *data) +{ + int i=0, j=0; + printk(KERN_DEBUG "==========The following is the debug message...========\n"); + printk(KERN_DEBUG "9 set of Voltages: =====>\n"); + for (i=0; i<=8; i++) { + printk(KERN_DEBUG "vin[%d] is: 0x%x\n", i, data->in[i]); + printk(KERN_DEBUG "vin[%d] max is: 0x%x\n", i, data->in_max[i]); + printk(KERN_DEBUG "vin[%d] min is: 0x%x\n", i, data->in_min[i]); + } + printk(KERN_DEBUG "Low Bit1 is: 0x%x\n", data->low_bits & 0xff); + printk(KERN_DEBUG "Low Bit2 is: 0x%x\n", data->low_bits >> 8); + printk(KERN_DEBUG "7 set of Fan Counts and 3 set of Duty Cycles: =====>\n"); + printk(KERN_DEBUG "fan_cfg is: 0x%x\n", data->fan_cfg); + for (i=0; i<=6; i++) { + printk(KERN_DEBUG "fan[%d] is: 0x%x\n", i, data->fan[i]); + printk(KERN_DEBUG "fan[%d] min is: 0x%x\n", i, data->fan_min[i]); + if (i<3) { + printk(KERN_DEBUG "pwm[%d] is: 0x%x\n", i, data->pwm[i]); + printk(KERN_DEBUG "pwm_flag[%d] is: 0x%x\n", i, data->pwm_flag[i]); + } + } + printk(KERN_DEBUG "3 set of Temperatures: =====>\n"); + printk(KERN_DEBUG "temp1 is: 0x%x\n", data->temp1[0]); + printk(KERN_DEBUG "temp1 high limit is: 0x%x\n", data->temp1[1]); + printk(KERN_DEBUG "temp1 low limit is: 0x%x\n", data->temp1[2]); + for (i=0; i<2; i++) { + for (j=0; j<7; j++) { + printk(KERN_DEBUG "temp_add[%d][%d] is: 0x%x\n", i, j, + data->temp_add[i][j]); + } + } + for (i=0; i<=6; i++) { + printk(KERN_DEBUG "fan_div[%d] is: 0x%x\n", i, data->fan_div[i]); + } + printk(KERN_DEBUG "==========End of the debug message...==================\n\n"); +} +#endif + +/* 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. */ + +/* read/write voltage meaured value and limits */ +static void w83792d_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int nr = ctl_name - W83792D_SYSCTL_IN0; + + /* result[0]: low limit, result[1]: high limit, + result[2]: measured value */ + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = IN_FROM_REG(nr, data->in_min[nr]*4); + results[1] = IN_FROM_REG(nr, data->in_max[nr]*4); + /* in7 and in8 do not have low bits, but the formula still + works */ + results[2] = IN_FROM_REG(nr, ((data->in[nr] << 2) | + ((data->low_bits >> (2 * nr)) + & 0x03))); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + /* Write Low limit into register. */ + data->in_min[nr] = SENSORS_LIMIT(IN_TO_REG(nr,results[0])/4, + 0, 255); + w83792d_write_value(client, W83792D_REG_IN_MIN[nr], + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + /* Write High limit into register. */ + data->in_max[nr] = SENSORS_LIMIT(IN_TO_REG(nr,results[1])/4, + 0, 255); + w83792d_write_value(client, W83792D_REG_IN_MAX[nr], + data->in_max[nr]); + } + } +} + +static void w83792d_set_fan_div(struct i2c_client *client, int nr, u8 newdiv) +{ + struct w83792d_data *data = client->data; + int min, old; + u8 tmp; + + min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); + old = FAN_FROM_REG(data->fan[nr], DIV_FROM_REG(data->fan_div[nr])); + data->fan_div[nr] = newdiv; + tmp = w83792d_read_value(client, W83792D_REG_FAN_DIV[nr >> 1]); + tmp &= (nr & 1) ? 0x8f : 0xf8; + tmp |= (nr & 1) ? ((newdiv << 4) & 0x70) : (newdiv & 0x07); + w83792d_write_value(client, W83792D_REG_FAN_DIV[nr >> 1], tmp); + data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + data->fan[nr] = FAN_TO_REG(old, DIV_FROM_REG(data->fan_div[nr])); + w83792d_write_value(client, W83792D_REG_FAN_MIN[nr], + data->fan_min[nr]); +} + +/* read/write fan meaured value and limits */ +static void w83792d_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int nr = ctl_name - W83792D_SYSCTL_FAN1; + + /* result[0]: low limit, result[1]: measured value */ + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + if (data->has_fan & (1 << nr)) { + 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])); + } else { + results[0] = 0; + results[1] = 0; + } + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1 && (data->has_fan & (1 << nr))) { + data->fan_min[nr] = FAN_TO_REG(results[0], + DIV_FROM_REG(data->fan_div[nr])); + w83792d_write_value(client, + W83792D_REG_FAN_MIN[nr], + data->fan_min[nr]); + } + } +} + +/* read/write temperature1 meaured value and limits */ +static void w83792d_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + + /* result[0]: high limit, result[1]: low limit + result[2]: measured value, the order is different with voltage(in) */ + if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = TEMP_FROM_REG(data->temp1[1]); + results[1] = TEMP_FROM_REG(data->temp1[2]); + results[2] = TEMP_FROM_REG(data->temp1[0]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp1[1] = TEMP_TO_REG(results[0]); + w83792d_write_value(client, W83792D_REG_TEMP1_OVER, + data->temp1[1]); + } + if (*nrels_mag >= 2) { + data->temp1[2] = TEMP_TO_REG(results[1]); + w83792d_write_value(client, W83792D_REG_TEMP1_HYST, + data->temp1[2]); + } + } +} + +/* read/write temperature2,3 meaured value and limits */ +static void w83792d_temp_add(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int nr = ctl_name - W83792D_SYSCTL_TEMP2; + int i=0, j=0; + + /* result[0]: high limit, result[1]: low limit + result[2]: measured value, the order is different with voltage(in) */ + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (i=0; i<3; i++) { + j = (i==0) ? 2 : ((i==1)?0:1); + if ((data->temp_add[nr][i*2+1]) & 0x80) { + results[j] = TEMP_FROM_REG(data->temp_add[nr][i*2]) + 5; + } else { + results[j] = TEMP_FROM_REG(data->temp_add[nr][i*2]); + } + } + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_add[nr][2] = TEMP_TO_REG(results[0]); + w83792d_write_value(client, + W83792D_REG_TEMP_ADD[nr][2], + data->temp_add[nr][2]); + if ((results[0]%10) == 0) { + w83792d_write_value(client, + W83792D_REG_TEMP_ADD[nr][3], 0x00); + } else { /* consider the 0.5 degree */ + w83792d_write_value(client, + W83792D_REG_TEMP_ADD[nr][3], 0x80); + } + } + if (*nrels_mag >= 2) { + data->temp_add[nr][4] = TEMP_TO_REG(results[1]); + w83792d_write_value(client, + W83792D_REG_TEMP_ADD[nr][4], + data->temp_add[nr][4]); + if ((results[1]%10) == 0) { + w83792d_write_value(client, + W83792D_REG_TEMP_ADD[nr][5], 0x00); + } else { /* consider the 0.5 degree */ + w83792d_write_value(client, + W83792D_REG_TEMP_ADD[nr][5], 0x80); + } + } + } +} + +/* +void w83792d_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = vid_from_reg(data->vid, data->vrm); + *nrels_mag = 1; + } +} + +void w83792d_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_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]; + } + } +} */ + +/* Read/Write Fan Divisor */ +static void w83792d_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int i = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (i=0; i<7; i++) { + results[i] = DIV_FROM_REG(data->fan_div[i]); + } + *nrels_mag = 7; + } +} + + +/* Under Smart Fan I mode: read/write the Fan1/2/3 target temperature */ +static void w83792d_thermal_cruise(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int i=0; + u8 target_tmp=0, target_mask=0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (i=0; i<3; i++) { + results[i] = data->thermal_cruise[i]; + } + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + for (i=0; i<3; i++) { + if (*nrels_mag < (i+1)) { + return; + } + target_tmp = results[i]; + target_tmp = target_tmp & 0x7f; + target_mask = w83792d_read_value(client, + W83792D_REG_THERMAL[i]) & 0x80; + data->thermal_cruise[i] = SENSORS_LIMIT(target_tmp, 0, 255); + w83792d_write_value(client, W83792D_REG_THERMAL[i], + (data->thermal_cruise[i])|target_mask); + } + } +} + +/* The tolerance of fan1/fan2/fan3, when using Thermal Cruise(Smart Fan I) + or Smart Fan II mode. */ +static void w83792d_fan_tolerance(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int i=0; + u8 tol_tmp, tol_mask; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (i=0; i<3; i++) { + results[i] = data->fan_tolerance[i]; + } + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + for (i=0; i<3; i++) { + if (*nrels_mag < (i+1)) { + return; + } + tol_mask = w83792d_read_value(client, + W83792D_REG_FAN_TOL[i]) & ((i==1)?0x0f:0xf0); + tol_tmp = SENSORS_LIMIT(results[i], 0, 15); + tol_tmp &= 0x0f; + data->fan_tolerance[i] = tol_tmp; + if (i==1) { + tol_tmp <<= 4; + } + w83792d_write_value(client, W83792D_REG_FAN_TOL[i], + tol_mask|tol_tmp); + } + } +} + +/* Under Smart Fan II mode: read/write the Fan1/2/3 temperature points */ +static void w83792d_sf2_points(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int nr = ctl_name - W83792D_SYSCTL_SF2_POINTS_FAN1; + int j=0; + u8 mask_tmp = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (j=0; j<4; j++) { + results[j] = data->sf2_points[nr][j]; + } + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + for (j=0; j<4; j++) { + if (*nrels_mag < (j+1)) { + return; + } + data->sf2_points[nr][j] = SENSORS_LIMIT(results[j], + 0, 127); + mask_tmp = w83792d_read_value(client, + W83792D_REG_POINTS[nr][j]) & 0x80; + w83792d_write_value(client, W83792D_REG_POINTS[nr][j], + mask_tmp|data->sf2_points[nr][j]); + } + } +} + +/* Smart Fan II Duty Cycle1/2/3 of Fan1/2/3. + Notice that: The Non-Stop can NOT be modified by user, + because it is related with some physical characters, + usually set by BIOS. User's modification to it may lead to + Fan's stop, then bring danger. */ +static void w83792d_sf2_levels(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int nr = ctl_name - W83792D_SYSCTL_SF2_LEVELS_FAN1; + int j = 0; + u8 mask_tmp = 0, level_tmp = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (j=0; j<4; j++) { + results[j] = (data->sf2_levels[nr][j] * 100) / 15; + } + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + for (j=1; j<4; j++) { /* start with 1: need ignore Non-Stop */ + if (*nrels_mag < j) { + return; + } + data->sf2_levels[nr][j] = + SENSORS_LIMIT((results[j]*15)/100, 0, 15); + mask_tmp = w83792d_read_value(client, W83792D_REG_LEVELS[nr][j]) + & ((j==3) ? 0xf0 : 0x0f); + if (j==3) { + level_tmp = data->sf2_levels[nr][j]; + } else { + level_tmp = data->sf2_levels[nr][j] << 4; + } + w83792d_write_value(client, W83792D_REG_LEVELS[nr][j], + level_tmp | mask_tmp); + } + } +} + +/* get reatime status of all sensors items: voltage, temp, fan */ +static void w83792d_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +/* Read/Write Chassis status and Reset Chassis. */ +static void w83792d_chassis(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + u8 temp1 = 0, temp2 = 0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = data->chassis[0]; + results[1] = data->chassis[1]; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + data->chassis[1] = SENSORS_LIMIT(results[1], 0 ,1); + temp1 = ((data->chassis[1]) << 7) & 0x80; + temp2 = w83792d_read_value(client, + W83792D_REG_CASE_OPEN_CLR) & 0x7f; + w83792d_write_value(client, + W83792D_REG_CASE_OPEN_CLR, + temp1|temp2); + } +} + +/* Read/Write PWM/DC value of Fan1,Fan2,Fan3, which controls the + Fan Duty Cycle */ +static void w83792d_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int nr = ctl_name - W83792D_SYSCTL_PWM1; + u8 pwm_mask; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = data->pwm[nr]; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + data->pwm[nr] = SENSORS_LIMIT(results[0], 0, 15); + pwm_mask = w83792d_read_value(client,W83792D_REG_PWM[nr]) & 0xf0; + w83792d_write_value(client,W83792D_REG_PWM[nr],pwm_mask|data->pwm[nr]); + } +} + +/* Read/Write PWM/DC mode for Fan1,Fan2,Fan3: + 1->PWM mode, 0->DC mode */ +static void w83792d_pwm_flag(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + int i = 0; + u8 pwm_flag_mask; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + for (i=0; i<3; i++) { + results[i] = data->pwm_flag[i]; + } + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + for (i=0; i<3; i++) { + if (*nrels_mag < (i+1)) { + return; + } + data->pwm_flag[i] = SENSORS_LIMIT(results[i], 0, 1); + pwm_flag_mask = w83792d_read_value(client, + W83792D_REG_PWM[i]) & 0x7f; + w83792d_write_value(client, W83792D_REG_PWM[i], + ((data->pwm_flag[i])<<7)|pwm_flag_mask); + } + } +} + +/* Read/Write Fan mode into:PWM/DC, Thermal Cruise(SmartFanI), SmartFanII + 0->PWM/DC mode, 1->Thermal Cruise mode, 2/3->SmartFanII mode */ +static void w83792d_fan_cfg(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83792d_data *data = client->data; + u8 temp_cfg1, temp_cfg2, temp_cfg3, temp_cfg4; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83792d_update_client(client); + results[0] = (data->fan_cfg) & 0x03; /* Fan1's Mode */ + results[1] = ((data->fan_cfg)>>2) & 0x03; /* Fan2's Mode */ + results[2] = ((data->fan_cfg)>>4) & 0x03; /* Fan3's Mode */ + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag < 3) { + return; + } + temp_cfg1 = SENSORS_LIMIT(results[0], 0, 3); + temp_cfg2 = SENSORS_LIMIT(results[1], 0, 3) << 2; + temp_cfg3 = SENSORS_LIMIT(results[2], 0, 3) << 4; + temp_cfg4 = w83792d_read_value(client,W83792D_REG_FAN_CFG) & 0xc0; + data->fan_cfg = ((temp_cfg4|temp_cfg3)|temp_cfg2)|temp_cfg1; + w83792d_write_value(client,W83792D_REG_FAN_CFG,data->fan_cfg); + } +} + +static int __init sm_w83792d_init(void) +{ + ENTER() + + printk(KERN_INFO "w83792d version %s (%s)\n", LM_VERSION, LM_DATE); + + LEAVE() + return i2c_add_driver(&w83792d_driver); +} + +static void __exit sm_w83792d_exit(void) +{ + ENTER() + + i2c_del_driver(&w83792d_driver); + + LEAVE() +} + + +MODULE_AUTHOR("Chunhao Huang @ Winbond"); +MODULE_DESCRIPTION("W83792AD/D driver for linux-2.4"); +MODULE_LICENSE("GPL"); + +module_init(sm_w83792d_init); +module_exit(sm_w83792d_exit); + --- linux-old/drivers/sensors/w83l785ts.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/w83l785ts.c Sun Feb 26 11:18:42 2006 @@ -0,0 +1,380 @@ +/* + * w83l785ts.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2004 Jean Delvare + * + * Inspired from the lm83 driver. The W83L785TS-S is a sensor chip made + * by Winbond. It reports a single external temperature with a 1 deg + * resolution and a 3 deg accuracy. Data sheet can be obtained from + * Winbond's website at: + * http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/W83L785TS-S.pdf + * + * Thanks to James Bolt for benchmarking the read + * error handling mechanism. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +#ifndef I2C_DRIVERID_W83L785TS +#define I2C_DRIVERID_W83L785TS 1047 +#endif + +/* How many retries on register read error */ +#define MAX_RETRIES 5 + +/* + * Address to scan + * Address is fully defined internally and cannot be changed. + */ + +static unsigned short normal_i2c[] = { 0x2e, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* + * Insmod parameters + */ + +SENSORS_INSMOD_1(w83l785ts); + +/* + * The W83L785TS-S registers + * Manufacturer ID is 0x5CA3 for Winbond. + */ + +#define W83L785TS_REG_MAN_ID1 0x4D +#define W83L785TS_REG_MAN_ID2 0x4C +#define W83L785TS_REG_CHIP_ID 0x4E +#define W83L785TS_REG_CONFIG 0x40 +#define W83L785TS_REG_TYPE 0x52 +#define W83L785TS_REG_TEMP 0x27 +#define W83L785TS_REG_TEMP_OVER 0x53 /* not sure about this one */ + +/* + * Conversions + * The W83L785TS-S uses signed 8-bit values. + */ + +#define TEMP_FROM_REG(val) (val) + +/* + * Functions declaration + */ + +static int w83l785ts_attach_adapter(struct i2c_adapter *adapter); +static int w83l785ts_detect(struct i2c_adapter *adapter, int address, unsigned + short flags, int kind); +static int w83l785ts_detach_client(struct i2c_client *client); +static u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval); +static void w83l785ts_update_client(struct i2c_client *client); +static void w83l785ts_temp(struct i2c_client *client, int operation, int + ctl_name, int *nrels_mag, long *results); + +/* + * Driver data (common to all clients) + */ + +static struct i2c_driver w83l785ts_driver = { + .name = "W83L785S-S sensor driver", + .id = I2C_DRIVERID_W83L785TS, + .flags = I2C_DF_NOTIFY, + .attach_adapter = w83l785ts_attach_adapter, + .detach_client = w83l785ts_detach_client, +}; + +/* + * Client data (each client gets its own) + */ + +struct w83l785ts_data { + struct i2c_client client; + int sysctl_id; + + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + s8 temp, temp_over; +}; + +/* + * Proc entries + * These files are created for each detected W83L785TS-S. + */ + +/* -- SENSORS SYSCTL START -- */ + +#define W83L785TS_SYSCTL_TEMP 1200 + +/* -- SENSORS SYSCTL END -- */ + + +static ctl_table w83l785ts_dir_table_template[] = +{ + {W83L785TS_SYSCTL_TEMP, "temp", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83l785ts_temp}, + {0} +}; + +/* + * Real code + */ + +static int w83l785ts_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, w83l785ts_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int w83l785ts_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *new_client; + struct w83l785ts_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { +#ifdef DEBUG + printk(KERN_DEBUG "w83l785ts.o: I2C bus doesn't support " + "byte read mode, skipping.\n"); +#endif + return 0; + } + + if (!(data = kmalloc(sizeof(struct w83l785ts_data), GFP_KERNEL))) { + printk(KERN_ERR "w83l785ts.o: Out of memory in w83l785ts_detect " + "(new_client).\n"); + return -ENOMEM; + } + + /* + * The common I2C client data is placed right after the + * W83L785TS-specific. The W83L785TS-specific data is pointed to by the + * data field from the I2C client data. + */ + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &w83l785ts_driver; + new_client->flags = 0; + + /* + * Now we do the remaining detection. A negative kind means that + * the driver was loaded with no force parameter (default), so we + * must both detect and identify the chip (actually there is only + * one possible kind of chip for now, W83L785TS-S). A zero kind means + * that the driver was loaded with the force parameter, the detection + * step shall be skipped. A positive kind means that the driver + * was loaded with the force parameter and a given kind of chip is + * requested, so both the detection and the identification steps + * are skipped. + */ + + if (kind < 0) { /* detection */ + if (((w83l785ts_read_value(new_client, W83L785TS_REG_CONFIG, 0) + & 0x80) != 0x00) + || ((w83l785ts_read_value(new_client, W83L785TS_REG_TYPE, 0) + & 0xFC) != 0x00)) { +#ifdef DEBUG + printk(KERN_DEBUG "w83l785ts.o: Detection failed at " + "0x%02x.\n", address); +#endif + goto ERROR1; + } + } + + if (kind <= 0) { /* identification */ + u16 man_id; + u8 chip_id; + + man_id = (w83l785ts_read_value(new_client, W83L785TS_REG_MAN_ID1, 0) << 8) + + w83l785ts_read_value(new_client, W83L785TS_REG_MAN_ID2, 0); + chip_id = w83l785ts_read_value(new_client, W83L785TS_REG_CHIP_ID, 0); + if (man_id == 0x5CA3) { /* Winbond */ + if (chip_id == 0x70) + kind = w83l785ts; + } + } + + if (kind <= 0) { /* identification failed */ + printk(KERN_INFO "w83l785ts.o: Unsupported chip.\n"); + goto ERROR1; + } + + if (kind == w83l785ts) { + type_name = "w83l785ts"; + client_name = "W83L785TS-S chip"; + } else { + printk(KERN_ERR "w83l785ts.o: Unknown kind %d.\n", kind); + goto ERROR1; + } + + /* + * OK, we got a valid chip so we can fill in the remaining client + * fields. + */ + + strcpy(new_client->name, client_name); + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* + * Tell the I2C layer a new client has arrived. + */ + + if ((err = i2c_attach_client(new_client))) { +#ifdef DEBUG + printk(KERN_ERR "w83l785ts.o: Failed attaching client.\n"); +#endif + goto ERROR1; + } + + /* + * Register a new directory entry. + */ + + if ((err = i2c_register_entry(new_client, type_name, + w83l785ts_dir_table_template, THIS_MODULE)) < 0) { +#ifdef DEBUG + printk(KERN_ERR "w83l785ts.o: Failed registering directory " + "entry.\n"); +#endif + goto ERROR2; + } + data->sysctl_id = err; + + /* + * Initialize the W83L785TS chip + * Nothing yet, assume it is already started. + */ + + return 0; + + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(data); + return err; +} + +static int w83l785ts_detach_client(struct i2c_client *client) +{ + int err; + + i2c_deregister_entry(((struct w83l785ts_data *) (client->data))->sysctl_id); + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "w83l785ts.o: Client deregistration failed, " + "client not detached.\n"); + return err; + } + + kfree(client->data); + return 0; +} + +static u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval) +{ + int value, i; + + /* Frequent read errors have been reported on Asus boards, so we + * retry on read errors. If it still fails (unlikely), return the + * default value requested by the caller. */ + for (i = 1; i <= MAX_RETRIES; i++) { + value = i2c_smbus_read_byte_data(client, reg); + if (value >= 0) + return value; + printk(KERN_WARNING "w83l785ts.o: Read failed, will retry " + "in %d.\n", i); + mdelay(i); + } + + printk(KERN_ERR "w83l785ts.o: Couldn't read value from register. " + "Please report.\n"); + return defval; +} + +static void w83l785ts_update_client(struct i2c_client *client) +{ + struct w83l785ts_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ * 2) + || (jiffies < data->last_updated) + || !data->valid) { +#ifdef DEBUG + printk(KERN_DEBUG "w83l785ts.o: Updating data.\n"); +#endif + data->temp = w83l785ts_read_value(client, W83L785TS_REG_TEMP, + data->temp); + data->temp_over = w83l785ts_read_value(client, + W83L785TS_REG_TEMP_OVER, data->temp_over); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static void w83l785ts_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83l785ts_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) { + *nrels_mag = 0; /* magnitude */ + } else if (operation == SENSORS_PROC_REAL_READ) { + w83l785ts_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp); + *nrels_mag = 2; + } +} + +static int __init sm_w83l785ts_init(void) +{ + printk(KERN_INFO "w83l785ts.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&w83l785ts_driver); +} + +static void __exit sm_w83l785ts_exit(void) +{ + i2c_del_driver(&w83l785ts_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("W83L785TS-S sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_w83l785ts_init); +module_exit(sm_w83l785ts_exit); --- linux-old/drivers/sensors/xeontemp.c Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/xeontemp.c Sun Feb 26 11:18:42 2006 @@ -0,0 +1,384 @@ +/* + xeontemp.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999,2003 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. +*/ + +/* The Xeon temperature sensor looks just like an ADM1021 with the remote + sensor only. There is are no ID registers so detection is difficult. */ + +#include +#include +#include +#include +#include +#define LM_DATE "20060214" +#define LM_VERSION "2.10.0" + +#ifndef I2C_DRIVERID_XEONTEMP +#define I2C_DRIVERID_XEONTEMP 1045 +#endif + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x18, 0x1a, 0x29, 0x2b, + 0x4c, 0x4e, SENSORS_I2C_END +}; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(xeontemp); + +/* xeontemp constants specified below */ + +/* The registers */ +/* Read-only */ +#define XEONTEMP_REG_REMOTE_TEMP 0x01 +#define XEONTEMP_REG_STATUS 0x02 +/* These use different addresses for reading/writing */ +#define XEONTEMP_REG_CONFIG_R 0x03 +#define XEONTEMP_REG_CONFIG_W 0x09 +#define XEONTEMP_REG_CONV_RATE_R 0x04 +#define XEONTEMP_REG_CONV_RATE_W 0x0A +/* limits */ +#define XEONTEMP_REG_REMOTE_TOS_R 0x07 +#define XEONTEMP_REG_REMOTE_TOS_W 0x0D +#define XEONTEMP_REG_REMOTE_THYST_R 0x08 +#define XEONTEMP_REG_REMOTE_THYST_W 0x0E +/* write-only */ +#define XEONTEMP_REG_ONESHOT 0x0F + +#define XEONTEMP_ALARM_RTEMP (XEONTEMP_ALARM_RTEMP_HIGH | XEONTEMP_ALARM_RTEMP_LOW\ + | XEONTEMP_ALARM_RTEMP_NA) +#define XEONTEMP_ALARM_ALL XEONTEMP_ALARM_RTEMP + +/* Conversions. Rounding and limit checking is only done on the TO_REG + variants. Note that you should be a bit careful with which arguments + these macros are called: arguments may be evaluated more than once. + Fixing this is just not worth it. */ +/* Conversions note: 1021 uses normal integer signed-byte format*/ +#define TEMP_FROM_REG(val) (val > 127 ? val-256 : val) +#define TEMP_TO_REG(val) (SENSORS_LIMIT((val < 0 ? val+256 : val),0,255)) + +/* Each client has this additional data */ +struct xeontemp_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 remote_temp, remote_temp_os, remote_temp_hyst, alarms; + u8 fail; +}; + +static int xeontemp_attach_adapter(struct i2c_adapter *adapter); +static int xeontemp_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void xeontemp_init_client(struct i2c_client *client); +static int xeontemp_detach_client(struct i2c_client *client); +static int xeontemp_read_value(struct i2c_client *client, u8 reg); +static int xeontemp_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask); +static int xeontemp_write_value(struct i2c_client *client, u8 reg, + u16 value); +static void xeontemp_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, + long *results); +static void xeontemp_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void xeontemp_update_client(struct i2c_client *client); + +/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */ +static int read_only = 0; + + +/* This is the driver that will be inserted */ +static struct i2c_driver xeontemp_driver = { + .name = "Xeon temp sensor driver", + .id = I2C_DRIVERID_XEONTEMP, + .flags = I2C_DF_NOTIFY, + .attach_adapter = xeontemp_attach_adapter, + .detach_client = xeontemp_detach_client, +}; + +/* -- SENSORS SYSCTL START -- */ + +#define XEONTEMP_SYSCTL_REMOTE_TEMP 1201 +#define XEONTEMP_SYSCTL_ALARMS 1203 + +#define XEONTEMP_ALARM_RTEMP_HIGH 0x10 +#define XEONTEMP_ALARM_RTEMP_LOW 0x08 +#define XEONTEMP_ALARM_RTEMP_NA 0x04 + +/* -- SENSORS SYSCTL END -- */ + +/* These files are created for each detected xeontemp. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table xeontemp_dir_table_template[] = { + {XEONTEMP_SYSCTL_REMOTE_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &xeontemp_remote_temp}, + {XEONTEMP_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &xeontemp_alarms}, + {0} +}; + +static int xeontemp_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, xeontemp_detect); +} + +static int xeontemp_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + struct i2c_client *new_client; + struct xeontemp_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto error0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access xeontemp_{read,write}_value. */ + + if (!(data = kmalloc(sizeof(struct xeontemp_data), GFP_KERNEL))) { + err = -ENOMEM; + goto error0; + } + + new_client = &data->client; + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &xeontemp_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + if (kind < 0) { + if ( + (xeontemp_read_value(new_client, XEONTEMP_REG_STATUS) & + 0x03) != 0x00) + goto error1; + } + + /* Determine the chip type. */ + + if (kind <= 0) { + kind = xeontemp; + } + + type_name = "xeontemp"; + client_name = "xeon sensors"; + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + data->type = kind; + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto error3; + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, type_name, + xeontemp_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto error4; + } + data->sysctl_id = i; + + xeontemp_init_client(new_client); + return 0; + + error4: + i2c_detach_client(new_client); + error3: + error1: + kfree(data); + error0: + return err; +} + +static void xeontemp_init_client(struct i2c_client *client) +{ + /* Enable ADC and disable suspend mode */ + xeontemp_write_value(client, XEONTEMP_REG_CONFIG_W, 0); + /* Set Conversion rate to 1/sec (this can be tinkered with) */ + xeontemp_write_value(client, XEONTEMP_REG_CONV_RATE_W, 0x04); +} + +static int xeontemp_detach_client(struct i2c_client *client) +{ + + int err; + + i2c_deregister_entry(((struct xeontemp_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("xeontemp.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; + +} + + +/* All registers are byte-sized */ +static int xeontemp_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* only update value if read succeeded; set fail bit if failed */ +static int xeontemp_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask) +{ + int i; + struct xeontemp_data *data = client->data; + + i = i2c_smbus_read_byte_data(client, reg); + if (i < 0) { + data->fail |= mask; + return i; + } + *val = i; + return 0; +} + +static int xeontemp_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if (read_only > 0) + return 0; + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void xeontemp_update_client(struct i2c_client *client) +{ + struct xeontemp_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + +#ifdef DEBUG + printk("Starting xeontemp update\n"); +#endif + + data->fail = 0; + xeontemp_rd_good(&(data->remote_temp), client, + XEONTEMP_REG_REMOTE_TEMP, XEONTEMP_ALARM_RTEMP); + xeontemp_rd_good(&(data->remote_temp_os), client, + XEONTEMP_REG_REMOTE_TOS_R, XEONTEMP_ALARM_RTEMP); + xeontemp_rd_good(&(data->remote_temp_hyst), client, + XEONTEMP_REG_REMOTE_THYST_R, + XEONTEMP_ALARM_RTEMP); + data->alarms = XEONTEMP_ALARM_ALL; + if (!xeontemp_rd_good(&(data->alarms), client, + XEONTEMP_REG_STATUS, 0)) + data->alarms &= XEONTEMP_ALARM_ALL; + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +void xeontemp_remote_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct xeontemp_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + xeontemp_update_client(client); + results[0] = TEMP_FROM_REG(data->remote_temp_os); + results[1] = TEMP_FROM_REG(data->remote_temp_hyst); + results[2] = TEMP_FROM_REG(data->remote_temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->remote_temp_os = TEMP_TO_REG(results[0]); + xeontemp_write_value(client, + XEONTEMP_REG_REMOTE_TOS_W, + data->remote_temp_os); + } + if (*nrels_mag >= 2) { + data->remote_temp_hyst = TEMP_TO_REG(results[1]); + xeontemp_write_value(client, + XEONTEMP_REG_REMOTE_THYST_W, + data->remote_temp_hyst); + } + } +} + +void xeontemp_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct xeontemp_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + xeontemp_update_client(client); + results[0] = data->alarms | data->fail; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + /* Can't write to it */ + } +} + +static int __init sm_xeontemp_init(void) +{ + printk(KERN_INFO "xeontemp.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&xeontemp_driver); +} + +static void __exit sm_xeontemp_exit(void) +{ + i2c_del_driver(&xeontemp_driver); +} + +MODULE_AUTHOR + ("Frodo Looijaard and Philip Edelbrock "); +MODULE_DESCRIPTION("xeontemp driver"); +MODULE_LICENSE("GPL"); + +MODULE_PARM(read_only, "i"); +MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); + +module_init(sm_xeontemp_init) +module_exit(sm_xeontemp_exit) --- linux-old/include/linux/sensors_compat.h Thu Jan 1 00:00:00 1970 +++ linux/include/linux/sensors_compat.h Sun Feb 26 11:18:42 2006 @@ -0,0 +1,51 @@ +/* + * Stolen from kernel 2.5.69 + * device.h - generic, centralized driver model + * To make it easier to backport from 2.5 + * + * Copyright (c) 2001-2003 Patrick Mochel + * + */ + +#ifndef _SENSORS_COMPAT_H_ +#define _SENSORS_COMPAT_H_ + +#include + +/* debugging and troubleshooting/diagnostic helpers. */ +#define dev_printk(level, dev, format, arg...) \ + printk(level "%s: " format , (dev)->name , ## arg) + +#ifdef DEBUG +#define dev_dbg(dev, format, arg...) \ + dev_printk(KERN_DEBUG , dev , format , ## arg) +#else +#define dev_dbg(dev, format, arg...) do {} while (0) +#endif + +#define dev_err(dev, format, arg...) \ + dev_printk(KERN_ERR , dev , format , ## arg) +#define dev_info(dev, format, arg...) \ + dev_printk(KERN_INFO , dev , format , ## arg) +#define dev_warn(dev, format, arg...) \ + dev_printk(KERN_WARNING , dev , format , ## arg) + + +/* The part below, taken from linux/init.h, is required for compatibility with + kernels 2.4.16 and older, which don't know about __devexit_p. */ + +/* Functions marked as __devexit may be discarded at kernel link time, depending + on config options. Newer versions of binutils detect references from + retained sections to discarded sections and flag an error. Pointers to + __devexit functions must use __devexit_p(function_name), the wrapper will + insert either the function_name or NULL, depending on the config options. + */ +#ifndef __devexit_p +#if defined(MODULE) || defined(CONFIG_HOTPLUG) +#define __devexit_p(x) x +#else +#define __devexit_p(x) NULL +#endif +#endif /* __devexit_p */ + +#endif /* _SENSORS_COMPAT_H_ */ --- linux-old/include/linux/sensors_vid.h Thu Jan 1 00:00:00 1970 +++ linux/include/linux/sensors_vid.h Sun Feb 26 11:18:42 2006 @@ -0,0 +1,93 @@ +/* + sensors_vid.h - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2002-2004 Mark D. Studebaker + With assistance from Trent Piepho + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + This file contains common code for decoding VID pins. + This file is #included in various sensor chip drivers. + As the user is unlikely to load more than one driver which + includes this code we don't worry about the wasted space. + References: VRM x.y DC-DC Converter Design Guidelines, + VRD 10.0 Design Guide, + available at http://developer.intel.com +*/ + +/* + AMD Opteron processors don't follow the Intel VRM spec. + I'm going to "make up" 2.4 as the VRM spec for the Opterons. + No good reason just a mnemonic for the 24x Opteron processor + series + + Opteron VID encoding is: + + 00000 = 1.550 V + 00001 = 1.525 V + . . . . + 11110 = 0.800 V + 11111 = 0.000 V (off) + */ + +/* + Legal val values 00 - 1F except for VRD 10.0, 0x00-0x3f. + vrm is the Intel VRM document version. + Note: vrm version is scaled by 10 and the return value is scaled by 1000 + to avoid floating point in the kernel. +*/ + +static inline int vid_from_reg(int val, int vrm) +{ + int vid; + + switch(vrm) { + + case 100: /* VRD 10.0 */ + if((val & 0x1f) == 0x1f) + return 0; + if((val & 0x1f) <= 0x09 || val == 0x0a) + vid = 10875 - (val & 0x1f) * 250; + else + vid = 18625 - (val & 0x1f) * 250; + if(val & 0x20) + vid -= 125; + vid /= 10; /* only return 3 dec. places for now */ + return vid; + + case 24: /* Opteron processor */ + return(val == 0x1f ? 0 : 1550 - val * 25); + + case 91: /* VRM 9.1 */ + case 90: /* VRM 9.0 */ + return(val == 0x1f ? 0 : + 1850 - val * 25); + + case 85: /* VRM 8.5 */ + return((val & 0x10 ? 25 : 0) + + ((val & 0x0f) > 0x04 ? 2050 : 1250) - + ((val & 0x0f) * 50)); + + case 84: /* VRM 8.4 */ + val &= 0x0f; + /* fall through */ + default: /* VRM 8.2 */ + return(val == 0x1f ? 0 : + val & 0x10 ? 5100 - (val) * 100 : + 2050 - (val) * 50); + } +} --- linux-old/drivers/sensors/Config.in Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/Config.in Sun Feb 26 11:18:42 2006 @@ -0,0 +1,69 @@ +# +# Sensor device configuration +# All depend on CONFIG_I2C_PROC. +# ISA-only devices depend on CONFIG_I2C_ISA also. +# + +if [ "$CONFIG_I2C" = "m" -o "$CONFIG_I2C" = "y" ] ; then +if [ "$CONFIG_I2C_PROC" = "m" -o "$CONFIG_I2C_PROC" = "y" ] ; then + mainmenu_option next_comment + comment 'Hardware sensors support' + + dep_mbool 'Hardware sensors support' CONFIG_SENSORS $CONFIG_I2C $CONFIG_I2C_PROC + + if [ "$CONFIG_SENSORS" != "n" ]; then + dep_tristate ' Analog Devices ADM1021 and compatibles' CONFIG_SENSORS_ADM1021 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Analog Devices ADM1024' CONFIG_SENSORS_ADM1024 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Analog Devices ADM1025' CONFIG_SENSORS_ADM1025 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Analog Devices ADM1026' CONFIG_SENSORS_ADM1026 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Analog Devices ADM1030, ADM1031' CONFIG_SENSORS_ADM1031 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Analog Devices ADM9240 and compatibles' CONFIG_SENSORS_ADM9240 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Asus ASB100' CONFIG_SENSORS_ASB100 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Dallas DS1621 and DS1625' CONFIG_SENSORS_DS1621 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Fintek F71805F' CONFIG_SENSORS_F71805F $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Fujitsu-Siemens Hermes' CONFIG_SENSORS_FSCHER $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Fujitsu-Siemens Poseidon' CONFIG_SENSORS_FSCPOS $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Fujitsu-Siemens Scylla' CONFIG_SENSORS_FSCSCY $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Genesys Logic GL518SM' CONFIG_SENSORS_GL518SM $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Genesys Logic GL520SM' CONFIG_SENSORS_GL520SM $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' HP Maxilife' CONFIG_SENSORS_MAXILIFE $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Intel Xeon Thermal Sensor' CONFIG_SENSORS_XEONTEMP $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' ITE 8705/8712, SiS950' CONFIG_SENSORS_IT87 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Maxim MAX1619' CONFIG_SENSORS_MAX1619 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Maxim MAX6650, MAX6651' CONFIG_SENSORS_MAX6650 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Myson MTP008' CONFIG_SENSORS_MTP008 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM63' CONFIG_SENSORS_LM63 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM75 and compatibles' CONFIG_SENSORS_LM75 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM78' CONFIG_SENSORS_LM78 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM80' CONFIG_SENSORS_LM80 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM83' CONFIG_SENSORS_LM83 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM85, Analog Devices ADM1027' CONFIG_SENSORS_LM85 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM87' CONFIG_SENSORS_LM87 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM90 and compatibles' CONFIG_SENSORS_LM90 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM92' CONFIG_SENSORS_LM92 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor LM93' CONFIG_SENSORS_LM93 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' National Semiconductor PC8736x Sensors' CONFIG_SENSORS_PC87360 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Silicon Integrated Systems Corp. SiS5595' CONFIG_SENSORS_SIS5595 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA + dep_tristate ' SMSC47M1xx Integrated Sensors' CONFIG_SENSORS_SMSC47M1 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA + dep_tristate ' Texas Instruments THMC50 and compatibles' CONFIG_SENSORS_THMC50 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' VIA 686a Integrated Hardware Monitor' CONFIG_SENSORS_VIA686A $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA + dep_tristate ' VIA VT1211 Integrated Sensors' CONFIG_SENSORS_VT1211 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA + dep_tristate ' VIA VT8231 Integrated Sensors' CONFIG_SENSORS_VT8231 $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA + dep_tristate ' Winbond W83781D, W83782D, W83783S, W83627HF, Asus AS99127F' CONFIG_SENSORS_W83781D $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Winbond W83792D' CONFIG_SENSORS_W83792D $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Winbond W83627HF, W83627THF, W83637HF, W83687THF, W83697HF' CONFIG_SENSORS_W83627HF $CONFIG_I2C $CONFIG_I2C_PROC $CONFIG_I2C_ISA + dep_tristate ' Winbond W83L785TS-S' CONFIG_SENSORS_W83L785TS $CONFIG_I2C $CONFIG_I2C_PROC + bool 'Other I2C devices' CONFIG_SENSORS_OTHER + if [ "$CONFIG_SENSORS_OTHER" = "y" ] ; then + dep_tristate ' Brooktree BT869 Video Modulator' CONFIG_SENSORS_BT869 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' DDC Monitor EDID EEPROM' CONFIG_SENSORS_DDCMON $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' EEprom (DIMM) reader ' CONFIG_SENSORS_EEPROM $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Matrix-Orbital LCD Displays' CONFIG_SENSORS_MATORB $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Philips PCF8574 Parallel I/O' CONFIG_SENSORS_PCF8574 $CONFIG_I2C $CONFIG_I2C_PROC + dep_tristate ' Philips PCF8591 D/A and A/D' CONFIG_SENSORS_PCF8591 $CONFIG_I2C $CONFIG_I2C_PROC + fi + fi + endmenu +fi +fi + --- linux-old/Makefile Sun Feb 26 10:30:38 2006 +++ linux/Makefile Sun Feb 26 11:18:42 2006 @@ -195,6 +195,7 @@ DRIVERS-$(CONFIG_ISDN_BOOL) += drivers/isdn/vmlinux-obj.o DRIVERS-$(CONFIG_CRYPTO) += crypto/crypto.o +DRIVERS-$(CONFIG_SENSORS) += drivers/sensors/sensor.o DRIVERS := $(DRIVERS-y) --- linux-old/drivers/Makefile Sat Dec 6 07:14:44 2003 +++ linux/drivers/Makefile Sun Feb 26 11:18:42 2006 @@ -8,7 +8,7 @@ mod-subdirs := dio hil mtd sbus video macintosh usb input telephony ide \ message/i2o message/fusion scsi md ieee1394 pnp isdn atm \ - fc4 net/hamradio i2c acpi bluetooth usb/gadget + fc4 net/hamradio i2c acpi bluetooth usb/gadget sensors subdir-y := parport char block net sound misc media cdrom hotplug subdir-m := $(subdir-y) @@ -45,6 +45,7 @@ # CONFIG_HAMRADIO can be set without CONFIG_NETDEVICE being set -- ch subdir-$(CONFIG_HAMRADIO) += net/hamradio subdir-$(CONFIG_I2C) += i2c +subdir-$(CONFIG_SENSORS) += sensors subdir-$(CONFIG_ACPI_BOOT) += acpi subdir-$(CONFIG_BLUEZ) += bluetooth --- linux-old/drivers/sensors/Makefile Thu Jan 1 00:00:00 1970 +++ linux/drivers/sensors/Makefile Sun Feb 26 11:18:42 2006 @@ -0,0 +1,56 @@ +# +# Makefile for the kernel hardware sensors drivers. +# + +MOD_LIST_NAME := SENSORS_MODULES +O_TARGET := sensor.o + +obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o +obj-$(CONFIG_SENSORS_ADM1024) += adm1024.o +obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o +obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o +obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o +obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o +obj-$(CONFIG_SENSORS_ASB100) += asb100.o +obj-$(CONFIG_SENSORS_BT869) += bt869.o +obj-$(CONFIG_SENSORS_DDCMON) += ddcmon.o +obj-$(CONFIG_SENSORS_DS1621) += ds1621.o +obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o +obj-$(CONFIG_SENSORS_FSCHER) += f71085f.o +obj-$(CONFIG_SENSORS_FSCHER) += fscher.o +obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o +obj-$(CONFIG_SENSORS_FSCSCY) += fscscy.o +obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o +obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_IT87) += it87.o +obj-$(CONFIG_SENSORS_LM63) += lm63.o +obj-$(CONFIG_SENSORS_LM75) += lm75.o +obj-$(CONFIG_SENSORS_LM78) += lm78.o +obj-$(CONFIG_SENSORS_LM80) += lm80.o +obj-$(CONFIG_SENSORS_LM83) += lm83.o +obj-$(CONFIG_SENSORS_LM85) += lm85.o +obj-$(CONFIG_SENSORS_LM87) += lm87.o +obj-$(CONFIG_SENSORS_LM90) += lm90.o +obj-$(CONFIG_SENSORS_LM92) += lm92.o +obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_MAX1619) += max1619.o +obj-$(CONFIG_SENSORS_MAX6650) += max6650.o +obj-$(CONFIG_SENSORS_MAXILIFE) += maxilife.o +obj-$(CONFIG_SENSORS_MTP008) += mtp008.o +obj-$(CONFIG_SENSORS_PC87360) += pc87360.o +obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o +obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o +obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o +obj-$(CONFIG_SENSORS_THMC50) += thmc50.o +obj-$(CONFIG_SENSORS_VIA686A) += via686a.o +obj-$(CONFIG_SENSORS_VT1211) += vt1211.o +obj-$(CONFIG_SENSORS_VT8231) += vt8231.o +obj-$(CONFIG_SENSORS_W83781D) += w83781d.o +obj-$(CONFIG_SENSORS_W83792D) += w83792d.o +obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o +obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_SENSORS_XEONTEMP) += xeontemp.o + +include $(TOPDIR)/Rules.make + --- linux-old/drivers/char/Config.in Sat Jul 31 16:45:19 2004 +++ linux/drivers/char/Config.in Sun Feb 26 11:18:42 2006 @@ -190,6 +190,8 @@ source drivers/i2c/Config.in +source drivers/sensors/Config.in + mainmenu_option next_comment comment 'Mice' tristate 'Bus Mouse Support' CONFIG_BUSMOUSE --- linux-old/drivers/i2c/Config.in Sun Feb 26 11:18:18 2006 +++ linux/drivers/i2c/Config.in Sun Feb 26 11:18:42 2006 @@ -71,6 +71,34 @@ fi # This is needed for automatic patch generation: sensors code starts here + bool 'I2C mainboard interfaces' CONFIG_I2C_MAINBOARD + if [ "$CONFIG_I2C_MAINBOARD" = "y" ]; then + dep_tristate ' Acer Labs ALI 1535' CONFIG_I2C_ALI1535 $CONFIG_I2C + dep_tristate ' Acer Labs ALI 1533 and 1543C' CONFIG_I2C_ALI15X3 $CONFIG_I2C + dep_tristate ' Acer Labs ALI 1563' CONFIG_I2C_ALI1563 $CONFIG_I2C + dep_tristate ' Apple Hydra Mac I/O' CONFIG_I2C_HYDRA $CONFIG_I2C_ALGOBIT + dep_tristate ' AMD 756/766/768/8111 and nVidia nForce' CONFIG_I2C_AMD756 $CONFIG_I2C + if [ "$CONFIG_I2C_AMD756" != "n" ]; then + dep_tristate ' SMBus multiplexing on the Tyan S4882' CONFIG_I2C_AMD756_S4882 $CONFIG_I2C_AMD756 + fi + dep_tristate ' AMD 8111 SMBus 2.0' CONFIG_I2C_AMD8111 $CONFIG_I2C + if [ "$CONFIG_ALPHA" = "y" ]; then + dep_tristate ' DEC Tsunami I2C interface' CONFIG_I2C_TSUNAMI $CONFIG_I2C_ALGOBIT + fi + dep_tristate ' Intel 82801AA, AB, BA, DB' CONFIG_I2C_I801 $CONFIG_I2C + dep_tristate ' Intel i810AA/AB/E and i815' CONFIG_I2C_I810 $CONFIG_I2C_ALGOBIT + dep_tristate ' Intel 82371AB PIIX4(E), 443MX, ServerWorks OSB4/CSB5, SMSC Victory66' CONFIG_I2C_PIIX4 $CONFIG_I2C + dep_tristate ' Nvidia Nforce2/Nforce3' CONFIG_I2C_NFORCE2 $CONFIG_I2C + dep_tristate ' SiS 5595' CONFIG_I2C_SIS5595 $CONFIG_I2C + dep_tristate ' SiS 630/730' CONFIG_I2C_SIS630 $CONFIG_I2C + dep_tristate ' SiS 645/961,645DX/961,735' CONFIG_I2C_SIS645 $CONFIG_I2C $CONFIG_HOTPLUG + dep_tristate ' Savage 4' CONFIG_I2C_SAVAGE4 $CONFIG_I2C_ALGOBIT + dep_tristate ' VIA Technologies, Inc. VT82C586B' CONFIG_I2C_VIA $CONFIG_I2C_ALGOBIT + dep_tristate ' VIA Technologies, Inc. VT596A/B, 686A/B, 8231, 8233, 8233A, 8235' CONFIG_I2C_VIAPRO $CONFIG_I2C + dep_tristate ' Voodoo3 I2C interface' CONFIG_I2C_VOODOO3 $CONFIG_I2C_ALGOBIT + dep_tristate ' Pseudo ISA adapter (for some hardware sensors)' CONFIG_I2C_ISA $CONFIG_I2C + fi + # This is needed for automatic patch generation: sensors code ends here dep_tristate 'I2C device interface' CONFIG_I2C_CHARDEV $CONFIG_I2C --- linux-old/drivers/i2c/Makefile Sun Feb 26 11:18:18 2006 +++ linux/drivers/i2c/Makefile Sun Feb 26 11:18:42 2006 @@ -35,6 +35,28 @@ obj-$(CONFIG_I2C_ALGO_SGI) += i2c-algo-sgi.o # This is needed for automatic patch generation: sensors code starts here +export-objs += i2c-amd756.o + +obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o +obj-$(CONFIG_I2C_ALI15X3) += i2c-ali15x3.o +obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o +obj-$(CONFIG_I2C_AMD756) += i2c-amd756.o +obj-$(CONFIG_I2C_AMD756_S4882) += i2c-amd756-s4882.o +obj-$(CONFIG_I2C_AMD8111) += i2c-amd8111.o +obj-$(CONFIG_I2C_HYDRA) += i2c-hydra.o +obj-$(CONFIG_I2C_I801) += i2c-i801.o +obj-$(CONFIG_I2C_I810) += i2c-i810.o +obj-$(CONFIG_I2C_ISA) += i2c-isa.o +obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o +obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o +obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o +obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o +obj-$(CONFIG_I2C_SIS645) += i2c-sis645.o +obj-$(CONFIG_I2C_SAVAGE4) += i2c-savage4.o +obj-$(CONFIG_I2C_TSUNAMI) += i2c-tsunami.o +obj-$(CONFIG_I2C_VIA) += i2c-via.o +obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o +obj-$(CONFIG_I2C_VOODOO3) += i2c-voodoo3.o # This is needed for automatic patch generation: sensors code ends here include $(TOPDIR)/Rules.make --- linux-old/Documentation/Configure.help Sun Feb 26 11:18:18 2006 +++ linux/Documentation/Configure.help Sun Feb 26 11:18:43 2006 @@ -29462,4 +29462,608 @@ # fill-prefix:" " # adaptive-fill:nil # fill-column:70 +I2C mainboard interfaces +CONFIG_I2C_MAINBOARD + Many modern mainboards have some kind of I2C interface integrated. This + is often in the form of a SMBus, or System Management Bus, which is + basically the same as I2C but which uses only a subset of the I2C + protocol. + + You will also want the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Acer Labs ALI 1535 +CONFIG_I2C_ALI1535 + If you say yes to this option, support will be included for the Acer + Labs ALI 1535 mainboard I2C interface. This can also be + built as a module. + +Acer Labs ALI 1533 and 1543C +CONFIG_I2C_ALI15X3 + If you say yes to this option, support will be included for the Acer + Labs ALI 1533 and 1543C mainboard I2C interfaces. This can also be + built as a module which can be inserted and removed while the kernel + is running. + +Acer Labs ALI 1563 +CONFIG_I2C_ALI1563 + If you say yes to this option, support will be included for the Acer + Labs ALI M1563 mainboard SMBus interface. This can also be built as a + module. + +AMD 756/766/768/8111 and nVidia nForce +CONFIG_I2C_AMD756 + If you say yes to this option, support will be included for the AMD + 756/766/768/8111 and nVidia nForce mainboard I2C interfaces. This can + also be built as a module which can be inserted and removed while the + kernel is running. + +SMBus multiplexing on the Tyan S4882 +CONFIG_I2C_AMD756_S4882 + Enabling this option will add specific SMBus support for the Tyan + S4882 motherboard. On this 4-CPU board, the SMBus is multiplexed + over 8 different channels, where the various memory module EEPROMs + and temperature sensors live. Saying yes here will give you access + to these in addition to the trunk. + +AMD 8111 SMBus 2.0 +CONFIG_I2C_AMD8111 + If you say yes to this option, support will be included for the AMD + 8111 mainboard SMBus 2.0 interface. This can also be + built as a module which can be inserted and removed while the kernel + is running. + +Apple Hydra Mac I/O +CONFIG_I2C_HYDRA + If you say yes to this option, support will be included for the + Hydra mainboard I2C interface. This can also be built as a module + which can be inserted and removed while the kernel is running. + +Intel I801 +CONFIG_I2C_I801 + If you say yes to this option, support will be included for the + Intel I801 mainboard I2C interfaces. "I810" mainboard sensor chips are + generally located on the I801's I2C bus. This can also be + built as a module which can be inserted and removed while the kernel + is running. + +Intel I810/I815 based Mainboard +CONFIG_I2C_I810 + If you say yes to this option, support will be included for the + Intel I810/I810E/I815/I845G mainboard I2C interfaces. The I2C busses + of these chips are generally used only for video devices. For "810" + mainboard sensor chips, use the I801 I2C driver instead. + + This can also be built as a module which can be inserted and removed + while the kernel is running. + +Intel 82371AB PIIX4(E) / ServerWorks OSB4 and CSB5 +CONFIG_I2C_PIIX4 + If you say yes to this option, support will be included for the + Intel PIIX4, PIIX4E, and 443MX, Serverworks OSB4/CSB5, + and SMSC Victory66 mainboard + I2C interfaces. This can also be + built as a module which can be inserted and removed while the kernel + is running. + +Nvidia Nforce2/Nforce3 based Mainboard +CONFIG_I2C_NFORCE2 + If you say yes to this option, support will be included for the + Nvidia Nforce2 and Nforce3 families of mainboard I2C interfaces. + This can also be built as a module which can be inserted and removed + while the kernel is running. + +Silicon Integrated Systems Corp. SiS5595 based Mainboard +CONFIG_I2C_SIS5595 + If you say yes to this option, support will be included for the + SiS5595 mainboard I2C interfaces. For integrated sensors on the + Sis5595, use CONFIG_SENSORS_SIS5595. This can also be + built as a module which can be inserted and removed while the kernel + is running. + +Silicon Integrated Systems Corp. SiS630/730 based Mainboard +CONFIG_I2C_SIS630 + If you say yes to this option, support will be included for the SiS 630 + and 730 mainboard I2C interfaces. This can also be built as a module + which can be inserted and removed while the kernel is running. + +Silicon Integrated Systems Corp. SiS645/961,645DX/961,735 based Mainboard +CONFIG_I2C_SIS645 + If you say yes to this option, support will be included for the SiS 645/961, + 645DX/961 and 735 mainboard I2C interfaces. This can also be built as a module + which can be inserted and removed while the kernel is running. + +VIA Technologies, Inc. VT82C586B +CONFIG_I2C_VIA + If you say yes to this option, support will be included for the VIA + Technologies I2C adapter found on some motherboards. This can also + be built as a module which can be inserted and removed while the + kernel is running. + +VIA Technologies, Inc. VT82C596, 596B, 686A/B, 8233, 8235 +CONFIG_I2C_VIAPRO + If you say yes to this option, support will be included for the VIA + Technologies I2C adapter on these chips. For integrated sensors on the + Via 686A/B, use CONFIG_SENSORS_VIA686A. This can also be + be built as a module which can be inserted and removed while the + kernel is running. + +3DFX Banshee / Voodoo3 +CONFIG_I2C_VOODOO3 + If you say yes to this option, support will be included for the + 3DFX Banshee and Voodoo3 I2C interfaces. The I2C busses on the these + chips are generally used only for video devices. + This can also be + built as a module which can be inserted and removed while the kernel + is running. + +DEC Tsunami 21272 +CONFIG_I2C_TSUNAMI + If you say yes to this option, support will be included for the DEC + Tsunami chipset I2C adapter. Requires the Alpha architecture; + do not enable otherwise. This can also be built as a module which + can be inserted and removed while the kernel is running. + +Pseudo ISA adapter (for hardware sensors modules) +CONFIG_I2C_ISA + This provides support for accessing some hardware sensor chips over + the ISA bus rather than the I2C or SMBus. If you want to do this, + say yes here. This feature can also be built as a module which can + be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Analog Devices ADM1021 and compatibles +CONFIG_SENSORS_ADM1021 + If you say yes here you get support for Analog Devices ADM1021 + and ADM1023 sensor chips and clones: Maxim MAX1617 and MAX1617A, + Genesys Logic GL523SM, National Semi LM84, TI THMC10 and Onsemi + MC1066. This can also be built as a module which can be inserted + and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Analog Devices ADM1024 +CONFIG_SENSORS_ADM1024 + If you say yes here you get support for Analog Devices ADM1024 sensor + chips. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Analog Devices ADM1025 +CONFIG_SENSORS_ADM1025 + If you say yes here you get support for Analog Devices ADM1025 sensor + chips. This can also be built as a module which can be inserted and + removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Analog Devices ADM1026 +CONFIG_SENSORS_ADM1026 + If you say yes here you get support for Analog Devices ADM1026 sensor + chips. This can also be built as a module which can be inserted and + removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Analog Devices ADM1030, ADM1031 +CONFIG_SENSORS_ADM1031 + If you say yes here you get support for Analog Devices ADM1030 and + ADM1031 sensor chips. This can also be built as a module which can + be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Analog Devices ADM9240 and compatibles +CONFIG_SENSORS_ADM9240 + If you say yes here you get support for Analog Devices ADM9240 + sensor chips and clones: the Dallas Semiconductor DS1780 and + the National Semiconductor LM81. This can also be built as a + module which can be inserted and removed while the kernel is + running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Asus ASB100 +CONFIG_SENSORS_ASB100 + If you say yes here you get support for the Asus ASB100 (aka + "Bach") sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find + them in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Dallas DS1621 and DS1625 +CONFIG_SENSORS_DS1621 + If you say yes here you get support for the Dallas DS1621 and DS1625x + sensor chips. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Fintek F71805F +CONFIG_SENSORS_F71805F + If you say yes here you get support for the hardware monitoring + features of the Fintek F71805F/FG Super-I/O chip. This can also be + built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Fujitsu-Siemens Hermes +CONFIG_SENSORS_FSCHER + If you say yes here you get support for the Fujitsu-Siemens Hermes + sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Fujitsu-Siemens Poseidon +CONFIG_SENSORS_FSCPOS + If you say yes here you get support for the Fujitsu-Siemens Poseidon + sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Fujitsu-Siemens Scylla +CONFIG_SENSORS_FSCSCY + If you say yes here you get support for the Fujitsu-Siemens Scylla + sensor chip. This can also be built as a module. This driver may/should + also work with the following Fujitsu-Siemens chips: "Poseidon", + "Poseidon II" and "Hydra". You may have to force loading of the module + for motherboards in these cases. Be careful - those motherboards have + not been tested with this driver. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Genesys Logic GL518SM +CONFIG_SENSORS_GL518SM + If you say yes here you get support for Genesys Logic GL518SM sensor + chips. This can also be built as a module which can be inserted and + removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Genesys Logic GL520SM +CONFIG_SENSORS_GL520SM + If you say yes here you get support for Genesys Logic GL518SM sensor + chips. This can also be built as a module which can be inserted and + removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +HP Maxilife +CONFIG_SENSORS_MAXILIFE + If you say yes here you get support for the HP Maxilife + sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Intel Xeon Thermal Sensor +CONFIG_SENSORS_XEONTEMP + If you say yes here you get support for the Intel Xeon processor + built-in thermal sensor. This can also be built as a module which + can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +ITE 8705, 8712, Sis950 +CONFIG_SENSORS_IT87 + If you say yes here you get support for the ITE 8705 and 8712 and + SiS950 sensor chips. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Maxim MAX1619 +CONFIG_SENSORS_MAX1619 + If you say yes here you get support for the Maxim MAX1619 sensor + chips. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Maxim MAX6650, MAX6651 +CONFIG_SENSORS_MAX6650 + If you say yes here you get support for the Maxim MAX6650 and + MAX6651 sensor chips. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Myson MTP008 +CONFIG_SENSORS_MTP008 + If you say yes here you get support for the Myson MTP008 + sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM63 +CONFIG_SENSORS_LM63 + If you say yes here you get support for National Semiconductor LM63 + sensor chips. This can also be built as a module which can be inserted + and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM75 and compatibles +CONFIG_SENSORS_LM75 + If you say yes here you get support for National Semiconductor LM75 + sensor chips and clones: Dallas Semiconductor DS75 and DS1775 (in + 9-bit precision mode), and TelCom (now Microchip) TCN75. This can + also be built as a module which can be inserted and removed while + the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM78 +CONFIG_SENSORS_LM78 + If you say yes here you get support for National Semiconductor LM78 + sensor chips family: the LM78-J and LM79. Many clone chips will + also work at least somewhat with this driver. This can also be built + as a module which can be inserted and removed while the kernel is + running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM80 +CONFIG_SENSORS_LM80 + If you say yes here you get support for National Semiconductor LM80 + sensor chips. This can also be built as a module which can be + inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM83 +CONFIG_SENSORS_LM83 + If you say yes here you get support for the National Semiconductor + LM83 sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find + them in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM85 +CONFIG_SENSORS_LM85 + If you say yes here you get support for National Semiconductor LM85 + sensor chips and compatibles. Compatible chips include the Analog + Devices ADM1027 and ADT7463 and SMSC EMC6D100 and EMC6D101. This + can also be built as a module which can be inserted and removed + while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM87 +CONFIG_SENSORS_LM87 + If you say yes here you get support for National Semiconductor LM87 + sensor chips. This can also be built as a module which can be + inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM90 +CONFIG_SENSORS_LM90 + If you say yes here you get support for the National Semiconductor + LM90, LM89 and LM99, Analog Devices ADM1032 and ADT7461, and Maxim + MAX6657 and MAX6658 sensor chips. This can also be built as a module. + + You will also need the latest user-space utilities: you can find + them in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM92 +CONFIG_SENSORS_LM92 + If you say yes here you get support for National Semiconductor LM92 + sensor chips. This can also be built as a module which can be + inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor LM93 +CONFIG_SENSORS_LM93 + If you say yes here you get support for National Semiconductor LM93 + sensor chips. This can also be built as a module which can be inserted + and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +National Semiconductor PC8736x Sensors +CONFIG_SENSORS_PC87360 + If you say yes here you get support for the integrated hardware + monitoring in the National Semicoductor PC87360, PC87363, PC87364, + PC87365 and PC87366 Super I/O chips. This can also be built as a + module which can be inserted and removed while the kernel is + running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Philips PCF8574 +CONFIG_SENSORS_PCF8574 + If you say yes here you get support for the Philips PCF8574 + I2C 8-bit Parallel I/O device. + This can also be built as a module which can be + inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Philips PCF8591 +CONFIG_SENSORS_PCF8591 + If you say yes here you get support for the Philips PCF8591 + I2C Quad D/A + Single A/D I/O device. + This can also be built as a module which can be + inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Silicon Integrated Systems Corp. SiS5595 Sensor +CONFIG_SENSORS_SIS5595 + If you say yes here you get support for the integrated sensors in + SiS5595 South Bridges. This can also be built as a module + which can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +SMSC47M1xx Super I/O Fan Support +CONFIG_SENSORS_SMSC47M1 + If you say yes here you get support for the integrated fan + monitoring and control in the SMSC 47M1xx Super I/O chips. + This can also be built as a module + which can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Texas Instruments THMC50 / Analog Devices ADM1022 +CONFIG_SENSORS_THMC50 + If you say yes here you get support for Texas Instruments THMC50 + sensor chips and clones: the Analog Devices ADM1022. + This can also be built as a module which + can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Via VT82C686A/B +CONFIG_SENSORS_VIA686A + If you say yes here you get support for the integrated sensors in + Via 686A/B South Bridges. This can also be built as a module + which can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Via VT1211 Sensors +CONFIG_SENSORS_VT1211 + If you say yes here you get support for the integrated sensors in + the Via VT1211 Super I/O device. This can also be built as a module + which can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Via VT8231 Sensors +CONFIG_SENSORS_VT8231 + If you say yes here you get support for the integrated sensors in + the Via VT8231 device. This can also be built as a module + which can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Winbond W83781D, W83782D, W83783S, W83627HF, AS99127F +CONFIG_SENSORS_W83781D + If you say yes here you get support for the Winbond W8378x series + of sensor chips: the W83781D, W83782D, W83783S and W83627HF, + and the similar Asus AS99127F. This can also be built as a module + which can be inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Winbond W83792D +CONFIG_SENSORS_W83792D + If you say yes here you get support for the Winbond W83792D + sensor chips. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Winbond W83627HF, W83627THF, W83637HF, W83687THF, W83697HF +CONFIG_SENSORS_W83627HF + If you say yes here you get support for the Winbond W836x7 series + of sensor chips: the W83627HF, W83627THF, W83637HF, W83687THF and + W83697HF. This can also be built as a module which can be inserted + and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find + them in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +Winbond W83L785TS-S +CONFIG_SENSORS_W83L785TS + If you say yes here you get support for the Winbond W83L785TS-S + sensor chip. This can also be built as a module. + + You will also need the latest user-space utilities: you can find + them in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + +EEprom (DIMM) reader +CONFIG_SENSORS_EEPROM + If you say yes here you get read-only access to the EEPROM data + available on modern memory DIMMs, and which could theoretically + also be available on other devices. This can also be built as a + module which can be inserted and removed while the kernel is + running. + + You will also need the latest user-space utilities: you can find them + in the lm_sensors package, which you can download at + http://www.lm-sensors.nu/ + # End: --- linux-old/MAINTAINERS Sun Dec 4 21:06:00 2005 +++ linux/MAINTAINERS Sun Feb 26 11:18:43 2006 @@ -1671,6 +1671,11 @@ L: linux-ide@vger.kernel.org S: Supported +SENSORS DRIVERS +L: lm-sensors@lm-sensors.org +W: http://www.lm-sensors.nu/ +S: Maintained + SGI VISUAL WORKSTATION 320 AND 540 P: Bent Hagemark M: bh@sgi.com --- linux-old/arch/i386/kernel/dmi_scan.c Sun Feb 26 10:30:38 2006 +++ linux/arch/i386/kernel/dmi_scan.c Sun Feb 26 11:18:43 2006 @@ -15,6 +15,7 @@ #include "pci-i386.h" unsigned long dmi_broken; +int is_unsafe_smbus; int is_sony_vaio_laptop; struct dmi_header @@ -351,6 +352,19 @@ } /* + * Don't access SMBus on IBM systems which get corrupted eeproms + */ + +static __init int disable_smbus(struct dmi_blacklist *d) +{ + if (is_unsafe_smbus == 0) { + is_unsafe_smbus = 1; + printk(KERN_INFO "%s machine detected. Disabling SMBus accesses.\n", d->ident); + } + return 0; +} + +/* * Check for a Sony Vaio system * * On a Sony system we want to enable the use of the sonypi @@ -671,6 +685,10 @@ NO_MATCH, NO_MATCH, } }, + { disable_smbus, "IBM", { + MATCH(DMI_SYS_VENDOR, "IBM"), + NO_MATCH, NO_MATCH, NO_MATCH + } }, { sony_vaio_laptop, "Sony Vaio", { /* This is a Sony Vaio laptop */ MATCH(DMI_SYS_VENDOR, "Sony Corporation"), MATCH(DMI_PRODUCT_NAME, "PCG-"), --- linux-old/arch/i386/kernel/i386_ksyms.c Sat Mar 20 09:08:17 2004 +++ linux/arch/i386/kernel/i386_ksyms.c Sun Feb 26 11:18:43 2006 @@ -179,6 +179,9 @@ EXPORT_SYMBOL(atomic_dec_and_lock); #endif +extern int is_unsafe_smbus; +EXPORT_SYMBOL(is_unsafe_smbus); + extern int is_sony_vaio_laptop; EXPORT_SYMBOL(is_sony_vaio_laptop);