/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>,
 *                   Hannu Savolainen 1993-1996,
 *                   Rob Hooft
 *                   
 *  Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)
 *
 *  Most if code is ported from OSS/Lite.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/opl3.h"

/*
 *    There is 18 possible 2 OP voices
 *      (9 in the left and 9 in the right).
 *      The first OP is the modulator and 2nd is the carrier.
 *
 *      The first three voices in the both sides may be connected
 *      with another voice to a 4 OP voice. For example voice 0
 *      can be connected with voice 3. The operators of voice 3 are
 *      used as operators 3 and 4 of the new 4 OP voice.
 *      In this case the 2 OP voice number 0 is the 'first half' and
 *      voice 3 is the second.
 */

#if 0

#define OPL3_USE_LEFT	0
#define OPL3_USE_RIGHT	1

static struct snd_opl3_physical_voice_info snd_opl3_pv_map[18] =
{
/*       No Mode Side           OP1     OP2     OP3   OP4       */
/*      ---------------------------------------------------     */
	{0, 2, OPL3_USE_LEFT,
	 {0x00, 0x03, 0x08, 0x0b}},
	{1, 2, OPL3_USE_LEFT,
	 {0x01, 0x04, 0x09, 0x0c}},
	{2, 2, OPL3_USE_LEFT,
	 {0x02, 0x05, 0x0a, 0x0d}},

	{3, 2, OPL3_USE_LEFT,
	 {0x08, 0x0b, 0x00, 0x00}},
	{4, 2, OPL3_USE_LEFT,
	 {0x09, 0x0c, 0x00, 0x00}},
	{5, 2, OPL3_USE_LEFT,
	 {0x0a, 0x0d, 0x00, 0x00}},

	{6, 2, OPL3_USE_LEFT,
	 {0x10, 0x13, 0x00, 0x00}},	/* Used by percussive voices */
	{7, 2, OPL3_USE_LEFT,
	 {0x11, 0x14, 0x00, 0x00}},	/* if the percussive mode */
	{8, 2, OPL3_USE_LEFT,
	 {0x12, 0x15, 0x00, 0x00}},	/* is selected */

	{0, 2, OPL3_USE_RIGHT,
	 {0x00, 0x03, 0x08, 0x0b}},
	{1, 2, OPL3_USE_RIGHT,
	 {0x01, 0x04, 0x09, 0x0c}},
	{2, 2, OPL3_USE_RIGHT,
	 {0x02, 0x05, 0x0a, 0x0d}},

	{3, 2, OPL3_USE_RIGHT,
	 {0x08, 0x0b, 0x00, 0x00}},
	{4, 2, OPL3_USE_RIGHT,
	 {0x09, 0x0c, 0x00, 0x00}},
	{5, 2, OPL3_USE_RIGHT,
	 {0x0a, 0x0d, 0x00, 0x00}},

	{6, 2, OPL3_USE_RIGHT,
	 {0x10, 0x13, 0x00, 0x00}},
	{7, 2, OPL3_USE_RIGHT,
	 {0x11, 0x14, 0x00, 0x00}},
	{8, 2, OPL3_USE_RIGHT,
	 {0x12, 0x15, 0x00, 0x00}}
};

#endif

/*

 */

static void snd_opl3_command(opl3_t * opl3,
			     unsigned int cmd, unsigned char val)
{
	unsigned long flags;
	unsigned short port;

	/*
	 * The original 2-OP synth requires a quite long delay after writing to a
	 * register. The OPL-3 survives with just two INBs
	 */

	port = cmd & OPL3_RIGHT ? opl3->r_port : opl3->l_port;

	spin_lock_irqsave(&opl3->reg_lock, flags);

	outb((unsigned char) cmd, port);

	if (opl3->hardware == OPL3_HW_OPL2) {
		snd_delay(1);
	} else {
		inb(port);
		inb(port);
	}

	outb((unsigned char) (val & 0xff), port + 1);

	if (opl3->hardware == OPL3_HW_OPL2) {
		snd_delay(3);
	} else {
		inb(port);
		inb(port);
	}

	spin_unlock_irqrestore(&opl3->reg_lock, flags);
}

/*

 */

static int snd_opl3_detect(opl3_t * opl3)
{
	/*
	 * This function returns 1 if the FM chip is present at the given I/O port
	 * The detection algorithm plays with the timer built in the FM chip and
	 * looks for a change in the status register.
	 *
	 * Note! The timers of the FM chip are not connected to AdLib (and compatible)
	 * boards.
	 *
	 * Note2! The chip is initialized if detected.
	 */

	unsigned char stat1, stat2, signature;

	/* some hardware doesn't support timers */
	if (opl3->hardware == OPL3_HW_OPL3_SV ||
	    opl3->hardware == OPL3_HW_OPL3_CS ||
	    opl3->hardware == OPL3_HW_OPL3_FM801)
		return 0;
	/* Reset timers 1 and 2 */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
	/* Reset the IRQ of the FM chip */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
	signature = stat1 = inb(opl3->l_port);	/* Status register */
	if ((stat1 & 0xe0) != 0x00) {	/* Should be 0x00 */
		snd_printd("OPL3: stat1 = 0x%x\n", stat1);
		return -ENODEV;
	}
	/* Set timer1 to 0xff */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff);
	/* Unmask and start timer 1 */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START);
	/* Now we have to delay at least 80us */
	snd_delay(50);
	/* Read status after timers have expired */
	stat2 = inb(opl3->l_port);
	/* Stop the timers */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
	/* Reset the IRQ of the FM chip */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
	if ((stat2 & 0xe0) != 0xc0) {	/* There is no YM3812 */
		snd_printd("OPL3: stat2 = 0x%x\n", stat2);
		return -ENODEV;
	}
	/* There is a FM chip in this address. Detect the type (OPL2 to OPL4) */
	if (opl3->hardware != OPL3_HW_AUTO)
		return 0;

	if (signature == 0x06) {	/* OPL2 */
		opl3->hardware = OPL3_HW_OPL2;
	} else {
		unsigned char tmp;

		opl3->hardware = OPL3_HW_OPL3;
		/*
		 * Detect availability of OPL4 (_experimental_). Works probably
		 * only after a cold boot. In addition the OPL4 port
		 * of the chip may not be connected to the PC bus at all.
		 */
		snd_opl3_command(opl3, OPL3_RIGHT | OPL3_REG_MODE, 0x00);
		snd_opl3_command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_MODE2_ENABLE | OPL3_MODE3_ENABLE);
		if ((tmp = inb(opl3->l_port)) == 0x02) {	/* Have a OPL4 */
			opl3->hardware = OPL3_HW_OPL4;
		}
		snd_opl3_command(opl3, OPL3_RIGHT | OPL3_REG_MODE, 0x00);
	}

	return 0;
}

/*
 *  AdLib timers
 */

/*
 *  Timer 1 - 80us
 */

static void snd_opl3_timer1_start(snd_timer_t * timer)
{
	unsigned long flags;
	unsigned char tmp;
	unsigned int ticks;
	opl3_t *opl3;

	opl3 = (opl3_t *) timer->private_data;
	spin_lock_irqsave(&opl3->timer_lock, flags);
	ticks = timer->sticks;
	tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK;
	opl3->timer_enable = tmp;
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks);	/* timer 1 count */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
	spin_unlock_irqrestore(&opl3->timer_lock, flags);
}

static void snd_opl3_timer1_stop(snd_timer_t * timer)
{
	unsigned long flags;
	unsigned char tmp;
	opl3_t *opl3;

	opl3 = (opl3_t *) timer->private_data;
	spin_lock_irqsave(&opl3->timer_lock, flags);
	tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START;
	opl3->timer_enable = tmp;
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
	spin_unlock_irqrestore(&opl3->timer_lock, flags);
}

/*
 *  Timer 2 - 320us
 */

static void snd_opl3_timer2_start(snd_timer_t * timer)
{
	unsigned long flags;
	unsigned char tmp;
	unsigned int ticks;
	opl3_t *opl3;

	opl3 = (opl3_t *) timer->private_data;
	spin_lock_irqsave(&opl3->timer_lock, flags);
	ticks = timer->sticks;
	tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK;
	opl3->timer_enable = tmp;
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks);	/* timer 1 count */
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
	spin_unlock_irqrestore(&opl3->timer_lock, flags);
}

static void snd_opl3_timer2_stop(snd_timer_t * timer)
{
	unsigned long flags;
	unsigned char tmp;
	opl3_t *opl3;

	opl3 = (opl3_t *) timer->private_data;
	spin_lock_irqsave(&opl3->timer_lock, flags);
	tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START;
	opl3->timer_enable = tmp;
	snd_opl3_command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
	spin_unlock_irqrestore(&opl3->timer_lock, flags);
}

/*

 */

static struct snd_stru_timer_hardware snd_opl3_timer1 =
{
	SND_TIMER_HW_STOP,		/* flags */
	80000,				/* resolution in us */
	256,				/* high ticks */
	NULL,				/* open */
	NULL,				/* close */
	NULL,				/* resolution */
	snd_opl3_timer1_start,		/* start */
	snd_opl3_timer1_stop,		/* stop */
};

static struct snd_stru_timer_hardware snd_opl3_timer2 =
{
	SND_TIMER_HW_STOP,		/* flags */
	320000,				/* resolution in us */
	256,				/* high ticks */
	NULL,				/* open */
	NULL,				/* close */
	NULL,				/* resolution */
	snd_opl3_timer2_start,		/* start */
	snd_opl3_timer2_stop,		/* stop */
};

static void snd_opl3_timers_init(snd_card_t * card, opl3_t * opl3, int timer_no)
{
	snd_timer_t *timer;

	timer = snd_timer_new_device(card, "AdLib timer");
	if (timer) {
		strcpy(timer->name, "AdLib timer #1");
		timer->private_data = opl3;
		memcpy(&timer->hw, &snd_opl3_timer1, sizeof(snd_opl3_timer1));
		if (snd_timer_register(timer, timer_no) < 0) {
			snd_timer_free(timer);
			timer = NULL;
		}
	}
	opl3->timer1 = timer;

	timer = snd_timer_new_device(card, "GF1 timer");
	if (timer) {
		strcpy(timer->name, "AdLib timer #2");
		timer->private_data = opl3;
		memcpy(&timer->hw, &snd_opl3_timer2, sizeof(snd_opl3_timer2));
		if (snd_timer_register(timer, timer_no + 1) < 0) {
			snd_timer_free(timer);
			timer = NULL;
		}
	}
	opl3->timer2 = timer;
}

static void snd_opl3_timers_done(opl3_t * opl3)
{
	if (opl3->timer1) {
		snd_timer_unregister(opl3->timer1);
		opl3->timer1 = NULL;
	}
	if (opl3->timer2) {
		snd_timer_unregister(opl3->timer2);
		opl3->timer2 = NULL;
	}
}


/*

 */

void snd_opl3_interrupt(snd_hwdep_t * hw)
{
	unsigned char status;
	opl3_t *opl3;
	snd_timer_t *timer;

	opl3 = (opl3_t *) hw->private_data;
	status = inb(opl3->l_port);
#if 0
	snd_printk("AdLib IRQ status = 0x%x\n", status);
#endif
	if (!(status & 0x80))
		return;

	if (status & 0x40) {
		timer = opl3->timer1;
		snd_timer_interrupt(timer, timer->sticks);
	}
	if (status & 0x20) {
		timer = opl3->timer2;
		snd_timer_interrupt(timer, timer->sticks);
	}
}

/*

 */

static void snd_opl3_free(void *private_data)
{
	opl3_t *opl3;

	opl3 = (opl3_t *) private_data;
	if (opl3->timer_dev >= 0)
		snd_opl3_timers_done(opl3);
	snd_kfree(private_data);
}

snd_hwdep_t *snd_opl3_new_device(snd_card_t * card,
				 unsigned short l_port,
				 unsigned short r_port,
				 unsigned short hardware,
				 int timer_dev)
{
	opl3_t *opl3;
	snd_hwdep_t *hw;

	if ((hw = snd_hwdep_new_device(card, "OPL2/OPL3")) == NULL)
		return NULL;
	opl3 = snd_kcalloc(sizeof(opl3_t), GFP_KERNEL);
	if (!opl3) {
		snd_hwdep_free(hw);
		return NULL;
	}
	opl3->hardware = hardware;
	opl3->l_port = l_port;
	opl3->r_port = r_port;
	opl3->timer_dev = timer_dev;
	strcpy(hw->name, hw->id);
	hw->private_data = opl3;
	hw->private_free = snd_opl3_free;
	opl3->reg_lock = SPIN_LOCK_UNLOCKED;
	opl3->timer_lock = SPIN_LOCK_UNLOCKED;
	if (snd_opl3_detect(opl3) < 0) {
		snd_hwdep_free(hw);
		snd_printd("opl3: OPL2/3 chip not detected at 0x%x/0x%x\n", opl3->l_port, opl3->r_port);
		return NULL;
	}
	switch (opl3->hardware & OPL3_HW_MASK) {
	case OPL3_HW_OPL2:
		strcpy(hw->name, "OPL2 FM");
		hw->type = SND_HWDEP_TYPE_OPL2;
		break;
	case OPL3_HW_OPL3:
		strcpy(hw->name, "OPL3 FM");
		hw->type = SND_HWDEP_TYPE_OPL3;
		break;
	case OPL3_HW_OPL4:
		strcpy(hw->name, "OPL4 FM");
		hw->type = SND_HWDEP_TYPE_OPL4;
		break;
	}
	if (timer_dev >= 0)
		snd_opl3_timers_init(card, opl3, timer_dev);
	return hw;
}

EXPORT_SYMBOL(snd_opl3_interrupt);
EXPORT_SYMBOL(snd_opl3_new_device);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
