/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:speaker.c 12.0$ */
/* $ACIS:speaker.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/caio/RCS/speaker.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:speaker.c 12.0$";
#endif

/* These routines control access to the speaker using the klscmd
 * call from kls.c.
 */

#include "../h/param.h"
#include "../h/conf.h"
#include "../h/uio.h"
#include "../h/errno.h"
#include "../machine/io.h"
#include "../machine/debug.h"
#include "../machinecons/kls.h"
#include "../machineio/speakerio.h"
#include "../machineio/speakervar.h"

struct	spkq	*spkfree;
struct	spkq	spkpool[SPKPOOLSZ];
struct	spk_softc spk_softc;
int	spkdebug = 0;

spkinit() 
{
	register struct spk_softc *spc = &spk_softc;

	spc->spkopen=0;
	spc->spkhead=NULL;
	spc->spkstate=SPKAWAKE;
	spkfree= (struct spkq *) klsminit(spkpool,sizeof(struct spkq),SPKPOOLSZ);
}

/*
 * spkreset may do anything in wants to the adapter directly except turn on
 * speaker complete info. No one else (other than 8051.c routines and
 * other reset routines) may talk to the adapter directly. Only klsreset
 * may call this routine!
 */
spkreset()
{
	register struct spk_softc *spc = &spk_softc;

	(void) kls_raw_cmd(EXTCMD,SPKREJ);
	/* queue a command to enable reset after other resets are done */
	klscmd(EXTCMD,SETSPKMOD,0);
	if (spc->spkhead != NULL)
		spkstart();
}

/* ARGSUSED */
spkopen(dev,flag)
	register dev_t dev;
	register int   flag;
{
	register struct spk_softc *spc = &spk_softc;
	
	DEBUGF(spkdebug,printf("SPKOPEN, flag=%d",spc->spkopen););
	if (spc->spkopen) {
		return(EBUSY);
	}
	spc->spkopen++;
	return(0);
}

/* ARGSUSED */
spkclose(dev,flag)
	register dev_t dev;
	register int   flag;
{
	register struct spk_softc *spc = &spk_softc;

	DEBUGF(spkdebug,printf("SPKCLOSE, flag=%d",spc->spkopen););
	spc->spkopen = 0;
	return(0);
}

/* ARGSUSED */
spkwrite(dev,uio) 
	dev_t dev;
	register struct uio *uio;
{
	register struct	spk_softc *spc = &spk_softc;
	register struct	spkq *qp;
	register int	highfreq,i,s;
	struct 	spk_blk	buffer;

	DEBUGF(spkdebug,printf("SPKWRITE, flag=%d",spc->spkopen););
	while (uio->uio_resid >= sizeof(struct spk_blk)) {
		if (uiomove(&buffer,sizeof(struct spk_blk),UIO_WRITE,uio))
			return(EFAULT);
		/* wait for the element in the queue to be ready (maybe this
		 * should be replaced with watermarks!)
		 */
		s=KLSSPL();
		while(!(qp= (struct spkq *) klsalloc(&spkfree)) ) {
			spc->spkstate = SPKASLEEP;
			sleep((caddr_t) &spkfree,SPKPRI);
		}
		splx(s);

		/* check validity of request */
		highfreq = buffer.freqhigh;
		for (i=7; i >= 0; i--)
			if (highfreq & (1 << i))
				break;
		qp->q_spkblk.freqhigh=(highfreq?MIN( 1 << i, SPKFREQMAX):0);
		qp->q_spkblk.freqlow = MAX(buffer.freqlow,(i ? SPKOLOMIN : SPK1LOMIN));
		qp->q_spkblk.duration=MIN(buffer.duration,SPKDURMAX);
		qp->q_spkblk.volume=MIN(buffer.volume,SPKVOLMAX);
		spkstrategy(qp);
	}
	return(0);
}

spkstrategy(qp)
	register struct spkq *qp;
{
	register struct spk_softc *spc = &spk_softc;
	register int s;

	qp->qp_next=NULL;

	s=KLSSPL();
	if (spc->spkhead==NULL) {
		spc->spkhead=qp;
		spc->spktail=qp;
		spkstart();
	} else {
		spc->spktail->qp_next=qp;
		spc->spktail=qp;
	}
	splx(s);
}

spkstart() 
{
	register struct spk_softc *spc = &spk_softc;
	register struct spk_blk *blk;


	if (spc->spkhead==NULL)
		return;
	blk = &spc->spkhead->q_spkblk;
	klscmd(EXTCMD,SPKVOLCMD+blk->volume,0);
	klscmd(SETFCHI,blk->freqhigh,0);
	klscmd(SETFCLO,blk->freqlow,0);
	klscmd(SPKDURHI,blk->duration >> 8,0);
	klscmd(SPKDURLO,blk->duration & 0xff,0);
}

spkrint()
{
	register struct spk_softc *spc = &spk_softc;
	register struct spkq *qp=spc->spkhead;

	if (spc->spkhead == NULL)    /* ignore calls if nothing on the queue */
		return;
	spc->spkhead=qp->qp_next;
	klsfree(spkfree,qp);
	if (spc->spkstate == SPKASLEEP) {
		spc->spkstate = SPKAWAKE;
		wakeup( (caddr_t)&spkfree);
	}
	if (spc->spkhead != NULL )
		spkstart();
	else
		klscmd(EXTCMD,SPKVOLCMD+CLICKVOL,0);
}

/* speaker() is called by other kernel routines which want to use the spkr.
 * It assumes the parameters passed are valid volume, frequency and time values.
 * if the speaker looks busy (lots of things on the queue) or is open, there
 * will be no attempt to add to the clutter (e.i. the request will be dropped.)
 */
speaker(vol,freqh,freql,time)
{
	register struct spk_softc *spc = &spk_softc;
	register struct spkq *qp;
	int s;

	/* don't bother the speaker is someone is trying to play a song! */
	s=KLSSPL();
	if ((spc->spkopen) && (spc->spkhead)) {
		splx(s);
		return;
	}
	/* if we can't get a queue, the spkr must be too busy to bother with */
	if (qp = (struct spkq *) klsalloc(&spkfree)) {
		qp->q_spkblk.volume=vol;
		qp->q_spkblk.duration=time;
		qp->q_spkblk.freqhigh=freqh;
		qp->q_spkblk.freqlow=freql;
		spkstrategy(qp);
	}
	splx(s);
}

spkwait()
{
	while (spk_softc.spkhead != NULL)
		delay(1);		/* call delay to force hc2 to update */
}
