2005-04-16 15:20:36 -07:00
/*
Copyright ( c ) 1998 , 1999 Frodo Looijaard < frodol @ dds . nl > and
Philip Edelbrock < phil @ netroedge . com >
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
/* Note: we assume there can only be one SIS5595 with one SMBus interface */
/*
Note : all have mfr . ID 0x1039 .
SUPPORTED PCI ID
5595 000 8
Note : these chips contain a 000 8 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 000 8 0540
550 000 8 0550
5513 000 8 5511
5581 000 8 5597
5582 000 8 5597
5597 000 8 5597
5598 000 8 5597 / 5598
630 000 8 0630
645 000 8 0645
646 000 8 0646
648 000 8 064 8
650 000 8 0650
651 000 8 0651
730 000 8 0730
735 000 8 0735
745 000 8 0745
746 000 8 0746
*/
/* TO DO:
* Add Block Transfers ( ugly , but supported by the adapter )
* Add adapter resets
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/pci.h>
# include <linux/ioport.h>
# include <linux/init.h>
# include <linux/i2c.h>
2008-07-14 22:38:33 +02:00
# include <linux/acpi.h>
2010-05-21 18:41:01 +02:00
# include <linux/io.h>
2005-04-16 15:20:36 -07:00
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_646 ,
PCI_DEVICE_ID_SI_648 ,
PCI_DEVICE_ID_SI_650 ,
PCI_DEVICE_ID_SI_651 ,
PCI_DEVICE_ID_SI_730 ,
PCI_DEVICE_ID_SI_735 ,
PCI_DEVICE_ID_SI_745 ,
PCI_DEVICE_ID_SI_746 ,
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 , /* terminates the list */
} ;
/* 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 . */
2005-09-25 16:23:07 +02:00
static u16 force_addr ;
2005-04-16 15:20:36 -07:00
module_param ( force_addr , ushort , 0 ) ;
MODULE_PARM_DESC ( force_addr , " Initialize the base address of the i2c controller " ) ;
2005-09-25 16:37:04 +02:00
static struct pci_driver sis5595_driver ;
2005-09-25 16:23:07 +02:00
static unsigned short sis5595_base ;
2007-07-12 14:12:30 +02:00
static struct pci_dev * sis5595_pdev ;
2005-04-16 15:20:36 -07:00
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 ) ;
}
2009-12-06 17:06:23 +01:00
static int __devinit sis5595_setup ( struct pci_dev * SIS5595_dev )
2005-04-16 15:20:36 -07:00
{
u16 a ;
u8 val ;
int * i ;
2012-01-12 20:32:03 +01:00
int retval ;
2005-04-16 15:20:36 -07:00
/* Look for imposters */
for ( i = blacklist ; * i ! = 0 ; i + + ) {
struct pci_dev * dev ;
dev = pci_get_device ( PCI_VENDOR_ID_SI , * i , NULL ) ;
if ( dev ) {
dev_err ( & SIS5595_dev - > dev , " Looked for SIS5595 but found unsupported device %.4x \n " , * i ) ;
pci_dev_put ( dev ) ;
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 ) {
dev_err ( & SIS5595_dev - > dev , " ACPI base address uninitialized - upgrade BIOS or use force_addr=0xaddr \n " ) ;
return - ENODEV ;
}
if ( force_addr )
sis5595_base = force_addr & ~ ( SIS5595_EXTENT - 1 ) ;
dev_dbg ( & SIS5595_dev - > dev , " ACPI Base address: %04x \n " , sis5595_base ) ;
/* NB: We grab just the two SMBus registers here, but this may still
* interfere with ACPI : - ( */
2008-07-14 22:38:33 +02:00
retval = acpi_check_region ( sis5595_base + SMB_INDEX , 2 ,
sis5595_driver . name ) ;
if ( retval )
return retval ;
2005-09-25 16:37:04 +02:00
if ( ! request_region ( sis5595_base + SMB_INDEX , 2 ,
sis5595_driver . name ) ) {
2005-04-16 15:20:36 -07:00
dev_err ( & SIS5595_dev - > dev , " SMBus registers 0x%04x-0x%04x already in use! \n " ,
sis5595_base + SMB_INDEX , sis5595_base + SMB_INDEX + 1 ) ;
return - ENODEV ;
}
if ( force_addr ) {
dev_info ( & SIS5595_dev - > dev , " forcing ISA address 0x%04X \n " , sis5595_base ) ;
if ( pci_write_config_word ( SIS5595_dev , ACPI_BASE , sis5595_base )
! = PCIBIOS_SUCCESSFUL )
goto error ;
if ( pci_read_config_word ( SIS5595_dev , ACPI_BASE , & a )
! = PCIBIOS_SUCCESSFUL )
goto error ;
if ( ( a & ~ ( SIS5595_EXTENT - 1 ) ) ! = sis5595_base ) {
/* doesn't work for some chips! */
dev_err ( & SIS5595_dev - > dev , " force address failed - not supported? \n " ) ;
goto error ;
}
}
if ( pci_read_config_byte ( SIS5595_dev , SIS5595_ENABLE_REG , & val )
! = PCIBIOS_SUCCESSFUL )
goto error ;
if ( ( val & 0x80 ) = = 0 ) {
dev_info ( & SIS5595_dev - > dev , " enabling ACPI \n " ) ;
if ( pci_write_config_byte ( SIS5595_dev , SIS5595_ENABLE_REG , val | 0x80 )
! = PCIBIOS_SUCCESSFUL )
goto error ;
if ( pci_read_config_byte ( SIS5595_dev , SIS5595_ENABLE_REG , & val )
! = PCIBIOS_SUCCESSFUL )
goto error ;
if ( ( val & 0x80 ) = = 0 ) {
/* doesn't work for some chips? */
dev_err ( & SIS5595_dev - > dev , " ACPI enable failed - not supported? \n " ) ;
goto error ;
}
}
/* Everything is happy */
return 0 ;
error :
release_region ( sis5595_base + SMB_INDEX , 2 ) ;
2012-01-12 20:32:03 +01:00
return - ENODEV ;
2005-04-16 15:20:36 -07:00
}
static int sis5595_transaction ( struct i2c_adapter * adap )
{
int temp ;
int result = 0 ;
int timeout = 0 ;
/* Make sure the SMBus host is ready to start transmitting */
temp = sis5595_read ( SMB_STS_LO ) + ( sis5595_read ( SMB_STS_HI ) < < 8 ) ;
if ( temp ! = 0x00 ) {
2005-06-23 22:18:08 +02:00
dev_dbg ( & adap - > dev , " SMBus busy (%04x). Resetting... \n " , temp ) ;
2005-04-16 15:20:36 -07:00
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 ) {
dev_dbg ( & adap - > dev , " Failed! (%02x) \n " , temp ) ;
2008-07-14 22:38:25 +02:00
return - EBUSY ;
2005-04-16 15:20:36 -07:00
} else {
2008-04-29 23:11:37 +02:00
dev_dbg ( & adap - > dev , " Successful! \n " ) ;
2005-04-16 15:20:36 -07:00
}
}
/* 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 {
msleep ( 1 ) ;
temp = sis5595_read ( SMB_STS_LO ) ;
} while ( ! ( temp & 0x40 ) & & ( timeout + + < MAX_TIMEOUT ) ) ;
/* If the SMBus is still busy, we give up */
2009-05-05 08:39:24 +02:00
if ( timeout > MAX_TIMEOUT ) {
2005-04-16 15:20:36 -07:00
dev_dbg ( & adap - > dev , " SMBus Timeout! \n " ) ;
2008-07-14 22:38:25 +02:00
result = - ETIMEDOUT ;
2005-04-16 15:20:36 -07:00
}
if ( temp & 0x10 ) {
dev_dbg ( & adap - > dev , " Error: Failed bus transaction \n " ) ;
2008-07-14 22:38:25 +02:00
result = - ENXIO ;
2005-04-16 15:20:36 -07:00
}
if ( temp & 0x20 ) {
dev_err ( & adap - > dev , " Bus collision! SMBus may be locked until "
" next hard reset (or not...) \n " ) ;
/* Clock stops and slave is stuck in mid-transmission */
2008-07-14 22:38:25 +02:00
result = - EIO ;
2005-04-16 15:20:36 -07:00
}
temp = sis5595_read ( SMB_STS_LO ) + ( sis5595_read ( SMB_STS_HI ) < < 8 ) ;
if ( temp ! = 0x00 ) {
sis5595_write ( SMB_STS_LO , temp & 0xff ) ;
sis5595_write ( SMB_STS_HI , temp > > 8 ) ;
}
temp = sis5595_read ( SMB_STS_LO ) + ( sis5595_read ( SMB_STS_HI ) < < 8 ) ;
if ( temp ! = 0x00 )
dev_dbg ( & adap - > dev , " Failed reset at end of transaction (%02x) \n " , temp ) ;
return result ;
}
2008-07-14 22:38:25 +02:00
/* Return negative errno on error. */
2005-04-16 15:20:36 -07:00
static s32 sis5595_access ( struct i2c_adapter * adap , u16 addr ,
unsigned short flags , char read_write ,
u8 command , int size , union i2c_smbus_data * data )
{
2008-07-14 22:38:25 +02:00
int status ;
2005-04-16 15:20:36 -07:00
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 ;
default :
2008-04-29 23:11:38 +02:00
dev_warn ( & adap - > dev , " Unsupported transaction %d \n " , size ) ;
2008-07-14 22:38:25 +02:00
return - EOPNOTSUPP ;
2005-04-16 15:20:36 -07:00
}
sis5595_write ( SMB_CTL_LO , ( ( size & 0x0E ) ) ) ;
2008-07-14 22:38:25 +02:00
status = sis5595_transaction ( adap ) ;
if ( status )
return status ;
2005-04-16 15:20:36 -07:00
if ( ( size ! = SIS5595_PROC_CALL ) & &
( ( read_write = = I2C_SMBUS_WRITE ) | | ( size = = SIS5595_QUICK ) ) )
return 0 ;
switch ( size ) {
2008-04-29 23:11:38 +02:00
case SIS5595_BYTE :
2005-04-16 15:20:36 -07:00
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 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 ;
}
2006-09-03 22:39:46 +02:00
static const struct i2c_algorithm smbus_algorithm = {
2005-04-16 15:20:36 -07:00
. smbus_xfer = sis5595_access ,
. functionality = sis5595_func ,
} ;
static struct i2c_adapter sis5595_adapter = {
. owner = THIS_MODULE ,
2008-07-14 22:38:29 +02:00
. class = I2C_CLASS_HWMON | I2C_CLASS_SPD ,
2005-04-16 15:20:36 -07:00
. algo = & smbus_algorithm ,
} ;
2012-01-12 20:32:04 +01:00
static DEFINE_PCI_DEVICE_TABLE ( sis5595_ids ) = {
2005-04-16 15:20:36 -07:00
{ PCI_DEVICE ( PCI_VENDOR_ID_SI , PCI_DEVICE_ID_SI_503 ) } ,
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( pci , sis5595_ids ) ;
static int __devinit sis5595_probe ( struct pci_dev * dev , const struct pci_device_id * id )
{
2007-07-12 14:12:30 +02:00
int err ;
2005-04-16 15:20:36 -07:00
if ( sis5595_setup ( dev ) ) {
dev_err ( & dev - > dev , " SIS5595 not detected, module not inserted. \n " ) ;
return - ENODEV ;
}
2007-02-17 19:13:42 +01:00
/* set up the sysfs linkage to our parent device */
2005-04-16 15:20:36 -07:00
sis5595_adapter . dev . parent = & dev - > dev ;
2009-01-07 14:29:18 +01:00
snprintf ( sis5595_adapter . name , sizeof ( sis5595_adapter . name ) ,
" SMBus SIS5595 adapter at %04x " , sis5595_base + SMB_INDEX ) ;
2007-07-12 14:12:30 +02:00
err = i2c_add_adapter ( & sis5595_adapter ) ;
if ( err ) {
release_region ( sis5595_base + SMB_INDEX , 2 ) ;
return err ;
}
2005-04-16 15:20:36 -07:00
2007-07-12 14:12:30 +02:00
/* Always return failure here. This is to allow other drivers to bind
* to this pci device . We don ' t really want to have control over the
* pci device , we only wanted to read as few register values from it .
*/
sis5595_pdev = pci_dev_get ( dev ) ;
return - ENODEV ;
2005-04-16 15:20:36 -07:00
}
static struct pci_driver sis5595_driver = {
. name = " sis5595_smbus " ,
. id_table = sis5595_ids ,
. probe = sis5595_probe ,
} ;
static int __init i2c_sis5595_init ( void )
{
return pci_register_driver ( & sis5595_driver ) ;
}
static void __exit i2c_sis5595_exit ( void )
{
pci_unregister_driver ( & sis5595_driver ) ;
2007-07-12 14:12:30 +02:00
if ( sis5595_pdev ) {
i2c_del_adapter ( & sis5595_adapter ) ;
release_region ( sis5595_base + SMB_INDEX , 2 ) ;
pci_dev_put ( sis5595_pdev ) ;
sis5595_pdev = NULL ;
}
2005-04-16 15:20:36 -07:00
}
MODULE_AUTHOR ( " Frodo Looijaard <frodol@dds.nl> " ) ;
MODULE_DESCRIPTION ( " SIS5595 SMBus driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( i2c_sis5595_init ) ;
module_exit ( i2c_sis5595_exit ) ;