/*
 * lowlevel i/o routines for Emu8000
 *
 *  Copyright (C) 1999 Steve Ratcliffe
 *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
 *
 *   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.
 */

#include "emu8000_local.h"

/*
 * The following routines read and write registers on the emu8000.  They
 * should always be called via the EMU8000*READ/WRITE macros and never
 * directly.  The macros handle the port number and command word.
 */
/* Write a word */
void snd_emu8000_poke(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val)
{
	unsigned long flags;
	spin_lock_irqsave(&emu->reg_lock, flags);
	if (reg != emu->last_reg) {
		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
		emu->last_reg = reg;
	}
	outw((unsigned short)val, port); /* Send data */
	spin_unlock_irqrestore(&emu->reg_lock, flags);
}

/* Read a word */
unsigned short snd_emu8000_peek(emu8000_t *emu, unsigned int port, unsigned int reg)
{
	unsigned short res;
	unsigned long flags;
	spin_lock_irqsave(&emu->reg_lock, flags);
	if (reg != emu->last_reg) {
		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
		emu->last_reg = reg;
	}
	res = inw(port);	/* Read data */
	spin_unlock_irqrestore(&emu->reg_lock, flags);
	return res;
}

/* Write a double word */
void snd_emu8000_poke_dw(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val)
{
	unsigned long flags;
	spin_lock_irqsave(&emu->reg_lock, flags);
	if (reg != emu->last_reg) {
		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
		emu->last_reg = reg;
	}
	outw((unsigned short)val, port); /* Send low word of data */
	outw((unsigned short)(val>>16), port+2); /* Send high word of data */
	spin_unlock_irqrestore(&emu->reg_lock, flags);
}

/* Read a double word */
unsigned int snd_emu8000_peek_dw(emu8000_t *emu, unsigned int port, unsigned int reg)
{
	unsigned short low;
	unsigned int res;
	unsigned long flags;
	spin_lock_irqsave(&emu->reg_lock, flags);
	if (reg != emu->last_reg) {
		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
		emu->last_reg = reg;
	}
	low = inw(port);	/* Read low word of data */
	res = low + (inw(port+2) << 16);
	spin_unlock_irqrestore(&emu->reg_lock, flags);
	return res;
}

/*
 * detect a card at the given port
 */
int snd_emu8000_detect(emu8000_t *emu)
{
	/* Initialise */
	EMU8000_HWCF1_WRITE(emu, 0x0059);
	EMU8000_HWCF2_WRITE(emu, 0x0020);
	EMU8000_HWCF3_WRITE(emu, 0x0000);
	/* Check for a recognisable emu8000 */
	/*
	if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c)
		return -ENODEV;
		*/
	if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058)
		return -ENODEV;
	if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003)
		return -ENODEV;

	return 0;
}

/*
 * Set up / close a channel to be used for DMA.
 */
void
snd_emu8000_dma_chan(emu8000_t *emu, int ch, int mode)
{
	if (mode == EMU8000_RAM_CLOSE) {
		EMU8000_CCCA_WRITE(emu, ch, 0);
		EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F);
		return;
	}
	EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
	EMU8000_VTFT_WRITE(emu, ch, 0);
	EMU8000_CVCF_WRITE(emu, ch, 0);
	EMU8000_PTRX_WRITE(emu, ch, 0x40000000);
	EMU8000_CPF_WRITE(emu, ch, 0x40000000);
	EMU8000_PSST_WRITE(emu, ch, 0);
	EMU8000_CSL_WRITE(emu, ch, 0);
	if (mode == EMU8000_RAM_WRITE) /* DMA write */
		EMU8000_CCCA_WRITE(emu, ch, 0x06000000);
	else	   /* DMA read */
		EMU8000_CCCA_WRITE(emu, ch, 0x04000000);
}

/*
 * Open up channels.
 */
int
snd_emu8000_open_dma(emu8000_t *emu, int write)
{
	int i;

	/* reserve all 30 voices for loading */
	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
		snd_emux_lock_voice(emu->emu, i);
		snd_emu8000_dma_chan(emu, i, write);
	}

	/* assign voice 31 and 32 to ROM */
	EMU8000_VTFT_WRITE(emu, 30, 0);
	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
	EMU8000_VTFT_WRITE(emu, 31, 0);
	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);

	return 0;
}

/*
 */
void
snd_emu8000_wait_dma(emu8000_t *emu)
{
	int i;

	/* wait until FULL bit in SMAxW register is false */
	for (i = 0; i < 10000; i++) {
		if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0)
			break;
		snd_emu8000_wait(emu, 10);
	}
}

/*
 * Close all dram channels.
 */
void
snd_emu8000_close_dma(emu8000_t *emu)
{
	int i;

	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
		snd_emux_unlock_voice(emu->emu, i);
	}
}

/*
 */
void snd_emu8000_read_wait(emu8000_t *emu)
{
	while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) {
		snd_emu8000_wait(emu, 5);
	}
}

/*
 */
void snd_emu8000_write_wait(emu8000_t *emu)
{
	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
		snd_emu8000_wait(emu, 5);
	}
}

/*
 */
void snd_emu8000_wait(emu8000_t *emu, unsigned short delay)
{
	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout((HZ*(unsigned long)delay + 44099)/44100);
}

