aboutsummaryrefslogblamecommitdiff
path: root/sys/i386/isa/pcaudio.c
blob: 4f840619bf91b30a15aa9ac4965dffcdef64c7e7 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
   
                                        





                                                                     

                                                                     



                                                                           
                                                                           











                                                                            
                                                              

   

                
                      
 
                      
                      
                     
                     
                       
                      
                     
                      

                          
                              
 


                                
 


                         
 






                                                                    
                                                              
                                                                
                                                          









                                                                     
                                                                



                              
                              

                              





































                                                                             
            

                                  

      


                               
                                              

                                             




                                   



                                 
                                

                     
                                  

                                                                                
                                                                     
 


                                     
 
                    
                                                   
 





                               
           
                                                          
                                


 
           




                               

                                        
                                         
  









                                                          
              





                                                         
                                                         
                                              
                                                                               









                                                                          
          

               


                          




                                                                
                                               
                                


                                                           
                                 





                                        


 
           

              

                          



                                
                                                                               

                               


                                                               
                


 
           
               
 

                          
                         
                         
                                
                


 
           
                  
 

                          
                                               


                                                
                


 
          

              



                                 
 

                                                              
                             
                              

                                                                         
                        



                                                      
         
                 


 
          





                                
          

                                 
                                                                 
                   
            




                                                                                
                
 



                 
          
                                                      

















                                                          
                                                                               






                                 
          
                                                       







                                                                      
                   




                            
          

                                              
                                   





                                                             


                                                                   
                                                   
                                     
                                      

                                                                                
                                



                                                              
                 
                                          
                                  
                                               
                                  

                                  




                                                                   
                                                                             


                                                 
                                                                                






                                                         
                                                

                                                     
         



                 
          
                                                                       
 
                            












                                                                 













                                                                         
                                          


















                                                                               
                                
                                  

                         
                                

                           
                     
                         




                     
           
                                 

                                                                       



                                         
                                                            

                                       
                                                                            
                                                 
                              


                                                             
                                                                        

                                                          
                                     

                                               
                                                                       
                              
                                           



                                                                             
                 


         
 
          
                                              
 
              
                        















                                                                     
 

                         
 
 

                               
                                         
 

                  
                                     

                                                   
                                        
         
 


                                                                          
      
/*-
 * 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: pcaudio.c,v 1.44 1998/12/13 23:32:44 eivind Exp $
 */

#include "pca.h"
#if NPCA > 0
#include "opt_devfs.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/filio.h>
#include <sys/poll.h>
#include <sys/vnode.h>

#include <machine/clock.h>
#include <machine/pcaudioio.h>

#include <i386/isa/isa.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/timerreg.h>

#ifdef DEVFS
#include <sys/devfsext.h>
#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];

#define	DSP_ULAW_NOT_WANTED
#include <i386/isa/snd/ulaw.h>

static unsigned char alaw_linear[] = {
	45, 	214, 	122, 	133, 	0, 		255, 	107, 	149, 
	86, 	171, 	126, 	129, 	0, 		255, 	117, 	138, 
	13, 	246, 	120, 	135, 	0, 		255, 	99, 	157, 
	70, 	187, 	124, 	131, 	0, 		255, 	113, 	142, 
	61, 	198, 	123, 	132,  	0, 		255, 	111, 	145, 
	94, 	163, 	127, 	128, 	0, 		255, 	119, 	136, 
	29, 	230, 	121, 	134, 	0, 		255, 	103, 	153, 
	78, 	179, 	125, 	130, 	0, 		255, 	115, 	140, 
	37, 	222, 	122, 	133, 	0, 		255, 	105, 	151, 
	82, 	175, 	126, 	129, 	0, 		255, 	116, 	139, 
	5, 	254, 	120, 	135, 	0, 		255, 	97, 	159, 
	66, 	191, 	124, 	131, 	0, 		255, 	112,	143, 
	53, 	206, 	123, 	132, 	0, 		255, 	109, 	147, 
	90, 	167, 	127, 	128, 	0, 		255,	118, 	137, 
	21, 	238, 	121, 	134, 	0, 		255, 	101,	155, 
	74, 	183, 	125, 	130, 	0, 		255, 	114, 	141, 
	49, 	210, 	123, 	133, 	0, 		255, 	108, 	148, 
	88, 	169, 	127, 	129, 	0, 		255, 	118, 	138, 
	17, 	242, 	121, 	135, 	0, 		255, 	100, 	156, 
	72, 	185, 	125, 	131, 	0, 		255, 	114, 	142, 
	64, 	194, 	124, 	132, 	0, 		255, 	112, 	144, 
	96, 	161, 	128, 	128, 	1, 		255, 	120, 	136, 
	33, 	226, 	122, 	134, 	0, 		255, 	104, 	152, 
	80, 	177, 	126, 	130, 	0, 		255, 	116, 	140, 
	41, 	218, 	122, 	133, 	0, 		255, 	106, 	150, 
	84, 	173, 	126, 	129, 	0, 		255, 	117, 	139, 
	9, 	250, 	120, 	135, 	0, 		255, 	98, 	158, 
	68, 	189, 	124, 	131, 	0, 		255, 	113, 	143, 
	57, 	202, 	123, 	132, 	0, 		255, 	110, 	146, 
	92, 	165, 	127, 	128, 	0, 		255, 	119, 	137, 
	25, 	234, 	121, 	134, 	0, 		255, 	102, 	154, 
	76, 	181, 	125, 	130, 	0, 		255, 	115, 	141, 
};

#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[2] = 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