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