diff options
author | Claudio Mignanti <c.mignanti@gmail.com> | 2009-02-09 21:38:27 +0000 |
---|---|---|
committer | Claudio Mignanti <c.mignanti@gmail.com> | 2009-02-09 21:38:27 +0000 |
commit | 8708c1f09228d56d9fa0b698765102a3160674b2 (patch) | |
tree | dc8f7dfe2a6aede9510f6af40b446439b80f13f5 /target/linux/etrax/files/arch/cris/arch-v10/drivers/i2c_gvc.c | |
parent | e5c9f0063792ad3d3945de1f33173b2ef82bae0d (diff) | |
download | master-31e0f0ae-8708c1f09228d56d9fa0b698765102a3160674b2.tar.gz master-31e0f0ae-8708c1f09228d56d9fa0b698765102a3160674b2.tar.bz2 master-31e0f0ae-8708c1f09228d56d9fa0b698765102a3160674b2.zip |
Add i2c driver (to etrax) improved by Geert Vancompernolle
SVN-Revision: 14462
Diffstat (limited to 'target/linux/etrax/files/arch/cris/arch-v10/drivers/i2c_gvc.c')
-rw-r--r-- | target/linux/etrax/files/arch/cris/arch-v10/drivers/i2c_gvc.c | 1316 |
1 files changed, 1316 insertions, 0 deletions
diff --git a/target/linux/etrax/files/arch/cris/arch-v10/drivers/i2c_gvc.c b/target/linux/etrax/files/arch/cris/arch-v10/drivers/i2c_gvc.c new file mode 100644 index 0000000000..d94bc1ade9 --- /dev/null +++ b/target/linux/etrax/files/arch/cris/arch-v10/drivers/i2c_gvc.c @@ -0,0 +1,1316 @@ +/*!*************************************************************************** +*! +*! FILE NAME : i2c.c +*! +*! +*! --------------------------------------------------------------------------- +*! +*! ( C ) Copyright 1999-2002 Axis Communications AB, LUND, SWEDEN +*! +*!***************************************************************************/ +/* $Id: i2c.c,v 1.16 2005/09/29 13:33:35 bjarne Exp $ */ + +#define DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC +//#undef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC + +/******************** INCLUDE FILES SECTION ****************************/ + +#include <linux/module.h> +#include <linux/fs.h> + +/**GVC**/ +#ifdef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC +#include <linux/types.h> /* for dev_t */ +#include <linux/cdev.h> /* for struct cdev */ +#endif +/**END GVC**/ + +#include "etraxi2c.h" + +/**GVC**/ +#include "i2c_errno.h" +/**END GVC**/ + +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/arch/io_interface_mux.h> +#include <asm/uaccess.h> + +#include "i2c_gvc.h" + +MODULE_DESCRIPTION( "I2C Device Driver - 1.1" ); + +/*!********************************************************************* + *!History I2C driver Geert Vancompernolle + *!--------------------------------------- + *! + *! - v1.0: + *! First official version. + *! + *! - v1.1: + *! Changes to remove unwanted spikes at ACK/NACK time. + *! + *!*********************************************************************/ + +MODULE_LICENSE( "GPL" ); + +/****************** MACRO's **********************/ + +#define D( x ) + +/**GVC**/ +#ifndef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC +/**END GVC**/ +#define I2C_MAJOR 123 /* LOCAL/EXPERIMENTAL */ +/**GVC**/ +#endif +/**END GVC**/ + +/**GVC**/ +#define WAITONEUS 1 +/* Following are abbreviations taken from Philips I2C standard */ +/* Values are representing time in us and are rounded to next whole number, if relevant */ +#define THDSTA 4 /* Hold delay time for (repeated) START condition */ +#define TLOW 5 /* LOW period of the SCL clock */ +#define THDDAT 1 /* Hold delay time for DATA: value of 0 is allowed but 1 taken to be sure */ +#define TSUDAT 1 /* Set-up time for DATA */ +#define THIGH 4 /* HIGH period of the SCL clock */ +#define TSUSTA 5 /* Set-up time for a repeated START condition */ +#define TSUSTO 4 /* Set-up time for STOP condition */ +#define TBUF 5 /* Bus-free time between STOP and START condition */ + +#define MAXBUSFREERETRIES 5 +#define MAXRETRIES 3 +#define WRITEADDRESS_MASK ( 0xFE ) +#define READADDRESS_MASK ( 0x01 ) +/**END GVC**/ + +#define SCL_HIGH 1 +#define SCL_LOW 0 +#define SDA_HIGH 1 +#define SDA_LOW 0 + +#ifdef CONFIG_ETRAX_I2C_USES_PB_NOT_PB_I2C +/* Use PB and not PB_I2C */ +#ifndef CONFIG_ETRAX_I2C_DATA_PORT +#define CONFIG_ETRAX_I2C_DATA_PORT 0 +#endif +#ifndef CONFIG_ETRAX_I2C_CLK_PORT +#define CONFIG_ETRAX_I2C_CLK_PORT 1 +#endif + +#define SDABIT CONFIG_ETRAX_I2C_DATA_PORT +#define SCLBIT CONFIG_ETRAX_I2C_CLK_PORT +#define i2c_enable() +#define i2c_disable() + +/* enable or disable output-enable, to select output or input on the i2c bus */ +#define i2c_sda_dir_out() \ + REG_SHADOW_SET( R_PORT_PB_DIR, port_pb_dir_shadow, SDABIT, 1 ) +#define i2c_sda_dir_in() \ + REG_SHADOW_SET( R_PORT_PB_DIR, port_pb_dir_shadow, SDABIT, 0 ) + +/* control the i2c clock and data signals */ +#define i2c_set_scl( x ) \ + REG_SHADOW_SET( R_PORT_PB_DATA, port_pb_data_shadow, SCLBIT, x ) +#define i2c_set_sda( x ) \ + REG_SHADOW_SET( R_PORT_PB_DATA, port_pb_data_shadow, SDABIT, x ) + +/* read status of SDA bit from the i2c interface */ +#define i2c_sda_is_high() ( ( ( *R_PORT_PB_READ & ( 1 << SDABIT ) ) ) >> SDABIT ) + +/**GVC**/ +/* read status of SCL bit from the i2c interface */ +#define i2c_scl_is_high() ( ( ( *R_PORT_PB_READ & ( 1 << SCLBIT ) ) ) >> SCLBIT ) +/**END GVC**/ + +#else +/* enable or disable the i2c interface */ +#define i2c_enable() *R_PORT_PB_I2C = ( port_pb_i2c_shadow |= IO_MASK( R_PORT_PB_I2C, i2c_en ) ) +#define i2c_disable() *R_PORT_PB_I2C = ( port_pb_i2c_shadow &= ~IO_MASK( R_PORT_PB_I2C, i2c_en ) ) + +/* enable or disable output-enable, to select output or input on the i2c bus */ +#define i2c_sda_dir_out() \ + *R_PORT_PB_I2C = ( port_pb_i2c_shadow &= ~IO_MASK( R_PORT_PB_I2C, i2c_oe_ ) ); \ + REG_SHADOW_SET( R_PORT_PB_DIR, port_pb_dir_shadow, 0, 1 ); +#define i2c_sda_dir_in() \ + *R_PORT_PB_I2C = ( port_pb_i2c_shadow |= IO_MASK( R_PORT_PB_I2C, i2c_oe_ ) ); \ + REG_SHADOW_SET( R_PORT_PB_DIR, port_pb_dir_shadow, 0, 0 ); + +/* control the i2c clock and data signals */ +#define i2c_set_scl( x ) \ + *R_PORT_PB_I2C = ( port_pb_i2c_shadow = ( port_pb_i2c_shadow & \ + ~IO_MASK( R_PORT_PB_I2C, i2c_set_scl ) ) | IO_FIELD( R_PORT_PB_I2C, i2c_set_scl, ( x ) ) ); \ + REG_SHADOW_SET( R_PORT_PB_DATA, port_pb_data_shadow, 1, x ); + +#define i2c_set_sda( x ) \ + *R_PORT_PB_I2C = ( port_pb_i2c_shadow = ( port_pb_i2c_shadow & \ + ~IO_MASK( R_PORT_PB_I2C, i2c_d ) ) | IO_FIELD( R_PORT_PB_I2C, i2c_d, ( x ) ) ); \ + REG_SHADOW_SET( R_PORT_PB_DATA, port_pb_data_shadow, 0, x ); + +/* read a bit from the i2c interface */ +#define i2c_sda_is_high() ( *R_PORT_PB_READ & 0x1 ) +#endif + +/* use the kernels delay routine */ +#define i2c_delay( usecs ) udelay( usecs ) + + +/****************** TYPEDEF's **********************/ + + +/****************** STATIC (file scope) VARIABLES **********************/ +static DEFINE_SPINLOCK( i2c_lock ); /* Protect directions etc */ +/**GVC**/ +#ifdef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC +static const char i2c_name[] = "i2cgvc"; +#else +static const char i2c_name[] = "i2c"; +#endif +/**END GVC**/ + + +/****************** PROTOTYPING SECTION *************************/ +static int i2c_open( struct inode *inode, struct file *filp ); +static int i2c_release( struct inode *inode, struct file *filp ); +/**GVC**/ +static int i2c_command( unsigned char slave + , unsigned char* wbuf + , unsigned char wlen + , unsigned char* rbuf + , unsigned char rlen + ); +static int i2c_bus_free_check( unsigned char maxretries ); +static void i2c_finalise( const char* text, unsigned long irqflags ); +/**END GVC**/ + + +/************************************************************************/ +/****************** AUXILIARIES *************************/ +/************************************************************************/ + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_open + *# + *# DESCRIPTION : opens an I2C device + *# + *# PARAMETERS : *inode: reference to inode + *# *filp : reference to file pointer + *# + *#--------------------------------------------------------------------------- + */ +static int i2c_open( struct inode *inode, struct file *filp ) +{ + return 0; +} /* i2c_open */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_release + *# + *# DESCRIPTION : Releases the I2C device + *# + *# PARAMETERS : *inode: reference to inode + *# *filp : reference to file pointer + *# + *#--------------------------------------------------------------------------- + */ +static int i2c_release( struct inode *inode, struct file *filp ) +{ + return 0; +} /* i2c_release */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_ioctl + *# + *# DESCRIPTION : Main device API: ioctl's to write/read + *# to/from i2c registers + *# + *# PARAMETERS : *inode: reference to inode + *# *filp : reference to file pointer + *# cmd : command to be executed during the ioctl call + *# arg : pointer to a structure with the data??? + *# + *# RETURN : result of the ioctl call + *# + *#--------------------------------------------------------------------------- + */ +static int i2c_ioctl( struct inode *inode + , struct file *file + , unsigned int cmd + , unsigned long arg + ) +{ + /* the acme ioctls */ + I2C_DATA i2cdata; + int RetVal = EI2CNOERRORS; + + if ( _IOC_TYPE( cmd ) != ETRAXI2C_IOCTYPE ) + { + return ( -EINVAL ); + } + + switch ( _IOC_NR( cmd ) ) + { + case I2C_WRITEREG: + /* write to an i2c slave */ + RetVal = i2c_writereg( I2C_ARGSLAVE( arg ) + , I2C_ARGREG( arg ) + , I2C_ARGVALUE( arg ) + ); + break; + + case I2C_READREG: + RetVal = i2c_readreg( I2C_ARGSLAVE( arg ), I2C_ARGREG( arg ) ); + break; + +/**GVC**/ + /* New functions added by GVC */ + case I2C_READ: + copy_from_user( (char*)&i2cdata, (char*)arg, sizeof( I2C_DATA ) ); + { + int RetryCntr = MAXRETRIES; + + do + { + RetVal = i2c_command( i2cdata.slave + , NULL + , 0 + , i2cdata.rbuf + , i2cdata.rlen + ); + } while ( ( EI2CNOERRORS != RetVal ) + &&( --RetryCntr ) + ); + } + copy_to_user( (char*)arg, (char*)&i2cdata, sizeof( I2C_DATA ) ); + break; + + case I2C_WRITE: + copy_from_user( (char*)&i2cdata, (char*)arg, sizeof( I2C_DATA ) ); + { + int RetryCntr = MAXRETRIES; + + do + { + RetVal = i2c_command( i2cdata.slave + , i2cdata.wbuf + , i2cdata.wlen + , NULL + , 0 + ); + } while ( ( EI2CNOERRORS != RetVal ) + &&( --RetryCntr ) + ); + } + break; + + case I2C_WRITEREAD: + copy_from_user( (char*)&i2cdata, (char*)arg, sizeof( I2C_DATA ) ); + { + int RetryCntr = MAXRETRIES; + + do + { + RetVal = i2c_command( i2cdata.slave + , i2cdata.wbuf + , i2cdata.wlen + , i2cdata.rbuf + , i2cdata.rlen + ); + } while ( ( EI2CNOERRORS != RetVal ) + &&( --RetryCntr ) + ); + } + copy_to_user( (char*)arg, (char*)&i2cdata, sizeof( I2C_DATA ) ); + break; +/**END GVC**/ + + default: + RetVal = -EINVAL; + } + + return ( -RetVal ); +} /* i2c_ioctl */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_command + *# + *# DESCRIPTION : general routine to read/write bytes from an I2C device + *# + *# 'i2c_command()' sends wlen bytes to the I2c bus and receives + *# rlen bytes from the I2c bus. + *# The data to be send must be placed in wbuf[ 0 ] upto wbuf[ wlen - 1 ). + *# The data to be received is assembled in rbuf[ 0 ] upto rbuf[ rlen - 1 ]. + *# + *# If no data is to be sent or received, put appropriate buffer parameter + *# to "NULL" and appropriate length parameter to "0". + *# + *# PARAMETERS : slave = slave address of the I2C device + *# wbuf = address of first element of write buffer (wbuf) + *# wlen = number of bytes to be written to slave + *# rbuf = address of first element of read buffer (rbuf) + *# rlen = number of bytes to be read from slave + *# + *# RETURN : + *# EI2CNOERRORS: I2C communication went fine + *# EI2CBUSNFREE: I2C bus is not free + *# EI2CWADDRESS: I2C write address failed + *# EI2CRADDRESS: I2C read address failed + *# EI2CSENDDATA: I2C send data failed + *# EI2CRECVDATA: I2C receive data failed + *# EI2CSTRTCOND: I2C start condition failed + *# EI2CRSTACOND: I2C repeated start condition failed + *# EI2CSTOPCOND: I2C stop condition failed + *# EI2CNOSNDBYT: I2C no bytes to be sent + *# EI2CNOSNDBUF: I2C no send buffer defined + *# EI2CNORCVBYT: I2C no bytes to be received + *# EI2CNORCVBUF: I2C no receive buffer defined + *# EI2CNOACKNLD: I2C no acknowledge received + *# + *# REMARK : + *# First, the send part is completed. + *# In the send routine, there is no stop generated. This is because maybe + *# a repeated start condition must be generated. + *# This happens when we want to receive some data from the I2c bus. If not, + *# at the end of the general I2c loop the stopcondition is generated. + *# If, on the contrary, there are a number of bytes to be received, a new + *# startcondition is generated in the 'if' part of the main I2c routine, + *# which controls the receiving part. + *# Only when the receiving of data is finished, a final stopcondition is + *# generated. + *# + *#--------------------------------------------------------------------------- + */ +static int i2c_command( unsigned char slave + , unsigned char* wbuf + , unsigned char wlen + , unsigned char* rbuf + , unsigned char rlen + ) +{ + /* Check arguments and report error if relevant... */ + if ( ( wlen > 0 ) && ( wbuf == NULL ) ) + { + printk( KERN_DEBUG "I2C: EI2CNOSNDBUF\n" ); + return ( EI2CNOSNDBUF ); + } + else if ( ( wlen == 0 ) && ( wbuf != NULL ) ) + { + printk( KERN_DEBUG "I2C: EI2CNOSNDBYT\n" ); + return ( EI2CNOSNDBYT ); + } + else if ( ( rlen > 0 ) && ( rbuf == NULL ) ) + { + printk( KERN_DEBUG "I2C: EI2CNORCVBUF\n" ); + return ( EI2CNORCVBUF ); + } + else if ( ( rlen == 0 ) && ( rbuf != NULL ) ) + { + printk( KERN_DEBUG "I2C: EI2CNORCVBYT\n" ); + return ( EI2CNORCVBYT ); + } + else if ( EI2CBUSNFREE == i2c_bus_free_check( MAXBUSFREERETRIES ) ) + { + /* There's no need to try more, since we weren't even + * able to start the I2C communication. + * So, no IRQ flags are stored yet, no changes to any other + * stuff like START, STOP, SENDBYTES... + * Result, simply write down the error and return the correct error code. + */ + printk( KERN_DEBUG "I2C: EI2CBUSNFREE\n" ); + return ( EI2CBUSNFREE ); + } + else + { + /* Finally... We made it... */ + unsigned long irqflags = 0; + + /* we don't like to be interrupted */ + local_irq_save( irqflags ); + + /* Check if there are bytes to be send, + * or if you immediately want to receive data. + */ + if ( 0 < wlen ) + { + /* start I2C communication */ + if ( EI2CNOERRORS != i2c_start() ) + { + return ( i2c_finalise( "I2C: EI2CSTRTCOND\n", irqflags ) + , EI2CSTRTCOND + ); + } + + /* send slave address: xxxxxxx0B (last bit must be zero) */ + if ( EI2CNOERRORS != i2c_outbyte( slave & WRITEADDRESS_MASK ) ) + { + return ( i2c_finalise( "I2C: EI2CWADDRESS\n", irqflags ) + , EI2CWADDRESS + ); + } + + while ( wlen-- ) + { + /* send register data */ + if ( EI2CNOERRORS != i2c_outbyte( *wbuf ) ) + { + return ( i2c_finalise( "I2C: EI2CSENDDATA\n", irqflags ) + , EI2CSENDDATA + ); + } + + wbuf++; + }; + + i2c_delay( TLOW ); + } + + /* + * Receiving data from I2c_bus + * If there are bytes to be received, a new start condition is + * generated => Repeated Startcondition. + * A final stopcondition is generated at the end of the main I2c + * routine. + */ + if ( 0 < rlen ) + { + /* + * Generate start condition if wlen == 0 + * or repeated start condition if wlen != 0... + */ + if ( EI2CNOERRORS != i2c_start() ) + { + return ( i2c_finalise( ( ( 0 < wlen ) + ? "I2C: EI2CRSTACOND\n" + : "I2C: EI2CSTRTCOND\n" + ) + , irqflags + ) + , ( ( 0 < wlen ) ? EI2CRSTACOND : EI2CSTRTCOND ) + ); + } + + /* Send ReadAddress: xxxxxxx1B (last bit must be one) */ + if ( EI2CNOERRORS != i2c_outbyte( slave | READADDRESS_MASK ) ) + { + return ( i2c_finalise( "I2C: EI2CRADDRESS\n", irqflags ) + , EI2CRADDRESS + ); + } + + while ( rlen-- ) + { + /* fetch register */ + *rbuf = i2c_inbyte(); + rbuf++; + + /* last received byte needs to be NACK-ed instead of ACK-ed */ + if ( rlen ) + { + i2c_sendack(); + } + else + { + i2c_sendnack(); + } + }; + } + + /* Generate final stop condition */ + if ( EI2CNOERRORS != i2c_stop() ) + { + return ( i2c_finalise( "I2C: EI2CSTOPCOND\n", irqflags ) + , EI2CSTOPCOND + ); + } + + /* enable interrupt again */ + local_irq_restore( irqflags ); + } + + return ( EI2CNOERRORS ); +} /* i2c_command */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_bus_free_check + *# + *# DESCRIPTION : checks if the I2C bus is free before starting + *# an I2C communication + *# + *# PARAMETERS : maxretries, the number of times we will try to release + *# the I2C bus + *# + *# RETURN : I2cStatus_I2cBusNotFreeError in case the bus is not free, + *# I2cStatus_I2cNoError otherwise + *# + *#--------------------------------------------------------------------------- + */ +static int i2c_bus_free_check( unsigned char maxretries ) +{ + i2c_sda_dir_in(); /* Release SDA line */ + i2c_set_scl( SCL_HIGH ); /* put SCL line high */ + + i2c_delay( WAITONEUS ); + + while ( ( !i2c_sda_is_high() || !i2c_scl_is_high() ) + &&( maxretries-- ) + ) + { + /* Try to release I2C bus by generating STOP conditions */ + i2c_stop(); + } + + if ( 0 == maxretries ) + { + printk( KERN_DEBUG "I2C: EI2CBUSNFREE\n" ); + return ( EI2CBUSNFREE ); + } + else + { + return ( EI2CNOERRORS ); + } +} /* i2c_bus_free_check */ + + +static void i2c_finalise( const char* errortxt + , unsigned long irqflags + ) +{ + printk( KERN_DEBUG "%s", errortxt ); + local_irq_restore( irqflags ); + /* The least we can do when things go terribly wrong, + * is to try to release the bus. + * If this fails, well, then I don't know + * what I can do more for the moment... + */ + (void)i2c_bus_free_check( MAXBUSFREERETRIES ); +} /* i2c_finalise */ + + +static struct file_operations i2c_fops = +{ + .owner = THIS_MODULE +, .ioctl = i2c_ioctl +, .open = i2c_open +, .release = i2c_release +}; + + +/***********************************************************************/ +/************* EXTERNAL FUNCTION DEFINITION SECTION ********************/ +/***********************************************************************/ + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_init + *# + *# DESCRIPTION : initialises the I2C device driver + *# + *# PARAMETERS : + *# + *#--------------------------------------------------------------------------- + */ +int __init i2c_init( void ) +{ + static int res = 0; + static int first = 1; + + if ( !first ) + { + return res; + } + + first = 0; + + /* Setup and enable the Port B I2C interface */ + +#ifndef CONFIG_ETRAX_I2C_USES_PB_NOT_PB_I2C + /* here, we're using the dedicated I2C pins of FoxBoard */ + if ( ( res = cris_request_io_interface( if_i2c, "I2C" ) ) ) + { + printk( KERN_CRIT "i2c_init: Failed to get IO interface\n" ); + return res; + } + + *R_PORT_PB_I2C = port_pb_i2c_shadow |= + IO_STATE( R_PORT_PB_I2C, i2c_en, on ) | + IO_FIELD( R_PORT_PB_I2C, i2c_d, 1 ) | + IO_FIELD( R_PORT_PB_I2C, i2c_set_scl, 1 ) | + IO_STATE( R_PORT_PB_I2C, i2c_oe_, enable ); + + port_pb_dir_shadow &= ~IO_MASK( R_PORT_PB_DIR, dir0 ); + port_pb_dir_shadow &= ~IO_MASK( R_PORT_PB_DIR, dir1 ); + + *R_PORT_PB_DIR = ( port_pb_dir_shadow |= + IO_STATE( R_PORT_PB_DIR, dir0, input ) | + IO_STATE( R_PORT_PB_DIR, dir1, output ) ); +#else + /* If everything goes fine, res = 0, meaning "if" fails => + * will do the "else" too and as such initialise the clock port... + * Clever trick! + */ + if ( ( res = cris_io_interface_allocate_pins( if_i2c + , 'b' + , CONFIG_ETRAX_I2C_DATA_PORT + , CONFIG_ETRAX_I2C_DATA_PORT + ) + ) + ) + { + printk( KERN_WARNING "i2c_init: Failed to get IO pin for I2C data port\n" ); + return ( res ); + } + /* Same here...*/ + else if ( ( res = cris_io_interface_allocate_pins( if_i2c + , 'b' + , CONFIG_ETRAX_I2C_CLK_PORT + , CONFIG_ETRAX_I2C_CLK_PORT + ) + ) + ) + { + cris_io_interface_free_pins( if_i2c + , 'b' + , CONFIG_ETRAX_I2C_DATA_PORT + , CONFIG_ETRAX_I2C_DATA_PORT + ); + printk( KERN_WARNING "i2c_init: Failed to get IO pin for I2C clk port\n" ); + } +#endif + + return ( res ); +} /* i2c_init */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_register + *# + *# DESCRIPTION : this registers the i2c driver as a character device + *# + *# PARAMETERS : + *# + *#--------------------------------------------------------------------------- + */ +static int __init i2c_register( void ) +{ + int res; +/**GVC**/ +#ifdef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC + dev_t devt; + struct cdev *my_i2cdev = NULL; +#endif +/**END GVC**/ + + res = i2c_init(); + + if ( res < 0 ) + { + return res; + } + +/**GVC**/ +#ifdef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC + res = alloc_chrdev_region( &devt, 0, 1, i2c_name ); + + if ( res < 0 ) + { + printk( KERN_DEBUG "I2C: EI2CNOMNUMBR\n" ); + return ( res ); + } + + my_i2cdev = cdev_alloc(); + my_i2cdev->ops = &i2c_fops; + my_i2cdev->owner = THIS_MODULE; + + /* make device "alive" */ + res = cdev_add( my_i2cdev, devt, 1 ); + + if ( res < 0 ) + { + printk( KERN_DEBUG "I2C: EI2CDADDFAIL\n" ); + return ( res ); + } +#else +/**END GVC**/ + res = register_chrdev( I2C_MAJOR, i2c_name, &i2c_fops ); + + if ( res < 0 ) + { + printk( KERN_ERR "i2c: couldn't get a major number.\n" ); + return res; + } +/**GVC**/ +#endif +/**END GVC**/ + + printk( KERN_INFO "I2C driver v2.2, (c) 1999-2004 Axis Communications AB\n" ); + +/**GVC**/ + printk( KERN_INFO " ==> Improvements done by Geert Vancompernolle - December 2006\n" ); + +#ifdef DYNAMIC_MAJOR_I2CDEV_NUMBER_ALLOC + printk( KERN_INFO "I2C Major: %d / I2C Name: %s\n", MAJOR( devt ), i2c_name ); +#else +/**END GVC**/ + printk( KERN_INFO "I2C Major: %d / I2C Name: %s\n", I2C_MAJOR, i2c_name ); +/**GVC**/ +#endif +/**END GVC**/ + + return ( 0 ); +} /* i2c_register */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_start + *# + *# DESCRIPTION : generate i2c start condition + *# + *# PARAMETERS : none + *# + *# RETURN : EI2CNOERRORS if OK, EI2CSTRTCOND otherwise + *# + *#--------------------------------------------------------------------------- + */ +int i2c_start( void ) +{ + /* Set SCL=1, SDA=1 */ + i2c_sda_dir_out(); + i2c_set_sda( SDA_HIGH ); + i2c_delay( WAITONEUS ); + i2c_set_scl( SCL_HIGH ); + i2c_delay( WAITONEUS ); + + /* Set SCL=1, SDA=0 */ + i2c_set_sda( SDA_LOW ); + i2c_delay( THDSTA ); + + /* Set SCL=0, SDA=0 */ + i2c_set_scl( SCL_LOW ); + /* We can take 1 us less than defined in spec (5 us), since the next action + * will be to set the dataline high or low and this action is 1 us + * before the clock is put high, so that makes our 5 us. + */ + i2c_delay( TLOW - WAITONEUS ); + + if ( i2c_sda_is_high() || i2c_scl_is_high() ) + { + printk( KERN_DEBUG "I2C: EI2CSTRTCOND\n" ); + return ( EI2CSTRTCOND ); + } + + return ( EI2CNOERRORS ); +} /* i2c_start */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_stop + *# + *# DESCRIPTION : generate i2c stop condition + *# + *# PARAMETERS : none + *# + *# RETURN : none + *# + *#--------------------------------------------------------------------------- + */ +int i2c_stop( void ) +{ + i2c_sda_dir_out(); + + /* Set SCL=0, SDA=0 */ + /* Don't change order, otherwise you might generate a start condition! */ + i2c_set_scl( SCL_LOW ); + i2c_delay( WAITONEUS ); + i2c_set_sda( SDA_LOW ); + i2c_delay( WAITONEUS ); + + /* Set SCL=1, SDA=0 */ + i2c_set_scl( SCL_HIGH ); + i2c_delay( TSUSTO ); + + /* Set SCL=1, SDA=1 */ + i2c_set_sda( SDA_HIGH ); + i2c_delay( TBUF ); + + i2c_sda_dir_in(); + + if ( !i2c_sda_is_high() || !i2c_scl_is_high() ) + { + printk( KERN_DEBUG "I2C: EI2CSTOPCOND\n" ); + return ( EI2CSTOPCOND ); + } + + return ( EI2CNOERRORS ); +} /* i2c_stop */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_outbyte + *# + *# DESCRIPTION : write a byte to the i2c interface + *# + *# PARAMETERS : x: byte to be sent on the I2C bus + *# + *# RETURN : none + *# + *#--------------------------------------------------------------------------- + */ +int i2c_outbyte( unsigned char x ) +{ + int i; + + i2c_sda_dir_out(); + + for ( i = 0; i < 8; i++ ) + { + if ( x & 0x80 ) + { + i2c_set_sda( SDA_HIGH ); + } + else + { + i2c_set_sda( SDA_LOW ); + } + + i2c_delay( TSUDAT ); + i2c_set_scl( SCL_HIGH ); + i2c_delay( THIGH ); + i2c_set_scl( SCL_LOW ); + i2c_delay( TSUDAT ); + i2c_set_sda( SDA_LOW ); + /* There should be only 5 us between falling edge and new rising + * edge of clock pulse. + * Since we spend already 1 us since clock edge was low, there are + * only ( TLOW - TSUDAT ) us left. + * Next to this, since the data line will be set up 1 us before the + * clock line is set up, we can reduce the delay with another us. + */ + i2c_delay( TLOW - TSUDAT - WAITONEUS ); + x <<= 1; + } + + /* enable input */ + i2c_sda_dir_in(); + + if ( !i2c_getack() ) + { + printk( KERN_DEBUG "I2C: EI2CNOACKNLD\n" ); + return( EI2CNOACKNLD ); + } + + return ( EI2CNOERRORS ); +} /* i2c_outbyte */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_inbyte + *# + *# DESCRIPTION : read a byte from the i2c interface + *# + *# PARAMETERS : none + *# + *# RETURN : returns the byte read from the I2C device + *# + *#--------------------------------------------------------------------------- + */ +unsigned char i2c_inbyte( void ) +{ + unsigned char aBitByte = 0; + unsigned char Mask = 0x80; /* !!! ATTENTION: do NOT use 'char', otherwise shifting is wrong!!! */ + /* Must be UNSIGNED, not SIGNED! */ + + + /* Switch off I2C to get bit */ + i2c_disable(); + i2c_sda_dir_in(); + + while ( Mask != 0 ) + { + i2c_set_scl( SCL_HIGH ); + i2c_delay( THIGH ); + + if ( i2c_sda_is_high() ) + { + aBitByte |= Mask; + } + + i2c_set_scl( SCL_LOW ); + + Mask >>= 1; + + i2c_delay( TLOW ); + } + + /* + * we leave the clock low, getbyte is usually followed + * by sendack/nack, they assume the clock to be low + */ + return ( aBitByte ); +} /* i2c_inbyte */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_getack + *# + *# DESCRIPTION : checks if ack was received from ic2 + *# + *# PARAMETERS : none + *# + *# RETURN : returns the ack state of the I2C device + *# + *#--------------------------------------------------------------------------- + */ +int i2c_getack( void ) +{ + int ack = 1; + + /* generate ACK clock pulse */ + i2c_set_scl( SCL_HIGH ); + + /* switch off I2C */ + i2c_disable(); + + /* now wait for ack */ + i2c_delay( THIGH ); + /* check for ack: if SDA is high, then NACK, else ACK */ + if ( i2c_sda_is_high() ) + { + ack = 0; + } + else + { + ack = 1; + } + + /* end clock pulse */ + i2c_enable(); + i2c_set_scl( SCL_LOW ); + i2c_sda_dir_out(); + i2c_set_sda( SDA_LOW ); + + /* Since we "lost" already THDDAT time, we can subtract it here... */ + i2c_delay( TLOW - THDDAT ); + + return ( ack ); +} /* i2c_getack */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_sendack + *# + *# DESCRIPTION : sends ACK on received data + *# + *# PARAMETERS : none + *# + *# RETURN : none + *# + *#--------------------------------------------------------------------------- + */ +void i2c_sendack( void ) +{ + /* enable output */ + /* Clock has been set to TLOW already at end of i2c_inbyte() + * and i2c_outbyte(), so no need to do it again. + */ + i2c_sda_dir_out(); + /* set ack pulse low */ + i2c_set_sda( SDA_LOW ); + /* generate clock pulse */ + i2c_delay( TSUDAT ); + i2c_set_scl( SCL_HIGH ); + i2c_delay( THIGH ); + i2c_set_scl( SCL_LOW ); + i2c_delay( THDDAT ); + /* reset data out */ + i2c_set_sda( SDA_HIGH ); + /* Subtract time spend already when waited to put SDA high */ + i2c_delay( TLOW - THDDAT ); + + /* release the SDA line */ + i2c_sda_dir_in(); +} /* i2c_sendack */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_sendnack + *# + *# DESCRIPTION : sends NACK on received data + *# + *# PARAMETERS : none + *# + *# RETURN : none + *# + *#--------------------------------------------------------------------------- + */ +void i2c_sendnack( void ) +{ + /* make sure the SDA line is set high prior to activation of the output. + * this way, you avoid an unnecessary peak to ground when a NACK has to + * be created. + */ + /* set data high */ + i2c_set_sda( SDA_HIGH ); + /* enable output */ + i2c_sda_dir_out(); + + /* generate clock pulse */ + i2c_delay( TSUDAT ); + i2c_set_scl( SCL_HIGH ); + i2c_delay( THIGH ); + i2c_set_scl( SCL_LOW ); + i2c_delay( TSUDAT ); + i2c_set_sda( SDA_LOW ); + i2c_delay( TLOW - TSUDAT ); + + /* There's no need to change the direction of SDA to "in" again, + * since a NACK is always followed by a stop condition. + * A STOP condition will put the direction of SDA back to "out" + * resulting in a useless SDA "dip" on the line... + */ + /* i2c_sda_dir_in(); */ +} /* i2c_sendnack */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_writereg + *# + *# DESCRIPTION : writes a value to a register of an I2C device + *# + *# PARAMETERS : theSlave = slave address of the I2C device + *# theReg = register of the I2C device that needs to be written + *# theValue = value to be written to the register + *# + *# RETURN : returns OR-ed result of the write action: + *# 0 = Ok + *# 1 = Slave_NoAck + *# 2 = Reg_NoAck + *# 4 = Val_NoAck + *# + *#--------------------------------------------------------------------------- + */ +int i2c_writereg( unsigned char theSlave + , unsigned char theReg + , unsigned char theValue + ) +{ + int error, cntr = 3; + unsigned long flags; + + spin_lock( &i2c_lock ); + + do + { + error = 0; + /* we don't like to be interrupted */ + local_irq_save( flags ); + + i2c_start(); + /* send slave address */ + if ( EI2CNOACKNLD == i2c_outbyte( theSlave & 0xfe ) ) + { + error = 1; + } + + /* now select register */ + if ( EI2CNOACKNLD == i2c_outbyte( theReg ) ) + { + error |= 2; + } + + /* send register register data */ + if ( EI2CNOACKNLD == i2c_outbyte( theValue ) ) + { + error |= 4; + } + + /* end byte stream */ + i2c_stop(); + /* enable interrupt again */ + local_irq_restore( flags ); + + } while ( error && cntr-- ); + + i2c_delay( TLOW ); + + spin_unlock( &i2c_lock ); + + return ( -error ); +} /* i2c_writereg */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_readreg + *# + *# DESCRIPTION : reads the value from a certain register of an I2C device. + *# Function first writes the register that it wants to read + *# later on. + *# + *# PARAMETERS : theSlave = slave address of the I2C device + *# theReg = register of the I2C device that needs to be written + *# + *# RETURN : returns OR-ed result of the write action: + *# 0 = Ok + *# 1 = Slave_NoAck + *# 2 = Reg_NoAck + *# 4 = Val_NoAck + *# + *#--------------------------------------------------------------------------- + */ +unsigned char i2c_readreg( unsigned char theSlave + , unsigned char theReg + ) +{ + unsigned char b = 0; + int error, cntr = 3; + unsigned long flags; + + spin_lock( &i2c_lock ); + + do + { + error = 0; + + /* we don't like to be interrupted */ + local_irq_save( flags ); + + /* generate start condition */ + i2c_start(); + + /* send slave address */ + if ( EI2CNOACKNLD == i2c_outbyte( theSlave & 0xfe ) ) + { + error = 1; + } + + /* now select register */ + i2c_sda_dir_out(); + + if ( EI2CNOACKNLD == i2c_outbyte( theReg ) ) + { + error |= 2; + } + + /* repeat start condition */ + i2c_delay( TLOW ); + i2c_start(); + + /* send slave address */ + if ( EI2CNOACKNLD == i2c_outbyte( theSlave | 0x01 ) ) + { + error |= 1; + } + + /* fetch register */ + b = i2c_inbyte(); + /* + * last received byte needs to be nacked + * instead of acked + */ + i2c_sendnack(); + + /* end sequence */ + i2c_stop(); + + /* enable interrupt again */ + local_irq_restore( flags ); + + } while ( error && cntr-- ); + + spin_unlock( &i2c_lock ); + + return ( b ); +} /* i2c_readreg */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_read + *# + *# DESCRIPTION : + *# + *# PARAMETERS : + *# + *# RETURN : + *# + *#--------------------------------------------------------------------------- + */ +int i2c_read( unsigned char slave, unsigned char* rbuf, unsigned char rlen ) +{ + return ( i2c_command( slave, NULL, 0, rbuf, rlen ) ); +} /* i2c_read */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_write + *# + *# DESCRIPTION : + *# + *# PARAMETERS : + *# + *# RETURN : + *# + *#--------------------------------------------------------------------------- + */ +int i2c_write( unsigned char slave, unsigned char* wbuf, unsigned char wlen ) +{ + return ( i2c_command( slave, wbuf, wlen, NULL, 0 ) ); +} /* i2c_write */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: i2c_writeread + *# + *# DESCRIPTION : + *# + *# PARAMETERS : + *# + *# RETURN : + *# + *#--------------------------------------------------------------------------- + */ +int i2c_writeread( unsigned char slave + , unsigned char* wbuf + , unsigned char wlen + , unsigned char* rbuf + , unsigned char rlen + ) +{ + return ( i2c_command( slave, wbuf, wlen, rbuf, rlen ) ); +} /* i2c_writeread */ + + +/*#--------------------------------------------------------------------------- + *# + *# FUNCTION NAME: module_init + *# + *# DESCRIPTION : this makes sure that i2c_register is called during boot + *# + *# PARAMETERS : + *# + *#--------------------------------------------------------------------------- + */ +module_init( i2c_register ); + +/****************** END OF FILE i2c.c ********************************/ |