xref: /plan9-contrib/sys/src/9/bcm/sdhost.c (revision 5c47fe09a0cc86dfb02c0ea4a2b6aec7eda2361f)
1 /*
2  * bcm2835 sdhost controller
3  *
4  * Copyright © 2016 Richard Miller <r.miller@acm.org>
5  */
6 
7 #include "u.h"
8 #include "../port/lib.h"
9 #include "../port/error.h"
10 #include "mem.h"
11 #include "dat.h"
12 #include "fns.h"
13 #include "io.h"
14 #include "../port/sd.h"
15 
16 extern SDio *sdcardlink;
17 
18 #define SDHOSTREGS	(VIRTIO+0x202000)
19 
20 enum {
21 	Extfreq		= 250*Mhz,	/* guess external clock frequency if vcore doesn't say */
22 	Initfreq	= 400000,	/* initialisation frequency for MMC */
23 	SDfreq		= 25*Mhz,	/* standard SD frequency */
24 	SDfreqhs	= 50*Mhz,	/* SD high speed frequency */
25 	FifoDepth	= 4,		/* "Limit fifo usage due to silicon bug" (linux driver) */
26 
27 	GoIdle		= 0,		/* mmc/sdio go idle state */
28 	MMCSelect	= 7,		/* mmc/sd card select command */
29 	Setbuswidth	= 6,		/* mmc/sd set bus width command */
30 	Switchfunc	= 6,		/* mmc/sd switch function command */
31 	Stoptransmission = 12,	/* mmc/sd stop transmission command */
32 	Appcmd = 55,			/* mmc/sd application command prefix */
33 };
34 
35 enum {
36 	/* Controller registers */
37 	Cmd		= 0x00>>2,
38 	Arg		= 0x04>>2,
39 	Timeout		= 0x08>>2,
40 	Clkdiv		= 0x0c>>2,
41 
42 	Resp0		= 0x10>>2,
43 	Resp1		= 0x14>>2,
44 	Resp2		= 0x18>>2,
45 	Resp3		= 0x1c>>2,
46 
47 	Status		= 0x20>>2,
48 	Poweron		= 0x30>>2,
49 	Dbgmode		= 0x34>>2,
50 	Hconfig		= 0x38>>2,
51 	Blksize		= 0x3c>>2,
52 	Data		= 0x40>>2,
53 	Blkcount	= 0x50>>2,
54 
55 	/* Cmd */
56 	Start		= 1<<15,
57 	Failed		= 1<<14,
58 	Respmask	= 7<<9,
59 	Resp48busy	= 4<<9,
60 	Respnone	= 2<<9,
61 	Resp136		= 1<<9,
62 	Resp48		= 0<<9,
63 	Host2card	= 1<<7,
64 	Card2host	= 1<<6,
65 
66 	/* Status */
67 	Busyint		= 1<<10,
68 	Blkint		= 1<<9,
69 	Sdioint		= 1<<8,
70 	Rewtimeout	= 1<<7,
71 	Cmdtimeout	= 1<<6,
72 	Crcerror	= 3<<4,
73 	Fifoerror	= 1<<3,
74 	Dataflag	= 1<<0,
75 	Intstatus	= (Busyint|Blkint|Sdioint|Dataflag),
76 	Errstatus	= (Rewtimeout|Cmdtimeout|Crcerror|Fifoerror),
77 
78 	/* Hconfig */
79 	BusyintEn	= 1<<10,
80 	BlkintEn	= 1<<8,
81 	SdiointEn	= 1<<5,
82 	DataintEn	= 1<<4,
83 	Slowcard	= 1<<3,
84 	Extbus4		= 1<<2,
85 	Intbuswide	= 1<<1,
86 	Cmdrelease	= 1<<0,
87 };
88 
89 static int cmdinfo[64] = {
90 [0]  Start | Respnone,
91 [2]  Start | Resp136,
92 [3]  Start | Resp48,
93 [5]  Start | Resp48,
94 [6]  Start | Resp48,
95 [63]  Start | Resp48 | Card2host,
96 [7]  Start | Resp48busy,
97 [8]  Start | Resp48,
98 [9]  Start | Resp136,
99 [11] Start | Resp48,
100 [12] Start | Resp48busy,
101 [13] Start | Resp48,
102 [16] Start | Resp48,
103 [17] Start | Resp48 | Card2host,
104 [18] Start | Resp48 | Card2host,
105 [24] Start | Resp48 | Host2card,
106 [25] Start | Resp48 | Host2card,
107 [41] Start | Resp48,
108 [52] Start | Resp48,
109 [53] Start | Resp48,
110 [55] Start | Resp48,
111 };
112 
113 typedef struct Ctlr Ctlr;
114 
115 struct Ctlr {
116 	Rendez	r;
117 	int	bcount;
118 	int	done;
119 	ulong	extclk;
120 	int	appcmd;
121 };
122 
123 static Ctlr sdhost;
124 
125 static void sdhostinterrupt(Ureg*, void*);
126 
127 static void
WR(int reg,u32int val)128 WR(int reg, u32int val)
129 {
130 	u32int *r = (u32int*)SDHOSTREGS;
131 
132 	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
133 	r[reg] = val;
134 }
135 
136 static int
datadone(void *)137 datadone(void*)
138 {
139 	return sdhost.done;
140 }
141 
142 static void
sdhostclock(uint freq)143 sdhostclock(uint freq)
144 {
145 	uint div;
146 
147 	div = sdhost.extclk / freq;
148 	if(sdhost.extclk / freq > freq)
149 		div++;
150 	if(div < 2)
151 		div = 2;
152 	WR(Clkdiv, div - 2);
153 }
154 
155 static int
sdhostinit(void)156 sdhostinit(void)
157 {
158 	u32int *r;
159 	ulong clk;
160 	int i;
161 
162 	/* disconnect emmc and connect sdhost to SD card gpio pins */
163 	for(i = 48; i <= 53; i++)
164 		gpiosel(i, Alt0);
165 	clk = getclkrate(ClkCore);
166 	if(clk == 0){
167 		clk = Extfreq;
168 		print("sdhost: assuming external clock %lud Mhz\n", clk/1000000);
169 	}
170 	sdhost.extclk = clk;
171 	sdhostclock(Initfreq);
172 	r = (u32int*)SDHOSTREGS;
173 	WR(Poweron, 0);
174 	WR(Timeout, 0xF00000);
175 	WR(Dbgmode, FINS(r[Dbgmode], 9, 10, (FifoDepth | FifoDepth<<5)));
176 	return 0;
177 }
178 
179 static int
sdhostinquiry(char * inquiry,int inqlen)180 sdhostinquiry(char *inquiry, int inqlen)
181 {
182 	return snprint(inquiry, inqlen, "BCM SDHost Controller");
183 }
184 
185 static void
sdhostenable(void)186 sdhostenable(void)
187 {
188 	u32int *r;
189 
190 	r = (u32int*)SDHOSTREGS;
191 	USED(r);
192 	WR(Poweron, 1);
193 	delay(10);
194 	WR(Hconfig, Intbuswide | Slowcard | BusyintEn);
195 	WR(Clkdiv, 0x7FF);
196 	intrenable(IRQsdhost, sdhostinterrupt, nil, 0, "sdhost");
197 }
198 
199 static int
sdhostcmd(u32int cmd,u32int arg,u32int * resp)200 sdhostcmd(u32int cmd, u32int arg, u32int *resp)
201 {
202 	u32int *r;
203 	u32int c;
204 	int i;
205 	ulong now;
206 
207 	r = (u32int*)SDHOSTREGS;
208 	assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
209 	c = cmd | cmdinfo[cmd];
210 	/*
211 	 * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix
212 	 */
213 	if(cmd == Switchfunc && !sdhost.appcmd)
214 		c |= Card2host;
215 	if(cmd != Stoptransmission && (i = (r[Dbgmode] & 0xF)) > 2){
216 		print("sdhost: previous command stuck: Dbg=%d Cmd=%ux\n", i, r[Cmd]);
217 		error(Eio);
218 	}
219 	/*
220 	 * GoIdle indicates new card insertion: reset bus width & speed
221 	 */
222 	if(cmd == GoIdle){
223 		WR(Hconfig, r[Hconfig] & ~Extbus4);
224 		sdhostclock(Initfreq);
225 	}
226 
227 	if(r[Status] & (Errstatus|Dataflag))
228 		WR(Status, Errstatus|Dataflag);
229 	sdhost.done = 0;
230 	WR(Arg, arg);
231 	WR(Cmd, c);
232 	now = m->ticks;
233 	while(((i=r[Cmd])&(Start|Failed)) == Start)
234 		if(m->ticks-now > HZ)
235 			break;
236 	if((i&(Start|Failed)) != 0){
237 		if(r[Status] != Cmdtimeout)
238 			print("sdhost: cmd %ux arg %ux error stat %ux\n", i, arg, r[Status]);
239 		i = r[Status];
240 		WR(Status, i);
241 		error(Eio);
242 	}
243 	switch(c & Respmask){
244 	case Resp136:
245 		resp[0] = r[Resp0];
246 		resp[1] = r[Resp1];
247 		resp[2] = r[Resp2];
248 		resp[3] = r[Resp3];
249 		break;
250 	case Resp48:
251 	case Resp48busy:
252 		resp[0] = r[Resp0];
253 		break;
254 	case Respnone:
255 		resp[0] = 0;
256 		break;
257 	}
258 	if((c & Respmask) == Resp48busy){
259 		tsleep(&sdhost.r, datadone, 0, 3000);
260 	}
261 	switch(cmd) {
262 	case MMCSelect:
263 		/*
264 		 * Once card is selected, use faster clock
265 		 */
266 		delay(1);
267 		sdhostclock(SDfreq);
268 		delay(1);
269 		break;
270 	case Setbuswidth:
271 		if(sdhost.appcmd){
272 			/*
273 			 * If card bus width changes, change host bus width
274 			 */
275 			switch(arg){
276 			case 0:
277 				WR(Hconfig, r[Hconfig] & ~Extbus4);
278 				break;
279 			case 2:
280 				WR(Hconfig, r[Hconfig] | Extbus4);
281 				break;
282 			}
283 		}else{
284 			/*
285 			 * If card switched into high speed mode, increase clock speed
286 			 */
287 			if((arg&0x8000000F) == 0x80000001){
288 				delay(1);
289 				sdhostclock(SDfreqhs);
290 				delay(1);
291 			}
292 		}
293 		break;
294 	}
295 	sdhost.appcmd = (cmd == Appcmd);
296 	return 0;
297 }
298 
299 void
sdhostiosetup(int write,void * buf,int bsize,int bcount)300 sdhostiosetup(int write, void *buf, int bsize, int bcount)
301 {
302 	USED(write);
303 	USED(buf);
304 
305 	sdhost.bcount = bcount;
306 	WR(Blksize, bsize);
307 	WR(Blkcount, bcount);
308 }
309 
310 static void
sdhostio(int write,uchar * buf,int len)311 sdhostio(int write, uchar *buf, int len)
312 {
313 	u32int *r;
314 	int piolen;
315 	u32int w;
316 
317 	r = (u32int*)SDHOSTREGS;
318 	assert((len&3) == 0);
319 	assert((PTR2UINT(buf)&3) == 0);
320 	okay(1);
321 	if(waserror()){
322 		okay(0);
323 		nexterror();
324 	}
325 	/*
326 	 * According to comments in the linux driver, the hardware "doesn't
327 	 * manage the FIFO DREQs properly for multi-block transfers" on input,
328 	 * so the dma must be stopped early and the last 3 words fetched with pio
329 	 */
330 	piolen = 0;
331 	if(!write && sdhost.bcount > 1){
332 		piolen = (FifoDepth-1) * sizeof(u32int);
333 		len -= piolen;
334 	}
335 	if(write)
336 		dmastart(DmaChanSdhost, DmaDevSdhost, DmaM2D,
337 			buf, &r[Data], len);
338 	else
339 		dmastart(DmaChanSdhost, DmaDevSdhost, DmaD2M,
340 			&r[Data], buf, len);
341 	if(dmawait(DmaChanSdhost) < 0)
342 		error(Eio);
343 	if(!write){
344 		cachedinvse(buf, len);
345 		buf += len;
346 		for(; piolen > 0; piolen -= sizeof(u32int)){
347 			if((r[Dbgmode] & 0x1F00) == 0){
348 				print("sdhost: FIFO empty after short dma read\n");
349 				error(Eio);
350 			}
351 			w = r[Data];
352 			*((u32int*)buf) = w;
353 			buf += sizeof(u32int);
354 		}
355 	}
356 	poperror();
357 	okay(0);
358 }
359 
360 static void
sdhostinterrupt(Ureg *,void *)361 sdhostinterrupt(Ureg*, void*)
362 {
363 	u32int *r;
364 	int i;
365 
366 	r = (u32int*)SDHOSTREGS;
367 	i = r[Status];
368 	WR(Status, i);
369 	if(i & Busyint){
370 		sdhost.done = 1;
371 		wakeup(&sdhost.r);
372 	}
373 }
374 
375 SDio sdiohost = {
376 	"sdhost",
377 	sdhostinit,
378 	sdhostenable,
379 	sdhostinquiry,
380 	sdhostcmd,
381 	sdhostiosetup,
382 	sdhostio,
383 };
384 
385 void
sdhostlink(void)386 sdhostlink(void)
387 {
388 	sdcardlink = &sdiohost;
389 }
390