074c5279ef
Use schedule_timeout_uninterruptible() instead of set_current_state()/schedule_timeout() to reduce kernel size. Signed-off-by: Nishanth Aravamudan <nacc@us.ibm.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: David S. Miller <davem@davemloft.net>
334 lines
7.0 KiB
C
334 lines
7.0 KiB
C
/*
|
|
* 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_no_busy(struct vfc_dev *dev, unsigned long usecs)
|
|
{
|
|
schedule_timeout_uninterruptible(usecs_to_jiffies(usecs));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|