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