/*
 * sound/386bsd/soundcard.c
 * 
 * Soundcard driver for 386BSD.
 * 
 * Copyright by Hannu Savolainen 1993
 * 
 * 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. 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.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 * $FreeBSD: src/sys/i386/isa/sound/soundcard.c,v 1.87 1999/12/20 18:05:01 eivind Exp $
 *
 */
#include <i386/isa/sound/sound_config.h>
#if NSND > 0	/* from "snd.h" */
#include "uart.h"

#include <sys/select.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <sys/mman.h>

#include <i386/isa/isa_device.h>


/*
**  Register definitions for DMA controller 1 (channels 0..3):
*/
#define	DMA1_CHN(c)	(IO_DMA1 + 1*(2*(c)))	/* addr reg for channel c */
#define	DMA1_SMSK	(IO_DMA1 + 1*10)	/* single mask register */
#define	DMA1_MODE	(IO_DMA1 + 1*11)	/* mode register */
#define	DMA1_FFC	(IO_DMA1 + 1*12)	/* clear first/last FF */

/*
**  Register definitions for DMA controller 2 (channels 4..7):
*/
#define	DMA2_CHN(c)	(IO_DMA2 + 2*(2*(c)))	/* addr reg for channel c */
#define	DMA2_SMSK	(IO_DMA2 + 2*10)	/* single mask register */
#define	DMA2_MODE	(IO_DMA2 + 2*11)	/* mode register */
#define	DMA2_FFC	(IO_DMA2 + 2*12)	/* clear first/last FF */


#define FIX_RETURN(ret) {if ((ret)<0) return -(ret); else return 0;}

static int      soundcards_installed = 0; /* Number of installed soundcards */
static int      soundcard_configured = 0;

static struct fileinfo files[SND_NDEVS];
struct selinfo  selinfo[SND_NDEVS >> 4];

int
MIDIbuf_poll (int dev, struct fileinfo *file, int events, select_table * wait);

int
audio_poll(int dev, struct fileinfo * file, int events, select_table * wait);

int
sequencer_poll (int dev, struct fileinfo *file, int events, select_table * wait);

static int sndprobe    __P((struct isa_device *));
static int sndattach   __P((struct isa_device *));

static d_open_t sndopen;
static d_close_t sndclose;
static d_ioctl_t sndioctl;
static d_read_t sndread;
static d_write_t sndwrite;
static d_poll_t sndpoll;
static d_mmap_t sndmmap;

static char     driver_name[] = "snd";

#define CDEV_MAJOR 30
static struct cdevsw snd_cdevsw = {
	/* open */	sndopen,
	/* close */	sndclose,
	/* read */	sndread,
	/* write */	sndwrite,
	/* ioctl */	sndioctl,
	/* poll */	sndpoll,
	/* mmap */	sndmmap,
	/* strategy */	nostrategy,
	/* name */	driver_name,
	/* maj */	CDEV_MAJOR,
	/* dump */	nodump,
	/* psize */	nopsize,
	/* flags */	0,
	/* bmaj */	-1
};




static void     sound_mem_init(void);

/*
 * for each "device XXX" entry in the config file, we have
 * a struct isa_driver which is linked into isa_devtab_null[]
 *
 * XXX It is a bit stupid to call the generic routine so many times and
 * switch then to the specific one, but the alternative way would be
 * to replicate some code in the probe/attach routines.
 */

struct isa_driver opldriver = {sndprobe, sndattach, "opl"};
struct isa_driver trixdriver = {sndprobe, sndattach, "trix"};
struct isa_driver trixsbdriver = {sndprobe, sndattach, "trixsb"};
struct isa_driver sbdriver = {sndprobe, sndattach, "sb"};
struct isa_driver sbxvidriver = {sndprobe, sndattach, "sbxvi"};
struct isa_driver sbmididriver = {sndprobe, sndattach, "sbmidi"};
struct isa_driver awedriver    = {sndprobe, sndattach, "awe"};
struct isa_driver pasdriver = {sndprobe, sndattach, "pas"};
struct isa_driver mpudriver = {sndprobe, sndattach, "mpu"};
struct isa_driver gusdriver = {sndprobe, sndattach, "gus"};
struct isa_driver gusxvidriver = {sndprobe, sndattach, "gusxvi"};
struct isa_driver gusmaxdriver = {sndprobe, sndattach, "gusmax"};
struct isa_driver uartdriver = {sndprobe, sndattach, "uart"};
struct isa_driver mssdriver = {sndprobe, sndattach, "mss"};
struct isa_driver cssdriver = {sndprobe, sndattach, "css"};
struct isa_driver sscapedriver = {sndprobe, sndattach, "sscape"};
struct isa_driver sscape_mssdriver = {sndprobe, sndattach, "sscape_mss"};
struct isa_driver nssdriver = {sndprobe, sndattach, "nss"};

short ipri_to_irq(u_short ipri);

static ointhand2_t sndintr;

u_long
get_time(void)
{
    struct timeval  timecopy;

    getmicrotime(&timecopy);
    return timecopy.tv_usec / (1000000 / hz) +
		(u_long) timecopy.tv_sec * hz;
}

static int
sndmmap( dev_t dev, vm_offset_t offset, int nprot )
{
	struct dma_buffparms * dmap;
	u_int min = minor(dev) >> 4;

	if (min > 0 ) return (-1);

	dmap =	audio_devs[min]->dmap_out;

	if (nprot & PROT_EXEC)
		return( -1 );
	dmap->mapping_flags |= DMA_MAP_MAPPED ;
	return( i386_btop(vtophys(dmap->raw_buf) + offset) );
}


static int
sndread(dev_t dev, struct uio * buf, int flag)
{
    int             count = buf->uio_resid;
    u_int min = minor(dev);

    FIX_RETURN(sound_read_sw(min, &files[min], buf, count));
}


static int
sndwrite(dev_t dev, struct uio * buf, int flag)
{
    int             count = buf->uio_resid;
    u_int min = minor(dev);

    FIX_RETURN(sound_write_sw(min, &files[min], buf, count));
}

static int
sndopen(dev_t dev, int flags, int mode, struct proc * p)
{
    int             retval;
    struct fileinfo tmp_file;
    u_int min = minor(dev);

    if (!soundcard_configured && min) {
	printf("SoundCard Error: soundcard system has not been configured\n");
	return ENODEV ;
    }
    tmp_file.mode = 0;

    if (flags & FREAD && flags & FWRITE)
	tmp_file.mode = OPEN_READWRITE;
    else if (flags & FREAD)
	tmp_file.mode = OPEN_READ;
    else if (flags & FWRITE)
	tmp_file.mode = OPEN_WRITE;

    selinfo[min >> 4].si_pid = 0;
    selinfo[min >> 4].si_flags = 0;
    if ((retval = sound_open_sw(min, &tmp_file)) < 0)
	FIX_RETURN(retval);

    bcopy((char *) &tmp_file, (char *) &files[min], sizeof(tmp_file));

    FIX_RETURN(retval);
}


static int
sndclose(dev_t dev, int flags, int mode, struct proc * p)
{
    u_int min = minor(dev);

    sound_release_sw(min, &files[min]);
    return 0 ;
}

static int
sndioctl(dev_t dev, u_long cmd, caddr_t arg, int mode, struct proc * p)
{
    u_int min = minor(dev);
    FIX_RETURN(sound_ioctl_sw(min, &files[min], cmd, arg));
}

int
sndpoll(dev_t dev, int events, struct proc * p)
{
    u_int min = minor(dev);

    /* printf ("snd_select(dev=%d, rw=%d, pid=%d)\n", min, rw, p->p_pid); */
#ifdef ALLOW_POLL
    switch (min & 0x0f) {
#ifdef CONFIG_SEQUENCER
    case SND_DEV_SEQ:
    case SND_DEV_SEQ2:
	return sequencer_poll(min, &files[min], events, p);
	break;
#endif

#ifdef CONFIG_MIDI
    case SND_DEV_MIDIN:
	return MIDIbuf_poll(min, &files[min], events, p);
	break;
#endif

#ifdef CONFIG_AUDIO
    case SND_DEV_DSP:
    case SND_DEV_DSP16:
    case SND_DEV_AUDIO:

	return audio_poll(min, &files[min], events, p);
	break;
#endif

    default:
	return 0;
    }

#endif	/* ALLOW_POLL */
    DEB(printf("sound_ioctl(min=%d, cmd=0x%x, arg=0x%x)\n", min, cmd, arg));

    return 0 ;
}

/* XXX this should become ffs(ipri), perhaps -1 lr 970705 */
short
ipri_to_irq(u_short ipri)
{
    /*
     * Converts the ipri (bitmask) to the corresponding irq number
     */
    int             irq;

    for (irq = 0; irq < 16; irq++)
	if (ipri == (1 << irq))
	    return irq;

    return -1;		/* Invalid argument */
}

static int
driver_to_voxunit(struct isa_driver * driver)
{
    /*
     * converts a sound driver pointer into the equivalent VoxWare device
     * unit number
     */
    if (driver == &opldriver)
	return (SNDCARD_ADLIB);
    else if (driver == &sbdriver)
	return (SNDCARD_SB);
    else if (driver == &pasdriver)
	return (SNDCARD_PAS);
    else if (driver == &gusdriver)
	return (SNDCARD_GUS);
    else if (driver == &mpudriver)
	return (SNDCARD_MPU401);
    else if (driver == &sbxvidriver)
	return (SNDCARD_SB16);
    else if (driver == &sbmididriver)
	return (SNDCARD_SB16MIDI);
    else if(driver == &awedriver)
	return(SNDCARD_AWE32);
    else if (driver == &uartdriver)
	return (SNDCARD_UART6850);
    else if (driver == &gusdriver)
	return (SNDCARD_GUS16);
    else if (driver == &mssdriver)
	return (SNDCARD_MSS);
    else if (driver == &cssdriver)
	return (SNDCARD_CS4232);
    else if (driver == &sscapedriver)
	return(SNDCARD_SSCAPE);
    else if (driver == &sscape_mssdriver)
	return(SNDCARD_SSCAPE_MSS);
    else if (driver == &trixdriver)
	return (SNDCARD_TRXPRO);
    else if (driver == &trixsbdriver)
	return (SNDCARD_TRXPRO_SB);
    else if (driver == &nssdriver)
	return (SNDCARD_NSS);
    else
	return (0);
}

/*
 * very dirty: tmp_osp is allocated in sndprobe, and used at the next
 * call in sndattach
 */

static sound_os_info *temp_osp;

/*
 * sndprobe is called for each isa_device. From here, a voxware unit
 * number is determined, and the appropriate probe routine is selected.
 * The parameters from the config line are passed to the hw_config struct.
 */

static int
sndprobe(struct isa_device * dev)
{
    struct address_info hw_config;
    int             unit;

    temp_osp = (sound_os_info *)malloc(sizeof(sound_os_info),
	    M_DEVBUF, M_NOWAIT);
    if (!temp_osp)
	panic("SOUND: Cannot allocate memory\n");

    /*
     * get config info from the kernel config. These may be overridden
     * by the local autoconfiguration routines though (e.g. pnp stuff).
     */

    hw_config.io_base = dev->id_iobase;
    hw_config.irq = ipri_to_irq(dev->id_irq);
    hw_config.dma = dev->id_drq;

    /*
     * misuse the flags field for read dma. Note that, to use 0 as
     * read dma channel, one of the high bits should be set.  lr970705 XXX
     */

    if (dev->id_flags != 0)
	hw_config.dma2 = dev->id_flags & 0x7;
    else
	hw_config.dma2 = -1;

    hw_config.always_detect = 0;
    hw_config.name = NULL;
    hw_config.card_subtype = 0;

    temp_osp->unit = dev->id_unit;
    hw_config.osp = temp_osp;
    unit = driver_to_voxunit(dev->id_driver);

    if (sndtable_probe(unit, &hw_config)) {
	dev->id_iobase = hw_config.io_base;
	dev->id_irq =  hw_config.irq == -1 ? 0 : (1 << hw_config.irq);
	dev->id_drq = hw_config.dma;

	if (hw_config.dma != hw_config.dma2 && ( hw_config.dma2 != -1))
	    dev->id_flags = hw_config.dma2 | 0x100; /* XXX lr */
	else
	    dev->id_flags = 0;
	return TRUE;
    }
    return 0;
}

static int
sndattach(struct isa_device * dev)
{
    int             unit;
    static int      midi_initialized = 0;
    static int      seq_initialized = 0;
    struct address_info hw_config;
    char   *dname;

    /*
     * Associate interrupt handlers with devices.  XXX this may be incomplete.
     */
    dname = dev->id_driver->name;
#if defined(CONFIG_AD1848)
    if (strcmp(dname, "css") == 0 || strcmp(dname, "gusxvi") == 0 ||
	strcmp(dname, "mss") == 0)
	dev->id_ointr = adintr;
#endif
#ifdef CONFIG_GUS
    if (strcmp(dname, "gus") == 0)
	dev->id_ointr = gusintr;
#endif
#ifdef CONFIG_PAS
    if (strcmp(dname, "pas") == 0)
	dev->id_ointr = pasintr;
#endif
#if NSB > 0 && (defined(CONFIG_MIDI) || defined(CONFIG_AUDIO))
    if (strcmp(dname, "sb") == 0)
	dev->id_ointr = sbintr;
#endif
    if (strcmp(dname, "sscape_mss") == 0)
	dev->id_ointr = sndintr;
#if NSSCAPE > 0
    if (strcmp(dname, "sscape") == 0 || strcmp(dname, "trix") == 0)
	dev->id_ointr = sscapeintr;
#endif
#if NUART > 0
    if (strcmp(dname, "uart0") == 0)
	dev->id_ointr = m6850intr;
#endif
#if NMPU > 0 && defined(CONFIG_MIDI)
    if (strcmp(dname, "mpu") == 0)
	dev->id_ointr = mpuintr;
#endif
#if NNSS > 0
    if (strcmp(dname, "nss") == 0)
	dev->id_ointr = nssintr;
#endif

    unit = driver_to_voxunit(dev->id_driver);
    hw_config.io_base = dev->id_iobase;
    hw_config.irq = ipri_to_irq(dev->id_irq);
    hw_config.dma = dev->id_drq;

    /* misuse the flags field for read dma */
    if (dev->id_flags != 0)
	hw_config.dma2 = dev->id_flags & 0x7;
    else
	hw_config.dma2 = -1;

    hw_config.card_subtype = 0;
    hw_config.osp = temp_osp;

    if (!unit)
	return FALSE;

    if (!(sndtable_init_card(unit, &hw_config))) {	/* init card */
	printf(" <Driver not configured>");
	return FALSE;
    }
    /*
     * Init the high level sound driver
     */

    if (!(soundcards_installed = sndtable_get_cardcount())) {
	DDB(printf("No drivers actually installed\n"));
	return FALSE;	/* No cards detected */
    }
    printf("\n");

#ifdef CONFIG_AUDIO
    if (num_audiodevs) {	/* Audio devices present */
	DMAbuf_init();
	sound_mem_init();
    }
    soundcard_configured = 1;
#endif

    if (num_midis && !midi_initialized)
	midi_initialized = 1;

    if ((num_midis + num_synths) && !seq_initialized) {
	seq_initialized = 1;
	sequencer_init();
    }

    cdevsw_add(&snd_cdevsw);
#define GID_SND GID_GAMES
#define UID_SND UID_ROOT
#define PERM_SND 0660
    /*
     *	make links to first successfully probed device, don't do it if
     *	duplicate creation of same node failed (ie. bad cookie returned)
     */
    if (dev->id_driver == &opldriver){
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_SEQ,
	    UID_SND, GID_SND, PERM_SND, "sequencer%r", dev->id_unit);
    } else if (dev->id_driver == &mpudriver || 
               dev->id_driver == &sbmididriver ||
	       dev->id_driver == &uartdriver){
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_MIDIN,
	    UID_SND, GID_SND, PERM_SND, "midi%r", dev->id_unit);
    } else {
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_DSP,
	    UID_SND, GID_SND, PERM_SND, "dsp%r", dev->id_unit);
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_DSP16,
	    UID_SND, GID_SND, PERM_SND, "dspW%r", dev->id_unit);
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_AUDIO,
	    UID_SND, GID_SND, PERM_SND, "audio%r", dev->id_unit);
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_CTL,
	    UID_SND, GID_SND, PERM_SND, "mixer%r", dev->id_unit);
	make_dev(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_STATUS,
	    UID_SND, GID_SND, PERM_SND, "sndstat%r", dev->id_unit);
    }
    return TRUE;
}


#ifdef CONFIG_AUDIO

static void
alloc_dmap(int dev, int chan, struct dma_buffparms * dmap)
{
    char           *tmpbuf;
    int            i;

    tmpbuf = contigmalloc(audio_devs[dev]->buffsize, M_DEVBUF, M_NOWAIT,
		0ul, 0xfffffful, 1ul, chan & 4 ? 0x20000ul : 0x10000ul);
    if (tmpbuf == NULL)
	printf("soundcard buffer alloc failed \n");

    if (tmpbuf == NULL) {
	printf("snd: Unable to allocate %d bytes of buffer\n",
	       2 * (int) audio_devs[dev]->buffsize);
	return;
    }
    dmap->raw_buf = tmpbuf;
    /*
     * Use virtual address as the physical address, since isa_dmastart
     * performs the phys address computation.
     */

    dmap->raw_buf_phys = (uintptr_t) tmpbuf;
    for (i = 0; i < audio_devs[dev]->buffsize; i++)   *tmpbuf++ = 0x80; 

}

static void
sound_mem_init(void)
{
    int             dev;
    static u_long dsp_init_mask = 0;

    for (dev = 0; dev < num_audiodevs; dev++)	/* Enumerate devices */
	if (!(dsp_init_mask & (1 << dev)))	/* Not already done */
	    if (audio_devs[dev]->dmachan1 >= 0) {
		dsp_init_mask |= (1 << dev);
		audio_devs[dev]->buffsize = DSP_BUFFSIZE;
		/* Now allocate the buffers */
		alloc_dmap(dev, audio_devs[dev]->dmachan1,
			audio_devs[dev]->dmap_out);
		if (audio_devs[dev]->flags & DMA_DUPLEX)
		    alloc_dmap(dev, audio_devs[dev]->dmachan2,
			    audio_devs[dev]->dmap_in);
	    }	/* for dev */
}

#endif


int
snd_ioctl_return(int *addr, int value)
{
    if (value < 0)
	return value;	/* Error */
    suword(addr, value);
    return 0;
}

#define MAX_UNIT 50
typedef void    (*irq_proc_t) (int irq);
static irq_proc_t irq_proc[MAX_UNIT] = {NULL};
static int      irq_irq[MAX_UNIT] = {0};

int
snd_set_irq_handler(int int_lvl, void (*hndlr) (int), sound_os_info * osp)
{
    if (osp->unit >= MAX_UNIT) {
	printf("Sound error: Unit number too high (%d)\n", osp->unit);
	return 0;
    }
    irq_proc[osp->unit] = hndlr;
    irq_irq[osp->unit] = int_lvl;
    return 1;
}

static void
sndintr(int unit)
{
    if ( (unit >= MAX_UNIT) || (irq_proc[unit] == NULL) )
	return;

    irq_proc[unit] (irq_irq[unit]);	/* Call the installed handler */
}

void
conf_printf(char *name, struct address_info * hw_config)
{
    if (!trace_init)
	return;

    printf("snd0: <%s> ", name);
#if 0
    if (hw_config->io_base != -1 ) 
    printf("at 0x%03x", hw_config->io_base);

    if (hw_config->irq != -1 )
	printf(" irq %d", hw_config->irq);

    if (hw_config->dma != -1 || hw_config->dma2 != -1) {
	printf(" dma %d", hw_config->dma);
	if (hw_config->dma2 != -1)
	    printf(",%d", hw_config->dma2);
    }
#endif

}

void
conf_printf2(char *name, int base, int irq, int dma, int dma2)
{
    if (!trace_init)
	return;

    printf("snd0: <%s> ", name);
#if 0
    if (hw_config->io_base != -1 ) 
    printf("at 0x%03x", hw_config->io_base);

    if (irq)
	printf(" irq %d", irq);

    if (dma != -1 || dma2 != -1) {
	printf(" dma %d", dma);
	if (dma2 != -1)
	    printf(",%d", dma2);
    }
#endif

}


void tenmicrosec (int j)
{
  int             i, k;
  for (k = 0; k < j/10 ; k++) {
      for (i = 0; i < 16; i++)
	  inb (0x80);
  }
}

#endif	/* NSND > 0 */




