xref: /inferno-os/os/mpc/spi.c (revision d0e1d143ef6f03c75c008c7ec648859dd260cbab)
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "io.h"
7 #include "../port/error.h"
8 
9 /*
10  * basic read/write interface to mpc8xx Serial Peripheral Interface;
11  * used by devtouch.c and devspi.c
12  */
13 
14 typedef struct Ctlr Ctlr;
15 
16 enum {
17 	/* spi-specific BD flags */
18 	BDContin=	1<<9,	/* continuous mode */
19 	RxeOV=		1<<1,	/* overrun */
20 	TxeUN=		1<<1,	/* underflow */
21 	BDme=		1<<0,	/* multimaster error */
22 	BDrxerr=		RxeOV|BDme,
23 	BDtxerr=		TxeUN|BDme,
24 
25 	/* spmod */
26 	MLoop=	1<<14,	/* loopback mode */
27 	MClockInv= 1<<13,	/* inactive state of SPICLK is high */
28 	MClockPhs= 1<<12,	/* SPCLK starts toggling at beginning of transfer */
29 	MDiv16=	1<<11,	/* use BRGCLK/16 as input to SPI baud rate */
30 	MRev=	1<<10,	/* normal operation */
31 	MMaster=	1<<9,
32 	MSlave=	0<<9,
33 	MEnable=	1<<8,
34 	/* LEN, PS fields */
35 
36 	/* spcom */
37 	STR=		1<<7,	/* start transmit */
38 
39 	/* spie */
40 	MME =	1<<5,
41 	TXE =	1<<4,
42 	BSY =	1<<2,
43 	TXB =	1<<1,
44 	RXB =	1<<0,
45 
46 	/* port B bits */
47 	SPIMISO =	IBIT(28),	/* master mode input */
48 	SPIMOSI = IBIT(29),	/* master mode output */
49 	SPICLK = IBIT(30),
50 
51 	/* maximum SPI I/O (can change) */
52 	Bufsize =	64,
53 };
54 
55 /*
56  * SPI software structures
57  */
58 
59 struct Ctlr {
60 	Lock;
61 	QLock	io;
62 	int	init;
63 	SPI*	spi;
64 	IOCparam*	sp;
65 
66 	BD*	rd;
67 	BD*	td;
68 	int	phase;
69 	Rendez	r;
70 	char*	txbuf;
71 	char*	rxbuf;
72 };
73 
74 static	Ctlr	spictlr[1];
75 
76 /* dcflush isn't needed if rxbuf and txbuf allocated in uncached IMMR memory */
77 #define	DCFLUSH(a,n)
78 
79 static	void	interrupt(Ureg*, void*);
80 
81 /*
82  * called by the reset routine of any driver using the SPI
83  */
84 void
85 spireset(void)
86 {
87 	IMM *io;
88 	SPI *spi;
89 	IOCparam *sp;
90 	CPMdev *cpm;
91 	Ctlr *ctlr;
92 
93 	ctlr = spictlr;
94 	if(ctlr->init)
95 		return;
96 	ctlr->init = 1;
97 	cpm = cpmdev(CPspi);
98 	spi = cpm->regs;
99 	ctlr->spi = spi;
100 	sp = cpm->param;
101 	if(sp == nil){
102 		print("SPI: can't allocate new parameter memory\n");
103 		return;
104 	}
105 	ctlr->sp = sp;
106 
107 	if(ctlr->rxbuf == nil)
108 		ctlr->rxbuf = cpmalloc(Bufsize, 2);
109 	if(ctlr->txbuf == nil)
110 		ctlr->txbuf = cpmalloc(Bufsize, 2);
111 
112 	if(ctlr->rd == nil){
113 		ctlr->rd = bdalloc(1);
114 		ctlr->rd->addr = PADDR(ctlr->rxbuf);
115 		ctlr->rd->length = 0;
116 		ctlr->rd->status = BDWrap;
117 	}
118 	if(ctlr->td == nil){
119 		ctlr->td = bdalloc(1);
120 		ctlr->td->addr = PADDR(ctlr->txbuf);
121 		ctlr->td->length = 0;
122 		ctlr->td->status = BDWrap|BDLast;
123 	}
124 
125 	/* select port pins */
126 	io = ioplock();
127 	io->pbdir |= SPICLK | SPIMOSI | SPIMISO;
128 	io->pbpar |= SPICLK | SPIMOSI | SPIMISO;
129 	iopunlock();
130 
131 	/* explicitly initialise parameters, because InitRxTx can't be used (see i2c/spi relocation errata) */
132 	sp = ctlr->sp;
133 	sp->rbase = PADDR(ctlr->rd);
134 	sp->tbase = PADDR(ctlr->td);
135 	sp->rfcr = 0x18;
136 	sp->tfcr = 0x18;
137 	sp->mrblr = Bufsize;
138 	sp->rstate = 0;
139 	sp->rptr = 0;
140 	sp->rbptr = sp->rbase;
141 	sp->rcnt = 0;
142 	sp->tstate = 0;
143 	sp->tbptr = sp->tbase;
144 	sp->tptr = 0;
145 	sp->tcnt = 0;
146 	eieio();
147 
148 	spi->spmode = MDiv16 | MRev | MMaster | ((8-1)<<4) | 1;	/* 8 bit characters */
149 	if(0)
150 		spi->spmode |= MLoop;	/* internal loop back mode for testing */
151 
152 	spi->spie = ~0;	/* clear events */
153 	eieio();
154 	spi->spim = MME|TXE|BSY|TXB|RXB;
155 	eieio();
156 	spi->spmode |= MEnable;
157 
158 	intrenable(VectorCPIC+cpm->irq, interrupt, spictlr, BUSUNKNOWN, "spi");
159 
160 }
161 
162 enum {
163 	Idling,
164 	Waitval,
165 	Readyval
166 };
167 
168 static void
169 interrupt(Ureg*, void *arg)
170 {
171 	int events;
172 	Ctlr *ctlr;
173 	SPI *spi;
174 
175 	ctlr = arg;
176 	spi = ctlr->spi;
177 	events = spi->spie;
178 	eieio();
179 	spi->spie = events;
180 	if(events & (MME|BSY|TXE)){
181 		print("SPI#%x\n", events);
182 		if(ctlr->phase != Idling){
183 			ctlr->phase = Idling;
184 			wakeup(&ctlr->r);
185 		}
186 	}else if(events & RXB){
187 		if(ctlr->phase == Waitval){
188 			ctlr->phase = Readyval;
189 			wakeup(&ctlr->r);
190 		}
191 	}
192 }
193 
194 static int
195 done(void *a)
196 {
197 	return ((Ctlr*)a)->phase != Waitval;
198 }
199 
200 /*
201  * send `nout' bytes on SPI from `out' and read as many bytes of reply into buffer `in';
202  * return the number of bytes received, or -1 on error.
203  */
204 long
205 spioutin(void *out, long nout, void *in)
206 {
207 	Ctlr *ctlr;
208 	int nb, p;
209 
210 	ctlr = spictlr;
211 	if(nout > Bufsize)
212 		return -1;
213 	qlock(&ctlr->io);
214 	if(waserror()){
215 		qunlock(&ctlr->io);
216 		return -1;
217 	}
218 	if(ctlr->phase != Idling)
219 		sleep(&ctlr->r, done, ctlr);
220 	memmove(ctlr->txbuf, out, nout);
221 	DCFLUSH(ctlr->txbuf, Bufsize);
222 	DCFLUSH(ctlr->rxbuf, Bufsize);
223 	ilock(ctlr);
224 	ctlr->phase = Waitval;
225 	ctlr->td->length = nout;
226 	ctlr->rd->status = BDEmpty|BDWrap|BDInt;
227 	ctlr->td->status = BDReady|BDWrap|BDLast;
228 	eieio();
229 	ctlr->spi->spcom = STR;
230 	eieio();
231 	iunlock(ctlr);
232 	sleep(&ctlr->r, done, ctlr);
233 	nb = ctlr->rd->length;
234 	if(nb > nout)
235 		nb = nout;	/* shouldn't happen */
236 	p = ctlr->phase;
237 	poperror();
238 	qunlock(&ctlr->io);
239 	if(p != Readyval)
240 		return -1;
241 	memmove(in, ctlr->rxbuf, nb);
242 	return nb;
243 }
244