summaryrefslogtreecommitdiff
path: root/drivers/sbus/char/vfc_i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sbus/char/vfc_i2c.c')
-rw-r--r--drivers/sbus/char/vfc_i2c.c347
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;
+}
+
+
+
+
+
+
+
+
+