/*
 *   ALSA lowlevel driver for RME Digi96
 *
 *      Copyright (c) 2000 Anders Torger <torger@ludd.luth.se>
 *
 *   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/pcm.h"
#include "../../include/control.h"
#include "../../include/info.h"
#include "../../include/rme96.h"

#define RME96_ISPLAYING(rme96) ((rme96)->wcreg & RME96_WCR_START)
#define RME96_ISRECORDING(rme96) ((rme96)->wcreg & RME96_WCR_START_2)

static int
snd_rme96_playback_ioctl(void *private_data,
			 snd_pcm_subchn_t *subchn,
			 unsigned int cmd,
			 unsigned long *arg);
static int
snd_rme96_capture_ioctl(void *private_data,
			snd_pcm_subchn_t *subchn,
			unsigned int cmd,
			unsigned long *arg);

static int
snd_rme96_playback_prepare(void *private_data,
			   snd_pcm_subchn_t *subchn);

static int
snd_rme96_capture_prepare(void *private_data,
			  snd_pcm_subchn_t *subchn);

static int
snd_rme96_playback_trigger(void *private_data,
			   snd_pcm_subchn_t *subchn, 
			   int cmd);

static int
snd_rme96_capture_trigger(void *private_data,
			  snd_pcm_subchn_t *subchn, 
			  int cmd);

static unsigned int
snd_rme96_playback_pointer(void *private_data,
			   snd_pcm_subchn_t *subchn);

static unsigned int
snd_rme96_capture_pointer(void *private_data,
			  snd_pcm_subchn_t *subchn);

static void __init 
snd_rme96_proc_init(rme96_t *rme96);

static void
snd_rme96_proc_done(rme96_t *rme96);

int
snd_rme96_create_switches(snd_card_t *card,
			  rme96_t *rme96);

/*
 * Digital output capabilites
 */
static snd_pcm_hardware_t snd_rme96_playback_info =
{
	/* modes */
	SND_PCM_CHNINFO_STREAM     |
	SND_PCM_CHNINFO_BLOCK      |
	SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_PAUSE,

	/* formats */
	SND_PCM_FMT_S16_LE |
	SND_PCM_FMT_S32_LE,

	/* rates */
	SND_PCM_RATE_32000 |
	SND_PCM_RATE_44100 | 
	SND_PCM_RATE_48000 | 
	SND_PCM_RATE_KNOT  | /* 64000 Hz */
	SND_PCM_RATE_88200 | 
	SND_PCM_RATE_96000,

	32000, /* min rate (Hz) */
	96000, /* max rate (Hz) */
	2,     /* min voices */
	2,     /* max voices */
	RME96_SPDIF_SMALL_BLOCK,  /* min fragment size */
	RME96_SPDIF_LARGE_BLOCK,  /* max fragment size */
	3,  /* fragment align */
	0,  /* fifo size (not used) */
	4,  /* bus transfer block size */
	/* functions */
	snd_rme96_playback_ioctl,
	snd_rme96_playback_prepare,
	snd_rme96_playback_trigger,
	snd_rme96_playback_pointer
};

/*
 * Digital input capabilites
 */
static snd_pcm_hardware_t snd_rme96_capture_info =
{
	/* modes */
	SND_PCM_CHNINFO_STREAM     |
	SND_PCM_CHNINFO_BLOCK      |
	SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_PAUSE,

	/* formats */
	SND_PCM_FMT_S16_LE |
	SND_PCM_FMT_S32_LE,

	/* rates */
	SND_PCM_RATE_32000 |
	SND_PCM_RATE_44100 | 
	SND_PCM_RATE_48000 | 
	SND_PCM_RATE_KNOT  | /* 64000 Hz */
	SND_PCM_RATE_88200 | 
	SND_PCM_RATE_96000,

	32000, /* min rate (Hz) */
	96000, /* max rate (Hz) */
	2, /* min voices */
	2, /* max voices */
	RME96_SPDIF_SMALL_BLOCK,
	RME96_SPDIF_LARGE_BLOCK,
	3,
	0,
	4,
	/* functions */
	snd_rme96_capture_ioctl,
	snd_rme96_capture_prepare,
	snd_rme96_capture_trigger,
	snd_rme96_capture_pointer
};

static int
snd_rme96_playback_getrate(rme96_t *rme96)
{
	int rate;
	
	rate = ((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_0) & 1) +
		(((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_1) & 1) << 1);
	switch (rate) {
	case 1:
		rate = 32000;
		break;
	case 2:
		rate = 44100;
		break;
	case 3:
		rate = 48000;
		break;
	}
	return (rme96->wcreg & RME96_WCR_DS) ? rate << 1 : rate;
}

static int
snd_rme96_capture_getrate(rme96_t *rme96)
{	
	int ftab;

	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	ftab = ((rme96->rcreg >> RME96_RCR_BITPOS_F0) & 1) +
		(((rme96->rcreg >> RME96_RCR_BITPOS_F1) & 1) << 1) +
		(((rme96->rcreg >> RME96_RCR_BITPOS_F2) & 1) << 2);
	switch (ftab) {
	case 0:
		if (rme96->rcreg & RME96_RCR_T_OUT) {
			return 64000;
		}
		return -1;
	case 3: return 96000;
	case 4: return 88200;
	case 5: return 48000;
	case 6: return 44100;
	case 7: return 32000;
	default:
	}
	return -1;
}

static int
snd_rme96_setclockmode(rme96_t *rme96,
		       int mode)
{
	switch (mode) {
	case RME96_CLOCKMODE_SLAVE:
		rme96->wcreg &= ~RME96_WCR_MASTER;
		rme96->areg &= ~RME96_AR_WSEL;
		break;
	case RME96_CLOCKMODE_MASTER:
		rme96->wcreg |= RME96_WCR_MASTER;
		rme96->areg &= ~RME96_AR_WSEL;
		break;
	case RME96_CLOCKMODE_WORDCLOCK:
		rme96->areg |= RME96_AR_WSEL;
		break;
	default:
		return -EINVAL;
	}
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
	return 0;
}

static int
snd_rme96_getclockmode(rme96_t *rme96)
{
	if (rme96->areg & RME96_AR_WSEL) {
		return RME96_CLOCKMODE_WORDCLOCK;
	}
	return (rme96->wcreg & RME96_WCR_MASTER) ? RME96_CLOCKMODE_MASTER :
		RME96_CLOCKMODE_SLAVE;
}

static int
snd_rme96_capture_setinputtype(rme96_t *rme96,
			       int type)
{
	switch (type) {
	case RME96_INPUT_OPTICAL:
		rme96->wcreg = (rme96->wcreg & ~RME96_WCR_INP_0) &
			~RME96_WCR_INP_1;
		break;
	case RME96_INPUT_COAXIAL:
		rme96->wcreg = (rme96->wcreg | RME96_WCR_INP_0) &
			~RME96_WCR_INP_1;
		break;
	case RME96_INPUT_INTERNAL:
		rme96->wcreg = (rme96->wcreg & ~RME96_WCR_INP_0) |
			RME96_WCR_INP_1;
		break;
	case RME96_INPUT_XLR:
		rme96->wcreg = (rme96->wcreg | RME96_WCR_INP_0) |
			RME96_WCR_INP_1;
		break;
	default:
		return -EINVAL;
	}
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	return 0;
}

static int
snd_rme96_capture_getinputtype(rme96_t *rme96)
{
	return ((rme96->wcreg >> RME96_WCR_BITPOS_INP_0) & 1) +
		(((rme96->wcreg >> RME96_WCR_BITPOS_INP_1) & 1) << 1);
}

static int
snd_rme96_playback_setformat(rme96_t *rme96,
			     int format)
{
	switch (format) {
	case SND_PCM_SFMT_S16_LE:
		rme96->wcreg &= ~RME96_WCR_MODE24;
		break;
	case SND_PCM_SFMT_S32_LE:
		rme96->wcreg |= RME96_WCR_MODE24;
		break;
	default:
		return -EINVAL;
	}
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	return 0;
}

static int
snd_rme96_capture_setformat(rme96_t *rme96,
			    int format)
{
	switch (format) {
	case SND_PCM_SFMT_S16_LE:
		rme96->wcreg &= ~RME96_WCR_MODE24_2;
		break;
	case SND_PCM_SFMT_S32_LE:
		rme96->wcreg |= RME96_WCR_MODE24_2;
		break;
	default:
		return -EINVAL;
	}
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	return 0;
}

static int
snd_rme96_playback_setrate(rme96_t *rme96,
			   int rate)
{
	int ds;

	ds = rme96->wcreg & RME96_WCR_DS;
	switch (rate) {
	case 32000:
		rme96->wcreg &= ~RME96_WCR_DS;
		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) &
			~RME96_WCR_FREQ_1;
		break;
	case 44100:
		rme96->wcreg &= ~RME96_WCR_DS;
		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_1) &
			~RME96_WCR_FREQ_0;
		break;
	case 48000:
		rme96->wcreg &= ~RME96_WCR_DS;
		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) |
			RME96_WCR_FREQ_1;
		break;
	case 64000:
		rme96->wcreg |= RME96_WCR_DS;
		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) &
			~RME96_WCR_FREQ_1;
		break;
	case 88200:
		rme96->wcreg |= RME96_WCR_DS;
		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_1) &
			~RME96_WCR_FREQ_0;
		break;
	case 96000:
		rme96->wcreg |= RME96_WCR_DS;
		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) |
			RME96_WCR_FREQ_1;
		break;
	default:
		return -EINVAL;
	}
	if ((!ds && rme96->wcreg & RME96_WCR_DS) ||
	    (ds && !(rme96->wcreg & RME96_WCR_DS)))
	{
		/* change to/from double-speed: reset the DAC (if available) */
		rme96->wcreg |= RME96_WCR_PD;
		writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
		rme96->wcreg &= ~RME96_WCR_PD;
		writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	} else {
		writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	}
	return 0;
}

static int
snd_rme96_fit_blocksize(int blocksize)
{
	if (blocksize > RME96_SPDIF_SMALL_BLOCK + (RME96_SPDIF_LARGE_BLOCK -
						   RME96_SPDIF_SMALL_BLOCK) / 2)
	{
		blocksize = RME96_SPDIF_LARGE_BLOCK;
	} else {
		blocksize = RME96_SPDIF_SMALL_BLOCK;
	}
	return blocksize;
}

static void
snd_rme96_set_runtime_block_properties(rme96_t *rme96,
				       snd_pcm_subchn_t *subchn)
{
	subchn->runtime->buf.block.frag_size = 
		snd_rme96_fit_blocksize(subchn->runtime->buf.block.frag_size);
	if (subchn->runtime->buf.block.frag_size == RME96_SPDIF_LARGE_BLOCK) {
		subchn->runtime->buf.block.frags_max =
			RME96_BUFFER_SIZE / RME96_SPDIF_LARGE_BLOCK - 1;
		subchn->runtime->frags =
			RME96_BUFFER_SIZE / RME96_SPDIF_LARGE_BLOCK;
		rme96->wcreg &= ~RME96_WCR_ISEL;
	} else {
		subchn->runtime->buf.block.frags_max =
			RME96_BUFFER_SIZE / RME96_SPDIF_SMALL_BLOCK - 1;
		subchn->runtime->frags =
			RME96_BUFFER_SIZE / RME96_SPDIF_SMALL_BLOCK;
		rme96->wcreg |= RME96_WCR_ISEL;
	}
	subchn->runtime->buf.block.frags_min = 1;	
	rme96->wcreg &= ~RME96_WCR_IDIS;
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
}

static void
snd_rme96_set_runtime_stream_properties(rme96_t *rme96,
					snd_pcm_subchn_t *subchn)
{
	subchn->runtime->buf.stream.queue_size = RME96_BUFFER_SIZE;
	subchn->runtime->buf.stream.fill = SND_PCM_FILL_NONE;
	subchn->runtime->buf.stream.max_fill = RME96_BUFFER_SIZE;
	rme96->wcreg |= RME96_WCR_IDIS;
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
}

static void
snd_rme96_playback_start(rme96_t *rme96,
			 int from_pause)
{
	if (!from_pause) {
		writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
	}

	rme96->wcreg |= RME96_WCR_START;
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
}

static void
snd_rme96_capture_start(rme96_t *rme96,
			int from_pause)
{
	if (!from_pause) {
		writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);
	}

	rme96->wcreg |= RME96_WCR_START_2;
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
}

static void
snd_rme96_playback_stop(rme96_t *rme96)
{
	/*
	 * Check if there is an unconfirmed IRQ, if so confirm it, or else
	 * the hardware will not stop generating interrupts
	 */
	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	if (rme96->rcreg & RME96_RCR_IRQ) {
		writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ);
	}	
	rme96->wcreg &= ~RME96_WCR_START;
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
}

static void
snd_rme96_capture_stop(rme96_t *rme96)
{
	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	if (rme96->rcreg & RME96_RCR_IRQ_2) {
		writel(0, rme96->iobase + RME96_IO_CONFIRM_REC_IRQ);
	}	
	rme96->wcreg &= ~RME96_WCR_START_2;
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
}

static int
snd_rme96_playback_user_copy(snd_pcm_subchn_t *subchn,
			     int voice, /* not used (interleaved data) */
			     int pos,
			     void *src,
			     int count)
{
	/* make use of constant size copy optimisations uaccess.h */
	switch (count) {
	case RME96_SPDIF_SMALL_BLOCK:
		return copy_from_user(
			phys_to_virt((unsigned long)
				     (((rme96_t *)subchn->runtime->private_data)
				      ->iobase + RME96_IO_PLAY_BUFFER +
				      (pos >> 2))),
			src, RME96_SPDIF_SMALL_BLOCK);
	case RME96_SPDIF_LARGE_BLOCK:
		return copy_from_user(
			phys_to_virt((unsigned long)
				     (((rme96_t *)subchn->runtime->private_data)
				      ->iobase + RME96_IO_PLAY_BUFFER +
				      (pos >> 2))),
			src, RME96_SPDIF_LARGE_BLOCK);
	default:
	}
	return copy_from_user(
		phys_to_virt((unsigned long)
			     (((rme96_t *)subchn->runtime->private_data)
			      ->iobase + RME96_IO_PLAY_BUFFER + (pos >> 2))),
		src, count);
}

static int
snd_rme96_capture_user_copy(snd_pcm_subchn_t *subchn,
			    int voice, /* not used (interleaved data) */
			    int pos,
			    void *dst,
			    int count)
{
	/* make use of constant size copy optimisations in uaccess.h */
	switch (count) {
	case RME96_SPDIF_SMALL_BLOCK:
		return copy_to_user(
			dst,
			phys_to_virt((unsigned long)
				     (((rme96_t *)subchn->runtime->private_data)
				      ->iobase + RME96_IO_REC_BUFFER +
				      (pos >> 2))),
			RME96_SPDIF_SMALL_BLOCK);
	case RME96_SPDIF_LARGE_BLOCK:
		return copy_to_user(
			dst,
			phys_to_virt((unsigned long)
				     (((rme96_t *)subchn->runtime->private_data)
				      ->iobase + RME96_IO_REC_BUFFER +
				      (pos >> 2))),
			RME96_SPDIF_LARGE_BLOCK);
	default:
	}
	return copy_to_user(
		dst,
		phys_to_virt((unsigned long)
			     (((rme96_t *)subchn->runtime->private_data)
			      ->iobase + RME96_IO_REC_BUFFER + (pos >> 2))),
		count);
}

void
snd_rme96_interrupt(int irq,
		    void *dev_id,
		    struct pt_regs *regs)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)dev_id;	

	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	/* fastpath out, to ease interrupt sharing */
	if (!((rme96->rcreg & RME96_RCR_IRQ) ||
	      (rme96->rcreg & RME96_RCR_IRQ_2)))
	{
		return;
	}
	spin_lock_irqsave(&rme96->lock, flags);	
	
	if (rme96->rcreg & RME96_RCR_IRQ) {
		/* playback */
		snd_pcm_transfer_done(rme96->playback_subchn);
		writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ);
	}
	if (rme96->rcreg & RME96_RCR_IRQ_2) {
		/* capture */
		snd_pcm_transfer_done(rme96->capture_subchn);		
		writel(0, rme96->iobase + RME96_IO_CONFIRM_REC_IRQ);
	}
	spin_unlock_irqrestore(&rme96->lock, flags);
}

static int
snd_rme96_playback_open(void *private_data,
			snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)private_data;
	
	snd_pcm_set_sync(subchn);
	
	spin_lock_irqsave(&rme96->lock, flags);	
	rme96->playback_subchn = subchn;
	subchn->runtime->private_data = private_data;
	subchn->runtime->hw = &snd_rme96_playback_info;
	subchn->runtime->hw_memcpy = snd_rme96_playback_user_copy;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_capture_open(void *private_data,
		       snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)private_data;

	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	if (rme96->rcreg & RME96_RCR_VERF) {
		return -EIO;
	}
	snd_pcm_set_sync(subchn);

	spin_lock_irqsave(&rme96->lock, flags);	
	rme96->capture_subchn = subchn;
	subchn->runtime->private_data = private_data;
	subchn->runtime->hw_memcpy = snd_rme96_capture_user_copy;
	subchn->runtime->hw = &snd_rme96_capture_info;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_playback_close(void *private_data,
			 snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)private_data;

	spin_lock_irqsave(&rme96->lock, flags);	
	rme96->playback_subchn = NULL;
	rme96->playback_prepared = 0;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_capture_close(void *private_data,
			snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)private_data;
	
	spin_lock_irqsave(&rme96->lock, flags);	
	rme96->capture_subchn = NULL;
	rme96->capture_prepared = 0;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_playback_ioctl(void *private_data,
			 snd_pcm_subchn_t *subchn,
			 unsigned int cmd,
			 unsigned long *arg)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)private_data;
	int res;

	if ((res = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg)) < 0) {
		return res;
	}

	spin_lock_irqsave(&rme96->lock, flags);	
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		if ((res = snd_rme96_playback_setrate(rme96,
						      subchn->runtime->
						      format.rate)) < 0)
		{
			spin_unlock_irqrestore(&rme96->lock, flags);
			return res;
		}
		if ((res = snd_rme96_playback_setformat(rme96,
							subchn->runtime->
							format.format)) < 0)
		{
			spin_unlock_irqrestore(&rme96->lock, flags);
			return res;
		}
	}
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_capture_ioctl(void *private_data,
			snd_pcm_subchn_t *subchn,
			unsigned int cmd,
			unsigned long *arg)
{
	unsigned long flags;
	rme96_t *rme96 = (rme96_t *)private_data;
	int res;
	
	if ((res = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg)) < 0) {
		return res;
	}
	spin_lock_irqsave(&rme96->lock, flags);	
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		if ((res = snd_rme96_capture_setformat(rme96,
						       subchn->runtime->
						       format.format)) < 0)
		{
			spin_unlock_irqrestore(&rme96->lock, flags);
			return res;
		}
		/* Since this the input is digital, we cannot choose rate */
		subchn->runtime->format.rate =
			snd_rme96_capture_getrate(rme96);
	}
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_playback_prepare(void *private_data,
			   snd_pcm_subchn_t *subchn)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	unsigned long flags;
	int res;
	
	spin_lock_irqsave(&rme96->lock, flags);	
	if (RME96_ISPLAYING(rme96)) {
		snd_rme96_playback_stop(rme96);
	}
	writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
	if ((res = snd_rme96_playback_setrate(rme96,
					      subchn->runtime->
					      format.rate)) < 0)
	{
		spin_unlock_irqrestore(&rme96->lock, flags);
		return res;
	}
	if ((res = snd_rme96_playback_setformat(rme96,
						subchn->runtime->
						format.format)) < 0)
	{
		spin_unlock_irqrestore(&rme96->lock, flags);
		return res;
	}
	if (rme96->capture_prepared) {
		/* capture is prepared, we must use the same settings */
		if (rme96->capture_subchn->runtime->mode !=
		    subchn->runtime->mode)
		{
			spin_unlock_irqrestore(&rme96->lock, flags);
			return -EINVAL;			
		}
		if (subchn->runtime->mode == SND_PCM_MODE_BLOCK) {
			subchn->runtime->buf.block.frag_size =
				snd_rme96_fit_blocksize(subchn->runtime->
							buf.block.frag_size);
			if (subchn->runtime->buf.block.frag_size !=
			    rme96->capture_subchn->runtime->buf.block.frag_size)
			{
				spin_unlock_irqrestore(&rme96->lock, flags);
				return -EINVAL;
			}
		}
	}
	if (subchn->runtime->mode == SND_PCM_MODE_BLOCK) {
		snd_rme96_set_runtime_block_properties(rme96, subchn);
	} else if (subchn->runtime->mode == SND_PCM_MODE_STREAM) {
		snd_rme96_set_runtime_stream_properties(rme96, subchn);
	}
	rme96->playback_prepared = 1;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_capture_prepare(void *private_data,
			  snd_pcm_subchn_t *subchn)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	unsigned long flags;
	int res;
	
	spin_lock_irqsave(&rme96->lock, flags);	
	if (RME96_ISRECORDING(rme96)) {
		snd_rme96_capture_stop(rme96);
	}
	writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);
	if ((res = snd_rme96_capture_setformat(rme96,
					       subchn->runtime->
					       format.format)) < 0)
	{
		spin_unlock_irqrestore(&rme96->lock, flags);
		return res;
	}
	/* Since this the input is digital, we cannot choose rate */
	subchn->runtime->format.rate =
		snd_rme96_capture_getrate(rme96);
	if (rme96->playback_prepared) {
		if (rme96->playback_subchn->runtime->mode !=
		    subchn->runtime->mode)
		{
			spin_unlock_irqrestore(&rme96->lock, flags);
			return -EINVAL;			
		}
		if (subchn->runtime->mode == SND_PCM_MODE_BLOCK) {
			subchn->runtime->buf.block.frag_size =
				snd_rme96_fit_blocksize(subchn->runtime->
							buf.block.frag_size);
			if (subchn->runtime->buf.block.frag_size !=
			    rme96->playback_subchn->runtime->
			    buf.block.frag_size)
			{
				spin_unlock_irqrestore(&rme96->lock, flags);
				return -EINVAL;
			}
		}		
	}
	if (subchn->runtime->mode == SND_PCM_MODE_BLOCK) {
		snd_rme96_set_runtime_block_properties(rme96, subchn);
	} else if (subchn->runtime->mode == SND_PCM_MODE_STREAM) {
		snd_rme96_set_runtime_stream_properties(rme96, subchn);
	}
	rme96->capture_prepared = 1;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_playback_trigger(void *private_data,
			   snd_pcm_subchn_t *subchn, 
			   int cmd)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	unsigned long flags;

	switch (cmd) {
	case SND_PCM_TRIGGER_SYNC_GO:
	case SND_PCM_TRIGGER_GO:
		if (!RME96_ISPLAYING(rme96)) {
			if (subchn != rme96->playback_subchn) {
				return -EBUSY;
			}
			snd_rme96_playback_start(rme96, 0);
		}
		break;

	case SND_PCM_TRIGGER_STOP:
		if (RME96_ISPLAYING(rme96)) {
			if (subchn != rme96->playback_subchn) {
				return -EBUSY;
			}
			snd_rme96_playback_stop(rme96);
		}
		break;

	case SND_PCM_TRIGGER_PAUSE_PUSH:
		if (RME96_ISPLAYING(rme96)) {
			snd_rme96_playback_stop(rme96);
			spin_lock_irqsave(&subchn->runtime->lock, flags);
			*subchn->runtime->status = SND_PCM_STATUS_PAUSED;
			spin_unlock_irqrestore(&subchn->runtime->lock, flags);
		}
		break;

	case SND_PCM_TRIGGER_PAUSE_RELEASE:
		if (!RME96_ISPLAYING(rme96)) {
			snd_rme96_playback_start(rme96, 1);
			spin_lock_irqsave(&subchn->runtime->lock, flags);
			*subchn->runtime->status = SND_PCM_STATUS_RUNNING;
			spin_unlock_irqrestore(&subchn->runtime->lock, flags);
		}
		break;
		
	default:
		return -EINVAL;
	}
	return 0;
}

static int
snd_rme96_capture_trigger(void *private_data,
			  snd_pcm_subchn_t *subchn, 
			  int cmd)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	unsigned long flags;

	switch (cmd) {
	case SND_PCM_TRIGGER_SYNC_GO:
	case SND_PCM_TRIGGER_GO:
		if (!RME96_ISRECORDING(rme96)) {
			if (subchn != rme96->capture_subchn) {
				return -EBUSY;
			}
			snd_rme96_capture_start(rme96, 0);
		}
		break;

	case SND_PCM_TRIGGER_STOP:
		if (RME96_ISRECORDING(rme96)) {
			if (subchn != rme96->capture_subchn) {
				return -EBUSY;
			}
			snd_rme96_capture_stop(rme96);
		}
		break;

	case SND_PCM_TRIGGER_PAUSE_PUSH:
		if (RME96_ISRECORDING(rme96)) {
			snd_rme96_capture_stop(rme96);
		}
		spin_lock_irqsave(&subchn->runtime->lock, flags);
		*subchn->runtime->status = SND_PCM_STATUS_PAUSED;
		spin_unlock_irqrestore(&subchn->runtime->lock, flags);
		break;

	case SND_PCM_TRIGGER_PAUSE_RELEASE:
		if (!RME96_ISRECORDING(rme96)) {
			snd_rme96_capture_start(rme96, 1);
		}
		spin_lock_irqsave(&subchn->runtime->lock, flags);
		*subchn->runtime->status = SND_PCM_STATUS_RUNNING;
		spin_unlock_irqrestore(&subchn->runtime->lock, flags);
		break;
		
	default:
		return -EINVAL;
	}

	return 0;
}

static unsigned int
snd_rme96_playback_pointer(void *private_data,
			   snd_pcm_subchn_t *subchn)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	
	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	return rme96->rcreg & RME96_RCR_AUDIO_ADDR_MASK;
}

static unsigned int
snd_rme96_capture_pointer(void *private_data,
			  snd_pcm_subchn_t *subchn)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	
	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
	return rme96->rcreg & RME96_RCR_AUDIO_ADDR_MASK;
}

void
snd_rme96_free(void *private_data)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	
	if (rme96 == NULL) {
		return;
	}
	snd_rme96_playback_stop(rme96);
	snd_rme96_capture_stop(rme96);
	snd_rme96_proc_done(rme96);
	snd_card_unregister(rme96->card);
        if (rme96->iobase != NULL) {
                iounmap(rme96->iobase);
        }
	snd_kfree(rme96);	
}

int __init
snd_rme96_create(int device,
		 rme96_t *rme96)
{
	int err;

	spin_lock_init(&rme96->lock);
	if ((rme96->iobase = ioremap(rme96->port, RME96_IO_SIZE)) == NULL) {
		return -ENOMEM;
	}
	
	/* set up ALSA pcm device */
	if ((err = snd_pcm_new(rme96->card, "Digi96", device,
			       1, 1, &rme96->pcm)) < 0)
	{
		return err;
	}
	rme96->pcm->private_data = rme96;
	strcpy(rme96->pcm->name, "Digi96");

	rme96->pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = rme96;
	rme96->pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open =
		snd_rme96_playback_open;
	rme96->pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close =
		snd_rme96_playback_close;
	rme96->playback_prepared = 0;

	rme96->pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = rme96;
	rme96->pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_rme96_capture_open;
	rme96->pcm->chn[SND_PCM_CHANNEL_CAPTURE].close =
		snd_rme96_capture_close;
	rme96->capture_prepared = 0;

	rme96->pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;

	/* make sure playback/capture is stopped, if by some reason active */
	snd_rme96_playback_stop(rme96);
	snd_rme96_capture_stop(rme96);
	
	/* set default values in registers */
	rme96->wcreg =
		RME96_WCR_FREQ_1 | /* set 44.1 kHz playback */
		RME96_WCR_SEL |    /* normal playback */
		RME96_WCR_MASTER | /* set to master clock mode */
		RME96_WCR_INP_0;   /* set coaxial input */

	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	
	/* reset the ADC (if available) */
	rme96->areg = RME96_AR_PD2; 
	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
	rme96->areg = 0;
	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);

	/* reset playback and record buffer pointers */
	writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
	writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);

	/* init switch interface */
	if ((err = snd_rme96_create_switches(rme96->card, rme96)) < 0) {
		return err;
	}

        /* init proc interface */
	snd_rme96_proc_init(rme96);
	
	return 0;
}

/*
 * proc interface
 */

static void 
snd_rme96_proc_read(snd_info_buffer_t *buffer,
		    void *private_data)
{
	rme96_t *rme96 = (rme96_t *)private_data;
	
	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);

	snd_iprintf(buffer, rme96->card->longname);
	snd_iprintf(buffer, " (index #%d)\n", rme96->card->number + 1);

	snd_iprintf(buffer, "\nGeneral settings\n");
	if (rme96->wcreg & RME96_WCR_IDIS) {
		snd_iprintf(buffer, "  block size: N/A (interrupts "
			    "disabled)\n");
	} else if (rme96->wcreg & RME96_WCR_ISEL) {
		snd_iprintf(buffer, "  block size: 2048 bytes");
	} else {
		snd_iprintf(buffer, "  block size: 8192 bytes\n");
	}	
	snd_iprintf(buffer, "\nDigital input settings\n");
	switch (snd_rme96_capture_getinputtype(rme96)) {
	case RME96_INPUT_OPTICAL:
		snd_iprintf(buffer, "  input: optical\n");
		break;
	case RME96_INPUT_COAXIAL:
		snd_iprintf(buffer, "  input: coaxial\n");
		break;
	case RME96_INPUT_INTERNAL:
		snd_iprintf(buffer, "  input: internal\n");
		break;
	case RME96_INPUT_XLR:
		snd_iprintf(buffer, "  input: XLR\n");
		break;
	}
	if (snd_rme96_capture_getrate(rme96) < 0) {
		snd_iprintf(buffer, "  sample rate: no valid signal\n");
	} else {
		snd_iprintf(buffer, "  sample rate: %d Hz\n",
			    snd_rme96_capture_getrate(rme96));
	}
	if (rme96->wcreg & RME96_WCR_MODE24_2) {
		snd_iprintf(buffer, "  sample format: 24 bit\n");
	} else {
		snd_iprintf(buffer, "  sample format: 16 bit\n");
	}
	
	snd_iprintf(buffer, "\nDigital output settings\n");
	if (rme96->wcreg & RME96_WCR_SEL) {
		snd_iprintf(buffer, "  output signal: normal playback\n");
	} else {
		snd_iprintf(buffer, "  output signal: same as input\n");
	}
	snd_iprintf(buffer, "  sample rate: %d Hz\n",
		    snd_rme96_playback_getrate(rme96));
	if (rme96->wcreg & RME96_WCR_MODE24) {
		snd_iprintf(buffer, "  sample format: 24 bit\n");
	} else {
		snd_iprintf(buffer, "  sample format: 16 bit\n");
	}
	if (rme96->areg & RME96_AR_WSEL) {
		snd_iprintf(buffer, "  clock mode: word clock\n");
	} else if (rme96->wcreg & RME96_WCR_MASTER) {
		snd_iprintf(buffer, "  clock mode: master\n");
	} else {
		snd_iprintf(buffer, "  clock mode: slave\n");
	}
	if (rme96->wcreg & RME96_WCR_PRO) {
		snd_iprintf(buffer, "  format: AES/EBU "
			    "(professional)\n");
	} else {
		snd_iprintf(buffer, "  format: S/PDIF "
			    "(consumer)\n");
	}
	if (rme96->wcreg & RME96_WCR_EMP) {
		snd_iprintf(buffer, "  emphasis: on\n");
	} else {
		snd_iprintf(buffer, "  emphasis: off\n");
	}
	if (rme96->wcreg & RME96_WCR_DOLBY) {
		snd_iprintf(buffer, "  non-audio (dolby): on\n");
	} else {
		snd_iprintf(buffer, "  non-audio (dolby): off\n");
	}
}

static void __init 
snd_rme96_proc_init(rme96_t *rme96)
{
	snd_info_entry_t *entry;

	if ((entry = snd_info_create_entry(rme96->card, "RME96")) != NULL) {
		entry->private_data = rme96;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 256;
		entry->t.text.read = snd_rme96_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	rme96->proc_entry = entry;
}

static void
snd_rme96_proc_done(rme96_t * rme96)
{
	if (rme96->proc_entry) {
		snd_info_unregister(rme96->proc_entry);
		rme96->proc_entry = NULL;
	}
}

/*
 * switch interface
 */

static int
snd_rme96_get_flag(snd_card_t *card,
		   snd_kswitch_t *kswitch,
		   snd_switch_t *uswitch)
{
	rme96_t *rme96 = (rme96_t *)kswitch->private_data;
	unsigned long flags;

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&rme96->lock, flags);	
	uswitch->value.enable = (rme96->wcreg & kswitch->private_value) ? 1 : 0;
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_set_flag(snd_card_t *card,
		   snd_kswitch_t *kswitch,
		   snd_switch_t *uswitch)
{
	rme96_t *rme96 = (rme96_t *)kswitch->private_data;
	unsigned long flags;
	
	spin_lock_irqsave(&rme96->lock, flags);
	if (uswitch->value.enable) {
		rme96->wcreg |= kswitch->private_value;
	} else {
		rme96->wcreg &= ~kswitch->private_value;
	}
	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static snd_kswitch_t snd_rme96_switch_loopback = {
	"Loopback Input",
	(snd_get_switch_t *)snd_rme96_get_flag,
	(snd_set_switch_t *)snd_rme96_set_flag,
	RME96_WCR_SEL,
	NULL,
	NULL	
};

static snd_kswitch_t snd_rme96_switch_emphasis = {
	"Emphasis",
	(snd_get_switch_t *)snd_rme96_get_flag,
	(snd_set_switch_t *)snd_rme96_set_flag,
	RME96_WCR_EMP,
	NULL,
	NULL	
};

static snd_kswitch_t snd_rme96_switch_nonaudio = {
	"Non-Audio (Dolby)",
	(snd_get_switch_t *)snd_rme96_get_flag,
	(snd_set_switch_t *)snd_rme96_set_flag,
	RME96_WCR_DOLBY,
	NULL,
	NULL	
};

static snd_kswitch_t snd_rme96_switch_pro_output = {
	"Professional Output Format",
	(snd_get_switch_t *)snd_rme96_get_flag,
	(snd_set_switch_t *)snd_rme96_set_flag,
	RME96_WCR_PRO,
	NULL,
	NULL	
};

static int
snd_rme96_get_spdif_in(snd_card_t *card,
		       snd_kswitch_t *kswitch,
		       snd_switch_t *uswitch)
	
{
	rme96_t *rme96 = (rme96_t *)kswitch->private_data;
	unsigned long flags;
	
	uswitch->type = SND_SW_TYPE_BYTE;
	uswitch->low = 0;
	uswitch->high = 3;

	spin_lock_irqsave(&rme96->lock, flags);
	uswitch->value.data8[0] = snd_rme96_capture_getinputtype(rme96);
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_set_spdif_in(snd_card_t *card,
		       snd_kswitch_t *kswitch,
		       snd_switch_t *uswitch)

{
	rme96_t *rme96 = (rme96_t *) kswitch->private_data;
	unsigned long flags;
	int err;

	if (uswitch->value.data8[0] > 3) {
		return -EINVAL;
	}
	spin_lock_irqsave(&rme96->lock, flags);
	err = snd_rme96_capture_setinputtype(rme96, uswitch->value.data8[0]);
	spin_unlock_irqrestore(&rme96->lock, flags);	
	return err;
}

static snd_kswitch_t snd_rme96_switch_spdif_in = {
	"S/PDIF Input Connector",
	(snd_get_switch_t *)snd_rme96_get_spdif_in,
	(snd_set_switch_t *)snd_rme96_set_spdif_in,
	0,
	NULL,
	NULL
};

static int
snd_rme96_get_clock_mode(snd_card_t *card,
			 snd_kswitch_t *kswitch,
			 snd_switch_t *uswitch)
	
{
	rme96_t *rme96 = (rme96_t *)kswitch->private_data;
	unsigned long flags;
	
	uswitch->type = SND_SW_TYPE_BYTE;
	uswitch->low = 0;
	uswitch->high = 2;

	spin_lock_irqsave(&rme96->lock, flags);
	uswitch->value.data8[0] = snd_rme96_getclockmode(rme96);
	spin_unlock_irqrestore(&rme96->lock, flags);
	return 0;
}

static int
snd_rme96_set_clock_mode(snd_card_t *card,
			 snd_kswitch_t *kswitch,
			 snd_switch_t *uswitch)

{
	rme96_t *rme96 = (rme96_t *) kswitch->private_data;
	unsigned long flags;
	int err;

	if (uswitch->value.data8[0] > 2) {
		return -EINVAL;
	}
	spin_lock_irqsave(&rme96->lock, flags);
	err = snd_rme96_setclockmode(rme96, uswitch->value.data8[0]);
	spin_unlock_irqrestore(&rme96->lock, flags);	
	return err;
}

static snd_kswitch_t snd_rme96_switch_clock_mode = {
	"Clock Mode",
	(snd_get_switch_t *)snd_rme96_get_clock_mode,
	(snd_set_switch_t *)snd_rme96_set_clock_mode,
	0,
	NULL,
	NULL
};

int
snd_rme96_create_switches(snd_card_t *card,
			  rme96_t *rme96)
{
	if (snd_control_switch_new(card, &snd_rme96_switch_spdif_in,
				   rme96) == 0 ||
	    snd_control_switch_new(card, &snd_rme96_switch_clock_mode,
				   rme96) == 0 ||
	    snd_control_switch_new(card, &snd_rme96_switch_loopback,
				   rme96) == 0 ||
	    snd_control_switch_new(card, &snd_rme96_switch_emphasis,
				   rme96) == 0 ||
	    snd_control_switch_new(card, &snd_rme96_switch_nonaudio,
				   rme96) == 0 ||	    
	    snd_control_switch_new(card, &snd_rme96_switch_pro_output,
				   rme96) == 0)
	{
		return -ENOMEM;
	}
	
	return 0;
}

/*
 * module stuff
 */

EXPORT_SYMBOL(snd_rme96_create);
EXPORT_SYMBOL(snd_rme96_free);
EXPORT_SYMBOL(snd_rme96_interrupt);

static int __init alsa_rme96_init(void)
{
	return 0;
}

static void __exit alsa_rme96_exit(void)
{
}

module_init(alsa_rme96_init)
module_exit(alsa_rme96_exit)
