/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986,1987
 * 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/ca_atr/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.
 *
 * Speaker functions must be accomplished from planar speaker, 
 *              and cannot deal with volume arguments.
 *
 */

#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 "../ca_atr/kls.h"
#include "../machineio/speakerio.h"
#include "../machineio/speakervar.h"
#include "../ca_atr/pcif.h"	/* need to get at cbcb */

#define ONETWOEIGHT 8		/* Milliseconds in 1/128 second speaker unit */
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 it 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;
	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    s, i;
	struct spk_blk  buffer;
	register unsigned highfreq, lowfreq, atr_dur, atrfreq;

	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;
		highfreq = (highfreq ? MIN(1 << i, SPKFREQMAX) : 0);
		lowfreq = MAX(buffer.freqlow, (i ? SPKOLOMIN : SPK1LOMIN));
		atr_dur = MIN(buffer.duration, SPKDURMAX);	/* 1/128 seconds */

		/*
		 * now convert RT frequency/duration to ATR
		 * frequency/duration 
		 */
		atrfreq = (595 * (highfreq * lowfreq * 100 + highfreq * 925 + 370)) / 19200;
		atr_dur = (atr_dur * 182) / 1280;
		if (atr_dur == 0)
			atr_dur = 1;

		/* load up the queue */
		qp->q_spkblk.freqhigh = (atrfreq >> 8);
		qp->q_spkblk.freqlow = (atrfreq & 0xff);
		qp->q_spkblk.duration = atr_dur;	/* 1/18.2 seconds */
		/* valid volumes are 0 ("silent tone") and 1 ( full volume ) */
		if (highfreq == 0) {
			qp->q_spkblk.volume = 0;
		} else {
			qp->q_spkblk.volume = buffer.volume;
		}
		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);
}

int             spk_time = 1000;/* used for click very small delay */
spkstart()
{
	register struct spk_softc *spc = &spk_softc;
	register struct spk_blk *blk;
	register struct spkq *qp = spc->spkhead;
	register int    temp;
	register int    n;

	if (qp == NULL)
		return;
	blk = &spc->spkhead->q_spkblk;

	DEBUGF(spkdebug, printf("SPKSTART: calling pc_req()\n"));
	/* make request to PC */
	if (pc_req(CB_SPKREQ, (struct pcspk_cmd *) blk, SPKENT) < 0) {
		printf("spkstart: speaker request to PC failed\n");
		spkrint();

	}

}

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

	if (qp == NULL)
		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();
}

/* 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, idivisor;

	/* Don't bother the speaker if 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;	/* In 1/18.2 seconds */
		qp->q_spkblk.freqhigh = (char) freqh;
		qp->q_spkblk.freqlow = (char) freql;
		spkstrategy(qp);
	}
	splx(s);
}

spkwait()
{
	while (spk_softc.spkhead != NULL);
}
