patch-2.4.4 linux/arch/cris/drivers/i2c.c
Next file: linux/arch/cris/drivers/i2c.h
Previous file: linux/arch/cris/drivers/gpio.c
Back to the patch index
Back to the overall index
- Lines: 692
- Date:
Fri Apr 6 10:42:55 2001
- Orig file:
v2.4.3/linux/arch/cris/drivers/i2c.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.4.3/linux/arch/cris/drivers/i2c.c linux/arch/cris/drivers/i2c.c
@@ -0,0 +1,691 @@
+/*!***************************************************************************
+*!
+*! FILE NAME : i2c.c
+*!
+*! DESCRIPTION: implements an interface for IIC/I2C, both directly from other
+*! kernel modules (i2c_writereg/readreg) and from userspace using
+*! ioctl()'s
+*!
+*! Nov 30 1998 Torbjorn Eliasson Initial version.
+*! Bjorn Wesen Elinux kernel version.
+*! Jan 14 2000 Johan Adolfsson Fixed PB shadow register stuff -
+*! don't use PB_I2C if DS1302 uses same bits,
+*! use PB.
+*! $Log: i2c.c,v $
+*! Revision 1.5 2001/02/27 13:52:48 bjornw
+*! malloc.h -> slab.h
+*!
+*! Revision 1.4 2001/02/15 07:17:40 starvik
+*! Corrected usage if port_pb_i2c_shadow
+*!
+*! Revision 1.3 2001/01/26 17:55:13 bjornw
+*! * Made I2C_USES_PB_NOT_PB_I2C a CONFIG option instead of assigning it
+*! magically. Config.in needs to set it for the options that need it, like
+*! Dallas 1302 support. Actually, it should be default since it screws up
+*! the PB bits even if you don't use I2C..
+*! * Include linux/config.h to get the above
+*!
+*! Revision 1.2 2001/01/18 15:49:30 bjornw
+*! 2.4 port of I2C including some cleanups (untested of course)
+*!
+*! Revision 1.1 2001/01/18 15:35:25 bjornw
+*! Verbatim copy of the Etrax i2c driver, 2.0 elinux version
+*!
+*!
+*! ---------------------------------------------------------------------------
+*!
+*! (C) Copyright 1999, 2000, 2001 Axis Communications AB, LUND, SWEDEN
+*!
+*!***************************************************************************/
+/* $Id: i2c.c,v 1.5 2001/02/27 13:52:48 bjornw Exp $ */
+/****************** INCLUDE FILES SECTION ***********************************/
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/config.h>
+
+#include <asm/etraxi2c.h>
+
+#include <asm/system.h>
+#include <asm/svinto.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+
+#include "i2c.h"
+
+/****************** I2C DEFINITION SECTION *************************/
+
+#define D(x)
+
+#define I2C_MAJOR 123 /* LOCAL/EXPERIMENTAL */
+static const char i2c_name[] = "i2c";
+
+#define CLOCK_LOW_TIME 8
+#define CLOCK_HIGH_TIME 8
+#define START_CONDITION_HOLD_TIME 8
+#define STOP_CONDITION_HOLD_TIME 8
+#define ENABLE_OUTPUT 0x01
+#define ENABLE_INPUT 0x00
+#define I2C_CLOCK_HIGH 1
+#define I2C_CLOCK_LOW 0
+#define I2C_DATA_HIGH 1
+#define I2C_DATA_LOW 0
+
+#if 0
+/* TODO: fix this so the CONFIG_ETRAX_I2C_USES... is set in Config.in instead */
+#if defined(CONFIG_DS1302) && (CONFIG_DS1302_SDABIT==0) && \
+ (CONFIG_DS1302_SCLBIT == 1)
+#define CONFIG_ETRAX_I2C_USES_PB_NOT_PB_I2C
+#endif
+#endif
+
+#ifdef CONFIG_ETRAX_I2C_USES_PB_NOT_PB_I2C
+/* Use PB and not PB_I2C */
+#define SDABIT 0
+#define SCLBIT 1
+#define i2c_enable()
+#define i2c_disable()
+
+/* enable or disable output-enable, to select output or input on the i2c bus */
+
+#define i2c_dir_out() \
+ REG_SHADOW_SET(R_PORT_PB_DIR, port_pb_dir_shadow, SDABIT, 1)
+#define i2c_dir_in() \
+ REG_SHADOW_SET(R_PORT_PB_DIR, port_pb_dir_shadow, SDABIT, 0)
+
+/* control the i2c clock and data signals */
+
+#define i2c_clk(x) \
+ REG_SHADOW_SET(R_PORT_PB_DATA, port_pb_data_shadow, SCLBIT, x)
+#define i2c_data(x) \
+ REG_SHADOW_SET(R_PORT_PB_DATA, port_pb_data_shadow, SDABIT, x)
+
+/* read a bit from the i2c interface */
+
+#define i2c_getbit() (*R_PORT_PB_READ & (1 << SDABIT))
+
+#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_dir_out() *R_PORT_PB_I2C = (port_pb_i2c_shadow &= ~IO_MASK(R_PORT_PB_I2C, i2c_oe_))
+#define i2c_dir_in() *R_PORT_PB_I2C = (port_pb_i2c_shadow |= IO_MASK(R_PORT_PB_I2C, i2c_oe_))
+
+/* control the i2c clock and data signals */
+
+#define i2c_clk(x) *R_PORT_PB_I2C = (port_pb_i2c_shadow = (port_pb_i2c_shadow & \
+ ~IO_MASK(R_PORT_PB_I2C, i2c_clk)) | IO_FIELD(R_PORT_PB_I2C, i2c_clk, (x)))
+
+#define i2c_data(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)))
+
+/* read a bit from the i2c interface */
+
+#define i2c_getbit() (*R_PORT_PB_READ & 0x1)
+#endif
+
+/* use the kernels delay routine */
+
+#define i2c_delay(usecs) udelay(usecs)
+
+
+/****************** FUNCTION DEFINITION SECTION *************************/
+
+
+/* generate i2c start condition */
+
+static void
+i2c_start(void)
+{
+ //
+ // SCL=1 SDA=1
+ //
+ i2c_dir_out();
+ i2c_delay(CLOCK_HIGH_TIME/6);
+ i2c_data(I2C_DATA_HIGH);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ //
+ // SCL=1 SDA=0
+ //
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(START_CONDITION_HOLD_TIME);
+ //
+ // SCL=0 SDA=0
+ //
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+}
+
+/* generate i2c stop condition */
+
+static void
+i2c_stop(void)
+{
+ i2c_dir_out();
+
+ //
+ // SCL=0 SDA=0
+ //
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(CLOCK_LOW_TIME*2);
+ //
+ // SCL=1 SDA=0
+ //
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME*2);
+ //
+ // SCL=1 SDA=1
+ //
+ i2c_data(I2C_DATA_HIGH);
+ i2c_delay(STOP_CONDITION_HOLD_TIME);
+
+ i2c_dir_in();
+}
+
+/* write a byte to the i2c interface */
+
+static void
+i2c_outbyte(unsigned char x)
+{
+ int i;
+
+ i2c_dir_out();
+
+ for (i = 0; i < 8; i++) {
+ if (x & 0x80)
+ i2c_data(I2C_DATA_HIGH);
+ else
+ i2c_data(I2C_DATA_LOW);
+
+ i2c_delay(CLOCK_LOW_TIME/2);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME/2);
+ x <<= 1;
+ }
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(CLOCK_LOW_TIME/2);
+
+ //
+ // enable input
+ //
+ i2c_dir_in();
+}
+
+/* read a byte from the i2c interface */
+
+static unsigned char
+i2c_inbyte(void)
+{
+ unsigned char aBitByte = 0;
+ int i;
+ int iaa;
+ //int dd= 0;
+
+ //
+ // enable output
+ //
+ i2c_dir_out();
+ //
+ // Release data bus by setting
+ // data high
+ //
+ i2c_data(I2C_DATA_HIGH);
+ //
+ // enable input
+ //
+ i2c_dir_in();
+ //
+ // Use PORT PB instead of I2C
+ // for input. (I2C not working)
+ //
+ i2c_clk(1);
+ i2c_data(1);
+/* *R_PORT_PB_DATA = *R_PORT_PB_READ | 0x03;*/
+ //
+ // get bits
+ //
+ for (i = 0; i < 8; i++) {
+ i2c_delay(CLOCK_LOW_TIME/2);
+ //
+ // low clock period
+ //
+ i2c_clk(I2C_CLOCK_HIGH);
+ //
+ // switch off I2C
+ //
+ i2c_data(1);
+ i2c_disable();
+ i2c_dir_in();
+ //
+ // wait before getting bit
+ //
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ aBitByte = (aBitByte << 1);
+ iaa = i2c_getbit();
+ aBitByte = aBitByte | iaa ;
+ //if (iaa > 0) dd++;
+ //
+ // wait
+ //
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ //
+ // end clock puls
+ //
+ i2c_enable();
+ i2c_dir_out();
+ i2c_clk(I2C_CLOCK_LOW);
+ //
+ // low clock period
+ //
+ i2c_delay(CLOCK_LOW_TIME/2);
+ }
+ i2c_dir_out();
+ return aBitByte;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_getack
+*#
+*# DESCRIPTION : checks if ack was received from ic2
+*#
+*#--------------------------------------------------------------------------*/
+
+static int
+i2c_getack(void)
+{
+ int ack = 1;
+ //
+ // enable output
+ //
+ i2c_dir_out();
+ //
+ // Release data bus by setting
+ // data high
+ //
+ i2c_data(I2C_DATA_HIGH);
+ //
+ // enable input
+ //
+ i2c_dir_in();
+ i2c_delay(CLOCK_HIGH_TIME/4);
+ //
+ // generate ACK clock pulse
+ //
+ i2c_clk(I2C_CLOCK_HIGH);
+ //
+ // Use PORT PB instead of I2C
+ // for input. (I2C not working)
+ //
+ i2c_clk(1);
+ i2c_data(1);
+
+/* *R_PORT_PB_DATA = *R_PORT_PB_READ | 0x03;*/
+ //
+ // switch off I2C
+ //
+ i2c_data(1);
+ i2c_disable();
+ i2c_dir_in();
+ //
+ // now wait for ack
+ //
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ //
+ // check for ack
+ //
+ if(i2c_getbit())
+ ack = 0;
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ if(!ack){
+ if(!i2c_getbit()) /* receiver pulld SDA low */
+ ack = 1;
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ }
+
+ //
+ // end clock pulse
+ //
+ i2c_enable();
+ i2c_dir_out();
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_HIGH_TIME/4);
+ //
+ // enable output
+ //
+ i2c_dir_out();
+ //
+ // remove ACK clock pulse
+ //
+ i2c_data(I2C_DATA_HIGH);
+ i2c_delay(CLOCK_LOW_TIME/2);
+ return ack;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: I2C::sendAck
+*#
+*# DESCRIPTION : Send ACK on received data
+*#
+*#--------------------------------------------------------------------------*/
+static void
+i2c_sendack(void)
+{
+ //
+ // enable output
+ //
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_dir_out();
+ //
+ // set ack pulse high
+ //
+ i2c_data(I2C_DATA_LOW);
+ //
+ // generate clock pulse
+ //
+ i2c_delay(CLOCK_HIGH_TIME/6);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME/6);
+ //
+ // reset data out
+ //
+ i2c_data(I2C_DATA_HIGH);
+ i2c_delay(CLOCK_LOW_TIME);
+ //
+ i2c_dir_in();
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_writereg
+*#
+*# DESCRIPTION : Writes a value to an I2C device
+*#
+*#--------------------------------------------------------------------------*/
+int
+i2c_writereg(unsigned char theSlave, unsigned char theReg,
+ unsigned char theValue)
+{
+ int error, cntr = 3;
+
+ do {
+ error = 0;
+ //
+ // we don't like to be interrupted
+ //
+ cli();
+ //
+ // generate start condition
+ //
+ i2c_start();
+ //
+ // dummy preamble
+ //
+ i2c_outbyte(0x01);
+ i2c_data(I2C_DATA_HIGH);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME); /* Dummy Acknowledge */
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_LOW_TIME); /* Repeated Start Condition */
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+
+ i2c_start();
+ //
+ // send slave address
+ //
+ i2c_outbyte(theSlave);
+ //
+ // wait for ack
+ //
+ if(!i2c_getack())
+ error = 1;
+ //
+ // now select register
+ //
+ i2c_dir_out();
+ i2c_outbyte(theReg);
+ //
+ // now it's time to wait for ack
+ //
+ if(!i2c_getack())
+ error |= 2;
+ //
+ // send register register data
+ //
+ i2c_outbyte(theValue);
+ //
+ // now it's time to wait for ack
+ //
+ if(!i2c_getack())
+ error |= 4;
+ //
+ // end byte stream
+ //
+ i2c_stop();
+ //
+ // enable interrupt again
+ //
+ sti();
+
+ } while(error && cntr--);
+
+ i2c_delay(CLOCK_LOW_TIME);
+
+ return -error;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_readreg
+*#
+*# DESCRIPTION : Reads a value from the decoder registers.
+*#
+*#--------------------------------------------------------------------------*/
+unsigned char
+i2c_readreg(unsigned char theSlave, unsigned char theReg)
+{
+ unsigned char b = 0;
+ int error, cntr = 3;
+
+ do {
+ error = 0;
+ //
+ // we don't like to be interrupted
+ //
+ cli();
+ //
+ // generate start condition
+ //
+ i2c_start();
+ //
+ // dummy preamble
+ //
+ i2c_outbyte(0x01);
+ i2c_data(I2C_DATA_HIGH);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME); /* Dummy Acknowledge */
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_LOW_TIME); /* Repeated Start Condition */
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+
+ i2c_start();
+
+ //
+ // send slave address
+ //
+ i2c_outbyte(theSlave);
+ //
+ // wait for ack
+ //
+ if(!i2c_getack())
+ error = 1;
+ //
+ // now select register
+ //
+ i2c_dir_out();
+ i2c_outbyte(theReg);
+ //
+ // now it's time to wait for ack
+ //
+ if(!i2c_getack())
+ error = 1;
+ //
+ // repeat start condition
+ //
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_start();
+ //
+ // send slave address
+ //
+ i2c_outbyte(theSlave | 0x01);
+ //
+ // wait for ack
+ //
+ if(!i2c_getack())
+ error = 1;
+ //
+ // fetch register
+ //
+ b = i2c_inbyte();
+ //
+ // send Ack
+ //
+ i2c_sendack();
+ //
+ // end sequence
+ //
+ i2c_stop();
+ //
+ // enable interrupt again
+ //
+ sti();
+ } while(error && cntr--);
+
+ return b;
+}
+
+static int
+i2c_open(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static int
+i2c_release(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+/* Main device API. ioctl's to write or read to/from i2c registers.
+ */
+
+static int
+i2c_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ if(_IOC_TYPE(cmd) != ETRAXI2C_IOCTYPE) {
+ return -EINVAL;
+ }
+
+ switch (_IOC_NR(cmd)) {
+ case I2C_WRITEREG:
+ // write to an i2c slave
+ D(printk("i2cw %d %d %d\n",
+ I2C_ARGSLAVE(arg),
+ I2C_ARGREG(arg),
+ I2C_ARGVALUE(arg)));
+
+ return i2c_writereg(I2C_ARGSLAVE(arg),
+ I2C_ARGREG(arg),
+ I2C_ARGVALUE(arg));
+ case I2C_READREG:
+ {
+ unsigned char val;
+ // read from an i2c slave
+ D(printk("i2cr %d %d ",
+ I2C_ARGSLAVE(arg),
+ I2C_ARGREG(arg)));
+ val = i2c_readreg(I2C_ARGSLAVE(arg), I2C_ARGREG(arg));
+ D(printk("= %d\n", val));
+ return val;
+ }
+ default:
+ return -EINVAL;
+
+ }
+
+ return 0;
+}
+
+static struct file_operations i2c_fops = {
+ owner: THIS_MODULE,
+ ioctl: i2c_ioctl,
+ open: i2c_open,
+ release: i2c_release,
+};
+
+static int __init
+i2c_init(void)
+{
+ int res;
+
+ /* Setup and enable the Port B I2C interface */
+
+#ifndef CONFIG_ETRAX_I2C_USES_PB_NOT_PB_I2C
+ *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_clk, 1) |
+ IO_STATE(R_PORT_PB_I2C, i2c_oe_, enable);
+#endif
+
+ 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));
+
+ /* register char device */
+
+ 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;
+ }
+
+ printk("I2C driver v2.2, (c) 1999-2001 Axis Communications AB\n");
+}
+
+/* this makes sure that i2c_init is called during boot */
+
+module_init(i2c_init);
+
+/****************** END OF FILE i2c.c ********************************/
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)