2005-04-17 02:20:36 +04:00
/*
* SMBus 2.0 driver for AMD - 8111 IO - Hub .
*
* Copyright ( c ) 2002 Vojtech Pavlik
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2.
*/
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/kernel.h>
# include <linux/stddef.h>
# include <linux/ioport.h>
# include <linux/init.h>
# include <linux/i2c.h>
# include <linux/delay.h>
# include <asm/io.h>
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Vojtech Pavlik <vojtech@suse.cz> " ) ;
MODULE_DESCRIPTION ( " AMD8111 SMBus 2.0 driver " ) ;
struct amd_smbus {
struct pci_dev * dev ;
struct i2c_adapter adapter ;
int base ;
int size ;
} ;
2005-09-25 18:37:04 +04:00
static struct pci_driver amd8111_driver ;
2005-04-17 02:20:36 +04:00
/*
* 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
*/
static 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 ) {
2007-02-14 00:09:02 +03:00
dev_warn ( & smbus - > dev - > dev ,
" Timeout while waiting for IBF to clear \n " ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
return 0 ;
}
static 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 ) {
2007-02-14 00:09:02 +03:00
dev_warn ( & smbus - > dev - > dev ,
" Timeout while waiting for OBF to set \n " ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
return 0 ;
}
2007-02-14 00:09:02 +03:00
static unsigned int amd_ec_read ( struct amd_smbus * smbus , unsigned char address ,
unsigned char * data )
2005-04-17 02:20:36 +04:00
{
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 ;
}
2007-02-14 00:09:02 +03:00
static unsigned int amd_ec_write ( struct amd_smbus * smbus , unsigned char address ,
unsigned char data )
2005-04-17 02:20:36 +04:00
{
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
2007-02-14 00:09:02 +03:00
static s32 amd8111_access ( struct i2c_adapter * adap , u16 addr ,
unsigned short flags , char read_write , u8 command , int size ,
union i2c_smbus_data * data )
2005-04-17 02:20:36 +04:00
{
struct amd_smbus * smbus = adap - > algo_data ;
unsigned char protocol , len , pec , temp [ 2 ] ;
int i ;
2007-02-14 00:09:02 +03:00
protocol = ( read_write = = I2C_SMBUS_READ ) ? AMD_SMB_PRTCL_READ
: AMD_SMB_PRTCL_WRITE ;
2005-04-17 02:20:36 +04:00
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 ) {
2007-02-14 00:09:02 +03:00
amd_ec_write ( smbus , AMD_SMB_DATA ,
data - > word & 0xff ) ;
amd_ec_write ( smbus , AMD_SMB_DATA + 1 ,
data - > word > > 8 ) ;
2005-04-17 02:20:36 +04:00
}
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 ) {
2007-02-14 00:09:02 +03:00
len = min_t ( u8 , data - > block [ 0 ] ,
I2C_SMBUS_BLOCK_MAX ) ;
2005-04-17 02:20:36 +04:00
amd_ec_write ( smbus , AMD_SMB_BCNT , len ) ;
for ( i = 0 ; i < len ; i + + )
2007-02-14 00:09:02 +03:00
amd_ec_write ( smbus , AMD_SMB_DATA + i ,
data - > block [ i + 1 ] ) ;
2005-04-17 02:20:36 +04:00
}
protocol | = AMD_SMB_PRTCL_BLOCK_DATA | pec ;
break ;
case I2C_SMBUS_I2C_BLOCK_DATA :
2007-02-14 00:09:02 +03:00
len = min_t ( u8 , data - > block [ 0 ] ,
I2C_SMBUS_BLOCK_MAX ) ;
2005-04-17 02:20:36 +04:00
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 + + )
2007-02-14 00:09:02 +03:00
amd_ec_write ( smbus , AMD_SMB_DATA + i ,
data - > block [ i + 1 ] ) ;
2005-04-17 02:20:36 +04:00
protocol | = AMD_SMB_PRTCL_I2C_BLOCK_DATA ;
break ;
case I2C_SMBUS_PROC_CALL :
amd_ec_write ( smbus , AMD_SMB_CMD , command ) ;
2007-02-14 00:09:02 +03:00
amd_ec_write ( smbus , AMD_SMB_DATA , data - > word & 0xff ) ;
2005-04-17 02:20:36 +04:00
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 :
2007-03-22 21:49:00 +03:00
len = min_t ( u8 , data - > block [ 0 ] ,
I2C_SMBUS_BLOCK_MAX - 1 ) ;
2005-04-17 02:20:36 +04:00
amd_ec_write ( smbus , AMD_SMB_CMD , command ) ;
amd_ec_write ( smbus , AMD_SMB_BCNT , len ) ;
for ( i = 0 ; i < len ; i + + )
2007-02-14 00:09:02 +03:00
amd_ec_write ( smbus , AMD_SMB_DATA + i ,
data - > block [ i + 1 ] ) ;
2005-04-17 02:20:36 +04:00
protocol = AMD_SMB_PRTCL_BLOCK_PROC_CALL | pec ;
read_write = I2C_SMBUS_READ ;
break ;
default :
dev_warn ( & adap - > dev , " 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 ) {
msleep ( 1 ) ;
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 ) ;
2007-02-14 00:09:02 +03:00
len = min_t ( u8 , len , I2C_SMBUS_BLOCK_MAX ) ;
2005-04-17 02:20:36 +04:00
case I2C_SMBUS_I2C_BLOCK_DATA :
for ( i = 0 ; i < len ; i + + )
2007-02-14 00:09:02 +03:00
amd_ec_read ( smbus , AMD_SMB_DATA + i ,
data - > block + i + 1 ) ;
2005-04-17 02:20:36 +04:00
data - > block [ 0 ] = len ;
break ;
}
return 0 ;
}
static u32 amd8111_func ( struct i2c_adapter * adapter )
{
2007-02-14 00:09:02 +03:00
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA |
2005-04-17 02:20:36 +04:00
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 ;
}
2006-09-04 00:39:46 +04:00
static const struct i2c_algorithm smbus_algorithm = {
2005-04-17 02:20:36 +04:00
. smbus_xfer = amd8111_access ,
. functionality = amd8111_func ,
} ;
static struct pci_device_id amd8111_ids [ ] = {
{ PCI_DEVICE ( PCI_VENDOR_ID_AMD , PCI_DEVICE_ID_AMD_8111_SMBUS2 ) } ,
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( pci , amd8111_ids ) ;
2007-02-14 00:09:02 +03:00
static int __devinit amd8111_probe ( struct pci_dev * dev ,
const struct pci_device_id * id )
2005-04-17 02:20:36 +04:00
{
struct amd_smbus * smbus ;
2007-02-14 00:09:02 +03:00
int error ;
2005-04-17 02:20:36 +04:00
2007-02-14 00:09:02 +03:00
if ( ! ( pci_resource_flags ( dev , 0 ) & IORESOURCE_IO ) )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2005-10-18 01:09:43 +04:00
smbus = kzalloc ( sizeof ( struct amd_smbus ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! smbus )
return - ENOMEM ;
smbus - > dev = dev ;
smbus - > base = pci_resource_start ( dev , 0 ) ;
smbus - > size = pci_resource_len ( dev , 0 ) ;
2007-02-14 00:09:02 +03:00
if ( ! request_region ( smbus - > base , smbus - > size , amd8111_driver . name ) ) {
error = - EBUSY ;
2005-04-17 02:20:36 +04:00
goto out_kfree ;
2007-02-14 00:09:02 +03:00
}
2005-04-17 02:20:36 +04:00
smbus - > adapter . owner = THIS_MODULE ;
snprintf ( smbus - > adapter . name , I2C_NAME_SIZE ,
" SMBus2 AMD8111 adapter at %04x " , smbus - > base ) ;
2007-02-14 00:09:01 +03:00
smbus - > adapter . id = I2C_HW_SMBUS_AMD8111 ;
2005-04-17 02:20:36 +04:00
smbus - > adapter . class = I2C_CLASS_HWMON ;
smbus - > adapter . algo = & smbus_algorithm ;
smbus - > adapter . algo_data = smbus ;
2007-02-17 21:13:42 +03:00
/* set up the sysfs linkage to our parent device */
2005-04-17 02:20:36 +04:00
smbus - > adapter . dev . parent = & dev - > dev ;
2007-02-14 00:09:02 +03:00
pci_write_config_dword ( smbus - > dev , AMD_PCI_MISC , 0 ) ;
2005-04-17 02:20:36 +04:00
error = i2c_add_adapter ( & smbus - > adapter ) ;
if ( error )
goto out_release_region ;
pci_set_drvdata ( dev , smbus ) ;
return 0 ;
out_release_region :
release_region ( smbus - > base , smbus - > size ) ;
out_kfree :
kfree ( smbus ) ;
2007-02-14 00:09:02 +03:00
return error ;
2005-04-17 02:20:36 +04:00
}
static void __devexit amd8111_remove ( struct pci_dev * dev )
{
struct amd_smbus * smbus = 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_smbus2 " ,
. id_table = amd8111_ids ,
. probe = amd8111_probe ,
. remove = __devexit_p ( amd8111_remove ) ,
} ;
static int __init i2c_amd8111_init ( void )
{
return pci_register_driver ( & amd8111_driver ) ;
}
static void __exit i2c_amd8111_exit ( void )
{
pci_unregister_driver ( & amd8111_driver ) ;
}
module_init ( i2c_amd8111_init ) ;
module_exit ( i2c_amd8111_exit ) ;