summaryrefslogtreecommitdiff
path: root/c/src/lib/libbsp/powerpc/beatnik/marvell/gti2c.c
blob: 2cc67f32589c75f725fc35d9910bcd430a9ae6a4 (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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/*	$NetBSD: gti2c.c,v 1.2 2005/02/27 00:27:21 perry Exp $	*/

/*
 * Copyright (c) 2005 Brocade Communcations, inc.
 * All rights reserved.
 *
 * Written by Matt Thomas for Brocade Communcations, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of Brocade Communications, Inc. may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY BROCADE COMMUNICATIONS, INC. ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL EITHER BROCADE COMMUNICATIONS, INC. BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Fixed many things + ported to RTEMS by Till Straumann, 2005 */

#include <stdio.h>
#include <rtems.h>
#include <libcpu/io.h>
#include <sys/errno.h>
#include <rtems/bspIo.h>
#include <rtems/score/sysstate.h>
#include <bsp/irq.h>
#include <rtems/libi2c.h>

#include <sys/cdefs.h>

#include <bsp/gtintrreg.h>
#include <bsp/gti2creg.h>
#include <bsp/gti2c_busdrv.h>

#define ENABLE_IRQ_AT_PIC_HACK	/* workaround for a bad HW bug */
#undef  DEBUG

#ifndef BSP_IRQ_MIN_PRIO
#define BSP_IRQ_MIN_PRIO 1
#endif

struct gti2c_softc {
	uint32_t	sc_gt;
	uint32_t	sc_cntl;
	int			sc_inited;
	rtems_id	sc_sync;
	int			sc_irqs; /* statistics */
};

#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#endif

typedef struct {
	rtems_libi2c_bus_t	bus_desc;	
	struct gti2c_softc	pvt;
} gti2c_desc_rec, *gti2c_desc;

STATIC rtems_status_code
gt_i2c_init(rtems_libi2c_bus_t *bh);
STATIC rtems_status_code
gt_i2c_send_start(rtems_libi2c_bus_t *bh);
STATIC rtems_status_code
gt_i2c_send_stop(rtems_libi2c_bus_t *bh);
STATIC rtems_status_code
gt_i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw);
STATIC int
gt_i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len);
STATIC int
gt_i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len);

static rtems_libi2c_bus_ops_t myops = {
	init:			gt_i2c_init,
	send_start:		gt_i2c_send_start,
	send_stop:		gt_i2c_send_stop,
	send_addr:		gt_i2c_send_addr,
	read_bytes:		gt_i2c_read_bytes,
	write_bytes:	gt_i2c_write_bytes,
};

static gti2c_desc_rec my_bus_tbl = {
	{
		ops:	&myops,
		size:	sizeof(my_bus_tbl),
	},/* public fields */
	{
		sc_gt:		BSP_MV64x60_BASE,
		sc_cntl:	I2C_Control_TWSIEn,
		sc_inited:	0,
		sc_sync:	0
	} /* our private fields */
};


static inline uint32_t
gt_read(uint32_t base, uint32_t off)
{
	return in_le32((volatile unsigned*)(base+off));
}

static inline void
gt_write(uint32_t base, uint32_t off, uint32_t val)
{
	out_le32((volatile unsigned*)(base+off), val);
}


static inline void
disable_irq(struct gti2c_softc *sc)
{
uint32_t v = gt_read(sc->sc_gt, I2C_REG_Control);
	gt_write(sc->sc_gt, I2C_REG_Control, v & ~I2C_Control_IntEn);
}


static rtems_status_code
gt_i2c_wait(struct gti2c_softc *sc, uint32_t control, uint32_t desired_status)
{
	uint32_t status;
	rtems_status_code rval;

	control |= I2C_Control_IntEn;

	gt_write(sc->sc_gt, I2C_REG_Control, control | sc->sc_cntl);

	if ( sc->sc_inited ) {

#ifdef ENABLE_IRQ_AT_PIC_HACK
		BSP_enable_irq_at_pic(BSP_IRQ_I2C);
#endif

		rval = rtems_semaphore_obtain(sc->sc_sync, RTEMS_WAIT, 100);

		if ( RTEMS_SUCCESSFUL != rval )
			return rval;
	} else {
		uint32_t then, now;

		/* run in polling mode - useful during init */
		if ( _System_state_Is_up(_System_state_Get()) ) {
			printk("WARNING: gti2c running in polled mode -- should initialize properly!\n");
		}

		asm volatile("mftb %0":"=r"(then));

		do {
			asm volatile("mftb %0":"=r"(now));
			/* poll timebase for .2 seconds assuming a bus clock of 100MHz */
			if ( now - then > (uint32_t)100000000/4/5 )
				return RTEMS_TIMEOUT;
		} while ( ! (I2C_Control_IFlg & gt_read(sc->sc_gt, I2C_REG_Control)) );
	}

	status = gt_read(sc->sc_gt, I2C_REG_Status);

	if ( status != desired_status && (status!=I2C_Status_ReStarted || desired_status!=I2C_Status_Started) )
		return RTEMS_IO_ERROR;

	return RTEMS_SUCCESSFUL;
}

static void
gt_i2c_intr(void *arg)
{
struct gti2c_softc * const sc = &my_bus_tbl.pvt;
	uint32_t v;

	v = gt_read(sc->sc_gt, I2C_REG_Control);
	if ((v & I2C_Control_IFlg) == 0) {
		printk("gt_i2c_intr: IRQ but IFlg not set??\n");
		return;
	}
	gt_write(sc->sc_gt, I2C_REG_Control, v & ~(I2C_Control_IntEn));
#if 0
	gt_read(sc->sc_gt, I2C_REG_Control);
	asm volatile("sync");
/* This is how bad it is: after turning off the IntEn bit, the line
 * still remains asserted! (shame on you.)
 *
 * The test below (on MVME6100; the MVME5500 has the same problem
 * but the main cause register address is different; substitute
 * 0xf100000c for 0xf1000c68 on a 5500).
 * 
 * The skew was 101 TB ticks or ~3us (bus freq 133MHz) which
 * really sucks.
 *
 * Therefore, we must disable the interrupt at the PIC 
 */
{unsigned from,to;
	asm volatile("mftb %0":"=r"(from));
	while ( in_le32((volatile unsigned*)0xf100000c) & 0x20 )
		;
	asm volatile("mftb %0":"=r"(to));
	printk("I2C IRQ remained asserted for %i TB ticks!\n",to-from);
}
#endif
#ifdef ENABLE_IRQ_AT_PIC_HACK
	BSP_disable_irq_at_pic(BSP_IRQ_I2C);
#endif

	sc->sc_irqs++;

	rtems_semaphore_release(sc->sc_sync);
}

STATIC rtems_status_code
gt_i2c_init(rtems_libi2c_bus_t *bh)
{
struct gti2c_softc * const	sc = &((gti2c_desc)bh)->pvt;
unsigned					m,n,N;

	disable_irq(sc);

	/* reset */
	gt_write(sc->sc_gt, I2C_REG_SoftReset, 0);
	gt_write(sc->sc_gt, I2C_REG_SlaveAddr, 0);
	gt_write(sc->sc_gt, I2C_REG_ExtSlaveAddr, 0);

	/* Set baud rate; I don't know the details
	 * but have to assume that it has to fit into 7 bits
	 * (as indicated by some experiment)
	 */
	n = 0, N=1<<n;
	do {
		n++, N<<=1;
		/* increase 2^n until m becomes small enough */
		m = BSP_bus_frequency / 10 / 62500 / N;
	} while ( m > 16 );

	/* n is at least 1 */
	if ( n > 8 ) {
		n = 8; m = 16;	/* nothing else we can do */
	}
	if ( 0 == m )
		m = 1; /* nothing we can do */

	gt_write(sc->sc_gt, I2C_REG_BaudRate, I2C_BaudRate(m-1, n-1));

	if ( !sc->sc_inited ) { 

		if ( _System_state_Is_up(_System_state_Get()) ) {
			rtems_irq_connect_data ii = {
				name:	BSP_IRQ_I2C,
				hdl:	gt_i2c_intr,
				on:		0,
				off:	0,
				isOn:	0
			};
			rtems_status_code err;
			/* synchronization semaphore */
			err = rtems_semaphore_create(
					rtems_build_name('g','i','2','c'),
					0, 
					RTEMS_SIMPLE_BINARY_SEMAPHORE | RTEMS_LOCAL,
					0,
					&sc->sc_sync);
			if ( err ) {
				sc->sc_sync = 0;
				return err;
			}
			if ( !BSP_install_rtems_irq_handler(&ii) ) {
				fprintf(stderr,"Unable to install interrupt handler\n");
				rtems_semaphore_delete(sc->sc_sync);
				return RTEMS_INTERNAL_ERROR;
			}
			BSP_irq_set_priority(BSP_IRQ_I2C, BSP_IRQ_MIN_PRIO);
			sc->sc_inited = 1;
		} else {
		}
	} else {
		rtems_semaphore_flush(sc->sc_sync);
	}
	return RTEMS_SUCCESSFUL;
}

STATIC rtems_status_code
gt_i2c_send_start(rtems_libi2c_bus_t *bh)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;

	return gt_i2c_wait(sc, I2C_Control_Start, I2C_Status_Started);
}

STATIC rtems_status_code
gt_i2c_send_stop(rtems_libi2c_bus_t *bh)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
uint32_t	data;

	data = gt_read(sc->sc_gt, I2C_REG_Status);
	if ( I2C_Status_Started == data || I2C_Status_ReStarted == data ) {
		/* According to the spec, a void message (start - stop sequence)
		 * is illegal and indeed, the chip plays bad tricks with us, i.e.,
		 * sometimes it hangs the bus so that it remains idle forever.
		 * so we have to address someone...
		 */
		gt_i2c_send_addr(bh, /*just something... */ 8, 1);
		data = gt_read(sc->sc_gt, I2C_REG_Status);
	}

	if ( I2C_Status_AddrReadAck == data ) {
		/* Another thing: spec says that the master generates stop only after
		 * not acknowledging the last byte. Again, the chip doesn't like
		 * to be stopped in this condition - hence we just do it the favor
		 * and read a single byte...
		 */
		gt_i2c_read_bytes(bh, (unsigned char *)&data, 1);
	}

	gt_write(sc->sc_gt, I2C_REG_Control, I2C_Control_Stop | sc->sc_cntl);

	/* should we poll for idle? There seems to be in IRQ when this completes */
	return RTEMS_SUCCESSFUL;
}

STATIC rtems_status_code
gt_i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
uint32_t data, wanted_status;
uint8_t read_mask = rw ? 1 : 0;
rtems_status_code error;

	if (read_mask) {
		wanted_status = I2C_Status_AddrReadAck;
	} else {
		wanted_status = I2C_Status_AddrWriteAck;
	}
	/*
	 * First byte contains whether this xfer is a read or write.
	 */
	data = read_mask;
	if (addr > 0x7f) {
		/*
		 * If this is a 10bit request, the first address byte is
		 * 0b11110<b9><b8><r/w>.
		 */
		data |= 0xf0 | ((addr & 0x300) >> 7);
		gt_write(sc->sc_gt, I2C_REG_Data, data);
		error = gt_i2c_wait(sc, 0, wanted_status);
		if (error)
			return error;
		/*
		 * The first address byte has been sent, now to send
		 * the second one.
		 */
		if (read_mask) {
			wanted_status = I2C_Status_2ndAddrReadAck;
		} else {
			wanted_status = I2C_Status_2ndAddrWriteAck;
		}
		data = (uint8_t) addr;
	} else {
		data |= (addr << 1);
	}

	gt_write(sc->sc_gt, I2C_REG_Data, data);
	return gt_i2c_wait(sc, 0, wanted_status);
}

STATIC int
gt_i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
rtems_status_code error;
register unsigned char *p=buf;

	while ( len-- > 0 ) {
		error = gt_i2c_wait(
					sc,
					len ? I2C_Control_ACK : 0,
					len ? I2C_Status_MasterReadAck : I2C_Status_MasterReadNoAck);
		if ( error ) {
			return -error;
		}
		*p++ = gt_read(sc->sc_gt, I2C_REG_Data);
	}

	return p-buf;
}

STATIC int
gt_i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
int rval = 0;
rtems_status_code error;

	while ( len-- > 0 ) {
		gt_write(sc->sc_gt, I2C_REG_Data, buf[rval]);
		error = gt_i2c_wait(sc, 0, I2C_Status_MasterWriteAck);
		if ( error ) {
			return -error;
		}
		rval++;
	}

	return rval;
}

rtems_libi2c_bus_t *gt64260_i2c_bus_descriptor = &my_bus_tbl.bus_desc;

#ifdef DEBUG_MODULAR

void
_cexpModuleInitialize(void *arg)
{
	gt_i2c_init(&gt64260_i2c_bus_descriptor->bus_desc);
}

int
_cexpModuleFinalize(void * arg)
{
struct gti2c_softc * const sc = &gt64260_i2c_bus_descriptor->pvt;

	rtems_irq_connect_data ii = {
			name:	BSP_IRQ_I2C,
			hdl:	gt_i2c_intr,
			on:		noop,
			off:	noop,
			isOn:	inoop
	};

	rtems_semaphore_delete(sc->sc_sync);

	return !BSP_remove_rtems_irq_handler(&ii);
}

#endif