/*
 *	/dev/audio for Atari Ste, MegaSte, TT, Falcon running Mint.
 *
 *	Based on audiodev 0.1 by Charles Briscoe-Smith, which is in
 *	turn based on lpdev 0.6 by Thierry Bousch.
 *
 *	10/28/95, Kay Roemer.
 *
 *	9705, John Blakeley - version 0.8 - now works on the Falcon, properly.
 *	9802, John Blakeley - version 0.9 - added support for 16bit F030 support.
 */

#include <string.h>
#include <osbind.h>
#include "kerbind.h"
#include "file.h"
#include "atarierr.h"
#include "audios.h"
#include "device.h"
#include "mfp.h"

#define AUDIO_VERSION	"0.9"

static long	audio_open	(FILEPTR *f);
static long	audio_write	(FILEPTR *f, char *buf, long bytes);
static long	audio_read	(FILEPTR *f, char *buf, long bytes);
static long	audio_lseek	(FILEPTR *f, long where, short whence);
static long	audio_ioctl	(FILEPTR *f, short mode, void *buf);
static long	audio_datime	(FILEPTR *f, short *timeptr, short rwflag);
static long	audio_close	(FILEPTR *f, short pid);
static long	audio_select	(FILEPTR *f, long proc, short mode);
static void	audio_unselect	(FILEPTR *f, long proc, short mode);

static long	audio_install	(void);

extern void	*u8copy   (void *, const void *, size_t);
extern void	*ulawcopy (void *, const void *, size_t);
extern void	*u16copy   (void *, const void *, size_t);
extern void	*m16copy   (void *, const void *, size_t);
extern void	*mu16copy   (void *, const void *, size_t);


DEVDRV audio_device = {
	audio_open, audio_write, audio_read, audio_lseek, audio_ioctl,
	audio_datime, audio_close, audio_select, audio_unselect
};

static struct dev_descr audio_descr = { &audio_device };

struct kerinfo *KERINFO;

long audio_rsel = 0L;
static struct flock audio_lock = { F_WRLCK, 0, 0L, 0L, -1 };

struct dmabuf dmab[2];
volatile short bufidx = 0;

struct device thedev;


extern long psg_init (struct device *);
extern long dma_init (struct device *);
extern long fal_init (struct device *);
extern long fal_mix_init (struct device *);
extern long lmc_init (struct device *);
extern long nomix_init (struct device *);

struct init_entry {
	long	(*init) (struct device *);
	char	*name;
};

static struct init_entry players[] = {
	{ fal_init, "F030-DMA" },
	{ dma_init, "STe-DMA" },
	{ psg_init, "PSG" },
	{ 0, 0 }
};

static struct init_entry mixers[] = {
	{ fal_mix_init, "F030-MIXER" },
	{ lmc_init, "LMC" },
	{ nomix_init, "NO-MIXER" },
	{ 0, 0 }
};


DEVDRV *
init (info)
	struct kerinfo *info;
{
	static char msg[200];
	long r;

	KERINFO = info;

	sprintf (msg, "\r\nAudio device for Mint, version %s\r\n",
		AUDIO_VERSION);
	c_conws (msg);
	c_conws ("(w) 1995 Charles Briscoe-Smith\r\n");
	c_conws ("(w) 1995 Kay Roemer\r\n");
	c_conws ("(w) 1997, 1998 John Blakeley\r\n");

	if (audio_install ()) {
		c_conws ("audio NOT installed.\r\n\r\n");
		return (DEVDRV *)0;
	}
	r = d_cntl (DEV_INSTALL, "u:\\dev\\audio", (long)&audio_descr);
	if (!r || r == EINVFN) {
		c_conws ("Cannot install audio device\r\n");
		return (DEVDRV *)0;
	}
	c_conws ("\r\n");
/*
	sprintf (msg, "\r\nbuffer 1 = %ld.\r\nbuffer 2 = %ld.\r\n",
			(long)&dmab[0].buf, (long)&dmab[1].buf);
	c_conws (msg);
*/
	return (DEVDRV *)1;
}

/*
 * install things
 */
static long
audio_install (void)
{
	static char msg[200];
	int i, j;

	dmab[0].used = dmab[1].used = 0;

	for (i = 0; players[i].init; ++i) {
		if (!(*players[i].init) (&thedev))
			break;
	}
	if (!players[i].init) {
		c_conws ("No sound hardware found\r\n");
		return 1;
	}
	for (j = 0; mixers[j].init; ++j) {
		if (!(*mixers[j].init) (&thedev))
			break;
	}
	if (!mixers[j].init) {
		c_conws ("No mixer hardware found\r\n");
		return 1;
	}

	thedev.copyfn = memcpy;

	sprintf (msg, "hardware: %s, %s\r\n",
		players[i].name, mixers[i].name);
	c_conws (msg);
	return 0;
}

/*
 * /dev/audio device driver
 */

static long
audio_open (fp)
	FILEPTR *fp;
{
	/* Nothing to do */
	return 0;
}

static long
audio_write (fp, buf, nbytes)
	FILEPTR *fp;
	char *buf;
	long nbytes;
{
	long r, bufsize;

	bufsize = nbytes;
	while (nbytes > 0) {
		while ((*thedev.wspace) () == 0) {
			if (fp->flags & O_NDELAY) {
				return bufsize - nbytes;
			}
			nap (100);
		}
		r = (*thedev.copyin) (buf, nbytes);
		if (r < 0) {
			if (nbytes == bufsize) {
				return r;
			}
			break;
		}
		nbytes -= r;
		buf += r;
	}
	return bufsize - nbytes;
}

static long
audio_read (fp, buf, nbytes)
	FILEPTR *fp;
	char *buf;
	long nbytes;
{
	return EACCDN;
}

static long
audio_lseek (fp, where, whence)
	FILEPTR *fp;
	long where;
	short whence;
{
	return 0;
}

static long
audio_ioctl (fp, mode, buf)
	FILEPTR *fp;
	short mode;
	void *buf;
{
	struct flock *g;
	long arg = (long)buf;

	switch (mode) {
	case AIOCSVOLUME:
	case AIOCSLVOLUME:
	case AIOCSRVOLUME:
	case AIOCSBASS:
	case AIOCSTREBLE:
		return (*thedev.mix_ioctl) (mode, buf);

	case AIOCRESET:
		(*thedev.reset) ();
		return 0;

	case AIOCSYNC:
		while (thedev.status)
			nap (50);
		return 0;

	case AIOCGBLKSIZE:
		*(long *)arg = BUFSIZE;
		break;

	case AIOCGFMTS:
		*(long *)arg = (long) thedev.format_map;
		break;

	case AIOCSFMT:
		thedev.curformat = arg;
		switch (arg) {
		case AFMT_U8:
			thedev.copyfn = u8copy;
			thedev.ssize = 8;
			break;

		case AFMT_S8:
			thedev.copyfn = memcpy;
			thedev.ssize = 8;
			break;

		case AFMT_ULAW:
			thedev.copyfn = ulawcopy;
			thedev.ssize = 8;
			break;

		case AFMT_U16:
			if (thedev.curchans == 1)
				thedev.copyfn = mu16copy;
			else
				thedev.copyfn = u16copy;
			thedev.ssize = 16;
			break;

		case AFMT_S16:
			if (thedev.curchans == 1)
				thedev.copyfn = m16copy;
			else
				thedev.copyfn = memcpy;
			thedev.ssize = 16;
			break;

		default:
			return EINVAL;
		}
		break;

	case AIOCGSPEED:
	case AIOCSSPEED:
	case AIOCGCHAN:
	case AIOCSCHAN:
		return (*thedev.ioctl) (mode, buf);

	case FIONREAD:
		*(long *)buf = (*thedev.rspace) ();
		break;

	case FIONWRITE:
		*(long *)buf = (*thedev.wspace) ();
		break;

	case F_GETLK:
		g = (struct flock *)buf;
		if (audio_lock.l_pid >= 0)
			*g = audio_lock;
		else
			g->l_type = F_UNLCK;
		break;

	case F_SETLK:
	case F_SETLKW:
		g = (struct flock *)buf;
		switch (g->l_type) {
		case F_UNLCK:
			if (!(fp->flags & O_LOCK) ||
			    g->l_pid != audio_lock.l_pid)
				return ENSLOCK;
			else {
				fp->flags &= ~O_LOCK;
				audio_lock.l_pid = -1;
				wake (IO_Q, (long)&audio_lock);
			}
			break;

		case F_RDLCK:
			TRACE(("audio: read locks are ignored"));
			break;

		case F_WRLCK:
			while (audio_lock.l_pid >= 0) {
				if (mode == F_SETLK) {
					*g = audio_lock;
					return ELOCKED;
				}
				sleep (IO_Q, (long)&audio_lock);
			}
			fp->flags |= O_LOCK;
			audio_lock.l_pid = g->l_pid;
			break;

		default:
			DEBUG (("audio: invalid lock type"));
			return EINVFN;
		}
		break;

	default:
		return EINVFN;
	}
	return 0;
}

static long
audio_datime (fp, timeptr, rwflag)
	FILEPTR *fp;
	short *timeptr;
	short rwflag;
{
	if (!rwflag) {
		timeptr[0] = t_gettime ();
		timeptr[1] = t_getdate ();
	}
	return 0;
}

static long
audio_close (fp, pid)
	FILEPTR *fp;
	short pid;
{
	if (audio_rsel)
		wakeselect (audio_rsel);
	if ((fp->flags & O_LOCK) && audio_lock.l_pid == pid) {
		fp->flags &= ~O_LOCK;
		audio_lock.l_pid = -1;
		wake (IO_Q, (long)&audio_lock);
	}
	return 0;
}

static long
audio_select (fp, proc, mode)
	FILEPTR *fp;
	long proc;
	short mode;
{
	switch (mode) {
	case O_RDONLY:
		return 1;

	case O_WRONLY:
		if ((*thedev.wspace) () > 0) {
			return 1;
		}
		if (audio_rsel == 0) {
			audio_rsel = proc;
			return 0;
		}
		return 2;

	case O_RDWR:
		return 0;
	}
	return 0;
}

static void
audio_unselect (fp, proc, mode)
	FILEPTR *fp;
	long proc;
	short mode;
{
	if (mode == O_WRONLY && proc == audio_rsel)
		audio_rsel = 0;
}


