2006-07-01 11:36:54 -04:00
/*
* SMBus driver for ACPI Embedded Controller ( $ Revision : 1.3 $ )
*
* Copyright ( c ) 2002 , 2005 Ducrot Bruno
* Copyright ( c ) 2005 Rich Townsend ( tiny hacks & tweaks )
*
* 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/version.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/kernel.h>
# include <linux/stddef.h>
# include <linux/sched.h>
# include <linux/init.h>
# include <linux/i2c.h>
# include <linux/acpi.h>
# include <linux/delay.h>
# include "i2c_ec.h"
# define xudelay(t) udelay(t)
# define xmsleep(t) msleep(t)
# define ACPI_EC_HC_COMPONENT 0x00080000
# define ACPI_EC_HC_CLASS "ec_hc_smbus"
# define ACPI_EC_HC_HID "ACPI0001"
# define ACPI_EC_HC_DRIVER_NAME "ACPI EC HC smbus driver"
# define ACPI_EC_HC_DEVICE_NAME "EC HC smbus"
# define _COMPONENT ACPI_EC_HC_COMPONENT
ACPI_MODULE_NAME ( " acpi_smbus " )
static int acpi_ec_hc_add ( struct acpi_device * device ) ;
static int acpi_ec_hc_remove ( struct acpi_device * device , int type ) ;
static struct acpi_driver acpi_ec_hc_driver = {
. name = ACPI_EC_HC_DRIVER_NAME ,
. class = ACPI_EC_HC_CLASS ,
. ids = ACPI_EC_HC_HID ,
. ops = {
. add = acpi_ec_hc_add ,
. remove = acpi_ec_hc_remove ,
} ,
} ;
/* Various bit mask for EC_SC (R) */
# define OBF 0x01
# define IBF 0x02
# define CMD 0x08
# define BURST 0x10
# define SCI_EVT 0x20
# define SMI_EVT 0x40
/* Commands for EC_SC (W) */
# define RD_EC 0x80
# define WR_EC 0x81
# define BE_EC 0x82
# define BD_EC 0x83
# define QR_EC 0x84
/*
* ACPI 2.0 chapter 13 SMBus 2.0 EC register model
*/
# define ACPI_EC_SMB_PRTCL 0x00 /* protocol, PEC */
# define ACPI_EC_SMB_STS 0x01 /* status */
# define ACPI_EC_SMB_ADDR 0x02 /* address */
# define ACPI_EC_SMB_CMD 0x03 /* command */
# define ACPI_EC_SMB_DATA 0x04 /* 32 data registers */
# define ACPI_EC_SMB_BCNT 0x24 /* number of data bytes */
# define ACPI_EC_SMB_ALRM_A 0x25 /* alarm address */
# define ACPI_EC_SMB_ALRM_D 0x26 /* 2 bytes alarm data */
# define ACPI_EC_SMB_STS_DONE 0x80
# define ACPI_EC_SMB_STS_ALRM 0x40
# define ACPI_EC_SMB_STS_RES 0x20
# define ACPI_EC_SMB_STS_STATUS 0x1f
# define ACPI_EC_SMB_STATUS_OK 0x00
# define ACPI_EC_SMB_STATUS_FAIL 0x07
# define ACPI_EC_SMB_STATUS_DNAK 0x10
# define ACPI_EC_SMB_STATUS_DERR 0x11
# define ACPI_EC_SMB_STATUS_CMD_DENY 0x12
# define ACPI_EC_SMB_STATUS_UNKNOWN 0x13
# define ACPI_EC_SMB_STATUS_ACC_DENY 0x17
# define ACPI_EC_SMB_STATUS_TIMEOUT 0x18
# define ACPI_EC_SMB_STATUS_NOTSUP 0x19
# define ACPI_EC_SMB_STATUS_BUSY 0x1A
# define ACPI_EC_SMB_STATUS_PEC 0x1F
# define ACPI_EC_SMB_PRTCL_WRITE 0x00
# define ACPI_EC_SMB_PRTCL_READ 0x01
# define ACPI_EC_SMB_PRTCL_QUICK 0x02
# define ACPI_EC_SMB_PRTCL_BYTE 0x04
# define ACPI_EC_SMB_PRTCL_BYTE_DATA 0x06
# define ACPI_EC_SMB_PRTCL_WORD_DATA 0x08
# define ACPI_EC_SMB_PRTCL_BLOCK_DATA 0x0a
# define ACPI_EC_SMB_PRTCL_PROC_CALL 0x0c
# define ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL 0x0d
# define ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA 0x4a
# define ACPI_EC_SMB_PRTCL_PEC 0x80
/* Length of pre/post transaction sleep (msec) */
# define ACPI_EC_SMB_TRANSACTION_SLEEP 1
# define ACPI_EC_SMB_ACCESS_SLEEP1 1
# define ACPI_EC_SMB_ACCESS_SLEEP2 10
static int acpi_ec_smb_read ( struct acpi_ec_smbus * smbus , u8 address , u8 * data )
{
u8 val ;
int err ;
err = ec_read ( smbus - > base + address , & val ) ;
if ( ! err ) {
* data = val ;
}
xmsleep ( ACPI_EC_SMB_TRANSACTION_SLEEP ) ;
return ( err ) ;
}
static int acpi_ec_smb_write ( struct acpi_ec_smbus * smbus , u8 address , u8 data )
{
int err ;
err = ec_write ( smbus - > base + address , data ) ;
return ( err ) ;
}
static int
acpi_ec_smb_access ( struct i2c_adapter * adap , u16 addr , unsigned short flags ,
char read_write , u8 command , int size ,
union i2c_smbus_data * data )
{
struct acpi_ec_smbus * smbus = adap - > algo_data ;
unsigned char protocol , len = 0 , pec , temp [ 2 ] = { 0 , 0 } ;
int i ;
if ( read_write = = I2C_SMBUS_READ ) {
protocol = ACPI_EC_SMB_PRTCL_READ ;
} else {
protocol = ACPI_EC_SMB_PRTCL_WRITE ;
}
pec = ( flags & I2C_CLIENT_PEC ) ? ACPI_EC_SMB_PRTCL_PEC : 0 ;
switch ( size ) {
case I2C_SMBUS_QUICK :
protocol | = ACPI_EC_SMB_PRTCL_QUICK ;
read_write = I2C_SMBUS_WRITE ;
break ;
case I2C_SMBUS_BYTE :
if ( read_write = = I2C_SMBUS_WRITE ) {
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA , data - > byte ) ;
}
protocol | = ACPI_EC_SMB_PRTCL_BYTE ;
break ;
case I2C_SMBUS_BYTE_DATA :
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_CMD , command ) ;
if ( read_write = = I2C_SMBUS_WRITE ) {
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA , data - > byte ) ;
}
protocol | = ACPI_EC_SMB_PRTCL_BYTE_DATA ;
break ;
case I2C_SMBUS_WORD_DATA :
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_CMD , command ) ;
if ( read_write = = I2C_SMBUS_WRITE ) {
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA , data - > word ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA + 1 ,
data - > word > > 8 ) ;
}
protocol | = ACPI_EC_SMB_PRTCL_WORD_DATA | pec ;
break ;
case I2C_SMBUS_BLOCK_DATA :
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_CMD , command ) ;
if ( read_write = = I2C_SMBUS_WRITE ) {
len = min_t ( u8 , data - > block [ 0 ] , 32 ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_BCNT , len ) ;
for ( i = 0 ; i < len ; i + + )
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA + i ,
data - > block [ i + 1 ] ) ;
}
protocol | = ACPI_EC_SMB_PRTCL_BLOCK_DATA | pec ;
break ;
case I2C_SMBUS_I2C_BLOCK_DATA :
len = min_t ( u8 , data - > block [ 0 ] , 32 ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_CMD , command ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_BCNT , len ) ;
if ( read_write = = I2C_SMBUS_WRITE ) {
for ( i = 0 ; i < len ; i + + ) {
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA + i ,
data - > block [ i + 1 ] ) ;
}
}
protocol | = ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA ;
break ;
case I2C_SMBUS_PROC_CALL :
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_CMD , command ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA , data - > word ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA + 1 , data - > word > > 8 ) ;
protocol = ACPI_EC_SMB_PRTCL_PROC_CALL | pec ;
read_write = I2C_SMBUS_READ ;
break ;
case I2C_SMBUS_BLOCK_PROC_CALL :
protocol | = pec ;
len = min_t ( u8 , data - > block [ 0 ] , 31 ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_CMD , command ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_BCNT , len ) ;
for ( i = 0 ; i < len ; i + + )
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_DATA + i ,
data - > block [ i + 1 ] ) ;
protocol = ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL | pec ;
read_write = I2C_SMBUS_READ ;
break ;
default :
ACPI_DEBUG_PRINT ( ( ACPI_DB_WARN , " EC SMBus adapter: "
" Unsupported transaction %d \n " , size ) ) ;
return ( - 1 ) ;
}
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_ADDR , addr < < 1 ) ;
acpi_ec_smb_write ( smbus , ACPI_EC_SMB_PRTCL , protocol ) ;
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_STS , temp + 0 ) ;
if ( ~ temp [ 0 ] & ACPI_EC_SMB_STS_DONE ) {
xudelay ( 500 ) ;
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_STS , temp + 0 ) ;
}
if ( ~ temp [ 0 ] & ACPI_EC_SMB_STS_DONE ) {
xmsleep ( ACPI_EC_SMB_ACCESS_SLEEP2 ) ;
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_STS , temp + 0 ) ;
}
if ( ( ~ temp [ 0 ] & ACPI_EC_SMB_STS_DONE )
| | ( temp [ 0 ] & ACPI_EC_SMB_STS_STATUS ) ) {
return ( - 1 ) ;
}
if ( read_write = = I2C_SMBUS_WRITE ) {
return ( 0 ) ;
}
switch ( size ) {
case I2C_SMBUS_BYTE :
case I2C_SMBUS_BYTE_DATA :
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_DATA , & data - > byte ) ;
break ;
case I2C_SMBUS_WORD_DATA :
case I2C_SMBUS_PROC_CALL :
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_DATA , temp + 0 ) ;
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_DATA + 1 , temp + 1 ) ;
data - > word = ( temp [ 1 ] < < 8 ) | temp [ 0 ] ;
break ;
case I2C_SMBUS_BLOCK_DATA :
case I2C_SMBUS_BLOCK_PROC_CALL :
len = 0 ;
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_BCNT , & len ) ;
len = min_t ( u8 , len , 32 ) ;
case I2C_SMBUS_I2C_BLOCK_DATA :
for ( i = 0 ; i < len ; i + + )
acpi_ec_smb_read ( smbus , ACPI_EC_SMB_DATA + i ,
data - > block + i + 1 ) ;
data - > block [ 0 ] = len ;
break ;
}
return ( 0 ) ;
}
static u32 acpi_ec_smb_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 acpi_ec_smbus_algorithm = {
. smbus_xfer = acpi_ec_smb_access ,
. functionality = acpi_ec_smb_func ,
} ;
static int acpi_ec_hc_add ( struct acpi_device * device )
{
int status ;
unsigned long val ;
struct acpi_ec_hc * ec_hc ;
struct acpi_ec_smbus * smbus ;
if ( ! device ) {
2006-07-01 16:48:23 -04:00
return - EINVAL ;
2006-07-01 11:36:54 -04:00
}
ec_hc = kmalloc ( sizeof ( struct acpi_ec_hc ) , GFP_KERNEL ) ;
if ( ! ec_hc ) {
2006-07-01 16:48:23 -04:00
return - ENOMEM ;
2006-07-01 11:36:54 -04:00
}
memset ( ec_hc , 0 , sizeof ( struct acpi_ec_hc ) ) ;
smbus = kmalloc ( sizeof ( struct acpi_ec_smbus ) , GFP_KERNEL ) ;
if ( ! smbus ) {
kfree ( ec_hc ) ;
2006-07-01 16:48:23 -04:00
return - ENOMEM ;
2006-07-01 11:36:54 -04:00
}
memset ( smbus , 0 , sizeof ( struct acpi_ec_smbus ) ) ;
ec_hc - > handle = device - > handle ;
strcpy ( acpi_device_name ( device ) , ACPI_EC_HC_DEVICE_NAME ) ;
strcpy ( acpi_device_class ( device ) , ACPI_EC_HC_CLASS ) ;
acpi_driver_data ( device ) = ec_hc ;
status = acpi_evaluate_integer ( ec_hc - > handle , " _EC " , NULL , & val ) ;
if ( ACPI_FAILURE ( status ) ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_WARN , " Error obtaining _EC \n " ) ) ;
2006-08-11 08:30:31 +02:00
kfree ( ec_hc ) ;
2006-07-01 11:36:54 -04:00
kfree ( smbus ) ;
2006-07-01 16:48:23 -04:00
return - EIO ;
2006-07-01 11:36:54 -04:00
}
smbus - > ec = acpi_driver_data ( device - > parent ) ;
smbus - > base = ( val & 0xff00ull ) > > 8 ;
smbus - > alert = val & 0xffull ;
smbus - > adapter . owner = THIS_MODULE ;
smbus - > adapter . algo = & acpi_ec_smbus_algorithm ;
smbus - > adapter . algo_data = smbus ;
if ( i2c_add_adapter ( & smbus - > adapter ) ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_WARN ,
" EC SMBus adapter: Failed to register adapter \n " ) ) ;
kfree ( smbus ) ;
kfree ( ec_hc ) ;
2006-07-01 16:48:23 -04:00
return - EIO ;
2006-07-01 11:36:54 -04:00
}
ec_hc - > smbus = smbus ;
printk ( KERN_INFO PREFIX " %s [%s] \n " ,
acpi_device_name ( device ) , acpi_device_bid ( device ) ) ;
2006-07-01 16:48:23 -04:00
return AE_OK ;
2006-07-01 11:36:54 -04:00
}
static int acpi_ec_hc_remove ( struct acpi_device * device , int type )
{
struct acpi_ec_hc * ec_hc ;
if ( ! device ) {
2006-07-01 16:48:23 -04:00
return - EINVAL ;
2006-07-01 11:36:54 -04:00
}
ec_hc = acpi_driver_data ( device ) ;
i2c_del_adapter ( & ec_hc - > smbus - > adapter ) ;
kfree ( ec_hc - > smbus ) ;
kfree ( ec_hc ) ;
2006-07-01 16:48:23 -04:00
return AE_OK ;
2006-07-01 11:36:54 -04:00
}
static int __init acpi_ec_hc_init ( void )
{
int result ;
result = acpi_bus_register_driver ( & acpi_ec_hc_driver ) ;
if ( result < 0 ) {
2006-07-01 16:48:23 -04:00
return - ENODEV ;
2006-07-01 11:36:54 -04:00
}
2006-07-01 16:48:23 -04:00
return 0 ;
2006-07-01 11:36:54 -04:00
}
static void __exit acpi_ec_hc_exit ( void )
{
acpi_bus_unregister_driver ( & acpi_ec_hc_driver ) ;
}
struct acpi_ec_hc * acpi_get_ec_hc ( struct acpi_device * device )
{
return ( ( struct acpi_ec_hc * ) acpi_driver_data ( device - > parent ) ) ;
}
EXPORT_SYMBOL ( acpi_get_ec_hc ) ;
module_init ( acpi_ec_hc_init ) ;
module_exit ( acpi_ec_hc_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Ducrot Bruno " ) ;
MODULE_DESCRIPTION ( " ACPI EC SMBus driver " ) ;