xref: /plan9-contrib/sys/src/9/bcm/i2c.c (revision 5c47fe09a0cc86dfb02c0ea4a2b6aec7eda2361f)
1*5c47fe09SDavid du Colombier /*
2*5c47fe09SDavid du Colombier  * bcm2835 i2c controller
3*5c47fe09SDavid du Colombier  *
4*5c47fe09SDavid du Colombier  *	Only i2c1 is supported.
5*5c47fe09SDavid du Colombier  *	i2c2 is reserved for HDMI.
6*5c47fe09SDavid du Colombier  *	i2c0 SDA0/SCL0 pins are not routed to P1 connector (except for early Rev 0 boards)
7*5c47fe09SDavid du Colombier  *
8*5c47fe09SDavid du Colombier  * maybe hardware problems lurking, see: https://github.com/raspberrypi/linux/issues/254
9*5c47fe09SDavid du Colombier  */
10*5c47fe09SDavid du Colombier 
11*5c47fe09SDavid du Colombier #include	"u.h"
12*5c47fe09SDavid du Colombier #include	"../port/lib.h"
13*5c47fe09SDavid du Colombier #include	"../port/error.h"
14*5c47fe09SDavid du Colombier #include	"mem.h"
15*5c47fe09SDavid du Colombier #include	"dat.h"
16*5c47fe09SDavid du Colombier #include	"fns.h"
17*5c47fe09SDavid du Colombier #include	"io.h"
18*5c47fe09SDavid du Colombier 
19*5c47fe09SDavid du Colombier #define I2CREGS	(VIRTIO+0x804000)
20*5c47fe09SDavid du Colombier #define SDA0Pin	2
21*5c47fe09SDavid du Colombier #define	SCL0Pin	3
22*5c47fe09SDavid du Colombier #define	Alt0	0x4
23*5c47fe09SDavid du Colombier 
24*5c47fe09SDavid du Colombier typedef struct I2c I2c;
25*5c47fe09SDavid du Colombier typedef struct Bsc Bsc;
26*5c47fe09SDavid du Colombier 
27*5c47fe09SDavid du Colombier /*
28*5c47fe09SDavid du Colombier  * Registers for Broadcom Serial Controller (i2c compatible)
29*5c47fe09SDavid du Colombier  */
30*5c47fe09SDavid du Colombier struct Bsc {
31*5c47fe09SDavid du Colombier 	u32int	ctrl;
32*5c47fe09SDavid du Colombier 	u32int	stat;
33*5c47fe09SDavid du Colombier 	u32int	dlen;
34*5c47fe09SDavid du Colombier 	u32int	addr;
35*5c47fe09SDavid du Colombier 	u32int	fifo;
36*5c47fe09SDavid du Colombier 	u32int	clkdiv;		/* default 1500 => 100 KHz assuming 150Mhz input clock */
37*5c47fe09SDavid du Colombier 	u32int	delay;		/* default (48<<16)|48 falling:rising edge */
38*5c47fe09SDavid du Colombier 	u32int	clktimeout;	/* default 64 */
39*5c47fe09SDavid du Colombier };
40*5c47fe09SDavid du Colombier 
41*5c47fe09SDavid du Colombier /*
42*5c47fe09SDavid du Colombier  * Per-controller info
43*5c47fe09SDavid du Colombier  */
44*5c47fe09SDavid du Colombier struct I2c {
45*5c47fe09SDavid du Colombier 	QLock	lock;
46*5c47fe09SDavid du Colombier 	Lock	reglock;
47*5c47fe09SDavid du Colombier 	Rendez	r;
48*5c47fe09SDavid du Colombier 	Bsc	*regs;
49*5c47fe09SDavid du Colombier };
50*5c47fe09SDavid du Colombier 
51*5c47fe09SDavid du Colombier static I2c i2c;
52*5c47fe09SDavid du Colombier 
53*5c47fe09SDavid du Colombier enum {
54*5c47fe09SDavid du Colombier 	/* ctrl */
55*5c47fe09SDavid du Colombier 	I2cen	= 1<<15,	/* I2c enable */
56*5c47fe09SDavid du Colombier 	Intr	= 1<<10,	/* interrupt on reception */
57*5c47fe09SDavid du Colombier 	Intt	= 1<<9,		/* interrupt on transmission */
58*5c47fe09SDavid du Colombier 	Intd	= 1<<8,		/* interrupt on done */
59*5c47fe09SDavid du Colombier 	Start	= 1<<7,		/* aka ST, start a transfer */
60*5c47fe09SDavid du Colombier 	Clear	= 1<<4,		/* clear fifo */
61*5c47fe09SDavid du Colombier 	Read	= 1<<0,		/* read transfer */
62*5c47fe09SDavid du Colombier 	Write	= 0<<0,		/* write transfer */
63*5c47fe09SDavid du Colombier 
64*5c47fe09SDavid du Colombier 	/* stat */
65*5c47fe09SDavid du Colombier 	Clkt	= 1<<9,		/* clock stretch timeout */
66*5c47fe09SDavid du Colombier 	Err	= 1<<8,			/* NAK */
67*5c47fe09SDavid du Colombier 	Rxf	= 1<<7,			/* RX fifo full */
68*5c47fe09SDavid du Colombier 	Txe	= 1<<6,			/* TX fifo full */
69*5c47fe09SDavid du Colombier 	Rxd	= 1<<5,			/* RX fifo has data */
70*5c47fe09SDavid du Colombier 	Txd	= 1<<4,			/* TX fifo has space */
71*5c47fe09SDavid du Colombier 	Rxr	= 1<<3,			/* RX fiio needs reading */
72*5c47fe09SDavid du Colombier 	Txw	= 1<<2,			/* TX fifo needs writing */
73*5c47fe09SDavid du Colombier 	Done	= 1<<1,		/* transfer done */
74*5c47fe09SDavid du Colombier 	Ta	= 1<<0,			/* Transfer active */
75*5c47fe09SDavid du Colombier 
76*5c47fe09SDavid du Colombier 	/* maximum I2C I/O (can change) */
77*5c47fe09SDavid du Colombier 	MaxIO =	128,
78*5c47fe09SDavid du Colombier 	MaxSA =	2,	/* longest subaddress */
79*5c47fe09SDavid du Colombier 	Bufsize = (MaxIO+MaxSA+1+4)&~3,		/* extra space for subaddress/clock bytes and alignment */
80*5c47fe09SDavid du Colombier 
81*5c47fe09SDavid du Colombier 	Chatty = 0,
82*5c47fe09SDavid du Colombier };
83*5c47fe09SDavid du Colombier 
84*5c47fe09SDavid du Colombier static void
i2cinterrupt(Ureg *,void *)85*5c47fe09SDavid du Colombier i2cinterrupt(Ureg*, void*)
86*5c47fe09SDavid du Colombier {
87*5c47fe09SDavid du Colombier 	Bsc *r;
88*5c47fe09SDavid du Colombier 	int st;
89*5c47fe09SDavid du Colombier 
90*5c47fe09SDavid du Colombier 	r = i2c.regs;
91*5c47fe09SDavid du Colombier 	st = 0;
92*5c47fe09SDavid du Colombier 	if((r->ctrl & Intr) && (r->stat & Rxd))
93*5c47fe09SDavid du Colombier 		st |= Intr;
94*5c47fe09SDavid du Colombier 	if((r->ctrl & Intt) && (r->stat & Txd))
95*5c47fe09SDavid du Colombier 		st |= Intt;
96*5c47fe09SDavid du Colombier 	if(r->stat & Done)
97*5c47fe09SDavid du Colombier 		st |= Intd;
98*5c47fe09SDavid du Colombier 	if(st){
99*5c47fe09SDavid du Colombier 		r->ctrl &= ~st;
100*5c47fe09SDavid du Colombier 		wakeup(&i2c.r);
101*5c47fe09SDavid du Colombier 	}
102*5c47fe09SDavid du Colombier }
103*5c47fe09SDavid du Colombier 
104*5c47fe09SDavid du Colombier static int
i2cready(void * st)105*5c47fe09SDavid du Colombier i2cready(void *st)
106*5c47fe09SDavid du Colombier {
107*5c47fe09SDavid du Colombier 	return (i2c.regs->stat & (uintptr)st);
108*5c47fe09SDavid du Colombier }
109*5c47fe09SDavid du Colombier 
110*5c47fe09SDavid du Colombier static void
i2cinit(void)111*5c47fe09SDavid du Colombier i2cinit(void)
112*5c47fe09SDavid du Colombier {
113*5c47fe09SDavid du Colombier 	i2c.regs = (Bsc*)I2CREGS;
114*5c47fe09SDavid du Colombier 	i2c.regs->clkdiv = 2500;
115*5c47fe09SDavid du Colombier 
116*5c47fe09SDavid du Colombier 	gpiosel(SDA0Pin, Alt0);
117*5c47fe09SDavid du Colombier 	gpiosel(SCL0Pin, Alt0);
118*5c47fe09SDavid du Colombier 	gpiopullup(SDA0Pin);
119*5c47fe09SDavid du Colombier 	gpiopullup(SCL0Pin);
120*5c47fe09SDavid du Colombier 
121*5c47fe09SDavid du Colombier 	intrenable(IRQi2c, i2cinterrupt, 0, 0, "i2c");
122*5c47fe09SDavid du Colombier }
123*5c47fe09SDavid du Colombier 
124*5c47fe09SDavid du Colombier /*
125*5c47fe09SDavid du Colombier  * To do subaddressing avoiding a STOP condition between the address and payload.
126*5c47fe09SDavid du Colombier  * 	- write the subaddress,
127*5c47fe09SDavid du Colombier  *	- poll until the transfer starts,
128*5c47fe09SDavid du Colombier  *	- overwrite the registers for the payload transfer, before the subaddress
129*5c47fe09SDavid du Colombier  * 		transaction has completed.
130*5c47fe09SDavid du Colombier  *
131*5c47fe09SDavid du Colombier  * FIXME: neither 10bit adressing nor 100Khz transfers implemented yet.
132*5c47fe09SDavid du Colombier  */
133*5c47fe09SDavid du Colombier static void
i2cio(int rw,int tenbit,uint addr,void * buf,int len,int salen,uint subaddr)134*5c47fe09SDavid du Colombier i2cio(int rw, int tenbit, uint addr, void *buf, int len, int salen, uint subaddr)
135*5c47fe09SDavid du Colombier {
136*5c47fe09SDavid du Colombier 	Bsc *r;
137*5c47fe09SDavid du Colombier 	uchar *p;
138*5c47fe09SDavid du Colombier 	int st;
139*5c47fe09SDavid du Colombier 
140*5c47fe09SDavid du Colombier 	if(tenbit)
141*5c47fe09SDavid du Colombier 		error("10bit addressing not supported");
142*5c47fe09SDavid du Colombier 	if(salen == 0 && subaddr)	/* default subaddress len == 1byte */
143*5c47fe09SDavid du Colombier 		salen = 1;
144*5c47fe09SDavid du Colombier 
145*5c47fe09SDavid du Colombier 	qlock(&i2c.lock);
146*5c47fe09SDavid du Colombier 	r = i2c.regs;
147*5c47fe09SDavid du Colombier 	r->ctrl = I2cen | Clear;
148*5c47fe09SDavid du Colombier 	r->addr = addr;
149*5c47fe09SDavid du Colombier 	r->stat = Clkt|Err|Done;
150*5c47fe09SDavid du Colombier 
151*5c47fe09SDavid du Colombier 	if(salen){
152*5c47fe09SDavid du Colombier 		r->dlen = salen;
153*5c47fe09SDavid du Colombier 		r->ctrl = I2cen | Start | Write;
154*5c47fe09SDavid du Colombier 		while((r->stat & Ta) == 0) {
155*5c47fe09SDavid du Colombier 			if(r->stat & (Err|Clkt)) {
156*5c47fe09SDavid du Colombier 				qunlock(&i2c.lock);
157*5c47fe09SDavid du Colombier 				error(Eio);
158*5c47fe09SDavid du Colombier 			}
159*5c47fe09SDavid du Colombier 		}
160*5c47fe09SDavid du Colombier 		r->dlen = len;
161*5c47fe09SDavid du Colombier 		r->ctrl = I2cen | Start | Intd | rw;
162*5c47fe09SDavid du Colombier 		for(; salen > 0; salen--)
163*5c47fe09SDavid du Colombier 			r->fifo = subaddr >> ((salen-1)*8);
164*5c47fe09SDavid du Colombier 		/*
165*5c47fe09SDavid du Colombier 		 * Adapted from Linux code...uses undocumented
166*5c47fe09SDavid du Colombier 		 * status information.
167*5c47fe09SDavid du Colombier 		 */
168*5c47fe09SDavid du Colombier 		if(rw == Read) {
169*5c47fe09SDavid du Colombier 			do {
170*5c47fe09SDavid du Colombier 				if(r->stat & (Err|Clkt)) {
171*5c47fe09SDavid du Colombier 					qunlock(&i2c.lock);
172*5c47fe09SDavid du Colombier 					error(Eio);
173*5c47fe09SDavid du Colombier 				}
174*5c47fe09SDavid du Colombier 				st = r->stat >> 28;
175*5c47fe09SDavid du Colombier 			} while(st != 0 && st != 4 && st != 5);
176*5c47fe09SDavid du Colombier 		}
177*5c47fe09SDavid du Colombier 	}
178*5c47fe09SDavid du Colombier 	else {
179*5c47fe09SDavid du Colombier 		r->dlen = len;
180*5c47fe09SDavid du Colombier 		r->ctrl = I2cen | Start | Intd | rw;
181*5c47fe09SDavid du Colombier 	}
182*5c47fe09SDavid du Colombier 
183*5c47fe09SDavid du Colombier 	p = buf;
184*5c47fe09SDavid du Colombier 	st = rw == Read? Rxd : Txd;
185*5c47fe09SDavid du Colombier 	while(len > 0){
186*5c47fe09SDavid du Colombier 		while((r->stat & (st|Done)) == 0){
187*5c47fe09SDavid du Colombier 			r->ctrl |= rw == Read? Intr : Intt;
188*5c47fe09SDavid du Colombier 			sleep(&i2c.r, i2cready, (void*)(st|Done));
189*5c47fe09SDavid du Colombier 		}
190*5c47fe09SDavid du Colombier 		if(r->stat & (Err|Clkt)){
191*5c47fe09SDavid du Colombier 			qunlock(&i2c.lock);
192*5c47fe09SDavid du Colombier 			error(Eio);
193*5c47fe09SDavid du Colombier 		}
194*5c47fe09SDavid du Colombier 		if(rw == Read){
195*5c47fe09SDavid du Colombier 			do{
196*5c47fe09SDavid du Colombier 				*p++ = r->fifo;
197*5c47fe09SDavid du Colombier 				len--;
198*5c47fe09SDavid du Colombier 			}while ((r->stat & Rxd) && len > 0);
199*5c47fe09SDavid du Colombier 		}else{
200*5c47fe09SDavid du Colombier 			do{
201*5c47fe09SDavid du Colombier 				r->fifo = *p++;
202*5c47fe09SDavid du Colombier 				len--;
203*5c47fe09SDavid du Colombier 			}while((r->stat & Txd) && len > 0);
204*5c47fe09SDavid du Colombier 		}
205*5c47fe09SDavid du Colombier 	}
206*5c47fe09SDavid du Colombier 	while((r->stat & Done) == 0)
207*5c47fe09SDavid du Colombier 		sleep(&i2c.r, i2cready, (void*)Done);
208*5c47fe09SDavid du Colombier 	if(r->stat & (Err|Clkt)){
209*5c47fe09SDavid du Colombier 		qunlock(&i2c.lock);
210*5c47fe09SDavid du Colombier 		error(Eio);
211*5c47fe09SDavid du Colombier 	}
212*5c47fe09SDavid du Colombier 	r->ctrl = 0;
213*5c47fe09SDavid du Colombier 	qunlock(&i2c.lock);
214*5c47fe09SDavid du Colombier }
215*5c47fe09SDavid du Colombier 
216*5c47fe09SDavid du Colombier 
217*5c47fe09SDavid du Colombier void
i2csetup(int)218*5c47fe09SDavid du Colombier i2csetup(int)
219*5c47fe09SDavid du Colombier {
220*5c47fe09SDavid du Colombier 	//print("i2csetup\n");
221*5c47fe09SDavid du Colombier 	i2cinit();
222*5c47fe09SDavid du Colombier }
223*5c47fe09SDavid du Colombier 
224*5c47fe09SDavid du Colombier long
i2csend(I2Cdev * d,void * buf,long len,ulong offset)225*5c47fe09SDavid du Colombier i2csend(I2Cdev *d, void *buf, long len, ulong offset)
226*5c47fe09SDavid du Colombier {
227*5c47fe09SDavid du Colombier 	i2cio(Write, d->tenbit, d->addr, buf, len, d->salen, offset);
228*5c47fe09SDavid du Colombier 	return len;
229*5c47fe09SDavid du Colombier }
230*5c47fe09SDavid du Colombier 
231*5c47fe09SDavid du Colombier long
i2crecv(I2Cdev * d,void * buf,long len,ulong offset)232*5c47fe09SDavid du Colombier i2crecv(I2Cdev *d, void *buf, long len, ulong offset)
233*5c47fe09SDavid du Colombier {
234*5c47fe09SDavid du Colombier 	i2cio(Read, d->tenbit, d->addr, buf, len, d->salen, offset);
235*5c47fe09SDavid du Colombier 	return len;
236*5c47fe09SDavid du Colombier }
237