| /* |
| * 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; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |