]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/9/pc/audiohda.c
audiohda: add pci id for nvidia GM204
[plan9front.git] / sys / src / 9 / pc / audiohda.c
index 3fc2b2342519551da8bd4d4688a1ac6e8b63d7c0..b8d3fc8d9596119c33cf4c7459aa9a0c213c5b4b 100644 (file)
@@ -11,6 +11,8 @@ typedef struct Codec Codec;
 typedef struct Ctlr Ctlr;
 typedef struct Bld Bld;
 typedef struct Ring Ring;
+typedef struct Stream Stream;
+
 typedef struct Id Id;
 typedef struct Widget Widget;
 typedef struct Codec Codec;
@@ -146,6 +148,7 @@ enum {
                        Pin = 1<<5,
                        Pbalanced = 1<<6,
                        Phdmi = 1<<7,
+                       Peapd = 1<<16,
                Inampcap = 0x0d,
                Outampcap = 0x12,
                Connlistlen = 0x0e,
@@ -158,12 +161,15 @@ enum {
        Getconnlist = 0xf02,
        Getstate = 0xf03,
        Setstate = 0x703,
+       Setpower = 0x705,
+       Getpower = 0xf05,
        Getstream = 0xf06,
        Setstream = 0x706,
        Getpinctl = 0xf07,
        Setpinctl = 0x707,
                Pinctlin = 1<<5,
                Pinctlout = 1<<6,
+               Pinctlhphn = 1<<7,
        Getunsolresp = 0xf08,
        Setunsolresp = 0x708,
        Getpinsense = 0xf09,
@@ -172,6 +178,10 @@ enum {
        Setgpi = 0x710,
        Getbeep = 0xf0a,
        Setbeep = 0x70a,
+       Seteapd = 0x70c,
+               Btlenable = 1,
+               Eapdenable = 2,
+               LRswap = 4,
        Getknob = 0xf0f,
        Setknob = 0x70f,
        Getdefault = 0xf1c,
@@ -218,6 +228,26 @@ struct Ring {
        ulong   wi;
 };
 
+struct Stream {
+       Ring    ring;
+
+       Bld     *blds;
+
+       uint    sdctl;
+       uint    sdintr;
+       uint    sdnum;
+
+       uint    afmt;
+       uint    atag;
+       int     active;
+
+       uint    pin;
+       uint    cad;
+
+       Widget  *conv;  /* DAC or ADC */
+       Widget  *jack;  /* the pin jack */
+};
+
 struct Id {
        Ctlr *ctlr;
        uint codec, nid;
@@ -237,8 +267,10 @@ struct Widget {
                        uint convrate, convfmt;
                };
        };
-       Widget *next;
-       Widget *from;
+       Widget *next;   /* next in function group */
+       Widget *path;   /* next in audio path */
+
+       Widget *link;   /* temporary for findpath */
 };
 
 struct Fungroup {
@@ -246,8 +278,6 @@ struct Fungroup {
        Codec *codec;
        uint type;
        Widget *first;
-       Widget *mixer;
-       Widget *src, *dst;
        Fungroup *next;
 };
 
@@ -286,31 +316,30 @@ struct Ctlr {
        ulong *rirb;
        ulong rirbsize;
        
-       uint sdctl;
-       uint sdintr;
-       uint sdnum;
-
-       Bld *blds;
-
-       Ring ring;
+       Stream sout;
+       Stream sin;
 
        uint iss, oss, bss;
 
        uint codecmask; 
        Codec *codec[Maxcodecs];
-
-       Widget *amp, *src;
-       uint pin;
-       uint cad;
-
-       int active;
-       uint afmt, atag;
 };
 
 #define csr32(c, r)    (*(ulong *)&(c)->mem[r])
 #define csr16(c, r)    (*(ushort *)&(c)->mem[r])
 #define csr8(c, r)     (*(uchar *)&(c)->mem[r])
 
+static char *widtype[] = {
+       "aout",
+       "ain",
+       "amix",
+       "asel",
+       "pin",
+       "power",
+       "knob",
+       "beep",
+};
+
 static char *pinport[] = {
        "jack",
        "nothing",
@@ -492,7 +521,13 @@ static uint
 getoutamprange(Widget *w)
 {
        uint r;
-       r = cmd(w->id, Getparm, Outampcap);
+
+       if((w->cap & Woutampcap) == 0)
+               return 0;
+       if((w->cap & Wampovrcap) == 0)
+               r = cmd(w->fg->id, Getparm, Outampcap);
+       else
+               r = cmd(w->id, Getparm, Outampcap);
        return (r >> 8) & 0x7f;
 }
 
@@ -515,8 +550,10 @@ setoutamp(Widget *w, int mute, int *vol)
 
        if((w->cap & Woutampcap) == 0)
                return;
-
-       r = cmd(w->id, Getparm, Outampcap);
+       if((w->cap & Wampovrcap) == 0)
+               r = cmd(w->fg->id, Getparm, Outampcap);
+       else
+               r = cmd(w->id, Getparm, Outampcap);
        zerodb = r & 0x7f;
        
        for(i=0; i<2; i++){
@@ -531,6 +568,30 @@ setoutamp(Widget *w, int mute, int *vol)
        }
 }
 
+static uint
+getinamprange(Widget *w)
+{
+       uint r;
+
+       if((w->cap & Winampcap) == 0)
+               return 0;
+       if((w->cap & Wampovrcap) == 0)
+               r = cmd(w->fg->id, Getparm, Inampcap);
+       else
+               r = cmd(w->id, Getparm, Inampcap);
+       return (r >> 8) & 0x7f;
+}
+
+static void
+getinamp(Widget *w, int vol[2])
+{
+       vol[0] = vol[1] = 0;
+       if((w->cap & Winampcap) == 0)
+               return;
+       vol[0] = cmd(w->id, Getamp, Agetin | Agetleft) & Againmask;
+       vol[1] = cmd(w->id, Getamp, Agetin | Agetright) & Againmask;
+}
+
 /* vol is 0...range or nil for 0dB; mute is 0/1; in is widget or nil for all */
 static void
 setinamp(Widget *w, Widget *in, int mute, int *vol)
@@ -540,8 +601,10 @@ setinamp(Widget *w, Widget *in, int mute, int *vol)
 
        if((w->cap & Winampcap) == 0)
                return;
-
-       r = cmd(w->id, Getparm, Inampcap);
+       if((w->cap & Wampovrcap) == 0)
+               r = cmd(w->fg->id, Getparm, Inampcap);
+       else
+               r = cmd(w->id, Getparm, Inampcap);
        zerodb = r & 0x7f;
        
        for(i=0; i<2; i++){
@@ -560,63 +623,137 @@ setinamp(Widget *w, Widget *in, int mute, int *vol)
 }
 
 static Widget *
-findpath(Widget *src)
+findpath(Widget *jack, int type, char *route)
 {
        Widget *q[Maxwidgets];
        uint l, r, i;
-       Widget *w, *v;
-       
+       Widget *w, *to;
+       Fungroup *fg;
+
+       fg = jack->fg;
+
        l = r = 0;
-       q[r++] = src;
-       for(w=src->fg->first; w; w=w->next)
-               w->from = nil;
-       src->from = src;
+       for(w=fg->first; w != nil; w = w->next)
+               w->link = nil;
+
+       if(route != nil && *route != 0){
+               w = jack;
+               while(*route++ == ','){
+                       i = strtoul(route, &route, 0);
+                       if(i >= Maxwidgets)
+                               return nil;
+                       to = fg->codec->widgets[i];
+                       if(to == nil || to->fg != fg || to->link != nil)
+                               return nil;
+                       if(type == Waout)
+                               to->link = w;
+                       else
+                               w->link = to;
+                       w = to;
+               }
+               if(w == jack || w->type != type)
+                       w = nil;
+               return w;
+       }
+
+       if(type == Waout){
+               q[r++] = jack;
+               jack->link = jack;
+       } else {
+               for(w=fg->first; w != nil; w = w->next)
+                       if(w->type == type){
+                               q[r++] = w;
+                               w->link = w;
+                       }
+       }
 
        while(l < r){
                w = q[l++];
-               if(w->type == Waout)
+               if(type == Waout){
+                       if(w->type == type)
+                               return w;
+               } else if(w == jack){
+                       for(w = jack->link; w != nil; w = w->link)
+                               if(w->type == type)
+                                       return w;
                        break;
+               }
                for(i=0; i<w->nlist; i++){
-                       v = w->list[i];
-                       if(v == nil || v->from)
+                       to = w->list[i];
+                       if(to == nil || to->link)
                                continue;
-                       v->from = w;
-                       q[r++] = v;
+                       to->link = w;
+                       q[r++] = to;
                }
        }
-       if(w->type != Waout)
-               return nil;
-       return w;
+
+       return nil;
 }
 
 static void
-connectpath(Widget *src, Widget *dst, uint stream)
+disconnectpath(Widget *from, Widget *to)
 {
-       Widget *w, *v;
-       uint i;
+       Widget *next;
 
-       for(w=src->fg->first; w != nil; w=w->next){
-               setoutamp(w, 1, nil);
-               setinamp(w, nil, 1, nil);
-               cmd(w->id, Setstream, 0);
+       for(; from != nil && from != to; from = next){
+               next = from->path;
+               from->path = nil;
+               setoutamp(from, 1, nil);
+               if(next != nil)
+                       setinamp(next, from, 1, nil);
        }
-       for(w=dst; w != src; w=v){
-               v = w->from;
-               setoutamp(w, 0, nil);
-               setinamp(v, w, 0, nil);
-               if(v->type == Waout || v->type == Wamix)
-                       continue;
-               if(v->nlist == 1)
+       setoutamp(to, 1, nil);
+}
+
+static void
+muteall(Ctlr *ctlr)
+{
+       Fungroup *fg;
+       Widget *w;
+       int i;
+
+       for(i=0; i<Maxcodecs; i++){
+               if(ctlr->codec[i] == nil)
                        continue;
-               for(i=0; i < v->nlist && v->list[i] != w; i++)
-                       ;
-               cmd(v->id, Setconn, i);
+               for(fg=ctlr->codec[i]->fgroup; fg; fg=fg->next){
+                       for(w=fg->first; w != nil; w=w->next){
+                               setinamp(w, nil, 1, nil);
+                               setoutamp(w, 1, nil);
+                               switch(w->type){
+                               case Wain:
+                               case Waout:
+                                       cmd(w->id, Setstream, 0);
+                                       break;
+                               case Wpin:
+                                       cmd(w->id, Setpinctl, 0);
+                                       break;
+                               }
+                       }
+               }
+       }
+}
+
+static void
+connectpath(Widget *from, Widget *to)
+{
+       Widget *next;
+       uint i;
+
+       for(; from != nil && from != to; from = next){
+               next = from->link;
+               from->path = next;
+               setoutamp(from, 0, nil);
+               if(next != nil){
+                       setinamp(next, from, 0, nil);
+                       for(i=0; i < next->nlist; i++){
+                               if(next->list[i] == from){
+                                       cmd(next->id, Setconn, i);      
+                                       break;
+                               }
+                       }
+               }
        }
-       setoutamp(src, 0, nil);
-       cmd(src->id, Setpinctl, Pinctlout);
-       cmd(dst->id, Setstream, (stream << 4) | 0);
-       cmd(dst->id, Setconvfmt, (1 << 14) | (1 << 4) | 1);
-       cmd(dst->id, Setchancnt, 1);
+       setoutamp(to, 0, nil);
 }
 
 static void
@@ -624,13 +761,14 @@ addconn(Widget *w, uint nid)
 {
        Widget *src;
 
-       if(nid >= Maxwidgets)
-               return;
-       if((src = w->fg->codec->widgets[nid]) == nil)
-               return;
-       for(nid=0; nid<w->nlist; nid++)
-               if(w->list[nid] == src)
-                       return;
+       src = nil;
+       if(nid < Maxwidgets)
+               src = w->fg->codec->widgets[nid];
+       if(src == nil || (src->fg != w->fg)){
+               print("hda: invalid connection %d:%s[%d] -> %d\n",
+                       w->id.nid, widtype[w->type & 7], w->nlist, nid);
+               src = nil;
+       }
        if((w->nlist % 16) == 0){
                void *p;
 
@@ -648,6 +786,9 @@ enumconns(Widget *w)
 {
        uint r, f, b, m, i, n, x, y;
 
+       if((w->cap & Wconncap) == 0)
+               return;
+
        r = cmd(w->id, Getparm, Connlistlen);
        n = r & 0x7f;
        b = (r & 0x80) ? 16 : 8;
@@ -660,7 +801,7 @@ enumconns(Widget *w)
                else
                        r = cmd(w->id, Getconnlist, i);
                y = r & (m>>1);
-               if(i && ((r & m) != y))
+               if(i && (r & m) != y)
                        while(++x < y)
                                addconn(w, x);
                addconn(w, y);
@@ -673,13 +814,16 @@ enumwidget(Widget *w)
 {
        w->cap = cmd(w->id, Getparm, Widgetcap);
        w->type = (w->cap >> 20) & 0x7;
-
-       enumconns(w);
-       
+       if(w->cap & Wpwrcap){
+               cmd(w->id, Setpower, 0);
+               delay(10);
+       }       
        switch(w->type){
        case Wpin:
                w->pin = cmd(w->id, Getdefault, 0);
                w->pincap = cmd(w->id, Getparm, Pincap);
+               if(w->pincap & Peapd)
+                       cmd(w->id, Seteapd, Eapdenable);
                break;
        }
 }
@@ -692,8 +836,26 @@ enumfungroup(Codec *codec, Id id)
        uint i, r, n, base;
 
        r = cmd(id, Getparm, Fungrtype) & 0x7f;
-       if(r != Graudio)
+       if(r != Graudio){
+               cmd(id, Setpower, 3);   /* turn off */
                return nil;
+       }
+
+       /* open eyes */
+       cmd(id, Setpower, 0);
+       delay(10);
+
+       r = cmd(id, Getparm, Subnodecnt);
+       n = r & 0xff;
+       base = (r >> 16) & 0xff;
+       if(base >= Maxwidgets){
+               print("hda: enumfungroup: base %d out of range\n", base);
+               return nil;
+       }
+       if(base+n > Maxwidgets){
+               print("hda: enumfungroup: widgets %d - %d out of range\n", base, base+n);
+               n = Maxwidgets - base;
+       }
 
        fg = mallocz(sizeof *fg, 1);
        if(fg == nil){
@@ -705,17 +867,12 @@ Nomem:
        fg->id = id;
        fg->type = r;
 
-       r = cmd(id, Getparm, Subnodecnt);
-       n = r & 0xff;
-       base = (r >> 16) & 0xff;
-       
-       if(base + n > Maxwidgets){
-               free(fg);
-               return nil;
-       }
-
        tail = &fg->first;
        for(i=0; i<n; i++){
+               if(codec->widgets[base + i] != nil){
+                       print("hda: enumfungroup: duplicate widget %d\n", base + i);
+                       continue;
+               }
                w = mallocz(sizeof(Widget), 1);
                if(w == nil){
                        while(w = fg->first){
@@ -728,18 +885,19 @@ Nomem:
                }
                w->id = newnid(id, base + i);
                w->fg = fg;
-               codec->widgets[base + i] = w;
                *tail = w;
                tail = &w->next;
+               codec->widgets[w->id.nid] = w;
        }
 
        for(i=0; i<n; i++)
                enumwidget(codec->widgets[base + i]);
+       for(i=0; i<n; i++)
+               enumconns(codec->widgets[base + i]);
 
        return fg;
 }
 
-
 static int
 enumcodec(Codec *codec, Id id)
 {
@@ -756,9 +914,6 @@ enumcodec(Codec *codec, Id id)
        codec->vid = vid;
        codec->rid = rid;
 
-       print("#A%d: codec #%d, vendor %08x, rev %08x\n",
-               id.ctlr->no, codec->id.codec, codec->vid, codec->rid);
-
        r = cmd(id, Getparm, Subnodecnt);
        n = r & 0xff;
        base = (r >> 16) & 0xff;
@@ -772,6 +927,10 @@ enumcodec(Codec *codec, Id id)
        }
        if(codec->fgroup == nil)
                return -1;
+
+       print("#A%d: codec #%d, vendor %08ux, rev %08ux\n",
+               id.ctlr->no, codec->id.codec, codec->vid, codec->rid);
+
        return 0;
 }
 
@@ -807,37 +966,105 @@ enumdev(Ctlr *ctlr)
 }
 
 static int
-connectpin(Ctlr *ctlr, uint pin, uint cad)
+connectpin(Ctlr *ctlr, Stream *s, int type, uint pin, uint cad, char *route)
 {
-       Widget *src, *dst;
+       Widget *jack, *conv;
 
-       if(cad >= Maxcodecs || pin >= Maxwidgets || ctlr->codec[cad] == nil)
+       if(s->atag == 0)
                return -1;
-       src = ctlr->codec[cad]->widgets[pin];
-       if(src == nil)
+       if(cad >= Maxcodecs || pin >= Maxwidgets || ctlr->codec[cad] == nil)
                return -1;
-       if(src->type != Wpin)
+       jack = ctlr->codec[cad]->widgets[pin];
+       if(jack == nil)
                return -1;
-       if((src->pincap & Pout) == 0)
+       if(jack->type != Wpin)
                return -1;
-       dst = findpath(src);
-       if(!dst)
+
+       conv = findpath(jack, type, route);
+       if(conv == nil)
                return -1;
-       connectpath(src, dst, ctlr->atag);
-       ctlr->amp = dst;
-       ctlr->src = src;
-       ctlr->pin = pin;
-       ctlr->cad = cad;
+
+       if(s->conv != nil && s->jack != nil){
+               if(s->conv->type == Waout)
+                       disconnectpath(s->conv, s->jack);
+               else
+                       disconnectpath(s->jack, s->conv);
+               cmd(s->conv->id, Setstream, 0);
+               cmd(s->jack->id, Setpinctl, 0);
+       }
+
+       if(type == Waout){
+               connectpath(conv, jack);
+               cmd(jack->id, Setpinctl, Pinctlout);
+       } else {
+               connectpath(jack, conv);
+               cmd(jack->id, Setpinctl, Pinctlin);
+       }
+
+       cmd(conv->id, Setconvfmt, s->afmt);
+       cmd(conv->id, Setstream, (s->atag << 4) | 0);
+       cmd(conv->id, Setchancnt, 1);
+
+       s->conv = conv;
+       s->jack = jack;
+       s->pin = pin;
+       s->cad = cad;
+
        return 0;
 }
 
 static int
-bestpin(Ctlr *ctlr, int *pcad)
+scoreout(Widget *w)
+{
+       int score;
+       uint r;
+
+       if((w->pincap & Pout) == 0)
+               return -1;
+       if(w->id.ctlr->sin.jack == w)
+               return -1;
+
+       score = 0;
+       r = w->pin;
+       if(((r >> 30) & 0x3) >= 2) /* fix or fix+jack */
+               score |= 32;
+       if(((r >> 12) & 0xf) == 4) /* green */
+               score |= 32;
+       if(((r >> 24) & 0xf) == 1) /* rear */
+               score |= 16;
+       if(((r >> 28) & 0x3) == 0) /* ext */
+               score |= 8;
+       if(((r >> 20) & 0xf) == 2) /* hpout */
+               score |= 4;
+       if(((r >> 20) & 0xf) == 0) /* lineout */
+               score |= 4;
+       return score;
+}
+
+static int
+scorein(Widget *w)
+{
+       int score;
+       uint r;
+
+       if((w->pincap & Pin) == 0)
+               return -1;
+       if(w->id.ctlr->sout.jack == w)
+               return -1;
+
+       score = 0;
+       r = w->pin;
+       if(((r >> 30) & 0x3) >= 2) /* fix or fix+jack */
+               score |= 4;
+       return score;
+}
+
+static int
+bestpin(Ctlr *ctlr, int *pcad, int (*fscore)(Widget *))
 {
        Fungroup *fg;
        Widget *w;
        int best, pin, score;
-       uint r;
        int i;
 
        pin = -1;
@@ -845,25 +1072,12 @@ bestpin(Ctlr *ctlr, int *pcad)
        for(i=0; i<Maxcodecs; i++){
                if(ctlr->codec[i] == nil)
                        continue;
-               for(fg=ctlr->codec[i]->fgroup; fg; fg=fg->next){
-                       for(w=fg->first; w; w=w->next){
+               for(fg=ctlr->codec[i]->fgroup; fg != nil; fg=fg->next){
+                       for(w=fg->first; w != nil; w=w->next){
                                if(w->type != Wpin)
                                        continue;
-                               if((w->pincap & Pout) == 0)
-                                       continue;
-                               score = 0;
-                               r = w->pin;
-                               if(((r >> 12) & 0xf) == 4) /* green */
-                                       score |= 32;
-                               if(((r >> 24) & 0xf) == 1) /* rear */
-                                       score |= 16;
-                               if(((r >> 28) & 0x3) == 0) /* ext */
-                                       score |= 8;
-                               if(((r >> 20) & 0xf) == 2) /* hpout */
-                                       score |= 4;
-                               if(((r >> 20) & 0xf) == 0) /* lineout */
-                                       score |= 4;
-                               if(score >= best){
+                               score = (*fscore)(w);
+                               if(score >= 0 && score >= best){
                                        best = score;
                                        pin = w->id.nid;
                                        *pcad = i;
@@ -898,6 +1112,29 @@ available(Ring *r)
        return m;
 }
 
+static long
+readring(Ring *r, uchar *p, long n)
+{
+       long n0, m;
+
+       n0 = n;
+       while(n > 0){
+               if((m = buffered(r)) <= 0)
+                       break;
+               if(m > n)
+                       m = n;
+               if(p){
+                       if(r->ri + m > r->nbuf)
+                               m = r->nbuf - r->ri;
+                       memmove(p, r->buf + r->ri, m);
+                       p += m;
+               }
+               r->ri = (r->ri + m) % r->nbuf;
+               n -= m;
+       }
+       return n0 - n;
+}
+
 static long
 writering(Ring *r, uchar *p, long n)
 {
@@ -922,93 +1159,87 @@ writering(Ring *r, uchar *p, long n)
 }
 
 static int
-streamalloc(Ctlr *ctlr)
+streamalloc(Ctlr *ctlr, Stream *s, int num)
 {
        Ring *r;
        int i;
 
-       r = &ctlr->ring;
-       memset(r, 0, sizeof(*r));
+       r = &s->ring;
        r->buf = xspanalloc(r->nbuf = Bufsize, 128, 0);
-       ctlr->blds = xspanalloc(Nblocks * sizeof(Bld), 128, 0);
-       if(r->buf == nil || ctlr->blds == nil){
+       s->blds = xspanalloc(Nblocks * sizeof(Bld), 128, 0);
+       if(r->buf == nil || s->blds == nil){
                print("hda: no memory for stream\n");
                return -1;
        }
        for(i=0; i<Nblocks; i++){
-               ctlr->blds[i].addrlo = PADDR(r->buf) + i*Blocksize;
-               ctlr->blds[i].addrhi = 0;
-               ctlr->blds[i].len = Blocksize;
-               ctlr->blds[i].flags = 0x01;     /* interrupt on completion */
+               s->blds[i].addrlo = PADDR(r->buf) + i*Blocksize;
+               s->blds[i].addrhi = 0;
+               s->blds[i].len = Blocksize;
+               s->blds[i].flags = 0x01;        /* interrupt on completion */
        }
 
-       /* output dma engine starts after inputs */
-       ctlr->sdnum = ctlr->iss;
-       ctlr->sdctl = Sdctl0 + ctlr->sdnum*0x20;
-       ctlr->sdintr = 1<<ctlr->sdnum;
-       ctlr->atag = ctlr->sdnum+1;
-       ctlr->afmt = Fmtstereo | Fmtsampw | Fmtdiv1 | Fmtmul1 | Fmtbase441;
-       ctlr->active = 0;
+       s->sdnum = num;
+       s->sdctl = Sdctl0 + s->sdnum*0x20;
+       s->sdintr = 1<<s->sdnum;
+       s->atag = s->sdnum+1;
+       s->afmt = Fmtstereo | Fmtsampw | Fmtdiv1 | Fmtmul1 | Fmtbase441;
+       s->active = 0;
 
        /* perform reset */
-       csr8(ctlr, ctlr->sdctl) &= ~(Srst | Srun | Scie | Seie | Sdie);
-       csr8(ctlr, ctlr->sdctl) |= Srst;
+       csr8(ctlr, s->sdctl) &= ~(Srst | Srun | Scie | Seie | Sdie);
+       csr8(ctlr, s->sdctl) |= Srst;
        microdelay(Codecdelay);
-       waitup8(ctlr, ctlr->sdctl, Srst, Srst);
-       csr8(ctlr, ctlr->sdctl) &= ~Srst;
+       waitup8(ctlr, s->sdctl, Srst, Srst);
+       csr8(ctlr, s->sdctl) &= ~Srst;
        microdelay(Codecdelay);
-       waitup8(ctlr, ctlr->sdctl, Srst, 0);
+       waitup8(ctlr, s->sdctl, Srst, 0);
 
        /* set stream number */
-       csr32(ctlr, ctlr->sdctl) = (ctlr->atag << Stagbit) |
-               (csr32(ctlr, ctlr->sdctl) & ~(0xF << Stagbit));
+       csr32(ctlr, s->sdctl) = (s->atag << Stagbit) |
+               (csr32(ctlr, s->sdctl) & ~(0xF << Stagbit));
 
        /* set stream format */
-       csr16(ctlr, Sdfmt+ctlr->sdctl) = ctlr->afmt;
+       csr16(ctlr, Sdfmt+s->sdctl) = s->afmt;
 
        /* program stream DMA & parms */
-       csr32(ctlr, Sdbdplo+ctlr->sdctl) = PADDR(ctlr->blds);
-       csr32(ctlr, Sdbdphi+ctlr->sdctl) = 0;
-       csr32(ctlr, Sdcbl+ctlr->sdctl) = r->nbuf;
-       csr16(ctlr, Sdlvi+ctlr->sdctl) = (Nblocks - 1) & 0xff;
+       csr32(ctlr, Sdbdplo+s->sdctl) = PADDR(s->blds);
+       csr32(ctlr, Sdbdphi+s->sdctl) = 0;
+       csr32(ctlr, Sdcbl+s->sdctl) = r->nbuf;
+       csr16(ctlr, Sdlvi+s->sdctl) = (Nblocks - 1) & 0xff;
 
        /* mask out ints */
-       csr8(ctlr, Sdsts+ctlr->sdctl) = Scompl | Sfifoerr | Sdescerr;
+       csr8(ctlr, Sdsts+s->sdctl) = Scompl | Sfifoerr | Sdescerr;
 
        /* enable global intrs for this stream */
-       csr32(ctlr, Intctl) |= ctlr->sdintr;
-       csr8(ctlr, ctlr->sdctl) |= Scie | Seie | Sdie;
+       csr32(ctlr, Intctl) |= s->sdintr;
+       csr8(ctlr, s->sdctl) |= Scie | Seie | Sdie;
 
        return 0;
 }
 
 static void
-streamstart(Ctlr *ctlr)
+streamstart(Ctlr *ctlr, Stream *s)
 {
-       ctlr->active = 1;
-       
-       csr8(ctlr, ctlr->sdctl) |= Srun;
-       waitup8(ctlr, ctlr->sdctl, Srun, Srun);
+       s->active = 1;
+       csr8(ctlr, s->sdctl) |= Srun;
+       waitup8(ctlr, s->sdctl, Srun, Srun);
 }
 
 static void
-streamstop(Ctlr *ctlr)
+streamstop(Ctlr *ctlr, Stream *s)
 {
-       csr8(ctlr, ctlr->sdctl) &= ~Srun;
-       waitup8(ctlr, ctlr->sdctl, Srun, 0);
-
-       ctlr->active = 0;
+       csr8(ctlr, s->sdctl) &= ~Srun;
+       waitup8(ctlr, s->sdctl, Srun, 0);
+       s->active = 0;
 }
 
 static uint
-streampos(Ctlr *ctlr)
+streampos(Ctlr *ctlr, Stream *s)
 {
-       Ring *r;
        uint p;
 
-       r = &ctlr->ring;
-       p = csr32(ctlr, Sdlpib+ctlr->sdctl);
-       if(p >= r->nbuf)
+       p = csr32(ctlr, Sdlpib+s->sdctl);
+       if(p >= s->ring.nbuf)
                p = 0;
        return p;
 }
@@ -1016,7 +1247,7 @@ streampos(Ctlr *ctlr)
 static long
 hdactl(Audio *adev, void *va, long n, vlong)
 {
-       char *p, *e, *x, *tok[4];
+       char *p, *e, *x, *route, *tok[4];
        int ntok;
        Ctlr *ctlr;
        uint pin, cad;
@@ -1026,6 +1257,7 @@ hdactl(Audio *adev, void *va, long n, vlong)
        e = p + n;
        
        for(; p < e; p = x){
+               route = nil;
                if(x = strchr(p, '\n'))
                        *x++ = 0;
                else
@@ -1034,22 +1266,38 @@ hdactl(Audio *adev, void *va, long n, vlong)
                if(ntok <= 0)
                        continue;
                if(cistrcmp(tok[0], "pin") == 0 && ntok >= 2){
-                       cad = ctlr->cad;
-                       pin = strtoul(tok[1], 0, 0);
+                       cad = ctlr->sout.cad;
+                       pin = strtoul(tok[1], &route, 0);
+                       if(ntok > 2)
+                               cad = strtoul(tok[2], 0, 0);
+                       if(connectpin(ctlr, &ctlr->sout, Waout, pin, cad, route) < 0)
+                               error("connectpin failed");
+               }else
+               if(cistrcmp(tok[0], "inpin") == 0 && ntok >= 2){
+                       cad = ctlr->sin.cad;
+                       pin = strtoul(tok[1], &route, 0);
                        if(ntok > 2)
                                cad = strtoul(tok[2], 0, 0);
-                       connectpin(ctlr, pin, cad);
+                       if(connectpin(ctlr, &ctlr->sin, Wain, pin, cad, route) < 0)
+                               error("connectpin failed");
                }else
                        error(Ebadctl);
        }
        return n;
 }
 
+static int
+inavail(void *arg)
+{
+       Ring *r = arg;
+       return buffered(r) > 0;
+}
+
 static int
 outavail(void *arg)
 {
-       Ctlr *ctlr = arg;
-       return available(&ctlr->ring) > 0;
+       Ring *r = arg;
+       return available(r) > 0;
 }
 
 static int
@@ -1057,7 +1305,7 @@ outrate(void *arg)
 {
        Ctlr *ctlr = arg;
        int delay = ctlr->adev->delay*BytesPerSample;
-       return (delay <= 0) || (buffered(&ctlr->ring) <= delay) || (ctlr->active == 0);
+       return (delay <= 0) || (buffered(&ctlr->sout.ring) <= delay) || (ctlr->sout.active == 0);
 }
 
 static long
@@ -1065,16 +1313,44 @@ hdabuffered(Audio *adev)
 {
        Ctlr *ctlr;
        ctlr = adev->ctlr;
-       return buffered(&ctlr->ring);
+       return buffered(&ctlr->sout.ring);
 }
 
 static void
 hdakick(Ctlr *ctlr)
 {
-       if(ctlr->active)
+       int delay;
+
+       if(ctlr->sout.active)
                return;
-       if(buffered(&ctlr->ring) > Blocksize)
-               streamstart(ctlr);
+       delay = ctlr->adev->delay*BytesPerSample;
+       if(buffered(&ctlr->sout.ring) >= delay)
+               streamstart(ctlr, &ctlr->sout);
+}
+
+static long
+hdaread(Audio *adev, void *vp, long n, vlong)
+{
+       uchar *p, *e;
+       Ctlr *ctlr;
+       Ring *ring;
+
+       p = vp;
+       e = p + n;
+       ctlr = adev->ctlr;
+       ring = &ctlr->sin.ring;
+       if(ring->buf == nil || ctlr->sin.conv == nil)
+               return 0;
+       while(p < e) {
+               if((n = readring(ring, p, e - p)) <= 0){
+                       if(!ctlr->sin.active)
+                               streamstart(ctlr, &ctlr->sin);
+                       sleep(&ring->r, inavail, ring);
+                       continue;
+               }
+               p += n;
+       }
+       return p - (uchar*)vp;
 }
 
 static long
@@ -1087,38 +1363,45 @@ hdawrite(Audio *adev, void *vp, long n, vlong)
        p = vp;
        e = p + n;
        ctlr = adev->ctlr;
-       ring = &ctlr->ring;
+       ring = &ctlr->sout.ring;
+       if(ring->buf == nil || ctlr->sout.conv == nil)
+               return 0;
        while(p < e) {
                if((n = writering(ring, p, e - p)) <= 0){
                        hdakick(ctlr);
-                       sleep(&ring->r, outavail, ctlr);
+                       sleep(&ring->r, outavail, ring);
                        continue;
                }
                p += n;
        }
        hdakick(ctlr);
-       sleep(&ring->r, outrate, ctlr);
+       while(outrate(ctlr) == 0)
+               sleep(&ring->r, outrate, ctlr);
        return p - (uchar*)vp;
 }
 
 static void
-hdaclose(Audio *adev)
+hdaclose(Audio *adev, int mode)
 {
        Ctlr *ctlr;
-       uchar z[1];
-       Ring *r;
+       Ring *ring;
 
        ctlr = adev->ctlr;
-       if(!ctlr->active)
-               return;
-       z[0] = 0;
-       r = &ctlr->ring;
-       while(r->wi % Blocksize)
-               hdawrite(adev, z, sizeof(z), 0);
+       if(mode == OREAD || mode == ORDWR){
+               if(ctlr->sin.active)
+                       streamstop(ctlr, &ctlr->sin);
+       }
+       if(mode == OWRITE || mode == ORDWR){
+               ring = &ctlr->sout.ring;
+               while(ring->wi % Blocksize)
+                       if(writering(ring, (uchar*)"", 1) <= 0)
+                               break;
+       }
 }
 
 enum {
        Vmaster,
+       Vrecord,
        Vspeed,
        Vdelay,
        Nvol,
@@ -1126,20 +1409,56 @@ enum {
 
 static Volume voltab[] = {
        [Vmaster] "master", 0, 0x7f, Stereo, 0,
+       [Vrecord] "recgain", 0, 0x7f, Stereo, 0,
        [Vspeed] "speed", 0, 0, Absolute, 0,
        [Vdelay] "delay", 0, 0, Absolute, 0,
        0
 };
 
+static Widget*
+findoutamp(Stream *s)
+{
+       Widget *w;
+
+       for(w = s->conv; w != nil; w = w->path){
+               if(w->cap & Woutampcap)
+                       return w;
+               if(w == s->jack)
+                       break;
+       }
+       return nil;
+}
+
+static Widget*
+findinamp(Stream *s)
+{
+       Widget *w, *p, *a;
+
+       a = nil;
+       for(p = nil, w = s->jack; w != nil; p = w, w = w->path){
+               w->link = p;    /* for setinamp */
+               if(w->cap & Winampcap)
+                       a = w;
+               if(w == s->conv)
+                       break;
+       }
+       return a;
+}
+
 static int
 hdagetvol(Audio *adev, int x, int a[2])
 {
        Ctlr *ctlr = adev->ctlr;
+       Widget *w;
 
        switch(x){
        case Vmaster:
-               if(ctlr->amp != nil)
-                       getoutamp(ctlr->amp, a);
+               if((w = findoutamp(&ctlr->sout)) != nil)
+                       getoutamp(w, a);
+               break;
+       case Vrecord:
+               if((w = findinamp(&ctlr->sin)) != nil)
+                       getinamp(w, a);
                break;
        case Vspeed:
                a[0] = adev->speed;
@@ -1155,17 +1474,28 @@ static int
 hdasetvol(Audio *adev, int x, int a[2])
 {
        Ctlr *ctlr = adev->ctlr;
+       Widget *w;
 
        switch(x){
        case Vmaster:
-               if(ctlr->amp != nil)
-                       setoutamp(ctlr->amp, 0, a);
+               if((w = findoutamp(&ctlr->sout)) != nil)
+                       setoutamp(w, 0, a);
+               break;
+       case Vrecord:
+               if((w = findinamp(&ctlr->sin)) != nil)
+                       setinamp(w, w->link, 0, a);
                break;
        case Vspeed:
                adev->speed = a[0];
                break;
        case Vdelay:
-               adev->delay = a[0];
+               if(a[0] < Blocksize/BytesPerSample) {
+                       adev->delay = Blocksize/BytesPerSample;
+               } else if(a[0] > (ctlr->sout.ring.nbuf/BytesPerSample)-1) {
+                       adev->delay = (ctlr->sout.ring.nbuf/BytesPerSample)-1;
+               } else {
+                       adev->delay = a[0];
+               }
                break;
        }
        return 0;
@@ -1174,9 +1504,13 @@ hdasetvol(Audio *adev, int x, int a[2])
 static void
 fillvoltab(Ctlr *ctlr, Volume *vt)
 {
+       Widget *w;
+
        memmove(vt, voltab, sizeof(voltab));
-       if(ctlr->amp != nil)
-               vt[Vmaster].range = getoutamprange(ctlr->amp);
+       if((w = findoutamp(&ctlr->sout)) != nil)
+               vt[Vmaster].range = getoutamprange(w);
+       if((w = findinamp(&ctlr->sin)) != nil)
+               vt[Vrecord].range = getinamprange(w);
 }
 
 static long
@@ -1200,22 +1534,32 @@ hdainterrupt(Ureg *, void *arg)
 {
        Ctlr *ctlr;
        Audio *adev;
-       uint sts;
        Ring *r;
+       uint sts;
 
        adev = arg;
        ctlr = adev->ctlr;
-       
        ilock(ctlr);
        sts = csr32(ctlr, Intsts);
-       if(sts & ctlr->sdintr){
-               /* ack interrupt */
-               csr8(ctlr, Sdsts+ctlr->sdctl) |= Scompl;
-               r = &ctlr->ring;
-               r->ri = streampos(ctlr);
-               if(ctlr->active && buffered(r) < Blocksize){
-                       streamstop(ctlr);
-                       r->ri = r->wi = streampos(ctlr);
+       if(sts & ctlr->sout.sdintr){
+               csr8(ctlr, Sdsts+ctlr->sout.sdctl) |= Scompl;
+
+               r = &ctlr->sout.ring;
+               r->ri = streampos(ctlr, &ctlr->sout);
+               if(ctlr->sout.active && buffered(r) < Blocksize){
+                       streamstop(ctlr, &ctlr->sout);
+                       r->ri = r->wi = streampos(ctlr, &ctlr->sout);
+               }
+               wakeup(&r->r);
+       }
+       if(sts & ctlr->sin.sdintr){
+               csr8(ctlr, Sdsts+ctlr->sin.sdctl) |= Scompl;
+
+               r = &ctlr->sin.ring;
+               r->wi = streampos(ctlr, &ctlr->sin);
+               if(ctlr->sin.active && available(r) < Blocksize){
+                       streamstop(ctlr, &ctlr->sin);
+                       r->ri = r->wi = streampos(ctlr, &ctlr->sin);
                }
                wakeup(&r->r);
        }
@@ -1227,37 +1571,83 @@ hdastatus(Audio *adev, void *a, long n, vlong)
 {
        Ctlr *ctlr = adev->ctlr;
        Codec *codec;
-       Fungroup *fg;
        Widget *w;
        uint r;
-       int k, i;
-       char *s;
+       int i, j, k;
+       char *s, *e;
        
        s = a;
-       k = snprint(s, n, "bufsize %6d buffered %6ld\n", Blocksize, buffered(&ctlr->ring));
+       e = s + n;
+       s = seprint(s, e, "bufsize %6d buffered %6ld\n", Blocksize, buffered(&ctlr->sout.ring));
        for(i=0; i<Maxcodecs; i++){
                if((codec = ctlr->codec[i]) == nil)
                        continue;
-               k += snprint(s+k, n-k, "codec %2d pin %3d\n",
-                       codec->id.codec, ctlr->pin);
-               for(fg=codec->fgroup; fg; fg=fg->next){
-                       for(w=fg->first; w; w=w->next){
-                               if(w->type != Wpin)
-                                       continue;
+               s = seprint(s, e, "codec %d pin %d inpin %d\n",
+                       codec->id.codec, ctlr->sout.pin, ctlr->sin.pin);
+               for(j=0; j<Maxwidgets; j++){
+                       if((w = codec->widgets[j]) == nil)
+                               continue;
+                       switch(w->type){
+                       case Wpin:
                                r = w->pin;
-                               k += snprint(s+k, n-k, "pin %3d %s %s %s %s %s %s\n",
-                                       w->id.nid,
-                                       (w->pincap & Pout) != 0 ? "out" : "in",
+                               s = seprint(s, e, "%s %d %s%s %s %s %s %s %s%s%s",
+                                       widtype[w->type&7], w->id.nid,
+                                       (w->pincap & Pin) != 0 ? "in" : "",
+                                       (w->pincap & Pout) != 0 ? "out" : "",
                                        pinport[(r >> 30) & 0x3],
                                        pinloc2[(r >> 28) & 0x3],
                                        pinloc[(r >> 24) & 0xf],
                                        pinfunc[(r >> 20) & 0xf],
-                                       pincol[(r >> 12) & 0xf]
+                                       pincol[(r >> 12) & 0xf],
+                                       (w->pincap & Phdmi) ? " hdmi" : "",
+                                       (w->pincap & Peapd) ? " eapd" : ""
                                );
+                               break;
+                       default:
+                               s = seprint(s, e, "%s %d %lux",
+                                       widtype[w->type&7], w->id.nid,
+                                       (ulong)w->cap);
+                       }
+                       if(w->nlist > 0){
+                               s = seprint(s, e, " ← ");
+                               for(k=0; k<w->nlist; k++){
+                                       if(k > 0)
+                                               s = seprint(s, e, ", ");
+                                       if(w->list[k] != nil)
+                                               s = seprint(s, e, "%s %d", widtype[w->list[k]->type&7], w->list[k]->id.nid);
+                               }
                        }
+                       s = seprint(s, e, "\n");
+               }
+       }
+
+       if(ctlr->sout.conv != nil && ctlr->sout.jack != nil){
+               s = seprint(s, e, "outpath ");
+               for(w=ctlr->sout.conv; w != nil; w = w->path){
+                       s = seprint(s, e, "%s %d", widtype[w->type&7], w->id.nid);
+                       if(w == ctlr->sout.jack)
+                               break;
+                       s = seprint(s, e, " → ");
                }
+               s = seprint(s, e, "\n");
+               if((w = findoutamp(&ctlr->sout)) != nil)
+                       s = seprint(s, e, "outamp %s %d\n", widtype[w->type&7], w->id.nid);
        }
-       return k;
+
+       if(ctlr->sin.conv != nil && ctlr->sin.jack != nil){
+               s = seprint(s, e, "inpath ");
+               for(w=ctlr->sin.jack; w != nil; w = w->path){
+                       s = seprint(s, e, "%s %d", widtype[w->type&7], w->id.nid);
+                       if(w == ctlr->sin.conv)
+                               break;
+                       s = seprint(s, e, " → ");
+               }
+               s = seprint(s, e, "\n");
+               if((w = findinamp(&ctlr->sin)) != nil)
+                       s = seprint(s, e, "inamp %s %d\n", widtype[w->type&7], w->id.nid);
+       }
+
+       return s - (char*)a;
 }
 
 
@@ -1345,9 +1735,44 @@ hdamatch(Pcidev *p)
 {
        while(p = pcimatch(p, 0, 0))
                switch((p->vid << 16) | p->did){
-               case (0x8086 << 16) | 0x27d8:
-               case (0x1002 << 16) | 0x4383:   /* ATI */
+               case (0x8086 << 16) | 0x2668:   /* Intel ICH6 (untested) */
+               case (0x8086 << 16) | 0x27d8:   /* Intel ICH7 */
+               case (0x8086 << 16) | 0x269a:   /* Intel ESB2 (untested) */
+               case (0x8086 << 16) | 0x284b:   /* Intel ICH8 */
+               case (0x8086 << 16) | 0x293f:   /* Intel ICH9 (untested) */
+               case (0x8086 << 16) | 0x293e:   /* Intel P35 (untested) */
+               case (0x8086 << 16) | 0x3b56:   /* Intel P55 (Ibex Peak) */
+               case (0x8086 << 16) | 0x811b:   /* Intel SCH (Poulsbo) */
+               case (0x8086 << 16) | 0x080a:   /* Intel SCH (Oaktrail) */
+               case (0x8086 << 16) | 0x1c20:   /* Intel PCH */
+               case (0x8086 << 16) | 0x1e20:   /* Intel (Thinkpad x230t) */
+               case (0x8086 << 16) | 0x8c20:   /* Intel 8 Series/C220 Series */
+               case (0x8086 << 16) | 0x8ca0:   /* Intel 9 Series */
+               case (0x8086 << 16) | 0x9c20:   /* Intel 8 Series Lynx Point */
+               case (0x8086 << 16) | 0x9ca0:   /* Intel Wildcat Point */
+               case (0x8086 << 16) | 0xa170:   /* Intel Sunrise Point-H */
+               case (0x8086 << 16) | 0x3a6e:   /* Intel ICH10 */
+
+               case (0x10de << 16) | 0x026c:   /* NVidia MCP51 (untested) */
+               case (0x10de << 16) | 0x0371:   /* NVidia MCP55 (untested) */
+               case (0x10de << 16) | 0x03e4:   /* NVidia MCP61 (untested) */
+               case (0x10de << 16) | 0x03f0:   /* NVidia MCP61A (untested) */
+               case (0x10de << 16) | 0x044a:   /* NVidia MCP65 (untested) */
+               case (0x10de << 16) | 0x055c:   /* NVidia MCP67 (untested) */
+               case (0x10de << 16) | 0x0fbb:   /* NVidia GM204 (untested) */
+
+               case (0x1002 << 16) | 0x437b:   /* ATI SB450 (untested) */
+               case (0x1002 << 16) | 0x4383:   /* ATI SB600 */
+               case (0x1002 << 16) | 0xaa55:   /* ATI HDMI (8500 series) */
                case (0x1002 << 16) | 0x7919:   /* ATI HDMI */
+
+               case (0x1106 << 16) | 0x3288:   /* VIA (untested) */
+               case (0x1039 << 16) | 0x7502:   /* SIS (untested) */
+               case (0x10b9 << 16) | 0x5461:   /* ULI (untested) */
+
+               case (0x1022 << 16) | 0x780d:   /* AMD FCH Azalia Controller */
+
+               case (0x15ad << 16) | 0x1977:   /* Vmware */
                        return p;
                }
        return nil;
@@ -1404,8 +1829,11 @@ hdareset(Audio *adev)
        if(cards == nil){
                p = nil;
                while(p = hdamatch(p)){
-                       ctlr = xspanalloc(sizeof(Ctlr), 8, 0);
-                       memset(ctlr, 0, sizeof(Ctlr));
+                       ctlr = mallocz(sizeof(Ctlr), 1);
+                       if(ctlr == nil){
+                               print("hda: can't allocate memory\n");
+                               return -1;
+                       }
                        ctlr->pcidev = p;
                        ctlr->next = cards;
                        cards = ctlr;
@@ -1413,7 +1841,7 @@ hdareset(Audio *adev)
        }
 
        /* pick a card from the list */
-       for(ctlr = cards; ctlr; ctlr = ctlr->next){
+       for(ctlr = cards; ctlr != nil; ctlr = ctlr->next){
                if(p = ctlr->pcidev){
                        ctlr->pcidev = nil;
                        goto Found;
@@ -1428,9 +1856,37 @@ Found:
        irq = p->intl;
        tbdf = p->tbdf;
 
-       /* magic for ATI */
-       if(p->vid == 0x1002)
-               pcicfgw8(p, 0x42, pcicfgr8(p, 0x42) | 2);
+       if(p->vid == 0x10de){
+               /* magic for NVidia */
+               pcicfgw8(p, 0x4e, (pcicfgr8(p, 0x4e) & 0xf0) | 0x0f);
+       }
+       if(p->vid == 0x10b9){
+               /* magic for ULI */
+               pcicfgw16(p, 0x40, pcicfgr16(p, 0x40) | 0x10);
+               pcicfgw32(p, PciBAR1, 0);
+       }
+       if(p->vid == 0x8086){
+               /* magic for Intel */
+               switch(p->did){
+               case 0x1c20:    /* PCH */
+               case 0x1e20:
+               case 0x811b:    /* SCH */
+               case 0x080a:
+               case 0x8c20:
+               case 0x8ca0:
+               case 0x9c20:
+               case 0x9ca0:
+               case 0xa170:
+                       pcicfgw16(p, 0x78, pcicfgr16(p, 0x78) & ~0x800);
+               }
+       }
+       if(p->vid == 0x1002){
+               /* magic for ATI */
+               pcicfgw8(p, 0x42, pcicfgr8(p, 0x42) | 0x02);
+       } else {
+               /* TCSEL */
+               pcicfgw8(p, 0x44, pcicfgr8(p, 0x44) & 0xf8);
+       }
 
        pcisetbme(p);
        pcisetpms(p, 0);
@@ -1449,24 +1905,43 @@ Found:
                print("#A%d: unable to start hda\n", ctlr->no);
                return -1;
        }
-       if(streamalloc(ctlr) < 0){
-               print("#A%d: streamalloc failed\n", ctlr->no);
-               return -1;
+
+       /* iss + oss + bss */
+       if(streamalloc(ctlr, &ctlr->sout, ctlr->iss) < 0)
+               print("#A%d: output streamalloc failed\n", ctlr->no);
+       if(ctlr->iss > 0){
+               if(streamalloc(ctlr, &ctlr->sin, 0) < 0)
+                       print("#A%d: input streamalloc failed\n", ctlr->no);
+       }
+       else if(ctlr->bss > 0){
+               if(ctlr->oss > 0){
+                       if(streamalloc(ctlr, &ctlr->sin, ctlr->oss) < 0)
+                               print("#A%d: input streamalloc failed\n", ctlr->no);
+               } else if(ctlr->bss > 1) {
+                       if(streamalloc(ctlr, &ctlr->sin, 1) < 0)
+                               print("#A%d: input streamalloc failed\n", ctlr->no);
+               }
        }
+
        if(enumdev(ctlr) < 0){
                print("#A%d: no audio codecs found\n", ctlr->no);
                return -1;
        }
-       best = bestpin(ctlr, &cad);
-       if(best < 0){
-               print("#A%d: no output pins found!\n", ctlr->no);
-               return -1;
-       }
-       if(connectpin(ctlr, best, cad) < 0){
-               print("#A%d: error connecting pin\n", ctlr->no);
-               return -1;
-       }
+       muteall(ctlr);
+
+       best = bestpin(ctlr, &cad, scoreout);
+       if(best < 0)
+               print("#A%d: no output pins found\n", ctlr->no);
+       else if(connectpin(ctlr, &ctlr->sout, Waout, best, cad, nil) < 0)
+               print("#A%d: error connecting output pin\n", ctlr->no);
+
+       best = bestpin(ctlr, &cad, scorein);
+       if(best < 0)
+               print("#A%d: no input pins found\n", ctlr->no);
+       else if(connectpin(ctlr, &ctlr->sin, Wain, best, cad, nil) < 0)
+               print("#A%d: error connecting input pin\n", ctlr->no);
 
+       adev->read = hdaread;
        adev->write = hdawrite;
        adev->close = hdaclose;
        adev->buffered = hdabuffered;