aboutsummaryrefslogtreecommitdiff
path: root/drivers/sbus/char/vfc_i2c.c
blob: 739cad9b19a1f55a98d9c4be5a5d3a08cf43a026 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
/*
 * 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) 
{
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(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;
}