]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/pc/audioac97.c
probe code
[plan9front.git] / sys / src / 9 / pc / audioac97.c
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 #include "../port/audioif.h"
9
10 typedef struct Hwdesc Hwdesc;
11 typedef struct Ctlr Ctlr;
12
13 enum {
14         Ioc     =       1<<31,
15         Bup     =       1<<30,
16 };
17
18 struct Hwdesc {
19         ulong addr;
20         ulong size;
21 };
22
23 enum {
24         Ndesc = 32,
25         Nts = 33,
26         Bufsize = 32768,        /* bytes, must be divisible by ndesc */
27         Maxbusywait = 500000, /* microseconds, roughly */
28         BytesPerSample = 4,
29 };
30
31 struct Ctlr {
32         /* keep these first, they want to be 8-aligned */
33         Hwdesc indesc[Ndesc];
34         Hwdesc outdesc[Ndesc];
35         Hwdesc micdesc[Ndesc];
36
37         Lock;
38         Rendez outr;
39
40         ulong port;
41         ulong mixport;
42
43         char *out;
44         char *in;
45         char *mic;
46
47         char *outp;
48         char *inp;
49         char *micp;
50
51         /* shared variables, ilock to access */
52         int outavail;
53         int inavail;
54         int micavail;
55
56         /* interrupt handler alone */
57         int outciv;
58         int inciv;
59         int micciv;
60
61         int tsouti;
62         uvlong tsoutp;
63         ulong tsout[Nts];
64         int tsoutb[Nts];
65
66         ulong civstat[Ndesc];
67         ulong lvistat[Ndesc];
68
69         int sis7012;
70
71         /* for probe */
72         Pcidev *pcidev;
73         Ctlr *next;
74 };
75
76 #define iorl(c, r)      (inl((c)->port+(r)))
77 #define iowl(c, r, l)   (outl((c)->port+(r), (ulong)(l)))
78
79 enum {
80         In = 0x00,
81         Out = 0x10,
82         Mic = 0x20,
83                 Bar = 0x00,     /* Base address register, 8-byte aligned */
84                 /* a 32-bit read at 0x04 can be used to get civ:lvi:sr in one step */
85                 Civ = 0x04,     /* current index value (desc being processed) */
86                 Lvi = 0x05,     /* Last valid index (index of first unused entry!) */
87                 Sr = 0x06,      /* status register */
88                         Fifoe = 1<<4,   /* fifo error (r/wc) */
89                         Bcis = 1<<3,    /* buffer completion interrupt status (r/wc) */
90                         Lvbci = 1<<2,   /* last valid buffer completion(in)/fetched(out) interrupt (r/wc) */
91                         Celv = 1<<1,    /* current equals last valid (ro) */
92                         Dch = 1<<0,     /* dma controller halted (ro) */
93                 Picb = 0x08,    /* position in current buffer */
94                 Piv = 0x0a,     /* prefetched index value */
95                 Cr = 0x0b,      /* control register */
96                         Ioce = 1<<4,    /* interrupt on buffer completion (if bit set in hwdesc.size) (rw) */
97                         Feie = 1<<3,    /* fifo error interrupt enable (rw) */
98                         Lvbie = 1<<2,   /* last valid buffer interrupt enable (rw) */
99                         RR = 1<<1,      /* reset busmaster related regs, excl. ioce,feie,lvbie (rw) */
100                         Rpbm = 1<<0,    /* run/pause busmaster. 0 stops, 1 starts (rw) */
101         Cnt = 0x2c,     /* global control */
102                 Ena16bit = 0x0<<22,
103                 Ena20bit = 0x1<<22,
104                 Ena2chan = 0x0<<20,
105                 Ena4chan = 0x1<<20,
106                 Enam6chan = 0x2<<20,
107                 EnaRESER = 0x3<<20,
108                 Sr2ie = 1<<6,   /* sdin2 interrupt enable (rw) */
109                 Srie = 1<<5,    /* sdin1 interrupt enable (rw) */
110                 Prie = 1<<4,    /* sdin0 interrupt enable (rw) */
111                 Aclso = 1<<3,   /* ac link shut-off (rw) */
112                 Acwr = 1<<2,    /* ac 97 warm reset (rw) */
113                 Accr = 1<<1,    /* ac 97 cold reset (rw) */
114                 GPIie = 1<<0,   /* GPI interrupt enable (rw) */
115         Sta = 0x30,                     /* global status */
116                 Cap6chan = 1<<21,
117                 Cap4chan = 1<<20,
118                 Md3 = 1<<17,    /* modem powerdown semaphore */
119                 Ad3 = 1<<16,    /* audio powerdown semaphore */
120                 Rcs = 1<<15,    /* read completion status (r/wc) */
121                 S2ri = 1<<29,   /* sdin2 resume interrupt (r/wc) */
122                 Sri = 1<<11,    /* sdin1 resume interrupt (r/wc) */
123                 Pri = 1<<10,    /* sdin0 resume interrupt (r/wc) */
124                 S2cr = 1<<28,   /* sdin2 codec ready (ro) */
125                 Scr = 1<<9,     /* sdin1 codec ready (ro) */
126                 Pcr = 1<<8,     /* sdin0 codec ready (ro) */
127                 Mint = 1<<7,    /* microphone in inetrrupt (ro) */
128                 Point = 1<<6,   /* pcm out interrupt (ro) */
129                 Piint = 1<<5,   /* pcm in interrupt (ro) */
130                 Moint = 1<<2,   /* modem out interrupt (ro) */
131                 Miint = 1<<1,   /* modem in interrupt (ro) */
132                 Gsci = 1<<0,    /* GPI status change interrupt */
133         Cas = 0x34,     /* codec access semaphore */
134                 Casp = 1<<0,    /* set to 1 on read if zero, cleared by hardware */
135 };
136
137 #define csr8r(c, r)     (inb((c)->port+(r)))
138 #define csr16r(c, r)    (ins((c)->port+(r)))
139 #define csr32r(c, r)    (inl((c)->port+(r)))
140 #define csr8w(c, r, b)  (outb((c)->port+(r), (int)(b)))
141 #define csr16w(c, r, w) (outs((c)->port+(r), (ushort)(w)))
142 #define csr32w(c, r, w) (outl((c)->port+(r), (ulong)(w)))
143
144 /* audioac97mix */
145 extern void ac97mixreset(Audio *,
146         void (*wr)(Audio*,int,ushort), 
147         ushort (*rr)(Audio*,int));
148
149 static void
150 ac97waitcodec(Audio *adev)
151 {
152         Ctlr *ctlr;
153         int i;
154         ctlr = adev->ctlr;
155         for(i = 0; i < Maxbusywait/10; i++){
156                 if((csr8r(ctlr, Cas) & Casp) == 0)
157                         break;
158                 microdelay(10);
159         }
160         if(i == Maxbusywait)
161                 print("#A%d: ac97 exhausted waiting codec access\n", adev->ctlrno);
162 }
163
164 static void
165 ac97mixw(Audio *adev, int port, ushort val)
166 {
167         Ctlr *ctlr;
168         ac97waitcodec(adev);
169         ctlr = adev->ctlr;
170         outs(ctlr->mixport+port, val);
171 }
172
173 static ushort
174 ac97mixr(Audio *adev, int port)
175 {
176         Ctlr *ctlr;
177         ac97waitcodec(adev);
178         ctlr = adev->ctlr;
179         return ins(ctlr->mixport+port);
180 }
181
182 static int
183 outavail(void *arg)
184 {
185         Ctlr *ctlr;
186         ctlr = arg;
187         return ctlr->outavail > 0;
188 }
189
190 static void
191 ac97interrupt(Ureg *, void *arg)
192 {
193         Audio *adev;
194         Ctlr *ctlr;
195         int civ, n, i;
196         ulong stat;
197         uvlong now;
198
199         adev = arg;
200         ctlr = adev->ctlr;
201         stat = csr32r(ctlr, Sta);
202         stat &= S2ri | Sri | Pri | Mint | Point | Piint | Moint | Miint | Gsci;
203         if(stat & Point){
204                 ilock(ctlr);
205                 if(ctlr->sis7012)
206                         csr16w(ctlr, Out + Picb, csr16r(ctlr, Out + Picb) & ~Dch);
207                 else
208                         csr16w(ctlr, Out + Sr, csr16r(ctlr, Out + Sr) & ~Dch);
209                 
210                 civ = csr8r(ctlr, Out + Civ);
211                 n = 0;
212                 while(ctlr->outciv != civ){
213                         ctlr->civstat[ctlr->outciv++]++;
214                         if(ctlr->outciv == Ndesc)
215                                 ctlr->outciv = 0;
216                         n += Bufsize/Ndesc;
217                 }
218
219                 now = fastticks(0);
220                 i = ctlr->tsouti;
221                 ctlr->tsoutb[i] = n;
222                 ctlr->tsout[i] = now - ctlr->tsoutp;
223                 ctlr->tsouti = (i + 1) % Nts;
224                 ctlr->tsoutp = now;
225                 ctlr->outavail += n;
226                 
227                 if(ctlr->outavail > Bufsize/2)
228                         wakeup(&ctlr->outr);
229                 stat &= ~Point; 
230                 iunlock(ctlr);
231         }
232         if(stat) /* have seen 0x400, which is sdin0 resume */
233                 iprint("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n",
234                         adev->ctlrno, stat);
235 }
236
237 static int
238 off2lvi(char *base, char *p)
239 {
240         int lvi;
241         lvi = p - base;
242         return lvi / (Bufsize/Ndesc);
243 }
244
245 static long
246 ac97medianoutrate(Audio *adev)
247 {
248         ulong ts[Nts], t;
249         uvlong hz;
250         int i, j;
251         Ctlr *ctlr;
252         ctlr = adev->ctlr;
253         fastticks(&hz);
254         for(i = 0; i < Nts; i++)
255                 if(ctlr->tsout[i] > 0)
256                         ts[i] = (ctlr->tsoutb[i] * hz) / ctlr->tsout[i];
257                 else
258                         ts[i] = 0;
259         for(i = 1; i < Nts; i++){
260                 t = ts[i];
261                 j = i;
262                 while(j > 0 && ts[j-1] > t){
263                         ts[j] = ts[j-1];
264                         j--;
265                 }
266                 ts[j] = t;
267         }
268         return ts[Nts/2] / BytesPerSample;
269 }
270
271 static long
272 ac97status(Audio *adev, void *a, long n, vlong)
273 {
274         char *p, *e;
275         Ctlr *ctlr;
276         int i;
277
278         ctlr = adev->ctlr;
279
280         p = a;
281         e = p + n;
282
283         p += snprint(p, e - p, "median rate %lud\n", ac97medianoutrate(adev));
284         p += snprint(p, e - p, "civ stats");
285         for(i = 0; i < Ndesc; i++)
286                 p += snprint(p, e - p, " %lud", ctlr->civstat[i]);
287         p += snprint(p, e - p, "\n");
288
289         p += snprint(p, e - p, "lvi stats");
290         for(i = 0; i < Ndesc; i++)
291                 p += snprint(p, e - p, " %lud", ctlr->lvistat[i]);
292         p += snprint(p, e - p, "\n");
293
294         return p - (char*)a;
295 }
296
297 static long
298 ac97buffered(Audio *adev)
299 {
300         Ctlr *ctlr;
301         ctlr = adev->ctlr;
302         return Bufsize - Bufsize/Ndesc - ctlr->outavail;
303 }
304
305 static void
306 ac97kick(Ctlr *ctlr, long reg)
307 {
308         csr8w(ctlr, reg+Cr, Ioce | Rpbm);
309 }
310
311 static long
312 ac97write(Audio *adev, void *a, long nwr, vlong)
313 {
314         Ctlr *ctlr;
315         char *p, *sp, *ep;
316         int len, lvi, olvi;
317         int t;
318         long n;
319
320         ctlr = adev->ctlr;
321         ilock(ctlr);
322         p = ctlr->outp;
323         sp = a;
324         ep = ctlr->out + Bufsize;
325         olvi = off2lvi(ctlr->out, p);
326         n = nwr;
327         while(n > 0){
328                 len = ep - p;
329                 if(ctlr->outavail < len)
330                         len = ctlr->outavail;
331                 if(n < len)
332                         len = n;
333                 ctlr->outavail -= len;
334                 iunlock(ctlr);
335                 memmove(p, sp, len);
336                 ilock(ctlr);
337                 p += len;
338                 sp += len;
339                 n -= len;
340                 if(p == ep)
341                         p = ctlr->out;
342                 lvi = off2lvi(ctlr->out, p);
343                 if(olvi != lvi){
344                         t = olvi;
345                         while(t != lvi){
346                                 t = (t + 1) % Ndesc;
347                                 ctlr->lvistat[t]++;
348                                 csr8w(ctlr, Out+Lvi, t);
349                                 ac97kick(ctlr, Out);
350                         }
351                         olvi = lvi;
352                 }
353                 if(ctlr->outavail == 0){
354                         ctlr->outp = p;
355                         iunlock(ctlr);
356                         sleep(&ctlr->outr, outavail, ctlr);
357                         ilock(ctlr);
358                 }
359         }
360         ctlr->outp = p;
361         iunlock(ctlr);
362         return nwr;
363 }
364
365 static Pcidev*
366 ac97match(Pcidev *p)
367 {
368         /* not all of the matched devices have been tested */
369         while(p = pcimatch(p, 0, 0))
370                 switch((p->vid<<16)|p->did){
371                 case (0x1039<<16)|0x7012:
372                 case (0x1022<<16)|0x746d:
373                 case (0x1022<<16)|0x7445:
374                 case (0x10de<<16)|0x01b1:
375                 case (0x10de<<16)|0x006a:
376                 case (0x10de<<16)|0x00da:
377                 case (0x10de<<16)|0x00ea:
378                 case (0x8086<<16)|0x2415:
379                 case (0x8086<<16)|0x2425:
380                 case (0x8086<<16)|0x2445:
381                 case (0x8086<<16)|0x2485:
382                 case (0x8086<<16)|0x24c5:
383                 case (0x8086<<16)|0x24d5:
384                 case (0x8086<<16)|0x25a6:
385                 case (0x8086<<16)|0x266e:
386                 case (0x8086<<16)|0x7195:
387                         return p;
388                 }
389         return nil;
390 }
391
392 static void
393 sethwp(Ctlr *ctlr, long off, void *ptr)
394 {
395         csr8w(ctlr, off+Cr, RR);
396         csr32w(ctlr, off+Bar, PCIWADDR(ptr));
397         csr8w(ctlr, off+Lvi, 0);
398 }
399
400 static int
401 ac97reset(Audio *adev)
402 {
403         static Ctlr *cards = nil;
404         Pcidev *p;
405         int i, irq, tbdf;
406         Ctlr *ctlr;
407         ulong ctl, stat = 0;
408
409         /* make a list of all ac97 cards if not already done */
410         if(cards == nil){
411                 p = nil;
412                 while(p = ac97match(p)){
413                         ctlr = xspanalloc(sizeof(Ctlr), 8, 0);
414                         memset(ctlr, 0, sizeof(Ctlr));
415                         ctlr->pcidev = p;
416                         ctlr->next = cards;
417                         cards = ctlr;
418                 }
419         }
420
421         /* pick a card from the list */
422         for(ctlr = cards; ctlr; ctlr = ctlr->next){
423                 if(p = ctlr->pcidev){
424                         ctlr->pcidev = nil;
425                         goto Found;
426                 }
427         }
428         return -1;
429
430 Found:
431         adev->ctlr = ctlr;
432         if(p->vid == 0x1039 && p->did == 0x7012)
433                 ctlr->sis7012 = 1;
434
435         if(p->mem[0].size == 64){
436                 ctlr->port = p->mem[0].bar & ~3;
437                 ctlr->mixport = p->mem[1].bar & ~3;
438         } else if(p->mem[1].size == 64){
439                 ctlr->port = p->mem[1].bar & ~3;
440                 ctlr->mixport = p->mem[0].bar & ~3;
441         } else if(p->mem[0].size == 256){                       /* sis7012 */
442                 ctlr->port = p->mem[1].bar & ~3;
443                 ctlr->mixport = p->mem[0].bar & ~3;
444         } else if(p->mem[1].size == 256){
445                 ctlr->port = p->mem[0].bar & ~3;
446                 ctlr->mixport = p->mem[1].bar & ~3;
447         }
448
449         irq = p->intl;
450         tbdf = p->tbdf;
451
452         print("#A%d: ac97 port 0x%04lux mixport 0x%04lux irq %d\n",
453                 adev->ctlrno, ctlr->port, ctlr->mixport, irq);
454
455         pcisetbme(p);
456         pcisetioe(p);
457
458         ctlr->mic = xspanalloc(Bufsize, 8, 0);
459         ctlr->in = xspanalloc(Bufsize, 8, 0);
460         ctlr->out = xspanalloc(Bufsize, 8, 0);
461
462         for(i = 0; i < Ndesc; i++){
463                 int size, off = i * (Bufsize/Ndesc);
464                 
465                 if(ctlr->sis7012)
466                         size = (Bufsize/Ndesc);
467                 else
468                         size = (Bufsize/Ndesc) / 2;
469                 
470                 ctlr->micdesc[i].addr = PCIWADDR(ctlr->mic + off);
471                 ctlr->micdesc[i].size = Ioc | size;
472                 ctlr->indesc[i].addr = PCIWADDR(ctlr->in + off);
473                 ctlr->indesc[i].size = Ioc | size;
474                 ctlr->outdesc[i].addr = PCIWADDR(ctlr->out + off);
475                 ctlr->outdesc[i].size = Ioc | size;
476         }
477
478         ctlr->outavail = Bufsize - Bufsize/Ndesc;
479         ctlr->outp = ctlr->out;
480
481         ctl = csr32r(ctlr, Cnt);
482         ctl &= ~(EnaRESER | Aclso);
483
484         if((ctl & Accr) == 0){
485                 print("#A%d: ac97 cold reset\n", adev->ctlrno);
486                 ctl |= Accr;
487         }else{
488                 print("#A%d: ac97 warm reset\n", adev->ctlrno);
489                 ctl |= Acwr;
490         }
491
492         csr32w(ctlr, Cnt, ctl);
493         for(i = 0; i < Maxbusywait; i++){
494                 if((csr32r(ctlr, Cnt) & Acwr) == 0)
495                         break;
496                 microdelay(1);
497         }
498         if(i == Maxbusywait)
499                 print("#A%d: ac97 gave up waiting Acwr to go down\n", adev->ctlrno);
500
501         for(i = 0; i < Maxbusywait; i++){
502                 if((stat = csr32r(ctlr, Sta)) & (Pcr | Scr | S2cr))
503                         break;
504                 microdelay(1);
505         }
506         if(i == Maxbusywait)
507                 print("#A%d: ac97 gave up waiting codecs become ready\n", adev->ctlrno);
508
509         print("#A%d: ac97 codecs ready:%s%s%s\n", adev->ctlrno,
510                 (stat & Pcr) ? " sdin0" : "",
511                 (stat & Scr) ? " sdin1" : "",
512                 (stat & S2cr) ? " sdin2" : "");
513
514         print("#A%d: ac97 codecs resumed:%s%s%s\n", adev->ctlrno,
515                 (stat & Pri) ? " sdin0" : "",
516                 (stat & Sri) ? " sdin1" : "",
517                 (stat & S2ri) ? " sdin2" : "");
518
519         sethwp(ctlr, In, ctlr->indesc);
520         sethwp(ctlr, Out, ctlr->outdesc);
521         sethwp(ctlr, Mic, ctlr->micdesc);
522
523         csr8w(ctlr, In+Cr, Ioce);       /*  | Lvbie | Feie */
524         csr8w(ctlr, Out+Cr, Ioce);      /*  | Lvbie | Feie */
525         csr8w(ctlr, Mic+Cr, Ioce);      /*  | Lvbie | Feie */
526
527         ac97mixreset(adev, ac97mixw, ac97mixr);
528
529         adev->write = ac97write;
530         adev->status = ac97status;
531         adev->buffered = ac97buffered;
532
533         intrenable(irq, ac97interrupt, adev, tbdf, adev->name);
534
535         return 0;
536 }
537
538 void
539 audioac97link(void)
540 {
541         addaudiocard("ac97", ac97reset);
542 }