/* sound_pnp.c: SB16 PnP interface */
/*
 * $Header$
 *
 * $Log: sound_pnp.c,v $
 *
 *
 */

/*
 * (c) Copyright 1996  D.W.Howells <dwh@nexor.co.uk>,
 */

#include <asm/bitops.h>
#include <asm/errno.h>
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/pnp.h>
#include "sound_config.h"

/* SB16 hardware */
extern int      sbc_base;	/* SB/SB16 I/O base */
int	sbc_dma16=-1, sbc_dma8=-1;

static int sb16pnp_attach(pnp_device *, int);
static int sb16pnp_control(pnp_device *, PNP_DRV_CTRL);
static int sb16pnp_reconfigure(pnp_device *, const pnp_possibility *,
			       pnp_config *);

static pnp_driver sb16_pnp = {
    NULL,
    "SB16",
    PNPDRVID('C','T','L',0x003,1), { 0, 0 },
    &sb16pnp_attach,
    &sb16pnp_control,
    &sb16pnp_reconfigure,
};

static pnp_device *sb16_pnpdev = NULL;

/*****************************************************************************/
/* control access to the audio driver - can't configure whilst open and can't
 * open whilst configuring. However, potentially multiple opens possible,
 */
static struct semaphore audio_driver_lock_queue = { 1, NULL };
static volatile int audio_driver_lock_count = 0;
int audio_driver_lock(int inconfig)
{
    int rt = 0;
    down(&audio_driver_lock_queue);
    if (inconfig) {
	if (audio_driver_lock_count)
	    rt = -EBUSY;
	else
	    audio_driver_lock_count--;
    } else {
	if (audio_driver_lock_count<0)
	    rt = -EBUSY;
	else
	    audio_driver_lock_count++;
    }
    up(&audio_driver_lock_queue);
    return rt;
}
void audio_driver_unlock(int inconfig)
{
    if (inconfig)
	audio_driver_lock_count++;
    else
	audio_driver_lock_count--;
}

/*****************************************************************************/
/* register PnP devices */
int init_sound_pnp(void)
{
    return pnp_register(&sb16_pnp);
}

/* remove sb16pnp */
void cleanup_sound_pnp(void)
{
    pnp_unregister(&sb16_pnp);
}

/*****************************************************************************/
/* attach/detach driver */
static int sb16pnp_attach(pnp_device *device, int attach)
{
    pnp_config conf;

    if (attach) {
	/* attach at most one SB16 driver */
	if (sb16_pnpdev)
	    return -EBUSY;
	sb16_pnpdev = device;
	printk("sb16pnp_attach: attach device (%02x,%02x): b:%04x d=%d,%d\n",
	       device->pd_card->pc_csn,
	       device->pd_dev,
	       sbc_base,sbc_dma8,sbc_dma16
	       );
	if (pnp_dev_current(device,&conf)==0)
	    sb16pnp_reconfigure(device,NULL,&conf);
	else
	    printk("sb16pnp_attach: unable to get current conf\n");
    } else {
	printk("sb16pnp_attach: detach device (%02x,%02x): b:%04x d=%d,%d\n",
	       device->pd_card->pc_csn,
	       device->pd_dev,
	       sbc_base,sbc_dma8,sbc_dma16
	       );
	sb16_pnpdev = NULL;
    }

    return 0;
}

/*****************************************************************************/
/* control driver (during device config) */
static int sb16pnp_control(pnp_device *device, PNP_DRV_CTRL req)
{
    switch (req) {
     case PNPDC_ACTIVATE:
	printk("sb16pnp: activate\n");
	break;
     case PNPDC_DEACTIVATE:
	printk("sb16pnp: deactivate\n");
	break;
     case PNPDC_RELEASE_RES:
	printk("sb16pnp: release resources\n");
	break;
     case PNPDC_LOCK:
	printk("sb16pnp: lock\n");
	break;
     case PNPDC_UNLOCK:
	printk("sb16pnp: unlock\n");
	break;
     default:
	printk("sb16pnp: unknown control\n");
	break;
    }
    return 0;
}

/*****************************************************************************/
/* request reconfigure */
static int sb16pnp_reconfigure(pnp_device *device, const pnp_possibility *poss,
			       pnp_config *conf)
{
    struct address_info *aio;
    int loop;

    /* reconfigure the SB/SB16 driver dsp/fm part */
    aio = sound_getconf(SNDCARD_SB);
    if (aio) {
	/* change the main SB ioport */
	if (test_bit(0,&conf->pc_s.irq))
	    aio->irq = conf->pc_irq[0].num;
	if (test_bit(0,&conf->pc_s.dma))
	    aio->dma = conf->pc_dma[0].chan;
	if (test_bit(1,&conf->pc_s.dma))
	    aio->dma2 = conf->pc_dma[1].chan;
	if (test_bit(0,&conf->pc_s.io))
	    aio->io_base = conf->pc_io[0].base;
    }

    /* reconfigure the SB16 external midi i/f part */
    aio = sound_getconf(SNDCARD_SB16MIDI);
    if (aio) {
	/* change the SB16 MIDI ioport */
	if (test_bit(0,&conf->pc_s.irq))
	    aio->irq = conf->pc_irq[0].num;
	if (test_bit(1,&conf->pc_s.io))
	    aio->io_base = conf->pc_io[1].base;
    }

#if 0
    if (test_bit(0,&conf->pc_s.irq))
	printk("selected irq[0]: %d\n",conf->pc_irq[0].num);
    if (test_bit(1,&conf->pc_s.irq))
	printk("selected irq[1]: %d\n",conf->pc_irq[1].num);

    if (test_bit(0,&conf->pc_s.dma))
	printk("selected dma[0]: %d\n",conf->pc_dma[0].chan);
    if (test_bit(1,&conf->pc_s.dma))
	printk("selected dma[1]: %d\n",conf->pc_dma[1].chan);

    for (loop=0; loop<8; loop++)
	if (test_bit(loop,&conf->pc_s.io))
	    printk("selected io[%d]: %04x\n",loop,conf->pc_io[loop].base);
#endif

    return 0;
}
