/*   -*- linux-c -*-
 *  Asihpi soundcard
 *  Copyright (c) by AudioScience Inc <alsa@audioscience.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of version 2 of the GNU General Public License as 
 *   published by the Free Software Foundation;
 *
 *   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *
 *  The following is not a condition of use, merely a request:
 *  If you modify this program, particularly if you fix errors, AudioScience Inc
 *  would appreciate it if you grant us the right to use those modifications
 *  for any purpose including commercial applications.
 */
	
// Mixer control
#ifndef ASI_STYLE_NAMES
#define ASI_STYLE_NAMES 1
#endif

#include <sound/driver.h>
#include <linux/version.h>
#include <linux/init.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
#include <linux/jiffies.h>
#else
#include <linux/sched.h>
#endif
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/info.h>
#include <sound/initval.h>

#define HPI_OS_LINUX
#include "hpi.h"

static int mixer_dump;
module_param(mixer_dump, int, 0444);
MODULE_PARM_DESC(mixer_dump, "Enable mixer control dump");

#define DEFAULT_SAMPLERATE 44100
static int adapter_fs = DEFAULT_SAMPLERATE;
module_param(adapter_fs, int, 0444);
MODULE_PARM_DESC(adapter_fs, "Adapter HW samplerate (if supported)");


MODULE_AUTHOR("Eliot Blennerhassett <EBlennerhassett@audioscience.com>");
MODULE_DESCRIPTION("AudioScience soundcard (HPI)");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{AudioScience,HPI soundcard}}");

#define MAX_PCM_DEVICES	 4
#define MAX_PCM_SUBSTREAMS	16
#define MAX_MIDI_DEVICES	 0

/* defaults */
#ifndef MAX_BUFFER_SIZE
#define MAX_BUFFER_SIZE		(64*1024)
#endif

#define TIMER_JIFFIES 15

typedef struct snd_card_asihpi {
	snd_card_t *card;
	spinlock_t mixer_lock;
	// ASI 
	HW16 wAdapterIndex;
	HW32 dwSerialNumber;
	HW16 wType;
	HW16 wVersion;
	HW16 wNumOutStreams;
	HW16 wNumInStreams;

	HPI_HMIXER hMixer;
} snd_card_asihpi_t;

typedef struct snd_card_asihpi_pcm {
	snd_card_asihpi_t *asihpi;
	spinlock_t lock;
	struct timer_list timer;
	unsigned int pcm_size;
	unsigned int pcm_count;
	unsigned int pcm_jiffie_per_period;
	unsigned int pcm_irq_pos;	/* IRQ position */
	unsigned int pcm_buf_pos;	/* position in buffer */
	snd_pcm_substream_t *substream;
	HPI_HOSTREAM hStream;
	HPI_DATA Data;
	unsigned int pcm_hpi_pos;	/* position in buffer */

} snd_card_asihpi_pcm_t;

static HPI_HSUBSYS *phSubSys;	/* handle to HPI audio subsystem */
static snd_card_t *snd_asihpi_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;

static void _HandleError(HW16 err, int line)
{
	char ErrorText[80];

	if (err) {
		HPI_GetErrorText(err, ErrorText);
		printk(KERN_WARNING "line %d: %s\n", line, ErrorText);
	}
}

#define HPI_HandleError(x)  _HandleError(x,__LINE__)
/***************************** GENERAL PCM ****************/
static int snd_card_asihpi_pcm_hw_params(snd_pcm_substream_t * substream,
					 snd_pcm_hw_params_t * params)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	unsigned int bps;

	bps = params_rate(params) * params_channels(params);
	bps *= snd_pcm_format_width(params_format(params));
	bps /= 8;
	if (bps <= 0)
		return -EINVAL;
	dpcm->pcm_size = params_buffer_bytes(params);
	dpcm->pcm_count = params_period_bytes(params);
	dpcm->pcm_jiffie_per_period = (dpcm->pcm_count * HZ / bps);
	snd_printd("Jiffies per period %ld\n",
		   (long)dpcm->pcm_jiffie_per_period);
	dpcm->pcm_irq_pos = 0;
	dpcm->pcm_buf_pos = 0;
	return 0;
}

static void snd_card_asihpi_pcm_timer_start(snd_pcm_substream_t *
					    substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;

	dpcm->timer.expires = dpcm->pcm_jiffie_per_period + jiffies;
	add_timer(&dpcm->timer);
}

static void snd_card_asihpi_pcm_timer_stop(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;

	del_timer(&dpcm->timer);
}

static void snd_card_asihpi_runtime_free(snd_pcm_runtime_t * runtime)
{
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	kfree(dpcm);
}

static snd_pcm_format_t hpi_to_alsa_formats[] = {
	-1,			/* INVALID */
	SNDRV_PCM_FORMAT_U8,	/* { HPI_FORMAT_PCM8_UNSIGNED        1 */
	SNDRV_PCM_FORMAT_S16,	/* { HPI_FORMAT_PCM16_SIGNED         2 */
	-1,			/* { HPI_FORMAT_MPEG_L1              3 */
	SNDRV_PCM_FORMAT_MPEG,	/* { HPI_FORMAT_MPEG_L2              4 */
	SNDRV_PCM_FORMAT_MPEG,	/* { HPI_FORMAT_MPEG_L3              5 */
	-1,			/* { HPI_FORMAT_DOLBY_AC2            6 */
	-1,			/* { HPI_FORMAT_DOLBY_AC3            7 */
	SNDRV_PCM_FORMAT_S16_BE,	/* { HPI_FORMAT_PCM16_BIGENDIAN      8 */
	-1,			/* { HPI_FORMAT_AA_TAGIT1_HITS       9 */
	-1,			/* { HPI_FORMAT_AA_TAGIT1_INSERTS   10 */
	SNDRV_PCM_FORMAT_S32,	/* { HPI_FORMAT_PCM32_SIGNED        11 */
	-1,			/* { HPI_FORMAT_RAW_BITSTREAM       12 */
	-1,			/* { HPI_FORMAT_AA_TAGIT1_HITS_EX1  13 */
	SNDRV_PCM_FORMAT_FLOAT,	/* { HPI_FORMAT_PCM32_FLOAT             14 */
	SNDRV_PCM_FORMAT_S24	/* { HPI_FORMAT_PCM24_SIGNED        15 */
};


static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsaFormat,
					   HW16 * hpiFormat)
{
	HW16 wFormat;

	for (wFormat = HPI_FORMAT_PCM8_UNSIGNED;
	     wFormat <= HPI_FORMAT_PCM24_SIGNED; wFormat++) {
		if (hpi_to_alsa_formats[wFormat] == alsaFormat) {
			*hpiFormat = wFormat;
			// snd_printd(KERN_INFO "Matched alsa format %d to asi format %d\n",alsaFormat,wFormat);
			return 0;
		}
	}

	snd_printd(KERN_WARNING "Failed match for alsa format %d\n",
		   alsaFormat);
	*hpiFormat = 0;
	return -EINVAL;
}

/***************************** PLAYBACK OPS ****************/
static void snd_card_asihpi_playback_timer_function(unsigned long data)
{
	snd_card_asihpi_pcm_t *dpcm = (snd_card_asihpi_pcm_t *)data;
	snd_pcm_runtime_t *runtime = dpcm->substream->runtime;

	unsigned int pos;
	int delta;
	HW16 wState, err;
	HW32 dwBufferSize;
	HW32 dwDataToPlay;
	HW32 dwSamplesPlayed;

	dpcm->timer.expires = dpcm->pcm_jiffie_per_period + jiffies;
	add_timer(&dpcm->timer);
	spin_lock_irq(&dpcm->lock);

	err =
	    HPI_OutStreamGetInfoEx(phSubSys, dpcm->hStream, &wState,
				   &dwBufferSize, &dwDataToPlay,
				   &dwSamplesPlayed, NULL);
	HPI_HandleError(err);
	snd_printd(KERN_INFO
		   "Playback timer state=%d, played=%d, left=%d\n", wState,
		   (int)dwSamplesPlayed, (int)dwDataToPlay);

	if ((wState == HPI_STATE_DRAINED))	// pretend to keep moving, so alsa thinks all its data has been consumed.
		pos = dpcm->pcm_buf_pos + dpcm->pcm_count;
	else
		pos = frames_to_bytes(runtime, dwSamplesPlayed);

	pos %= dpcm->pcm_size;
	delta = pos - dpcm->pcm_buf_pos;
	if (delta < 0)
		delta += dpcm->pcm_size;
	dpcm->pcm_irq_pos += delta;
	dpcm->pcm_buf_pos = pos;

	if (dpcm->pcm_irq_pos >= dpcm->pcm_count) {
		dpcm->pcm_irq_pos %= dpcm->pcm_count;
		snd_pcm_period_elapsed(dpcm->substream);
	}
	spin_unlock_irq(&dpcm->lock);
}

static int snd_card_asihpi_playback_ioctl(snd_pcm_substream_t * substream,
					  unsigned int cmd, void *arg)
{
	snd_printd(KERN_INFO "playback ioctl %d\n", cmd);
	return snd_pcm_lib_ioctl(substream, cmd, arg);
}

static int snd_card_asihpi_playback_trigger(snd_pcm_substream_t *
					    substream, int cmd)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	HW16 err;

	snd_printd(KERN_INFO "playback trigger\n");
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		snd_printd("\tstart\n");
		err = HPI_OutStreamStart(phSubSys, dpcm->hStream);
		snd_card_asihpi_pcm_timer_start(substream);
		HPI_HandleError(err);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		snd_printd("\tstop\n");
		snd_card_asihpi_pcm_timer_stop(substream);
		//?     err = HPI_OutStreamStop(phSubSys,dpcm->hStream );
		err = HPI_OutStreamReset(phSubSys, dpcm->hStream);
		HPI_HandleError(err);
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		snd_printd("\tpause release\n");
		err = HPI_OutStreamStart(phSubSys, dpcm->hStream);
		snd_card_asihpi_pcm_timer_start(substream);
		HPI_HandleError(err);
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		snd_printd("\tpause push\n");
		snd_card_asihpi_pcm_timer_stop(substream);
		err = HPI_OutStreamStop(phSubSys, dpcm->hStream);
		HPI_HandleError(err);
	default:
		snd_printd("\tINVALID\n");
		return -EINVAL;
	}
	return 0;
}

static int snd_card_asihpi_playback_hw_params(snd_pcm_substream_t *
					      substream,
					      snd_pcm_hw_params_t * params)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	int err;
	HW16 wFormat;

	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params))) < 0)
		return err;

	if ((err =
	     snd_card_asihpi_format_alsa2hpi(params_format(params),
					     &wFormat)) != 0) {
		return err;
	}

	err =
	    HPI_FormatCreate(&dpcm->Data.Format, params_channels(params),
			     wFormat, params_rate(params), 0, 0);
	HPI_HandleError(err);

	dpcm->Data.dwpbData = (HW32) runtime->dma_area;
	dpcm->Data.dwDataSize = MAX_BUFFER_SIZE;

	snd_printd(KERN_INFO "playback hwparams\n");

	return snd_card_asihpi_pcm_hw_params(substream, params);
}

static int
snd_card_asihpi_playback_hw_free(snd_pcm_substream_t * substream)
{
	snd_pcm_lib_free_pages(substream);
	return 0;
}


static int snd_card_asihpi_playback_prepare(snd_pcm_substream_t *
					    substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	int err;

	snd_printd(KERN_INFO "playback prepare\n");

	err = HPI_OutStreamReset(phSubSys, dpcm->hStream);
	HPI_HandleError(err);
	dpcm->pcm_irq_pos = 0;
	dpcm->pcm_buf_pos = 0;

	return 0;
}

static snd_pcm_uframes_t
snd_card_asihpi_playback_pointer(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;

	return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
}

static snd_pcm_hardware_t snd_card_asihpi_playback = {
	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE,
	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
	.rate_min = 5500,
	.rate_max = 48000,
	.channels_min = 1,
	.channels_max = 2,
	.buffer_bytes_max = MAX_BUFFER_SIZE,
	.period_bytes_min = MAX_BUFFER_SIZE / 8,
	.period_bytes_max = MAX_BUFFER_SIZE / 2,
	.periods_min = 2,
	.periods_max = 8,
	.fifo_size = 0,
};

static void snd_card_asihpi_playback_format(HPI_HOSTREAM hStream,
					    u64 * formats)
{
	HPI_FORMAT hpi_format = { 44100, 128000, 0, 0, 0, 2, 0 };
	HW16 wFormat;
	HW16 err;

	for (wFormat = HPI_FORMAT_PCM8_UNSIGNED;
	     wFormat <= HPI_FORMAT_PCM24_SIGNED; wFormat++) {
		hpi_format.wFormat = wFormat;
		err =
		    HPI_OutStreamQueryFormat(phSubSys, hStream,
					     &hpi_format);
		if (!err && (hpi_to_alsa_formats[wFormat] != -1)) {
			*formats |= (1ULL << hpi_to_alsa_formats[wFormat]);
			//snd_printk(KERN_INFO "HPI format %d, alsa %d\n",wFormat,hpi_to_alsa_formats[wFormat]);
		}
	}
}

static int snd_card_asihpi_playback_open(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm;
	snd_card_asihpi_t *asihpi = snd_pcm_substream_chip(substream);
	HW16 err;

	dpcm = kcalloc(1, sizeof(*dpcm), GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;

	err =
	    HPI_OutStreamOpen(phSubSys, asihpi->wAdapterIndex,
			      substream->number, &dpcm->hStream);
	HPI_HandleError(err);
	if (err)
		kfree(dpcm);
	if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
		return -EBUSY;
	if (err)
		return -EIO;

	snd_card_asihpi_playback_format(dpcm->hStream,
					&snd_card_asihpi_playback.formats);

	init_timer(&dpcm->timer);
	dpcm->timer.data = (unsigned long) dpcm;
	dpcm->timer.function = snd_card_asihpi_playback_timer_function;
	spin_lock_init(&dpcm->lock);
	dpcm->substream = substream;
	runtime->private_data = dpcm;
	runtime->private_free = snd_card_asihpi_runtime_free;
	runtime->hw = snd_card_asihpi_playback;

	snd_printd(KERN_INFO "playback open\n");

	return 0;
}

static int snd_card_asihpi_playback_close(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	HW16 err;

	err = HPI_OutStreamClose(phSubSys, dpcm->hStream);
	HPI_HandleError(err);

	snd_printd(KERN_INFO "playback close\n");

	return 0;
}

static int snd_card_asihpi_playback_copy(snd_pcm_substream_t * substream,
					 int channel,
					 snd_pcm_uframes_t pos, void *src,
					 snd_pcm_uframes_t count)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	HW16 err;
	unsigned int len;

	len = frames_to_bytes(runtime, count);

	if (copy_from_user(runtime->dma_area, src, len))
		return -EFAULT;

	snd_printd(KERN_INFO "playback_copy %u bytes\n", len);

	dpcm->Data.dwDataSize = len;
	dpcm->Data.dwpbData = (HW32) runtime->dma_area;

	err = HPI_OutStreamWrite(phSubSys, dpcm->hStream, &dpcm->Data);
	HPI_HandleError(err);

	return 0;
}

static int snd_card_asihpi_playback_silence(snd_pcm_substream_t *
					    substream, int channel,
					    snd_pcm_uframes_t pos,
					    snd_pcm_uframes_t count)
{
	unsigned int len;
	HW16 err;
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;

	len = frames_to_bytes(runtime, count);
	snd_printd(KERN_INFO "playback silence  %u bytes\n", len);

	memset(runtime->dma_area, 0, len);
	err = HPI_OutStreamWrite(phSubSys, dpcm->hStream, &dpcm->Data);
	HPI_HandleError(err);
	return 0;
}

static snd_pcm_ops_t snd_card_asihpi_playback_ops = {
	.open = snd_card_asihpi_playback_open,
	.close = snd_card_asihpi_playback_close,
	.ioctl = snd_card_asihpi_playback_ioctl,
	.hw_params = snd_card_asihpi_playback_hw_params,
	.hw_free = snd_card_asihpi_playback_hw_free,
	.prepare = snd_card_asihpi_playback_prepare,
	.trigger = snd_card_asihpi_playback_trigger,
	.pointer = snd_card_asihpi_playback_pointer,
	.copy = snd_card_asihpi_playback_copy,
	.silence = snd_card_asihpi_playback_silence,
};

/***************************** CAPTURE OPS ****************/
static void snd_card_asihpi_capture_timer_function(unsigned long data)
{
	snd_card_asihpi_pcm_t *dpcm = (snd_card_asihpi_pcm_t *)data;
	HW16 wState, err;
	HW32 dwBufferSize, dwDataToPlay, dwSamplesPlayed;

	dpcm->timer.expires = dpcm->pcm_jiffie_per_period + jiffies;
	add_timer(&dpcm->timer);
	spin_lock_irq(&dpcm->lock);

	err =
	    HPI_InStreamGetInfoEx(phSubSys, dpcm->hStream, &wState,
				  &dwBufferSize, &dwDataToPlay,
				  &dwSamplesPlayed, NULL);
	HPI_HandleError(err);

	dpcm->pcm_buf_pos =
	    (dpcm->pcm_irq_pos + dwDataToPlay) % dpcm->pcm_size;
	snd_printd("Capture timer %d samples, %d left, pos %d\n",
		   (int)dwSamplesPlayed, (int)dwDataToPlay, dpcm->pcm_buf_pos);
	if (dwDataToPlay >= dpcm->pcm_count) {
		snd_pcm_period_elapsed(dpcm->substream);
	}
	spin_unlock_irq(&dpcm->lock);
}

static int snd_card_asihpi_capture_ioctl(snd_pcm_substream_t * substream,
					 unsigned int cmd, void *arg)
{
	return snd_pcm_lib_ioctl(substream, cmd, arg);
}

static int snd_card_asihpi_capture_prepare(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	int err;

	err = HPI_InStreamReset(phSubSys, dpcm->hStream);
	HPI_HandleError(err);
	dpcm->pcm_irq_pos = 0;
	dpcm->pcm_buf_pos = 0;

	snd_printd("Capture Prepare\n");
	return 0;
}


static int snd_card_asihpi_capture_hw_params(snd_pcm_substream_t *
					     substream,
					     snd_pcm_hw_params_t * params)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	int err;
	HW16 wFormat;

	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params))) < 0)
		return err;
	if ((err =
	     snd_card_asihpi_format_alsa2hpi(params_format(params),
					     &wFormat)) != 0)
		return err;

	// HPI_InstreamQueryFormat();

	err =
	    HPI_FormatCreate(&dpcm->Data.Format, params_channels(params),
			     wFormat, params_rate(params), 0, 0);
	HPI_HandleError(err);

	if ((err =
	     HPI_InStreamSetFormat(phSubSys, dpcm->hStream,
				   &dpcm->Data.Format)) != 0)
		return -EINVAL;

	dpcm->Data.dwpbData = (HW32) runtime->dma_area;
	dpcm->Data.dwDataSize = MAX_BUFFER_SIZE;

	snd_printd(KERN_INFO "Capture hwparams\n");

	return snd_card_asihpi_pcm_hw_params(substream, params);
}

static int snd_card_asihpi_capture_hw_free(snd_pcm_substream_t * substream)
{
	snd_pcm_lib_free_pages(substream);
	return 0;
}

static int snd_card_asihpi_capture_trigger(snd_pcm_substream_t * substream,
					   int cmd)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	HW16 err;

	if (cmd == SNDRV_PCM_TRIGGER_START) {
		snd_printd("Capture Trigger Start\n");
		err = HPI_InStreamStart(phSubSys, dpcm->hStream);
		snd_card_asihpi_pcm_timer_start(substream);
	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
		snd_printd("Capture Trigger Stop\n");
		snd_card_asihpi_pcm_timer_stop(substream);
		err = HPI_InStreamStop(phSubSys, dpcm->hStream);
	} else {
		return -EINVAL;
	}
	return 0;
}

static snd_pcm_uframes_t
snd_card_asihpi_capture_pointer(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;

	//snd_printd("Capture pointer %d\n",dpcm->pcm_buf_pos);
	return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
}

static snd_pcm_hardware_t snd_card_asihpi_capture = {
	.info = SNDRV_PCM_INFO_INTERLEAVED,
	.formats = SNDRV_PCM_FMTBIT_S16,
	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
	.rate_min = 5500,
	.rate_max = 48000,
	.channels_min = 1,
	.channels_max = 2,
	.buffer_bytes_max = MAX_BUFFER_SIZE,
	.period_bytes_min = MAX_BUFFER_SIZE / 16,
	.period_bytes_max = MAX_BUFFER_SIZE,
	.periods_min = 1,
	.periods_max = 16,
	.fifo_size = 0,
};

static void snd_card_asihpi_capture_format(HPI_HOSTREAM hStream,
					   u64 * formats)
{
	HPI_FORMAT hpi_format = { 44100, 128000, 0, 0, 0, 2, 0 };
	HW16 wFormat;
	HW16 err;

	for (wFormat = HPI_FORMAT_PCM8_UNSIGNED;
	     wFormat <= HPI_FORMAT_PCM24_SIGNED; wFormat++) {
		hpi_format.wFormat = wFormat;
		err =
		    HPI_InStreamQueryFormat(phSubSys, hStream,
					    &hpi_format);
		if (!err)
			*formats |= (1ULL << hpi_to_alsa_formats[wFormat]);
	}
}

static int snd_card_asihpi_capture_open(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm;
	snd_card_asihpi_t *asihpi = snd_pcm_substream_chip(substream);
	HW16 err;

	dpcm = kcalloc(1, sizeof(*dpcm), GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;

	snd_printd("HPI_InStreamOpen Adapter %d Stream %d\n",
		   asihpi->wAdapterIndex, substream->number);

	err =
	    HPI_InStreamOpen(phSubSys, asihpi->wAdapterIndex,
			     substream->number, &dpcm->hStream);
	HPI_HandleError(err);
	if (err)
		kfree(dpcm);
	if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
		return -EBUSY;
	if (err)
		return -EIO;

	snd_card_asihpi_capture_format(dpcm->hStream,
				       &snd_card_asihpi_capture.formats);

	init_timer(&dpcm->timer);
	dpcm->timer.data = (unsigned long) dpcm;
	dpcm->timer.function = snd_card_asihpi_capture_timer_function;
	spin_lock_init(&dpcm->lock);
	dpcm->substream = substream;
	runtime->private_data = dpcm;
	runtime->private_free = snd_card_asihpi_runtime_free;
	runtime->hw = snd_card_asihpi_capture;
	return 0;
}

static int snd_card_asihpi_capture_close(snd_pcm_substream_t * substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	HW16 err;

	err = HPI_InStreamClose(phSubSys, dpcm->hStream);
	HPI_HandleError(err);

	return 0;
}

static int snd_card_asihpi_capture_copy(snd_pcm_substream_t * substream,
					int channel, snd_pcm_uframes_t pos,
					void *dst, snd_pcm_uframes_t count)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	snd_card_asihpi_pcm_t *dpcm = runtime->private_data;
	HW16 err;

	dpcm->Data.dwpbData = (HW32) runtime->dma_area;
	dpcm->Data.dwDataSize = frames_to_bytes(runtime, count);

	//snd_printd("Capture copy %d bytes\n",dpcm->Data.dwDataSize);

	err = HPI_InStreamRead(phSubSys, dpcm->hStream, &dpcm->Data);
	HPI_HandleError(err);

	dpcm->pcm_irq_pos =
	    (dpcm->pcm_irq_pos + dpcm->Data.dwDataSize) % dpcm->pcm_size;

	if (copy_to_user(dst, runtime->dma_area, dpcm->Data.dwDataSize))
		return -EFAULT;

	return 0;
}

static snd_pcm_ops_t snd_card_asihpi_capture_ops = {
	.open = snd_card_asihpi_capture_open,
	.close = snd_card_asihpi_capture_close,
	.ioctl = snd_card_asihpi_capture_ioctl,
	.hw_params = snd_card_asihpi_capture_hw_params,
	.hw_free = snd_card_asihpi_capture_hw_free,
	.prepare = snd_card_asihpi_capture_prepare,
	.trigger = snd_card_asihpi_capture_trigger,
	.pointer = snd_card_asihpi_capture_pointer,
	.copy = snd_card_asihpi_capture_copy
};

static int __init snd_card_asihpi_pcm(snd_card_asihpi_t * asihpi,
				      int device, int substreams)
{
	snd_pcm_t *pcm;
	int err;

	if ((err =
	     snd_pcm_new(asihpi->card, "Asihpi PCM", device,
			 asihpi->wNumOutStreams, asihpi->wNumInStreams,
			 &pcm)) < 0)
		return err;
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
			&snd_card_asihpi_playback_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
			&snd_card_asihpi_capture_ops);
	pcm->private_data = asihpi;
	pcm->info_flags = 0;
	strcpy(pcm->name, "Asihpi PCM");

	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
					      snd_dma_continuous_data(GFP_KERNEL),
					      0, MAX_BUFFER_SIZE);
	return 0;
}

/***************************** MIXER CONTROLS ****************/
typedef struct {
	HW16 wControlType;	// HPI_CONTROL_METER, _VOLUME etc
	HW16 wSrcNodeType;
	HW16 wSrcNodeIndex;
	HW16 wDstNodeType;
	HW16 wDstNodeIndex;
} hpi_control_t;

#define TEXT(a) a

#ifdef ASI_STYLE_NAMES
#define ASIHPI_SOURCENODE_STRINGS \
{ \
	TEXT("none!"), \
	TEXT("OutStr"), \
	TEXT("LineIn"), \
	TEXT("AesIn"), \
	TEXT("Tuner"), \
	TEXT("RF"), \
	TEXT("Clock"), \
	TEXT("Bitstr"), \
	TEXT("Mic"), \
}
#else
#define ASIHPI_SOURCENODE_STRINGS \
{ \
	TEXT("no source"), \
	TEXT("PCM"), \
	TEXT("Line"), \
	TEXT("Digital"), \
	TEXT("Tuner"), \
	TEXT("RF"), \
	TEXT("Clock"), \
	TEXT("Bitstream"), \
	TEXT("Mic"), \
}
#endif

#define NUM_SOURCENODE_STRINGS 9
#if (NUM_SOURCENODE_STRINGS != (HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_BASE+1))
#error TEXT("Sourcenode strings don't match #defines")
#endif

#ifdef ASI_STYLE_NAMES
#define ASIHPI_DESTNODE_STRINGS \
{ \
	TEXT("no destination"), \
	TEXT("InStr"), \
	TEXT("LinOut"), \
	TEXT("AesOut"), \
	TEXT("RF"), \
	TEXT("Speak") \
}
#else
#define ASIHPI_DESTNODE_STRINGS \
{ \
	TEXT("no destination"), \
	TEXT("PCM"), \
	TEXT("Line"), \
	TEXT("Digital"), \
	TEXT("RF"), \
	TEXT("Speaker") \
}
#endif
#define NUM_DESTNODE_STRINGS 6
#if (NUM_DESTNODE_STRINGS != (HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_BASE+1))
#error "Destnode strings don't match #defines"
#endif

static char *asihpi_src_names[] = ASIHPI_SOURCENODE_STRINGS;
static char *asihpi_dst_names[] = ASIHPI_DESTNODE_STRINGS;

/*------------------------------------------------------------
   Volume controls
 ------------------------------------------------------------*/
static int snd_asihpi_volume_info(snd_kcontrol_t * kcontrol,
				  snd_ctl_elem_info_t * uinfo)
{
	HPI_HCONTROL hControl = kcontrol->private_value;
	HW16 err;
	short nMinGain_01dB;
	short nMaxGain_01dB;
	short nStepGain_01dB;

	err =
	    HPI_VolumeQueryRange(phSubSys, hControl, &nMinGain_01dB,
			       &nMaxGain_01dB, &nStepGain_01dB);
	if (err) {
		nMaxGain_01dB = 0;
		nMinGain_01dB = -10000;
		nStepGain_01dB = 100;
	}
	//    HPI_HandleError(err);

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;
	uinfo->value.integer.min = nMinGain_01dB / HPI_UNITS_PER_dB;
	uinfo->value.integer.max = nMaxGain_01dB / HPI_UNITS_PER_dB;
	uinfo->value.integer.step = nStepGain_01dB / HPI_UNITS_PER_dB;
	return 0;
}

static int snd_asihpi_volume_get(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	short anGain0_01dB[HPI_MAX_CHANNELS];

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	HPI_VolumeGetGain(phSubSys, hControl, anGain0_01dB);
	ucontrol->value.integer.value[0] = anGain0_01dB[0] / 100;
	ucontrol->value.integer.value[1] = anGain0_01dB[1] / 100;

	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return 0;
}

static int snd_asihpi_volume_put(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change;
	HPI_HCONTROL hControl = kcontrol->private_value;
	short anGain0_01dB[HPI_MAX_CHANNELS];


	anGain0_01dB[0] =
	    (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
	anGain0_01dB[1] =
	    (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB;
	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	/*  change = asihpi->mixer_volume[addr][0] != left ||
	   asihpi->mixer_volume[addr][1] != right;
	 */
	change = 1;
	HPI_VolumeSetGain(phSubSys, hControl, anGain0_01dB);
	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return change;
}

static void __init snd_asihpi_volume_new(hpi_control_t * asihpi_control,
					 snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_volume_info;
	snd_control->get = snd_asihpi_volume_get;
	snd_control->put = snd_asihpi_volume_put;
	snd_control->index = asihpi_control->wSrcNodeIndex + 1;

	if (asihpi_control->wDstNodeType)
		sprintf(snd_control->name, "%s %s%d",
			asihpi_src_names[asihpi_control->wSrcNodeType],
			asihpi_dst_names[asihpi_control->wDstNodeType],
			asihpi_control->wDstNodeIndex + 1);
	else {
		strcpy(snd_control->name,
		       asihpi_src_names[asihpi_control->wSrcNodeType]);
	}
	strcat(snd_control->name, " Volume");
}

/*------------------------------------------------------------
   Level controls
 ------------------------------------------------------------*/
static int snd_asihpi_level_info(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_info_t * uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;
	uinfo->value.integer.min = -20;
	uinfo->value.integer.max = 26;
	return 0;
}

static int snd_asihpi_level_get(snd_kcontrol_t * kcontrol,
				snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	short anGain0_01dB[HPI_MAX_CHANNELS];

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	HPI_LevelGetGain(phSubSys, hControl, anGain0_01dB);
	ucontrol->value.integer.value[0] =
	    anGain0_01dB[0] / HPI_UNITS_PER_dB;
	ucontrol->value.integer.value[1] =
	    anGain0_01dB[1] / HPI_UNITS_PER_dB;

	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return 0;
}

static int snd_asihpi_level_put(snd_kcontrol_t * kcontrol,
				snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change;
	HPI_HCONTROL hControl = kcontrol->private_value;
	short anGain0_01dB[HPI_MAX_CHANNELS];

	anGain0_01dB[0] =
	    (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
	anGain0_01dB[1] =
	    (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB;
	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	/*  change = asihpi->mixer_level[addr][0] != left ||
	   asihpi->mixer_level[addr][1] != right;
	 */
	change = 1;
	HPI_LevelSetGain(phSubSys, hControl, anGain0_01dB);
	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return change;
}

static void __init snd_asihpi_level_new(hpi_control_t * asihpi_control,
					snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_level_info;
	snd_control->get = snd_asihpi_level_get;
	snd_control->put = snd_asihpi_level_put;

	if (asihpi_control->wDstNodeType ==
	    HPI_DESTNODE_LINEOUT - HPI_DESTNODE_BASE) {
		snd_control->index = asihpi_control->wDstNodeIndex + 1;
#ifdef ASI_STYLE_NAMES
		strcpy(snd_control->name, "LineOut Level");
#else
		strcpy(snd_control->name, "Playback Volume");
#endif
	} else if (asihpi_control->wSrcNodeType ==
		   HPI_SOURCENODE_LINEIN - HPI_SOURCENODE_BASE) {
		snd_control->index = asihpi_control->wSrcNodeIndex + 1;
#ifdef ASI_STYLE_NAMES
		strcpy(snd_control->name, "LineIn Level");
#else
		strcpy(snd_control->name, "Capture Volume");
#endif
	}
}

/*------------------------------------------------------------
   Meter controls
 ------------------------------------------------------------*/
static int snd_asihpi_meter_info(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_info_t * uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;
	uinfo->value.integer.min = -192 * HPI_UNITS_PER_dB;
	uinfo->value.integer.max = 32767;
	return 0;
}

static int snd_asihpi_meter_get(snd_kcontrol_t * kcontrol,
				snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	short anGain0_01dB[HPI_MAX_CHANNELS], i;
	short nLinear = 0;
	HW16 err;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	err = HPI_MeterGetRms(phSubSys, hControl, anGain0_01dB);

	// convert 0..32767 level to 0.01dB (20log10), 0 is -100.00dB
	for (i = 0; i < HPI_MAX_CHANNELS; i++) {
		nLinear = anGain0_01dB[i];
		if ((nLinear == 0) || err)
			anGain0_01dB[i] = -192 * HPI_UNITS_PER_dB;
		//      else if(nLinear > 0) { // actually should always be > 32  32768 => 0dB, 32.768=>-60dB
		//anGain0_01dB[i] = (short)((float)(20*log10((float)nLinear/32767.0))*100.0);// units are 0.01dB
		// }  // else don't have to touch the LogValue when it is -ve since it is already a log value
	}
	ucontrol->value.integer.value[0] = anGain0_01dB[0];
	ucontrol->value.integer.value[1] = anGain0_01dB[1];

	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return 0;
}


static void __init snd_asihpi_meter_new(hpi_control_t * asihpi_control,
					snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_meter_info;
	snd_control->get = snd_asihpi_meter_get;
	snd_control->put = 0;
	snd_control->access =
	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;

	if (asihpi_control->wDstNodeType) {
		snd_control->index = asihpi_control->wDstNodeIndex + 1;
		strcpy(snd_control->name,
		       asihpi_dst_names[asihpi_control->wDstNodeType]);
	} else {
		snd_control->index = asihpi_control->wSrcNodeIndex + 1;
		strcpy(snd_control->name,
		       asihpi_src_names[asihpi_control->wSrcNodeType]);
	}
	strcat(snd_control->name, " Meter");
}

/*------------------------------------------------------------
   Multiplexer controls
 ------------------------------------------------------------*/
static int snd_card_asihpi_mux_count_sources(snd_kcontrol_t * snd_control)
{
	HPI_HCONTROL hControl = snd_control->private_value;
	hpi_control_t asihpi_control;
	int s, err;
	for (s = 0; s < 32; s++) {
		err = HPI_Multiplexer_QuerySource(phSubSys, hControl, s,
						  &asihpi_control.
						  wSrcNodeType,
						  &asihpi_control.
						  wSrcNodeIndex);
		if (err)
			break;
	}
	return (s);
}

static int snd_asihpi_mux_info(snd_kcontrol_t * kcontrol,
			       snd_ctl_elem_info_t * uinfo)
{
	int err;
	HW16 wSrcNodeType, wSrcNodeIndex;
	HPI_HCONTROL hControl = kcontrol->private_value;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items =
	    snd_card_asihpi_mux_count_sources(kcontrol);

	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
		uinfo->value.enumerated.item =
		    uinfo->value.enumerated.items - 1;

	err =
	    HPI_Multiplexer_QuerySource(phSubSys, hControl,
					uinfo->value.enumerated.item,
					&wSrcNodeType, &wSrcNodeIndex);

	sprintf(uinfo->value.enumerated.name, "%s %d",
		asihpi_src_names[wSrcNodeType - HPI_SOURCENODE_BASE],
		wSrcNodeIndex + 1);
	return 0;
}

static int snd_asihpi_mux_get(snd_kcontrol_t * kcontrol,
			      snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	HW16 wSourceType, wSourceIndex;
	HW16 wSrcNodeType, wSrcNodeIndex;
	int s;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);

	HPI_Multiplexer_GetSource(phSubSys, hControl, &wSourceType,
				  &wSourceIndex);
	// search to find the index with this source and index.  Should cache it!
	for (s = 0; s < 16; s++) {
		HPI_Multiplexer_QuerySource(phSubSys, hControl, s,
					    &wSrcNodeType, &wSrcNodeIndex);
		if ((wSourceType == wSrcNodeType)
		    && (wSourceIndex == wSrcNodeIndex)) {
			ucontrol->value.enumerated.item[0] = s;
			spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
			return 0;
		}
	}
	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return -EINVAL;
}

static int snd_asihpi_mux_put(snd_kcontrol_t * kcontrol,
			      snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change;
	HPI_HCONTROL hControl = kcontrol->private_value;
	HW16 wSourceType, wSourceIndex;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	change = 1;

	HPI_Multiplexer_QuerySource(phSubSys, hControl,
				    ucontrol->value.enumerated.item[0],
				    &wSourceType, &wSourceIndex);
	HPI_Multiplexer_SetSource(phSubSys, hControl, wSourceType,
				  wSourceIndex);
	return change;
}


static void __init snd_asihpi_mux_new(hpi_control_t * asihpi_control,
				      snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_mux_info;
	snd_control->get = snd_asihpi_mux_get;
	snd_control->put = snd_asihpi_mux_put;
	if (asihpi_control->wSrcNodeType) {
		strcpy(snd_control->name,
		       asihpi_src_names[asihpi_control->wSrcNodeType]);
		snd_control->index = asihpi_control->wSrcNodeIndex + 1;
	} else {
		strcpy(snd_control->name,
		       asihpi_dst_names[asihpi_control->wDstNodeType]);
		snd_control->index = asihpi_control->wDstNodeIndex + 1;
	}
#ifdef ASI_STYLE_NAMES
	strcat(snd_control->name, " Multiplexer");
#else
	strcat(snd_control->name, " Route");
#endif
}

/*------------------------------------------------------------
   Channel mode controls
 ------------------------------------------------------------*/
static int snd_asihpi_cmode_info(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_info_t * uinfo)
{
	static char *texts[4] = { "Normal", "Swap", "ToLeft", "ToRight" };

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = 4;

	if (uinfo->value.enumerated.item > 3)
		uinfo->value.enumerated.item = 3;

	strcpy(uinfo->value.enumerated.name,
	       texts[uinfo->value.enumerated.item]);
	return 0;
}

static int snd_asihpi_cmode_get(snd_kcontrol_t * kcontrol,
				snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	HW16 wMode;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);

	if (HPI_ChannelModeGet(phSubSys, hControl, &wMode))
		wMode = 1;

	ucontrol->value.enumerated.item[0] = wMode - 1;;

	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return 0;
}

static int snd_asihpi_cmode_put(snd_kcontrol_t * kcontrol,
				snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change;
	HPI_HCONTROL hControl = kcontrol->private_value;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	change = 1;

	HPI_ChannelModeSet(phSubSys, hControl,
			   ucontrol->value.enumerated.item[0] + 1);
	return change;
}


static void __init snd_asihpi_cmode_new(hpi_control_t * asihpi_control,
					snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_cmode_info;
	snd_control->get = snd_asihpi_cmode_get;
	snd_control->put = snd_asihpi_cmode_put;
	if (asihpi_control->wSrcNodeType) {
		strcpy(snd_control->name,
		       asihpi_src_names[asihpi_control->wSrcNodeType]);
		snd_control->index = asihpi_control->wSrcNodeIndex + 1;
	} else {
		strcpy(snd_control->name,
		       asihpi_dst_names[asihpi_control->wDstNodeType]);
		snd_control->index = asihpi_control->wDstNodeIndex + 1;
	}
	strcat(snd_control->name, " Channel Mode");
}

/*------------------------------------------------------------
   Sampleclock source  controls
 ------------------------------------------------------------*/
static char *sampleclock_sources[] =
    { "bad", "Adapter", "AES/EBU", "Word External", "Word Header",
"SMPTE" };

static int snd_asihpi_clksrc_info(snd_kcontrol_t * kcontrol,
				  snd_ctl_elem_info_t * uinfo)
{

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = 5;

	if (uinfo->value.enumerated.item > 4)
		uinfo->value.enumerated.item = 4;

	strcpy(uinfo->value.enumerated.name,
	       sampleclock_sources[uinfo->value.enumerated.item + 1]);
	return 0;
}

static int snd_asihpi_clksrc_get(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	HW16 wSource;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);

	if (HPI_SampleClock_GetSource(phSubSys, hControl, &wSource))
		wSource = 1;

	ucontrol->value.enumerated.item[0] = wSource - 1;;

	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return 0;
}

static int snd_asihpi_clksrc_put(snd_kcontrol_t * kcontrol,
				 snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change;
	HPI_HCONTROL hControl = kcontrol->private_value;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	change = 1;

	HPI_SampleClock_SetSource(phSubSys, hControl,
				  ucontrol->value.enumerated.item[0] + 1);
	return change;
}

static void __init snd_asihpi_clksrc_new(hpi_control_t * asihpi_control,
					 snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_clksrc_info;
	snd_control->get = snd_asihpi_clksrc_get;
	snd_control->put = snd_asihpi_clksrc_put;
	if (asihpi_control->wSrcNodeType) {
		strcpy(snd_control->name,
		       asihpi_src_names[asihpi_control->wSrcNodeType]);
		snd_control->index = asihpi_control->wSrcNodeIndex + 1;
	} else {
		strcpy(snd_control->name,
		       asihpi_dst_names[asihpi_control->wDstNodeType]);
		snd_control->index = asihpi_control->wDstNodeIndex + 1;
	}
	strcat(snd_control->name, " Source");
}

/*------------------------------------------------------------
   Clkrate controls
 ------------------------------------------------------------*/
static int snd_asihpi_clkrate_info(snd_kcontrol_t * kcontrol,
				   snd_ctl_elem_info_t * uinfo)
{
	//HPI_HCONTROL hControl= kcontrol->private_value;
	//HW16 err;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 8000;
	uinfo->value.integer.max = 96000;
	uinfo->value.integer.step = 100;

	return 0;
}

static int snd_asihpi_clkrate_get(snd_kcontrol_t * kcontrol,
				  snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	HPI_HCONTROL hControl = kcontrol->private_value;
	HW32 dwRate;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	HPI_SampleClock_GetSampleRate(phSubSys, hControl, &dwRate);
	ucontrol->value.integer.value[0] = dwRate;
	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return 0;
}

static int snd_asihpi_clkrate_put(snd_kcontrol_t * kcontrol,
				  snd_ctl_elem_value_t * ucontrol)
{
	snd_card_asihpi_t *asihpi = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change;
	HPI_HCONTROL hControl = kcontrol->private_value;

	spin_lock_irqsave(&asihpi->mixer_lock, flags);
	/*  change = asihpi->mixer_clkrate[addr][0] != left ||
	   asihpi->mixer_clkrate[addr][1] != right;
	 */
	change = 1;
	HPI_SampleClock_SetSampleRate(phSubSys, hControl,
				      ucontrol->value.integer.value[0]);
	spin_unlock_irqrestore(&asihpi->mixer_lock, flags);
	return change;
}

static void __init snd_asihpi_clkrate_new(hpi_control_t * asihpi_control,
					  snd_kcontrol_new_t * snd_control)
{
	snd_control->info = snd_asihpi_clkrate_info;
	snd_control->get = snd_asihpi_clkrate_get;
	snd_control->put = snd_asihpi_clkrate_put;
	snd_control->index = asihpi_control->wSrcNodeIndex + 1;

	if (asihpi_control->wDstNodeType)
		sprintf(snd_control->name, "%s %s%d",
			asihpi_src_names[asihpi_control->wSrcNodeType],
			asihpi_dst_names[asihpi_control->wDstNodeType],
			asihpi_control->wDstNodeIndex + 1);
	else {
		strcpy(snd_control->name,
		       asihpi_src_names[asihpi_control->wSrcNodeType]);
	}
	strcat(snd_control->name, " Rate");
}

/*------------------------------------------------------------
   Mixer
 ------------------------------------------------------------*/
static int __init snd_card_asihpi_new_mixer(snd_card_asihpi_t * asihpi)
{
	snd_card_t *card = asihpi->card;
	unsigned int idx = 0;
	int err;
	HPI_HCONTROL hControl;
	snd_kcontrol_new_t snd_control;
	char snd_control_name[44];	// asound.h line 745 unsigned char name[44];
	hpi_control_t asihpi_control;

	snd_assert(asihpi != NULL, return -EINVAL);
	spin_lock_init(&asihpi->mixer_lock);
	strcpy(card->mixername, "Asihpi Mixer");

	err =
	    HPI_MixerOpen(phSubSys, asihpi->wAdapterIndex,
			  &asihpi->hMixer);
	HPI_HandleError(err);
	if (err)
		return -err;

	memset(&snd_control, 0, sizeof(snd_control));
	snd_control.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
	snd_control.name = snd_control_name;

	/* now iterate through mixer controls */
	for (idx = 0; idx < 512; idx++) {
		err = HPI_MixerGetControlByIndex(phSubSys, asihpi->hMixer,
						 idx,
						 &asihpi_control.wSrcNodeType,
						 &asihpi_control.wSrcNodeIndex,
						 &asihpi_control.wDstNodeType,
						 &asihpi_control.wDstNodeIndex,
						 &asihpi_control.wControlType, 
						 &hControl);
		if (err) {
			if (err==HPI_ERROR_CONTROL_DISABLED) {
				if (mixer_dump)
					snd_printk(KERN_INFO
						   "Disabled HPI Control(%d)\n",
						   idx);
				continue;
			} else {
				break;
			}
		}
	
		asihpi_control.wSrcNodeType -= HPI_SOURCENODE_BASE;
		asihpi_control.wDstNodeType -= HPI_DESTNODE_BASE;
		snd_control.index = 0;
		snd_control.private_value = hControl;
		snd_control.access =
		    SNDRV_CTL_ELEM_ACCESS_WRITE |
		    SNDRV_CTL_ELEM_ACCESS_READ;

		switch (asihpi_control.wControlType) {
		case HPI_CONTROL_VOLUME:
			snd_asihpi_volume_new(&asihpi_control,
					      &snd_control);
			break;
		case HPI_CONTROL_LEVEL:
			snd_asihpi_level_new(&asihpi_control,
					     &snd_control);
			break;
		case HPI_CONTROL_MULTIPLEXER:
			snd_asihpi_mux_new(&asihpi_control, &snd_control);
			break;
		case HPI_CONTROL_CHANNEL_MODE:
			snd_asihpi_cmode_new(&asihpi_control,
					     &snd_control);
			break;
		case HPI_CONTROL_METER:
			snd_asihpi_meter_new(&asihpi_control,
					     &snd_control);
			break;
		case HPI_CONTROL_SAMPLECLOCK:
			snd_asihpi_clksrc_new(&asihpi_control,
					      &snd_control);
			if ((err =
			     snd_ctl_add(card,
					 snd_ctl_new1(&snd_control,
						      asihpi))) < 0)
				return err;
			snd_asihpi_clkrate_new(&asihpi_control,
					       &snd_control);
			break;
		case HPI_CONTROL_CONNECTION:	// ignore these
			continue;
		case HPI_CONTROL_MUTE:
		case HPI_CONTROL_AESEBU_TRANSMITTER:
		case HPI_CONTROL_AESEBU_RECEIVER:
		case HPI_CONTROL_TUNER:
		case HPI_CONTROL_ONOFFSWITCH:
		case HPI_CONTROL_VOX:
		case HPI_CONTROL_BITSTREAM:
		case HPI_CONTROL_MICROPHONE:
		case HPI_CONTROL_PARAMETRIC_EQ:
		case HPI_CONTROL_COMPANDER:
		default:
			if (mixer_dump)
				snd_printk(KERN_INFO
					   "Untranslated HPI Control(%d) %d %d %d %d %d\n",
					   idx,
					   asihpi_control.wControlType,
					   asihpi_control.wSrcNodeType,
					   asihpi_control.wSrcNodeIndex,
					   asihpi_control.wDstNodeType,
					   asihpi_control.wDstNodeIndex);
			continue;
		};

		if ((err =
		     snd_ctl_add(card,
				 snd_ctl_new1(&snd_control,
					      asihpi))) < 0) {
			return err;
		} else if (mixer_dump)
			snd_printk(KERN_INFO "Added %s(%d)\n",
				   snd_control.name, snd_control.index);
	}
	if (HPI_ERROR_INVALID_OBJ_INDEX != err)	// this is normal
		HPI_HandleError(err);

	snd_printk(KERN_INFO "%d mixer controls found\n", idx);

	return 0;
}

/*------------------------------------------------------------
   /proc interface 
 ------------------------------------------------------------*/

static void
snd_asihpi_proc_read(snd_info_entry_t * entry, snd_info_buffer_t * buffer)
{
	snd_card_asihpi_t *asihpi = entry->private_data;
	HW16 wVersion;
	HPI_HCONTROL hControl;
	HW32 dwRate;
	HW16 wSource;
	int err;

	snd_iprintf(buffer, "ASIHPI driver proc file\n");
	snd_iprintf(buffer,
		    "Adapter ID=%4X\nIndex=%d\nNumOutStreams=%d\nNumInStreams=%d\n",
		    asihpi->wType, asihpi->wAdapterIndex,
		    asihpi->wNumOutStreams, asihpi->wNumInStreams);

	wVersion = asihpi->wVersion;
	snd_iprintf(buffer,
		    "S/N=%ld\nHw Version %c%d\nDSP code version %03d\n",
		    asihpi->dwSerialNumber, ((wVersion >> 3) & 0xf) + 'A',
		    wVersion & 0x7,
		    ((wVersion >> 13) * 100) + ((wVersion >> 7) & 0x3f));

	err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
				  HPI_CONTROL_SAMPLECLOCK, &hControl);

	err = HPI_SampleClock_GetSampleRate(phSubSys, hControl, &dwRate);
	err = HPI_SampleClock_GetSource(phSubSys, hControl, &wSource);
	snd_iprintf(buffer, "SampleClock=%ldHz, source %s\n", dwRate,
		    sampleclock_sources[wSource]);
}


static void __devinit snd_asihpi_proc_init(snd_card_asihpi_t * asihpi)
{
	snd_info_entry_t *entry;

	if (!snd_card_proc_new(asihpi->card, "info", &entry))
		snd_info_set_text_ops(entry, asihpi, 1024, snd_asihpi_proc_read);
}

/*------------------------------------------------------------
   CARD
 ------------------------------------------------------------*/
static int __init snd_card_asihpi_probe(int dev)
{
	HW16 wVersion;
	int pcm_substreams;

	snd_card_t *card;
	struct snd_card_asihpi *asihpi;
	int idx, err;

	HPI_HCONTROL hControl;

	//  if (!enable[dev])
	//return -ENODEV;
	err = HPI_AdapterOpen(phSubSys, dev);

	HPI_HandleError(err);
	if (err)
		return -ENODEV;

	// first try to give the card the same index as its hardware index
	card = snd_card_new(dev, SNDRV_DEFAULT_STR1, THIS_MODULE,
			    sizeof(struct snd_card_asihpi));
	if (card == NULL) {
		// if that fails, try the default index==next available
		card =
		    snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
				 THIS_MODULE,
				 sizeof(struct snd_card_asihpi));
		if (card == NULL)
			return -ENOMEM;
		snd_printk(KERN_WARNING
			   "**** WARNING **** Adapter index %d->ALSA index %d\n",
			   dev, card->number);
	}

	asihpi = (struct snd_card_asihpi *) card->private_data;
	asihpi->card = card;
	asihpi->wAdapterIndex = dev;
	err = HPI_AdapterGetInfo(phSubSys,
				 asihpi->wAdapterIndex,
				 &asihpi->wNumOutStreams,
				 &asihpi->wNumInStreams,
				 &asihpi->wVersion,
				 &asihpi->dwSerialNumber, &asihpi->wType);
	HPI_HandleError(err);
	wVersion = asihpi->wVersion;
	snd_printk(KERN_INFO "Adapter ID=%4X Index=%d NumOutStreams=%d NumInStreams=%d S/N=%ld\n" "Hw Version %c%d DSP code version %03d\n", asihpi->wType, asihpi->wAdapterIndex, asihpi->wNumOutStreams, asihpi->wNumInStreams, asihpi->dwSerialNumber, ((wVersion >> 3) & 0xf) + 'A',	// Hw version major
		   wVersion & 0x7,	// Hw version minor
		   ((wVersion >> 13) * 100) + ((wVersion >> 7) & 0x3f)	// DSP code version
	    );

	idx = 0;
	pcm_substreams = asihpi->wNumOutStreams;
	if (pcm_substreams < asihpi->wNumInStreams)
		pcm_substreams = asihpi->wNumInStreams;
	if ((err = snd_card_asihpi_pcm(asihpi, idx, pcm_substreams)) < 0)
		goto __nodev;
	if ((err = snd_card_asihpi_new_mixer(asihpi)) < 0)
		goto __nodev;

	err = HPI_MixerGetControl(phSubSys, asihpi->hMixer,
				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
				  HPI_CONTROL_SAMPLECLOCK, &hControl);

	err =
	    HPI_SampleClock_SetSampleRate(phSubSys, hControl, adapter_fs);

	snd_asihpi_proc_init(asihpi);

	strcpy(card->driver, "ASIHPI");
	sprintf(card->shortname, "AudioScience ASI%4X", asihpi->wType);
	sprintf(card->longname, "%s %i", card->shortname, dev + 1);
	if ((err = snd_card_register(card)) == 0) {
		snd_asihpi_cards[dev] = card;
		return 0;
	}
      __nodev:
	snd_card_free(card);
	return err;
}


static int __init alsa_card_asihpi_init(void)
{
	int dev, cards;

	HW16 err = 0;		// HPI error
	HW16 wNumAdapters = 0;
	HW16 awAdapterList[HPI_MAX_ADAPTERS];
	HW16 wListLength = HPI_MAX_ADAPTERS;
	HW32 dwVersion;

	snd_printd(KERN_INFO "ASIHPI driver start\n");

	phSubSys = HPI_SubSysCreate();
	if (phSubSys == NULL) {
		snd_printd(KERN_ERR
			   "phSubSys==NULL, ASI HPI driver not loaded?\n");
		return 0;
	}

	err = HPI_SubSysGetVersion(phSubSys, &dwVersion);
	HPI_HandleError(err);
	snd_printd(KERN_INFO "HPI_SubSysGetVersion=%lx\n", dwVersion);

	err = HPI_SubSysFindAdapters(phSubSys,
				     &wNumAdapters,
				     awAdapterList, wListLength);
	HPI_HandleError(err);
	snd_printd(KERN_INFO "HPI_SubSysFindAdapters NumberAdapters=%d \n",
		   wNumAdapters);

	for (dev = cards = 0; dev < HPI_MAX_ADAPTERS; dev++) {
		if (awAdapterList[dev] && snd_card_asihpi_probe(dev) < 0) {
#ifdef MODULE
			snd_printk(KERN_ERR
				   "Asihpi soundcard #%i not found or device busy\n",
				   dev + 1);
#endif
			break;
		}
		cards++;
	}
	if (!cards) {
#ifdef MODULE
		snd_printk(KERN_ERR
			   "Asihpi soundcard not found or device busy\n");
#endif
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_asihpi_exit(void)
{
	int idx;

	for (idx = 0; idx < SNDRV_CARDS; idx++)
		snd_card_free(snd_asihpi_cards[idx]);
}

module_init(alsa_card_asihpi_init)
module_exit(alsa_card_asihpi_exit)
