aboutsummaryrefslogtreecommitdiff
path: root/arch/cris/arch-v32/drivers/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/cris/arch-v32/drivers/i2c.c')
-rw-r--r--arch/cris/arch-v32/drivers/i2c.c611
1 files changed, 611 insertions, 0 deletions
diff --git a/arch/cris/arch-v32/drivers/i2c.c b/arch/cris/arch-v32/drivers/i2c.c
new file mode 100644
index 00000000000..440c20a9496
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/i2c.c
@@ -0,0 +1,611 @@
+/*!***************************************************************************
+*!
+*! 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.
+*| June 23 2003 Pieter Grimmerink Added 'i2c_sendnack'. i2c_readreg now
+*| generates nack on last received byte,
+*| instead of ack.
+*| i2c_getack changed data level while clock
+*| was high, causing DS75 to see a stop condition
+*!
+*! ---------------------------------------------------------------------------
+*!
+*! (C) Copyright 1999-2002 Axis Communications AB, LUND, SWEDEN
+*!
+*!***************************************************************************/
+/* $Id: i2c.c,v 1.2 2005/05/09 15:29:49 starvik 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/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
+
+#define i2c_enable()
+#define i2c_disable()
+
+/* enable or disable output-enable, to select output or input on the i2c bus */
+
+#define i2c_dir_out() crisv32_io_set_dir(&cris_i2c_data, crisv32_io_dir_out)
+#define i2c_dir_in() crisv32_io_set_dir(&cris_i2c_data, crisv32_io_dir_in)
+
+/* control the i2c clock and data signals */
+
+#define i2c_clk(x) crisv32_io_set(&cris_i2c_clk, x)
+#define i2c_data(x) crisv32_io_set(&cris_i2c_data, x)
+
+/* read a bit from the i2c interface */
+
+#define i2c_getbit() crisv32_io_rd(&cris_i2c_data)
+
+#define i2c_delay(usecs) udelay(usecs)
+
+/****************** VARIABLE SECTION ************************************/
+
+static struct crisv32_iopin cris_i2c_clk;
+static struct crisv32_iopin cris_i2c_data;
+
+/****************** FUNCTION DEFINITION SECTION *************************/
+
+
+/* generate i2c start condition */
+
+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 */
+
+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 */
+
+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 */
+
+unsigned char
+i2c_inbyte(void)
+{
+ unsigned char aBitByte = 0;
+ int i;
+
+ /* Switch off I2C to get bit */
+ i2c_disable();
+ i2c_dir_in();
+ i2c_delay(CLOCK_HIGH_TIME/2);
+
+ /* Get bit */
+ aBitByte |= i2c_getbit();
+
+ /* Enable I2C */
+ i2c_enable();
+ i2c_delay(CLOCK_LOW_TIME/2);
+
+ for (i = 1; i < 8; i++) {
+ aBitByte <<= 1;
+ /* Clock pulse */
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+
+ /* Switch off I2C to get bit */
+ i2c_disable();
+ i2c_dir_in();
+ i2c_delay(CLOCK_HIGH_TIME/2);
+
+ /* Get bit */
+ aBitByte |= i2c_getbit();
+
+ /* Enable I2C */
+ i2c_enable();
+ i2c_delay(CLOCK_LOW_TIME/2);
+ }
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+
+ /*
+ * we leave the clock low, getbyte is usually followed
+ * by sendack/nack, they assume the clock to be low
+ */
+ i2c_clk(I2C_CLOCK_LOW);
+ return aBitByte;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_getack
+*#
+*# DESCRIPTION : checks if ack was received from ic2
+*#
+*#--------------------------------------------------------------------------*/
+
+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);
+ /*
+ * 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);
+ }
+
+ /*
+ * our clock is high now, make sure data is low
+ * before we enable our output. If we keep data high
+ * and enable output, we would generate a stop condition.
+ */
+ i2c_data(I2C_DATA_LOW);
+
+ /*
+ * 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
+*#
+*#--------------------------------------------------------------------------*/
+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_sendnack
+*#
+*# DESCRIPTION : Sends NACK on received data
+*#
+*#--------------------------------------------------------------------------*/
+void
+i2c_sendnack(void)
+{
+ /*
+ * enable output
+ */
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_dir_out();
+ /*
+ * set data high
+ */
+ i2c_data(I2C_DATA_HIGH);
+ /*
+ * 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);
+
+ 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;
+ unsigned long flags;
+
+ do {
+ error = 0;
+ /*
+ * we don't like to be interrupted
+ */
+ local_irq_save(flags);
+
+ i2c_start();
+ /*
+ * send slave address
+ */
+ i2c_outbyte((theSlave & 0xfe));
+ /*
+ * 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
+ */
+ local_irq_restore(flags);
+
+ } 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;
+ unsigned long flags;
+
+ do {
+ error = 0;
+ /*
+ * we don't like to be interrupted
+ */
+ local_irq_save(flags);
+ /*
+ * generate start condition
+ */
+ i2c_start();
+
+ /*
+ * send slave address
+ */
+ i2c_outbyte((theSlave & 0xfe));
+ /*
+ * 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();
+ /*
+ * 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--);
+
+ 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,
+};
+
+int __init
+i2c_init(void)
+{
+ int res;
+
+ /* Setup and enable the Port B I2C interface */
+
+ crisv32_io_get_name(&cris_i2c_data, CONFIG_ETRAX_I2C_DATA_PORT);
+ crisv32_io_get_name(&cris_i2c_clk, CONFIG_ETRAX_I2C_CLK_PORT);
+
+ /* 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(KERN_INFO "I2C driver v2.2, (c) 1999-2001 Axis Communications AB\n");
+
+ return 0;
+}
+
+/* this makes sure that i2c_init is called during boot */
+
+module_init(i2c_init);
+
+/****************** END OF FILE i2c.c ********************************/