diff options
Diffstat (limited to 'drivers/sbus/char/vfc_i2c.c')
-rw-r--r-- | drivers/sbus/char/vfc_i2c.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/drivers/sbus/char/vfc_i2c.c b/drivers/sbus/char/vfc_i2c.c new file mode 100644 index 000000000000..95e3cebf792c --- /dev/null +++ b/drivers/sbus/char/vfc_i2c.c @@ -0,0 +1,347 @@ +/* + * drivers/sbus/char/vfc_i2c.c + * + * Driver for the Videopix Frame Grabber. + * + * Functions that support the Phillips i2c(I squared C) bus on the vfc + * Documentation for the Phillips I2C bus can be found on the + * phillips home page + * + * Copyright (C) 1996 Manish Vachharajani (mvachhar@noc.rutgers.edu) + * + */ + +/* NOTE: It seems to me that the documentation regarding the +pcd8584t/pcf8584 does not show the correct way to address the i2c bus. +Based on the information on the I2C bus itself and the remainder of +the Phillips docs the following algorithims apper to be correct. I am +fairly certain that the flowcharts in the phillips docs are wrong. */ + + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sbus.h> + +#if 0 +#define VFC_I2C_DEBUG +#endif + +#include "vfc.h" +#include "vfc_i2c.h" + +#define WRITE_S1(__val) \ + sbus_writel(__val, &dev->regs->i2c_s1) +#define WRITE_REG(__val) \ + sbus_writel(__val, &dev->regs->i2c_reg) + +#define VFC_I2C_READ (0x1) +#define VFC_I2C_WRITE (0x0) + +/****** + The i2c bus controller chip on the VFC is a pcd8584t, but + phillips claims it doesn't exist. As far as I can tell it is + identical to the PCF8584 so I treat it like it is the pcf8584. + + NOTE: The pcf8584 only cares + about the msb of the word you feed it +*****/ + +int vfc_pcf8584_init(struct vfc_dev *dev) +{ + /* This will also choose register S0_OWN so we can set it. */ + WRITE_S1(RESET); + + /* The pcf8584 shifts this value left one bit and uses + * it as its i2c bus address. + */ + WRITE_REG(0x55000000); + + /* This will set the i2c bus at the same speed sun uses, + * and set another magic bit. + */ + WRITE_S1(SELECT(S2)); + WRITE_REG(0x14000000); + + /* Enable the serial port, idle the i2c bus and set + * the data reg to s0. + */ + WRITE_S1(CLEAR_I2C_BUS); + udelay(100); + return 0; +} + +void vfc_i2c_delay_wakeup(struct vfc_dev *dev) +{ + /* Used to profile code and eliminate too many delays */ + VFC_I2C_DEBUG_PRINTK(("vfc%d: Delaying\n", dev->instance)); + wake_up(&dev->poll_wait); +} + +void vfc_i2c_delay_no_busy(struct vfc_dev *dev, unsigned long usecs) +{ + init_timer(&dev->poll_timer); + dev->poll_timer.expires = jiffies + + ((unsigned long)usecs*(HZ))/1000000; + dev->poll_timer.data=(unsigned long)dev; + dev->poll_timer.function=(void *)(unsigned long)vfc_i2c_delay_wakeup; + add_timer(&dev->poll_timer); + sleep_on(&dev->poll_wait); + del_timer(&dev->poll_timer); +} + +void inline vfc_i2c_delay(struct vfc_dev *dev) +{ + vfc_i2c_delay_no_busy(dev, 100); +} + +int vfc_init_i2c_bus(struct vfc_dev *dev) +{ + WRITE_S1(ENABLE_SERIAL | SELECT(S0) | ACK); + vfc_i2c_reset_bus(dev); + return 0; +} + +int vfc_i2c_reset_bus(struct vfc_dev *dev) +{ + VFC_I2C_DEBUG_PRINTK((KERN_DEBUG "vfc%d: Resetting the i2c bus\n", + dev->instance)); + if(dev == NULL) + return -EINVAL; + if(dev->regs == NULL) + return -EINVAL; + WRITE_S1(SEND_I2C_STOP); + WRITE_S1(SEND_I2C_STOP | ACK); + vfc_i2c_delay(dev); + WRITE_S1(CLEAR_I2C_BUS); + VFC_I2C_DEBUG_PRINTK((KERN_DEBUG "vfc%d: I2C status %x\n", + dev->instance, + sbus_readl(&dev->regs->i2c_s1))); + return 0; +} + +int vfc_i2c_wait_for_bus(struct vfc_dev *dev) +{ + int timeout = 1000; + + while(!(sbus_readl(&dev->regs->i2c_s1) & BB)) { + if(!(timeout--)) + return -ETIMEDOUT; + vfc_i2c_delay(dev); + } + return 0; +} + +int vfc_i2c_wait_for_pin(struct vfc_dev *dev, int ack) +{ + int timeout = 1000; + int s1; + + while ((s1 = sbus_readl(&dev->regs->i2c_s1)) & PIN) { + if (!(timeout--)) + return -ETIMEDOUT; + vfc_i2c_delay(dev); + } + if (ack == VFC_I2C_ACK_CHECK) { + if(s1 & LRB) + return -EIO; + } + return 0; +} + +#define SHIFT(a) ((a) << 24) +int vfc_i2c_xmit_addr(struct vfc_dev *dev, unsigned char addr, char mode) +{ + int ret, raddr; +#if 1 + WRITE_S1(SEND_I2C_STOP | ACK); + WRITE_S1(SELECT(S0) | ENABLE_SERIAL); + vfc_i2c_delay(dev); +#endif + + switch(mode) { + case VFC_I2C_READ: + raddr = SHIFT(((unsigned int)addr | 0x1)); + WRITE_REG(raddr); + VFC_I2C_DEBUG_PRINTK(("vfc%d: receiving from i2c addr 0x%x\n", + dev->instance, addr | 0x1)); + break; + case VFC_I2C_WRITE: + raddr = SHIFT((unsigned int)addr & ~0x1); + WRITE_REG(raddr); + VFC_I2C_DEBUG_PRINTK(("vfc%d: sending to i2c addr 0x%x\n", + dev->instance, addr & ~0x1)); + break; + default: + return -EINVAL; + }; + + WRITE_S1(SEND_I2C_START); + vfc_i2c_delay(dev); + ret = vfc_i2c_wait_for_pin(dev,VFC_I2C_ACK_CHECK); /* We wait + for the + i2c send + to finish + here but + Sun + doesn't, + hmm */ + if (ret) { + printk(KERN_ERR "vfc%d: VFC xmit addr timed out or no ack\n", + dev->instance); + return ret; + } else if (mode == VFC_I2C_READ) { + if ((ret = sbus_readl(&dev->regs->i2c_reg) & 0xff000000) != raddr) { + printk(KERN_WARNING + "vfc%d: returned slave address " + "mismatch(%x,%x)\n", + dev->instance, raddr, ret); + } + } + return 0; +} + +int vfc_i2c_xmit_byte(struct vfc_dev *dev,unsigned char *byte) +{ + int ret; + u32 val = SHIFT((unsigned int)*byte); + + WRITE_REG(val); + + ret = vfc_i2c_wait_for_pin(dev, VFC_I2C_ACK_CHECK); + switch(ret) { + case -ETIMEDOUT: + printk(KERN_ERR "vfc%d: VFC xmit byte timed out or no ack\n", + dev->instance); + break; + case -EIO: + ret = XMIT_LAST_BYTE; + break; + default: + break; + }; + + return ret; +} + +int vfc_i2c_recv_byte(struct vfc_dev *dev, unsigned char *byte, int last) +{ + int ret; + + if (last) { + WRITE_REG(NEGATIVE_ACK); + VFC_I2C_DEBUG_PRINTK(("vfc%d: sending negative ack\n", + dev->instance)); + } else { + WRITE_S1(ACK); + } + + ret = vfc_i2c_wait_for_pin(dev, VFC_I2C_NO_ACK_CHECK); + if(ret) { + printk(KERN_ERR "vfc%d: " + "VFC recv byte timed out\n", + dev->instance); + } + *byte = (sbus_readl(&dev->regs->i2c_reg)) >> 24; + return ret; +} + +int vfc_i2c_recvbuf(struct vfc_dev *dev, unsigned char addr, + char *buf, int count) +{ + int ret, last; + + if(!(count && buf && dev && dev->regs) ) + return -EINVAL; + + if ((ret = vfc_i2c_wait_for_bus(dev))) { + printk(KERN_ERR "vfc%d: VFC I2C bus busy\n", dev->instance); + return ret; + } + + if ((ret = vfc_i2c_xmit_addr(dev, addr, VFC_I2C_READ))) { + WRITE_S1(SEND_I2C_STOP); + vfc_i2c_delay(dev); + return ret; + } + + last = 0; + while (count--) { + if (!count) + last = 1; + if ((ret = vfc_i2c_recv_byte(dev, buf, last))) { + printk(KERN_ERR "vfc%d: " + "VFC error while receiving byte\n", + dev->instance); + WRITE_S1(SEND_I2C_STOP); + ret = -EINVAL; + } + buf++; + } + WRITE_S1(SEND_I2C_STOP | ACK); + vfc_i2c_delay(dev); + return ret; +} + +int vfc_i2c_sendbuf(struct vfc_dev *dev, unsigned char addr, + char *buf, int count) +{ + int ret; + + if (!(buf && dev && dev->regs)) + return -EINVAL; + + if ((ret = vfc_i2c_wait_for_bus(dev))) { + printk(KERN_ERR "vfc%d: VFC I2C bus busy\n", dev->instance); + return ret; + } + + if ((ret = vfc_i2c_xmit_addr(dev, addr, VFC_I2C_WRITE))) { + WRITE_S1(SEND_I2C_STOP); + vfc_i2c_delay(dev); + return ret; + } + + while(count--) { + ret = vfc_i2c_xmit_byte(dev, buf); + switch(ret) { + case XMIT_LAST_BYTE: + VFC_I2C_DEBUG_PRINTK(("vfc%d: " + "Receiver ended transmission with " + " %d bytes remaining\n", + dev->instance, count)); + ret = 0; + goto done; + break; + case 0: + break; + default: + printk(KERN_ERR "vfc%d: " + "VFC error while sending byte\n", dev->instance); + break; + }; + + buf++; + } +done: + WRITE_S1(SEND_I2C_STOP | ACK); + vfc_i2c_delay(dev); + return ret; +} + + + + + + + + + |