/*- * Copyright (c) 1994-1998 Søren Schmidt * 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, * without modification, immediately at the beginning of the file. * 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. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. * * $Id$ */ #include "pca.h" #if NPCA > 0 #include "opt_devfs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define DSP_ULAW_NOT_WANTED #include #define LINEAR_ALAW_NOT_WANTED #include #ifdef DEVFS #include #endif /* DEVFS */ #define BUF_SIZE 8192 #define SAMPLE_RATE 8000 #define INTERRUPT_RATE 16000 static struct pca_status { char open; /* device open */ char queries; /* did others try opening */ unsigned char *buf[3]; /* triple buffering */ unsigned char *buffer; /* current buffer ptr */ unsigned in_use[3]; /* buffers fill */ unsigned index; /* index in current buffer */ unsigned counter; /* sample counter */ unsigned scale; /* sample counter scale */ unsigned sample_rate; /* sample rate */ unsigned processed; /* samples processed */ unsigned volume; /* volume for pc-speaker */ char encoding; /* Ulaw, Alaw or linear */ char current; /* current buffer */ unsigned char oldval; /* old timer port value */ char timer_on; /* is playback running */ struct selinfo wsel; /* select/poll status */ } pca_status; static char buffer1[BUF_SIZE]; static char buffer2[BUF_SIZE]; static char buffer3[BUF_SIZE]; static char volume_table[256]; #ifdef DEVFS static void *pca_devfs_token; static void *pcac_devfs_token; #endif static int pca_sleep = 0; static int pca_initialized = 0; static void pcaintr(struct clockframe *frame); static int pcaprobe(struct isa_device *dvp); static int pcaattach(struct isa_device *dvp); struct isa_driver pcadriver = { pcaprobe, pcaattach, "pca", }; static d_open_t pcaopen; static d_close_t pcaclose; static d_write_t pcawrite; static d_ioctl_t pcaioctl; static d_poll_t pcapoll; #define CDEV_MAJOR 24 static struct cdevsw pca_cdevsw = { pcaopen, pcaclose, noread, pcawrite, /*24*/ pcaioctl, nostop, nullreset, nodevtotty,/* pcaudio */ pcapoll, nommap, NULL, "pca", NULL, -1 }; static void pca_continue __P((void)); static void pca_init __P((void)); static void pca_pause __P((void)); static __inline void conv(const void *table, void *buff, unsigned int n) { __asm__("1:\tmovb (%2), %3\n" "\txlatb\n" "\tmovb %3, (%2)\n" "\tinc %2\n" "\tdec %1\n" "\tjnz 1b\n" : :"b" (table), "c" (n), "D" (buff), "a" ((char)n) :"bx","cx","di","ax"); } static void pca_volume(int volume) { int i, j; for (i=0; i<256; i++) { j = ((i-128)*volume)/25; /* XXX j = ((i-128)*volume)/100; */ if (j<-128) j = -128; if (j>127) j = 127; volume_table[i] = (((255-(j + 128))/4)+1); } } static void pca_init(void) { pca_status.open = 0; pca_status.queries = 0; pca_status.timer_on = 0; pca_status.buf[0] = (unsigned char *)&buffer1[0]; pca_status.buf[1] = (unsigned char *)&buffer2[0]; pca_status.buf[2] = (unsigned char *)&buffer3[0]; pca_status.buffer = pca_status.buf[0]; pca_status.in_use[0] = pca_status.in_use[1] = pca_status.in_use[3] = 0; pca_status.current = 0; pca_status.sample_rate = SAMPLE_RATE; pca_status.scale = (pca_status.sample_rate << 8) / INTERRUPT_RATE; pca_status.encoding = AUDIO_ENCODING_ULAW; pca_status.volume = 100; pca_volume(pca_status.volume); } static int pca_start(void) { int x = splhigh(); int rv = 0; /* use the first buffer */ pca_status.current = 0; pca_status.index = 0; pca_status.counter = 0; pca_status.buffer = pca_status.buf[pca_status.current]; pca_status.oldval = inb(IO_PPI) | 0x03; /* acquire the timers */ if (acquire_timer2(TIMER_LSB|TIMER_ONESHOT)) rv = -1; else if (acquire_timer0(INTERRUPT_RATE, pcaintr)) { release_timer2(); rv = -1; } else pca_status.timer_on = 1; splx(x); return rv; } static void pca_stop(void) { int x = splhigh(); /* release the timers */ release_timer0(); release_timer2(); /* reset the buffer */ pca_status.in_use[0] = pca_status.in_use[1] = pca_status.in_use[2] = 0; pca_status.index = 0; pca_status.counter = 0; pca_status.current = 0; pca_status.buffer = pca_status.buf[pca_status.current]; pca_status.timer_on = 0; splx(x); } static void pca_pause(void) { int x = splhigh(); release_timer0(); release_timer2(); pca_status.timer_on = 0; splx(x); } static void pca_continue(void) { int x = splhigh(); pca_status.oldval = inb(IO_PPI) | 0x03; acquire_timer2(TIMER_LSB|TIMER_ONESHOT); acquire_timer0(INTERRUPT_RATE, pcaintr); pca_status.timer_on = 1; splx(x); } static int pca_wait(void) { int error, x; if (!pca_status.timer_on) return 0; while (pca_status.in_use[0] || pca_status.in_use[1] || pca_status.in_use[2]) { x = spltty(); pca_sleep = 1; error = tsleep(&pca_sleep, PZERO|PCATCH, "pca_drain", 0); pca_sleep = 0; splx(x); if (error != 0 && error != ERESTART) { pca_stop(); return error; } } return 0; } static int pcaprobe(struct isa_device *dvp) { return(-1); } static int pcaattach(struct isa_device *dvp) { printf("pca%d: PC speaker audio driver\n", dvp->id_unit); pca_init(); #ifdef DEVFS pca_devfs_token = devfs_add_devswf(&pca_cdevsw, 0, DV_CHR, 0, 0, 0600, "pcaudio"); pcac_devfs_token = devfs_add_devswf(&pca_cdevsw, 128, DV_CHR, 0, 0, 0600, "pcaudioctl"); #endif /*DEVFS*/ return 1; } static int pcaopen(dev_t dev, int flags, int fmt, struct proc *p) { /* audioctl device can always be opened */ if (minor(dev) == 128) return 0; if (minor(dev) > 0) return ENXIO; if (!pca_initialized) { pca_init(); pca_initialized = 1; } /* audio device can only be open by one process */ if (pca_status.open) { pca_status.queries = 1; return EBUSY; } pca_status.buffer = pca_status.buf[0]; pca_status.in_use[0] = pca_status.in_use[1] = pca_status.in_use[2] = 0; pca_status.timer_on = 0; pca_status.open = 1; pca_status.processed = 0; return 0; } static int pcaclose(dev_t dev, int flags, int fmt, struct proc *p) { /* audioctl device can always be closed */ if (minor(dev) == 128) return 0; if (minor(dev) > 0) return ENXIO; /* audio device close drains all output and restores timers */ pca_wait(); pca_stop(); pca_status.open = 0; return 0; } static int pcawrite(dev_t dev, struct uio *uio, int flag) { int count, error, which, x; /* only audio device can be written */ if (minor(dev) > 0) return ENXIO; while ((count = min(BUF_SIZE, uio->uio_resid)) > 0) { if (pca_status.in_use[0] && pca_status.in_use[1] && pca_status.in_use[2]) { if (flag & IO_NDELAY) return EWOULDBLOCK; x = spltty(); pca_sleep = 1; error = tsleep(&pca_sleep, PZERO|PCATCH, "pca_wait", 0); pca_sleep = 0; splx(x); if (error != 0 && error != ERESTART) { pca_stop(); return error; } } if (!pca_status.in_use[0]) which = 0; else if (!pca_status.in_use[1]) which = 1; else which = 2; if (count && !pca_status.in_use[which]) { uiomove(pca_status.buf[which], count, uio); pca_status.processed += count; switch (pca_status.encoding) { case AUDIO_ENCODING_ULAW: conv(ulaw_dsp, pca_status.buf[which], count); break; case AUDIO_ENCODING_ALAW: conv(alaw_linear, pca_status.buf[which], count); break; case AUDIO_ENCODING_RAW: break; } pca_status.in_use[which] = count; if (!pca_status.timer_on) if (pca_start()) return EBUSY; } } return 0; } static int pcaioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) { audio_info_t *auptr; switch(cmd) { case AUDIO_GETINFO: auptr = (audio_info_t *)data; auptr->play.sample_rate = pca_status.sample_rate; auptr->play.channels = 1; auptr->play.precision = 8; auptr->play.encoding = pca_status.encoding; auptr->play.gain = pca_status.volume; auptr->play.port = 0; auptr->play.samples = pca_status.processed; auptr->play.eof = 0; auptr->play.pause = !pca_status.timer_on; auptr->play.error = 0; auptr->play.waiting = pca_status.queries; auptr->play.open = pca_status.open; auptr->play.active = pca_status.timer_on; return 0; case AUDIO_SETINFO: auptr = (audio_info_t *)data; if (auptr->play.sample_rate != (unsigned int)~0) { pca_status.sample_rate = auptr->play.sample_rate; pca_status.scale = (pca_status.sample_rate << 8) / INTERRUPT_RATE; } if (auptr->play.encoding != (unsigned int)~0) { pca_status.encoding = auptr->play.encoding; } if (auptr->play.gain != (unsigned int)~0) { pca_status.volume = auptr->play.gain; pca_volume(pca_status.volume); } if (auptr->play.pause != (unsigned char)~0) { if (auptr->play.pause) pca_pause(); else pca_continue(); } return 0; case AUDIO_DRAIN: case AUDIO_COMPAT_DRAIN: return pca_wait(); case AUDIO_FLUSH: case AUDIO_COMPAT_FLUSH: pca_stop(); return 0; case FIONBIO: return 0; } return ENXIO; } static void pcaintr(struct clockframe *frame) { if (pca_status.index < pca_status.in_use[pca_status.current]) { disable_intr(); __asm__("outb %0,$0x61\n" "andb $0xFE,%0\n" "outb %0,$0x61" : : "a" ((char)pca_status.oldval) ); __asm__("xlatb\n" "outb %0,$0x42" : : "a" ((char)pca_status.buffer[pca_status.index]), "b" (volume_table) ); enable_intr(); pca_status.counter += pca_status.scale; pca_status.index = (pca_status.counter >> 8); } if (pca_status.index >= pca_status.in_use[pca_status.current]) { pca_status.index = pca_status.counter = 0; pca_status.in_use[pca_status.current] = 0; pca_status.current++; if (pca_status.current > 2) pca_status.current = 0; pca_status.buffer = pca_status.buf[pca_status.current]; if (pca_sleep) wakeup(&pca_sleep); if (pca_status.wsel.si_pid) { selwakeup((struct selinfo *)&pca_status.wsel.si_pid); pca_status.wsel.si_pid = 0; pca_status.wsel.si_flags = 0; } } } static int pcapoll(dev_t dev, int events, struct proc *p) { int s; struct proc *p1; int revents = 0; s = spltty(); if (events & (POLLOUT | POLLWRNORM)) if (!pca_status.in_use[0] || !pca_status.in_use[1] || !pca_status.in_use[2]) revents |= events & (POLLOUT | POLLWRNORM); else { if (pca_status.wsel.si_pid && (p1=pfind(pca_status.wsel.si_pid)) && p1->p_wchan == (caddr_t)&selwait) pca_status.wsel.si_flags = SI_COLL; else pca_status.wsel.si_pid = p->p_pid; } splx(s); return (revents); } static pca_devsw_installed = 0; static void pca_drvinit(void *unused) { dev_t dev; if( ! pca_devsw_installed ) { dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev,&pca_cdevsw, NULL); pca_devsw_installed = 1; } } SYSINIT(pcadev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,pca_drvinit,NULL) #endif