diff options
30 files changed, 5774 insertions, 972 deletions
diff --git a/sys/dev/pcm/isa/mss.c b/sys/dev/pcm/isa/mss.c index a6bf624..26eaee5 100644 --- a/sys/dev/pcm/isa/mss.c +++ b/sys/dev/pcm/isa/mss.c @@ -94,7 +94,7 @@ snddev_info mss_op_desc = { NULL /* mss_read */, NULL /* mss_write */, mss_ioctl, - sndpoll /* mss_poll */, + sndselect /* mss_select */, mss_intr, mss_callback , @@ -133,8 +133,8 @@ mss_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x530; - printf("mss_probe: no address supplied, try default 0x%x\n", - dev->id_iobase); + BVDDB(printf("mss_probe: no address supplied, try default 0x%x\n", + dev->id_iobase)); } if (snd_conflict(dev->id_iobase)) return 0 ; @@ -150,13 +150,13 @@ mss_probe(struct isa_device *dev) tmp = inb(dev->id_iobase + 3); if (tmp == 0xff) { /* Bus float */ - DEB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp)); + BVDDB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp)); dev->id_flags &= ~DV_F_TRUE_MSS ; goto mss_probe_end; } tmp &= 0x3f ; if (tmp != 0x04 && tmp != 0x0f && tmp != 0x00) { - DEB(printf("No MSS signature detected on port 0x%x (0x%x)\n", + BVDDB(printf("No MSS signature detected on port 0x%x (0x%x)\n", dev->id_iobase, inb(dev->id_iobase + 3))); return 0; } @@ -276,10 +276,12 @@ mss_open(dev_t dev, int flags, int mode, struct proc * p) d->flags |= SND_F_BUSY_DSP ; else if (dev == SND_DEV_DSP16) d->flags |= SND_F_BUSY_DSP16 ; - if ( ! (d->flags & SND_F_BUSY) ) { + if ( d->flags & SND_F_BUSY ) + splx(s); /* device was already set, no need to reinit */ + else { /* * device was idle. Do the necessary initialization, - * but no need keep interrupts blocked since this device + * but no need keep interrupts blocked. * will not get them */ @@ -313,7 +315,6 @@ mss_open(dev_t dev, int flags, int mode, struct proc * p) } ask_init(d); /* and reset buffers... */ } - splx(s); return 0 ; } @@ -343,14 +344,15 @@ mss_close(dev_t dev, int flags, int mode, struct proc * p) d->flags &= ~SND_F_BUSY_DSP ; else if (dev == SND_DEV_DSP16) d->flags &= ~SND_F_BUSY_DSP16 ; - if ( ! (d->flags & SND_F_BUSY_ANY) ) { /* last one ... */ + if ( d->flags & SND_F_BUSY_ANY ) /* still some device open */ + splx(s); + else { /* last one */ d->flags |= SND_F_CLOSING ; splx(s); /* is this ok here ? */ snd_flush(d); outb(io_Status(d), 0); /* Clear interrupt status */ d->flags = 0 ; } - splx(s); return 0 ; } @@ -435,6 +437,7 @@ mss_callback(snddev_info *d, int reason) ad_write_cnt(d, 30, cnt); break ; + case SND_CB_STOP : case SND_CB_ABORT : /* XXX check this... */ m = ad_read(d,9) ; @@ -548,11 +551,11 @@ again: c=mc11 = FULL_DUPLEX(d) ? opti_read(d->conf_base, 11) : 0xc ; mc11 &= 0x0c ; if (c & 0x10) { - printf("Warning: CD interrupt\n"); + DEB(printf("Warning: CD interrupt\n");) mc11 |= 0x10 ; } if (c & 0x20) { - printf("Warning: MPU interrupt\n"); + DEB(printf("Warning: MPU interrupt\n");) mc11 |= 0x20 ; } if (mc11 & masked) @@ -578,7 +581,7 @@ again: } opti_write(d->conf_base, 11, ~mc11); /* ack */ if (--loops) goto again; - printf("xxx too many loops\n"); + DEB(printf("xxx too many loops\n");) } /* @@ -637,13 +640,13 @@ gus_readw(int io_base, u_char reg) static int AD_WAIT_INIT(snddev_info *d, int x) { - int n = 0; /* to shut up the compiler... */ + int arg=x, n = 0; /* to shut up the compiler... */ for (; x-- ; ) if ( (n=inb(io_Index_Addr(d))) & IA_BUSY) DELAY(10); else return n ; - printf("AD_WAIT_INIT FAILED 0x%02x\n", n); + printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n); return n ; } @@ -1061,7 +1064,7 @@ mss_detect(struct isa_device *dev) else break ; if ((inb(io_Index_Addr(d)) & IA_BUSY) != 0x00) { /* Not a AD1848 */ - DEB(printf("mss_detect error, busy still set (0x%02x)\n", + BVDDB(printf("mss_detect error, busy still set (0x%02x)\n", inb(io_Index_Addr(d)))); return 0; } @@ -1076,7 +1079,7 @@ mss_detect(struct isa_device *dev) tmp1 = ad_read(d, 0) ; tmp2 = ad_read(d, 1) ; if ( tmp1 != 0xaa || tmp2 != 0x45) { - DEB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n", + BVDDB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n", tmp1, tmp2)); return 0; } @@ -1087,7 +1090,7 @@ mss_detect(struct isa_device *dev) tmp2 = ad_read(d, 1) ; if (tmp1 != 0x45 || tmp2 != 0xaa) { - DEB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); + BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); return 0; } @@ -1101,7 +1104,7 @@ mss_detect(struct isa_device *dev) tmp1 = ad_read(d, 12); if ((tmp & 0x0f) != (tmp1 & 0x0f)) { - DEB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n", + BVDDB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n", tmp1, tmp)); return 0; } @@ -1112,7 +1115,7 @@ mss_detect(struct isa_device *dev) * 0x0A=RevC. also CS4231/CS4231A and OPTi931 */ - printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f); + BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);) /* * The original AD1848/CS4248 has just 16 indirect registers. This @@ -1125,7 +1128,7 @@ mss_detect(struct isa_device *dev) for (i = 0; i < 16; i++) if ((tmp1 = ad_read(d, i)) != (tmp2 = ad_read(d, i + 16))) { - DEB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", + BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", i, tmp1, tmp2)); /* * note - this seems to fail on the 4232 on I11. So we just break @@ -1148,7 +1151,7 @@ mss_detect(struct isa_device *dev) name = "CS4248" ; /* Our best knowledge just now */ } if ((tmp1 & 0xf0) == 0x00) { - printf("this should be an OPTi931\n"); + BVDDB(printf("this should be an OPTi931\n");) } else if ((tmp1 & 0xc0) == 0xC0) { /* * The 4231 has bit7=1 always, and bit6 we just set to 1. @@ -1162,14 +1165,13 @@ mss_detect(struct isa_device *dev) ad_write(d, 0, 0xaa); if ((tmp1 = ad_read(d, 16)) == 0xaa) { /* Rotten bits? */ - DEB(printf("mss_detect error - step H(%x)\n", tmp1)); + BVDDB(printf("mss_detect error - step H(%x)\n", tmp1)); return 0; } /* * Verify that some bits of I25 are read only. */ - DEB(printf("mss_detect() - step I\n")); tmp1 = ad_read(d, 25); /* Original bits */ ad_write(d, 25, ~tmp1); /* Invert all bits */ if ((ad_read(d, 25) & 0xe7) == (tmp1 & 0xe7)) { @@ -1240,7 +1242,7 @@ mss_detect(struct isa_device *dev) break ; default: /* Assume CS4231 */ - printf("unknown id 0x%02x, assuming CS4231\n", id); + BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);) d->bd_id = MD_CS4231; } @@ -1249,7 +1251,7 @@ mss_detect(struct isa_device *dev) } } - DEB(printf("mss_detect() - Detected %s\n", name)); + BVDDB(printf("mss_detect() - Detected %s\n", name)); strcpy(d->name, name); dev->id_flags &= ~DV_F_DEV_MASK ; dev->id_flags |= (d->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK ; @@ -1390,7 +1392,7 @@ cs423x_attach(u_long csn, u_long vend_id, char *name, return ; } snddev_last_probed = &tmp_d; - if (d.flags & DV_PNP_SBCODEC) { /*** use sb-compatible codec ***/ + if (d.flags) { /*** use sb-compatible codec ***/ dev->id_alive = 16 ; /* number of io ports ? */ tmp_d = sb_op_desc ; if (vend_id==0x2000a865 || vend_id==0x3000a865 || vend_id==0x8140d315) { @@ -1455,6 +1457,7 @@ cs423x_attach(u_long csn, u_long vend_id, char *name, dev->id_intr = pcmintr ; dev->id_flags = DV_F_DUAL_DMA | (d.drq[1] ) ; + tmp_d.synth_base = d.port[1]; /* XXX check this for yamaha */ pcmattach(dev); } @@ -1508,7 +1511,7 @@ opti931_attach(u_long csn, u_long vend_id, char *name, enable_pnp_card(); snddev_last_probed = &tmp_d; - tmp_d = d.flags & DV_PNP_SBCODEC ? sb_op_desc : mss_op_desc ; + tmp_d = d.flags ? sb_op_desc : mss_op_desc ; strcpy(tmp_d.name, name); @@ -1526,10 +1529,11 @@ opti931_attach(u_long csn, u_long vend_id, char *name, dev->id_iobase = d.port[0] - 4 ; /* old mss have 4 bytes before... */ tmp_d.io_base = dev->id_iobase; /* needed for ad_write to work... */ tmp_d.alt_base = d.port[2]; + tmp_d.synth_base = d.port[1]; opti_write(p, 4, 0xd6 /* fifo empty, OPL3, audio enable, SB3.2 */ ); ad_write (&tmp_d, 10, 2); /* enable interrupts */ - if (d.flags & DV_PNP_SBCODEC) { /* sb-compatible codec */ + if (d.flags) { /* sb-compatible codec */ /* * the 931 is not a real SB, it has important pieces of * hardware controlled by both the WSS and the SB port... @@ -1598,7 +1602,6 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; /* d.irq[1] = d.irq[0] ; */ - printf("pnp_read 0xf2 returns 0x%x\n", pnp_read(0xf2) ); pnp_write ( 0xf2, 0xff ); /* enable power on the guspnp */ write_pnp_parms ( &d , 0 ); @@ -1623,7 +1626,6 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, DELAY(1000 * 30); /* release reset and enable DAC */ gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 3 ); - printf("resetting the gus...\n"); DELAY(1000 * 30); /* end of reset */ @@ -1645,7 +1647,7 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, /* enable access to hidden regs */ tmp = gus_read(tmp_d.conf_base, 0x5b /* IVERI */ ); gus_write(tmp_d.conf_base, 0x5b , tmp | 1 ); - printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) ); + BVDDB(printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) );) strcpy(tmp_d.name, name); diff --git a/sys/dev/pcm/isa/sb.c b/sys/dev/pcm/isa/sb.c index 09440e1..3d75aea 100644 --- a/sys/dev/pcm/isa/sb.c +++ b/sys/dev/pcm/isa/sb.c @@ -92,7 +92,7 @@ snddev_info sb_op_desc = { NULL /* use generic sndread */, NULL /* use generic sndwrite */, sb_dsp_ioctl, - sndpoll, + sndselect, sbintr, sb_callback, @@ -123,7 +123,7 @@ sb_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x220; - printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n"); + BVDDB(printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n");) if (snd_conflict(dev->id_iobase)) dev->id_iobase = 0x240; } @@ -196,6 +196,10 @@ sb_dsp_open(dev_t dev, int flags, int mode, struct proc * p) d->play_fmt = d->rec_fmt = AFMT_U8 ; break ; } + if ( (flags & FREAD) == 0) + d->rec_fmt = 0 ; + if ( (flags & FWRITE) == 0) + d->play_fmt = 0 ; d->flags |= SND_F_BUSY ; d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; @@ -250,6 +254,8 @@ sb_dsp_ioctl(dev_t dev, int cmd, caddr_t arg, int mode, struct proc * p) /* * for the remaining functions, use the default handler. + * ENOSYS means that the default handler should take care + * of implementing the ioctl. */ return ENOSYS ; @@ -292,7 +298,7 @@ again: DEB(printf("sbintr, flags 0x%08lx reason %d\n", d->flags, reason)); if ( reason & 1 ) { /* possibly a write interrupt */ if ( d->dbuf_out.dl ) - dsp_wrintr(d); + dsp_wrintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: wrintr but write DMA inactive!\n"); @@ -300,7 +306,7 @@ again: } if ( reason & 2 ) { if ( d->dbuf_in.dl ) - dsp_rdintr(d); + dsp_rdintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: rdintr but read DMA inactive!\n"); @@ -340,16 +346,11 @@ sb_callback(snddev_info *d, int reason) case SND_CB_INIT : /* called with int enabled and no pending io */ dsp_speed(d); snd_set_blocksize(d); - if (d->play_fmt & AFMT_MU_LAW) + if ( (d->play_fmt & AFMT_MU_LAW) || (d->rec_fmt & AFMT_MU_LAW) ) d->flags |= SND_F_XLAT8 ; else d->flags &= ~SND_F_XLAT8 ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - return 1; - break; - case SND_CB_START : /* called with int disabled */ if (d->bd_flags & BD_F_SB16) { u_char c, c1 ; @@ -363,42 +364,40 @@ sb_callback(snddev_info *d, int reason) */ int swap = 1 ; /* default... */ - if (rd) { - /* need not to swap if channel is already correct */ - if ( d->rec_fmt == AFMT_S16_LE && b->chan > 4 ) + if (d->play_fmt == 0) { + /* do whatever the read channel wants */ + if ( d->rec_fmt == AFMT_S16_LE && d->dbuf_in.chan > 4 ) swap = 0; - if ( d->rec_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->rec_fmt != AFMT_S16_LE && d->dbuf_in.chan < 4 ) swap = 0; - if (swap && (d->flags & SND_F_WRITING || d->dbuf_out.dl)) { - /* cannot swap if writing is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break; /* XXX should return an error */ - } } else { - /* need not to swap if channel is already correct */ - if ( d->play_fmt == AFMT_S16_LE && b->chan > 4 ) + /* privilege the write channel */ + if ( d->play_fmt == AFMT_S16_LE && d->dbuf_out.chan > 4 ) swap = 0; - if ( d->play_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->play_fmt != AFMT_S16_LE && d->dbuf_out.chan < 4 ) swap = 0; - if (swap && ( d->flags & SND_F_READING || d->dbuf_in.dl)) { - /* cannot swap if reading is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break ; /* XXX should return an error */ + if ( d->rec_fmt ) { + /* check for possible config errors. */ + if (d->rec_fmt == d->play_fmt) { + DDB(printf("sorry, read DMA channel unavailable\n")); + } } } - + DEB(printf("sb16: play_fmt %d, rec_fmt %x, swap %d\n", + d->play_fmt, d->rec_fmt, swap);) if (swap) { int c = d->dbuf_in.chan ; d->dbuf_in.chan = d->dbuf_out.chan; d->dbuf_out.chan = c ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - DEB(printf("START dma chan: play %d, rec %d\n", - d->dbuf_out.chan, d->dbuf_in.chan)); + } } + reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); + reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); + break ; + case SND_CB_START : /* called with int disabled */ + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; /* * XXX note: c1 and l should be set basing on d->rec_fmt, * but there is no choice once a 16 or 8-bit channel @@ -408,7 +407,7 @@ sb_callback(snddev_info *d, int reason) if ( b->chan > 4 ) { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA16 ; c1 = DSP_F16_SIGNED ; - l /= 2 ; + l /= 2 ; } else { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA8 ; c1 = 0 ; @@ -421,28 +420,25 @@ sb_callback(snddev_info *d, int reason) sb_cmd3(d->io_base, c1 , l - 1) ; } else if (d->bd_flags & BD_F_ESS) { /* XXX this code is still incomplete */ - sb_cmd2(d->io_base, 0xb8, rd ? 0x0e : 0x04 ); /* auto dma */ - sb_cmd2(d->io_base, 0xa8, 2 /* chans */ ); - sb_cmd2(d->io_base, 0xb9, 2); /* demand mode */ - /* - input: 0xb8 -> 0x0e ; - 0xa8 -> channels - 0xb9 -> 2 - mono,U8: 51, d0 - mono,S16 71, f4 - st, U8 51, 98 - st, S16 71, bc - */ - } else { /* SBPro */ + } else { /* SBPro -- stereo not supported */ u_char c ; if (!rd) sb_cmd(d->io_base, DSP_CMD_SPKON); - /* code for the SB2 and SB3 */ + /* code for the SB2 and SB3, only MONO */ if (d->bd_flags & BD_F_HISPEED) - c = (rd) ? DSP_CMD_HSADC_AUTO : DSP_CMD_HSDAC_AUTO ; + c = (rd) ? 0x98 : 0x90 ; else - c = (rd) ? DSP_CMD_ADC8_AUTO : DSP_CMD_DAC8_AUTO ; - sb_cmd3(d->io_base, c , l - 1) ; + c = (rd) ? 0x2c : 0x1c ; + /* + * some ESS extensions -- they can do 16 bits + */ + if ( (rd && d->rec_fmt == AFMT_S16_LE) || + (!rd && d->play_fmt == AFMT_S16_LE) ) { + c |= 1; + l /= 2 ; + } + sb_cmd3(d->io_base, 0x48 , l - 1) ; + sb_cmd(d->io_base, c ) ; } break; @@ -451,7 +447,7 @@ sb_callback(snddev_info *d, int reason) { int cmd = DSP_CMD_DMAPAUSE_8 ; /* default: halt 8 bit chan */ if ( d->bd_flags & BD_F_SB16 && b->chan > 4 ) - cmd = DSP_CMD_DMAPAUSE_16 ; + cmd = DSP_CMD_DMAPAUSE_16 ; if (d->bd_flags & BD_F_HISPEED) { sb_reset_dsp(d->io_base); d->flags |= SND_F_INIT ; @@ -485,9 +481,9 @@ sb_reset_dsp(int io_base) { int loopc; - outb(DSP_RESET, 1); + outb(io_base + SBDSP_RST, 1); DELAY(100); - outb(DSP_RESET, 0); + outb(io_base + SBDSP_RST, 0); for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) DELAY(30); @@ -568,7 +564,9 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) d->name, dev->id_unit, d->irq); else sb_setmixer(io_base, IRQ_NR, x); - + if (d->dbuf_out.chan == d->dbuf_in.chan) { + printf("WARNING: sb: misconfigured secondary DMA channel\n"); + } sb_setmixer(io_base, DMA_NR, (1 << d->dbuf_out.chan) | (1 << d->dbuf_in.chan)); break ; @@ -599,41 +597,22 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) DELAY(20); } - if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) { + /* the ESS488 can be treated as an SBPRO */ printf("ESS488 (rev %d)\n", ess_minor & 0x0f); - else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { - if ( (ess_minor & 0xf) >= 8 ) - printf("ESS1688 (rev %d)\n", ess_minor & 0x0f); - else - printf("ESS688 (rev %d)\n", ess_minor & 0x0f); - } else break ; - d->bd_id = (ess_major << 8) | ess_minor ; - d->bd_flags |= BD_F_ESS; - /* - * ESS-specific initialization, taken from OSSFree 3.8 - */ - { - static u_char irqs[16] = { - 0, 0, 0x50, 0, 0, 0x54, 0, 0x58, - 0, 0x50, 0x5c, 0, 0, 0, 0, 0 }; - static u_char drqs[8] = { - 0x51, 0x52, 0, 0x53, 0, 0, 0, 0 }; - x = irqs[d->irq & 0xf]; - if (x == 0) - printf("ESS : invalid IRQ %d\n", d->irq); - else { - sb_cmd(io_base, 0xb1 ); /* set IRQ */ - sb_cmd(io_base, x ); - - x = drqs[ d->dbuf_out.chan & 0x7 ]; - if (x == 0) - printf("ESS : invalid DRQ %d\n", d->dbuf_out.chan); - else { - sb_cmd(io_base, 0xb2 ); /* set DRQ */ - sb_cmd(io_base, x ); - } - } + } else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { + int rev = ess_minor & 0xf ; + if ( rev >= 8 ) + printf("ESS1868 (rev %d)\n", rev); + else + printf("ESS688 (rev %d)\n", rev); + d->audio_fmt |= AFMT_S16_LE; /* in fact it is U16_LE */ + break ; /* XXX */ + } else { + printf("Unknown card 0x%x 0x%x -- hope it is SBPRO\n", + ess_major, ess_minor); + break ; } } @@ -679,8 +658,8 @@ sb_cmd(int io_base, u_char val) int i; for (i = 0; i < 1000 ; i++) { - if ((inb(DSP_STATUS) & 0x80) == 0) { - outb(DSP_COMMAND, val); + if ((inb(io_base + SBDSP_STATUS) & 0x80) == 0) { + outb(io_base + SBDSP_CMD, val); return 1; } if (i > 10) @@ -712,32 +691,23 @@ sb_cmd2(int io_base, u_char cmd, int val) return 0; } +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + */ void sb_setmixer(int io_base, u_int port, u_int value) { u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - outb(MIXER_DATA, (u_char) (value & 0xff)); + outb(io_base + 5, (u_char) (value & 0xff)); DELAY(10); splx(flags); } -u_int -sb_get_byte(int io_base) -{ - int i; - - for (i = 1000; i; i--) - if (inb(DSP_DATA_AVAIL) & 0x80) - return inb(DSP_READ); - else - DELAY(20); - return 0xffff; -} - int sb_getmixer(int io_base, u_int port) { @@ -745,15 +715,29 @@ sb_getmixer(int io_base, u_int port) u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - val = inb(MIXER_DATA); + val = inb(io_base + 5); DELAY(10); splx(flags); return val; } +u_int +sb_get_byte(int io_base) +{ + int i; + + for (i = 1000; i; i--) + if (inb(DSP_DATA_AVAIL) & 0x80) + return inb(DSP_READ); + else + DELAY(20); + return 0xffff; +} + + /* * various utility functions for the DSP @@ -806,11 +790,12 @@ dsp_speed(snddev_info *d) } /* - * only some models can do stereo, and only if not + * This is code for the SB3.x and lower. + * Only some models can do stereo, and only if not * simultaneously using midi. + * At the moment we do not support either... */ - if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) - d->flags &= ~SND_F_STEREO; + d->flags &= ~SND_F_STEREO; /* * here enforce speed limitations. @@ -830,26 +815,12 @@ dsp_speed(snddev_info *d) RANGE(speed, 4000, max_speed); - /* - * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz - * stereo - */ -#if !defined (SM_GAMES) - /* - * Max. stereo speed is 22050 - */ - if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) - speed = 22050; -#endif - - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed *= 2; /* * Now the speed should be valid. Compute the value to be * programmed into the board. - * - * XXX stereo init is still missing... */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ @@ -878,7 +849,7 @@ dsp_speed(snddev_info *d) speed = (1000000 + tmp / 2) / tmp; } - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed /= 2; d->play_speed = d->rec_speed = speed; @@ -1024,6 +995,79 @@ sb_mixer_set(snddev_info *d, int dev, int value) */ #if NPNP > 0 +static char *ess1868_probe(u_long csn, u_long vend_id); +static void ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device ess1868 = { + "ESS1868", + ess1868_probe, + ess1868_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, ess1868); + +static char * +ess1868_probe(u_long csn, u_long vend_id) +{ + /* + * pnp X 1 os enable drq0 3 irq0 12 port0 0x240 + */ + if (vend_id == 0x68187316) { + struct pnp_cinfo d ; + read_pnp_parms ( &d , 1 ) ; + if (d.enable == 0) { + printf("This is an ESS1868, but LDN 1 is disabled\n"); + return NULL; + } + return "ESS1868" ; + } + return NULL ; +} + +static void +ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev) +{ + struct pnp_cinfo d ; + snddev_info tmp_d ; /* patched copy of the basic snddev_info */ + int the_irq = 0 ; + + tmp_d = sb_op_desc; + snddev_last_probed = &tmp_d; + +#if 0 + read_pnp_parms ( &d , 3 ); /* disable LDN 3 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 3 ); + + read_pnp_parms ( &d , 2 ); /* disable LDN 2 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 2 ); + read_pnp_parms ( &d , 0 ); /* read config base */ + tmp_d.conf_base = d.port[0]; + write_pnp_parms ( &d , 0 ); +#endif + + read_pnp_parms ( &d , 1 ) ; + dev->id_iobase = d.port[0]; + d.port[1] = 0 ; + d.port[2] = 0 ; + write_pnp_parms ( &d , 1 ); + enable_pnp_card(); + + dev->id_drq = d.drq[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = 0 /* DV_F_DUAL_DMA | (d.drq[1] ) */; + + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + pcmattach(dev); +} + static char *opti925_probe(u_long csn, u_long vend_id); static void opti925_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); @@ -1155,6 +1199,7 @@ sb16pnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; d.port[1] = 0 ; /* only the first address is used */ dev->id_iobase = d.port[0]; + tmp_d.synth_base = d.port[2]; write_pnp_parms ( &d , 0 ); enable_pnp_card(); diff --git a/sys/dev/pcm/isa/sb.h b/sys/dev/pcm/isa/sb.h index 1735c14..0bd4210 100644 --- a/sys/dev/pcm/isa/sb.h +++ b/sys/dev/pcm/isa/sb.h @@ -11,18 +11,21 @@ extern int sbc_major, sbc_minor ; * sound blaster registers */ -#define DSP_RESET (io_base + 0x6) +#define SBDSP_RST 0x6 #define DSP_READ (io_base + 0xA) #define DSP_WRITE (io_base + 0xC) -#define DSP_COMMAND (io_base + 0xC) -#define DSP_STATUS (io_base + 0xC) +#define SBDSP_CMD 0xC +#define SBDSP_STATUS 0xC #define DSP_DATA_AVAIL (io_base + 0xE) #define DSP_DATA_AVL16 (io_base + 0xF) + +#if 0 #define MIXER_ADDR (io_base + 0x4) #define MIXER_DATA (io_base + 0x5) #define OPL3_LEFT (io_base + 0x0) #define OPL3_RIGHT (io_base + 0x2) #define OPL3_BOTH (io_base + 0x8) +#endif /* * DSP Commands. There are many, and in many cases they are used explicitly diff --git a/sys/dev/sound/isa/mss.c b/sys/dev/sound/isa/mss.c index a6bf624..26eaee5 100644 --- a/sys/dev/sound/isa/mss.c +++ b/sys/dev/sound/isa/mss.c @@ -94,7 +94,7 @@ snddev_info mss_op_desc = { NULL /* mss_read */, NULL /* mss_write */, mss_ioctl, - sndpoll /* mss_poll */, + sndselect /* mss_select */, mss_intr, mss_callback , @@ -133,8 +133,8 @@ mss_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x530; - printf("mss_probe: no address supplied, try default 0x%x\n", - dev->id_iobase); + BVDDB(printf("mss_probe: no address supplied, try default 0x%x\n", + dev->id_iobase)); } if (snd_conflict(dev->id_iobase)) return 0 ; @@ -150,13 +150,13 @@ mss_probe(struct isa_device *dev) tmp = inb(dev->id_iobase + 3); if (tmp == 0xff) { /* Bus float */ - DEB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp)); + BVDDB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp)); dev->id_flags &= ~DV_F_TRUE_MSS ; goto mss_probe_end; } tmp &= 0x3f ; if (tmp != 0x04 && tmp != 0x0f && tmp != 0x00) { - DEB(printf("No MSS signature detected on port 0x%x (0x%x)\n", + BVDDB(printf("No MSS signature detected on port 0x%x (0x%x)\n", dev->id_iobase, inb(dev->id_iobase + 3))); return 0; } @@ -276,10 +276,12 @@ mss_open(dev_t dev, int flags, int mode, struct proc * p) d->flags |= SND_F_BUSY_DSP ; else if (dev == SND_DEV_DSP16) d->flags |= SND_F_BUSY_DSP16 ; - if ( ! (d->flags & SND_F_BUSY) ) { + if ( d->flags & SND_F_BUSY ) + splx(s); /* device was already set, no need to reinit */ + else { /* * device was idle. Do the necessary initialization, - * but no need keep interrupts blocked since this device + * but no need keep interrupts blocked. * will not get them */ @@ -313,7 +315,6 @@ mss_open(dev_t dev, int flags, int mode, struct proc * p) } ask_init(d); /* and reset buffers... */ } - splx(s); return 0 ; } @@ -343,14 +344,15 @@ mss_close(dev_t dev, int flags, int mode, struct proc * p) d->flags &= ~SND_F_BUSY_DSP ; else if (dev == SND_DEV_DSP16) d->flags &= ~SND_F_BUSY_DSP16 ; - if ( ! (d->flags & SND_F_BUSY_ANY) ) { /* last one ... */ + if ( d->flags & SND_F_BUSY_ANY ) /* still some device open */ + splx(s); + else { /* last one */ d->flags |= SND_F_CLOSING ; splx(s); /* is this ok here ? */ snd_flush(d); outb(io_Status(d), 0); /* Clear interrupt status */ d->flags = 0 ; } - splx(s); return 0 ; } @@ -435,6 +437,7 @@ mss_callback(snddev_info *d, int reason) ad_write_cnt(d, 30, cnt); break ; + case SND_CB_STOP : case SND_CB_ABORT : /* XXX check this... */ m = ad_read(d,9) ; @@ -548,11 +551,11 @@ again: c=mc11 = FULL_DUPLEX(d) ? opti_read(d->conf_base, 11) : 0xc ; mc11 &= 0x0c ; if (c & 0x10) { - printf("Warning: CD interrupt\n"); + DEB(printf("Warning: CD interrupt\n");) mc11 |= 0x10 ; } if (c & 0x20) { - printf("Warning: MPU interrupt\n"); + DEB(printf("Warning: MPU interrupt\n");) mc11 |= 0x20 ; } if (mc11 & masked) @@ -578,7 +581,7 @@ again: } opti_write(d->conf_base, 11, ~mc11); /* ack */ if (--loops) goto again; - printf("xxx too many loops\n"); + DEB(printf("xxx too many loops\n");) } /* @@ -637,13 +640,13 @@ gus_readw(int io_base, u_char reg) static int AD_WAIT_INIT(snddev_info *d, int x) { - int n = 0; /* to shut up the compiler... */ + int arg=x, n = 0; /* to shut up the compiler... */ for (; x-- ; ) if ( (n=inb(io_Index_Addr(d))) & IA_BUSY) DELAY(10); else return n ; - printf("AD_WAIT_INIT FAILED 0x%02x\n", n); + printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n); return n ; } @@ -1061,7 +1064,7 @@ mss_detect(struct isa_device *dev) else break ; if ((inb(io_Index_Addr(d)) & IA_BUSY) != 0x00) { /* Not a AD1848 */ - DEB(printf("mss_detect error, busy still set (0x%02x)\n", + BVDDB(printf("mss_detect error, busy still set (0x%02x)\n", inb(io_Index_Addr(d)))); return 0; } @@ -1076,7 +1079,7 @@ mss_detect(struct isa_device *dev) tmp1 = ad_read(d, 0) ; tmp2 = ad_read(d, 1) ; if ( tmp1 != 0xaa || tmp2 != 0x45) { - DEB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n", + BVDDB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n", tmp1, tmp2)); return 0; } @@ -1087,7 +1090,7 @@ mss_detect(struct isa_device *dev) tmp2 = ad_read(d, 1) ; if (tmp1 != 0x45 || tmp2 != 0xaa) { - DEB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); + BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); return 0; } @@ -1101,7 +1104,7 @@ mss_detect(struct isa_device *dev) tmp1 = ad_read(d, 12); if ((tmp & 0x0f) != (tmp1 & 0x0f)) { - DEB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n", + BVDDB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n", tmp1, tmp)); return 0; } @@ -1112,7 +1115,7 @@ mss_detect(struct isa_device *dev) * 0x0A=RevC. also CS4231/CS4231A and OPTi931 */ - printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f); + BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);) /* * The original AD1848/CS4248 has just 16 indirect registers. This @@ -1125,7 +1128,7 @@ mss_detect(struct isa_device *dev) for (i = 0; i < 16; i++) if ((tmp1 = ad_read(d, i)) != (tmp2 = ad_read(d, i + 16))) { - DEB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", + BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", i, tmp1, tmp2)); /* * note - this seems to fail on the 4232 on I11. So we just break @@ -1148,7 +1151,7 @@ mss_detect(struct isa_device *dev) name = "CS4248" ; /* Our best knowledge just now */ } if ((tmp1 & 0xf0) == 0x00) { - printf("this should be an OPTi931\n"); + BVDDB(printf("this should be an OPTi931\n");) } else if ((tmp1 & 0xc0) == 0xC0) { /* * The 4231 has bit7=1 always, and bit6 we just set to 1. @@ -1162,14 +1165,13 @@ mss_detect(struct isa_device *dev) ad_write(d, 0, 0xaa); if ((tmp1 = ad_read(d, 16)) == 0xaa) { /* Rotten bits? */ - DEB(printf("mss_detect error - step H(%x)\n", tmp1)); + BVDDB(printf("mss_detect error - step H(%x)\n", tmp1)); return 0; } /* * Verify that some bits of I25 are read only. */ - DEB(printf("mss_detect() - step I\n")); tmp1 = ad_read(d, 25); /* Original bits */ ad_write(d, 25, ~tmp1); /* Invert all bits */ if ((ad_read(d, 25) & 0xe7) == (tmp1 & 0xe7)) { @@ -1240,7 +1242,7 @@ mss_detect(struct isa_device *dev) break ; default: /* Assume CS4231 */ - printf("unknown id 0x%02x, assuming CS4231\n", id); + BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);) d->bd_id = MD_CS4231; } @@ -1249,7 +1251,7 @@ mss_detect(struct isa_device *dev) } } - DEB(printf("mss_detect() - Detected %s\n", name)); + BVDDB(printf("mss_detect() - Detected %s\n", name)); strcpy(d->name, name); dev->id_flags &= ~DV_F_DEV_MASK ; dev->id_flags |= (d->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK ; @@ -1390,7 +1392,7 @@ cs423x_attach(u_long csn, u_long vend_id, char *name, return ; } snddev_last_probed = &tmp_d; - if (d.flags & DV_PNP_SBCODEC) { /*** use sb-compatible codec ***/ + if (d.flags) { /*** use sb-compatible codec ***/ dev->id_alive = 16 ; /* number of io ports ? */ tmp_d = sb_op_desc ; if (vend_id==0x2000a865 || vend_id==0x3000a865 || vend_id==0x8140d315) { @@ -1455,6 +1457,7 @@ cs423x_attach(u_long csn, u_long vend_id, char *name, dev->id_intr = pcmintr ; dev->id_flags = DV_F_DUAL_DMA | (d.drq[1] ) ; + tmp_d.synth_base = d.port[1]; /* XXX check this for yamaha */ pcmattach(dev); } @@ -1508,7 +1511,7 @@ opti931_attach(u_long csn, u_long vend_id, char *name, enable_pnp_card(); snddev_last_probed = &tmp_d; - tmp_d = d.flags & DV_PNP_SBCODEC ? sb_op_desc : mss_op_desc ; + tmp_d = d.flags ? sb_op_desc : mss_op_desc ; strcpy(tmp_d.name, name); @@ -1526,10 +1529,11 @@ opti931_attach(u_long csn, u_long vend_id, char *name, dev->id_iobase = d.port[0] - 4 ; /* old mss have 4 bytes before... */ tmp_d.io_base = dev->id_iobase; /* needed for ad_write to work... */ tmp_d.alt_base = d.port[2]; + tmp_d.synth_base = d.port[1]; opti_write(p, 4, 0xd6 /* fifo empty, OPL3, audio enable, SB3.2 */ ); ad_write (&tmp_d, 10, 2); /* enable interrupts */ - if (d.flags & DV_PNP_SBCODEC) { /* sb-compatible codec */ + if (d.flags) { /* sb-compatible codec */ /* * the 931 is not a real SB, it has important pieces of * hardware controlled by both the WSS and the SB port... @@ -1598,7 +1602,6 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; /* d.irq[1] = d.irq[0] ; */ - printf("pnp_read 0xf2 returns 0x%x\n", pnp_read(0xf2) ); pnp_write ( 0xf2, 0xff ); /* enable power on the guspnp */ write_pnp_parms ( &d , 0 ); @@ -1623,7 +1626,6 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, DELAY(1000 * 30); /* release reset and enable DAC */ gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 3 ); - printf("resetting the gus...\n"); DELAY(1000 * 30); /* end of reset */ @@ -1645,7 +1647,7 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, /* enable access to hidden regs */ tmp = gus_read(tmp_d.conf_base, 0x5b /* IVERI */ ); gus_write(tmp_d.conf_base, 0x5b , tmp | 1 ); - printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) ); + BVDDB(printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) );) strcpy(tmp_d.name, name); diff --git a/sys/dev/sound/isa/sb.c b/sys/dev/sound/isa/sb.c index 09440e1..3d75aea 100644 --- a/sys/dev/sound/isa/sb.c +++ b/sys/dev/sound/isa/sb.c @@ -92,7 +92,7 @@ snddev_info sb_op_desc = { NULL /* use generic sndread */, NULL /* use generic sndwrite */, sb_dsp_ioctl, - sndpoll, + sndselect, sbintr, sb_callback, @@ -123,7 +123,7 @@ sb_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x220; - printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n"); + BVDDB(printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n");) if (snd_conflict(dev->id_iobase)) dev->id_iobase = 0x240; } @@ -196,6 +196,10 @@ sb_dsp_open(dev_t dev, int flags, int mode, struct proc * p) d->play_fmt = d->rec_fmt = AFMT_U8 ; break ; } + if ( (flags & FREAD) == 0) + d->rec_fmt = 0 ; + if ( (flags & FWRITE) == 0) + d->play_fmt = 0 ; d->flags |= SND_F_BUSY ; d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; @@ -250,6 +254,8 @@ sb_dsp_ioctl(dev_t dev, int cmd, caddr_t arg, int mode, struct proc * p) /* * for the remaining functions, use the default handler. + * ENOSYS means that the default handler should take care + * of implementing the ioctl. */ return ENOSYS ; @@ -292,7 +298,7 @@ again: DEB(printf("sbintr, flags 0x%08lx reason %d\n", d->flags, reason)); if ( reason & 1 ) { /* possibly a write interrupt */ if ( d->dbuf_out.dl ) - dsp_wrintr(d); + dsp_wrintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: wrintr but write DMA inactive!\n"); @@ -300,7 +306,7 @@ again: } if ( reason & 2 ) { if ( d->dbuf_in.dl ) - dsp_rdintr(d); + dsp_rdintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: rdintr but read DMA inactive!\n"); @@ -340,16 +346,11 @@ sb_callback(snddev_info *d, int reason) case SND_CB_INIT : /* called with int enabled and no pending io */ dsp_speed(d); snd_set_blocksize(d); - if (d->play_fmt & AFMT_MU_LAW) + if ( (d->play_fmt & AFMT_MU_LAW) || (d->rec_fmt & AFMT_MU_LAW) ) d->flags |= SND_F_XLAT8 ; else d->flags &= ~SND_F_XLAT8 ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - return 1; - break; - case SND_CB_START : /* called with int disabled */ if (d->bd_flags & BD_F_SB16) { u_char c, c1 ; @@ -363,42 +364,40 @@ sb_callback(snddev_info *d, int reason) */ int swap = 1 ; /* default... */ - if (rd) { - /* need not to swap if channel is already correct */ - if ( d->rec_fmt == AFMT_S16_LE && b->chan > 4 ) + if (d->play_fmt == 0) { + /* do whatever the read channel wants */ + if ( d->rec_fmt == AFMT_S16_LE && d->dbuf_in.chan > 4 ) swap = 0; - if ( d->rec_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->rec_fmt != AFMT_S16_LE && d->dbuf_in.chan < 4 ) swap = 0; - if (swap && (d->flags & SND_F_WRITING || d->dbuf_out.dl)) { - /* cannot swap if writing is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break; /* XXX should return an error */ - } } else { - /* need not to swap if channel is already correct */ - if ( d->play_fmt == AFMT_S16_LE && b->chan > 4 ) + /* privilege the write channel */ + if ( d->play_fmt == AFMT_S16_LE && d->dbuf_out.chan > 4 ) swap = 0; - if ( d->play_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->play_fmt != AFMT_S16_LE && d->dbuf_out.chan < 4 ) swap = 0; - if (swap && ( d->flags & SND_F_READING || d->dbuf_in.dl)) { - /* cannot swap if reading is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break ; /* XXX should return an error */ + if ( d->rec_fmt ) { + /* check for possible config errors. */ + if (d->rec_fmt == d->play_fmt) { + DDB(printf("sorry, read DMA channel unavailable\n")); + } } } - + DEB(printf("sb16: play_fmt %d, rec_fmt %x, swap %d\n", + d->play_fmt, d->rec_fmt, swap);) if (swap) { int c = d->dbuf_in.chan ; d->dbuf_in.chan = d->dbuf_out.chan; d->dbuf_out.chan = c ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - DEB(printf("START dma chan: play %d, rec %d\n", - d->dbuf_out.chan, d->dbuf_in.chan)); + } } + reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); + reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); + break ; + case SND_CB_START : /* called with int disabled */ + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; /* * XXX note: c1 and l should be set basing on d->rec_fmt, * but there is no choice once a 16 or 8-bit channel @@ -408,7 +407,7 @@ sb_callback(snddev_info *d, int reason) if ( b->chan > 4 ) { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA16 ; c1 = DSP_F16_SIGNED ; - l /= 2 ; + l /= 2 ; } else { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA8 ; c1 = 0 ; @@ -421,28 +420,25 @@ sb_callback(snddev_info *d, int reason) sb_cmd3(d->io_base, c1 , l - 1) ; } else if (d->bd_flags & BD_F_ESS) { /* XXX this code is still incomplete */ - sb_cmd2(d->io_base, 0xb8, rd ? 0x0e : 0x04 ); /* auto dma */ - sb_cmd2(d->io_base, 0xa8, 2 /* chans */ ); - sb_cmd2(d->io_base, 0xb9, 2); /* demand mode */ - /* - input: 0xb8 -> 0x0e ; - 0xa8 -> channels - 0xb9 -> 2 - mono,U8: 51, d0 - mono,S16 71, f4 - st, U8 51, 98 - st, S16 71, bc - */ - } else { /* SBPro */ + } else { /* SBPro -- stereo not supported */ u_char c ; if (!rd) sb_cmd(d->io_base, DSP_CMD_SPKON); - /* code for the SB2 and SB3 */ + /* code for the SB2 and SB3, only MONO */ if (d->bd_flags & BD_F_HISPEED) - c = (rd) ? DSP_CMD_HSADC_AUTO : DSP_CMD_HSDAC_AUTO ; + c = (rd) ? 0x98 : 0x90 ; else - c = (rd) ? DSP_CMD_ADC8_AUTO : DSP_CMD_DAC8_AUTO ; - sb_cmd3(d->io_base, c , l - 1) ; + c = (rd) ? 0x2c : 0x1c ; + /* + * some ESS extensions -- they can do 16 bits + */ + if ( (rd && d->rec_fmt == AFMT_S16_LE) || + (!rd && d->play_fmt == AFMT_S16_LE) ) { + c |= 1; + l /= 2 ; + } + sb_cmd3(d->io_base, 0x48 , l - 1) ; + sb_cmd(d->io_base, c ) ; } break; @@ -451,7 +447,7 @@ sb_callback(snddev_info *d, int reason) { int cmd = DSP_CMD_DMAPAUSE_8 ; /* default: halt 8 bit chan */ if ( d->bd_flags & BD_F_SB16 && b->chan > 4 ) - cmd = DSP_CMD_DMAPAUSE_16 ; + cmd = DSP_CMD_DMAPAUSE_16 ; if (d->bd_flags & BD_F_HISPEED) { sb_reset_dsp(d->io_base); d->flags |= SND_F_INIT ; @@ -485,9 +481,9 @@ sb_reset_dsp(int io_base) { int loopc; - outb(DSP_RESET, 1); + outb(io_base + SBDSP_RST, 1); DELAY(100); - outb(DSP_RESET, 0); + outb(io_base + SBDSP_RST, 0); for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) DELAY(30); @@ -568,7 +564,9 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) d->name, dev->id_unit, d->irq); else sb_setmixer(io_base, IRQ_NR, x); - + if (d->dbuf_out.chan == d->dbuf_in.chan) { + printf("WARNING: sb: misconfigured secondary DMA channel\n"); + } sb_setmixer(io_base, DMA_NR, (1 << d->dbuf_out.chan) | (1 << d->dbuf_in.chan)); break ; @@ -599,41 +597,22 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) DELAY(20); } - if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) { + /* the ESS488 can be treated as an SBPRO */ printf("ESS488 (rev %d)\n", ess_minor & 0x0f); - else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { - if ( (ess_minor & 0xf) >= 8 ) - printf("ESS1688 (rev %d)\n", ess_minor & 0x0f); - else - printf("ESS688 (rev %d)\n", ess_minor & 0x0f); - } else break ; - d->bd_id = (ess_major << 8) | ess_minor ; - d->bd_flags |= BD_F_ESS; - /* - * ESS-specific initialization, taken from OSSFree 3.8 - */ - { - static u_char irqs[16] = { - 0, 0, 0x50, 0, 0, 0x54, 0, 0x58, - 0, 0x50, 0x5c, 0, 0, 0, 0, 0 }; - static u_char drqs[8] = { - 0x51, 0x52, 0, 0x53, 0, 0, 0, 0 }; - x = irqs[d->irq & 0xf]; - if (x == 0) - printf("ESS : invalid IRQ %d\n", d->irq); - else { - sb_cmd(io_base, 0xb1 ); /* set IRQ */ - sb_cmd(io_base, x ); - - x = drqs[ d->dbuf_out.chan & 0x7 ]; - if (x == 0) - printf("ESS : invalid DRQ %d\n", d->dbuf_out.chan); - else { - sb_cmd(io_base, 0xb2 ); /* set DRQ */ - sb_cmd(io_base, x ); - } - } + } else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { + int rev = ess_minor & 0xf ; + if ( rev >= 8 ) + printf("ESS1868 (rev %d)\n", rev); + else + printf("ESS688 (rev %d)\n", rev); + d->audio_fmt |= AFMT_S16_LE; /* in fact it is U16_LE */ + break ; /* XXX */ + } else { + printf("Unknown card 0x%x 0x%x -- hope it is SBPRO\n", + ess_major, ess_minor); + break ; } } @@ -679,8 +658,8 @@ sb_cmd(int io_base, u_char val) int i; for (i = 0; i < 1000 ; i++) { - if ((inb(DSP_STATUS) & 0x80) == 0) { - outb(DSP_COMMAND, val); + if ((inb(io_base + SBDSP_STATUS) & 0x80) == 0) { + outb(io_base + SBDSP_CMD, val); return 1; } if (i > 10) @@ -712,32 +691,23 @@ sb_cmd2(int io_base, u_char cmd, int val) return 0; } +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + */ void sb_setmixer(int io_base, u_int port, u_int value) { u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - outb(MIXER_DATA, (u_char) (value & 0xff)); + outb(io_base + 5, (u_char) (value & 0xff)); DELAY(10); splx(flags); } -u_int -sb_get_byte(int io_base) -{ - int i; - - for (i = 1000; i; i--) - if (inb(DSP_DATA_AVAIL) & 0x80) - return inb(DSP_READ); - else - DELAY(20); - return 0xffff; -} - int sb_getmixer(int io_base, u_int port) { @@ -745,15 +715,29 @@ sb_getmixer(int io_base, u_int port) u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - val = inb(MIXER_DATA); + val = inb(io_base + 5); DELAY(10); splx(flags); return val; } +u_int +sb_get_byte(int io_base) +{ + int i; + + for (i = 1000; i; i--) + if (inb(DSP_DATA_AVAIL) & 0x80) + return inb(DSP_READ); + else + DELAY(20); + return 0xffff; +} + + /* * various utility functions for the DSP @@ -806,11 +790,12 @@ dsp_speed(snddev_info *d) } /* - * only some models can do stereo, and only if not + * This is code for the SB3.x and lower. + * Only some models can do stereo, and only if not * simultaneously using midi. + * At the moment we do not support either... */ - if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) - d->flags &= ~SND_F_STEREO; + d->flags &= ~SND_F_STEREO; /* * here enforce speed limitations. @@ -830,26 +815,12 @@ dsp_speed(snddev_info *d) RANGE(speed, 4000, max_speed); - /* - * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz - * stereo - */ -#if !defined (SM_GAMES) - /* - * Max. stereo speed is 22050 - */ - if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) - speed = 22050; -#endif - - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed *= 2; /* * Now the speed should be valid. Compute the value to be * programmed into the board. - * - * XXX stereo init is still missing... */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ @@ -878,7 +849,7 @@ dsp_speed(snddev_info *d) speed = (1000000 + tmp / 2) / tmp; } - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed /= 2; d->play_speed = d->rec_speed = speed; @@ -1024,6 +995,79 @@ sb_mixer_set(snddev_info *d, int dev, int value) */ #if NPNP > 0 +static char *ess1868_probe(u_long csn, u_long vend_id); +static void ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device ess1868 = { + "ESS1868", + ess1868_probe, + ess1868_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, ess1868); + +static char * +ess1868_probe(u_long csn, u_long vend_id) +{ + /* + * pnp X 1 os enable drq0 3 irq0 12 port0 0x240 + */ + if (vend_id == 0x68187316) { + struct pnp_cinfo d ; + read_pnp_parms ( &d , 1 ) ; + if (d.enable == 0) { + printf("This is an ESS1868, but LDN 1 is disabled\n"); + return NULL; + } + return "ESS1868" ; + } + return NULL ; +} + +static void +ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev) +{ + struct pnp_cinfo d ; + snddev_info tmp_d ; /* patched copy of the basic snddev_info */ + int the_irq = 0 ; + + tmp_d = sb_op_desc; + snddev_last_probed = &tmp_d; + +#if 0 + read_pnp_parms ( &d , 3 ); /* disable LDN 3 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 3 ); + + read_pnp_parms ( &d , 2 ); /* disable LDN 2 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 2 ); + read_pnp_parms ( &d , 0 ); /* read config base */ + tmp_d.conf_base = d.port[0]; + write_pnp_parms ( &d , 0 ); +#endif + + read_pnp_parms ( &d , 1 ) ; + dev->id_iobase = d.port[0]; + d.port[1] = 0 ; + d.port[2] = 0 ; + write_pnp_parms ( &d , 1 ); + enable_pnp_card(); + + dev->id_drq = d.drq[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = 0 /* DV_F_DUAL_DMA | (d.drq[1] ) */; + + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + pcmattach(dev); +} + static char *opti925_probe(u_long csn, u_long vend_id); static void opti925_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); @@ -1155,6 +1199,7 @@ sb16pnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; d.port[1] = 0 ; /* only the first address is used */ dev->id_iobase = d.port[0]; + tmp_d.synth_base = d.port[2]; write_pnp_parms ( &d , 0 ); enable_pnp_card(); diff --git a/sys/dev/sound/isa/sb.h b/sys/dev/sound/isa/sb.h index 1735c14..0bd4210 100644 --- a/sys/dev/sound/isa/sb.h +++ b/sys/dev/sound/isa/sb.h @@ -11,18 +11,21 @@ extern int sbc_major, sbc_minor ; * sound blaster registers */ -#define DSP_RESET (io_base + 0x6) +#define SBDSP_RST 0x6 #define DSP_READ (io_base + 0xA) #define DSP_WRITE (io_base + 0xC) -#define DSP_COMMAND (io_base + 0xC) -#define DSP_STATUS (io_base + 0xC) +#define SBDSP_CMD 0xC +#define SBDSP_STATUS 0xC #define DSP_DATA_AVAIL (io_base + 0xE) #define DSP_DATA_AVL16 (io_base + 0xF) + +#if 0 #define MIXER_ADDR (io_base + 0x4) #define MIXER_DATA (io_base + 0x5) #define OPL3_LEFT (io_base + 0x0) #define OPL3_RIGHT (io_base + 0x2) #define OPL3_BOTH (io_base + 0x8) +#endif /* * DSP Commands. There are many, and in many cases they are used explicitly diff --git a/sys/dev/sound/isa/sb16.c b/sys/dev/sound/isa/sb16.c index 09440e1..3d75aea 100644 --- a/sys/dev/sound/isa/sb16.c +++ b/sys/dev/sound/isa/sb16.c @@ -92,7 +92,7 @@ snddev_info sb_op_desc = { NULL /* use generic sndread */, NULL /* use generic sndwrite */, sb_dsp_ioctl, - sndpoll, + sndselect, sbintr, sb_callback, @@ -123,7 +123,7 @@ sb_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x220; - printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n"); + BVDDB(printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n");) if (snd_conflict(dev->id_iobase)) dev->id_iobase = 0x240; } @@ -196,6 +196,10 @@ sb_dsp_open(dev_t dev, int flags, int mode, struct proc * p) d->play_fmt = d->rec_fmt = AFMT_U8 ; break ; } + if ( (flags & FREAD) == 0) + d->rec_fmt = 0 ; + if ( (flags & FWRITE) == 0) + d->play_fmt = 0 ; d->flags |= SND_F_BUSY ; d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; @@ -250,6 +254,8 @@ sb_dsp_ioctl(dev_t dev, int cmd, caddr_t arg, int mode, struct proc * p) /* * for the remaining functions, use the default handler. + * ENOSYS means that the default handler should take care + * of implementing the ioctl. */ return ENOSYS ; @@ -292,7 +298,7 @@ again: DEB(printf("sbintr, flags 0x%08lx reason %d\n", d->flags, reason)); if ( reason & 1 ) { /* possibly a write interrupt */ if ( d->dbuf_out.dl ) - dsp_wrintr(d); + dsp_wrintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: wrintr but write DMA inactive!\n"); @@ -300,7 +306,7 @@ again: } if ( reason & 2 ) { if ( d->dbuf_in.dl ) - dsp_rdintr(d); + dsp_rdintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: rdintr but read DMA inactive!\n"); @@ -340,16 +346,11 @@ sb_callback(snddev_info *d, int reason) case SND_CB_INIT : /* called with int enabled and no pending io */ dsp_speed(d); snd_set_blocksize(d); - if (d->play_fmt & AFMT_MU_LAW) + if ( (d->play_fmt & AFMT_MU_LAW) || (d->rec_fmt & AFMT_MU_LAW) ) d->flags |= SND_F_XLAT8 ; else d->flags &= ~SND_F_XLAT8 ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - return 1; - break; - case SND_CB_START : /* called with int disabled */ if (d->bd_flags & BD_F_SB16) { u_char c, c1 ; @@ -363,42 +364,40 @@ sb_callback(snddev_info *d, int reason) */ int swap = 1 ; /* default... */ - if (rd) { - /* need not to swap if channel is already correct */ - if ( d->rec_fmt == AFMT_S16_LE && b->chan > 4 ) + if (d->play_fmt == 0) { + /* do whatever the read channel wants */ + if ( d->rec_fmt == AFMT_S16_LE && d->dbuf_in.chan > 4 ) swap = 0; - if ( d->rec_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->rec_fmt != AFMT_S16_LE && d->dbuf_in.chan < 4 ) swap = 0; - if (swap && (d->flags & SND_F_WRITING || d->dbuf_out.dl)) { - /* cannot swap if writing is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break; /* XXX should return an error */ - } } else { - /* need not to swap if channel is already correct */ - if ( d->play_fmt == AFMT_S16_LE && b->chan > 4 ) + /* privilege the write channel */ + if ( d->play_fmt == AFMT_S16_LE && d->dbuf_out.chan > 4 ) swap = 0; - if ( d->play_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->play_fmt != AFMT_S16_LE && d->dbuf_out.chan < 4 ) swap = 0; - if (swap && ( d->flags & SND_F_READING || d->dbuf_in.dl)) { - /* cannot swap if reading is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break ; /* XXX should return an error */ + if ( d->rec_fmt ) { + /* check for possible config errors. */ + if (d->rec_fmt == d->play_fmt) { + DDB(printf("sorry, read DMA channel unavailable\n")); + } } } - + DEB(printf("sb16: play_fmt %d, rec_fmt %x, swap %d\n", + d->play_fmt, d->rec_fmt, swap);) if (swap) { int c = d->dbuf_in.chan ; d->dbuf_in.chan = d->dbuf_out.chan; d->dbuf_out.chan = c ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - DEB(printf("START dma chan: play %d, rec %d\n", - d->dbuf_out.chan, d->dbuf_in.chan)); + } } + reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); + reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); + break ; + case SND_CB_START : /* called with int disabled */ + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; /* * XXX note: c1 and l should be set basing on d->rec_fmt, * but there is no choice once a 16 or 8-bit channel @@ -408,7 +407,7 @@ sb_callback(snddev_info *d, int reason) if ( b->chan > 4 ) { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA16 ; c1 = DSP_F16_SIGNED ; - l /= 2 ; + l /= 2 ; } else { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA8 ; c1 = 0 ; @@ -421,28 +420,25 @@ sb_callback(snddev_info *d, int reason) sb_cmd3(d->io_base, c1 , l - 1) ; } else if (d->bd_flags & BD_F_ESS) { /* XXX this code is still incomplete */ - sb_cmd2(d->io_base, 0xb8, rd ? 0x0e : 0x04 ); /* auto dma */ - sb_cmd2(d->io_base, 0xa8, 2 /* chans */ ); - sb_cmd2(d->io_base, 0xb9, 2); /* demand mode */ - /* - input: 0xb8 -> 0x0e ; - 0xa8 -> channels - 0xb9 -> 2 - mono,U8: 51, d0 - mono,S16 71, f4 - st, U8 51, 98 - st, S16 71, bc - */ - } else { /* SBPro */ + } else { /* SBPro -- stereo not supported */ u_char c ; if (!rd) sb_cmd(d->io_base, DSP_CMD_SPKON); - /* code for the SB2 and SB3 */ + /* code for the SB2 and SB3, only MONO */ if (d->bd_flags & BD_F_HISPEED) - c = (rd) ? DSP_CMD_HSADC_AUTO : DSP_CMD_HSDAC_AUTO ; + c = (rd) ? 0x98 : 0x90 ; else - c = (rd) ? DSP_CMD_ADC8_AUTO : DSP_CMD_DAC8_AUTO ; - sb_cmd3(d->io_base, c , l - 1) ; + c = (rd) ? 0x2c : 0x1c ; + /* + * some ESS extensions -- they can do 16 bits + */ + if ( (rd && d->rec_fmt == AFMT_S16_LE) || + (!rd && d->play_fmt == AFMT_S16_LE) ) { + c |= 1; + l /= 2 ; + } + sb_cmd3(d->io_base, 0x48 , l - 1) ; + sb_cmd(d->io_base, c ) ; } break; @@ -451,7 +447,7 @@ sb_callback(snddev_info *d, int reason) { int cmd = DSP_CMD_DMAPAUSE_8 ; /* default: halt 8 bit chan */ if ( d->bd_flags & BD_F_SB16 && b->chan > 4 ) - cmd = DSP_CMD_DMAPAUSE_16 ; + cmd = DSP_CMD_DMAPAUSE_16 ; if (d->bd_flags & BD_F_HISPEED) { sb_reset_dsp(d->io_base); d->flags |= SND_F_INIT ; @@ -485,9 +481,9 @@ sb_reset_dsp(int io_base) { int loopc; - outb(DSP_RESET, 1); + outb(io_base + SBDSP_RST, 1); DELAY(100); - outb(DSP_RESET, 0); + outb(io_base + SBDSP_RST, 0); for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) DELAY(30); @@ -568,7 +564,9 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) d->name, dev->id_unit, d->irq); else sb_setmixer(io_base, IRQ_NR, x); - + if (d->dbuf_out.chan == d->dbuf_in.chan) { + printf("WARNING: sb: misconfigured secondary DMA channel\n"); + } sb_setmixer(io_base, DMA_NR, (1 << d->dbuf_out.chan) | (1 << d->dbuf_in.chan)); break ; @@ -599,41 +597,22 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) DELAY(20); } - if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) { + /* the ESS488 can be treated as an SBPRO */ printf("ESS488 (rev %d)\n", ess_minor & 0x0f); - else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { - if ( (ess_minor & 0xf) >= 8 ) - printf("ESS1688 (rev %d)\n", ess_minor & 0x0f); - else - printf("ESS688 (rev %d)\n", ess_minor & 0x0f); - } else break ; - d->bd_id = (ess_major << 8) | ess_minor ; - d->bd_flags |= BD_F_ESS; - /* - * ESS-specific initialization, taken from OSSFree 3.8 - */ - { - static u_char irqs[16] = { - 0, 0, 0x50, 0, 0, 0x54, 0, 0x58, - 0, 0x50, 0x5c, 0, 0, 0, 0, 0 }; - static u_char drqs[8] = { - 0x51, 0x52, 0, 0x53, 0, 0, 0, 0 }; - x = irqs[d->irq & 0xf]; - if (x == 0) - printf("ESS : invalid IRQ %d\n", d->irq); - else { - sb_cmd(io_base, 0xb1 ); /* set IRQ */ - sb_cmd(io_base, x ); - - x = drqs[ d->dbuf_out.chan & 0x7 ]; - if (x == 0) - printf("ESS : invalid DRQ %d\n", d->dbuf_out.chan); - else { - sb_cmd(io_base, 0xb2 ); /* set DRQ */ - sb_cmd(io_base, x ); - } - } + } else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { + int rev = ess_minor & 0xf ; + if ( rev >= 8 ) + printf("ESS1868 (rev %d)\n", rev); + else + printf("ESS688 (rev %d)\n", rev); + d->audio_fmt |= AFMT_S16_LE; /* in fact it is U16_LE */ + break ; /* XXX */ + } else { + printf("Unknown card 0x%x 0x%x -- hope it is SBPRO\n", + ess_major, ess_minor); + break ; } } @@ -679,8 +658,8 @@ sb_cmd(int io_base, u_char val) int i; for (i = 0; i < 1000 ; i++) { - if ((inb(DSP_STATUS) & 0x80) == 0) { - outb(DSP_COMMAND, val); + if ((inb(io_base + SBDSP_STATUS) & 0x80) == 0) { + outb(io_base + SBDSP_CMD, val); return 1; } if (i > 10) @@ -712,32 +691,23 @@ sb_cmd2(int io_base, u_char cmd, int val) return 0; } +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + */ void sb_setmixer(int io_base, u_int port, u_int value) { u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - outb(MIXER_DATA, (u_char) (value & 0xff)); + outb(io_base + 5, (u_char) (value & 0xff)); DELAY(10); splx(flags); } -u_int -sb_get_byte(int io_base) -{ - int i; - - for (i = 1000; i; i--) - if (inb(DSP_DATA_AVAIL) & 0x80) - return inb(DSP_READ); - else - DELAY(20); - return 0xffff; -} - int sb_getmixer(int io_base, u_int port) { @@ -745,15 +715,29 @@ sb_getmixer(int io_base, u_int port) u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - val = inb(MIXER_DATA); + val = inb(io_base + 5); DELAY(10); splx(flags); return val; } +u_int +sb_get_byte(int io_base) +{ + int i; + + for (i = 1000; i; i--) + if (inb(DSP_DATA_AVAIL) & 0x80) + return inb(DSP_READ); + else + DELAY(20); + return 0xffff; +} + + /* * various utility functions for the DSP @@ -806,11 +790,12 @@ dsp_speed(snddev_info *d) } /* - * only some models can do stereo, and only if not + * This is code for the SB3.x and lower. + * Only some models can do stereo, and only if not * simultaneously using midi. + * At the moment we do not support either... */ - if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) - d->flags &= ~SND_F_STEREO; + d->flags &= ~SND_F_STEREO; /* * here enforce speed limitations. @@ -830,26 +815,12 @@ dsp_speed(snddev_info *d) RANGE(speed, 4000, max_speed); - /* - * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz - * stereo - */ -#if !defined (SM_GAMES) - /* - * Max. stereo speed is 22050 - */ - if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) - speed = 22050; -#endif - - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed *= 2; /* * Now the speed should be valid. Compute the value to be * programmed into the board. - * - * XXX stereo init is still missing... */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ @@ -878,7 +849,7 @@ dsp_speed(snddev_info *d) speed = (1000000 + tmp / 2) / tmp; } - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed /= 2; d->play_speed = d->rec_speed = speed; @@ -1024,6 +995,79 @@ sb_mixer_set(snddev_info *d, int dev, int value) */ #if NPNP > 0 +static char *ess1868_probe(u_long csn, u_long vend_id); +static void ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device ess1868 = { + "ESS1868", + ess1868_probe, + ess1868_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, ess1868); + +static char * +ess1868_probe(u_long csn, u_long vend_id) +{ + /* + * pnp X 1 os enable drq0 3 irq0 12 port0 0x240 + */ + if (vend_id == 0x68187316) { + struct pnp_cinfo d ; + read_pnp_parms ( &d , 1 ) ; + if (d.enable == 0) { + printf("This is an ESS1868, but LDN 1 is disabled\n"); + return NULL; + } + return "ESS1868" ; + } + return NULL ; +} + +static void +ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev) +{ + struct pnp_cinfo d ; + snddev_info tmp_d ; /* patched copy of the basic snddev_info */ + int the_irq = 0 ; + + tmp_d = sb_op_desc; + snddev_last_probed = &tmp_d; + +#if 0 + read_pnp_parms ( &d , 3 ); /* disable LDN 3 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 3 ); + + read_pnp_parms ( &d , 2 ); /* disable LDN 2 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 2 ); + read_pnp_parms ( &d , 0 ); /* read config base */ + tmp_d.conf_base = d.port[0]; + write_pnp_parms ( &d , 0 ); +#endif + + read_pnp_parms ( &d , 1 ) ; + dev->id_iobase = d.port[0]; + d.port[1] = 0 ; + d.port[2] = 0 ; + write_pnp_parms ( &d , 1 ); + enable_pnp_card(); + + dev->id_drq = d.drq[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = 0 /* DV_F_DUAL_DMA | (d.drq[1] ) */; + + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + pcmattach(dev); +} + static char *opti925_probe(u_long csn, u_long vend_id); static void opti925_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); @@ -1155,6 +1199,7 @@ sb16pnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; d.port[1] = 0 ; /* only the first address is used */ dev->id_iobase = d.port[0]; + tmp_d.synth_base = d.port[2]; write_pnp_parms ( &d , 0 ); enable_pnp_card(); diff --git a/sys/dev/sound/isa/sb8.c b/sys/dev/sound/isa/sb8.c index 09440e1..3d75aea 100644 --- a/sys/dev/sound/isa/sb8.c +++ b/sys/dev/sound/isa/sb8.c @@ -92,7 +92,7 @@ snddev_info sb_op_desc = { NULL /* use generic sndread */, NULL /* use generic sndwrite */, sb_dsp_ioctl, - sndpoll, + sndselect, sbintr, sb_callback, @@ -123,7 +123,7 @@ sb_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x220; - printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n"); + BVDDB(printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n");) if (snd_conflict(dev->id_iobase)) dev->id_iobase = 0x240; } @@ -196,6 +196,10 @@ sb_dsp_open(dev_t dev, int flags, int mode, struct proc * p) d->play_fmt = d->rec_fmt = AFMT_U8 ; break ; } + if ( (flags & FREAD) == 0) + d->rec_fmt = 0 ; + if ( (flags & FWRITE) == 0) + d->play_fmt = 0 ; d->flags |= SND_F_BUSY ; d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; @@ -250,6 +254,8 @@ sb_dsp_ioctl(dev_t dev, int cmd, caddr_t arg, int mode, struct proc * p) /* * for the remaining functions, use the default handler. + * ENOSYS means that the default handler should take care + * of implementing the ioctl. */ return ENOSYS ; @@ -292,7 +298,7 @@ again: DEB(printf("sbintr, flags 0x%08lx reason %d\n", d->flags, reason)); if ( reason & 1 ) { /* possibly a write interrupt */ if ( d->dbuf_out.dl ) - dsp_wrintr(d); + dsp_wrintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: wrintr but write DMA inactive!\n"); @@ -300,7 +306,7 @@ again: } if ( reason & 2 ) { if ( d->dbuf_in.dl ) - dsp_rdintr(d); + dsp_rdintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: rdintr but read DMA inactive!\n"); @@ -340,16 +346,11 @@ sb_callback(snddev_info *d, int reason) case SND_CB_INIT : /* called with int enabled and no pending io */ dsp_speed(d); snd_set_blocksize(d); - if (d->play_fmt & AFMT_MU_LAW) + if ( (d->play_fmt & AFMT_MU_LAW) || (d->rec_fmt & AFMT_MU_LAW) ) d->flags |= SND_F_XLAT8 ; else d->flags &= ~SND_F_XLAT8 ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - return 1; - break; - case SND_CB_START : /* called with int disabled */ if (d->bd_flags & BD_F_SB16) { u_char c, c1 ; @@ -363,42 +364,40 @@ sb_callback(snddev_info *d, int reason) */ int swap = 1 ; /* default... */ - if (rd) { - /* need not to swap if channel is already correct */ - if ( d->rec_fmt == AFMT_S16_LE && b->chan > 4 ) + if (d->play_fmt == 0) { + /* do whatever the read channel wants */ + if ( d->rec_fmt == AFMT_S16_LE && d->dbuf_in.chan > 4 ) swap = 0; - if ( d->rec_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->rec_fmt != AFMT_S16_LE && d->dbuf_in.chan < 4 ) swap = 0; - if (swap && (d->flags & SND_F_WRITING || d->dbuf_out.dl)) { - /* cannot swap if writing is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break; /* XXX should return an error */ - } } else { - /* need not to swap if channel is already correct */ - if ( d->play_fmt == AFMT_S16_LE && b->chan > 4 ) + /* privilege the write channel */ + if ( d->play_fmt == AFMT_S16_LE && d->dbuf_out.chan > 4 ) swap = 0; - if ( d->play_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->play_fmt != AFMT_S16_LE && d->dbuf_out.chan < 4 ) swap = 0; - if (swap && ( d->flags & SND_F_READING || d->dbuf_in.dl)) { - /* cannot swap if reading is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break ; /* XXX should return an error */ + if ( d->rec_fmt ) { + /* check for possible config errors. */ + if (d->rec_fmt == d->play_fmt) { + DDB(printf("sorry, read DMA channel unavailable\n")); + } } } - + DEB(printf("sb16: play_fmt %d, rec_fmt %x, swap %d\n", + d->play_fmt, d->rec_fmt, swap);) if (swap) { int c = d->dbuf_in.chan ; d->dbuf_in.chan = d->dbuf_out.chan; d->dbuf_out.chan = c ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - DEB(printf("START dma chan: play %d, rec %d\n", - d->dbuf_out.chan, d->dbuf_in.chan)); + } } + reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); + reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); + break ; + case SND_CB_START : /* called with int disabled */ + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; /* * XXX note: c1 and l should be set basing on d->rec_fmt, * but there is no choice once a 16 or 8-bit channel @@ -408,7 +407,7 @@ sb_callback(snddev_info *d, int reason) if ( b->chan > 4 ) { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA16 ; c1 = DSP_F16_SIGNED ; - l /= 2 ; + l /= 2 ; } else { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA8 ; c1 = 0 ; @@ -421,28 +420,25 @@ sb_callback(snddev_info *d, int reason) sb_cmd3(d->io_base, c1 , l - 1) ; } else if (d->bd_flags & BD_F_ESS) { /* XXX this code is still incomplete */ - sb_cmd2(d->io_base, 0xb8, rd ? 0x0e : 0x04 ); /* auto dma */ - sb_cmd2(d->io_base, 0xa8, 2 /* chans */ ); - sb_cmd2(d->io_base, 0xb9, 2); /* demand mode */ - /* - input: 0xb8 -> 0x0e ; - 0xa8 -> channels - 0xb9 -> 2 - mono,U8: 51, d0 - mono,S16 71, f4 - st, U8 51, 98 - st, S16 71, bc - */ - } else { /* SBPro */ + } else { /* SBPro -- stereo not supported */ u_char c ; if (!rd) sb_cmd(d->io_base, DSP_CMD_SPKON); - /* code for the SB2 and SB3 */ + /* code for the SB2 and SB3, only MONO */ if (d->bd_flags & BD_F_HISPEED) - c = (rd) ? DSP_CMD_HSADC_AUTO : DSP_CMD_HSDAC_AUTO ; + c = (rd) ? 0x98 : 0x90 ; else - c = (rd) ? DSP_CMD_ADC8_AUTO : DSP_CMD_DAC8_AUTO ; - sb_cmd3(d->io_base, c , l - 1) ; + c = (rd) ? 0x2c : 0x1c ; + /* + * some ESS extensions -- they can do 16 bits + */ + if ( (rd && d->rec_fmt == AFMT_S16_LE) || + (!rd && d->play_fmt == AFMT_S16_LE) ) { + c |= 1; + l /= 2 ; + } + sb_cmd3(d->io_base, 0x48 , l - 1) ; + sb_cmd(d->io_base, c ) ; } break; @@ -451,7 +447,7 @@ sb_callback(snddev_info *d, int reason) { int cmd = DSP_CMD_DMAPAUSE_8 ; /* default: halt 8 bit chan */ if ( d->bd_flags & BD_F_SB16 && b->chan > 4 ) - cmd = DSP_CMD_DMAPAUSE_16 ; + cmd = DSP_CMD_DMAPAUSE_16 ; if (d->bd_flags & BD_F_HISPEED) { sb_reset_dsp(d->io_base); d->flags |= SND_F_INIT ; @@ -485,9 +481,9 @@ sb_reset_dsp(int io_base) { int loopc; - outb(DSP_RESET, 1); + outb(io_base + SBDSP_RST, 1); DELAY(100); - outb(DSP_RESET, 0); + outb(io_base + SBDSP_RST, 0); for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) DELAY(30); @@ -568,7 +564,9 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) d->name, dev->id_unit, d->irq); else sb_setmixer(io_base, IRQ_NR, x); - + if (d->dbuf_out.chan == d->dbuf_in.chan) { + printf("WARNING: sb: misconfigured secondary DMA channel\n"); + } sb_setmixer(io_base, DMA_NR, (1 << d->dbuf_out.chan) | (1 << d->dbuf_in.chan)); break ; @@ -599,41 +597,22 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) DELAY(20); } - if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) { + /* the ESS488 can be treated as an SBPRO */ printf("ESS488 (rev %d)\n", ess_minor & 0x0f); - else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { - if ( (ess_minor & 0xf) >= 8 ) - printf("ESS1688 (rev %d)\n", ess_minor & 0x0f); - else - printf("ESS688 (rev %d)\n", ess_minor & 0x0f); - } else break ; - d->bd_id = (ess_major << 8) | ess_minor ; - d->bd_flags |= BD_F_ESS; - /* - * ESS-specific initialization, taken from OSSFree 3.8 - */ - { - static u_char irqs[16] = { - 0, 0, 0x50, 0, 0, 0x54, 0, 0x58, - 0, 0x50, 0x5c, 0, 0, 0, 0, 0 }; - static u_char drqs[8] = { - 0x51, 0x52, 0, 0x53, 0, 0, 0, 0 }; - x = irqs[d->irq & 0xf]; - if (x == 0) - printf("ESS : invalid IRQ %d\n", d->irq); - else { - sb_cmd(io_base, 0xb1 ); /* set IRQ */ - sb_cmd(io_base, x ); - - x = drqs[ d->dbuf_out.chan & 0x7 ]; - if (x == 0) - printf("ESS : invalid DRQ %d\n", d->dbuf_out.chan); - else { - sb_cmd(io_base, 0xb2 ); /* set DRQ */ - sb_cmd(io_base, x ); - } - } + } else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { + int rev = ess_minor & 0xf ; + if ( rev >= 8 ) + printf("ESS1868 (rev %d)\n", rev); + else + printf("ESS688 (rev %d)\n", rev); + d->audio_fmt |= AFMT_S16_LE; /* in fact it is U16_LE */ + break ; /* XXX */ + } else { + printf("Unknown card 0x%x 0x%x -- hope it is SBPRO\n", + ess_major, ess_minor); + break ; } } @@ -679,8 +658,8 @@ sb_cmd(int io_base, u_char val) int i; for (i = 0; i < 1000 ; i++) { - if ((inb(DSP_STATUS) & 0x80) == 0) { - outb(DSP_COMMAND, val); + if ((inb(io_base + SBDSP_STATUS) & 0x80) == 0) { + outb(io_base + SBDSP_CMD, val); return 1; } if (i > 10) @@ -712,32 +691,23 @@ sb_cmd2(int io_base, u_char cmd, int val) return 0; } +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + */ void sb_setmixer(int io_base, u_int port, u_int value) { u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - outb(MIXER_DATA, (u_char) (value & 0xff)); + outb(io_base + 5, (u_char) (value & 0xff)); DELAY(10); splx(flags); } -u_int -sb_get_byte(int io_base) -{ - int i; - - for (i = 1000; i; i--) - if (inb(DSP_DATA_AVAIL) & 0x80) - return inb(DSP_READ); - else - DELAY(20); - return 0xffff; -} - int sb_getmixer(int io_base, u_int port) { @@ -745,15 +715,29 @@ sb_getmixer(int io_base, u_int port) u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - val = inb(MIXER_DATA); + val = inb(io_base + 5); DELAY(10); splx(flags); return val; } +u_int +sb_get_byte(int io_base) +{ + int i; + + for (i = 1000; i; i--) + if (inb(DSP_DATA_AVAIL) & 0x80) + return inb(DSP_READ); + else + DELAY(20); + return 0xffff; +} + + /* * various utility functions for the DSP @@ -806,11 +790,12 @@ dsp_speed(snddev_info *d) } /* - * only some models can do stereo, and only if not + * This is code for the SB3.x and lower. + * Only some models can do stereo, and only if not * simultaneously using midi. + * At the moment we do not support either... */ - if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) - d->flags &= ~SND_F_STEREO; + d->flags &= ~SND_F_STEREO; /* * here enforce speed limitations. @@ -830,26 +815,12 @@ dsp_speed(snddev_info *d) RANGE(speed, 4000, max_speed); - /* - * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz - * stereo - */ -#if !defined (SM_GAMES) - /* - * Max. stereo speed is 22050 - */ - if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) - speed = 22050; -#endif - - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed *= 2; /* * Now the speed should be valid. Compute the value to be * programmed into the board. - * - * XXX stereo init is still missing... */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ @@ -878,7 +849,7 @@ dsp_speed(snddev_info *d) speed = (1000000 + tmp / 2) / tmp; } - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed /= 2; d->play_speed = d->rec_speed = speed; @@ -1024,6 +995,79 @@ sb_mixer_set(snddev_info *d, int dev, int value) */ #if NPNP > 0 +static char *ess1868_probe(u_long csn, u_long vend_id); +static void ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device ess1868 = { + "ESS1868", + ess1868_probe, + ess1868_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, ess1868); + +static char * +ess1868_probe(u_long csn, u_long vend_id) +{ + /* + * pnp X 1 os enable drq0 3 irq0 12 port0 0x240 + */ + if (vend_id == 0x68187316) { + struct pnp_cinfo d ; + read_pnp_parms ( &d , 1 ) ; + if (d.enable == 0) { + printf("This is an ESS1868, but LDN 1 is disabled\n"); + return NULL; + } + return "ESS1868" ; + } + return NULL ; +} + +static void +ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev) +{ + struct pnp_cinfo d ; + snddev_info tmp_d ; /* patched copy of the basic snddev_info */ + int the_irq = 0 ; + + tmp_d = sb_op_desc; + snddev_last_probed = &tmp_d; + +#if 0 + read_pnp_parms ( &d , 3 ); /* disable LDN 3 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 3 ); + + read_pnp_parms ( &d , 2 ); /* disable LDN 2 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 2 ); + read_pnp_parms ( &d , 0 ); /* read config base */ + tmp_d.conf_base = d.port[0]; + write_pnp_parms ( &d , 0 ); +#endif + + read_pnp_parms ( &d , 1 ) ; + dev->id_iobase = d.port[0]; + d.port[1] = 0 ; + d.port[2] = 0 ; + write_pnp_parms ( &d , 1 ); + enable_pnp_card(); + + dev->id_drq = d.drq[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = 0 /* DV_F_DUAL_DMA | (d.drq[1] ) */; + + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + pcmattach(dev); +} + static char *opti925_probe(u_long csn, u_long vend_id); static void opti925_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); @@ -1155,6 +1199,7 @@ sb16pnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; d.port[1] = 0 ; /* only the first address is used */ dev->id_iobase = d.port[0]; + tmp_d.synth_base = d.port[2]; write_pnp_parms ( &d , 0 ); enable_pnp_card(); diff --git a/sys/i386/isa/snd/CARDS b/sys/i386/isa/snd/CARDS new file mode 100644 index 0000000..af6048e --- /dev/null +++ b/sys/i386/isa/snd/CARDS @@ -0,0 +1,178 @@ +In this file I will try to build a database of cards supported by this +driver. I also include the command to use for manual configuration of +the card in case your BIOS is not PnP-aware. Of course it is your +responsibility to pick up free prot ranges and irq and drq channels. + +For PnP cards, I also include the vendor_id and serial numbers of +cards I have encountered. Underscores in the PnP id mean that there is +a large variety of values in those positions. + +Finally, where available, I have put the URL where you can find the +data sheets of the chip. + + +CS4236: PnP id 0x36__630e +CS4237: PnP id 0x37__630e + + http://www.crystal.com/ 4237full.pdf + + pnp 1 0 os enable port0 0x534 port2 0x220 irq0 5 drq0 1 drq1 3 + + work like a charm. All modes, including full duplex, supported in + MSS mode. + + NOTE: cards based on the 4237, and possibly the 4236 as well, + connect the CD to AUX2. When used in MODE1/2 (as this driver + does) there is no route in the input mixer from AUX2 to the + ADC. So, to record directly from the CD, the only way is + to set the input to SOUND_MASK_IMIX. + +CS4232: PnP id 0x3242630e + + pnp 1 0 os enable port0 0x534 port2 0x220 irq0 5 drq0 1 drq1 3 + + This chip is reported as broken in the OSS documentation. As a + matter of fact, on my Intel Zappa motherboard, I have problems in + make it use the secondary DMA channel. I have it working in + half duplex (both capture and playback) in SB3.2 emulation, + and working in playback mode in MSS emulation. + Also have reports from some user that it works ok. + +OPTi931: PnP id 0x3109143e + + http://www.opti.com/ opti931_21.pdf + + pnp 1 1 os enable port0 0x534 port2 0x220 port3 0xe0d irq0 10 drq0 1 drq1 6 + + The data sheets of this chip are very cryptic and do not match + what the cards I have seem to do. I have it working + in WSS emulation, in full duplex and all modes. + In SB emulation mode the driver does not work yet (maybe I do + not initialize it the right way). It is not a major concern + anyways. + I am strongly convinced of a couple of bugs in the chip. I have + sent email to OPTI but got no reply so far. The bugs are: + - you cannot set both playback and capture format to use + a companded mode (ULAW, ALAW). If you do, the card will screw + up on the capture section. + The driver has a partial fix in software: when using ULAW, it + programs ULAW on the playback section, U8 on the capture, and + does a conversion in software (much like the SBPro). Of course + you lose 4-5 bits of dynamic range in the process. + - in full duplex (and single DMA mode), the card occasionally + misses interrupts, or generates spurious ones. Spurious ints are + not problematic since they can be ignored, but missed ones are + as you can imagine... This is fixed by auto-dma mode. + +SB16 PnP: PnP id 0x__008c0e + + http://www.creative.com sbhwpg.pdf or SBHWPG.EXE + + pnp 1 0 os enable port0 0x220 irq0 5 drq0 1 drq1 5 + + There are many such cards (plain SB16 PnP, AWE32, AWE64, Vibra16, + etc.) all differing in the PnP id. They have different synthesis + devices, which we do not support, so we are not affected by these + differences. Don't worry if the driver identifies the card as a + different SB16 than the one you have. + + Full duplex support of this card is tricky since one channel can + work in 16-bit and the other in 8-bit mode. You will need to use + the new set of ioctl to use separate data formats on the two + channels (the vat driver does this). Of course, quality in 8-bit + is much lower than in 16-bit. + + Full duplex operation is unsupported by Creative. It seems to + work, although on my Vibra16 the command to stop DMA transfer + seems to erroneously affect both channels instead of the one + they are issued for. The driver has a workaround, but I cannot + guarantee that it works for everybody. I have had several + positive reports. + + Some docs on how to use this card with the voxware driver + are at http://multiverse.com/~rhh/awedrv + + NOTE: 980122 I have had a couple of reports from people using + the Vibra16X (and clones, e.g. based on the Avance Logic + chipsets) which appears to use two 8-bit DMA channels. It + might be possible that new SB16 are able to do true full duplex! + The driver _does not_ support these boards although the fix + should be relatively easy. + +ASound: + Avance Logic makes SB clones, and apparently documentation + is available at + + http://www.realtek.com.tw/cp/cp.html + + these card should be recognised as SB16 clones. + +Yamaha SA2/SA3 + + http://www.yamaha.com ? YM711.pdf + (this is a huge file containing a few pages scanned and converted + to pdf. Not very useful or detailed.) + + pnp 1 0 os enable port0 0x220 port1 0x530 irq0 5 drq0 1 drq1 5 + + this card emulates a WSS or SB. Have reports that it works, although + it has incomplete mixer support (Yamaha defines an additional set + of registers to control some mixer functions, such as the master + output volume). Currently we set the master volume to 100%. + Driver reported to work also on Toshiba DynaBook Portege 300CT + with OPL3SA-3(non-PNP) + +Ensoniq Soundscape VIVO + + pnp 1 0 os enable port0 0x220 port1 0x530 irq0 5 drq0 1 drq1 5 + + this card emulates a WSS or SB. Have reports that it works. + +GusPnP: PnP id 0x0100561e + + pnp 1 0 os enable port0 0x220 port1 0x320 port2 0x32c irq0 5 drq0 7 drq1 5 + + It works in U8 and S16 mode, ulaw support still not working. + + +NOT WORKING YET: + +OPTI925: PnP id 0x2509143e + + there is code to recognize it as a SB clone. I have reports that + it probes ok, but not sure if it works. + +OPTI924: PnP + + I have this card but it is still unsupported. + +OPTI930: + + should work as an MSS clone, but support for it is not implemented + yet. + +ESS1868 +ESS688 + + http://www.esstech.com + + pnp 1 1 os enable irq0 7 drq0 1 port1 0x220 + + There used to be documentation for the 1868 on the ESS site + (files ds1868b1.pdf and ds1868b2.pdf) but I am unable to find + it now (980122). I have asked ESS but no reply so far. + + partly supported in SB emulation. + + (the ESS688 is used on many notebooks. It is probably able to do 8 + and 16-bit, half duplex). + +PCI cards: + + some vendors have PCI cards. This code _cannot_ work on these + cards as it is now, since they cannot obviously use the ISA DMA + controller. As there are no data sheets available for these PCI + cards, none of them is supported at the moment, although support + should be easy as soon as I can put my hands on the data sheets + and cards. + diff --git a/sys/i386/isa/snd/README b/sys/i386/isa/snd/README index ac45b22..4c8119e 100644 --- a/sys/i386/isa/snd/README +++ b/sys/i386/isa/snd/README @@ -1,126 +1,288 @@ --- A new FreeBSD audio driver --- by Luigi Rizzo (luigi@iet.unipi.it) +This is a new, completely rewritten, audio driver for FreeBSD. +Only "soundcard.h" has remained largely similar to the original +OSS/Voxware header file, mostly for compatibility with existing +applications. + +This driver tries to cover those areas where the Voxware 3.0 driver +is mostly lacking: full-duplex, audio applications, modern (mostly +PnP) cards. For backward compatibility, the driver implements most +of the Voxware ioctl() audio calls, so that many applications -- +even commercial ones -- will run unmodified with this driver. On +the other hand, at the moment this driver does not support /dev/midi +and /dev/synth, or some ioctl() used in xquake. Do not expect +/dev/synth to be supported before summer'98. + +I also have implemented a new software interface with an independent +set of ioctl(), to support some functions which were not easy to +express with the existing software interface (e.g. full duplex on +the SB16). To make an effective use of the new functionalities you +need to recompile applications by replacing the audio module(s). +Such modified driver modules are present in the misc/ directory +for several applications. + +This file gives quick information on how to install the driver. For +more info you are invited to look at the doc/ directory where you +can find more documentation (in Latex and Postscript) on the driver +and how to extend it. I am afraid there is not yet a complete set of +manual pages for using the driver. The files in doc/ should give a +good idea of what the driver is supposed to do and how, the Voxware +documentation should be of some help, and the various driver modules +in misc/ should also give some help. -This is an experimental version of the audio driver for FreeBSD. -I have almost completely rewritten the main parts of the code, -starting from the Voxware 3.5-alpha release with patches from -Amancio Hasty. The only file which is largely similar to the original -ones is "soundcard.h" (for compatibility reasons, since it contains -the definition of all the ioctls). +Also note that you might need the PnP code (pnp971020.tgz), which +as of January 1998 has been incorporated in -current and -stable +versions of FreeBSD. -Visit http://www.iet.unipi.it/~luigi/FreeBSD.html for the latest -information on the drivers. There you can obtain the latest source -package that includes documentation on the design of the driver. +Updated versions of this code will available at the following URL: + http://www.iet.unipi.it/~luigi/FreeBSD.html -CARD SUPPORT INFORMATION: +Please READ CAREFULLY this file (and possibly the LaTeX documentation) +to build a working kernel. The configuration is DIFFERENT (and +hopefully much simpler) from the original Voxware driver. The +relevant steps are indicated at "---INSTALLATION---". -For PnP cards, I also include the vendor_id and serial numbers of -cards I have encountered. +This code (and the associated patches) should work unmodified on 2.2.5 +and in general on 2.2.X versions of FreeBSD. Minor modifications will be +necessary to make the code work on 2.1.X. Finally, depending on the +version of their system, 3.0 users might need to uncomment the +following line in snd/sound.h: + + /* #define USE_POLL */ + +This archive includes: +* the main files for the driver, in this directory, which must be + moved to /sys/i386/isa/snd; +* miscellaneous, but important, files in the "misc" subdirectory; +* documentation, in the "doc" subdirectory; + +CARDS: + + The driver supports most clones of WSS, SB16 and SBPro cards. + This includes those based on the Crystal CS423x, OPTI931, GUSPnP, + Yamaha, SB16/32 (both plain ISA, PnP, and the various AWExx). + Many PnP cards are directly recognised, for others you might need + manual configuration. See the file "CARDS" for more details. -CS4236: PnP id 0x3642630e -CS4237: PnP id 0x3742630e +APPLICATIONS: + + In general, most applications which use /dev/audio or /dev/dsp + work unmodified or with a specially-supplied module. + + UNMODIFIED: + - raplayer (Real Audio Player), rvplayer (linux version) + - xboing + - xanim + - various mpeg players (mpg123, amp, ...); - works like a charm. All modes, including full duplex, supported in - MSS mode. + WITH SPECIAL DRIVER MODULE (supplied) + - speak_freely, full duplex (requires removing the definition of + HALF_DUPLEX in the Makefile); + - the realaudio player (3.0, dynamically linked); + - vat, full duplex (driver included); + - nas, full duplex (driver included); + - timidity, a software midi-to-pcm converter; -CS4232: PnP id 0x3242630e + NOT WORKING + - xquake (we do not support mmapped buffers yet); - this chip is reported as broken in the OSS documentation. As a - matter of fact, on my Intel Zappa motherboard, I have problems in - make it use the secondary DMA channel. I have it working in - half duplex (both capture and playback) in SB3.2 emulation, - and working in playback mode in MSS emulation. -OPTi931: PnP id 0x3109143e + ---INSTALLATION--- - The data sheets of this chip are very cryptic and do not match - what the cards I have seem to do. I have it working - in WSS emulation, in full duplex and all modes. - In SB emulation mode the driver does not work yet (maybe I do - not initialize it the right way). It is not a major concern - anyways. - I am strongly convinced of a couple of bugs in the chip. I have - sent email to OPTI but got no reply so far. The bugs are: - - you cannot set both playback and capture format to use - a companded mode (ULAW, ALAW). If you do, the card will screw - up on the capture section. - The driver has a partial fix in software: when using ULAW, it - programs ULAW on the playback section, U8 on the capture, and - does a conversion in software (much like the SBPro). Of course - you lose 4-5 bits of dynamic range in the process. - - in full duplex (and single DMA mode), the card occasionally - misses interrupts, or generates spurious ones. Spurious ints are - not problematic since they can be ignored, but missed ones are - as you can imagine... This is fixed by auto-dma mode. +In order to use this driver, you need FreeBSD 2.2 or above (I have +tested this code on 2.2.1 and 2.2.5, and have many reports of the +driver working on various vintages of 3.0 ). The enclosed patch +file refers to FreeBSD 2.2.5. Your mileage may vary -- e.g. both +-stable and -current at the time of this writing (Jan.98) have +included the modifications to isa.c . -SB16 PnP: PnP id 0xXX008c0e +Installation requires the following steps: - There are many such cards (plain SB16 PnP, AWE32, AWE64, Vibra16, - etc.) all differing in the PnP id. They have different synthesis - devices, which we do not support, so we are not affected by these - differences. Don't worry if the driver identifies the card as a - different SB16 than the one you have. + * install the PnP support files (pnp971020.tgz). This is optional, + in case you don't you might need to do some simple modifications + to the file because pnp.h might not exist. - Full duplex support of this card is tricky since one channel can - work in 16-bit and the other in 8-bit mode. You will need to use - the new set of ioctl to use separate data formats on the two - channels (the vat driver does this). Of course, quality in 8-bit - is much lower than in 16-bit. + * unpack the content of this archive in /sys/i386/isa/snd/ + + * if you are running a version of 2.2 earlies than 2.2.5-RELEASE, + apply the patches in "patches.22x" to isa.c - Full duplex operation is unsupported by Creative. It seems to - work, although on my Vibra16 the command to stop DMA transfer - seems to erroneously affect both channels instead of the one - they are issued for. The driver has a workaround, but I cannot - guarantee that it works for everybody. I have had several - positive reports. + * patch the following system files using the patches in + "patches.225" : -Yamaha SA2 + /sys/i386/isa/isa.c + a couple of new functions have been added; + + /sys/i386/conf/files.i386 + lines related to this audio driver have been added; - this card emulates a WSS or SB. Have reports that it works, although - it has mixer problems (maybe the driver has the wrong set of - parameters). + * update file soundcard.h by copying the one in this directory into: -Ensoniq Soundscape VIVO + /sys/i386/include/soundcard.h + /usr/include/machine/soundcard.h - this card emulates a WSS or SB. Have reports that it works. + The new file should be compatible with the old one, but has + the definition of new ioctl() calls which are implemented by + this driver. -GusPnP: PnP id 0x0100561e + * add the following lines to your kernel configuration file: - I have code to recognize the board as MSS, but some initializations - of the IW chip is still missing, so the card is not supported yet. - I am working on this problem... + controller pnp0 # this is required for PnP support -OPTI924: PnP + device pcm0 at isa ? port? tty irq N drq D flags F vector pcmintr - I have this card but it is still unsupported. + where -OPTI925: PnP id 0x2509143e + N is the IRQ address used by the sound card, + D is the primary DMA channel used by the sound card, + F is used to specify a number of options, in particular: + bit 2..0 secondary DMA channel; + bit 4 set if the board uses two dma channels; + bit 15..8 board type, overrides autodetection; leave it + zero if don't know what to put in (and you don't, + since this is unsupported at the moment...). - there is code to recognize it as a SB clone. I have reports that - it probes ok, but not sure if it works. + The code will probe for common port addresses (0x220, 0x240 + for SB and clonse, 0x530 for WSS and clones), so you don't need + to specify them if your system uses one of them. In case you + do, note that for WSS cards the code assumes that the board + occupies 8 IO addresses, the first four used to configure + IRQ/DRQ, and the other four to access codec registers. Many + boards (including all the ones I have) do not have registers + to program IRQ and DRQ, so they really start at 0x534... yet + I decided to use the old convention for historical reasons. -OPTI930: + You can use multiple sound cards, in which case you need more + lines like - should work as an MSS clone, but support for it is not implemented - yet. + device pcm1 at isa ? port? tty irq N drq D flags F vector pcmintr + device pcm2 at isa ? port? tty irq N drq D flags F vector pcmintr + ... -ESS1868 + EXAMPLES: a typical "device" line for the SB16 (full duplex) is - this card is not supported yet. It might work in SB emulation but - am not sure. + device pcm0 at isa ? port? tty irq 5 drq 1 flags 0x15 vector pcmintr -ESS688 + The driver will check at the default addresses (or the one you + specify) which type of SoundBlaster you have (1.5, 2.0, 3.X + aka SBPro, 4.X aka SB16) and use the correct commands. You + _do_not_ need to specify different drivers (sb,sbpro,sbxvi) as + it was the case (and a big source of confusion) in the previous + sound driver. - this card is used on many notebook. I don't have docs on this card - so I cannot support it. Pointers to the data sheets are welcome. + For a WSS-compatible codec (non PnP) working in full duplex using + dma channels 1 and 3, you can specify: -PCI cards: + device pcm0 at isa ? port 0x530 tty irq 7 drq 1 flags 0x13 vector pcmintr - some vendors have PCI cards. This code _cannot_ work on these - cards as it is now, since they cannot obviously use the ISA DMA - controller. As there are no data sheets available for these PCI - cards, none of them is supported at the moment, although support - should be easy as soon as I can put my hands on the data sheets - and cards. + (0x530 is a default if no port address is specified). The + "flags 0x13" specifies that you have a dual dma board with + channel 3 as secondary DMA channel. + + * build the kernel using the following steps + + config MYKERNEL + cd /sys/compile/MYKERNEL + make depend + make + + * PnP support: + + For PnP cards, only the line for "pcm0" is needed (the code + will allocate entries for more cards if found), but IT MUST + INCLUDE ALL FIELDS, including "vector pcmintr". You can use + the following line: + + device pcm0 at isa ? port? tty irq 7 drq 1 vector pcmintr + + NOTE that: + - the parameters for the PnP device(s) will be read from the + configuration of the card(s); they are usually assigned by + the bios, and there is no way (at the moment) to override + them, so if you have a broken (or no) PnP bios your only + chance is to patch the pnp attach code in the driver for your + card (ad1848.c, sb_dsp.c, clones.c) and write there the + parameters you want; + - The driver will assign unit numbers to the PnP cards starting + from the next free one (e.g. 1, 2, ...) same as it is done + with PCI devices which are clones of ISA devices. + + The driver assumes a working PnP bios, which will assign correct + addresses and IO and DMA channels to the devices. If you do not + have a PnP-aware BIOS, you must boot with the -c option and assign + addresses manually the first time. The general commands are described in + the pnp README file. For the card-specific commands check in the + file CARDS. + +WHAT IF THIS DRIVER DOES NOT WORK: + +If the driver does not work with your hardware, I am willing to +help but I need the following: + + - relevant lines in your config file; + - dmesg output + - possibly, pnpinfo output + +Please send all the above in plain text, not as a mime attachment. + +Common mistakes: + +* you are trying to use /dev/audio0 instead of /dev/audio1 + For technical reasons, a PnP device is attached as unit 1 instead + of unit 0 -- most applications are programmed to access the audio + hardware through /dev/audio, /dev/dsp, /dev/mixer which are in turn + symlinks to the correct device entries. Check them and possibly fix + these symlinks in /dev + +* you have used a wrong config line + + The configuration of this driver is different from the Voxware one. + Please read the information in this file carefully. + +* your BIOS is leaving the card disabled. + + Some BIOSes do not initialize the card, or leave it disabled. At the + moment, the fix is to use the pnp code booting with "-c" and set the + correct port, irq, drq etc for the card. See the PnP documentation. + +* your card is not recognized. + + This driver supports a large, but still limited, number of cards, + mostly SB and WSS clones. Other cards may or may not work depending + on how closely they emulate these devices. In case, send me an email + with the info indicated above. + +* the mixer does not work well + + Different cards have different connections to the mixer, so it might + well be that to control the volume of your CD you have to use the FM + port, etc. Also, on some cards the volume might be lower than you + expect. The mixer code still does not try to exploit the features of + each card, and it just provides basic functionalities. + +--- ACKNWOLEDGEMENTS --- + +Several people helped, directly or indirectly, in the development of +this driver. In particular I would like to thank: + + * Hannu Savolainen (the Voxware author) for making his code + available. It was a very good source of technical info; + * Amancio Hasty for continuous support and his work on guspnp code; + * Jim Lowe for his suggestion on the block-mode select; + * Allison Mankin and Brad Karp at ISI-East for supplying a GUS PnP + which allowed me to support this card; + * Eric J. Schwertfeger for donating sn ES1868 card for writing the + driver. + * and many people who had the patience to try the driver + on their cards and report success/fauilure and useful + information. + +It was certainly helpful to have the data sheets for some of the +devices I support available on the net, especially in the (unfortunately +rare) cases where the data sheets matched the actual behaviour of +the product. Too bad that no one of the chip/card manufacturers I +have contacted by email regarding missing or inconsistent documentation +on their products did even care to reply to my messages. diff --git a/sys/i386/isa/snd/ad1848.c b/sys/i386/isa/snd/ad1848.c index a6bf624..26eaee5 100644 --- a/sys/i386/isa/snd/ad1848.c +++ b/sys/i386/isa/snd/ad1848.c @@ -94,7 +94,7 @@ snddev_info mss_op_desc = { NULL /* mss_read */, NULL /* mss_write */, mss_ioctl, - sndpoll /* mss_poll */, + sndselect /* mss_select */, mss_intr, mss_callback , @@ -133,8 +133,8 @@ mss_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x530; - printf("mss_probe: no address supplied, try default 0x%x\n", - dev->id_iobase); + BVDDB(printf("mss_probe: no address supplied, try default 0x%x\n", + dev->id_iobase)); } if (snd_conflict(dev->id_iobase)) return 0 ; @@ -150,13 +150,13 @@ mss_probe(struct isa_device *dev) tmp = inb(dev->id_iobase + 3); if (tmp == 0xff) { /* Bus float */ - DEB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp)); + BVDDB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp)); dev->id_flags &= ~DV_F_TRUE_MSS ; goto mss_probe_end; } tmp &= 0x3f ; if (tmp != 0x04 && tmp != 0x0f && tmp != 0x00) { - DEB(printf("No MSS signature detected on port 0x%x (0x%x)\n", + BVDDB(printf("No MSS signature detected on port 0x%x (0x%x)\n", dev->id_iobase, inb(dev->id_iobase + 3))); return 0; } @@ -276,10 +276,12 @@ mss_open(dev_t dev, int flags, int mode, struct proc * p) d->flags |= SND_F_BUSY_DSP ; else if (dev == SND_DEV_DSP16) d->flags |= SND_F_BUSY_DSP16 ; - if ( ! (d->flags & SND_F_BUSY) ) { + if ( d->flags & SND_F_BUSY ) + splx(s); /* device was already set, no need to reinit */ + else { /* * device was idle. Do the necessary initialization, - * but no need keep interrupts blocked since this device + * but no need keep interrupts blocked. * will not get them */ @@ -313,7 +315,6 @@ mss_open(dev_t dev, int flags, int mode, struct proc * p) } ask_init(d); /* and reset buffers... */ } - splx(s); return 0 ; } @@ -343,14 +344,15 @@ mss_close(dev_t dev, int flags, int mode, struct proc * p) d->flags &= ~SND_F_BUSY_DSP ; else if (dev == SND_DEV_DSP16) d->flags &= ~SND_F_BUSY_DSP16 ; - if ( ! (d->flags & SND_F_BUSY_ANY) ) { /* last one ... */ + if ( d->flags & SND_F_BUSY_ANY ) /* still some device open */ + splx(s); + else { /* last one */ d->flags |= SND_F_CLOSING ; splx(s); /* is this ok here ? */ snd_flush(d); outb(io_Status(d), 0); /* Clear interrupt status */ d->flags = 0 ; } - splx(s); return 0 ; } @@ -435,6 +437,7 @@ mss_callback(snddev_info *d, int reason) ad_write_cnt(d, 30, cnt); break ; + case SND_CB_STOP : case SND_CB_ABORT : /* XXX check this... */ m = ad_read(d,9) ; @@ -548,11 +551,11 @@ again: c=mc11 = FULL_DUPLEX(d) ? opti_read(d->conf_base, 11) : 0xc ; mc11 &= 0x0c ; if (c & 0x10) { - printf("Warning: CD interrupt\n"); + DEB(printf("Warning: CD interrupt\n");) mc11 |= 0x10 ; } if (c & 0x20) { - printf("Warning: MPU interrupt\n"); + DEB(printf("Warning: MPU interrupt\n");) mc11 |= 0x20 ; } if (mc11 & masked) @@ -578,7 +581,7 @@ again: } opti_write(d->conf_base, 11, ~mc11); /* ack */ if (--loops) goto again; - printf("xxx too many loops\n"); + DEB(printf("xxx too many loops\n");) } /* @@ -637,13 +640,13 @@ gus_readw(int io_base, u_char reg) static int AD_WAIT_INIT(snddev_info *d, int x) { - int n = 0; /* to shut up the compiler... */ + int arg=x, n = 0; /* to shut up the compiler... */ for (; x-- ; ) if ( (n=inb(io_Index_Addr(d))) & IA_BUSY) DELAY(10); else return n ; - printf("AD_WAIT_INIT FAILED 0x%02x\n", n); + printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n); return n ; } @@ -1061,7 +1064,7 @@ mss_detect(struct isa_device *dev) else break ; if ((inb(io_Index_Addr(d)) & IA_BUSY) != 0x00) { /* Not a AD1848 */ - DEB(printf("mss_detect error, busy still set (0x%02x)\n", + BVDDB(printf("mss_detect error, busy still set (0x%02x)\n", inb(io_Index_Addr(d)))); return 0; } @@ -1076,7 +1079,7 @@ mss_detect(struct isa_device *dev) tmp1 = ad_read(d, 0) ; tmp2 = ad_read(d, 1) ; if ( tmp1 != 0xaa || tmp2 != 0x45) { - DEB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n", + BVDDB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n", tmp1, tmp2)); return 0; } @@ -1087,7 +1090,7 @@ mss_detect(struct isa_device *dev) tmp2 = ad_read(d, 1) ; if (tmp1 != 0x45 || tmp2 != 0xaa) { - DEB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); + BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); return 0; } @@ -1101,7 +1104,7 @@ mss_detect(struct isa_device *dev) tmp1 = ad_read(d, 12); if ((tmp & 0x0f) != (tmp1 & 0x0f)) { - DEB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n", + BVDDB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n", tmp1, tmp)); return 0; } @@ -1112,7 +1115,7 @@ mss_detect(struct isa_device *dev) * 0x0A=RevC. also CS4231/CS4231A and OPTi931 */ - printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f); + BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);) /* * The original AD1848/CS4248 has just 16 indirect registers. This @@ -1125,7 +1128,7 @@ mss_detect(struct isa_device *dev) for (i = 0; i < 16; i++) if ((tmp1 = ad_read(d, i)) != (tmp2 = ad_read(d, i + 16))) { - DEB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", + BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", i, tmp1, tmp2)); /* * note - this seems to fail on the 4232 on I11. So we just break @@ -1148,7 +1151,7 @@ mss_detect(struct isa_device *dev) name = "CS4248" ; /* Our best knowledge just now */ } if ((tmp1 & 0xf0) == 0x00) { - printf("this should be an OPTi931\n"); + BVDDB(printf("this should be an OPTi931\n");) } else if ((tmp1 & 0xc0) == 0xC0) { /* * The 4231 has bit7=1 always, and bit6 we just set to 1. @@ -1162,14 +1165,13 @@ mss_detect(struct isa_device *dev) ad_write(d, 0, 0xaa); if ((tmp1 = ad_read(d, 16)) == 0xaa) { /* Rotten bits? */ - DEB(printf("mss_detect error - step H(%x)\n", tmp1)); + BVDDB(printf("mss_detect error - step H(%x)\n", tmp1)); return 0; } /* * Verify that some bits of I25 are read only. */ - DEB(printf("mss_detect() - step I\n")); tmp1 = ad_read(d, 25); /* Original bits */ ad_write(d, 25, ~tmp1); /* Invert all bits */ if ((ad_read(d, 25) & 0xe7) == (tmp1 & 0xe7)) { @@ -1240,7 +1242,7 @@ mss_detect(struct isa_device *dev) break ; default: /* Assume CS4231 */ - printf("unknown id 0x%02x, assuming CS4231\n", id); + BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);) d->bd_id = MD_CS4231; } @@ -1249,7 +1251,7 @@ mss_detect(struct isa_device *dev) } } - DEB(printf("mss_detect() - Detected %s\n", name)); + BVDDB(printf("mss_detect() - Detected %s\n", name)); strcpy(d->name, name); dev->id_flags &= ~DV_F_DEV_MASK ; dev->id_flags |= (d->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK ; @@ -1390,7 +1392,7 @@ cs423x_attach(u_long csn, u_long vend_id, char *name, return ; } snddev_last_probed = &tmp_d; - if (d.flags & DV_PNP_SBCODEC) { /*** use sb-compatible codec ***/ + if (d.flags) { /*** use sb-compatible codec ***/ dev->id_alive = 16 ; /* number of io ports ? */ tmp_d = sb_op_desc ; if (vend_id==0x2000a865 || vend_id==0x3000a865 || vend_id==0x8140d315) { @@ -1455,6 +1457,7 @@ cs423x_attach(u_long csn, u_long vend_id, char *name, dev->id_intr = pcmintr ; dev->id_flags = DV_F_DUAL_DMA | (d.drq[1] ) ; + tmp_d.synth_base = d.port[1]; /* XXX check this for yamaha */ pcmattach(dev); } @@ -1508,7 +1511,7 @@ opti931_attach(u_long csn, u_long vend_id, char *name, enable_pnp_card(); snddev_last_probed = &tmp_d; - tmp_d = d.flags & DV_PNP_SBCODEC ? sb_op_desc : mss_op_desc ; + tmp_d = d.flags ? sb_op_desc : mss_op_desc ; strcpy(tmp_d.name, name); @@ -1526,10 +1529,11 @@ opti931_attach(u_long csn, u_long vend_id, char *name, dev->id_iobase = d.port[0] - 4 ; /* old mss have 4 bytes before... */ tmp_d.io_base = dev->id_iobase; /* needed for ad_write to work... */ tmp_d.alt_base = d.port[2]; + tmp_d.synth_base = d.port[1]; opti_write(p, 4, 0xd6 /* fifo empty, OPL3, audio enable, SB3.2 */ ); ad_write (&tmp_d, 10, 2); /* enable interrupts */ - if (d.flags & DV_PNP_SBCODEC) { /* sb-compatible codec */ + if (d.flags) { /* sb-compatible codec */ /* * the 931 is not a real SB, it has important pieces of * hardware controlled by both the WSS and the SB port... @@ -1598,7 +1602,6 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; /* d.irq[1] = d.irq[0] ; */ - printf("pnp_read 0xf2 returns 0x%x\n", pnp_read(0xf2) ); pnp_write ( 0xf2, 0xff ); /* enable power on the guspnp */ write_pnp_parms ( &d , 0 ); @@ -1623,7 +1626,6 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, DELAY(1000 * 30); /* release reset and enable DAC */ gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 3 ); - printf("resetting the gus...\n"); DELAY(1000 * 30); /* end of reset */ @@ -1645,7 +1647,7 @@ guspnp_attach(u_long csn, u_long vend_id, char *name, /* enable access to hidden regs */ tmp = gus_read(tmp_d.conf_base, 0x5b /* IVERI */ ); gus_write(tmp_d.conf_base, 0x5b , tmp | 1 ); - printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) ); + BVDDB(printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) );) strcpy(tmp_d.name, name); diff --git a/sys/i386/isa/snd/dmabuf.c b/sys/i386/isa/snd/dmabuf.c index c6b1c57..8c647cb 100644 --- a/sys/i386/isa/snd/dmabuf.c +++ b/sys/i386/isa/snd/dmabuf.c @@ -162,9 +162,8 @@ dsp_wrintr(snddev_info *d) b->dl, b->rp, b->rl, b->fp, b->fl)); /* * start another dma operation only if have ready data in the buffer, - * there is no pending abort, have a full-duplex device - * or have half duplex device - * and there is no * pending op on the other side. + * there is no pending abort, have a full-duplex device, or have a + * half duplex device and there is no pending op on the other side. * * Force transfers to be aligned to a boundary of 4, which is * needed when doing stereo and 16-bit. We could make this @@ -176,12 +175,24 @@ dsp_wrintr(snddev_info *d) int l = min(b->rl, d->play_blocksize ); /* avoid too large transfer */ l &= DMA_ALIGN_MASK ; /* realign things */ - if (l != b->dl) { + /* + * check if we need to reprogram the DMA on the sound card. + * This happens if the size has changed _and_ the new size + * is smaller, or it matches the blocksize. + */ + if (l != b->dl && (l < b->dl || l == d->play_blocksize) ) { /* for any reason, size has changed. Stop and restart */ DEB(printf("wrintr: bsz change from %d to %d, rp %d rl %d\n", b->dl, l, b->rp, b->rl)); - b->dl = l; /* record previous transfer size */ d->callback(d, SND_CB_WR | SND_CB_STOP ); + /* + * at high speed, it might well be that the count + * changes in the meantime. So we try to update b->rl + */ + dsp_wr_dmaupdate(b) ; + l = min(b->rl, d->play_blocksize ); + l &= DMA_ALIGN_MASK ; /* realign things */ + b->dl = l; /* record previous transfer size */ d->callback(d, SND_CB_WR | SND_CB_START ); } } else { @@ -193,7 +204,7 @@ dsp_wrintr(snddev_info *d) d->callback(d, SND_CB_WR | SND_CB_STOP ); /* stop dma */ if (d->flags & SND_F_WRITING) DEB(printf("Race! got wrint while reloading...\n")); - else + else if (b->rl <= 0) /* XXX added 980110 lr */ reset_dbuf(b, SND_CHAN_WR); } /* @@ -236,7 +247,7 @@ dsp_write_body(snddev_info *d, struct uio *buf) * the previous operation. */ bsz = b->dl ? MIN_CHUNK_SIZE : b->bufsize ; - while (( n = buf->uio_resid )) { + while ( n = buf->uio_resid ) { l = min (n, bsz); /* at most n bytes ... */ s = spltty(); /* no interrupts here ... */ dsp_wr_dmaupdate(b); @@ -750,7 +761,7 @@ snd_flush(snddev_info *d) return -1 ; } if ( ret && --count == 0) { - printf("timeout flushing dbuf_out.chan, cnt 0x%x flags 0x%08lx\n", + printf("timeout flushing dbuf_out.chan, cnt 0x%x flags 0x%08x\n", b->rl, d->flags); break; } diff --git a/sys/i386/isa/snd/doc/Makefile b/sys/i386/isa/snd/doc/Makefile new file mode 100644 index 0000000..355a77f --- /dev/null +++ b/sys/i386/isa/snd/doc/Makefile @@ -0,0 +1,40 @@ +# Makefile for the sound documentation + +ARTICLE=sound +INPUTS= +STYLES= +.SUFFIXES: .fig .ps +OTHERSTUFF= +FIGURES= +PSS= +ALLFIG= $(FIGURES) +COMPAT= psfigps.tex + +ALLPS= $(ALLFIG:.fig=.ps) + +ALLSRCS= $(ARTICLE).tex $(STYLES) $(ALLFIG) $(PSS) $(INPUTS) \ + $(COMPAT) $(OTHERSTUFF) Makefile + +.fig.ps: + fig2dev -L ps -m 0.5 $*.fig > $*.ps + +all: $(ARTICLE).dvi pnp.dvi ioctl.dvi Makefile + +$(ARTICLE).dvi: allfig $(ARTICLE).tex $(INPUTS) $(STYLES) $(PSS) + latex $(ARTICLE).tex + +pnp.dvi: allfig pnp.tex $(INPUTS) $(STYLES) $(PSS) + latex pnp.tex + +ioctl.dvi: allfig pnp.tex $(INPUTS) $(STYLES) $(PSS) + latex ioctl.tex + +allfig: $(ALLPS) + +clean: + rm -f $(ALLPS) a c *.dvi *.aux *.log rl + +tgz: $(ALLSRCS) + tar cvf - $(ALLSRCS) | gzip -9 > $(ARTICLE).tgz + + diff --git a/sys/i386/isa/snd/doc/sound.ps.gz b/sys/i386/isa/snd/doc/sound.ps.gz Binary files differnew file mode 100644 index 0000000..12beb0a --- /dev/null +++ b/sys/i386/isa/snd/doc/sound.ps.gz diff --git a/sys/i386/isa/snd/doc/sound.tex b/sys/i386/isa/snd/doc/sound.tex new file mode 100644 index 0000000..d8e7059 --- /dev/null +++ b/sys/i386/isa/snd/doc/sound.tex @@ -0,0 +1,917 @@ +\documentclass[11pt]{article} + +\textwidth 6in +\textheight 9in +\oddsidemargin 0in +\evensidemargin 0in +\topmargin 0in + +% \input{psfigps.tex} + +\begin{document} +\author{Luigi Rizzo\\ + Dipartimento di Ingegneria dell'Informazione -- Universit\`a di Pisa\\ + via Diotisalvi 2 -- 56126 Pisa (Italy)\\ + email: {\tt l.rizzo@iet.unipi.it}} +\title{The FreeBSD audio driver} +\maketitle + +\begin{abstract} +In this paper we describe the architecture of the new FreeBSD audio +driver. We also give detailed information on the internal data +structures used by the driver, and examples on how to add support +for new devices. + +{\bf WARNING:} since the code is rapidly evolving, there might be some +minor differences between this paper and the source code. In doubt, +the source code is the ultimate reference. + +\end{abstract} + +\section{Introduction} + +The audio driver is the part of the kernel which handles audio-related +peripherals. In FreeBSD, historically, there has been not much support +for these devices. The original FreeBSD audio driver has been based on +the Voxware 3.0 release, originally developed for Linux and ported to +FreeBSD at 1.1 times. The driver in the source tree has been +essentially unsupported since then. + +In parallel, some volunteers have been using a driver derived from +Voxware 3.5, which had support for full-duplex audio cards which have +become popular in more recent times. + +Both versions of the Voxware code are extremely complex and not very +much in line with BSD device drivers. There are several motivations +for that, the main one (we believe) was that the code has evolved +through the years to support a number of boards which are sufficiently +similar to suggest not to write a completely different driver, and +sufficiently different to require the addition of state variables, new +support routines, and feature enahncements, to the existing driver. +The unfortunate result of this evolution was that the code in our hand +was extremely difficult to configure, follow, and in many cases unable to +support multiple devices. Furthermore, the complexity of the code, +prevented proper support for it. + +The desire for a functional audio driver for FreeBSD, together with +the lack of support for the code currently in the source tree, and +its complexity of the original driver, were for us sufficient +motivations to start a large rewrite of the code. + +From a user's point of view, we were especially concerned with the +complexity of the configuration process, which, to be done properly, +required a number of different devices to be compiled into the kernel, +with (at times) subtle dependencies among the modules and the options +to be used. So, one of our goal was to simplify the configuration +process, a goal which we believe to have achieved. + +From the kernel developer's point of view, the Voxware driver was hard +to understand for two reasons. First, its structure was not much in +line with BSD driver. This obviously derives from the original +development on a different system. The second reason is the evolution +of the driver during time, which, we believe, has degraded the quality of +the code from a carefully designed original version. + +We have started our work basing on Voxware 3.5. The Voxware code +served mainly as a reference on what the features of the various +boards were, and how problems had been dealt with. Our code +then evolved into a fairly complete rewrite of the main modules, +including the main configuration mechanisms, the DMA support +routines, and, in many cases, also the board-specific drivers which +could make use of many of the simplifications we have introduced. + +\section{What the audio driver does} + +The audio driver is intended to control all peripherals related to +audio handling. This includes digital audio sampling and generation +({\tt pcm}), communication with MIDI devices ({\tt midi}) and the +synthesizers ({\tt synth}) which several audio cards provide. In +addition to these devices, which have explicit entries in the kernel +configuration file, audio cards generally provide a {\em mixer} +device, which controls audio paths within the board. +Our devices currently has only support for {\tt pcm} and mixer +devices. + +\subsection{The {\tt pcm} device} + +The {\tt pcm} device is in charge of the audio sampling and +generation. Such device is generally implemented by a CODEC, a piece +of hardware containing one or two A/D and D/A converters. Depending on +the board, the minimum and maximum sampling rate, the number of bits +per sample, and the data format (e.g. the availability of compressed +audio formats) varies. At a minimum, all boards seem to be capable of +sampling mono audio with 8-bit unsigned values and 8 KHz resolutions. +Most boards (especially the Windows-Sound-System ones, known as WSS or +MSS) are also capable of full duplex stereo with 16-bit and 44.1KHz +sampling rate (corresponding to CD-quality). + +The {\tt pcm} device implements the {\tt ioctl()} calls to select the +desired data format, and {\tt read()} and {\tt write()} functions to +allow a smooth exchange of data with the codec, and {\tt select()} to +notify the application when I/O is possible. + + +\subsection{The mixer} + +The mixer controls audio data paths, i.e. which sources are directed +to the ADC input, which ones are directed to the audio output, and the +attenuation on the various channels. There is a wide variety of +features in this subsystem, as some boards do provide only on-off +control on a limited number of sources, whereas some boards provide +many sources and fine-grained volume controls. + +We believe that a sufficiently general model of the {\tt mixer} +is one actually using {\em two} mixers, one to connect sources to the +ADC part of the codec, and one to connect sources to the physical +output (usually the speakers). Some early boards often implement the +mixer leading to the ADC only as a multiplexer with one active line, +but that can be modeled by thinking of a very coarse (1 bit) volume +control, and a restriction on the volumes on the various sources. + + +\section{Kernel Configuration} + +The configuration of the driver is done through the following +statements included in the system configuration file: +\begin{verbatim} +device pcm0 at isa? port? tty irq N drq D flags F vector pcmintr +\end{verbatim} +The {\tt device pcm0} statement is in charge of bringing into the +kernel all modules related to audio support. + +The main audio subsystem is configured by using the {\tt device +pcm0} statement. This line supplies the main parameters related to the +configuration of a single sound card, which are: +\begin{itemize} +\item {\bf port} the I/O address for the codec (and generally mixer) + subsystem. If no address is supplied, the driver looks at + different addresses for the most common boards. +\item {\bf irq} the interrupt line used by the board. +\item {\bf drq} the first DMA channel used by the board. Modern + sound boards supporting full-duplex can use two DMA channels, + so we use the {\tt flags} field to specify the second channel. +\item {\bf flags} used to specify the second DMA channel, the device + type (overriding the autoconfiguration mechanism), and + other information. + In particular, the flags have the following use:\\ + \begin{center} + \begin{tabular}{|c|l|} + \hline Bits & Meaning \\ + \hline \hline + 2..0 & secondary (read) DMA channel for dual-dma boards. \\ + \hline + 4 & Enable secondary dma channel \\ + \hline + 15..8 & Specific device type/class. 0 means autoconfigure\\ + \hline + \end{tabular} + \end{center} +\end{itemize} +A sound board often has more than the codec, and uses more than one +I/O address block. Since the {\tt device} line in the config file +does not permit to +specify more than one address (except, perhaps, by abusing the {\tt +flags} field, which we have already used though), we will +use two additional {\tt device} entries to specify the i/o address +for the MIDI ({\tt midi}) and Synthesizer ({\tt synth}) devices, +respectively. These entries do not correspond to actual devices +recognized by the kernel (in the sense that their probe routine will +always fail, and their attach routine will do nothing and will never +be called); rather, the parameters specified in these lines are used +to extend the description of the board provided by the {\tt device +pcmX} line. +There is no support at the moment for {\tt midi} and {\tt synth} +devices. + +\subsection{Plug and Play support} + +As a side effect of our work on the audio driver, we have implemented +support for Plug and Play (PnP) device configuration. This was a +necessity since many modern audio boards are configured through +PnP. PnP support is described in a separate document. + +In order to support PnP boards, one device entry is still necessary +(i.e. {\tt pcm0}), to let the +configure program include all the necessary parameters. The entry must +include all fields, e.g. +\begin{verbatim} +device pcm0 at isa? port? tty irq 7 drq 1 vector pcmintr +\end{verbatim} +although +the actual configuration of PnP devices (i.e. port addresses, IRQ, +DMA, etc. will be auto detected, and unit numbers will be assigned +starting from the next free one (e.g. {\tt pcm1, pcm2, ...}). + +\section{Code structure} + +The following table details the various file which make up the code. +All files reside in the directory {\tt /sys/i386/isa/snd} . + +\begin{center} +\begin{tabular}{|l|p{4in}|} +\hline +File Name & Description \\ +\hline \hline +{\tt sound.c} & main device routines and autoconfiguration.\\ +\hline +{\tt sound.h} & generic header for all kernel files.\\ +\hline +{\tt soundcard.h} & generic header for all userland sound apps.\\ +\hline +{\tt dmabuf.c} & DMA support. \\ +\hline +{\tt sb\_dsp.c} & SoundBlaster driver \\ +\hline +{\tt ad1848.c} & MSS-compatible driver \\ +\hline +{\tt clones.c} & Miscellaneous code to enable SB/MSS emulation in + various boards. \\ +\hline +\end{tabular} +\end{center} + +\subsection{The {\tt snddev\_info} structure} + +Each known board is fully described by a {\tt snddev\_info} +structure. The structure contains information of use at initialization +time (such as board type, name, probe and attach routines), and at +runtime. The latter include all board-specific functions, and the +generic status information about the board when in use. + +{\tt snddev\_info} is made of two parts, the first one which is +generally initialized from a template which univoquely identifies the +board, the second one which contains run-time parameters. The first part +of the structure is: + +\begin{verbatim} +typedef struct _snddev_info snddev_info ; +struct _snddev_info { + char name[64]; + int type ; /* board type/class, search key during probes */ + int (*probe)(struct isa_device * dev); + int (*attach)(struct isa_device * dev) ; + d_open_t *open ; + d_close_t *close ; + d_read_t *read ; + d_write_t *write ; + d_ioctl_t *ioctl ; + d_select_t *select ; + irq_proc_t *isr ; + snd_callback_t *callback; + + int bufsize; /* DMA buffer size */ + u_long audio_fmt ; /* bitmap of supported audio formats */ +\end{verbatim} +where the names of the fields clearly identify the functions. +Board-specific code generally supplies, in the template, initializers +for all the above fields. At attach time, and before calling the +board-specific attach routines, the descriptor associated with the +device, {\tt pcm\_info[dev->id\_unit]} is bzeroed, initialized with +the template, then the fields in the second part of the structure +(shown next) are initialized with default values (partly taken from +the device config line, as in the case of i/o, irq and dma addresses), +and the required buffers are allocated. + +\begin{verbatim} + int io_base, alt_base, conf_base, + mix_base, midi_base, synth_base; + + int bd_id; /* board-specific id */ + snd_dbuf dbuf_out, dbuf_in; + int status_len, status_ptr ; /* to implement sndstat */ + + volatile u_long flags; /* generic flags */ + + u_long bd_flags; /* board-specific flags */ + int play_speed, rec_speed; + + int play_blocksize, rec_blocksize; + u_long play_fmt, rec_fmt ; + u_long mix_devs, mix_rec_devs, mix_recsrc; + u_short mix_levels[32]; + + u_long interrupts; + void *device_data; +\end{verbatim} +The structure provides all generic fields for audio support, plus a +couple of board-specific fields, {\tt bd\_id} and {\tt bd\_flags}. The +former should be used to distinguish different versions of the same +device (e.g. major/minor id in case of the SoundBlaster, codec type +for MSS-compatible boards, etc.), the latter can be used to hold +board-specific flags. Should these info not be sufficient, room is +provided for a pointer to device-specific data so that the structure +can be extended at will. + +Note that the descriptor has room for several base addresses. One of +this ({\tt io\_base}) is derived from the the kernel config line, and +corresponds to the base address for codec access. The others can be +derived using various mechanisms, e.g. hardwired info (some boards are +known to have a given subsystem at a given address), additional lines +in the config file, or PnP autoconfiguration info. These addresses are +used to access the subsystems, and to check for conflicts during the +configuration process. + +Of particular importance is the {\tt flags} fields, which holds many +flags used to determine the status of the device. Among them: +\begin{verbatim} +#define SND_F_BUSY 0x0001 /* has been opened (for write) */ +#define SND_F_BUSY_AUDIO 0x10000000 +#define SND_F_BUSY_DSP 0x20000000 +#define SND_F_BUSY_DSP16 0x40000000 +\end{verbatim} +These flags indicate the busy status of the board. They are of +exclusive use of the device-specific {\tt open()} and {\tt close()} +routines. The intended purpose is to allow only one or more opens +on the device. For the way the kernel operates, when multiple +descriptors are open on the same device (e.g. because the process +{\tt fork()}'ed after opening the device), only the last {\tt +close()} will result in the invokation of the device-specific close. +But, since in the Voxware mode, a device is accessed using three +minor numbers (corresponding to {\tt /dev/dsp, /dev/audio, /dev/dspW}), +we have to keep track of opens issued on the different devices and +only call the close routines on the very last close. + +\begin{verbatim} +#define SND_F_READING 0x0004 /* have a pending read */ +#define SND_F_WRITING 0x0008 /* have a pending write */ +\end{verbatim} +Allowing a single open does not suffice to exclude the possibility +that a process forks and then multiple calls are issued concurrently. +These two flags are used in the generic read/write routines to +prevent concurrent calls of the same type, and serialize calls of +different types for half-duplex devices. These two flags are +generally used in the generic read/write code, and there should be +no need to use them in the device-specific code, unless the read/write +routines are reimplemented. + +\begin{verbatim} +#define SND_F_CLOSING 0x0040 /* a pending close */ +#define SND_F_ABORTING 0x1000 /* a pending abort on write */ +\end{verbatim} +These flags mark special conditions, which should be known to the +DMA routines to shut down the device properly. The {\tt CLOSING} flags +are generally set by a call to {\tt close()} so that DMA transfers are +shut down at the end of the current operation. The {\tt ABORTING} +flags are set in response to user breaks or special {\tt ioctl}'s +such as {\tt PAUSE}, and cause immediate suspension of the output. + +\begin{verbatim} +#define SND_F_STEREO 0x0100 +#define SND_F_XLAT8 0x0400 /* u-law <--> 8-bit unsigned */ +#define SND_F_XLAT16 0x0800 /* u-law <--> 16-bit signed */ +\end{verbatim} +These are generic flags, marking the use of stereo audio and the +need for special translations in order to support {\tt /dev/audio} +on boards which do not have native $\mu$LAW support. In practice, +the latter flags are only used for the SoundBlaster; furthermore, +conversion between $\mu$LAW and 16-bit signed is currently not +implemented (but required to implement full-duplex through {\tt +/dev/audio} on the SB16). + +\begin{verbatim} +#define SND_F_INIT 0x4000 /* changed parameters. need init */ +\end{verbatim} +Finally, this flag marks the need for board reinitialization at the +next convenient time. This is because some parameters (e.g. speed, +channels, etc.) might be changed while an I/O operation is in progress +(e.g. a previous write is being flushed), and the board can only be +reprogrammed at the end of a transfer. This flag helps in supporting +the functionality. In the generic {\tt ioctl} handler, as an example, +all calls which set a parameter update the descriptor, set the flag, +and then invoke the callback function to apply the change. If successful, +the flag will be cleared, otherwise it will stay set for the update to +take place at the next convenient time. + +\subsection{Autoconfiguration} + +The main component of the driver is the file {\tt sound.c} which +contains the autoconfiguration stuff and the main device routines. + +The configuration process occurs as follows (see the following +diagram). + +\begin{verbatim} +pcmprobe(dev) { + foreach (p in supported devices) + if (p->probe(dev) is successful) { + snddev_last_probed = p ; + return 1 ; + } + return 0 ; /* fail */ +} + +pcmattach(dev) { + initialize pcm_info[dev->id_unit] with data from the template, + from snddev_last_probed, and from dev; + call snddev_last_probed->attach(dev); +} + +xxx_probe(dev) { + try to identify the board, possibly updating dev; can use + pcm_info[dev->id_unit] as temporary storage (reset on exit); + return 1 on success, 0 on failure. +} + +xxx_attach(dev) { + initialize board and do the necessary allocation, complete the + info in pcm_info[dev->id_unit] (initialized from the template + and dev) +} +\end{verbatim} +The kernel invokes {\tt pcmprobe()} to detect a board, passing it +the parameters from the configuration line. The routine then scans +the set of supported devices to see if the device is compatible +with the configured unit, and in case invokes the probe routine, +which is passed a pointer to the {\tt isa\_device} structure +describing the device. + +Compatibility is checked using the following rules. If a device +type is specified, only that device is checked for. If the ``device +type'' refers to a class of devices (e.g. all SoundBlaster-like +boards, all MSS-like boards), then all known devices in that class +are probed. Finally, if no device type is specified, then all +devices are probed. Internally, a {\tt snddev\_info} structure is +defined for each known board. For each board class, an array holds +pointers to the descriptors of each board. Finally, an array holds +pointers to the arrays describing each board class. + +The probe routine is not supposed to do any resource allocation, +just to check for the presence of the board. The only allowed side +effect of the probe routine is to modify the content of the {\tt +isa\_device} structure, in case a generic parameter was passed, to +instantiate it to the correct value. If the board-specific probe +routine is successful, so is {\tt pcmprobe()}. This will result in +the invokation of {\tt pcmattach()}, with a pointer to the same +{\tt isa\_device} structure that was used at probe time. + +{\tt pcmattach()} will now copy the {\tt snddev\_info} structure +describing the successfully-probed device into a permanent device +descriptor, fill it up with run-time information, and call the +board-specific attach routine. All the above is only possible +because {\tt pcmprobe()} saves a pointer ({\tt last\_probed}) to +the {\tt snddev\_info} corresponding to the successfully probed +device before returning. + +The board-specific attach routines has the task of configuring the +boards using the supplied parameters. The only parameter passed +to the attach routine is the {\tt isa\_device} pointer. This +structure includes the unit number, so the board-specific can +identify the permanent device descriptor as {\tt pcm\_info[unit]} +(use {\tt midi\_info}, {\tt synth\_info} for other devices). + +Although the return type is {\tt int}, the return value from the +board-specific attach routine is not checked, similarly to what is +done for the generic ISA attach routines. The only action that the +kernel does on a successful attach is to register an interrupt for +the device. As a consequence, a failed attach can be marked by +setting {\tt dev->id\_irq = 0 ;} which has the consequence of not +registering an interrupt for the device. + +\subsection{Main device routines} + +Upon a successful attach, {\tt pcmattach()} registers a {\tt cdevsw} +entry for the audio device. All audio devices share the same routines, +{\tt sndopen, sndclose, sndread, sndwrite, sndioctl, sndselect}, +which perform some generic actions and then invoke the board-specific +routines, if available. The board-specific routines, as well as +the probe and attach routines, have the same interface of a BSD +device driver. This was a design choice meant to simplify development: +one can, in principle, develop a driver outside the audio driver, +and then move it under the audio driver with minimal effort. +Additionally, this approach simplifies life to a kernel developer +since there are no new interfaces to be learned. + +\subsubsection{{\tt sndopen()} and {\tt sndclose()} } + +The generic open and close routines do very little, they basically +check that the unit and device number correspond to a supported +device, and then invoke the (required) open/close routines for +the specific board. There is little more the generic routines can +do, since board vary widely and the open and close actions are very +board-specific. + +\subsubsection{{\tt sndread()} and {\tt sndwrite()} } + +On the contrary, the generic read and write actions are very similar +across all boards. In all cases, checks must be done for valid unit +and device numbers. Then, concurrent operations of the same type +must be denied. Finally, operations of different types on half-duplex +devices must be properly sequenced. After these checks have been +done, the generic read/write code requires to move data between the +user buffer and the DMA buffer, in blocks of appropriate size. + +\subsection{{\tt sndioctl()}} + +The audio driver uses a very large number of {\tt ioctl}'s. Many +of these simply require to read, or set, parameters in the device +descriptor, so they can be implemented by the generic routine for +all boards. Other ioctl calls, instead, require board-specific +actions. We have devised a mechanism to allow board-specific ioctl +to handle the desired set of ioctl calls, leaving the generic +routine the task to deal with other, unhandled, calls. + +The generic routine, after checking device and unit numbers, passes +control to the board-specific routine, if existing. This can do its +own processing and return the error status. If the return value is +{\tt ENOSYS} (error number 78, ``Function not implemented''), then +the generic ioctl {\tt switch} statement is invoked which performs +the generic actions. + +The generic routine tries to implement as much as possible of the +sound calls, leaving little processing to the user code. In +particular, it is assumed that all calls which just read the status +of the device can fetch it directly from the audio descriptor. +Updates to the parameters are done in the generic ioctl by setting +the parameter in the descriptor (when possible, by checking that +values are acceptable), setting the {\tt SND\_F\_INIT} flag and +calling the device-specific {\tt callback()} function, if available. +This in turn will try to perform the necessary action or, if not +possible, leave the flag set for later operation. + + +\subsection{{\tt sndselect()}} + +A properly working select routine is fundamental for a audio device, +because often an application has to handle multiple data streams. +While it is true that an application can determine how long +a read or write call will take to complete, knowing buffer sizes and +sample rates, it is certainly more convenient and efficient to have a +working {\tt select()}. The generic select routine should cover all +needs for devices using DMA. The body of the routine is very +standard. The only noticeable thing is that, when the user specified a +preferred block size through one of the available {\tt ioctl()} calls, +then {\tt select()} will return only when at least one whole block +can be transferred. In other cases (default) one byte will suffice to +make {\tt select()} return (although the system will still choose a blocksize +for DMA transfers, corresponding to 0.25~s of data). + +\subsection{{\tt /dev/sndstat}} + +In the Voxware driver, a special, readonly, status device ({\tt +/dev/sndstat}) was defined (minor number 6) which returned information +on the audio system, including supported devices and configured +devices. In our code, {\tt /dev/sndstat} is supported directly in +the generic routines (in file {\tt sound.c}) and is defined for +every unit returning the same information. In particular, the +status device returns data from a statically allocated 4~KB buffer +({\tt status\_buf}) which is filled-up at the first open with the +function {\tt init\_status()}. + +\section{DMA support} + +DMA handling routines are an important module of the audio code. +All the DMA-related code is in file {\tt dmabuf.c} and is completely +new for this release. + +At the time of this writing, DMA support does not use AUTOMODE. + +Using DMA mode, the sound card requests the transfer of data by +issuing DMA requests to the ISA DMA controller, which in turn +satisfies the request. The ISA DMA controller can be programmed with a +count, so that a signal (TC) is generated after the required amount of data +has been transferred. In ``AUTO'' mode, the ISA DMA controller +reinitializes itself at the end of the transfer. Otherwise, requests +are not served anymore until the controller is reprogrammed. + +In principle, the codec could just forward the TC signal to the +appropriate interrupt line when enabled for DMA transfers. However, +many codec usually have a DMA count register themselves, and generate +an interrupt after the programmed number of bytes is transferred, +in many cases without looking at the value of TC. This makes it +possible to program the ISA DMA controller to use a large buffer, +while letting the codec generate interrupts on smaller (and perhaps +variable size) blocks. + +There are several problems to be dealt with by the DMA code in the +audio driver. First, and most important, we need to avoid overruns +or underruns in transferring data from/to the audio board. This is +more important for (old) boards which do not have a properly sized +on-board FIFO. The second problem is to minimize the latency in +all i/o functions, something which is especially important for +full-duplex applications. + +In the Voxware code, the DMA routines used a buffer partitioned into +a number of fixed-size fragments. The programmer should use ioctls to +select the fragment size which was best suited to his needs, +generally in terms of latency. + +In our code, we use a completely different approach. The DMA buffer +is a single memory block divided into two, variable-size, areas: +{\em READY} and {\em FREE}. Each area is identified by an +offset into the buffer and a length. The data structure describing a +DMA buffer is the following: +\begin{verbatim} +typedef struct _snd_dbuf { + char *buf; /* pointer to the data buffer */ + int bufsize ; /* total buffer size */ + volatile int rp, fp; /* pointers to the ready and free area */ + volatile int dl; /* transfer size */ + volatile int rl, fl; /* length of ready and free areas. */ + int int_count; /* how many interrupts on this channel */ + int chan; /* dma channel */ + int sample_size ; /* 1, 2, 4 */ + struct selinfo sel; /* support for select */ + u_long total; /* total bytes processed */ + u_long prev_total; /* copy of the above when GETxPTR called */ +} snd_dbuf ; +\end{verbatim} +The READY area contains +data ready to be transferred to the codec or to the user, depending +on the direction of the transfer. The FREE area is empty and +available for use. Both READY and FREE can wrap through the end of +the buffer. When the dma engine is in use, it transfers {\tt dl} bytes +from the beginning of the READY area (play) or to the beginning of the +FREE area (record). + +The status of a dma transfer can in many cases be detected from the +value of the length components of the structures. If {\tt dl == 0}, +then the DMA engine is not active. If {\em fl == 0}, a user write will +be blocking or a new DMA read cannot be started. +If {\em rl == 0}, a user read will be blocking, or a new DMA write +cannot be started. A idle dma descriptor has {\tt dl = rl = 0, fl += bufsize}. + +\subsection{Handling block sizes} + +The purpose of the subdivision of buffers in three areas is that we +can have one pending DMA operation using the DMA area, some space for +the next DMA operation (the READY area for a write, the FREE area for +a READ), and a pending read or write on the third area. + +Obviously we should avoid each of these areas to become too large and +eat all the available buffer space, or too small and make operations +inefficient. We will analize the details in the following two +sections. + +\subsection{DMA write} + +In write operations, the boundary between READY and FREE is advanced +to make room for user data. When possible, a DMA operation is +started by advancing the boundary between DMA and READY by the +amount of data which must be transferred. At the end of the DMA +transfer, the DMA area is shrunk to zero length by extending the +FREE area. These actions occur in the user write routine {\tt +dsp\_write\_body()}, and in the write interrupt service routine +{\tt dsp\_wrintr()}. + +By using a straightforward implementation, the time required for the +user write routine to make data available to the DMA engine would be +proportional to the block size used in the write operation. This is +mainly because of the use of the {\tt uiomove} function in the +routine, possibly followed by a format conversion for translating +between $\mu$-LAW and the native format supported by the codec. +In order to minimize this latency, we do the transfer in blocks of +increasing size, doubling it at each pass. This gives us very low +latency, while at the same time enables the use of large blocks when +needed, without requesting the user to specify a block size. + + +\subsection{DMA read} + +In read operations, a DMA operation needs to be started first, by +advancing the boundary between DMA and FREE by the amount of required +data. When the DMA transfer is complete, the boundary between READY +and DMA is advanced. User reads can be served by removing data from +the READY area and advancing the boundary between FREE and READY +accordingly. These actions occur in the user read routine {\tt +dsp\_read\_body()} and in the read interrupt service routine {\tt +dsp\_rdintr()}. + +Even for DMA read, we move data to user space in blocks of increasing +size, so as to minimize latency in freeing space in the buffer. +However, implementing the read poses another difficulty in deciding +when to return data to a requesting application. Typically, +a read request should return when the requested number of bytes is +available. If data are already available, we can simply copy them +and return. If no data is available and the DMA is inactive, we +can start it with the requested amount of data, and then wait for +the request to complete. However, we are in trouble if the read +request arrives when a DMA operation is already scheduled (a normal +situation) and the transfer length is (possibly much) larger than +the requested amount of data. In this case, the interrupt will only +arrive at the end of the operation, which might take a long time. +This is not a problem with writes, since data are already buffered +for the whole operation. + +\subsection{Why this does not always work...} + +Using single DMA mode avoids that, because of slow interrupt +response, samples are played beyond the end of the buffer, or +capture overwrites some old, still unread, data at the other end +of the buffer. +Single DMA mode has the problem of requiring fast interrupt +response to ensure a smooth data transfer. If the system is not quick +to reinitialize the DMA controller, some samples might be missed. To +mitigate the problem, some controllers provide an internal FIFO which +increases the allowable interrupt response time. As an example, a +codec with 16-samples FIFO at 8~KHz gives 2~ms to reinitialize the +codec itself, which is a reasonable time. +This of course assumes that +the codec makes good use of the FIFO e.g. in the play queue generates +an interrupt when the count goes to 0 even when the FIFO is full. Some +don't. + +Another problem of using single DMA is that the same count is +programmed into the two devices. It appears that some codecs when +working in full duplex have the bad habit of forgetting to count some +cycles, resulting in interrupts not being generated. + +So, to sum up, single DMA mode works very well with well-behaved +codecs and fast systems. When the system becomes slow, late interrupt +response might cause ``clicks'' in the output, or missing samples in +the input. When the codec is buggy, there might be deadlocks because +of missing interrupts (we have only seen these in full duplex on some +cards). + +\subsection{Auto-mode DMA} + +Auto-mode DMA refers to a special operating mode of the DMA engine +which does not require the CPU to reinitialize the channel at the +end of a transfer. Auto DMA can be enabled independently in the +ISA DMA controller and in the sound card. The advantage of auto +mode is that data can be transferred continuously and do not require +the interrupt service routine to have a low latency (if AUTO DMA +is used on both the ISA DMA controller and in the sound card). + +Enabling AUTO DMA in the ISA DMA controller only saves the small time +needed to reinitialize the DMA controller, and can help preventing the +deadlock with broken codecs, since the ISA DMA controller will always +be available to serve DMA requests. A drawback of this approach is +that the codec might try to transfer data beyond the allowed space. + +Enabling AUTO DMA in the codec only does not make any sense. + +To overcome the problem of DMA transfers going beyond the allowed +space, the following strategy can be used. For playback, we must +insure that the region beyond the end of valid data contains data +which do not produce audible effects. As an example, it can be +initialized with replicas of the last sample (1, 2 or 4 bytes +depending on the operating mode). The amount of space to be +initialized in this way should correspond to the expected maximum +interrupt latency. + +For record, the whole buffer should not be used to acquire data, so +that any overrun will just fill a guard region in the buffer, and not +overwrite old data. Again the size of the guard region should be +computed depending on the expected maximum interrupt latency. + +\section{Board-specific routines} + +All board-specific routines are contained in one or more board-specific +source files, and possibly some include files. The board-specific +file(s) should provide as a minimum a template {\tt snddev\_info} +describing the board, and the various functions referenced in the +template and not defined elsewhere. This generally includes the +probe, attach, open, close, the callback, and the interrupt +dispatcher. + +In order to make board-specific files easily readable, we suggest +to use a standard structure for them. As a reference, one can +look at file {\tt sb\_dsp.c} which contains the driver for the +SoundBlaster board. + +The standard structure begins with the copyright. Then the body of the +file is enclosed in the following lines: +\begin{verbatim} +#include <i386/isa/snd/sound.h> +#if NPCM > 0 +... body of the file +#endif /* NPCM > 0 */ +\end{verbatim} +where the {\tt "sound.h"} contains all the generic include files and +definitions for kernel audio modules. It also includes the +config-generated file {\tt "snd.h"} which defines the number of +statically-configured audio devices, NPCM (note that one is required +but often suffices, since PnP devices will use additional unit numbers +up to the maximum of {\tt NPCM\_MAX} which is currently defined as 8. +The total number of audio devices, which can be used and should be +incremented when PnP devices are detected, is held in the global +variable {\tt u\_long nsnd} which is initialized with NPCM. + +Next come the {\tt \#include} for all board-specific include files, +followed by the prototype declaration for all static functions which go +into the {\tt snddev\_info}, and then prototypes for all static +functions in this module. We encourage to make functions static if not +used by other modules, to reduce pollution of the name space. + +After function prototypes, put the (initialized) descriptors for the +board(s) supported by this file, e.g. +\begin{verbatim} +snddev_info sb_op_desc = { + "SoundBlaster", SNDCARD_SB, + sb_probe, sb_attach, + sb_open, sb_close, NULL /* rd */, NULL /* wr */, + sb_dsp_ioctl, sndselect, sbintr, sb_callback, + DSP_BUFFSIZE, + AFMT_U8, +} +\end{verbatim} + +\subsection{{\tt xxx\_probe()}} + +The board-specific probe routine is passed a pointer to a {\tt struct +isa\_device} which contains all information collected from the {\tt +device pcmX} line in the kernel configuration file. The probe code +should do the minimum action to detect the board type and see if +parameters are compatible with the board's features. It returns 1 in +case of success, 0 in case of failure. + +The probe code is +not supposed to do any allocation. If some temporary storage is +necessary, the probe code can use {\tt pcm\_info[dev->id\_unit]}, +which is available, although not initialized at this stage. This +is convenient since, often, the probe and attach routine share the +same code for determining the board type, and can store the detected +information right there. Note however that {\tt +pcm\_info[dev->id\_unit]} will be reinitialized at attach time, so that +data structure should not be regarded as persistent information, but +only as a convenient storage area. + +\subsection{{\tt xxx\_attach()}} + +Same as the probe routine, the attach routine is passed a pointer +to a {\tt struct isa\_device} with info from the configuration +file. This time, however, the generic attach routine will also +initialize {\tt pcm\_info[dev->id\_unit]} copying the template +corresponding to the device identified by the probe routine; +moreover, values from the kernel config line are also copied in +the correct places (in particular, this involves the various fields +which depend on the value of {\tt flags}. + +\subsection{Interrupt dispatcher} + +All audio devices register the same interrupt service routine ({\em +ISR}), {\tt pcmintr}. This merely calls the ISR which is specified +in the device descriptor, passing it the unit number for which the +interrupt has arrived (in practice, one is not aware of the presence +of {\tt pcmintr}, which could also disappear in the future, and +should write the ISR routine as a standard ISR for BSD systems. + +The ISR mainly has to find out the interrupt reason (accessing {\tt +pcm\_info[unit]} to collect board specific parameters) and perform +whatever action is necessary. Usually, it is only necessary to call +{\tt dsp\_wrintr()} or {\tt dsp\_rdintr()}, and possibly clear the +interrupt flag in the codec. + +\subsection{Callback routine} + +The main interface between the generic audio code and the device +specific code is the callback routine which is specified in the device +descriptor. The callback routine is passed an {\tt int} argument which +describes the reason of the callback. The argument is made of a mode +(read and/or write) and a reason. Typically the callback is invoked +from the dma code, upon user writes or interrupt routines. The code in +the callback routine should typically perform codec-specific actions +to start/stop a dma transfer in the desired direction. +As an extension, the callback function can also be called by the +generic routines at open and close times. This only happens if +device-specific open and close are not implemented, and the +functionalities are implemented here. + +Again this is done for convenience, since in many cases the open and +close routines share a common part (e.g. to check for device busy +conditions, etc.) which this way need not to be replicated in +different drivers. + +\subsection{Mixer} + +Sound cards generally have a mixer device, which is in charge of +controlling signal flow, attenuation and filtering on the various +paths. We mutuate the Voxware scheme in this driver, both because it +is reasonably flexible, and for compatibility with existing +applications. In this scheme, each of the possible mixer channels is +associated to a bit in a bitmask. + +Setting levels for a mixer channel generally just requires to write a +value (associated to the level) in some location. Since the number of +bits used by various mixer devices largely differs from board to +board, the Voxware driver uses a description table to map channels +into the appropriate addresses. A table is made of an array of {\tt +mixer\_ent} entries, which are set with the {\tt PMIX\_ENT} and +{\tt MIX\_ENT} macros (all defined in {\tt sound.h}): +entries: + +\begin{verbatim} +struct mixer_def { + u_int regno:7; + u_int polarity:1; + u_int bitoffs:4; + u_int nbits:4; +}; +typedef struct mixer_def mixer_ent; +typedef struct mixer_def mixer_tab[32][2]; + +#define MIX_ENT(name, reg_l, pol_l, pos_l, len_l, reg_r, pol_r, pos_r, len_r) \ + {{reg_l, pol_l, pos_l, len_l}, {reg_r, pol_r, pos_r, len_r}} +#define PMIX_ENT(name, reg_l, pos_l, len_l, reg_r, pos_r, len_r) \ + {{reg_l, 0, pos_l, len_l}, {reg_r, 0, pos_r, len_r}} + +void +change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval); +\end{verbatim} +The table is used by {\tt change\_bits()}, which accepts a pointer +to a table, a pointer to the old value of the register, device +(0..31), channel (left or right), and the new value to be set, in +the range 0..100. + +\end{document} diff --git a/sys/i386/isa/snd/misc/README b/sys/i386/isa/snd/misc/README new file mode 100644 index 0000000..e50b041 --- /dev/null +++ b/sys/i386/isa/snd/misc/README @@ -0,0 +1,82 @@ +Various support files for the new sound driver for FreeBSD. + + audio-voxware.cc + + this is the driver for "vat" (tested with vat 4.0b2 in the + ports distribution) to make things work in full duplex. It + does _not_ use non-blocking i/o, otherwise vat will happily + eat most of your CPU time since select will often return true + even if less than one frame is ready. + + The environment variable AUDIODEV will let you use different + sound cards if you have more than one. It can contain the unit + number (0, 1, 2, ...) or the full pathname. + + As an additional bonus, you can select a fourth input which is + a ulaw file which is written to the network instead of the + input audio data. + + CPU usage on my p5/133 in full duplex, local feedback and + silence suppression (most demanding configuration I believe): + PCM 3.5% + DVI 4.8% + LPC 11 % + GSM 23 % + + auvoxware.c + + this is the driver for "nas" (tested with nas 1.2b5). Works + fine in full duplex. + + linux_a.c + + driver for timidity, a midi-to-pcm converter. Using this program + (and if you have spare cpu cycles) you can play midi files to + /dev/audio without the need for a synthesis device. + + linux.patch + + this is a patch for 2.2.X users for the linux emulator. The + files to correct are in /sys/i386/linux, and this patch + implements a few ioctl which are used by the realvideo player + for linux (rvplayer). In order to use these patches you have + to recompile the linux_mod.o and install it in place as follows + + cd /usr/src/lkm/linux + make + mv linux_mod.o /lkm/linux_mod.o + + pcmio.c + + a simple program to do i/o with the audio device. You can set + on the command line the device, the speed, data format, stereo + or mono... And if you use a bidirectional device, you can play + and record at the same time. + + E.g. + + pcmio -f 1 +rec,stereo,44100,s16 song + + will record from /dev/audio1 stereo,16-bit data to the file "song" + + pcmio +play,stereo,44100,s16,loop 50s:20s song + + will continuosly play 50s from "song" (skipping the first 20s) + to /dev/audio + + pcmio +rec,ulaw - | tee tapefile > /dev/audio + + will record using ulaw (mono at 8KHz, default) to stdout, and + the pipe will both dump things to "tapefile" and to /dev/audio + to listen what you are recording (you could do the same with + "cat..."). + + soundbyte.c + + the audio module for speak_freely + + test.c + + lets you monitor the status of the device (/dev/audio1) by + mmapping the descriptor and dumping to screen the interesting + fields. diff --git a/sys/i386/isa/snd/misc/audio-voxware.cc b/sys/i386/isa/snd/misc/audio-voxware.cc new file mode 100644 index 0000000..16feb46 --- /dev/null +++ b/sys/i386/isa/snd/misc/audio-voxware.cc @@ -0,0 +1,435 @@ +/* + * Copyright (c) 1991-1993 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +/* + * Full Duplex audio module for the new sound driver and full duplex + * cards. Luigi Rizzo, from original sources supplied by Amancio Hasty. + * + * This includes some enhancements: + * - the audio device to use can be in the AUDIODEV env. variable. + * It can be either a unit number or a full pathname; + * - use whatever format is available from the card (included split + * format e.g. for the sb16); + * - limit the maximum size of the playout queue to approx 4 frames; + * this is necessary if the write channel is slower than expected; + * the fix is based on two new ioctls, AIOGCAP and AIONWRITE, + * but the code should compile with the old driver as well. + */ + +#include <osfcn.h> +#include <machine/soundcard.h> +#include "audio.h" +#include "mulaw.h" +#include "Tcl.h" + +#define ULAW_ZERO 0x7f + +extern const u_char lintomulawX[]; + +class VoxWare : public Audio { + public: + VoxWare(); + virtual int FrameReady(); + virtual u_char* Read(); + virtual void Write(u_char *); + virtual void SetRGain(int); + virtual void SetPGain(int); + virtual void OutputPort(int); + virtual void InputPort(int); + virtual void Obtain(); + virtual void Release(); + virtual void RMute(); + virtual void RUnmute(); + virtual int HalfDuplex() const; + protected: + int ext_fd; /* source for external file */ + + u_char* readbuf; + u_short *s16_buf; + + int play_fmt ; +#if defined(AIOGCAP) /* new sound driver */ + int rec_fmt ; /* the sb16 has split format... */ + snd_capabilities soundcaps; +#endif +}; + +static class VoxWareMatcher : public Matcher { +public: + VoxWareMatcher() : Matcher("audio") {} + TclObject* match(const char* fmt) { + if (strcmp(fmt, "voxware") == 0) + return (new VoxWare); + return (0); + } +} linux_audio_matcher; + +VoxWare::VoxWare() +{ + readbuf = new u_char[blksize]; + s16_buf = new u_short[blksize]; + + memset(readbuf, ULAW_ZERO, blksize); + + ext_fd = -1 ; /* no external audio */ + iports = 4; /* number of input ports */ +} + +void +VoxWare::Obtain() +{ + char *thedev; + char buf[64]; + int d = -1; + + if (HaveAudio()) + abort(); + thedev=getenv("AUDIODEV"); + if (thedev==NULL) + thedev="/dev/audio"; + else if ( thedev[0] >= '0' && thedev[0] <= '9' ) { + d = atoi(thedev); + sprintf(buf,"/dev/audio%d", d); + thedev = buf ; + } + fd = open(thedev, O_RDWR ); + thedev=getenv("MIXERDEV"); + if (thedev == NULL) + if (d < 0) + thedev = "/dev/mixer"; + else { + sprintf(buf,"/dev/mixer%d", d); + thedev = buf ; + } + + if (fd >= 0) { + int i = -1 ; +#if defined(AIOGCAP) /* new sound driver */ + i = ioctl(fd, AIOGCAP, &soundcaps); + if (i == 0) { + snd_chan_param pa; + struct snd_size sz; + + pa.play_rate = pa.rec_rate = 8000 ; + pa.play_format = pa.rec_format = AFMT_MU_LAW ; + switch (soundcaps.formats & (AFMT_FULLDUPLEX | AFMT_WEIRD)) { + case AFMT_FULLDUPLEX : + /* + * this entry for cards with decent full duplex. Use s16 + * preferably (some are broken in ulaw) or ulaw or u8 otherwise. + */ + if (soundcaps.formats & AFMT_S16_LE) + pa.play_format = pa.rec_format = AFMT_S16_LE ; + else if (soundcaps.formats & AFMT_MU_LAW) + pa.play_format = pa.rec_format = AFMT_MU_LAW ; + else if (soundcaps.formats & AFMT_U8) + pa.play_format = pa.rec_format = AFMT_U8 ; + else { + printf("sorry, no supported formats\n"); + close(fd); + fd = -1 ; + return; + } + break ; + case AFMT_FULLDUPLEX | AFMT_WEIRD : + /* this is the sb16... */ + if (soundcaps.formats & AFMT_S16_LE) { + pa.play_format = AFMT_U8 ; + pa.rec_format = AFMT_S16_LE; + } else { + printf("sorry, no supported formats\n"); + close(fd); + fd = -1 ; + return; + } + break ; + default : + printf("sorry don't know how to deal with this card\n"); + close (fd); + fd = -1; + break; + } + ioctl(fd, AIOSFMT, &pa); + play_fmt = pa.play_format ; + rec_fmt = pa.rec_format ; + sz.play_size = (play_fmt == AFMT_S16_LE) ? 2*blksize : blksize; + sz.rec_size = (rec_fmt == AFMT_S16_LE) ? 2*blksize : blksize; + ioctl(fd, AIOSSIZE, &sz); + } else +#endif + { /* setup code for old voxware driver */ + } + Audio::Obtain(); + } +} + +/* + * note: HalfDuplex() uses a modified function of the new driver, + * which will return AFMT_FULLDUPLEX set in SNDCTL_DSP_GETFMTS + * for full-duplex devices. In the old driver this was 0 so + * the default is to use half-duplex for them. Note also that I have + * not tested half-duplex operation. + */ +int +VoxWare::HalfDuplex() const +{ + int i; + ioctl(fd, SNDCTL_DSP_GETFMTS, &i); +#if 0 + printf("SNDCTL_DSP_GETFMTS returns 0x%08x %s duplex\n", + i, i & AFMT_FULLDUPLEX ? "full":"half"); +#endif + return (i & AFMT_FULLDUPLEX) ? 0 : 1 ; +} + +void VoxWare::Release() +{ + if (HaveAudio()) { + Audio::Release(); + } +} + +void VoxWare::Write(u_char *cp) +{ + int i = blksize, l; + static int peak = 0; + + if (play_fmt == AFMT_S16_LE) { + for (i=0; i< blksize; i++) + s16_buf[i] = mulawtolin[cp[i]] ; + cp = (u_char *)s16_buf; + i = 2 *blksize ; + } + else if (play_fmt == AFMT_S8) { + for (i=0; i< blksize; i++) { + int x = mulawtolin[cp[i]] ; + x = (x >> 8 ) & 0xff; + cp[i] = (u_char)x ; + } + i = blksize ; + } else if (play_fmt == AFMT_U8) { + /* + * when translating to 8-bit formats, need to implement AGC + * to avoid loss of resolution in the conversion. + * The peak is multiplied by 2^13 + */ + for (i=0; i< blksize; i++) { + int x = mulawtolin[cp[i]] ; +#if 0 /* AGC -- still not complete... */ + if (x < 0) x = -x ; + if (x > peak) peak = ( peak*16 + x - peak ) / 16 ; + else peak = ( peak*8192 + x - peak ) / 8192 ; + if (peak < 128) peak = 128 ; + /* at this point peak is in the range 128..32k + * samples can be scaled and clipped consequently. + */ + x = x * 32768/peak ; + if (x > 32767) x = 32767; + else if (x < -32768) x = -32768; +#endif + x = (x >> 8 ) & 0xff; + x = (x ^ 0x80) & 0xff ; + cp[i] = (u_char)x ; + } + i = blksize ; + } +#if 0 && defined(AIOGCAP) + int queued; + ioctl(fd, AIONWRITE, &queued); + queued = soundcaps.bufsize - queued ; + if (play_fmt == AFMT_S16_LE) { + if (queued > 8*blksize) + i -= 8 ; + } else { + if (queued > 4*blksize) + i -= 4 ; + } +#endif + for ( ; i > 0 ; i -= l) { + l = write(fd, cp, i); + cp += l; + } +} + +u_char* VoxWare::Read() +{ + u_char* cp; + int l=0, l0 = blksize, i = blksize; + static int smean = 0 ; /* smoothed mean to remove DC */ + static int loops = 20 ; + + cp = readbuf; + + if (rec_fmt == AFMT_S16_LE) { + cp = (u_char *)s16_buf; + l0 = i = 2 *blksize ; + } + for ( ; i > 0 ; i -= l ) { + l = read(fd, cp, i); + cp += l ; + } + if (rec_fmt == AFMT_S16_LE) { + for (i=0; i< blksize; i++) { +#if 1 /* remove DC component... */ + int mean = smean >> 13; + int dif = ((short) s16_buf[i]) - mean; + smean += dif ; + readbuf[i] = lintomulawX[ dif & 0x1ffff ] ; +#else + readbuf[i] = lintomulaw[ s16_buf[i] ] ; +#endif + } + } + else if (rec_fmt == AFMT_S8) { + for (i=0; i< blksize; i++) + readbuf[i] = lintomulaw[ readbuf[i]<<8 ] ; + } + else if (rec_fmt == AFMT_U8) { + for (i=0; i< blksize; i++) + readbuf[i] = lintomulaw[ (readbuf[i]<<8) ^ 0x8000 ] ; + } + if (iport == 3) { + l = read(ext_fd, readbuf, blksize); + if (l < blksize) { + lseek(ext_fd, (off_t) 0, 0); + read(ext_fd, readbuf+l, blksize - l); + } + } + return readbuf; +} + +/* + * should check that I HaveAudio() before trying to set gain. + * + * In most mixer devices, there is only a master volume control on + * the capture channel, so the following code does not really work + * as expected. The only (partial) exception is the MIC line, where + * there is generally a 20dB boost which can be enabled or not + * depending on the type of device. + */ +void VoxWare::SetRGain(int level) +{ + double x = level; + level = (int) (x/2.56); + int foo = (level<<8) | level; + if (!HaveAudio()) + Obtain(); + switch (iport) { + case 2: + case 1: + break; + case 0: + if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_MIC), &foo) == -1) + printf("failed to set mic volume \n"); + break; + } + if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_IGAIN), &foo) == -1) + printf("failed set input line volume \n"); + rgain = level; +} + +void VoxWare::SetPGain(int level) +{ + float x = level; + level = (int) (x/2.56); + int foo = (level<<8) | level; + if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_PCM), &foo) == -1) { + printf("failed to output level %d \n", level); + } + pgain = level; +} + +void VoxWare::OutputPort(int p) +{ + oport = p; +} + +void VoxWare::InputPort(int p) +{ + int src = 0; + + if (ext_fd >=0 && p != 3) { + close(ext_fd); + ext_fd = -1 ; + } + + switch(p) { + case 3: + fprintf(stderr,"input from file %s\n", ext_fname); + if (ext_fd == -1) + ext_fd = open(ext_fname, 0); + if (ext_fd != -1) + lseek(ext_fd, (off_t) 0, 0); + break; + case 2: + src = 1 << SOUND_MIXER_LINE; + break; + case 1: /* cd ... */ + src = 1 << SOUND_MIXER_CD; + break; + case 0 : + src = 1 << SOUND_MIXER_MIC; + break; + } + if ( ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &src) == -1 ) { + printf("failed to select input \n"); + p = 0; + } + iport = p; +} + +void VoxWare::RMute() +{ + rmute |= 1; +} + +void VoxWare::RUnmute() +{ + rmute &=~ 1; +} + +/* + * FrameReady must return 0 every so often, or the system will keep + * processing mike data and not other events. + */ +int VoxWare::FrameReady() +{ + int i = 0; + int lim = blksize; + + ioctl(fd, FIONREAD, &i ); + if (rec_fmt == AFMT_S16_LE) lim = 2*blksize; + return (i >= lim) ? 1 : 0 ; +} +/*** end of file ***/ diff --git a/sys/i386/isa/snd/misc/auvoxware.c b/sys/i386/isa/snd/misc/auvoxware.c new file mode 100644 index 0000000..1ae9316 --- /dev/null +++ b/sys/i386/isa/snd/misc/auvoxware.c @@ -0,0 +1,1301 @@ +/* + SCCS: @(#) auvoxware.c 11.4 95/04/14 +*/ +/*------------------------------------------------------------------------- + +Copyright (C) 1995 The Santa Cruz Operation, Inc. +All Rights Reserved. + +Permission to use, copy, modify and distribute this software +for any purpose is hereby granted without fee, provided that the +above copyright notice and this notice appear in all copies +and that both the copyright notice and this notice appear in +supporting documentation. SCO makes no representations about +the suitability of this software for any purpose. It is provided +"AS IS" without express or implied warranty. + +SCO DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL SCO BE LIABLE FOR ANY SPECIAL, INDIRECT, +PUNITIVE, CONSEQUENTIAL OR INCIDENTAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, LOSS OF DATA OR LOSS OF +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +-------------------------------------------------------------------------*/ +/* + AUVoxConfig additions (sysseh@devetir.qld.gov.au) + 96-01-15 + Put the following keywords in - + minrate - Minimum sampling rate + maxrate - Maximum sampling rate + fragsize - The fragment size + minfrags - Minimum number of frags in queue + maxfrags - Maximum fragments in queue + wordsize - 8 or 16 bit samples + device - What device file to use + numchans - Mono (1) or stereo (2) + debug - Output messages during operation + verbose - Be chatty about config + inputsection - Next lot of specs are for input + outputsection - Next specs are for output + end - End an input or output section +*/ +/* + SCO Modification History: + S005, 24-Apr-95, shawnm@sco.com + base # of driver buffer fragments on data rate + S004, 12-Apr-95, shawnm@sco.com + finish integration of ausco.c, fix setitimer calls + S003, 28-Mar-95, shawnm@sco.com, sysseh@devetir.qld.gov.au + incorporate patch for stereo/mono mixing from Stephen Hocking + S002, 21-Mar-95, shawnm@sco.com + incorporate signal handling and audio block/unblock from ausco.c + S001, 21-Mar-95, shawnm@sco.com, sysseh@devetir.qld.gov.au + SYSSEH incorporate parts of patch from Stephen Hocking +*/ +/* + * Copyright 1993 Network Computing Devices, Inc. Copyright (C) Siemens + * Nixdorf Informationssysteme AG 1993 + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name Network Computing Devices, Inc. or + * Siemens Nixdorf Informationssysteme AG not be used in advertising or + * publicity pertaining to distribution of this software without specific, + * written prior permission. + * + * THIS SOFTWARE IS PROVIDED `AS-IS'. NETWORK COMPUTING DEVICES, INC. AND + * SIEMENS NIXDORF INFORMATIONSSYSTEME AG DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NONINFRINGEMENT. IN NO EVENT SHALL NETWORK COMPUTING DEVICES, INC. NOR + * SIEMENS NIXDORF INFORMATIONSSYSTEME AG BE LIABLE FOR ANY DAMAGES + * WHATSOEVER, INCLUDING SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, + * INCLUDING LOSS OF USE, DATA, OR PROFITS, EVEN IF ADVISED OF THE + * POSSIBILITY THEREOF, AND REGARDLESS OF WHETHER IN AN ACTION IN CONTRACT, + * TORT OR NEGLIGENCE, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * $NCDId: @(#)auvoxware.c,v 1.10 1996/04/24 17:04:19 greg Exp $ + * + * Copyright (C) Siemens Nixdorf Informationssysteme AG 1993 All rights reserved + */ + +/* + * Originally from the merge of auvoxware by Amancio Hasty (hasty@netcom.com) + * & auvoxsvr4 by Stephen Hocking (sysseh@devetir.qld.gov.au). + * 16bit fixes and Linux patches supplied by Christian + * Schlichtherle (s_schli@ira.uka.de). + * + * BUGS: + * - When the soundcard can do only 8 bit recording, "aurecord" records + * twice as long as it should. Is this our fault? + * + * TODO: + * - Adapt the buffer sizes to the current sampling rate, + * so that we can record/play high quality audio samples without + * swallows/pauses. + * Note that setting the buffer size to a fixed maximum will not work, + * because it causes playing at slow sample rate to pause. :-( + * I already tried to do this, but it seems that the rest of the server + * code doesn't recognize the changed buffer sizes. Any help in doing + * this is welcome! + * [chris] + * - Support a second input channel for stereo sampling, + * so that microphone sampling is done on the mono channel + * while line sampling is done on the stereo channel. + * [chris] + * + * CHANGELOG: + * - 94/7/2: + * Completely rewrote this file. Features: + * + Makes use of two sound cards if available. + * So you can concurrently record and play samples. + * + Tested to work with all combinations of 8/16 bit, mono/stereo + * sound card sampling modes. + * + Uses a stereo input channel if hardware supports this. + * + Can play stereo samples on mono sound cards (but who cares?). + * + Always uses the highest possible audio quality, i.e. 8/16 bit and + * mono/stereo parameters are fixed while only the sampling rate is + * variable. This reduces swallows and pauses to the (currently) + * unavoidable minimum while consuming a little bit more cpu time. + * + Format conversion stuff is pushed back to the rest of the server code. + * Only mono/stereo conversion is done here. + * + Debugging output uses indentation. + * [chris] + */ + +#include <stdio.h> +#include "config.h" + +FILE *yyin; + +static char *searchpath = CONFSEARCHPATH; + +#if defined(DEBUGDSPOUT) || defined(DEBUGDSPIN) +int dspin, dspout; +#endif + +# define PRMSG(x, a, b) \ + if (doDebug) { \ + fprintf(stderr, "auvoxware.c: %*s", debug_msg_indentation, ""); \ + fprintf(stderr, (x), (a), (b)); \ + fflush(stderr); \ + } +# define IDENTMSG (debug_msg_indentation += 2) +# define UNIDENTMSG (debug_msg_indentation -= 2) + +static int doDebug = 0; +int beVerbose = 0; +static int debug_msg_indentation = 0; + +#include <errno.h> +#include "dixstruct.h" /* for RESTYPE */ +#include "os.h" /* for xalloc/xfree and NULL */ +#include <fcntl.h> +#include <sys/time.h> +#include <sys/param.h> +#include <assert.h> + +#ifdef __FreeBSD__ +#include <machine/soundcard.h> +#include <machine/pcaudioio.h> +#else +#include <sys/soundcard.h> +#endif + +#include <audio/audio.h> +#include <audio/Aproto.h> +#include "au.h" + +#ifdef sco +static AuBool scoAudioBlocked = AuFalse; +#endif /* sco */ + +static AuBool processFlowEnabled; + +#define SERVER_CLIENT 0 + +#define MIN_MINIBUF_SAMPLES 256 +#define MAX_MINIBUF_SAMPLES 1024 /* Must be a power of 2 */ + +#define auDefaultInputGain AuFixedPointFromSum(50, 0) +#define auDefaultOutputGain AuFixedPointFromSum(50, 0) + +#define PhysicalOneTrackBufferSize \ + PAD4(auMinibufSamples * auNativeBytesPerSample * 1) +#define PhysicalTwoTrackBufferSize \ + PAD4(auMinibufSamples * auNativeBytesPerSample * 2) + +/* VOXware sound driver mixer control variables */ + +static int Letsplay; +static int level[100]; +static int mixerfd; /* The mixer device */ +static int devmask = 0; /* Bitmask for supported mixer devices */ +static int recsrc = 0; /* Currently selected recording sources */ +static int recmask = 0; /* Supported recording sources */ +static int stereodevs = 0; /* Channels supporting stereo */ + +static char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + +/* end of VOXware driver mixer control variables */ + +int ParseError = 0; /* Did we fail to parse conf. file? */ + +SndStat *confStat; + +SndStat sndStatIn = +{ + -1, + 16, + 2, + 0, + 4000, + 44100, + 256, + 3, + 32, + "/dev/dsp", + 0 +}, sndStatOut = +{ + -1, + 16, + 2, + 0, + 4000, + 44100, + 256, + 3, + 32, + "/dev/dsp", + 0 +}; + +static AuUint8 *auOutputMono, + *auOutputStereo, + *auInput; + +static ComponentPtr monoInputDevice, + stereoInputDevice, + monoOutputDevice, + stereoOutputDevice; + +extern AuInt32 auMinibufSamples; + + +#define auPhysicalOutputChangableMask AuCompDeviceGainMask + +#define auPhysicalOutputValueMask \ + (AuCompCommonAllMasks \ + | AuCompDeviceMinSampleRateMask \ + | AuCompDeviceMaxSampleRateMask \ + | AuCompDeviceGainMask \ + | AuCompDeviceLocationMask \ + | AuCompDeviceChildrenMask) + +#define auPhysicalInputChangableMask \ + (AuCompDeviceGainMask | AuCompDeviceLineModeMask) + +#define auPhysicalInputValueMask \ + (AuCompCommonAllMasks \ + | AuCompDeviceMinSampleRateMask \ + | AuCompDeviceMaxSampleRateMask \ + | AuCompDeviceLocationMask \ + | AuCompDeviceGainMask \ + | AuCompDeviceChildrenMask) + +static void setPhysicalOutputGain(); +static void setPhysicalInputGainAndLineMode(); + +void setDebugOn () +{ + doDebug = 1; +} + +void setVerboseOn () +{ + beVerbose = 1; +} + +#ifdef sco + +AuBlock +AuBlockAudio() +{ + scoAudioBlocked = AuTrue; + return 0; +} + +void +AuUnBlockAudio(AuBlock id) +{ + scoAudioBlocked = AuFalse; +} + +#endif /* sco */ + +static int createServerComponents(auServerDeviceListSize, + auServerBucketListSize, + auServerRadioListSize, + auServerMinRate, + auServerMaxRate) +AuUint32 *auServerDeviceListSize, + *auServerBucketListSize, + *auServerRadioListSize, + *auServerMinRate, + *auServerMaxRate; +{ + AuDeviceID stereo, mono; + ComponentPtr d, *p; + int i; + AuUint8 formatIn, + formatOut; + AuUint32 bytesPerSampleIn, + bytesPerSampleOut; + static AuBool initialized = AuFalse; + extern RESTYPE auComponentType; + extern ComponentPtr *auServerDevices, /* array of devices */ + *auServerBuckets, /* array of server owned buckets */ + *auServerRadios, /* array of server owned radios */ + auDevices, /* list of all devices */ + auBuckets, /* list of all buckets */ + auRadios; /* list of all radios */ + extern AuUint32 auNumServerDevices, /* number of devices */ + auNumActions, /* number of defined actions */ + auNumServerBuckets, /* number of server owned buckets */ + auNumServerRadios; /* number of server owned radios */ + + PRMSG("createServerComponents(...);\n", 0, 0); + IDENTMSG; + + *auServerMinRate = aumax(sndStatIn.minSampleRate, + sndStatOut.minSampleRate); + *auServerMaxRate = aumax(sndStatIn.maxSampleRate, + sndStatOut.maxSampleRate); + + auNumServerDevices = *auServerDeviceListSize + = *auServerBucketListSize + = *auServerRadioListSize + = 0; + + formatIn = (sndStatIn.wordSize == 16) ? AuFormatLinearSigned16LSB + : AuFormatLinearUnsigned8; + formatOut = (sndStatOut.wordSize == 16) ? AuFormatLinearSigned16LSB + : AuFormatLinearUnsigned8; + + bytesPerSampleIn = sndStatIn.wordSize / 8; + bytesPerSampleOut = sndStatOut.wordSize / 8; + + AU_ALLOC_DEVICE(d, 1, 0); + d->id = FakeClientID(SERVER_CLIENT); + d->changableMask = auPhysicalOutputChangableMask; + d->valueMask = auPhysicalOutputValueMask; + d->kind = AuComponentKindPhysicalOutput; + d->use = AuComponentUseExportMask; + d->access = AuAccessExportMask | AuAccessListMask; + d->format = formatOut; + d->numTracks = 1; + d->description.type = AuStringLatin1; + d->description.string = "Mono Channel Output"; + d->description.len = strlen(d->description.string); + d->minSampleRate = sndStatOut.minSampleRate; + d->maxSampleRate = sndStatOut.maxSampleRate; + d->location = AuDeviceLocationCenterMask | AuDeviceLocationInternalMask; + d->numChildren = 0; + d->minibuf = auOutputMono; + d->minibufSize = d->numTracks * bytesPerSampleOut * auMinibufSamples; + d->physicalDeviceMask = PhysicalOutputMono; + AU_ADD_DEVICE(d); + + monoOutputDevice = d; + + AU_ALLOC_DEVICE(d, 2, 1); + d->id = FakeClientID(SERVER_CLIENT); + d->changableMask = auPhysicalOutputChangableMask; + d->valueMask = auPhysicalOutputValueMask; + d->kind = AuComponentKindPhysicalOutput; + d->use = AuComponentUseExportMask; + d->access = AuAccessExportMask | AuAccessListMask; + d->format = formatOut; + d->numTracks = 2; + d->description.type = AuStringLatin1; + d->description.string = "Stereo Channel Output"; + d->description.len = strlen(d->description.string); + d->minSampleRate = sndStatOut.minSampleRate; + d->maxSampleRate = sndStatOut.maxSampleRate; + d->location = AuDeviceLocationCenterMask | AuDeviceLocationInternalMask; + d->numChildren = 1; + d->children = (AuID *) ((AuUint8 *) d + PAD4(sizeof(ComponentRec))); + d->childSwap = (char *) (d->children + d->numChildren); + d->children[0] = monoOutputDevice->id; + d->minibuf = auOutputStereo; + d->minibufSize = d->numTracks * bytesPerSampleOut * auMinibufSamples; + d->physicalDeviceMask = PhysicalOutputStereo; + AU_ADD_DEVICE(d); + + stereoOutputDevice = d; + + AU_ALLOC_DEVICE(d, (sndStatIn.isStereo + 1), 0); + d->id = FakeClientID(SERVER_CLIENT); + d->changableMask = auPhysicalInputChangableMask; + d->valueMask = auPhysicalInputValueMask; + d->kind = AuComponentKindPhysicalInput; + d->use = AuComponentUseImportMask; + d->access = AuAccessImportMask | AuAccessListMask; + d->format = formatIn; + d->numTracks = sndStatIn.isStereo + 1; + d->description.type = AuStringLatin1; + d->description.string = (sndStatIn.isStereo) ? "Stereo Channel Input" + : "Mono Channel Input"; + d->description.len = strlen(d->description.string); + d->minSampleRate = sndStatOut.minSampleRate; + d->maxSampleRate = sndStatOut.maxSampleRate; + d->location = AuDeviceLocationRightMask | AuDeviceLocationLeftMask + | AuDeviceLocationExternalMask; + d->numChildren = 0; + d->gain = auDefaultInputGain; + d->minibuf = auInput; + d->minibufSize = d->numTracks * bytesPerSampleIn * auMinibufSamples; + d->physicalDeviceMask = (sndStatIn.isStereo) ? PhysicalInputStereo + : PhysicalInputMono; + AU_ADD_DEVICE(d); + + monoInputDevice = d; /* Should have two input devices - FIXME */ + stereoInputDevice = d; + + /* set the array of server devices */ + if (!(auServerDevices = (ComponentPtr *) aualloc(sizeof(ComponentPtr) + * auNumServerDevices))) { + UNIDENTMSG; + return AuBadAlloc; + } + + p = auServerDevices; + d = auDevices; + + while (d) { + *p++ = d; + d = d->next; + } + + if (!initialized) { + initialized = AuTrue; + setPhysicalOutputGain(auDefaultOutputGain); + setPhysicalInputGainAndLineMode(auDefaultInputGain, 0); + } + + UNIDENTMSG; + + return AuSuccess; +} + +static AuInt32 setTimer(rate) +AuInt32 rate; +{ + AuInt16 timer_ms; + AuInt32 foo; + struct itimerval ntval, otval; + + PRMSG("setTimer(rate = %d);\n", rate, 0); + IDENTMSG; + + /* change timer according to new sample rate */ + if (rate == 0) { /* Disable timer case */ + ntval.it_value.tv_sec = ntval.it_value.tv_usec = 0; + ntval.it_interval.tv_sec = ntval.it_interval.tv_usec = 0; + timer_ms = 0x7fff; + } else { + timer_ms = (auMinibufSamples * 500) / rate; + ntval.it_interval.tv_sec = 0; + ntval.it_interval.tv_usec = timer_ms * 1000; + ntval.it_value.tv_sec = 0; + ntval.it_value.tv_usec = timer_ms *1000 / 2 ; + } + foo = setitimer(ITIMER_REAL, &ntval, &otval); + + UNIDENTMSG; + + return timer_ms; +} + + +#ifdef sco +static void +oneMoreTick() +{ + struct itimerval ntval, otval; + int foo; + + ntval.it_interval.tv_sec = 0; + ntval.it_interval.tv_usec = 0; + ntval.it_value.tv_sec = 0; + ntval.it_value.tv_usec = 10; + foo = setitimer(ITIMER_REAL, &ntval, &otval); +} +#endif /* sco */ + + +static void +setFragmentSize(sndStatPtr) +SndStat *sndStatPtr; +{ + int fragarg, i, j; + int datarate, numfrags; + + datarate = sndStatPtr->curSampleRate; + if (sndStatPtr->isStereo) + datarate *= 2; + if (sndStatPtr->wordSize == 16) + datarate *= 2; + datarate /= 2; /* half second */ + numfrags = datarate / MAX_MINIBUF_SAMPLES; + if (numfrags < sndStatPtr->minFrags) + numfrags = sndStatPtr->minFrags; + else if (numfrags > sndStatPtr->maxFrags) + numfrags = sndStatPtr->maxFrags; + + j = MAX_MINIBUF_SAMPLES; + for (i = 0; j; i++) /* figure out what power of 2 MAX_MINIBUF_SAMPLES is */ + j = j >> 1; + fragarg = (numfrags << 16) | i; /* numfrags of size MAX_MINIBUF_SAMPLES */ + ioctl(sndStatPtr->fd, SNDCTL_DSP_SETFRAGMENT, &fragarg); +} + + +static AuUint32 setSampleRate(rate) +AuUint32 rate; +{ + int numSamplesIn, numSamplesOut; + AuBlock l; + + PRMSG("setSampleRate(rate = %d);\n", rate, 0); + IDENTMSG; + + l = AuBlockAudio(); + + if (sndStatOut.curSampleRate != rate) { + sndStatOut.curSampleRate = rate; + +#if defined(SNDCTL_DSP_SETFRAGMENT) + setFragmentSize(&sndStatOut); +#endif + ioctl(sndStatOut.fd, SNDCTL_DSP_SYNC, NULL); + ioctl(sndStatOut.fd, SNDCTL_DSP_SPEED, &(sndStatOut.curSampleRate)); + } + + if (sndStatIn.fd == sndStatOut.fd) + sndStatIn = sndStatOut; + else if (sndStatIn.curSampleRate != rate) { + sndStatIn.curSampleRate = rate; + +#if defined(SNDCTL_DSP_SETFRAGMENT) + setFragmentSize(&sndStatIn); +#endif + ioctl(sndStatIn.fd, SNDCTL_DSP_SYNC, NULL); + ioctl(sndStatIn.fd, SNDCTL_DSP_SPEED, &(sndStatIn.curSampleRate)); + } + +#if defined(AUDIO_DRAIN) + if (sndStatOut.isPCSpeaker) + ioctl (sndStatOut.fd, AUDIO_DRAIN, 0); +#endif + + AuUnBlockAudio(l); + + setTimer(rate); + + UNIDENTMSG; + + return sndStatOut.curSampleRate; +} + + +static void serverReset() +{ + PRMSG("serverReset();\n", 0, 0); + IDENTMSG; + + setTimer(0); +#ifndef sco + signal(SIGALRM, SIG_IGN); +#endif /* sco */ + + PRMSG("Audio Synchronisation...", 0, 0); + +#if defined(AUDIO_DRAIN) + if (sndStatOut.isPCSpeaker) + ioctl(sndStatOut.fd,AUDIO_DRAIN,0); + else { +#endif + + ioctl(sndStatIn.fd, SNDCTL_DSP_SYNC, NULL); + if (sndStatOut.fd != sndStatIn.fd) + ioctl(sndStatOut.fd, SNDCTL_DSP_SYNC, NULL); + +#if defined(AUDIO_DRAIN) + } +#endif + + if (doDebug) { + fputs(" done.\n", stderr); + fflush(stderr); + } + UNIDENTMSG; +} + + +static void +#ifdef sco +intervalProc(int i) +#else +intervalProc() +#endif /* sco */ +{ + extern void AuProcessData(); + +#ifndef sco + signal(SIGALRM, SIG_IGN); + + if (processFlowEnabled) + { + AuProcessData(); + signal(SIGALRM, intervalProc); + } +#else + if (!scoAudioBlocked && processFlowEnabled) + AuProcessData(); +#endif /* sco */ +} + +/** + * Gains are mapped thusly: + * + * Software s 0 - 100 + * Hardware h 0 - 100 +**/ + +static void +setPhysicalOutputGain(gain) + AuFixedPoint gain; +{ + AuInt32 g = AuFixedPointIntegralAddend(gain); + AuInt32 i, gusvolume; + + + if (g > 100) + g = 100; + if (g < 0) + g = 0; + Letsplay = g; + gusvolume = g | (g << 8); + if (mixerfd != -1) + i = ioctl(mixerfd, MIXER_WRITE(SOUND_MIXER_PCM), &gusvolume); +} + +static AuFixedPoint +getPhysicalOutputGain() +{ + AuInt16 outputGain; + + outputGain = Letsplay; + + return AuFixedPointFromSum(outputGain, 0); +} + +static void +setPhysicalInputGainAndLineMode(gain, lineMode) + AuFixedPoint gain; + AuUint8 lineMode; +{ + AuInt16 g = AuFixedPointIntegralAddend(gain); + AuInt16 inputAttenuation; + AuInt16 zero = 0; + AuInt32 params[4]; + + if (g < 100) + inputAttenuation = g; + else + inputAttenuation = 100; + + inputAttenuation = inputAttenuation << 8 | inputAttenuation; + + if (lineMode == AuDeviceLineModeHigh) { + ioctl(mixerfd, MIXER_WRITE(SOUND_MIXER_MIC), &inputAttenuation); + ioctl(mixerfd, MIXER_WRITE(SOUND_MIXER_LINE), &zero); + } + + if (lineMode == AuDeviceLineModeLow) { + ioctl(mixerfd, MIXER_WRITE(SOUND_MIXER_LINE), &inputAttenuation); + ioctl(mixerfd, MIXER_WRITE(SOUND_MIXER_MIC), &zero); + } +} + +static void enableProcessFlow() +{ + AuUint8 *p; + + PRMSG("enableProcessFlow();\n", 0, 0); + +#ifdef sco + if (!processFlowEnabled) + { +#endif /* sco */ + + processFlowEnabled = AuTrue; + +#ifndef sco + signal(SIGALRM, intervalProc); +#else + setTimer(sndStatOut.curSampleRate); +#endif /* sco */ + +#ifdef sco + } +#endif /* sco */ +} + +static void disableProcessFlow() +{ + +#ifndef sco + signal(SIGALRM, SIG_IGN); +#endif /* sco */ + + PRMSG("disableProcessFlow();\n", 0, 0); + +#ifdef sco + if (processFlowEnabled) + { +#endif /* sco */ + + ioctl(sndStatOut.fd, SNDCTL_DSP_SYNC, NULL); +#ifndef sco + ioctl(sndStatOut.fd, SNDCTL_DSP_SPEED, &sndStatOut.curSampleRate); +#endif /* sco */ + + if (sndStatOut.fd != sndStatIn.fd) + { + ioctl(sndStatIn.fd, SNDCTL_DSP_SYNC, NULL); +#ifndef sco + ioctl(sndStatIn.fd, SNDCTL_DSP_SPEED, &sndStatIn.curSampleRate); +#endif /* sco */ + } + +#ifdef sco + oneMoreTick(); +#endif + + processFlowEnabled = AuFalse; + +#ifdef sco + } +#endif /* sco */ +} + + +#if defined(__GNUC__) && !defined(linux) +inline +#endif +static void monoToStereoLinearSigned16LSB(numSamples) +AuUint32 numSamples; +{ + AuInt16 *s = (AuInt16*)monoOutputDevice->minibuf; + AuInt16 *d = (AuInt16*)stereoOutputDevice->minibuf; + + while (numSamples--) { + *d++ = *s; + *d++ = *s++; + } +} + +#if defined(__GNUC__) && !defined(linux) +inline +#endif +static void monoToStereoLinearUnsigned8(numSamples) +AuUint32 numSamples; +{ + AuUint8 *s = (AuUint8*)monoOutputDevice->minibuf; + AuUint8 *d = (AuUint8*)stereoOutputDevice->minibuf; + + while (numSamples--) { + *d++ = *s; + *d++ = *s++; + } +} + +static void writePhysicalOutputsMono() +{ + AuBlock l; + void* buf; + int bufSize; + + if (sndStatOut.isStereo) { + switch (monoOutputDevice->format) { + case AuFormatLinearSigned16LSB: + monoToStereoLinearSigned16LSB(monoOutputDevice->minibufSamples); + break; + + case AuFormatLinearUnsigned8: + monoToStereoLinearUnsigned8(monoOutputDevice->minibufSamples); + break; + + default: + /* check createServerComponents(...)! */ + assert(0); + } + + buf = stereoOutputDevice->minibuf; + bufSize = stereoOutputDevice->bytesPerSample + * monoOutputDevice->minibufSamples; + } else { + buf = monoOutputDevice->minibuf; + bufSize = monoOutputDevice->bytesPerSample + * monoOutputDevice->minibufSamples; + } + + l = AuBlockAudio(); + { int b, w; + char *p = buf ; + for (b=bufSize; b>0; b -= w) { + w = write(sndStatOut.fd, p, b); + p += w ; + } + } + +#ifdef DEBUGDSPOUT + { + char tempbuf[80]; + + sprintf(tempbuf, "\nwriteMono buf: %d size: %d\n", buf, bufSize); + write(dspout, tempbuf, strlen(tempbuf)); + write(dspout, buf, bufSize); + } +#endif /* DEBUGDSPOUT */ + + AuUnBlockAudio(l); +} + +#if defined(__GNUC__) && !defined(linux) +inline +#endif +static void stereoToMonoLinearSigned16LSB(numSamples) +AuUint32 numSamples; +{ + AuInt16 *s = (AuInt16*)stereoOutputDevice->minibuf; + AuInt16 *d = (AuInt16*)monoOutputDevice->minibuf; + + while (numSamples--) { + *d++ = (s[0] + s[1]) / 2; + s += 2; + } +} + +#if defined(__GNUC__) && !defined(linux) +inline +#endif +static void stereoToMonoLinearUnsigned8(numSamples) +AuUint32 numSamples; +{ + AuUint8 *s = (AuUint8*)stereoOutputDevice->minibuf; + AuUint8 *d = (AuUint8*)monoOutputDevice->minibuf; + + while (numSamples--) { + *d++ = (s[0] + s[1]) / 2; + s += 2; + } +} + +static void writePhysicalOutputsStereo() +{ + AuBlock l; + void* buf; + int bufSize; + + if (sndStatOut.isStereo) { + buf = stereoOutputDevice->minibuf; + bufSize = stereoOutputDevice->bytesPerSample + * stereoOutputDevice->minibufSamples; + } else { + switch (stereoOutputDevice->format) { + case AuFormatLinearSigned16LSB: + stereoToMonoLinearSigned16LSB(stereoOutputDevice->minibufSamples); + break; + + case AuFormatLinearUnsigned8: + stereoToMonoLinearUnsigned8(stereoOutputDevice->minibufSamples); + break; + + default: + /* check createServerComponents(...)! */ + assert(0); + } + + buf = monoOutputDevice->minibuf; + bufSize = monoOutputDevice->bytesPerSample + * stereoOutputDevice->minibufSamples; + } + + l = AuBlockAudio(); + { int b, w; + char *p = buf ; + for (b=bufSize; b>0; b -= w) { + w = write(sndStatOut.fd, p, b); + p += w ; + } + } + +#ifdef DEBUGDSPOUT + { + char tempbuf[80]; + + sprintf(tempbuf, "\nwriteStereo buf: %d size: %d\n", buf, bufSize); + write(dspout, tempbuf, strlen(tempbuf)); + write(dspout, buf, bufSize); + } +#endif /* DEBUGDSPOUT */ + + AuUnBlockAudio(l); +} + +static void writePhysicalOutputsBoth() +{ + AuInt16 *m = (AuInt16 *) monoOutputDevice->minibuf; + AuInt16 *p, *s; + AuUint8 *m8 = (AuUint8 *) monoOutputDevice->minibuf; + AuUint8 *p8, *s8; + AuUint32 max = aumax(monoOutputDevice->minibufSamples, + stereoOutputDevice->minibufSamples); + AuUint32 i; + + switch (stereoOutputDevice->format) { + case AuFormatLinearSigned16LSB: + p = s = (AuInt16 *) stereoOutputDevice->minibuf; + + for (i = 0; i < max; i++) { + *p++ = (*m + *s++) / 2; + *p++ = (*m++ + *s++) / 2; + } + break; + + case AuFormatLinearUnsigned8: + p8 = s8 = (AuUint8 *) stereoOutputDevice->minibuf; + + for (i = 0; i < max; i++) { + *p8++ = (*m8 + *s8++) / 2; + *p8++ = (*m8++ + *s8++) / 2; + } + break; + + default: + assert(0); + } + + stereoOutputDevice->minibufSamples = max; + + writePhysicalOutputsStereo(); +} + +static void readPhysicalInputs() +{ + AuBlock l; + char *p = stereoInputDevice->minibuf ; + int b,r ; + + /* Should make use of two input devices - FIXME */ + l = AuBlockAudio(); + for (b = stereoInputDevice->bytesPerSample * auMinibufSamples; + b > 0 ; b -= r) { + r = read(sndStatIn.fd, p, b); + p += r ; + } + +#ifdef DEBUGDSPIN + { + char tempbuf[80]; + sprintf(tempbuf, "\nreadInputs buf: %d size: %d\n", + stereoInputDevice->minibuf, + stereoInputDevice->bytesPerSample * auMinibufSamples); + write(dspin, tempbuf, strlen(tempbuf)); + write(dspin, stereoInputDevice->minibuf, + stereoInputDevice->bytesPerSample * auMinibufSamples); + } +#endif /* DEBUGDSPIN */ + + AuUnBlockAudio(l); +} + +static void +noop() +{ +} + +static void +setWritePhysicalOutputFunction(flow, funct) + CompiledFlowPtr flow; + void (**funct) (); +{ + AuUint32 mask = flow->physicalDeviceMask; + + if ((mask & (PhysicalOutputMono | PhysicalOutputStereo)) == + (PhysicalOutputMono | PhysicalOutputStereo)) + *funct = writePhysicalOutputsBoth; + else if (mask & PhysicalOutputMono) + *funct = writePhysicalOutputsMono; + else if (mask & PhysicalOutputStereo) + *funct = writePhysicalOutputsStereo; + else + *funct = noop; +} + +/* + * Setup soundcard at maximum audio quality. + */ + +#ifdef __FreeBSD__ +#define NO_16_BIT_SAMPLING +#endif + +static void setupSoundcard(sndStatPtr) +SndStat* sndStatPtr; +{ + int samplesize; + + PRMSG("setupSoundcard(...);\n", 0, 0); + IDENTMSG; + + if (beVerbose) + if (sndStatPtr == &sndStatOut) + fprintf (stderr, "++ Setting up Output device\n"); + else + fprintf (stderr, "++ Setting up Input device\n"); + + if (sndStatPtr->isPCSpeaker) { + if (beVerbose) + fprintf(stderr, "+++ Device is a PC speaker\n"); + sndStatPtr->curSampleRate = sndStatPtr->maxSampleRate + = sndStatPtr->minSampleRate = 8000; + sndStatPtr->isStereo = 0; + sndStatPtr->wordSize = 8; + } + else { + if (beVerbose) + fprintf(stderr, "+++ requesting wordsize of %d, ", sndStatPtr->wordSize); + if (ioctl(sndStatPtr->fd, SNDCTL_DSP_SAMPLESIZE, &sndStatPtr->wordSize) + || sndStatPtr->wordSize != 16) { + sndStatPtr->wordSize = 8; + ioctl(sndStatPtr->fd, SNDCTL_DSP_SAMPLESIZE, &sndStatPtr->wordSize); + } + if (beVerbose) + fprintf(stderr, "got %d\n", sndStatPtr->wordSize); + + if (beVerbose) + fprintf(stderr, "+++ requesting %d channel(s), ", sndStatPtr->isStereo + 1); + if (ioctl(sndStatPtr->fd, SNDCTL_DSP_STEREO, &sndStatPtr->isStereo) == -1 + || !sndStatPtr->isStereo) { + sndStatPtr->isStereo = 0; + ioctl(sndStatPtr->fd, SNDCTL_DSP_STEREO, &sndStatPtr->isStereo); + } + if (beVerbose) + fprintf(stderr, "got %d channel(s)\n", sndStatPtr->isStereo + 1); + + if (beVerbose) + fprintf (stderr, "+++ Requesting minimum sample rate of %d, ", sndStatPtr->minSampleRate); + ioctl(sndStatPtr->fd, SNDCTL_DSP_SPEED, &sndStatPtr->minSampleRate); + if (beVerbose) + fprintf (stderr, "got %d\n", sndStatPtr->minSampleRate); + + if (beVerbose) + fprintf (stderr, "+++ Requesting maximum sample rate of %d, ", sndStatPtr->maxSampleRate); + ioctl(sndStatPtr->fd, SNDCTL_DSP_SPEED, &sndStatPtr->maxSampleRate); + if (beVerbose) + fprintf (stderr, "got %d\n", sndStatPtr->maxSampleRate); + + sndStatPtr->curSampleRate = sndStatPtr->maxSampleRate; + + } + +#if defined(SNDCTL_DSP_SETFRAGMENT) + setFragmentSize(sndStatPtr); +#endif + + UNIDENTMSG; +} + + +#ifdef sco +static AuBool +scoInterrupts() +{ + struct sigaction act; + + act.sa_handler = intervalProc; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGALRM); + if (sigaction(SIGALRM, &act, (struct sigaction *)NULL) == -1) + { + ErrorF("sigaction SIGALRM"); + return FALSE; + } + + return TRUE; +} +#endif /* sco */ + +/* +* Find the config file +*/ + +static FILE *openConfigFile (char *path) +{ + static char buf[1024]; + FILE *config; + + strcat (buf, path); + strcat (buf, "AUVoxConfig"); + if ((config = fopen (buf, "r")) != NULL) + return config; + else + return NULL; +} + + +AuBool AuInitPhysicalDevices() +{ + static AuBool AL_initialized = AuFalse; + static AuUint8 *physicalBuffers; + int fd; + AuUint32 physicalBuffersSize; + extern AuUint8 *auPhysicalOutputBuffers; + extern AuUint32 auPhysicalOutputBuffersSize; + extern void AuProcessData(); +#if defined(AUDIO_GETINFO) + audio_info_t spkrinf; +#endif + + PRMSG("AuInitPhysicalDevices();\n", 0, 0); + IDENTMSG; + + /* + * create the input and output ports + */ + if (!AL_initialized) { + int fd; + AuInt32 i; + + AL_initialized = AuTrue; + + if ((yyin = openConfigFile (CONFSEARCHPATH)) != NULL) + yyparse(); + + if ((fd = open(sndStatOut.device, O_RDWR, 0)) == -1) { + UNIDENTMSG; + return AuFalse; + } +#if defined(AUDIO_GETINFO) + if (sndStatOut.isPCSpeaker) { + ioctl(fd, AUDIO_GETINFO, &spkrinf); + spkrinf.play.encoding = AUDIO_ENCODING_RAW; + ioctl(fd, AUDIO_SETINFO, &spkrinf); + } +#endif + +#ifdef DEBUGDSPOUT + dspout = open("/tmp/dspout", O_RDWR | O_CREAT, 00666); +#endif +#ifdef DEBUGDSPIN + dspin = open("/tmp/dspin", O_RDWR | O_CREAT, 00666); +#endif + + sndStatOut.fd = fd; + + if ((fd = open(sndStatIn.device, O_RDONLY, 0)) != -1) + sndStatIn.fd = fd; + else + sndStatIn.fd = sndStatOut.fd; + + setupSoundcard(&sndStatOut); + if (sndStatOut.fd != sndStatIn.fd) + setupSoundcard(&sndStatIn); + + if (!sndStatOut.isPCSpeaker) { + if ((mixerfd = open("/dev/mixer", O_RDONLY, 0)) == -1) { + UNIDENTMSG; + return AuFalse; + } + + if (ioctl(mixerfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { + close(mixerfd); + mixerfd = -1; + } else { + if (ioctl(mixerfd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) { + return AuFalse; + } + + { + /* Enable all used recording sources ( mic & line ) and + * control which is active via level setting later. There + * should be a better way to do this! + */ + int mask = SOUND_MASK_MIC | SOUND_MASK_LINE; /* enable these */ + mask &= recmask; /* if supported */ + if (ioctl(mixerfd, SOUND_MIXER_WRITE_RECSRC, &mask) == -1) { + return AuFalse; + } + } + + if (ioctl(mixerfd, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) { + UNIDENTMSG; + return AuFalse; + } + + if (ioctl(mixerfd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1) { + UNIDENTMSG; + return AuFalse; + } + + /* get all sound levels */ + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & devmask) { + + if (ioctl(mixerfd, MIXER_READ(i), &level[i]) == -1) { + UNIDENTMSG; + return AuFalse; + } + } + } + + } + } + } + + if (physicalBuffers) + aufree(physicalBuffers); + + auMinibufSamples = MAX_MINIBUF_SAMPLES; + + /* the output buffers need to be twice as large for output range checking */ + physicalBuffersSize = + PhysicalTwoTrackBufferSize + /* mono/stereo input */ + PhysicalOneTrackBufferSize * 2 + /* mono output */ + PhysicalTwoTrackBufferSize * 2; /* stereo output */ + + if (!(physicalBuffers = (AuUint8 *) aualloc(physicalBuffersSize))) { + UNIDENTMSG; + return AuFalse; + } + + auInput = physicalBuffers; + auOutputMono = auInput + PhysicalTwoTrackBufferSize; + auOutputStereo = auOutputMono + 2 * PhysicalOneTrackBufferSize; + + auPhysicalOutputBuffers = auOutputMono; + auPhysicalOutputBuffersSize = physicalBuffersSize - + PhysicalTwoTrackBufferSize; + + /* + * Call AuProcessData() in signal handler often enough to drain the + * input devices and keep the output devices full at the current + * sample rate. + */ + + processFlowEnabled = AuFalse; + +#ifdef sco + if (!scoInterrupts()) + { + return AuFalse; + } +#else + signal(SIGALRM, intervalProc); +#endif /* sco */ + + setTimer(0); + + AuRegisterCallback(AuCreateServerComponentsCB, createServerComponents); + AuRegisterCallback(AuSetPhysicalOutputGainCB, setPhysicalOutputGain); + AuRegisterCallback(AuGetPhysicalOutputGainCB, getPhysicalOutputGain); + AuRegisterCallback(AuSetPhysicalInputGainAndLineModeCB, + setPhysicalInputGainAndLineMode); + AuRegisterCallback(AuEnableProcessFlowCB, enableProcessFlow); + AuRegisterCallback(AuDisableProcessFlowCB, disableProcessFlow); + AuRegisterCallback(AuReadPhysicalInputsCB, readPhysicalInputs); + AuRegisterCallback(AuSetWritePhysicalOutputFunctionCB, + setWritePhysicalOutputFunction); + + AuRegisterCallback(AuSetSampleRateCB, setSampleRate); + + /* bogus resource so we can have a cleanup function at server reset */ + AddResource(FakeClientID(SERVER_CLIENT), + CreateNewResourceType(serverReset), 0); + + UNIDENTMSG; + + return AuTrue; +} diff --git a/sys/i386/isa/snd/misc/files.i386 b/sys/i386/isa/snd/misc/files.i386 new file mode 100644 index 0000000..bc12938 --- /dev/null +++ b/sys/i386/isa/snd/misc/files.i386 @@ -0,0 +1,240 @@ +# This file tells config what files go into building a kernel, +# files marked standard are always included. +# +# $Id: files.i386,v 1.141.2.11 1997/04/04 04:35:46 gibbs Exp $ +# +aic7xxx_asm optional ahc device-driver \ + dependency "$S/dev/aic7xxx/*.[chyl]" \ + compile-with "cd $S/dev/aic7xxx; make obj; make BINDIR=${.CURDIR} all install" \ + no-obj no-implicit-rule \ + clean "aic7xxx_asm" +# +aic7xxx_{seq,reg}.h optional ahc device-driver \ + compile-with "./aic7xxx_asm ${INCLUDES} -o aic7xxx_seq.h -r aic7xxx_reg.h $S/dev/aic7xxx/aic7xxx.seq" \ + no-obj no-implicit-rule before-depend \ + clean "aic7xxx_seq.h aic7xxx_reg.h" \ + dependency "$S/dev/aic7xxx/aic7xxx.{reg,seq} aic7xxx_asm" +# +linux_genassym optional compat_linux \ + dependency "$S/i386/linux/linux_genassym.c $S/i386/linux/linux.h" \ + compile-with "${CC} ${CFLAGS} ${PARAM} -UKERNEL -o $@ $<" \ + no-obj no-implicit-rule \ + clean "linux_genassym" +# +linux_assym.h optional compat_linux \ + dependency "linux_genassym" \ + compile-with "./linux_genassym > $@" \ + no-obj no-implicit-rule before-depend \ + clean "linux_assym.h" +# +i386/scsi/93cx6.c optional ahc device-driver +i386/apm/apm.c optional apm device-driver +i386/apm/apm_setup.s optional apm +i386/eisa/3c5x9.c optional ep device-driver +i386/eisa/aic7770.c optional ahc device-driver \ + dependency "aic7xxx_reg.h $S/i386/eisa/aic7770.c" +i386/eisa/aha1742.c optional ahb device-driver +i386/eisa/bt74x.c optional bt device-driver +i386/eisa/eisaconf.c optional eisa +i386/eisa/if_vx_eisa.c optional vx device-driver +i386/eisa/if_fea.c optional fea device-driver +i386/i386/autoconf.c standard device-driver +i386/i386/cons.c standard +i386/i386/db_disasm.c optional ddb +i386/i386/db_interface.c optional ddb +i386/i386/db_trace.c optional ddb +i386/i386/i386-gdbstub.c optional ddb +i386/i386/exception.s standard +i386/i386/identcpu.c standard +i386/i386/in_cksum.c optional inet +# locore.s needs to be handled in Makefile to put it first. Otherwise it's +# now normal. +# i386/i386/locore.s standard +i386/i386/machdep.c standard +i386/i386/math_emulate.c optional math_emulate +i386/i386/mem.c standard +i386/i386/microtime.s standard +i386/i386/perfmon.c optional perfmon profiling-routine +i386/i386/perfmon.c optional perfmon +i386/i386/pmap.c standard +i386/i386/procfs_machdep.c standard +i386/i386/support.s standard +i386/i386/swtch.s standard +i386/i386/sys_machdep.c standard +i386/i386/trap.c standard +i386/i386/userconfig.c optional userconfig +i386/i386/vm_machdep.c standard +i386/ibcs2/ibcs2_fcntl.c optional ibcs2 +i386/ibcs2/ibcs2_stat.c optional ibcs2 +i386/ibcs2/ibcs2_ipc.c optional ibcs2 +i386/ibcs2/ibcs2_msg.c optional ibcs2 +i386/ibcs2/ibcs2_misc.c optional ibcs2 +i386/ibcs2/ibcs2_other.c optional ibcs2 +i386/ibcs2/ibcs2_signal.c optional ibcs2 +i386/ibcs2/ibcs2_ioctl.c optional ibcs2 +i386/ibcs2/ibcs2_socksys.c optional ibcs2 +i386/ibcs2/ibcs2_sysi86.c optional ibcs2 +i386/ibcs2/ibcs2_util.c optional ibcs2 +i386/ibcs2/ibcs2_isc.c optional ibcs2 +i386/ibcs2/ibcs2_isc_sysent.c optional ibcs2 +i386/ibcs2/ibcs2_xenix.c optional ibcs2 +i386/ibcs2/ibcs2_xenix_sysent.c optional ibcs2 +i386/ibcs2/ibcs2_errno.c optional ibcs2 +i386/ibcs2/ibcs2_sysent.c optional ibcs2 +i386/ibcs2/ibcs2_sysvec.c optional ibcs2 +i386/ibcs2/imgact_coff.c optional ibcs2 +i386/isa/aha1542.c optional aha device-driver +i386/isa/aic6360.c optional aic device-driver +i386/isa/b004.c optional bqu device-driver +i386/isa/bt5xx-445.c optional bt device-driver +i386/isa/clock.c standard +i386/isa/cronyx.c optional cx device-driver +i386/isa/ctx.c optional ctx device-driver +i386/isa/cx.c optional cx device-driver +i386/isa/cy.c optional cy device-driver +i386/isa/diskslice_machdep.c standard +i386/isa/elink.c optional ep device-driver +i386/isa/elink.c optional ie device-driver +i386/isa/fd.c optional fd device-driver +i386/isa/ft.c optional ft device-driver +i386/isa/gpib.c optional gp device-driver +i386/isa/asc.c optional asc device-driver +i386/isa/gsc.c optional gsc device-driver +i386/isa/if_ar.c optional ar device-driver +i386/isa/if_cx.c optional cx device-driver +i386/isa/if_ed.c optional ed device-driver +i386/isa/if_eg.c optional eg device-driver +i386/isa/if_el.c optional el device-driver +i386/isa/if_ep.c optional ep device-driver +i386/isa/if_ex.c optional ex device-driver +i386/isa/if_fe.c optional fe device-driver +i386/isa/if_ie.c optional ie device-driver +i386/isa/if_ix.c optional ix device-driver +i386/isa/if_le.c optional le device-driver +i386/isa/if_lnc.c optional lnc device-driver +i386/isa/if_sr.c optional sr device-driver +i386/isa/if_ze.c optional ze device-driver +i386/isa/if_zp.c optional zp device-driver +i386/isa/isa.c optional isa device-driver +i386/isa/istallion.c optional stli device-driver +i386/isa/joy.c optional joy device-driver +i386/isa/kbdio.c optional psm device-driver +i386/isa/kbdio.c optional sc device-driver +i386/isa/kbdio.c optional vt device-driver +i386/isa/lpt.c optional lpt device-driver +i386/isa/labpc.c optional labpc device-driver +i386/isa/mcd.c optional mcd device-driver +i386/isa/mse.c optional mse device-driver +i386/isa/ncr5380.c optional nca device-driver +i386/isa/npx.c optional npx device-driver +i386/isa/pcaudio.c optional pca device-driver +i386/isa/matcd/matcd.c optional matcd device-driver +i386/isa/pcibus.c optional pci device-driver +i386/isa/pcicx.c optional ze device-driver +i386/isa/pcicx.c optional zp device-driver +i386/isa/pcvt/pcvt_drv.c optional vt device-driver +i386/isa/pcvt/pcvt_ext.c optional vt device-driver +i386/isa/pcvt/pcvt_kbd.c optional vt device-driver +i386/isa/pcvt/pcvt_out.c optional vt device-driver +i386/isa/pcvt/pcvt_sup.c optional vt device-driver +i386/isa/pcvt/pcvt_vtf.c optional vt device-driver +i386/isa/pnp.c optional pnp device-driver +i386/isa/prof_machdep.c optional profiling-routine +i386/isa/psm.c optional psm device-driver +i386/isa/qcam.c optional qcam device-driver +i386/isa/qcamio.c optional qcam device-driver +i386/isa/random_machdep.c standard +i386/isa/rc.c optional rc device-driver +i386/isa/scd.c optional scd device-driver +i386/isa/seagate.c optional sea device-driver +i386/isa/si.c optional si device-driver +i386/isa/si_code.c optional si device-driver +i386/isa/sio.c optional sio device-driver +i386/isa/snd/sound.c optional pcm device-driver +i386/isa/snd/dmabuf.c optional pcm device-driver +i386/isa/snd/ad1848.c optional pcm device-driver +i386/isa/snd/sb_dsp.c optional pcm device-driver +i386/isa/snd/clones.c optional pcm device-driver +i386/isa/spigot.c optional spigot device-driver +i386/isa/spkr.c optional speaker device-driver +i386/isa/stallion.c optional stl device-driver +i386/isa/syscons.c optional sc device-driver +i386/isa/tw.c optional tw device-driver +i386/isa/ultra14f.c optional uha device-driver +i386/isa/wd.c optional wdc device-driver +i386/isa/wd.c optional wd device-driver +i386/isa/atapi.c optional atapi device-driver +i386/isa/wcd.c optional wcd device-driver +i386/isa/wd7000.c optional wds device-driver +i386/isa/wt.c optional wt device-driver +i386/linux/imgact_linux.c optional compat_linux +i386/linux/linux_dummy.c optional compat_linux +i386/linux/linux_file.c optional compat_linux +i386/linux/linux_ioctl.c optional compat_linux +i386/linux/linux_ipc.c optional compat_linux +i386/linux/linux_locore.s optional compat_linux \ + dependency "linux_assym.h" +i386/linux/linux_misc.c optional compat_linux +i386/linux/linux_signal.c optional compat_linux +i386/linux/linux_socket.c optional compat_linux +i386/linux/linux_stats.c optional compat_linux +i386/linux/linux_sysent.c optional compat_linux +i386/linux/linux_sysvec.c optional compat_linux +i386/linux/linux_util.c optional compat_linux +i386/scsi/aic7xxx.c optional ahc device-driver \ + dependency "aic7xxx_{reg,seq}.h" +i386/scsi/bt.c optional bt device-driver +libkern/bcd.c standard +libkern/divdi3.c standard +libkern/inet_ntoa.c standard +libkern/index.c standard +libkern/mcount.c optional profiling-routine +libkern/moddi3.c standard +libkern/qdivrem.c standard +libkern/qsort.c standard +libkern/random.c standard +libkern/scanc.c standard +libkern/skpc.c standard +libkern/strcat.c standard +libkern/strcmp.c standard +libkern/strcpy.c standard +libkern/strlen.c standard +libkern/strncmp.c standard +libkern/strncpy.c standard +libkern/udivdi3.c standard +libkern/umoddi3.c standard +gnu/i386/fpemul/div_small.s optional gpl_math_emulate +gnu/i386/fpemul/errors.c optional gpl_math_emulate +gnu/i386/fpemul/fpu_arith.c optional gpl_math_emulate +gnu/i386/fpemul/fpu_aux.c optional gpl_math_emulate +gnu/i386/fpemul/fpu_entry.c optional gpl_math_emulate +gnu/i386/fpemul/fpu_etc.c optional gpl_math_emulate +gnu/i386/fpemul/fpu_trig.c optional gpl_math_emulate +gnu/i386/fpemul/get_address.c optional gpl_math_emulate +gnu/i386/fpemul/load_store.c optional gpl_math_emulate +gnu/i386/fpemul/poly_2xm1.c optional gpl_math_emulate +gnu/i386/fpemul/poly_atan.c optional gpl_math_emulate +gnu/i386/fpemul/poly_div.s optional gpl_math_emulate +gnu/i386/fpemul/poly_l2.c optional gpl_math_emulate +gnu/i386/fpemul/poly_mul64.s optional gpl_math_emulate +gnu/i386/fpemul/poly_sin.c optional gpl_math_emulate +gnu/i386/fpemul/poly_tan.c optional gpl_math_emulate +gnu/i386/fpemul/polynomial.s optional gpl_math_emulate +gnu/i386/fpemul/reg_add_sub.c optional gpl_math_emulate +gnu/i386/fpemul/reg_compare.c optional gpl_math_emulate +gnu/i386/fpemul/reg_constant.c optional gpl_math_emulate +gnu/i386/fpemul/reg_div.s optional gpl_math_emulate +gnu/i386/fpemul/reg_ld_str.c optional gpl_math_emulate +gnu/i386/fpemul/reg_mul.c optional gpl_math_emulate +gnu/i386/fpemul/reg_norm.s optional gpl_math_emulate +gnu/i386/fpemul/reg_round.s optional gpl_math_emulate +gnu/i386/fpemul/reg_u_add.s optional gpl_math_emulate +gnu/i386/fpemul/reg_u_div.s optional gpl_math_emulate +gnu/i386/fpemul/reg_u_mul.s optional gpl_math_emulate +gnu/i386/fpemul/reg_u_sub.s optional gpl_math_emulate +gnu/i386/fpemul/wm_shrx.s optional gpl_math_emulate +gnu/i386/fpemul/wm_sqrt.s optional gpl_math_emulate +gnu/i386/isa/dgb.c optional dgb device-driver +gnu/i386/isa/nic3008.c optional nic device-driver +gnu/i386/isa/nic3009.c optional nnic device-driver +pci/wd82371.c optional wd device-driver diff --git a/sys/i386/isa/snd/misc/linux.patch b/sys/i386/isa/snd/misc/linux.patch new file mode 100644 index 0000000..aacfcbc --- /dev/null +++ b/sys/i386/isa/snd/misc/linux.patch @@ -0,0 +1,41 @@ +--- linux.h.orig Tue Dec 3 16:47:28 1996 ++++ linux.h Mon Nov 17 00:05:29 1997 +@@ -491,6 +491,9 @@ + #define LINUX_SNDCTL_DSP_GETOSPACE 0x500C + #define LINUX_SNDCTL_DSP_GETISPACE 0x500D + #define LINUX_SNDCTL_DSP_NONBLOCK 0x500E ++#define LINUX_SNDCTL_DSP_GETCAPS 0x500F ++#define LINUX_SNDCTL_DSP_GETIPTR 0x5011 ++#define LINUX_SNDCTL_DSP_GETOPTR 0x5012 + #define LINUX_SOUND_MIXER_WRITE_VOLUME 0x4d00 + #define LINUX_SOUND_MIXER_WRITE_BASS 0x4d01 + #define LINUX_SOUND_MIXER_WRITE_TREBLE 0x4d02 +--- linux_ioctl.c.orig Sat Nov 9 22:10:15 1996 ++++ linux_ioctl.c Mon Nov 17 10:20:14 1997 +@@ -691,6 +691,26 @@ + args->cmd = SNDCTL_DSP_NONBLOCK; + return ioctl(p, (struct ioctl_args *)args, retval); + ++ case LINUX_SNDCTL_DSP_GETCAPS: ++ args->cmd = SNDCTL_DSP_GETCAPS; ++ return ioctl(p, (struct ioctl_args *)args, retval); ++ ++ case LINUX_SNDCTL_DSP_GETIPTR: ++ args->cmd = SNDCTL_DSP_GETIPTR; ++ return ioctl(p, (struct ioctl_args *)args, retval); ++ ++ case LINUX_SNDCTL_DSP_GETOPTR: ++ args->cmd = SNDCTL_DSP_GETOPTR; ++ { int a= ioctl(p, (struct ioctl_args *)args, retval); ++ struct count_info *p = (struct count_info *)(args->arg); ++#if 0 ++ p->bytes += 128 ; ++ uprintf("GETOPTR bytes %d blk %d ptr %d\n", ++ p->bytes, p->blocks, p->ptr); ++#endif ++ return a; ++ } ++ + case LINUX_SOUND_MIXER_WRITE_VOLUME: + args->cmd = SOUND_MIXER_WRITE_VOLUME; + return ioctl(p, (struct ioctl_args *)args, retval); diff --git a/sys/i386/isa/snd/misc/linux_a.c b/sys/i386/isa/snd/misc/linux_a.c new file mode 100644 index 0000000..3a86aad --- /dev/null +++ b/sys/i386/isa/snd/misc/linux_a.c @@ -0,0 +1,238 @@ +/* + * TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka + * Toivonen <toivonen@clinet.fi> + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 675 + * Mass Ave, Cambridge, MA 02139, USA. + * + * linux_audio.c + * + * Functions to play sound on the VoxWare audio driver (Linux or FreeBSD) + * + */ + +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef linux +#include <sys/ioctl.h> /* new with 1.2.0? Didn't need this under + * 1.1.64 */ +#include <linux/soundcard.h> +#endif + +#ifdef __FreeBSD__ +#include <stdio.h> +#include <machine/soundcard.h> +#endif + +#include "config.h" +#include "output.h" +#include "controls.h" + +static int open_output(void); /* 0=success, 1=warning, -1=fatal + * error */ +static void close_output(void); +static void output_data(int32 * buf, int32 count); +static void flush_output(void); +static void purge_output(void); + +/* export the playback mode */ + +#define dpm linux_play_mode + +PlayMode dpm = { + DEFAULT_RATE, PE_16BIT | PE_SIGNED, + -1, + {0}, /* default: get all the buffer fragments you + * can */ + "Linux dsp device", 'd', + "/dev/dsp", + open_output, + close_output, + output_data, + flush_output, + purge_output +}; + + +/*************************************************************************/ +/* + * We currently only honor the PE_MONO bit, the sample rate, and the number + * of buffer fragments. We try 16-bit signed data first, and then 8-bit + * unsigned if it fails. If you have a sound device that can't handle either, + * let me know. + */ + +static int +open_output(void) +{ + int fd, tmp, i, warnings = 0; + + /* Open the audio device */ + fd = open(dpm.name, O_RDWR /* | O_NDELAY */); + if (fd < 0) { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s", + dpm.name, sys_errlist[errno]); + return -1; + } + /* They can't mean these */ + dpm.encoding &= ~(PE_ULAW | PE_BYTESWAP); + + + /* + * Set sample width to whichever the user wants. If it fails, try the + * other one. + */ + + i = tmp = (dpm.encoding & PE_16BIT) ? 16 : 8; + if (dpm.encoding & PE_16BIT) { + int fmt = AFMT_S16_LE ; + + if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 || fmt != AFMT_S16_LE) { + fmt = AFMT_U8 ; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 || fmt != AFMT_U8) { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s doesn't support 16- or 8-bit sample width", + dpm.name); + close(fd); + return -1; + } + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, + "Sample width adjusted to %d bits", tmp); + dpm.encoding ^= PE_16BIT; + warnings = 1; + } + } + if (dpm.encoding & PE_16BIT) + dpm.encoding |= PE_SIGNED; + else + dpm.encoding &= ~PE_SIGNED; + + + /* + * Try stereo or mono, whichever the user wants. If it fails, try the + * other. + */ + + i = tmp = (dpm.encoding & PE_MONO) ? 0 : 1; + if ((ioctl(fd, SNDCTL_DSP_STEREO, &tmp) < 0) || tmp != i) { + i = tmp = (dpm.encoding & PE_MONO) ? 1 : 0; + + if ((ioctl(fd, SNDCTL_DSP_STEREO, &tmp) < 0) || tmp != i) { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s doesn't support mono or stereo samples", + dpm.name); + close(fd); + return -1; + } + if (tmp == 0) + dpm.encoding |= PE_MONO; + else + dpm.encoding &= ~PE_MONO; + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, "Sound adjusted to %sphonic", + (tmp == 0) ? "mono" : "stereo"); + warnings = 1; + } + /* Set the sample rate */ + + tmp = dpm.rate; + if (ioctl(fd, SNDCTL_DSP_SPEED, &tmp) < 0) { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s doesn't support a %d Hz sample rate", + dpm.name, dpm.rate); + close(fd); + return -1; + } + if (tmp != dpm.rate) { + dpm.rate = tmp; + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, + "Output rate adjusted to %d Hz", dpm.rate); + warnings = 1; + } + /* Older VoxWare drivers don't have buffer fragment capabilities */ +#ifdef SNDCTL_DSP_SETFRAGMENT + /* Set buffer fragments (in extra_param[0]) */ + + tmp = 2+ AUDIO_BUFFER_BITS ; + if (!(dpm.encoding & PE_MONO)) + tmp++; + if (dpm.encoding & PE_16BIT) + tmp++; + tmp |= (dpm.extra_param[0] << 16); + i = tmp; + if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) { + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, + "%s doesn't support %d-byte buffer fragments", dpm.name, (1 << i)); + /* + * It should still work in some fashion. We should use a secondary + * buffer anyway -- 64k isn't enough. + */ + warnings = 1; + } +#else + if (dpm.extra_param[0]) { + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, + "%s doesn't support buffer fragments", dpm.name); + warnings = 1; + } +#endif + + dpm.fd = fd; + + return warnings; +} + +static void +output_data(int32 * buf, int32 count) +{ + char *p; + int res, l; + + if (!(dpm.encoding & PE_MONO)) + count *= 2; /* Stereo samples */ + + if (dpm.encoding & PE_16BIT) { + /* Convert data to signed 16-bit PCM */ + s32tos16(buf, count); + res = count*2; + } else { + /* Convert to 8-bit unsigned and write out. */ + s32tou8(buf, count); + res = count ; + } + for (p = buf ; res > 0 ; res -= l ) { + l = write(dpm.fd, p, res); + if (l < 0) return ; + p += l ; + } +} + +static void +close_output(void) +{ + close(dpm.fd); +} + +static void +flush_output(void) +{ + ioctl(dpm.fd, SNDCTL_DSP_SYNC); +} + +static void +purge_output(void) +{ + ioctl(dpm.fd, SNDCTL_DSP_RESET); +} diff --git a/sys/i386/isa/snd/misc/mmap_test.c b/sys/i386/isa/snd/misc/mmap_test.c new file mode 100644 index 0000000..f9e8b3e --- /dev/null +++ b/sys/i386/isa/snd/misc/mmap_test.c @@ -0,0 +1,278 @@ +/* + * This is a simple program which demonstrates use of mmapped DMA buffer + * of the sound driver directly from application program. + * + * This sample program works (currently) only with Linux, FreeBSD and BSD/OS + * (FreeBSD and BSD/OS require OSS version 3.8-beta16 or later. + * + * Note! Don't use mmapped DMA buffers (direct audio) unless you have + * very good reasons to do it. Programs using this feature will not + * work with all soundcards. GUS (GF1) is one of them (GUS MAX works). + * + * This program requires version 3.5-beta7 or later of OSS + * (3.8-beta16 or later in FreeBSD and BSD/OS). + */ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/soundcard.h> +#include <sys/time.h> + +main() +{ + int fd, sz, fsz, i, tmp, n, l, have_data=0, nfrag; + int caps; + + int sd, sl=0, sp; + + unsigned char data[500000], *dp = data; + + struct buffmem_desc imemd, omemd; + caddr_t buf; + struct timeval tim; + + unsigned char *op; + + struct audio_buf_info info; + + int frag = 0xffff000c; /* Max # fragments of 2^13=8k bytes */ + + fd_set writeset; + + close(0); + if ((fd=open("/dev/dsp", O_RDWR, 0))==-1) + { + perror("/dev/dsp"); + exit(-1); + } +/* + * Then setup sampling parameters. Just sampling rate in this case. + */ + + tmp = 48000; + ioctl(fd, SNDCTL_DSP_SPEED, &tmp); + printf("Speed set to %d\n", tmp); + +/* + * Load some test data. + */ + + sl = sp = 0; + if ((sd=open("smpl", O_RDONLY, 0))!=-1) + { + sl = read(sd, data, sizeof(data)); + printf("%d bytes read from file.\n", sl); + close(sd); + } + else perror("smpl"); + + if (ioctl(fd, SNDCTL_DSP_GETCAPS, &caps)==-1) + { + perror("/dev/dsp"); + fprintf(stderr, "Sorry but your sound driver is too old\n"); + exit(-1); + } + +/* + * Check that the device has capability to do this. Currently just + * CS4231 based cards will work. + * + * The application should also check for DSP_CAP_MMAP bit but this + * version of driver doesn't have it yet. + */ +/* ioctl(fd, SNDCTL_DSP_SETSYNCRO, 0); */ + +/* + * You need version 3.5-beta7 or later of the sound driver before next + * two lines compile. There is no point to modify this program to + * compile with older driver versions since they don't have working + * mmap() support. + */ + if (!(caps & DSP_CAP_TRIGGER) || + !(caps & DSP_CAP_MMAP)) + { + fprintf(stderr, "Sorry but your soundcard can't do this\n"); + exit(-1); + } + +/* + * Select the fragment size. This is propably important only when + * the program uses select(). Fragment size defines how often + * select call returns. + */ + + ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag); + +/* + * Compute total size of the buffer. It's important to use this value + * in mmap() call. + */ + + if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info)==-1) + { + perror("GETOSPACE"); + exit(-1); + } + + sz = info.fragstotal * info.fragsize; + fsz = info.fragsize; + +/* + * Call mmap(). + * + * IMPORTANT NOTE!!!!!!!!!!! + * + * Full duplex audio devices have separate input and output buffers. + * It is not possible to map both of them at the same mmap() call. The buffer + * is selected based on the prot argument in the following way: + * + * - PROT_READ (alone) selects the input buffer. + * - PROT_WRITE (alone) selects the output buffer. + * - PROT_WRITE|PROT_READ together select the output buffer. This combination + * is required in BSD to make the buffer accessible. With just PROT_WRITE + * every attempt to access the returned buffer will result in segmentation/bus + * error. PROT_READ|PROT_WRITE is also permitted in Linux with OSS version + * 3.8-beta16 and later (earlier versions don't accept it). + * + * Non duplex devices have just one buffer. When an application wants to do both + * input and output it's recommended that the device is closed and re-opened when + * switching between modes. PROT_READ|PROT_WRITE can be used to open the buffer + * for both input and output (with OSS 3.8-beta16 and later) but the result may be + * unpredictable. + */ + + if ((buf=mmap(NULL, sz, PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0))==(caddr_t)-1) + { + perror("mmap (write)"); + exit(-1); + } + printf("mmap (out) returned %08x\n", buf); + op=buf; + +/* + * op contains now a pointer to the DMA buffer + */ + +/* + * Then it's time to start the engine. The driver doesn't allow read() and/or + * write() when the buffer is mapped. So the only way to start operation is + * to togle device's enable bits. First set them off. Setting them on enables + * recording and/or playback. + */ + + tmp = 0; + ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp); + printf("Trigger set to %08x\n", tmp); + +/* + * It might be usefull to write some data to the buffer before starting. + */ + + tmp = PCM_ENABLE_OUTPUT; + ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp); + printf("Trigger set to %08x\n", tmp); + +/* + * The machine is up and running now. Use SNDCTL_DSP_GETOPTR to get the + * buffer status. + * + * NOTE! The driver empties each buffer fragmen after they have been + * played. This prevents looping sound if there are some performance problems + * in the application side. For similar reasons it recommended that the + * application uses some amout of play ahead. It can rewrite the unplayed + * data later if necessary. + */ + + nfrag = 0; + while (1) + { + struct count_info count; + int p, l, extra; + + FD_ZERO(&writeset); + FD_SET(fd, &writeset); + + tim.tv_sec = 10; + tim.tv_usec= 0; + + select(fd+1, &writeset, &writeset, NULL, NULL); +/* + * SNDCTL_DSP_GETOPTR (and GETIPTR as well) return three items. The + * bytes field returns number of bytes played since start. It can be used + * as a real time clock. + * + * The blocks field returns number of fragment transitions (interrupts) since + * previous GETOPTR call. It can be used as a method to detect underrun + * situations. + * + * The ptr field is the DMA pointer inside the buffer area (in bytes from + * the beginning of total buffer area). + */ + + if (ioctl(fd, SNDCTL_DSP_GETOPTR, &count)==-1) + { + perror("GETOPTR"); + exit(-1); + } + + nfrag += count.blocks; + +#ifdef VERBOSE + + printf("\rTotal: %09d, Fragment: %03d, Ptr: %06d", + count.bytes, nfrag, count.ptr); + fflush(stdout); +#endif + +/* + * Caution! This version doesn't check for bounds of the DMA + * memory area. It's possible that the returned pointer value is not aligned + * to fragment boundaries. It may be several samples behind the boundary + * in case there was extra delay between the actual hardware interrupt and + * the time when DSP_GETOPTR was called. + * + * Don't just call memcpy() with length set to 'fragment_size' without + * first checking that the transfer really fits to the buffer area. + * A mistake of just one byte causes seg fault. It may be easiest just + * to align the returned pointer value to fragment boundary before using it. + * + * It would be very good idea to write few extra samples to next fragment + * too. Otherwise several (uninitialized) samples from next fragment + * will get played before your program gets chance to initialize them. + * Take in count the fact thaat there are other processes batling about + * the same CPU. This effect is likely to be very annoying if fragment + * size is decreased too much. + */ + +/* + * Just a minor clarification to the above. The following line alings + * the pointer to fragment boundaries. Note! Don't trust that fragment + * size is always a power of 2. It may not be so in future. + */ + count.ptr = (count.ptr/fsz)*fsz; + +#ifdef VERBOSE + printf(" memcpy(%6d, %4d)", (dp-data), fsz); + fflush(stdout); +#endif + +/* + * Set few bytes in the beginning of next fragment too. + */ + if ((count.ptr+fsz+16) < sz) /* Last fragment? */ + extra = 16; + else + extra = 0; + + memcpy(op+count.ptr, dp, fsz+extra); + + dp += fsz; + if (dp > (data+sl-fsz)) + dp = data; + } + + exit(0); +} diff --git a/sys/i386/isa/snd/misc/pcmio.c b/sys/i386/isa/snd/misc/pcmio.c new file mode 100644 index 0000000..5bab10e --- /dev/null +++ b/sys/i386/isa/snd/misc/pcmio.c @@ -0,0 +1,236 @@ +/* + * pcmio.c -- a simple utility for controlling audio I/O + * (rate, channels, resolution...) + * + * (C) Luigi Rizzo 1998 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <machine/soundcard.h> + +char * usage_string = +"Usage: %s [-f device] [+parameters] [len:skip] file { file }\n" +"where device is the device to be used (default /dev/audio)\n" +"[len:skip] is the subsection of the file to be played\n" +"with a '-' indicating the default\n" +"and parameters is a comma-separated list containing one or more of\n" +" N the sampling speed\n" +" stereo|mono \n" +" cd|u8|alaw|ulaw|s16 data format\n" +" loop loop (play cyclically)\n" +; + +#define BLKSZ 32768 +int format=AFMT_MU_LAW; +int rate=8000 ; +int stereo = 0 ; +char dev[128]; +int audiodev; +int play = 1 ; /* default */ +int loop = 0 ; +int skip = 0; +int size = -1 ; + +extern char *optarg; +extern int optind, optopt, opterr, optreset; + +int +usage(char *s) +{ + printf(usage_string, s); + exit(0); +} + +int +parse_len(char *s) +{ + int par = -1; + int mul = 1; + int p = strlen(s); + + if (p == 0) + return -1 ; + + if (*s != '-') + par = atoi(s); + else + return -1 ; + switch(s[p-1]) { + case 'k': + case 'K': + mul = 1024; + break; + case 'm': + case 'M': + mul = 1024*1024; + break; + case 's': + mul = rate * (stereo+1); + if (format == AFMT_S16_LE) + mul += mul; + break; + } + return par*mul; +} + +void +parse_fmt(char *fmt) +{ + char *s; + int v, last = 0 ; + +again: + while (*fmt && (*fmt == ' ' || *fmt == '\t')) fmt++; + s = fmt; + while (*s && ! (*s == ',' || *s == ':' || *s == ';') ) s++; + if (*s) + *s='\0'; + else + last = 1 ; + v = atoi(fmt) ; + if (v > 0 && v < 1000000) + rate = v ; + else { + if (!strcmp(fmt, "ulaw")) format = AFMT_MU_LAW; + else if (!strcmp(fmt, "alaw")) format = AFMT_A_LAW; + else if (!strcmp(fmt, "u8")) format = AFMT_U8 ; + else if (!strcmp(fmt, "s16")) format = AFMT_S16_LE ; + else if (!strcmp(fmt, "mono")) stereo = 0; + else if (!strcmp(fmt, "stereo")) stereo = 1; + else if (!strcmp(fmt, "loop")) loop = 1; + else if (!strcmp(fmt, "rec")) play = 0; + else if (!strcmp(fmt, "play")) play = 1; + else if (!strcmp(fmt, "cd")) { + stereo = 1 ; + format = AFMT_S16_LE ; + rate = 44100; + } + } + if (last == 0) { + fmt = s+1; + goto again; + } +} + +char buf[BLKSZ]; + +int +main(int argc, char *argv[]) +{ + int i,c; + int ac = argc; + char *p; + + strcpy(dev, "/dev/audio"); + + while ( (c= getopt(argc, argv, "f:") ) != EOF ) { + switch (c) { + case 'f' : + if (optarg[0] >='0' && optarg[0] <='9') + sprintf(dev, "/dev/audio%s", optarg); + else + strcpy(dev, optarg); + break; + } + } + if (*argv[optind] == '+') { + parse_fmt(argv[optind]+1); + optind++; + } + /* + * assume a string with a "," and no "/" as a command + */ + if (strstr(argv[optind],",") && !strstr(argv[optind],"/") ) { + parse_fmt(argv[optind]); + optind++; + } + /* + * assume a string with a ":" and no "/" as a time limit + */ + if ( (p = strstr(argv[optind] , ":")) && !strstr(argv[optind],"/") ) { + *p = '\0'; + size = parse_len(argv[optind]); + skip = parse_len(p+1); + optind++; + } + printf("Using device %s, speed %d, mode 0x%08x, %s\n", + dev, rate, format, stereo ? "stereo":"mono"); + printf("using files: "); + for (i=optind; i< argc ; i++) + printf("[%d] %s, ",i, argv[i]); + printf("\n"); + + audiodev = open(dev, play ? 1 : 0); + if (audiodev < 0) { + printf("failed to open %d\n", dev); + exit(2); + } + ioctl(audiodev, SNDCTL_DSP_SETFMT, &format); + ioctl(audiodev, SNDCTL_DSP_STEREO, &stereo); + ioctl(audiodev, SNDCTL_DSP_SPEED, &rate); + printf("-- format %d,%s,0x%08x, len %d skip %d\n", + rate, stereo? "stereo":"mono",format, size, skip); + if (play) { + off_t ofs; + int limit; +again: + for (i=optind; i< argc ; i++) { + int l = -2; + int f = open(argv[i], O_RDONLY); + int sz ; + + printf("opened %s returns %d\n", argv[i], f); + if (f < 0) + continue; + limit = size; + if (skip > 0) { + ofs = skip; + lseek(f, ofs, 0 /* begin */ ); + } + sz = BLKSZ; + if (limit > 0 && limit < sz) + sz = limit ; + while ( (l = read(f, buf, sz) ) > 0 ) { + write(audiodev, buf, l); + if (limit > 0) { + limit -= l ; + if (limit > 0 && limit < sz) + sz = limit ; + if (limit <= 0 ) + break; + if (limit < sz) + sz = limit ; + } + } + close(f); + } + if (loop) + goto again; + } else { /* record */ + int l = -2; + int f ; + if (!strcmp(argv[optind], "-") ) + f = 1; + else + f = open(argv[optind], O_WRONLY | O_CREAT | O_TRUNC, 0664); + fprintf(stderr,"open %s returns %d\n", argv[optind], f); + + while ( size > 0 && (l = read(audiodev, buf, BLKSZ) ) > 0 ) { + if (l <= skip) { + skip -= l ; /* at most skip = 0 */ + continue; + } else { /* l > skip */ + l -= skip ; + if (l > size) + l = size ; + write(f, buf+skip, l); + skip = 0 ; + size -= l ; + } + } + close(f); + } +} diff --git a/sys/i386/isa/snd/misc/soundbyte.c b/sys/i386/isa/snd/misc/soundbyte.c new file mode 100644 index 0000000..2e8d8cc --- /dev/null +++ b/sys/i386/isa/snd/misc/soundbyte.c @@ -0,0 +1,275 @@ +/* + * Sound interface for Speak Freely for Unix + * + * Designed and implemented in July of 1990 by John Walker + * + * FreeBSD / voxware version + */ + +#define BUFL 8000 + +#include "speakfree.h" + +#include <sys/dir.h> +#include <sys/file.h> + +/* #include <math.h> */ +#include <errno.h> + +#include <sys/ioctl.h> +#ifdef LINUX +#include <linux/soundcard.h> +#else +#include <machine/soundcard.h> +#endif +#define AUDIO_MIN_GAIN 0 +#define AUDIO_MAX_GAIN 255 +static int abuf_size; + +#define SoundFile "/dev/audio" +#define AUDIO_CTLDEV "/dev/mixer" + +#define MAX_GAIN 100 + +struct sound_buf { + struct sound_buf *snext; /* Next sound buffer */ + int sblen; /* Length of this sound buffer */ + unsigned char sbtext[2]; /* Actual sampled sound */ +}; + +/* Local variables */ + +static int audiof = -1; /* Audio device file descriptor */ +static int Audio_fd; /* Audio control port */ +struct sound_buf *sbchain = NULL, /* Sound buffer chain links */ + *sbtail = NULL; +static int sbtotal = 0; /* Total sample bytes in memory */ +static int playing = FALSE;/* Replay in progress ? */ +/* static int playqsize; *//* Output queue size */ +static int playlen = 0; /* Length left to play */ +static unsigned char *playbuf = NULL; /* Current play pointer */ +static int squelch = 0; /* Squelch value */ + +/* Convert local gain into device parameters */ + +static unsigned +scale_gain(unsigned g) +{ + return (AUDIO_MIN_GAIN + (unsigned) + ((int) ((((double) (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) * + ((double) g / (double) MAX_GAIN)) + 0.5))); +} + +#ifdef HALF_DUPLEX +static int oldvol = -1; +#endif + +/* + * SOUNDINIT -- Open the sound peripheral and initialise for access. Return + * TRUE if successful, FALSE otherwise. + */ + +int +soundinit(int iomode) +{ + int attempts = 3; + + assert(audiof == -1); + while (attempts-- > 0) { + if ((audiof = open(SoundFile, iomode)) >= 0) { + + if ((Audio_fd = open(AUDIO_CTLDEV, O_RDWR)) < 0) { + perror(AUDIO_CTLDEV); + return FALSE; + } + /* fcntl(audiof, F_SETFL, O_NDELAY); */ +#ifndef AUDIO_BLOCKING + if (ioctl(audiof, SNDCTL_DSP_NONBLOCK, NULL) < 0) { + perror("SNDCTL_DSP_NONBLOCK"); + return FALSE; + } + if (ioctl(audiof, SNDCTL_DSP_GETBLKSIZE, &abuf_size) < 0) { + perror("SNDCTL_DSP_GETBLKSIZE"); + return FALSE; + } +#endif +#ifdef HALF_DUPLEX + if (iomode == O_RDONLY) { + if (oldvol == -1) + oldvol = soundgetvol(); + soundplayvol(0); + } else if (iomode == O_WRONLY && oldvol != -1 ) { + if (soundgetvol() == 0) + soundplayvol(oldvol); + oldvol = -1; + } +#endif + return TRUE; + } + if (errno != EINTR) + break; + fprintf(stderr, "Audio open: retrying EINTR attempt %d\n", attempts); + } + return FALSE; +} + +/* SOUNDTERM -- Close the sound device. chan=1 for play, 2:capture */ + +void +soundterm(int chan) +{ + if (audiof >= 0) { + int arg; +#ifdef AIOSTOP /* FreeBSD */ + if (chan == 2) { + arg = AIOSYNC_CAPTURE; + ioctl(audiof, AIOSTOP, &arg); + } +#endif +#ifdef SNDCTL_DSP_SYNC + if (chan == 1) + ioctl(audiof, SNDCTL_DSP_SYNC); +#endif +#ifdef HALF_DUPLEX + if (oldvol != -1) { + if (soundgetvol() == 0) + soundplayvol(oldvol); + oldvol = -1; + } +#endif + if (close(audiof) < 0) + perror("closing audio device"); + if (close(Audio_fd) < 0) + perror("closing audio control device"); + audiof = -1; + } +} + +/* SOUNDPLAY -- Begin playing a sound. */ + +void +soundplay(int len, unsigned char *buf) +{ + int ios; + + assert(audiof != -1); + while (TRUE) { + ios = write(audiof, buf, len); + if (ios == -1) + sf_usleep(100000); + else { + if (ios < len) { + buf += ios; + len -= ios; + } else + break; + } + } +} + +/* SOUNDPLAYVOL -- Set playback volume from 0 (silence) to 100 (full on). */ + +void +soundplayvol(int value) +{ + int arg; + + arg = (value << 8) | value; + + if (ioctl(Audio_fd, SOUND_MIXER_WRITE_PCM, &arg) < 0) + perror("SOUND_MIXER_WRITE_PCM"); +} + +#ifdef HALF_DUPLEX + +/* SOUNDGETVOL -- Get current playback volume. */ + +int +soundgetvol() +{ + int arg, v1, v2; + + if (ioctl(Audio_fd, SOUND_MIXER_READ_PCM, &arg) < 0) { + perror("SOUND_MIXER_READ_PCM"); + return -1; + } + v1 = arg & 0xFF; + v2 = (arg >> 8) & 0xFF; + return (v1 > v2) ? v1 : v2; +} +#endif + +/* SOUNDRECGAIN -- Set recording gain from 0 (minimum) to 100 (maximum). */ + +void +soundrecgain(int value) +{ + int arg; + + arg = (value << 8) | value; + + if (ioctl(Audio_fd, SOUND_MIXER_WRITE_RECLEV, &arg) < 0) + perror("SOUND_MIXER_WRITE_RECLEV"); +} + +/* + * SOUNDDEST -- Set destination for generated sound. If "where" is 0, + * sound goes to the built-in speaker; if 1, to the audio output jack. + */ + +void +sounddest(int where) +{ +} + +/* SOUNDGRAB -- Return audio information in the record queue. */ + +int +soundgrab(char *buf, int len) +{ + long read_size; + int c; + + read_size = len; +#ifndef AUDIO_BLOCKING + if (read_size > abuf_size) { + read_size = abuf_size; + } +#endif + while (TRUE) { + c = read(audiof, buf, read_size); + if (c < 0) { + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + c = 0; + } + } + break; + } + if (c < 0) { + perror("soundgrab"); + } + return c; +} + +/* SOUNDFLUSH -- Flush any queued sound. */ + +void +soundflush() +{ + char sb[BUFL]; + int c; + +#ifndef AUDIO_BLOCKING + while (TRUE) { + c = read(audiof, sb, BUFL < abuf_size ? BUFL : abuf_size); + if (c < 0 && errno == EAGAIN) + c = 0; + if (c < 0) + perror("soundflush"); + if (c <= 0) + break; + } +#endif +} diff --git a/sys/i386/isa/snd/misc/test.c b/sys/i386/isa/snd/misc/test.c new file mode 100644 index 0000000..2725dcc --- /dev/null +++ b/sys/i386/isa/snd/misc/test.c @@ -0,0 +1,84 @@ +/* + * test.c -- a simple utility for testing audio I/O + * + * (C) Luigi Rizzo 1997 + * + * This code mmaps the io descriptor, then every second dumps the + * relevant data structures. + * + * call it as "test unit" where unit is the unit number you want + * to see displayed. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/select.h> + +caddr_t r, w, d; + +#include </sys/i386/isa/snd/sound.h> + +void +print_d(u_long *p, int unit) +{ + snddev_info *d; + int i; + for (i=0; i<2000; i++) { + d = (snddev_info *)&(p[i]); + if ( d->magic == MAGIC(unit) ) + break; + } + if (i == 2000) { + printf("desc not found\n"); + return; + } + printf("device type %d name %s\n", d->type, d->name); + for (i=0;;i++) { + if (i%20 == 0) + printf("flags... fmt speed .bsz. c in-rl:in-dl:in-fl.ints " + " c ou-fl:ou_dl:ou-rl.ints |\n"); + printf("%08x %3x %5d %5d %d %5d %5d %5d %4d %d %5d %5d %5d %4d |\n", + d->flags, d->play_fmt, d->play_speed, d->play_blocksize, + d->dbuf_in.chan, + d->dbuf_in.rl, + d->dbuf_in.dl, + d->dbuf_in.fl, + d->dbuf_in.int_count, + + d->dbuf_out.chan, + d->dbuf_out.fl, + d->dbuf_out.dl, + d->dbuf_out.rl, + d->dbuf_out.int_count); + sleep(1); + } +} + +main(int argc, char *argv[]) +{ + int fd ; + int unit = 0; + char devn[64]; + + if (argc>1) unit=atoi(argv[1]); + sprintf(devn,"/dev/mixer%d", unit); + fd = open (devn, O_RDWR); + printf("open returns %d\n", fd); + + w = mmap(NULL, 0x10000, PROT_READ, 0, fd, 0); /* play */ + r = mmap(NULL, 0x10000, PROT_READ, 0, fd, 1<<24); /* rec */ + d = mmap(NULL, 0x2000, PROT_READ, 0, fd, 2<<24); /* desc */ + + printf("mmap: w 0x%08lx, r 0x%08lx, d 0x%08lx\n", w, r, d); + if (d && (int)d != -1 ) { + print_d((u_long *)d, unit); + } + if (w && (int)w != -1) munmap(w, 0x10000); + if (r && (int)r != -1) munmap(r, 0x10000); + if (d && (int)d != -1) munmap(d, 0x2000); + return 0; +} diff --git a/sys/i386/isa/snd/sb_dsp.c b/sys/i386/isa/snd/sb_dsp.c index 09440e1..3d75aea 100644 --- a/sys/i386/isa/snd/sb_dsp.c +++ b/sys/i386/isa/snd/sb_dsp.c @@ -92,7 +92,7 @@ snddev_info sb_op_desc = { NULL /* use generic sndread */, NULL /* use generic sndwrite */, sb_dsp_ioctl, - sndpoll, + sndselect, sbintr, sb_callback, @@ -123,7 +123,7 @@ sb_probe(struct isa_device *dev) bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); if (dev->id_iobase == -1) { dev->id_iobase = 0x220; - printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n"); + BVDDB(printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n");) if (snd_conflict(dev->id_iobase)) dev->id_iobase = 0x240; } @@ -196,6 +196,10 @@ sb_dsp_open(dev_t dev, int flags, int mode, struct proc * p) d->play_fmt = d->rec_fmt = AFMT_U8 ; break ; } + if ( (flags & FREAD) == 0) + d->rec_fmt = 0 ; + if ( (flags & FWRITE) == 0) + d->play_fmt = 0 ; d->flags |= SND_F_BUSY ; d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; @@ -250,6 +254,8 @@ sb_dsp_ioctl(dev_t dev, int cmd, caddr_t arg, int mode, struct proc * p) /* * for the remaining functions, use the default handler. + * ENOSYS means that the default handler should take care + * of implementing the ioctl. */ return ENOSYS ; @@ -292,7 +298,7 @@ again: DEB(printf("sbintr, flags 0x%08lx reason %d\n", d->flags, reason)); if ( reason & 1 ) { /* possibly a write interrupt */ if ( d->dbuf_out.dl ) - dsp_wrintr(d); + dsp_wrintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: wrintr but write DMA inactive!\n"); @@ -300,7 +306,7 @@ again: } if ( reason & 2 ) { if ( d->dbuf_in.dl ) - dsp_rdintr(d); + dsp_rdintr(d); else { if (d->bd_flags & BD_F_SB16) printf("WARNING: rdintr but read DMA inactive!\n"); @@ -340,16 +346,11 @@ sb_callback(snddev_info *d, int reason) case SND_CB_INIT : /* called with int enabled and no pending io */ dsp_speed(d); snd_set_blocksize(d); - if (d->play_fmt & AFMT_MU_LAW) + if ( (d->play_fmt & AFMT_MU_LAW) || (d->rec_fmt & AFMT_MU_LAW) ) d->flags |= SND_F_XLAT8 ; else d->flags &= ~SND_F_XLAT8 ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - return 1; - break; - case SND_CB_START : /* called with int disabled */ if (d->bd_flags & BD_F_SB16) { u_char c, c1 ; @@ -363,42 +364,40 @@ sb_callback(snddev_info *d, int reason) */ int swap = 1 ; /* default... */ - if (rd) { - /* need not to swap if channel is already correct */ - if ( d->rec_fmt == AFMT_S16_LE && b->chan > 4 ) + if (d->play_fmt == 0) { + /* do whatever the read channel wants */ + if ( d->rec_fmt == AFMT_S16_LE && d->dbuf_in.chan > 4 ) swap = 0; - if ( d->rec_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->rec_fmt != AFMT_S16_LE && d->dbuf_in.chan < 4 ) swap = 0; - if (swap && (d->flags & SND_F_WRITING || d->dbuf_out.dl)) { - /* cannot swap if writing is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break; /* XXX should return an error */ - } } else { - /* need not to swap if channel is already correct */ - if ( d->play_fmt == AFMT_S16_LE && b->chan > 4 ) + /* privilege the write channel */ + if ( d->play_fmt == AFMT_S16_LE && d->dbuf_out.chan > 4 ) swap = 0; - if ( d->play_fmt != AFMT_S16_LE && b->chan < 4 ) + if ( d->play_fmt != AFMT_S16_LE && d->dbuf_out.chan < 4 ) swap = 0; - if (swap && ( d->flags & SND_F_READING || d->dbuf_in.dl)) { - /* cannot swap if reading is already active */ - DDB(printf("sorry, DMA channel unavailable\n")); - swap = 0; - break ; /* XXX should return an error */ + if ( d->rec_fmt ) { + /* check for possible config errors. */ + if (d->rec_fmt == d->play_fmt) { + DDB(printf("sorry, read DMA channel unavailable\n")); + } } } - + DEB(printf("sb16: play_fmt %d, rec_fmt %x, swap %d\n", + d->play_fmt, d->rec_fmt, swap);) if (swap) { int c = d->dbuf_in.chan ; d->dbuf_in.chan = d->dbuf_out.chan; d->dbuf_out.chan = c ; - reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); - reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); - DEB(printf("START dma chan: play %d, rec %d\n", - d->dbuf_out.chan, d->dbuf_in.chan)); + } } + reset_dbuf(& (d->dbuf_in), SND_CHAN_RD ); + reset_dbuf(& (d->dbuf_out), SND_CHAN_WR ); + break ; + case SND_CB_START : /* called with int disabled */ + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; /* * XXX note: c1 and l should be set basing on d->rec_fmt, * but there is no choice once a 16 or 8-bit channel @@ -408,7 +407,7 @@ sb_callback(snddev_info *d, int reason) if ( b->chan > 4 ) { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA16 ; c1 = DSP_F16_SIGNED ; - l /= 2 ; + l /= 2 ; } else { c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA8 ; c1 = 0 ; @@ -421,28 +420,25 @@ sb_callback(snddev_info *d, int reason) sb_cmd3(d->io_base, c1 , l - 1) ; } else if (d->bd_flags & BD_F_ESS) { /* XXX this code is still incomplete */ - sb_cmd2(d->io_base, 0xb8, rd ? 0x0e : 0x04 ); /* auto dma */ - sb_cmd2(d->io_base, 0xa8, 2 /* chans */ ); - sb_cmd2(d->io_base, 0xb9, 2); /* demand mode */ - /* - input: 0xb8 -> 0x0e ; - 0xa8 -> channels - 0xb9 -> 2 - mono,U8: 51, d0 - mono,S16 71, f4 - st, U8 51, 98 - st, S16 71, bc - */ - } else { /* SBPro */ + } else { /* SBPro -- stereo not supported */ u_char c ; if (!rd) sb_cmd(d->io_base, DSP_CMD_SPKON); - /* code for the SB2 and SB3 */ + /* code for the SB2 and SB3, only MONO */ if (d->bd_flags & BD_F_HISPEED) - c = (rd) ? DSP_CMD_HSADC_AUTO : DSP_CMD_HSDAC_AUTO ; + c = (rd) ? 0x98 : 0x90 ; else - c = (rd) ? DSP_CMD_ADC8_AUTO : DSP_CMD_DAC8_AUTO ; - sb_cmd3(d->io_base, c , l - 1) ; + c = (rd) ? 0x2c : 0x1c ; + /* + * some ESS extensions -- they can do 16 bits + */ + if ( (rd && d->rec_fmt == AFMT_S16_LE) || + (!rd && d->play_fmt == AFMT_S16_LE) ) { + c |= 1; + l /= 2 ; + } + sb_cmd3(d->io_base, 0x48 , l - 1) ; + sb_cmd(d->io_base, c ) ; } break; @@ -451,7 +447,7 @@ sb_callback(snddev_info *d, int reason) { int cmd = DSP_CMD_DMAPAUSE_8 ; /* default: halt 8 bit chan */ if ( d->bd_flags & BD_F_SB16 && b->chan > 4 ) - cmd = DSP_CMD_DMAPAUSE_16 ; + cmd = DSP_CMD_DMAPAUSE_16 ; if (d->bd_flags & BD_F_HISPEED) { sb_reset_dsp(d->io_base); d->flags |= SND_F_INIT ; @@ -485,9 +481,9 @@ sb_reset_dsp(int io_base) { int loopc; - outb(DSP_RESET, 1); + outb(io_base + SBDSP_RST, 1); DELAY(100); - outb(DSP_RESET, 0); + outb(io_base + SBDSP_RST, 0); for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) DELAY(30); @@ -568,7 +564,9 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) d->name, dev->id_unit, d->irq); else sb_setmixer(io_base, IRQ_NR, x); - + if (d->dbuf_out.chan == d->dbuf_in.chan) { + printf("WARNING: sb: misconfigured secondary DMA channel\n"); + } sb_setmixer(io_base, DMA_NR, (1 << d->dbuf_out.chan) | (1 << d->dbuf_in.chan)); break ; @@ -599,41 +597,22 @@ sb_dsp_init(snddev_info *d, struct isa_device *dev) DELAY(20); } - if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) { + /* the ESS488 can be treated as an SBPRO */ printf("ESS488 (rev %d)\n", ess_minor & 0x0f); - else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { - if ( (ess_minor & 0xf) >= 8 ) - printf("ESS1688 (rev %d)\n", ess_minor & 0x0f); - else - printf("ESS688 (rev %d)\n", ess_minor & 0x0f); - } else break ; - d->bd_id = (ess_major << 8) | ess_minor ; - d->bd_flags |= BD_F_ESS; - /* - * ESS-specific initialization, taken from OSSFree 3.8 - */ - { - static u_char irqs[16] = { - 0, 0, 0x50, 0, 0, 0x54, 0, 0x58, - 0, 0x50, 0x5c, 0, 0, 0, 0, 0 }; - static u_char drqs[8] = { - 0x51, 0x52, 0, 0x53, 0, 0, 0, 0 }; - x = irqs[d->irq & 0xf]; - if (x == 0) - printf("ESS : invalid IRQ %d\n", d->irq); - else { - sb_cmd(io_base, 0xb1 ); /* set IRQ */ - sb_cmd(io_base, x ); - - x = drqs[ d->dbuf_out.chan & 0x7 ]; - if (x == 0) - printf("ESS : invalid DRQ %d\n", d->dbuf_out.chan); - else { - sb_cmd(io_base, 0xb2 ); /* set DRQ */ - sb_cmd(io_base, x ); - } - } + } else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) { + int rev = ess_minor & 0xf ; + if ( rev >= 8 ) + printf("ESS1868 (rev %d)\n", rev); + else + printf("ESS688 (rev %d)\n", rev); + d->audio_fmt |= AFMT_S16_LE; /* in fact it is U16_LE */ + break ; /* XXX */ + } else { + printf("Unknown card 0x%x 0x%x -- hope it is SBPRO\n", + ess_major, ess_minor); + break ; } } @@ -679,8 +658,8 @@ sb_cmd(int io_base, u_char val) int i; for (i = 0; i < 1000 ; i++) { - if ((inb(DSP_STATUS) & 0x80) == 0) { - outb(DSP_COMMAND, val); + if ((inb(io_base + SBDSP_STATUS) & 0x80) == 0) { + outb(io_base + SBDSP_CMD, val); return 1; } if (i > 10) @@ -712,32 +691,23 @@ sb_cmd2(int io_base, u_char cmd, int val) return 0; } +/* + * in the SB, there is a set of indirect "mixer" registers with + * address at offset 4, data at offset 5 + */ void sb_setmixer(int io_base, u_int port, u_int value) { u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - outb(MIXER_DATA, (u_char) (value & 0xff)); + outb(io_base + 5, (u_char) (value & 0xff)); DELAY(10); splx(flags); } -u_int -sb_get_byte(int io_base) -{ - int i; - - for (i = 1000; i; i--) - if (inb(DSP_DATA_AVAIL) & 0x80) - return inb(DSP_READ); - else - DELAY(20); - return 0xffff; -} - int sb_getmixer(int io_base, u_int port) { @@ -745,15 +715,29 @@ sb_getmixer(int io_base, u_int port) u_long flags; flags = spltty(); - outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */ DELAY(10); - val = inb(MIXER_DATA); + val = inb(io_base + 5); DELAY(10); splx(flags); return val; } +u_int +sb_get_byte(int io_base) +{ + int i; + + for (i = 1000; i; i--) + if (inb(DSP_DATA_AVAIL) & 0x80) + return inb(DSP_READ); + else + DELAY(20); + return 0xffff; +} + + /* * various utility functions for the DSP @@ -806,11 +790,12 @@ dsp_speed(snddev_info *d) } /* - * only some models can do stereo, and only if not + * This is code for the SB3.x and lower. + * Only some models can do stereo, and only if not * simultaneously using midi. + * At the moment we do not support either... */ - if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) - d->flags &= ~SND_F_STEREO; + d->flags &= ~SND_F_STEREO; /* * here enforce speed limitations. @@ -830,26 +815,12 @@ dsp_speed(snddev_info *d) RANGE(speed, 4000, max_speed); - /* - * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz - * stereo - */ -#if !defined (SM_GAMES) - /* - * Max. stereo speed is 22050 - */ - if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) - speed = 22050; -#endif - - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed *= 2; /* * Now the speed should be valid. Compute the value to be * programmed into the board. - * - * XXX stereo init is still missing... */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ @@ -878,7 +849,7 @@ dsp_speed(snddev_info *d) speed = (1000000 + tmp / 2) / tmp; } - if (d->flags & SND_F_STEREO) + if (d->flags & SND_F_STEREO) /* really unused right now... */ speed /= 2; d->play_speed = d->rec_speed = speed; @@ -1024,6 +995,79 @@ sb_mixer_set(snddev_info *d, int dev, int value) */ #if NPNP > 0 +static char *ess1868_probe(u_long csn, u_long vend_id); +static void ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device ess1868 = { + "ESS1868", + ess1868_probe, + ess1868_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, ess1868); + +static char * +ess1868_probe(u_long csn, u_long vend_id) +{ + /* + * pnp X 1 os enable drq0 3 irq0 12 port0 0x240 + */ + if (vend_id == 0x68187316) { + struct pnp_cinfo d ; + read_pnp_parms ( &d , 1 ) ; + if (d.enable == 0) { + printf("This is an ESS1868, but LDN 1 is disabled\n"); + return NULL; + } + return "ESS1868" ; + } + return NULL ; +} + +static void +ess1868_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev) +{ + struct pnp_cinfo d ; + snddev_info tmp_d ; /* patched copy of the basic snddev_info */ + int the_irq = 0 ; + + tmp_d = sb_op_desc; + snddev_last_probed = &tmp_d; + +#if 0 + read_pnp_parms ( &d , 3 ); /* disable LDN 3 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 3 ); + + read_pnp_parms ( &d , 2 ); /* disable LDN 2 */ + d.port[0] = 0 ; + d.enable = 0 ; + write_pnp_parms ( &d , 2 ); + read_pnp_parms ( &d , 0 ); /* read config base */ + tmp_d.conf_base = d.port[0]; + write_pnp_parms ( &d , 0 ); +#endif + + read_pnp_parms ( &d , 1 ) ; + dev->id_iobase = d.port[0]; + d.port[1] = 0 ; + d.port[2] = 0 ; + write_pnp_parms ( &d , 1 ); + enable_pnp_card(); + + dev->id_drq = d.drq[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = 0 /* DV_F_DUAL_DMA | (d.drq[1] ) */; + + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + pcmattach(dev); +} + static char *opti925_probe(u_long csn, u_long vend_id); static void opti925_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); @@ -1155,6 +1199,7 @@ sb16pnp_attach(u_long csn, u_long vend_id, char *name, read_pnp_parms ( &d , 0 ) ; d.port[1] = 0 ; /* only the first address is used */ dev->id_iobase = d.port[0]; + tmp_d.synth_base = d.port[2]; write_pnp_parms ( &d , 0 ); enable_pnp_card(); diff --git a/sys/i386/isa/snd/sbcard.h b/sys/i386/isa/snd/sbcard.h index 1735c14..0bd4210 100644 --- a/sys/i386/isa/snd/sbcard.h +++ b/sys/i386/isa/snd/sbcard.h @@ -11,18 +11,21 @@ extern int sbc_major, sbc_minor ; * sound blaster registers */ -#define DSP_RESET (io_base + 0x6) +#define SBDSP_RST 0x6 #define DSP_READ (io_base + 0xA) #define DSP_WRITE (io_base + 0xC) -#define DSP_COMMAND (io_base + 0xC) -#define DSP_STATUS (io_base + 0xC) +#define SBDSP_CMD 0xC +#define SBDSP_STATUS 0xC #define DSP_DATA_AVAIL (io_base + 0xE) #define DSP_DATA_AVL16 (io_base + 0xF) + +#if 0 #define MIXER_ADDR (io_base + 0x4) #define MIXER_DATA (io_base + 0x5) #define OPL3_LEFT (io_base + 0x0) #define OPL3_RIGHT (io_base + 0x2) #define OPL3_BOTH (io_base + 0x8) +#endif /* * DSP Commands. There are many, and in many cases they are used explicitly diff --git a/sys/i386/isa/snd/sound.c b/sys/i386/isa/snd/sound.c index 3f5007ca..ebb5374 100644 --- a/sys/i386/isa/snd/sound.c +++ b/sys/i386/isa/snd/sound.c @@ -50,13 +50,7 @@ * */ -#include "opt_devfs.h" - #include <i386/isa/snd/sound.h> -#ifdef DEVFS -#include <sys/devfsext.h> -#endif /* DEVFS */ - #if NPCM > 0 /* from "snd.h" */ @@ -76,7 +70,7 @@ static d_mmap_t sndmmap; static struct cdevsw snd_cdevsw = { sndopen, sndclose, sndread, sndwrite, sndioctl, nxstop, nxreset, nxdevtotty, - sndpoll, sndmmap, nxstrategy, "snd", + sndselect, sndmmap, nxstrategy, "snd", NULL, -1, }; @@ -187,7 +181,8 @@ pcmattach(struct isa_device * dev) d->io_base = dev->id_iobase ; d->irq = ffs(dev->id_irq) - 1 ; d->dbuf_out.chan = dev->id_drq ; - if (dev->id_flags != -1 && dev->id_flags & DV_F_DUAL_DMA) /* enable dma2 */ + if (dev->id_flags != -1 && dev->id_flags & DV_F_DUAL_DMA && + (dev->id_flags & DV_F_DRQ_MASK) != 4 ) /* enable dma2 */ d->dbuf_in.chan = dev->id_flags & DV_F_DRQ_MASK ; else d->dbuf_in.chan = d->dbuf_out.chan ; @@ -220,23 +215,6 @@ pcmattach(struct isa_device * dev) isadev = makedev(CDEV_MAJOR, 0); cdevsw_add(&isadev, &snd_cdevsw, NULL); -#ifdef DEVFS - /* - * XXX remember to store the returned tokens if you want to - * be able to remove the device later - */ - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_DSP, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "dsp%n", dev->id_unit); - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_DSP16, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "dspW%n", dev->id_unit); - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_AUDIO, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "audio%n", dev->id_unit); - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_CTL, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "mixer%n", dev->id_unit); - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_STATUS, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "status%n", dev->id_unit); -#endif - /* * should try and find a suitable value for id_id, otherwise * the interrupt is not registered and dispatched properly. @@ -253,36 +231,24 @@ pcmattach(struct isa_device * dev) d->magic = MAGIC(dev->id_unit); /* debugging... */ /* * and finally, call the device attach routine + * XXX I should probably use d->attach(dev) */ stat = snddev_last_probed->attach(dev); +#if 0 + /* + * XXX hooks for synt support. Try probe and attach... + */ + if (d->synth_base && opl3_probe(dev) ) { + opl3_attach(dev); + } +#endif snddev_last_probed = NULL ; return stat ; } -int -midiattach(struct isa_device * dev) -{ -#if 0 -#ifdef DEVFS - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_MIDIN, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "status%n", dev->id_unit); -#endif /* DEVFS */ -#endif - return 0 ; -} - -int -synthattach(struct isa_device * dev) -{ -#if 0 -#ifdef DEVFS - devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_SYNTH, - DV_CHR, UID_ROOT, GID_WHEEL, 0600, "status%n", dev->id_unit); -#endif /* DEVFS */ -#endif - return 0 ; -} +int midiattach(struct isa_device * dev) { return 0 ; } +int synthattach(struct isa_device * dev) { return 0 ; } struct isa_driver pcmdriver = { pcmprobe, pcmattach, "pcm" } ; @@ -296,10 +262,12 @@ pcmintr(int unit) pcm_info[unit].interrupts++; if (pcm_info[unit].isr) pcm_info[unit].isr(unit); +#if 0 /* these do not exist at the moment. */ if (midi_info[unit].isr) midi_info[unit].isr(unit); if (synth_info[unit].isr) synth_info[unit].isr(unit); +#endif } static snddev_info * @@ -342,8 +310,19 @@ get_snddev_info(dev_t dev, int *unit) if (unit) *unit = u ; - if (u > NPCM_MAX) + if (u >= NPCM_MAX || + ( pcm_info[u].io_base == 0 && (dev & 0x0f) != SND_DEV_STATUS)) { + int i; + for (i = 0 ; i < NPCM_MAX ; i++) + if (pcm_info[i].io_base) + break ; + if (i != NPCM_MAX) + printf("pcm%d: unit not configured, perhaps you want pcm%d ?\n", + u, i); + else + printf("no pcm units configured\b"); return NULL ; + } switch(dev & 0x0f) { case SND_DEV_CTL : /* /dev/mixer handled by pcm */ case SND_DEV_STATUS : /* /dev/sndstat handled by pcm */ @@ -351,16 +330,13 @@ get_snddev_info(dev_t dev, int *unit) case SND_DEV_DSP : case SND_DEV_DSP16 : case SND_DEV_AUDIO : + case SND_DEV_SEQ : /* XXX goes here... */ d = & pcm_info[u] ; break ; - case SND_DEV_SEQ : case SND_DEV_SEQ2 : - d = & synth_info[u] ; - break ; case SND_DEV_MIDIN: - d = & midi_info[u] ; - break ; default: + printf("unsupported subdevice %d\n", dev & 0xf); return NULL ; } return d ; @@ -386,13 +362,18 @@ sndopen(dev_t i_dev, int flags, int mode, struct proc * p) DEB(printf("open snd%d subdev %d flags 0x%08x mode 0x%08x\n", unit, dev & 0xf, flags, mode)); - if (d == NULL) { - printf("open: unit %d dev %d not configured, perhaps you want unit %d ?\n", - unit, dev & 0xf, unit + 1 ); + if (d == NULL) return (ENXIO) ; - } switch(dev & 0x0f) { + case SND_DEV_SEQ: /* sequencer. Hack... */ +#if 0 /* XXX hook for opl3 support */ + if (d->synth_base) + return opl3_open(i_dev, flags, mode, p); + else +#endif + return ENXIO ; + case SND_DEV_CTL : /* mixer ... */ return 0 ; /* always succeed */ @@ -423,17 +404,21 @@ sndclose(dev_t i_dev, int flags, int mode, struct proc * p) DEB(printf("close snd%d subdev %d\n", unit, dev & 0xf)); - if (d == NULL) { - printf("close: unit %d dev %d not configured\n", unit, dev & 0xf ); + if (d == NULL) return (ENXIO) ; - } - switch(dev & 0x0f) { /* only those for which close makes sense */ + + switch(dev & 0xf) { /* only those for which close makes sense */ + case SND_DEV_SEQ: +#if 0 /* XXX hook for opl3 support */ + if (d->synth_base) + return opl3_close(i_dev, flags, mode, p); + else +#endif + return ENXIO ; + case SND_DEV_AUDIO : case SND_DEV_DSP : case SND_DEV_DSP16 : - case SND_DEV_SEQ: - case SND_DEV_SEQ2: - case SND_DEV_MIDIN: if (d->close) return d->close(i_dev, flags, mode, p); } @@ -452,10 +437,9 @@ sndread(dev_t i_dev, struct uio * buf, int flag) d = get_snddev_info(dev, &unit); DEB(printf("read snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); - if (d == NULL) { - printf("read: unit %d not configured\n", unit ); + if (d == NULL) return ENXIO ; - } + if ( (dev & 0x0f) == SND_DEV_STATUS ) { int l, c; u_char *p; @@ -470,11 +454,9 @@ sndread(dev_t i_dev, struct uio * buf, int flag) p = status_buf + d->status_ptr ; d->status_ptr += l ; splx(s); - ret = uiomove(p, l, buf) ; - if (ret) - printf("pcm-stat: bad copyout\n"); - return ret ; + return uiomove(p, l, buf) ; } + if (d->read) /* device-specific read */ return d->read(i_dev, buf, flag); @@ -487,6 +469,10 @@ sndread(dev_t i_dev, struct uio * buf, int flag) /* another reader is in, deny request */ splx(s); DDB(printf("read denied, another reader is in\n")); + /* + * sleep for a while to avoid killing the machine. + */ + tsleep( (void *)s, PZERO, "sndar", hz ) ; return EBUSY ; } if ( ! FULL_DUPLEX(d) ) { /* half duplex */ @@ -494,6 +480,7 @@ sndread(dev_t i_dev, struct uio * buf, int flag) /* another writer is in, deny request */ splx(s); DDB(printf("read denied, half duplex and a writer is in\n")); + tsleep( (void *)s, PZERO, "sndaw", hz ) ; return EBUSY ; } while ( d->dbuf_out.dl ) { @@ -530,10 +517,9 @@ sndwrite(dev_t i_dev, struct uio * buf, int flag) DEB(printf("write snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); - if (d == NULL) { - printf("write: unit %d not configured\n", unit ); + if (d == NULL) return (ENXIO) ; - } + switch( dev & 0x0f) { /* only writeable devices */ case SND_DEV_MIDIN: /* XXX is this writable ? */ case SND_DEV_SEQ : @@ -558,13 +544,15 @@ sndwrite(dev_t i_dev, struct uio * buf, int flag) /* another writer is in, deny request */ splx(s); DDB(printf("write denied, another writer is in\n")); + tsleep( (void *)s, PZERO , "sndaw", hz ) ; return EBUSY ; } if ( ! FULL_DUPLEX(d) ) { /* half duplex */ if ( d->flags & SND_F_READING ) { - DDB(printf("write denied, half duplex and a reader is in\n")); /* another reader is in, deny request */ splx(s); + DDB(printf("write denied, half duplex and a reader is in\n")); + tsleep( (void *)s, PZERO, "sndar", hz ) ; return EBUSY ; } while ( d->dbuf_in.dl ) { @@ -613,9 +601,17 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) dev = minor(i_dev); d = get_snddev_info(dev, &unit); - if (d == NULL) { - printf("ioctl: unit %d not configured\n", unit ); + if (d == NULL) return (ENXIO) ; + + if ( (dev & 0x0f) == SND_DEV_SEQ ) { + /* sequencer. Hack... */ +#if 0 + if (d->synth_base) + return opl3_ioctl(i_dev, cmd, arg, mode, p) ; + else +#endif + return ENXIO ; } if (d->ioctl) ret = d->ioctl(dev, cmd, arg, mode, p); @@ -745,7 +741,7 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) break ; case AIOSYNC: - printf("AIOSYNC chan 0x%03lx pos %ld unimplemented\n", + printf("AIOSYNC chan 0x%03lx pos %d unimplemented\n", ((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos); break; @@ -812,10 +808,6 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) break ; case SNDCTL_DSP_STEREO: - if ( *(int *)arg == -1) { - *(int *)arg = (d->flags & SND_F_STEREO) ? 1 : 0 ; - break; - } if ( *(int *)arg == 0 ) d->flags &= ~SND_F_STEREO ; /* mono */ else if ( *(int *)arg == 1 ) @@ -856,7 +848,15 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) break ; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ - d->play_fmt = d->rec_fmt = *(int *)arg ; + /* + * when some card (SB16) is opened RDONLY or WRONLY, + * only one of the fields is set, the other becomes 0. + * This makes it possible to select DMA channels at runtime. + */ + if (d->play_fmt) + d->play_fmt = *(int *)arg ; + if (d->rec_fmt) + d->rec_fmt = *(int *)arg ; splx(s); if (ask_init(d)) *(int *)arg = d->play_fmt ; @@ -864,7 +864,7 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) case SNDCTL_DSP_SUBDIVIDE: /* XXX watch out, this is RW! */ - printf("SNDCTL_DSP_SUBDIVIDE yet unimplemented\n"); + DEB(printf("SNDCTL_DSP_SUBDIVIDE yet unimplemented\n");) break; case SNDCTL_DSP_SETFRAGMENT: @@ -874,15 +874,23 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) int bytes, count; bytes = *(int *)arg & 0xffff ; count = ( *(int *)arg >> 16) & 0xffff ; - if (bytes < 7) - bytes = 7 ; if (bytes > 15) bytes = 15 ; - d->play_blocksize = - d->rec_blocksize = min ( 1<< bytes, d->dbuf_in.bufsize) ; + bytes = 1 << bytes ; + if (bytes <= 1) { /* means no blocks */ + d->flags &= ~SND_F_HAS_SIZE ; + } else { + RANGE (bytes, 40, d->dbuf_out.bufsize /4); + d->play_blocksize = + d->rec_blocksize = bytes & ~3 ; /* align to multiple of 4 */ + d->flags |= SND_F_HAS_SIZE ; + } + splx(s); + ask_init(d); +#if 0 + /* XXX todo: set the buffer size to the # of fragments */ count = d->dbuf_in.bufsize / d->play_blocksize ; bytes = ffs(d->play_blocksize) - 1; -#if 0 /* * don't change arg, since it's fake anyways and some * programs might fail if we do. @@ -941,12 +949,12 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) if (b->dl) dsp_wr_dmaupdate( b ); a->bytes = b->total; - a->blocks = (b->total - b->prev_total + - d->play_blocksize -1 ) / d->play_blocksize ; + a->blocks = (b->total - b->prev_total + /* +d->play_blocksize -1*/ ) / d->play_blocksize ; a->ptr = b->rp ; /* XXX not sure... */ b->prev_total = b->total ; } - break ; + break; case SNDCTL_DSP_GETCAPS : *(int *) arg = 0x0 ; /* revision */ @@ -979,14 +987,6 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) *(int *)arg = d->mix_recsrc ; break; - case SOUND_MIXER_READ_CAPS : - /* - * XXX - This needs to be fixed when the sound driver actually - * supports multiple inputs. - */ - *(int *)arg = SOUND_CAP_EXCL_INPUT ; - break; - default: DEB(printf("default ioctl snd%d subdev %d fn 0x%08x fail\n", unit, dev & 0xf, cmd)); @@ -998,10 +998,11 @@ sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) } /* - * function to poll what is currently available. Used to be select. + * we use the name 'select', but the new "poll" interface this is + * really sndpoll. Second arg is not "rw" but "events" */ int -sndpoll(dev_t i_dev, int events, struct proc *p) +sndselect(dev_t i_dev, int rw, struct proc * p) { int dev, unit, c = 1 /* default: success */ ; snddev_info *d ; @@ -1009,15 +1010,22 @@ sndpoll(dev_t i_dev, int events, struct proc *p) dev = minor(i_dev); d = get_snddev_info(dev, &unit); - DEB(printf("sndpoll dev 0x%04x events 0x%08x\n",i_dev, events)); + DEB(printf("sndselect dev 0x%04x rw 0x%08x\n",i_dev, rw)); if (d == NULL ) { - printf("poll: unit %d not configured\n", unit ); - return ( (events & (POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM)) | POLLHUP); +#ifdef USE_POLL + return ( (rw & (POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM)) | POLLHUP); +#else + return (ENXIO) ; +#endif } - if (d->poll == NULL) - return ( (events & (POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM)) | POLLHUP); - else if (d->poll != sndpoll ) - return d->poll(i_dev, events, p); + if (d->select == NULL) +#ifdef USE_POLL + return ( (rw & (POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM)) | POLLHUP); +#else + return 1 ; /* always success ? */ +#endif + else if (d->select != sndselect ) + return d->select(i_dev, rw, p); else { /* handle it here with the generic code */ @@ -1031,7 +1039,11 @@ sndpoll(dev_t i_dev, int events, struct proc *p) * In all other cases, select will return when 1 byte is ready. */ lim = 1; - if (events & (POLLOUT | POLLWRNORM) ) { +#ifdef USE_POLL + if (rw & (POLLOUT | POLLWRNORM) ) { +#else + if (rw == FWRITE) { +#endif if ( d->flags & SND_F_HAS_SIZE ) lim = d->play_blocksize ; /* XXX fix the test here for half duplex devices */ @@ -1042,12 +1054,19 @@ sndpoll(dev_t i_dev, int events, struct proc *p) c = d->dbuf_out.fl ; if (c < lim) /* no space available */ selrecord(p, & (d->wsel)); +#ifdef USE_POLL else - revents |= events & (POLLOUT | POLLWRNORM); + revents |= rw & (POLLOUT | POLLWRNORM); +#endif splx(flags); } +#ifdef USE_POLL } - if (events & (POLLIN | POLLRDNORM)) { + if (rw & (POLLIN | POLLRDNORM)) { +#else + return c < lim ? 0 : 1 ; + } else if (rw == FREAD) { +#endif if ( d->flags & SND_F_HAS_SIZE ) lim = d->rec_blocksize ; /* XXX fix the test here */ @@ -1060,15 +1079,24 @@ sndpoll(dev_t i_dev, int events, struct proc *p) c = d->dbuf_in.rl ; if (c < lim) /* no data available */ selrecord(p, & (d->rsel)); +#ifdef USE_POLL else - revents |= events & (POLLIN | POLLRDNORM); + revents |= rw & (POLLIN | POLLRDNORM); +#endif splx(flags); } - DEB(printf("sndpoll on read: %d >= %d flags 0x%08x\n", + DEB(printf("sndselect on read: %d >= %d flags 0x%08x\n", c, lim, d->flags)); return c < lim ? 0 : 1 ; +#ifdef USE_POLL } return revents; +#else + } else { + DDB(printf("select on exceptions, unimplemented\n")); + return 1; + } +#endif } return ENXIO ; /* notreached */ } @@ -1163,8 +1191,6 @@ init_status(snddev_info *d) /* * Write the status information to the status_buf and update * status_len. There is a limit of SNDSTAT_BUF_SIZE bytes for the data. - * put_status handles this and returns 0 in case of failure. Since - * it never oveflows the buffer, we do not care to check. */ int i; @@ -1172,7 +1198,7 @@ init_status(snddev_info *d) if (status_len != 0) /* only do init once */ return ; sprintf(status_buf, - "FreeBSD Audio Driver (971117) " __DATE__ " " __TIME__ "\n" + "FreeBSD Audio Driver (980123) " __DATE__ " " __TIME__ "\n" "Installed devices:\n"); for (i = 0; i < NPCM_MAX; i++) { @@ -1188,12 +1214,18 @@ init_status(snddev_info *d) i, midi_info[i].name, midi_info[i].io_base, midi_info[i].irq, midi_info[i].dbuf_out.chan, midi_info[i].dbuf_in.chan); - if (synth_info[i].open) + if (pcm_info[i].synth_base) { + char *s = "???"; + switch (pcm_info[i].synth_type) { + case 2 : s = "OPL2"; break; + case 3 : s = "OPL3"; break; + case 4 : s = "OPL4"; break; + } + sprintf(status_buf + strlen(status_buf), - "synth%d: <%s> at 0x%x irq %d dma %d:%d\n", - i, synth_info[i].name, synth_info[i].io_base, - synth_info[i].irq, - synth_info[i].dbuf_out.chan, synth_info[i].dbuf_in.chan); + "sequencer%d: <%s> at 0x%x (not functional)\n", + i, s, pcm_info[i].synth_base); + } } status_len = strlen(status_buf) ; } @@ -1233,8 +1265,8 @@ snd_conflict(int io_base) (io_base == pcm_info[i].mix_base ) || (io_base == pcm_info[i].midi_base) || (io_base == pcm_info[i].synth_base) ) { - printf("device at 0x%x already attached as unit %d\n", - io_base, i); + BVDDB(printf("device at 0x%x already attached as unit %d\n", + io_base, i);) return 1 ; } } diff --git a/sys/i386/isa/snd/sound.h b/sys/i386/isa/snd/sound.h index 5ab9986..93dd406 100644 --- a/sys/i386/isa/snd/sound.h +++ b/sys/i386/isa/snd/sound.h @@ -1,3 +1,5 @@ +/* uncomment the next line for -current with select->poll changes */ +#define USE_POLL /* * sound.h * @@ -62,10 +64,13 @@ #include <sys/errno.h> #include <sys/malloc.h> #include <sys/buf.h> -#include <sys/poll.h> #include <i386/isa/isa_device.h> #include <machine/clock.h> /* for DELAY */ +#ifdef USE_POLL +#include <sys/poll.h> +#define d_select_t d_poll_t +#endif #else struct isa_device { int dummy ; } ; @@ -74,7 +79,7 @@ struct isa_device { int dummy ; } ; #define d_read_t void #define d_write_t void #define d_ioctl_t void -#define d_poll_t void +#define d_select_t void #endif /* KERNEL */ typedef void (irq_proc_t) (int irq); @@ -125,7 +130,7 @@ struct _snddev_info { d_read_t *read ; d_write_t *write ; d_ioctl_t *ioctl ; - d_poll_t *poll ; + d_select_t *select ; irq_proc_t *isr ; snd_callback_t *callback; @@ -152,6 +157,7 @@ struct _snddev_info { /* * whereas from here, parameters are set at runtime. + * io_base == 0 means that the board is not configured. */ int io_base ; /* primary I/O address for the board */ @@ -159,7 +165,6 @@ struct _snddev_info { int conf_base ; /* and the opti931 also has a config space */ int mix_base ; /* base for the mixer... */ int midi_base ; /* base for the midi */ - int synth_base ; /* base for the synth */ int irq ; int bd_id ; /* used to hold board-id info, eg. sb version, @@ -202,6 +207,7 @@ struct _snddev_info { #define SND_F_BUSY_DSP 0x20000000 #define SND_F_BUSY_DSP16 0x40000000 #define SND_F_BUSY_ANY 0x70000000 +#define SND_F_BUSY_SYNTH 0x80000000 /* * the next two are used to allow only one pending operation of * each type. @@ -279,6 +285,8 @@ struct _snddev_info { u_long interrupts; /* counter of interrupts */ u_long magic; #define MAGIC(unit) ( 0xa4d10de0 + unit ) + int synth_base ; /* base for the synth */ + int synth_type ; /* type of synth */ void *device_data ; /* just in case it is needed...*/ } ; @@ -434,14 +442,17 @@ typedef struct mixer_def mixer_tab[32][2]; #define MIX_NONE(name) MIX_ENT(name, 0,0,0,0, 0,0,0,0) +/* + * some macros for debugging purposes + * DDB/DEB to enable/disable debugging stuff + * BVDDB to enable debugging when bootverbose + */ #define DDB(x) x /* XXX */ +#define BVDDB(x) if (bootverbose) x #ifndef DEB #define DEB(x) #endif -#ifndef DDB -#define DDB(x) -#endif extern snddev_info pcm_info[NPCM_MAX] ; extern snddev_info midi_info[NPCM_MAX] ; @@ -458,6 +469,12 @@ int midiattach(struct isa_device * dev); int synthattach(struct isa_device * dev); /* + * functions in isa.c + */ + +int isa_dmastatus(int chan); +int isa_dmastop(int chan); +/* * DMA buffer calls */ @@ -483,7 +500,7 @@ int dsp_rdabort(snddev_info *d, int restart); void dsp_wr_dmaupdate(snd_dbuf *b); void dsp_rd_dmaupdate(snd_dbuf *b); -d_poll_t sndpoll; +d_select_t sndselect; /* * library functions (in sound.c) @@ -527,9 +544,4 @@ int sb_getmixer (int io_base, u_int port); * so it is better to make this the default behaviour */ -/* - * the following flags are for PnP cards only and are undocumented - */ -#define DV_PNP_SBCODEC 0x1 - #endif diff --git a/sys/i386/isa/snd/soundcard.h b/sys/i386/isa/snd/soundcard.h index 726cbb5..2dc1a42 100644 --- a/sys/i386/isa/snd/soundcard.h +++ b/sys/i386/isa/snd/soundcard.h @@ -866,7 +866,7 @@ typedef struct copr_msg { #define SOUND_DEVICE_LABELS { \ "Vol ", "Bass ", "Trebl", "Synth", "Pcm ", "Spkr ", "Line ", \ - "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", \ + "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", \ "Line1", "Line2", "Line3", "Digital1", "Digital2", "Digital3", \ "PhoneIn", "PhoneOut", "Video", "Radio", "Monitor"} |