add ac97 driver
This commit is contained in:
parent
1a81fb86eb
commit
d642d726ba
5 changed files with 1119 additions and 1216 deletions
|
@ -1,15 +0,0 @@
|
||||||
enum
|
|
||||||
{
|
|
||||||
Bufsize = 1024, /* 5.8 ms each, must be power of two */
|
|
||||||
Nbuf = 128, /* .74 seconds total */
|
|
||||||
Dma = 6,
|
|
||||||
IrqAUDIO = 7,
|
|
||||||
SBswab = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define seteisadma(a, b) dmainit(a, Bufsize);
|
|
||||||
#define CACHELINESZ 8
|
|
||||||
#define UNCACHED(type, v) (type*)((ulong)(v))
|
|
||||||
|
|
||||||
#define Int0vec
|
|
||||||
#define setvec(v, f, a) intrenable(v, f, a, BUSUNKNOWN, "audio")
|
|
609
sys/src/9/pc/audioac97.c
Normal file
609
sys/src/9/pc/audioac97.c
Normal file
|
@ -0,0 +1,609 @@
|
||||||
|
#include "u.h"
|
||||||
|
#include "../port/lib.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fns.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "../port/error.h"
|
||||||
|
#include "../port/audio.h"
|
||||||
|
|
||||||
|
typedef struct Hwdesc Hwdesc;
|
||||||
|
typedef struct Ctlr Ctlr;
|
||||||
|
static uint sis7012 = 0;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Ioc = 1<<31,
|
||||||
|
Bup = 1<<30,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Hwdesc {
|
||||||
|
ulong addr;
|
||||||
|
ulong size;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Ndesc = 32,
|
||||||
|
Nts = 33,
|
||||||
|
Bufsize = 32768, /* bytes, must be divisible by ndesc */
|
||||||
|
Maxbusywait = 500000, /* microseconds, roughly */
|
||||||
|
BytesPerSample = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Ctlr {
|
||||||
|
/* keep these first, they want to be 8-aligned */
|
||||||
|
Hwdesc indesc[Ndesc];
|
||||||
|
Hwdesc outdesc[Ndesc];
|
||||||
|
Hwdesc micdesc[Ndesc];
|
||||||
|
|
||||||
|
Lock;
|
||||||
|
Rendez outr;
|
||||||
|
|
||||||
|
ulong port;
|
||||||
|
ulong mixport;
|
||||||
|
|
||||||
|
char *out;
|
||||||
|
char *in;
|
||||||
|
char *mic;
|
||||||
|
|
||||||
|
char *outp;
|
||||||
|
char *inp;
|
||||||
|
char *micp;
|
||||||
|
|
||||||
|
/* shared variables, ilock to access */
|
||||||
|
int outavail;
|
||||||
|
int inavail;
|
||||||
|
int micavail;
|
||||||
|
|
||||||
|
/* interrupt handler alone */
|
||||||
|
int outciv;
|
||||||
|
int inciv;
|
||||||
|
int micciv;
|
||||||
|
|
||||||
|
int tsouti;
|
||||||
|
uvlong tsoutp;
|
||||||
|
ulong tsout[Nts];
|
||||||
|
int tsoutb[Nts];
|
||||||
|
|
||||||
|
ulong civstat[Ndesc];
|
||||||
|
ulong lvistat[Ndesc];
|
||||||
|
|
||||||
|
int targetrate;
|
||||||
|
int hardrate;
|
||||||
|
|
||||||
|
int attachok;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define iorl(c, r) (inl((c)->port+(r)))
|
||||||
|
#define iowl(c, r, l) (outl((c)->port+(r), (ulong)(l)))
|
||||||
|
|
||||||
|
enum {
|
||||||
|
In = 0x00,
|
||||||
|
Out = 0x10,
|
||||||
|
Mic = 0x20,
|
||||||
|
Bar = 0x00, /* Base address register, 8-byte aligned */
|
||||||
|
/* a 32-bit read at 0x04 can be used to get civ:lvi:sr in one step */
|
||||||
|
Civ = 0x04, /* current index value (desc being processed) */
|
||||||
|
Lvi = 0x05, /* Last valid index (index of first unused entry!) */
|
||||||
|
Sr = 0x06, /* status register */
|
||||||
|
Fifoe = 1<<4, /* fifo error (r/wc) */
|
||||||
|
Bcis = 1<<3, /* buffer completion interrupt status (r/wc) */
|
||||||
|
Lvbci = 1<<2, /* last valid buffer completion(in)/fetched(out) interrupt (r/wc) */
|
||||||
|
Celv = 1<<1, /* current equals last valid (ro) */
|
||||||
|
Dch = 1<<0, /* dma controller halted (ro) */
|
||||||
|
Picb = 0x08, /* position in current buffer */
|
||||||
|
Piv = 0x0a, /* prefetched index value */
|
||||||
|
Cr = 0x0b, /* control register */
|
||||||
|
Ioce = 1<<4, /* interrupt on buffer completion (if bit set in hwdesc.size) (rw) */
|
||||||
|
Feie = 1<<3, /* fifo error interrupt enable (rw) */
|
||||||
|
Lvbie = 1<<2, /* last valid buffer interrupt enable (rw) */
|
||||||
|
RR = 1<<1, /* reset busmaster related regs, excl. ioce,feie,lvbie (rw) */
|
||||||
|
Rpbm = 1<<0, /* run/pause busmaster. 0 stops, 1 starts (rw) */
|
||||||
|
Cnt = 0x2c, /* global control */
|
||||||
|
Ena16bit = 0x0<<22,
|
||||||
|
Ena20bit = 0x1<<22,
|
||||||
|
Ena2chan = 0x0<<20,
|
||||||
|
Ena4chan = 0x1<<20,
|
||||||
|
Enam6chan = 0x2<<20,
|
||||||
|
EnaRESER = 0x3<<20,
|
||||||
|
Sr2ie = 1<<6, /* sdin2 interrupt enable (rw) */
|
||||||
|
Srie = 1<<5, /* sdin1 interrupt enable (rw) */
|
||||||
|
Prie = 1<<4, /* sdin0 interrupt enable (rw) */
|
||||||
|
Aclso = 1<<3, /* ac link shut-off (rw) */
|
||||||
|
Acwr = 1<<2, /* ac 97 warm reset (rw) */
|
||||||
|
Accr = 1<<1, /* ac 97 cold reset (rw) */
|
||||||
|
GPIie = 1<<0, /* GPI interrupt enable (rw) */
|
||||||
|
Sta = 0x30, /* global status */
|
||||||
|
Cap6chan = 1<<21,
|
||||||
|
Cap4chan = 1<<20,
|
||||||
|
Md3 = 1<<17, /* modem powerdown semaphore */
|
||||||
|
Ad3 = 1<<16, /* audio powerdown semaphore */
|
||||||
|
Rcs = 1<<15, /* read completion status (r/wc) */
|
||||||
|
S2ri = 1<<29, /* sdin2 resume interrupt (r/wc) */
|
||||||
|
Sri = 1<<11, /* sdin1 resume interrupt (r/wc) */
|
||||||
|
Pri = 1<<10, /* sdin0 resume interrupt (r/wc) */
|
||||||
|
S2cr = 1<<28, /* sdin2 codec ready (ro) */
|
||||||
|
Scr = 1<<9, /* sdin1 codec ready (ro) */
|
||||||
|
Pcr = 1<<8, /* sdin0 codec ready (ro) */
|
||||||
|
Mint = 1<<7, /* microphone in inetrrupt (ro) */
|
||||||
|
Point = 1<<6, /* pcm out interrupt (ro) */
|
||||||
|
Piint = 1<<5, /* pcm in interrupt (ro) */
|
||||||
|
Moint = 1<<2, /* modem out interrupt (ro) */
|
||||||
|
Miint = 1<<1, /* modem in interrupt (ro) */
|
||||||
|
Gsci = 1<<0, /* GPI status change interrupt */
|
||||||
|
Cas = 0x34, /* codec access semaphore */
|
||||||
|
Casp = 1<<0, /* set to 1 on read if zero, cleared by hardware */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define csr8r(c, r) (inb((c)->port+(r)))
|
||||||
|
#define csr16r(c, r) (ins((c)->port+(r)))
|
||||||
|
#define csr32r(c, r) (inl((c)->port+(r)))
|
||||||
|
#define csr8w(c, r, b) (outb((c)->port+(r), (int)(b)))
|
||||||
|
#define csr16w(c, r, w) (outs((c)->port+(r), (ushort)(w)))
|
||||||
|
#define csr32w(c, r, w) (outl((c)->port+(r), (ulong)(w)))
|
||||||
|
|
||||||
|
static void
|
||||||
|
ac97waitcodec(Audio *adev)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
int i;
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
for(i = 0; i < Maxbusywait/10; i++){
|
||||||
|
if((csr8r(ctlr, Cas) & Casp) == 0)
|
||||||
|
break;
|
||||||
|
microdelay(10);
|
||||||
|
}
|
||||||
|
if(i == Maxbusywait)
|
||||||
|
print("#A%d: ac97 exhausted waiting codec access\n", adev->ctlrno);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ac97mixw(Audio *adev, int port, ushort val)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ac97waitcodec(adev);
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
outs(ctlr->mixport+port, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ushort
|
||||||
|
ac97mixr(Audio *adev, int port)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ac97waitcodec(adev);
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
return ins(ctlr->mixport+port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
outavail(void *arg)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ctlr = arg;
|
||||||
|
return ctlr->outavail > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ac97interrupt(Ureg *, void *arg)
|
||||||
|
{
|
||||||
|
Audio *adev;
|
||||||
|
Ctlr *ctlr;
|
||||||
|
int civ, n, i;
|
||||||
|
ulong stat;
|
||||||
|
uvlong now;
|
||||||
|
|
||||||
|
adev = arg;
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
stat = csr32r(ctlr, Sta);
|
||||||
|
|
||||||
|
stat &= S2ri | Sri | Pri | Mint | Point | Piint | Moint | Miint | Gsci;
|
||||||
|
|
||||||
|
ilock(ctlr);
|
||||||
|
if(stat & Point){
|
||||||
|
if(sis7012)
|
||||||
|
csr16w(ctlr, Out + Picb, csr16r(ctlr, Out + Picb) & ~Dch);
|
||||||
|
else
|
||||||
|
csr16w(ctlr, Out + Sr, csr16r(ctlr, Out + Sr) & ~Dch);
|
||||||
|
|
||||||
|
civ = csr8r(ctlr, Out + Civ);
|
||||||
|
n = 0;
|
||||||
|
while(ctlr->outciv != civ){
|
||||||
|
ctlr->civstat[ctlr->outciv++]++;
|
||||||
|
if(ctlr->outciv == Ndesc)
|
||||||
|
ctlr->outciv = 0;
|
||||||
|
n += Bufsize/Ndesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
now = fastticks(0);
|
||||||
|
i = ctlr->tsouti;
|
||||||
|
ctlr->tsoutb[i] = n;
|
||||||
|
ctlr->tsout[i] = now - ctlr->tsoutp;
|
||||||
|
ctlr->tsouti = (i + 1) % Nts;
|
||||||
|
ctlr->tsoutp = now;
|
||||||
|
ctlr->outavail += n;
|
||||||
|
|
||||||
|
if(ctlr->outavail > Bufsize/2)
|
||||||
|
wakeup(&ctlr->outr);
|
||||||
|
stat &= ~Point;
|
||||||
|
}
|
||||||
|
iunlock(ctlr);
|
||||||
|
if(stat) /* have seen 0x400, which is sdin0 resume */
|
||||||
|
print("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n", adev->ctlrno, stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
off2lvi(char *base, char *p)
|
||||||
|
{
|
||||||
|
int lvi;
|
||||||
|
lvi = p - base;
|
||||||
|
return lvi / (Bufsize/Ndesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long
|
||||||
|
ac97medianoutrate(Audio *adev)
|
||||||
|
{
|
||||||
|
ulong ts[Nts], t;
|
||||||
|
uvlong hz;
|
||||||
|
int i, j;
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
fastticks(&hz);
|
||||||
|
for(i = 0; i < Nts; i++)
|
||||||
|
if(ctlr->tsout[i] > 0)
|
||||||
|
ts[i] = (ctlr->tsoutb[i] * hz) / ctlr->tsout[i];
|
||||||
|
else
|
||||||
|
ts[i] = 0;
|
||||||
|
for(i = 1; i < Nts; i++){
|
||||||
|
t = ts[i];
|
||||||
|
j = i;
|
||||||
|
while(j > 0 && ts[j-1] > t){
|
||||||
|
ts[j] = ts[j-1];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
ts[j] = t;
|
||||||
|
}
|
||||||
|
return ts[Nts/2] / BytesPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ac97volume(Audio *adev, char *msg)
|
||||||
|
{
|
||||||
|
adev->volwrite(adev, msg, strlen(msg), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ac97attach(Audio *adev)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
if(!ctlr->attachok){
|
||||||
|
ac97hardrate(adev, ctlr->hardrate);
|
||||||
|
ac97volume(adev, "audio 75");
|
||||||
|
ac97volume(adev, "head 100");
|
||||||
|
ac97volume(adev, "master 100");
|
||||||
|
ctlr->attachok = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static long
|
||||||
|
ac97status(Audio *adev, void *a, long n, vlong off)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
char *buf;
|
||||||
|
long i, l;
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
l = 0;
|
||||||
|
buf = malloc(READSTR);
|
||||||
|
l += snprint(buf + l, READSTR - l, "rate %d\n", ctlr->targetrate);
|
||||||
|
l += snprint(buf + l, READSTR - l, "median rate %lud\n", ac97medianoutrate(adev));
|
||||||
|
l += snprint(buf + l, READSTR - l, "hard rate %d\n", ac97hardrate(adev, -1));
|
||||||
|
|
||||||
|
l += snprint(buf + l, READSTR - l, "civ stats");
|
||||||
|
for(i = 0; i < Ndesc; i++)
|
||||||
|
l += snprint(buf + l, READSTR - l, " %lud", ctlr->civstat[i]);
|
||||||
|
l += snprint(buf + l, READSTR - l, "\n");
|
||||||
|
|
||||||
|
l += snprint(buf + l, READSTR - l, "lvi stats");
|
||||||
|
for(i = 0; i < Ndesc; i++)
|
||||||
|
l += snprint(buf + l, READSTR - l, " %lud", ctlr->lvistat[i]);
|
||||||
|
snprint(buf + l, READSTR - l, "\n");
|
||||||
|
|
||||||
|
n = readstr(off, a, n, buf);
|
||||||
|
free(buf);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long
|
||||||
|
ac97buffered(Audio *adev)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
return Bufsize - Bufsize/Ndesc - ctlr->outavail;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long
|
||||||
|
ac97ctl(Audio *adev, void *a, long n, vlong)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
char *tok[2], *p;
|
||||||
|
int ntok;
|
||||||
|
long t;
|
||||||
|
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
if(n > READSTR)
|
||||||
|
n = READSTR - 1;
|
||||||
|
p = malloc(READSTR);
|
||||||
|
|
||||||
|
if(waserror()){
|
||||||
|
free(p);
|
||||||
|
nexterror();
|
||||||
|
}
|
||||||
|
memmove(p, a, n);
|
||||||
|
p[n] = 0;
|
||||||
|
ntok = tokenize(p, tok, nelem(tok));
|
||||||
|
if(ntok > 1 && !strcmp(tok[0], "rate")){
|
||||||
|
t = strtol(tok[1], 0, 10);
|
||||||
|
if(t < 8000 || t > 48000)
|
||||||
|
error("rate must be between 8000 and 48000");
|
||||||
|
ctlr->targetrate = t;
|
||||||
|
ctlr->hardrate = t;
|
||||||
|
ac97hardrate(adev, ctlr->hardrate);
|
||||||
|
poperror();
|
||||||
|
free(p);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
error("invalid ctl");
|
||||||
|
return n; /* shut up, you compiler you */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ac97kick(Ctlr *ctlr, long reg)
|
||||||
|
{
|
||||||
|
csr8w(ctlr, reg+Cr, Ioce | Rpbm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long
|
||||||
|
ac97write(Audio *adev, void *a, long nwr, vlong)
|
||||||
|
{
|
||||||
|
Ctlr *ctlr;
|
||||||
|
char *p, *sp, *ep;
|
||||||
|
int len, lvi, olvi;
|
||||||
|
int t;
|
||||||
|
long n;
|
||||||
|
|
||||||
|
ctlr = adev->ctlr;
|
||||||
|
ilock(ctlr);
|
||||||
|
p = ctlr->outp;
|
||||||
|
sp = a;
|
||||||
|
ep = ctlr->out + Bufsize;
|
||||||
|
olvi = off2lvi(ctlr->out, p);
|
||||||
|
n = nwr;
|
||||||
|
while(n > 0){
|
||||||
|
len = ep - p;
|
||||||
|
if(ctlr->outavail < len)
|
||||||
|
len = ctlr->outavail;
|
||||||
|
if(n < len)
|
||||||
|
len = n;
|
||||||
|
ctlr->outavail -= len;
|
||||||
|
iunlock(ctlr);
|
||||||
|
memmove(p, sp, len);
|
||||||
|
ilock(ctlr);
|
||||||
|
p += len;
|
||||||
|
sp += len;
|
||||||
|
n -= len;
|
||||||
|
if(p == ep)
|
||||||
|
p = ctlr->out;
|
||||||
|
lvi = off2lvi(ctlr->out, p);
|
||||||
|
if(olvi != lvi){
|
||||||
|
t = olvi;
|
||||||
|
while(t != lvi){
|
||||||
|
t = (t + 1) % Ndesc;
|
||||||
|
ctlr->lvistat[t]++;
|
||||||
|
csr8w(ctlr, Out+Lvi, t);
|
||||||
|
ac97kick(ctlr, Out);
|
||||||
|
}
|
||||||
|
olvi = lvi;
|
||||||
|
}
|
||||||
|
if(ctlr->outavail == 0){
|
||||||
|
ctlr->outp = p;
|
||||||
|
iunlock(ctlr);
|
||||||
|
sleep(&ctlr->outr, outavail, ctlr);
|
||||||
|
ilock(ctlr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctlr->outp = p;
|
||||||
|
iunlock(ctlr);
|
||||||
|
return nwr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Pcidev*
|
||||||
|
ac97match(Pcidev *p)
|
||||||
|
{
|
||||||
|
/* not all of the matched devices have been tested */
|
||||||
|
while(p = pcimatch(p, 0, 0))
|
||||||
|
switch(p->vid){
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case 0x1039:
|
||||||
|
switch(p->did){
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case 0x7012:
|
||||||
|
sis7012 = 1;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
case 0x1022:
|
||||||
|
switch(p->did){
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case 0x746d:
|
||||||
|
case 0x7445:
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
case 0x10de:
|
||||||
|
switch(p->did){
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case 0x01b1:
|
||||||
|
case 0x006a:
|
||||||
|
case 0x00da:
|
||||||
|
case 0x00ea:
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
case 0x8086:
|
||||||
|
switch(p->did){
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case 0x2415:
|
||||||
|
case 0x2425:
|
||||||
|
case 0x2445:
|
||||||
|
case 0x2485:
|
||||||
|
case 0x24c5:
|
||||||
|
case 0x24d5:
|
||||||
|
case 0x25a6:
|
||||||
|
case 0x266e:
|
||||||
|
case 0x7195:
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sethwp(Ctlr *ctlr, long off, void *ptr)
|
||||||
|
{
|
||||||
|
csr8w(ctlr, off+Cr, RR);
|
||||||
|
csr32w(ctlr, off+Bar, PCIWADDR(ptr));
|
||||||
|
csr8w(ctlr, off+Lvi, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ac97reset(Audio *adev)
|
||||||
|
{
|
||||||
|
static int ncards = 1;
|
||||||
|
int i, irq, tbdf;
|
||||||
|
Pcidev *p;
|
||||||
|
Ctlr *ctlr;
|
||||||
|
ulong ctl, stat = 0;
|
||||||
|
|
||||||
|
p = nil;
|
||||||
|
for(i = 0; i < ncards; i++)
|
||||||
|
if((p = ac97match(p)) == nil)
|
||||||
|
return -1;
|
||||||
|
ncards++;
|
||||||
|
|
||||||
|
ctlr = xspanalloc(sizeof(Ctlr), 8, 0);
|
||||||
|
memset(ctlr, 0, sizeof(Ctlr));
|
||||||
|
adev->ctlr = ctlr;
|
||||||
|
ctlr->targetrate = 44100;
|
||||||
|
ctlr->hardrate = 44100;
|
||||||
|
|
||||||
|
if(p->mem[0].size == 64){
|
||||||
|
ctlr->port = p->mem[0].bar & ~3;
|
||||||
|
ctlr->mixport = p->mem[1].bar & ~3;
|
||||||
|
} else if(p->mem[1].size == 64){
|
||||||
|
ctlr->port = p->mem[1].bar & ~3;
|
||||||
|
ctlr->mixport = p->mem[0].bar & ~3;
|
||||||
|
} else if(p->mem[0].size == 256){ /* sis7012 */
|
||||||
|
ctlr->port = p->mem[1].bar & ~3;
|
||||||
|
ctlr->mixport = p->mem[0].bar & ~3;
|
||||||
|
} else if(p->mem[1].size == 256){
|
||||||
|
ctlr->port = p->mem[0].bar & ~3;
|
||||||
|
ctlr->mixport = p->mem[1].bar & ~3;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq = p->intl;
|
||||||
|
tbdf = p->tbdf;
|
||||||
|
|
||||||
|
print("#A%d: ac97 port 0x%04lux mixport 0x%04lux irq %d\n",
|
||||||
|
adev->ctlrno, ctlr->port, ctlr->mixport, irq);
|
||||||
|
|
||||||
|
pcisetbme(p);
|
||||||
|
pcisetioe(p);
|
||||||
|
|
||||||
|
ctlr->mic = xspanalloc(Bufsize, 8, 0);
|
||||||
|
ctlr->in = xspanalloc(Bufsize, 8, 0);
|
||||||
|
ctlr->out = xspanalloc(Bufsize, 8, 0);
|
||||||
|
|
||||||
|
for(i = 0; i < Ndesc; i++){
|
||||||
|
int size, off = i * (Bufsize/Ndesc);
|
||||||
|
|
||||||
|
if(sis7012)
|
||||||
|
size = (Bufsize/Ndesc);
|
||||||
|
else
|
||||||
|
size = (Bufsize/Ndesc) / 2;
|
||||||
|
|
||||||
|
ctlr->micdesc[i].addr = PCIWADDR(ctlr->mic + off);
|
||||||
|
ctlr->micdesc[i].size = Ioc | size;
|
||||||
|
ctlr->indesc[i].addr = PCIWADDR(ctlr->in + off);
|
||||||
|
ctlr->indesc[i].size = Ioc | size;
|
||||||
|
ctlr->outdesc[i].addr = PCIWADDR(ctlr->out + off);
|
||||||
|
ctlr->outdesc[i].size = Ioc | size;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlr->outavail = Bufsize - Bufsize/Ndesc;
|
||||||
|
ctlr->outp = ctlr->out;
|
||||||
|
|
||||||
|
ctl = csr32r(ctlr, Cnt);
|
||||||
|
ctl &= ~(EnaRESER | Aclso);
|
||||||
|
|
||||||
|
if((ctl & Accr) == 0){
|
||||||
|
print("#A%d: ac97 cold reset\n", adev->ctlrno);
|
||||||
|
ctl |= Accr;
|
||||||
|
}else{
|
||||||
|
print("#A%d: ac97 warm reset\n", adev->ctlrno);
|
||||||
|
ctl |= Acwr;
|
||||||
|
}
|
||||||
|
|
||||||
|
csr32w(ctlr, Cnt, ctl);
|
||||||
|
for(i = 0; i < Maxbusywait; i++){
|
||||||
|
if((csr32r(ctlr, Cnt) & Acwr) == 0)
|
||||||
|
break;
|
||||||
|
microdelay(1);
|
||||||
|
}
|
||||||
|
if(i == Maxbusywait)
|
||||||
|
print("#A%d: ac97 gave up waiting Acwr to go down\n", adev->ctlrno);
|
||||||
|
|
||||||
|
for(i = 0; i < Maxbusywait; i++){
|
||||||
|
if((stat = csr32r(ctlr, Sta)) & (Pcr | Scr | S2cr))
|
||||||
|
break;
|
||||||
|
microdelay(1);
|
||||||
|
}
|
||||||
|
if(i == Maxbusywait)
|
||||||
|
print("#A%d: ac97 gave up waiting codecs become ready\n", adev->ctlrno);
|
||||||
|
|
||||||
|
print("#A%d: ac97 codecs ready:%s%s%s\n", adev->ctlrno,
|
||||||
|
(stat & Pcr) ? " sdin0" : "",
|
||||||
|
(stat & Scr) ? " sdin1" : "",
|
||||||
|
(stat & S2cr) ? " sdin2" : "");
|
||||||
|
|
||||||
|
print("#A%d: ac97 codecs resumed:%s%s%s\n", adev->ctlrno,
|
||||||
|
(stat & Pri) ? " sdin0" : "",
|
||||||
|
(stat & Sri) ? " sdin1" : "",
|
||||||
|
(stat & S2ri) ? " sdin2" : "");
|
||||||
|
|
||||||
|
sethwp(ctlr, In, ctlr->indesc);
|
||||||
|
sethwp(ctlr, Out, ctlr->outdesc);
|
||||||
|
sethwp(ctlr, Mic, ctlr->micdesc);
|
||||||
|
|
||||||
|
csr8w(ctlr, In+Cr, Ioce); /* | Lvbie | Feie */
|
||||||
|
csr8w(ctlr, Out+Cr, Ioce); /* | Lvbie | Feie */
|
||||||
|
csr8w(ctlr, Mic+Cr, Ioce); /* | Lvbie | Feie */
|
||||||
|
|
||||||
|
adev->attach = ac97attach;
|
||||||
|
adev->write = ac97write;
|
||||||
|
adev->status = ac97status;
|
||||||
|
adev->ctl = ac97ctl;
|
||||||
|
adev->buffered = ac97buffered;
|
||||||
|
|
||||||
|
ac97mixreset(adev, ac97mixw, ac97mixr);
|
||||||
|
|
||||||
|
intrenable(irq, ac97interrupt, adev, tbdf, adev->name);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
audioac97link(void)
|
||||||
|
{
|
||||||
|
addaudiocard("ac97audio", ac97reset);
|
||||||
|
}
|
365
sys/src/9/pc/audioac97mix.c
Normal file
365
sys/src/9/pc/audioac97mix.c
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
#include "u.h"
|
||||||
|
#include "../port/lib.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fns.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "../port/error.h"
|
||||||
|
#include "../port/audio.h"
|
||||||
|
|
||||||
|
typedef ushort (*ac97rdfn)(Audio *, int);
|
||||||
|
typedef void (*ac97wrfn)(Audio *, int, ushort);
|
||||||
|
|
||||||
|
typedef struct Mixer Mixer;
|
||||||
|
typedef struct Volume Volume;
|
||||||
|
|
||||||
|
struct Mixer {
|
||||||
|
QLock;
|
||||||
|
ac97wrfn wr;
|
||||||
|
ac97rdfn rr;
|
||||||
|
int vra;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { Maxbusywait = 500000 };
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Reset = 0x0,
|
||||||
|
Capmic = 0x1,
|
||||||
|
Captonectl = 0x4,
|
||||||
|
Capsimstereo = 0x8,
|
||||||
|
Capheadphones = 0x10,
|
||||||
|
Caploudness = 0x20,
|
||||||
|
Capdac18 = 0x40,
|
||||||
|
Capdac20 = 0x80,
|
||||||
|
Capadc18 = 0x100,
|
||||||
|
Capadc20 = 0x200,
|
||||||
|
Capenh = 0xfc00,
|
||||||
|
Master = 0x02,
|
||||||
|
Headphone = 0x04,
|
||||||
|
Monomaster = 0x06,
|
||||||
|
Mastertone = 0x08,
|
||||||
|
Pcbeep = 0x0A,
|
||||||
|
Phone = 0x0C,
|
||||||
|
Mic = 0x0E,
|
||||||
|
Line = 0x10,
|
||||||
|
Cd = 0x12,
|
||||||
|
Video = 0x14,
|
||||||
|
Aux = 0x16,
|
||||||
|
Pcmout = 0x18,
|
||||||
|
Mute = 0x8000,
|
||||||
|
Recsel = 0x1A,
|
||||||
|
Recgain = 0x1C,
|
||||||
|
Micgain = 0x1E,
|
||||||
|
General = 0x20,
|
||||||
|
ThreeDctl = 0x22,
|
||||||
|
Ac97RESER = 0x24,
|
||||||
|
Powerdowncsr = 0x26,
|
||||||
|
Adcpower = 0x1,
|
||||||
|
Dacpower = 0x2,
|
||||||
|
Anlpower = 0x4,
|
||||||
|
Refpower = 0x8,
|
||||||
|
Inpower = 0x100,
|
||||||
|
Outpower = 0x200,
|
||||||
|
Mixpower = 0x400,
|
||||||
|
Mixvrefpower = 0x800,
|
||||||
|
Aclinkpower = 0x1000,
|
||||||
|
Clkpower = 0x2000,
|
||||||
|
Auxpower = 0x4000,
|
||||||
|
Eamppower = 0x8000,
|
||||||
|
Extid = 0x28,
|
||||||
|
Extcsr = 0x2A,
|
||||||
|
Extvra = 1<<0,
|
||||||
|
Extdra = 1<<1,
|
||||||
|
Extspdif = 1<<2,
|
||||||
|
Extvrm = 1<<3,
|
||||||
|
Extiddsa0 = 0<<4, /* extid only */
|
||||||
|
Extiddsa1 = 1<<4, /* extid only */
|
||||||
|
Extiddsa2 = 2<<4, /* extid only */
|
||||||
|
Extiddsa3 = 3<<4, /* extid only */
|
||||||
|
Extcsrspsa34 = 0<<4, /* extcsr only */
|
||||||
|
Extcsrspsa78 = 1<<4, /* extcsr only */
|
||||||
|
Extcsrspsa69 = 2<<4, /* extcsr only */
|
||||||
|
ExtcsrspsaAB = 3<<4, /* extcsr only */
|
||||||
|
Extcdac = 1<<6,
|
||||||
|
Extsdac = 1<<7,
|
||||||
|
Extldac = 1<<8,
|
||||||
|
Extidamap = 1<<9, /* extid only */
|
||||||
|
Extidrev11 = 0<<10, /* extid only */
|
||||||
|
Extidrev22 = 1<<10, /* extid only */
|
||||||
|
Extidrev23 = 2<<10, /* extid only */
|
||||||
|
Extidprim = 0<<14, /* extid only */
|
||||||
|
Extidsec0 = 1<<14, /* extid only */
|
||||||
|
Extidsec1 = 2<<14, /* extid only */
|
||||||
|
Extidsec2 = 3<<14, /* extid only */
|
||||||
|
Extcsrmadc = 1<<9, /* extcsr only */
|
||||||
|
Extcsrspcv = 1<<10, /* extcsr only */
|
||||||
|
Extcsrpri = 1<<11, /* extcsr only */
|
||||||
|
Extcsrprj = 1<<12, /* extcsr only */
|
||||||
|
Extcsrprk = 1<<13, /* extcsr only */
|
||||||
|
Extcsrprl = 1<<14, /* extcsr only */
|
||||||
|
Extcsrvcfg = 1<<15, /* extcsr only */
|
||||||
|
Pcmfrontdacrate = 0x2C,
|
||||||
|
Pcmsurrounddacrate = 0x2E,
|
||||||
|
Pcmlfedacrate = 0x30,
|
||||||
|
Pcmadcrate = 0x32,
|
||||||
|
Pcmmicadcrate = 0x34,
|
||||||
|
CenterLfe = 0x36,
|
||||||
|
LrSurround = 0x38,
|
||||||
|
Spdifcsr = 0x3a,
|
||||||
|
Spdifpro = 1<<0,
|
||||||
|
Spdifnonaudio = 1<<1,
|
||||||
|
Spdifcopy = 1<<2,
|
||||||
|
Spdifpre = 1<<3,
|
||||||
|
SpdifCC = 0x7f<<4,
|
||||||
|
Spdifl = 1<<11,
|
||||||
|
Spdif44k1 = 0<<12,
|
||||||
|
Spdif32k = 1<<12,
|
||||||
|
Spdif48k = 2<<12,
|
||||||
|
Spdifdsr = 1<<14,
|
||||||
|
Spdifv = 1<<15,
|
||||||
|
VID1 = 0x7c,
|
||||||
|
VID2 = 0x7e,
|
||||||
|
Speed = 0x1234567,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Stereo,
|
||||||
|
Absolute,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Vmaster,
|
||||||
|
Vhead,
|
||||||
|
Vaudio,
|
||||||
|
Vcd,
|
||||||
|
Vbass,
|
||||||
|
Vtreb,
|
||||||
|
Vbeep,
|
||||||
|
Vphone,
|
||||||
|
Vmic,
|
||||||
|
Vline,
|
||||||
|
Vvideo,
|
||||||
|
Vaux,
|
||||||
|
Vrecgain,
|
||||||
|
Vmicgain,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Volume {
|
||||||
|
int reg;
|
||||||
|
int range;
|
||||||
|
int type;
|
||||||
|
int cap;
|
||||||
|
char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Topology {
|
||||||
|
Volume *this;
|
||||||
|
Volume *next[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
Volume vol[] = {
|
||||||
|
[Vmaster] {Master, 63, Stereo, 0, "master"},
|
||||||
|
[Vaudio] {Pcmout, 31, Stereo, 0, "audio"},
|
||||||
|
[Vhead] {Headphone, 31, Stereo, Capheadphones, "head"},
|
||||||
|
[Vbass] {Mastertone, 15, Left, Captonectl, "bass"},
|
||||||
|
[Vtreb] {Mastertone, 15, Right, Captonectl, "treb"},
|
||||||
|
[Vbeep] {Pcbeep, 31, Right, 0, "beep"},
|
||||||
|
[Vphone] {Phone, 31, Right, 0, "phone"},
|
||||||
|
[Vmic] {Mic, 31, Right, Capmic, "mic"},
|
||||||
|
[Vline] {Line, 31, Stereo, 0, "line"},
|
||||||
|
[Vcd] {Cd, 31, Stereo, 0, "cd"},
|
||||||
|
[Vvideo] {Video, 31, Stereo, 0, "video"},
|
||||||
|
[Vaux] {Aux, 63, Stereo, 0, "aux"},
|
||||||
|
[Vrecgain] {Recgain, 15, Stereo, 0, "recgain"},
|
||||||
|
[Vmicgain] {Micgain, 15, Right, Capmic, "micgain"},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
long
|
||||||
|
ac97mixtopology(Audio *adev, void *a, long n, vlong off)
|
||||||
|
{
|
||||||
|
Mixer *m;
|
||||||
|
char *buf;
|
||||||
|
long l;
|
||||||
|
ulong caps;
|
||||||
|
m = adev->mixer;
|
||||||
|
qlock(m);
|
||||||
|
caps = m->rr(adev, Reset);
|
||||||
|
caps |= m->rr(adev, Extid) << 16;
|
||||||
|
l = 0;
|
||||||
|
buf = malloc(READSTR);
|
||||||
|
l += snprint(buf+l, READSTR-l, "not implemented. have fun.\n");
|
||||||
|
USED(caps);
|
||||||
|
USED(l);
|
||||||
|
qunlock(m);
|
||||||
|
n = readstr(off, a, n, buf);
|
||||||
|
free(buf);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
ac97mixread(Audio *adev, void *a, long n, vlong off)
|
||||||
|
{
|
||||||
|
Mixer *m;
|
||||||
|
char *nam, *buf;
|
||||||
|
long l;
|
||||||
|
ushort v;
|
||||||
|
ulong caps;
|
||||||
|
int i, rang, le, ri;
|
||||||
|
buf = malloc(READSTR);
|
||||||
|
m = adev->mixer;
|
||||||
|
qlock(m);
|
||||||
|
l = 0;
|
||||||
|
caps = m->rr(adev, Reset);
|
||||||
|
caps |= m->rr(adev, Extid) << 16;
|
||||||
|
for(i = 0; vol[i].name != 0; ++i){
|
||||||
|
if(vol[i].cap && ((vol[i].cap & caps) == 0))
|
||||||
|
continue;
|
||||||
|
v = m->rr(adev, vol[i].reg);
|
||||||
|
nam = vol[i].name;
|
||||||
|
rang = vol[i].range;
|
||||||
|
if(vol[i].type == Absolute){
|
||||||
|
l += snprint(buf+l, READSTR-l, "%s %d", nam, v);
|
||||||
|
} else {
|
||||||
|
ri = ((rang-(v&rang)) * 100) / rang;
|
||||||
|
le = ((rang-((v>>8)&rang)) * 100) / rang;
|
||||||
|
if(vol[i].type == Stereo)
|
||||||
|
l += snprint(buf+l, READSTR-l, "%s %d %d", nam, le, ri);
|
||||||
|
if(vol[i].type == Left)
|
||||||
|
l += snprint(buf+l, READSTR-l, "%s %d", nam, le);
|
||||||
|
if(vol[i].type == Right)
|
||||||
|
l += snprint(buf+l, READSTR-l, "%s %d", nam, ri);
|
||||||
|
if(v&Mute)
|
||||||
|
l += snprint(buf+l, READSTR-l, " mute");
|
||||||
|
}
|
||||||
|
l += snprint(buf+l, READSTR-l, "\n");
|
||||||
|
}
|
||||||
|
qunlock(m);
|
||||||
|
n = readstr(off, a, n, buf);
|
||||||
|
free(buf);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
ac97mixwrite(Audio *adev, void *a, long n, vlong)
|
||||||
|
{
|
||||||
|
Mixer *m;
|
||||||
|
char *tok[4];
|
||||||
|
int ntok, i, left, right, rang, reg;
|
||||||
|
ushort v;
|
||||||
|
m = adev->mixer;
|
||||||
|
qlock(m);
|
||||||
|
ntok = tokenize(a, tok, 4);
|
||||||
|
for(i = 0; vol[i].name != 0; ++i){
|
||||||
|
if(!strcmp(vol[i].name, tok[0])){
|
||||||
|
rang = vol[i].range;
|
||||||
|
reg = vol[i].reg;
|
||||||
|
left = right = 0;
|
||||||
|
if(ntok > 1)
|
||||||
|
left = right = atoi(tok[1]);
|
||||||
|
if(ntok > 2)
|
||||||
|
right = atoi(tok[2]);
|
||||||
|
|
||||||
|
if(vol[i].type == Absolute){
|
||||||
|
m->wr(adev, reg, left);
|
||||||
|
} else {
|
||||||
|
left = rang - ((left*rang)) / 100;
|
||||||
|
right = rang - ((right*rang)) / 100;
|
||||||
|
switch(vol[i].type){
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case Left:
|
||||||
|
v = m->rr(adev, reg);
|
||||||
|
v = (v & 0x007f) | (left << 8);
|
||||||
|
m->wr(adev, reg, v);
|
||||||
|
break;
|
||||||
|
case Right:
|
||||||
|
v = m->rr(adev, reg);
|
||||||
|
v = (v & 0x7f00) | right;
|
||||||
|
m->wr(adev, reg, v);
|
||||||
|
break;
|
||||||
|
case Stereo:
|
||||||
|
v = (left<<8) | right;
|
||||||
|
m->wr(adev, reg, v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qunlock(m);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(vol[i].name == nil){
|
||||||
|
char *p;
|
||||||
|
for(p = tok[0]; *p; ++p)
|
||||||
|
if(*p < '0' || *p > '9') {
|
||||||
|
qunlock(m);
|
||||||
|
error("no such volume setting");
|
||||||
|
}
|
||||||
|
rang = vol[0].range;
|
||||||
|
reg = vol[0].reg;
|
||||||
|
left = right = rang - ((atoi(tok[0])*rang)) / 100;
|
||||||
|
v = (left<<8) | right;
|
||||||
|
m->wr(adev, reg, v);
|
||||||
|
}
|
||||||
|
qunlock(m);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ac97hardrate(Audio *adev, int rate)
|
||||||
|
{
|
||||||
|
Mixer *m;
|
||||||
|
int oldrate;
|
||||||
|
m = adev->mixer;
|
||||||
|
oldrate = m->rr(adev, Pcmfrontdacrate);
|
||||||
|
if(rate > 0)
|
||||||
|
m->wr(adev, Pcmfrontdacrate, rate);
|
||||||
|
return oldrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ac97mixreset(Audio *adev, ac97wrfn wr, ac97rdfn rr)
|
||||||
|
{
|
||||||
|
Mixer *m;
|
||||||
|
int i;
|
||||||
|
ushort t;
|
||||||
|
if(adev->mixer == nil)
|
||||||
|
adev->mixer = malloc(sizeof(Mixer));
|
||||||
|
m = adev->mixer;
|
||||||
|
m->wr = wr;
|
||||||
|
m->rr = rr;
|
||||||
|
adev->volread = ac97mixread;
|
||||||
|
adev->volwrite = ac97mixwrite;
|
||||||
|
m->wr(adev, Reset, 0);
|
||||||
|
m->wr(adev, Powerdowncsr, 0);
|
||||||
|
|
||||||
|
t = (Adcpower | Dacpower | Anlpower | Refpower);
|
||||||
|
for(i = 0; i < Maxbusywait; i++){
|
||||||
|
if((m->rr(adev, Powerdowncsr) & t) == t)
|
||||||
|
break;
|
||||||
|
microdelay(1);
|
||||||
|
}
|
||||||
|
if(i == Maxbusywait)
|
||||||
|
print("#A%d: ac97 exhausted waiting powerup\n", adev->ctlrno);
|
||||||
|
|
||||||
|
t = m->rr(adev, Extid);
|
||||||
|
print("#A%d: ac97 codec ext:%s%s%s%s%s%s%s\n", adev->ctlrno,
|
||||||
|
(t & Extvra) ? " vra" : "",
|
||||||
|
(t & Extdra) ? " dra" : "",
|
||||||
|
(t & Extspdif) ? " spdif" : "",
|
||||||
|
(t & Extvrm) ? " vrm" : "",
|
||||||
|
(t & Extcdac) ? " cdac" : "",
|
||||||
|
(t & Extsdac) ? " sdac" : "",
|
||||||
|
(t & Extldac) ? " ldac" : "");
|
||||||
|
|
||||||
|
if(t & Extvra){
|
||||||
|
m->wr(adev, Extcsr, Extvra);
|
||||||
|
m->vra = 1;
|
||||||
|
} else {
|
||||||
|
print("#A%d: ac97 vra extension not supported\n", adev->ctlrno);
|
||||||
|
m->vra = 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,6 +73,8 @@ link
|
||||||
usbohci
|
usbohci
|
||||||
usbehci usbehcipc
|
usbehci usbehcipc
|
||||||
|
|
||||||
|
audioac97 audioac97mix
|
||||||
|
|
||||||
misc
|
misc
|
||||||
archmp mp apic
|
archmp mp apic
|
||||||
mtrr
|
mtrr
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue