/*
 *  Asihpi soundcard

 *  Copyright (C) 1997-2017  AudioScience Inc. <support@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.
 */

#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpioctl.h"
#include "hpicmn.h"

#ifndef HPI_BUILD_SANITISE
#ifndef KERNEL_ALSA_BUILD
/* building in ALSA source tree */
#include "adriver.h"
#else
/* building in kernel tree */
#endif

#ifndef snd_dma_pci_data
#define snd_dma_pci_data(pci) (&(pci)->dev)
#endif

#define hpi_handle_object  HPI_HandleObject
#define hpi_init_message_response HPI_InitMessageResponse
#define hpi_handle_to_indexes HPI_HandleToIndexes
#define hpi_format_create HPI_FormatCreate
#define hpi_send_recv HPI_Message

#define hpi_adapter_get_info(...) HPI_AdapterGetInfo(NULL, __VA_ARGS__)
#define hpi_adapter_get_property(...) HPI_AdapterGetProperty(NULL, __VA_ARGS__)
#define hpi_adapter_set_property(...) HPI_AdapterSetProperty(NULL, __VA_ARGS__)

#define hpi_instream_open(...) HPI_InStreamOpen(NULL, __VA_ARGS__)
#define hpi_instream_start(...) HPI_InStreamStart(NULL, __VA_ARGS__)
#define hpi_instream_stop(...) HPI_InStreamStop(NULL, __VA_ARGS__)
#define hpi_instream_reset(...) HPI_InStreamReset(NULL, __VA_ARGS__)
#define hpi_instream_close(...) HPI_InStreamClose(NULL, __VA_ARGS__)
#define hpi_instream_get_info_ex(...)  HPI_InStreamGetInfoEx(NULL, __VA_ARGS__)
#define hpi_instream_group_add(...) HPI_InStreamGroupAdd(NULL, __VA_ARGS__)
#define hpi_instream_group_reset(...) HPI_InStreamGroupReset(NULL, __VA_ARGS__)
#define hpi_instream_group_get_map(...) HPI_InStreamGroupGetMap(NULL, __VA_ARGS__)
#define hpi_instream_query_format(...) HPI_InStreamQueryFormat(NULL, __VA_ARGS__)
#define hpi_instream_set_format(...) HPI_InStreamSetFormat(NULL, __VA_ARGS__)
#define hpi_instream_host_buffer_free(...) HPI_InStreamHostBufferFree(NULL, __VA_ARGS__)
#define hpi_instream_read_buf(...) HPI_InStreamReadBuf(NULL, __VA_ARGS__)

#define hpi_outstream_open(...) HPI_OutStreamOpen(NULL, __VA_ARGS__)
#define hpi_outstream_start(...) HPI_OutStreamStart(NULL, __VA_ARGS__)
#define hpi_outstream_stop(...) HPI_OutStreamStop(NULL, __VA_ARGS__)
#define hpi_outstream_reset(...) HPI_OutStreamReset(NULL, __VA_ARGS__)
#define hpi_outstream_close(...) HPI_OutStreamClose(NULL, __VA_ARGS__)
#define hpi_outstream_get_info_ex(...)  HPI_OutStreamGetInfoEx(NULL, __VA_ARGS__)
#define hpi_outstream_group_add(...) HPI_OutStreamGroupAdd(NULL, __VA_ARGS__)
#define hpi_outstream_group_reset(...) HPI_OutStreamGroupReset(NULL, __VA_ARGS__)
#define hpi_outstream_group_get_map(...) HPI_OutStreamGroupGetMap(NULL, __VA_ARGS__)
#define hpi_outstream_query_format(...) HPI_OutStreamQueryFormat(NULL, __VA_ARGS__)
#define hpi_outstream_set_format(...) HPI_OutStreamSetFormat(NULL, __VA_ARGS__)
#define hpi_outstream_write_buf(...) HPI_OutStreamWriteBuf(NULL, __VA_ARGS__)

#define hpi_mixer_open(...) HPI_MixerOpen(NULL, __VA_ARGS__)
#define hpi_mixer_get_control(...) HPI_MixerGetControl(NULL, __VA_ARGS__)
#define hpi_mixer_get_control_by_index(...) HPI_MixerGetControlByIndex(NULL, __VA_ARGS__)

#define hpi_sample_clock_get_sample_rate(...) HPI_SampleClock_GetSampleRate(NULL, __VA_ARGS__)
#define hpi_sample_clock_query_local_rate(...) HPI_SampleClock_QueryLocalRate(NULL, __VA_ARGS__)
#define hpi_sample_clock_get_local_rate(...) HPI_SampleClock_GetLocalRate(NULL, __VA_ARGS__)
#define hpi_sample_clock_set_local_rate(...) HPI_SampleClock_SetLocalRate(NULL, __VA_ARGS__)
#define hpi_sample_clock_query_source(...) HPI_SampleClock_QuerySource(NULL, __VA_ARGS__)
#define hpi_sample_clock_get_source(...) HPI_SampleClock_GetSource(NULL, __VA_ARGS__)
#define hpi_sample_clock_set_source(...) HPI_SampleClock_SetSource(NULL, __VA_ARGS__)
#define hpi_sample_clock_query_source_index(...) HPI_SampleClock_QuerySourceIndex(NULL, __VA_ARGS__)
#define hpi_sample_clock_get_source_index(...) HPI_SampleClock_GetSourceIndex(NULL, __VA_ARGS__)
#define hpi_sample_clock_set_source_index(...) HPI_SampleClock_SetSourceIndex(NULL, __VA_ARGS__)

#define hpi_volume_query_range(...) HPI_VolumeQueryRange(NULL, __VA_ARGS__)
#define hpi_volume_query_channels(...) HPI_Volume_QueryChannels(NULL, __VA_ARGS__)
#define hpi_volume_get_gain(...) HPI_VolumeGetGain(NULL, __VA_ARGS__)
#define hpi_volume_set_gain(...) HPI_VolumeSetGain(NULL, __VA_ARGS__)
#define hpi_volume_get_mute(...) HPI_VolumeGetMute(NULL, __VA_ARGS__)
#define hpi_volume_set_mute(...) HPI_VolumeSetMute(NULL, __VA_ARGS__)

#define hpi_level_query_range(...) HPI_LevelQueryRange(NULL, __VA_ARGS__)
#define hpi_level_get_gain(...) HPI_LevelGetGain(NULL, __VA_ARGS__)
#define hpi_level_set_gain(...) HPI_LevelSetGain(NULL, __VA_ARGS__)

#define hpi_tuner_query_gain(...) HPI_Tuner_QueryGain(NULL, __VA_ARGS__)
#define hpi_tuner_get_gain(...) HPI_Tuner_GetGain(NULL, __VA_ARGS__)
#define hpi_tuner_set_gain(...) HPI_Tuner_SetGain(NULL, __VA_ARGS__)
#define hpi_tuner_query_band(...) HPI_Tuner_QueryBand(NULL, __VA_ARGS__)
#define hpi_tuner_get_band(...) HPI_Tuner_GetBand(NULL, __VA_ARGS__)
#define hpi_tuner_set_band(...) HPI_Tuner_SetBand(NULL, __VA_ARGS__)
#define hpi_tuner_query_frequency(...) HPI_Tuner_QueryFrequency(NULL, __VA_ARGS__)
#define hpi_tuner_get_frequency(...) HPI_Tuner_GetFrequency(NULL, __VA_ARGS__)
#define hpi_tuner_set_frequency(...) HPI_Tuner_SetFrequency(NULL, __VA_ARGS__)

#define hpi_meter_get_peak(...) HPI_MeterGetPeak(NULL, __VA_ARGS__)
#define hpi_meter_query_channels(...) HPI_Meter_QueryChannels(NULL, __VA_ARGS__)
#define hpi_multiplexer_query_source(...) HPI_Multiplexer_QuerySource(NULL, __VA_ARGS__)
#define hpi_multiplexer_get_source(...) HPI_Multiplexer_GetSource(NULL, __VA_ARGS__)
#define hpi_multiplexer_set_source(...) HPI_Multiplexer_SetSource(NULL, __VA_ARGS__)

#define hpi_channel_mode_query_mode(...) HPI_ChannelMode_QueryMode(NULL, __VA_ARGS__)
#define hpi_channel_mode_get(...) HPI_ChannelModeGet(NULL, __VA_ARGS__)
#define hpi_channel_mode_set(...) HPI_ChannelModeSet(NULL, __VA_ARGS__)

#define hpi_aesebu_receiver_get_error_status(...) HPI_AESEBU_Receiver_GetErrorStatus(NULL, __VA_ARGS__)

static u16 hpi_aesebu_receiver_get_format(u32 h, u16 *f)
{
	return HPI_AESEBU_Receiver_GetFormat(NULL, h, f);
}

static u16 hpi_aesebu_receiver_set_format(u32 h, u16 f)
{
	return HPI_AESEBU_Receiver_SetFormat(NULL, h, f);
}

static u16 hpi_aesebu_transmitter_get_format(u32 h, u16 *f)
{
	return HPI_AESEBU_Transmitter_GetFormat(NULL, h, f);
}

static u16 hpi_aesebu_transmitter_set_format(u32 h, u16 f)
{
	return HPI_AESEBU_Transmitter_SetFormat(NULL, h, f);
}

#endif

#include <linux/pci.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/hrtimer.h>
#include <linux/wait.h>
#include <linux/module.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>
#include <sound/tlv.h>
#include <sound/hwdep.h>
#include <sound/pcm-indirect.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("AudioScience inc. <support@audioscience.com>");
MODULE_DESCRIPTION("AudioScience ALSA ASI5xxx ASI6xxx ASI87xx ASI89xx "
			HPI_VER_STRING);

static int dev;
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static bool enable_hpi_hwdep = true;
static bool split_adapter = false;

module_param_array(index, int, NULL, S_IRUGO);
MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard.");

module_param_array(id, charp, NULL, S_IRUGO);
MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard.");

module_param_array(enable, bool, NULL, S_IRUGO);
MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard.");

module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(enable_hpi_hwdep,
		"ALSA enable HPI hwdep for AudioScience soundcard ");

module_param(split_adapter, bool, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(split_adapter,
		"ALSA split adapter's streams and mixer into separate devices for AudioScience soundcard.");

/* identify driver */
#ifdef KERNEL_ALSA_BUILD
static char *build_info = "Built using headers from kernel source";
module_param(build_info, charp, S_IRUGO);
MODULE_PARM_DESC(build_info, "Built using headers from kernel source");
#else
static char *build_info = "Built within ALSA source";
module_param(build_info, charp, S_IRUGO);
MODULE_PARM_DESC(build_info, "Built within ALSA source");
#endif

/* set to 1 to dump every control from adapter to log */
static const int mixer_dump;

#define DEFAULT_SAMPLERATE 44100
static int adapter_fs = DEFAULT_SAMPLERATE;

/* defaults */
#define PERIODS_MIN 2
#define PERIOD_BYTES_MIN (8 * 1024)
#define BUFFER_BYTES_MAX (512 * 1024)

static int min_periods = PERIODS_MIN;
static int min_period_bytes = PERIOD_BYTES_MIN;

module_param(min_periods, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(min_periods, "ALSA min_periods setting");

module_param(min_period_bytes, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(min_period_bytes, "ALSA min_period_bytes setting");

#define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7)

struct clk_source {
	int source;
	int index;
	const char *name;
};

struct clk_cache {
	int count;
	int has_local;
	struct clk_source s[MAX_CLOCKSOURCES];
};

/* Per card data */
struct snd_card_asihpi {
	struct snd_card *card;
	struct pci_dev *pci;
	struct hpi_adapter_obj *hpi;
	int split_device_idx;
	uint8_t instreams;
	uint8_t outstreams;

	void (*pcm_start)(struct snd_pcm_substream *substream);
	void (*pcm_stop)(struct snd_pcm_substream *substream);

	u32 h_mixer;
	struct clk_cache cc;

	unsigned can_dma:1;
	unsigned support_grouping:1;
	unsigned support_mrx:1;
	unsigned use_interrupts:1;

	u16 update_interval_frames;
	u16 in_max_chans;
	u16 out_max_chans;
	u16 in_min_chans;
	u16 out_min_chans;

};

/* Per stream data */
struct snd_card_asihpi_pcm {
	struct hrtimer hrt;
	unsigned int poll_time_ns;
	atomic_t respawn_timer;
	unsigned int buffer_bytes;
	unsigned int period_bytes;
	unsigned int bytes_per_sec;
	struct snd_pcm_indirect indirect_pcm_state;
	struct snd_pcm_substream *substream;
	snd_pcm_uframes_t prev_appl_ptr;
	u32 h_stream;
	struct hpi_format format;
	struct hpi_hostbuffer_status *buf_status;
};

/* HPI stream info packaged for trace */
struct hpi_stream_info {
	u16 state;
	u32 buffer_size;
	u32 bytes_avail;
	u32 on_card_bytes;
	u32 samples_transferred;
};

static snd_pcm_uframes_t snd_pcm_asihpi_playback_avail(struct snd_pcm_runtime *runtime)
{
	snd_pcm_sframes_t avail = READ_ONCE(runtime->status->hw_ptr) + runtime->buffer_size - READ_ONCE(runtime->control->appl_ptr);
	if (avail < 0)
		avail += runtime->boundary;
	else if ((snd_pcm_uframes_t) avail >= runtime->boundary)
		avail -= runtime->boundary;
	return avail;
}

#define CREATE_TRACE_POINTS
#include "trace_asihpi.h"

/* universal stream verbs work with out or in stream handles */

/* Functions to allow driver to give a buffer to HPI for busmastering */

#ifndef HPI_BUILD_SANITISE
#define adapter_index wAdapterIndex
#define obj_index wObjIndex
#define buffer_size dwBufferSize
#define pci_address dwPciAddress
#define command dwCommand
#define error wError
#endif
static u16 hpi_stream_host_buffer_allocate(
	u32 h_stream,   /* handle to stream. */
	size_t size_req_in_bytes, /* size in bytes of bus mastering buffer */
	size_t *size_allocated_in_bytes,  /* size in bytes actually allocated */
	size_t *previous_allocated_size_in_bytes,  /* maximum buffer size in bytes */
	dma_addr_t *buffer_phy_address   /* buffer physical address */
)
{
	struct hpi_message hm;
	struct hpi_response hr;
	unsigned int obj = hpi_handle_object(h_stream);

	if (!h_stream)
		return HPI_ERROR_INVALID_OBJ;
	hpi_init_message_response(&hm, &hr, obj,
			obj == HPI_OBJ_OSTREAM ?
				HPI_OSTREAM_HOSTBUFFER_ALLOC :
				HPI_ISTREAM_HOSTBUFFER_ALLOC);

	hpi_handle_to_indexes(h_stream, &hm.adapter_index,
				&hm.obj_index);

	hm.u.d.u.buffer.buffer_size = size_req_in_bytes;
	hpi_send_recv(&hm, &hr);

	if (size_allocated_in_bytes) {
		*size_allocated_in_bytes = hr.u.d.u.stream_info.dwBufferSize;
	}

	if (previous_allocated_size_in_bytes) {
		*previous_allocated_size_in_bytes = hr.u.d.u.stream_info.dwDataAvailable;
	}

	if (buffer_phy_address) {
		*buffer_phy_address = hr.u.d.u.stream_info.dwAuxiliaryDataAvailable;
	}

	return hr.error;
}

static u16 hpi_stream_host_buffer_free(
	u32 h_stream   /* handle to stream. */
)
{
	struct hpi_message hm;
	struct hpi_response hr;
	unsigned int obj = hpi_handle_object(h_stream);

	if (!h_stream)
		return HPI_ERROR_INVALID_OBJ;

	hpi_init_message_response(&hm, &hr, obj,
			obj == HPI_OBJ_OSTREAM ?
				HPI_OSTREAM_HOSTBUFFER_FREE :
				HPI_ISTREAM_HOSTBUFFER_FREE);

	hpi_handle_to_indexes(h_stream, &hm.adapter_index, &hm.obj_index);

	hpi_send_recv(&hm, &hr);
	return hr.wError;
}

static u16 hpi_stream_host_buffer_get_info(
	u32 h_stream,   /* handle to stream. */
	uint8_t **buffer_virt_address, /* buffer virtual address */
	struct hpi_hostbuffer_status **hostbuffer_status
)
{
	struct hpi_message hm;
	struct hpi_response hr;
	unsigned int obj = hpi_handle_object(h_stream);

	if (!h_stream)
		return HPI_ERROR_INVALID_OBJ;
	hpi_init_message_response(&hm, &hr, obj,
			obj == HPI_OBJ_OSTREAM ?
				HPI_OSTREAM_HOSTBUFFER_GET_INFO :
				HPI_ISTREAM_HOSTBUFFER_GET_INFO);

	hpi_handle_to_indexes(h_stream, &hm.adapter_index, &hm.obj_index);

	hpi_send_recv(&hm, &hr);

	if (!hr.error) {
		if (buffer_virt_address) {
			*buffer_virt_address = hr.u.d.u.hostbuffer_info.pBuffer;
		}
		if (hostbuffer_status) {
			*hostbuffer_status = hr.u.d.u.hostbuffer_info.pStatus;
		}
	}

	return hr.error;
}
#ifndef HPI_BUILD_SANITISE
#undef adapter_index
#undef obj_index
#undef buffer_size
#undef pci_address
#undef command
#undef error
#endif

static inline u16 hpi_stream_start(u32 h_stream)
{
	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
		return hpi_outstream_start(h_stream);
	else
		return hpi_instream_start(h_stream);
}

static inline u16 hpi_stream_stop(u32 h_stream)
{
	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
		return hpi_outstream_stop(h_stream);
	else
		return hpi_instream_stop(h_stream);
}

static inline u16 hpi_stream_get_info_ex(
    u32 h_stream,
    u16        *pw_state,
    u32        *pbuffer_size,
    u32        *pdata_in_buffer,
    u32        *psample_count,
    u32        *pauxiliary_data
)
{
	u16 e;
	if (hpi_handle_object(h_stream)  ==  HPI_OBJ_OSTREAM)
		e = hpi_outstream_get_info_ex(h_stream, pw_state,
					pbuffer_size, pdata_in_buffer,
					psample_count, pauxiliary_data);
	else
		e = hpi_instream_get_info_ex(h_stream, pw_state,
					pbuffer_size, pdata_in_buffer,
					psample_count, pauxiliary_data);
	return e;
}

static inline u16 hpi_stream_group_add(
					u32 h_master,
					u32 h_stream)
{
	if (hpi_handle_object(h_master) ==  HPI_OBJ_OSTREAM)
		return hpi_outstream_group_add(h_master, h_stream);
	else
		return hpi_instream_group_add(h_master, h_stream);
}

static inline u16 hpi_stream_group_reset(u32 h_stream)
{
	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
		return hpi_outstream_group_reset(h_stream);
	else
		return hpi_instream_group_reset(h_stream);
}

static inline u16 hpi_stream_group_get_map(
				u32 h_stream, u32 *mo, u32 *mi)
{
	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
		return hpi_outstream_group_get_map(h_stream, mo, mi);
	else
		return hpi_instream_group_get_map(h_stream, mo, mi);
}

static u16 handle_error(u16 err, int line, char *filename)
{
	if (err)
		snd_printk(KERN_WARNING
			"in file %s, line %d: HPI error %d\n",
			filename, line, err);
	return err;
}

#define hpi_handle_error(x) handle_error(x, __LINE__, __FILE__)

#ifndef HPI_BUILD_SANITISE
#include <linux/version.h>

#if (HAVE_STRSCPY != 1)
ssize_t strscpy(char *d, const char *s, size_t sz)
{
	return strlcpy(d, s, sz);
}
#endif

#if (HAVE_SND_CTL_ENUM_INFO != 1)
static int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels,
                      unsigned int items, const char *const names[])
{
	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	info->count = channels;
	info->value.enumerated.items = items;
	if (!items)
		return 0;
	if (info->value.enumerated.item >= items)
		info->value.enumerated.item = items - 1;
	strscpy(info->value.enumerated.name,
		names[info->value.enumerated.item],
		sizeof(info->value.enumerated.name));
	return 0;
}
#endif


#ifndef SNDRV_CTL_ELEM_ID_NAME_MAXLEN
#define SNDRV_CTL_ELEM_ID_NAME_MAXLEN 44
#endif

#if (HAVE_PCM_FORMAT_TO_BITS != 1)
/* Strong-typed conversion of pcm_format to bitwise */
static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format)
{
	return 1ULL << (__force int) pcm_format;
}
#endif


#if (HAVE_SND_PCM_STOP_XRUN != 1)
static int snd_pcm_stop_xrun(struct snd_pcm_substream *substream)
{
	unsigned long flags;
	int ret = 0;

	snd_pcm_stream_lock_irqsave(substream, flags);
	if (snd_pcm_running(substream))
		ret = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
	snd_pcm_stream_unlock_irqrestore(substream, flags);
	return ret;
}
#endif

#if (HPI_MIN_KERNEL_VERSION >= KERNEL_VERSION(3, 18, 0))
#warning "Time to revisit non-atomic PCM locking"
#endif

#endif
/***************************** GENERAL PCM ****************/

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 */
#if 1
	/* ALSA can't handle 3 byte sample size together with power-of-2
	 *  constraint on buffer_bytes, so disable this format
	 */
	-1
#else
	/* SNDRV_PCM_FORMAT_S24_3LE */ /* HPI_FORMAT_PCM24_SIGNED 15 */
#endif
};


static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsa_format,
					   u16 *hpi_format)
{
	u16 format;

	for (format = HPI_FORMAT_PCM8_UNSIGNED;
	     format <= HPI_FORMAT_PCM24_SIGNED; format++) {
		if (hpi_to_alsa_formats[format] == alsa_format) {
			*hpi_format = format;
			return 0;
		}
	}

	snd_printd(KERN_WARNING "failed match for alsa format %d\n",
		   alsa_format);
	*hpi_format = 0;
	return -EINVAL;
}

static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi,
					 struct snd_pcm_hardware *pcmhw)
{
	u16 err;
	u32 h_control;
	u32 sample_rate;
	int idx;
	unsigned int rate_min = 200000;
	unsigned int rate_max = 0;
	unsigned int rates = 0;

	if (asihpi->support_mrx) {
		rates |= SNDRV_PCM_RATE_CONTINUOUS;
		rates |= SNDRV_PCM_RATE_8000_192000;
		rate_min = 8000;
		rate_max = 192000;
	} else {
		/* on cards without SRC,
		   valid rates are determined by sampleclock */
		err = hpi_mixer_get_control(asihpi->h_mixer,
					  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
					  HPI_CONTROL_SAMPLECLOCK, &h_control);
		if (err) {
			dev_err(&asihpi->pci->dev,
				"No local sampleclock, err %d\n", err);
		}

		for (idx = -1; idx < 100; idx++) {
			if (idx == -1) {
				if (hpi_sample_clock_get_sample_rate(h_control,
								&sample_rate))
					continue;
			} else if (hpi_sample_clock_query_local_rate(h_control,
							idx, &sample_rate)) {
				break;
			}

			rate_min = min(rate_min, sample_rate);
			rate_max = max(rate_max, sample_rate);

			switch (sample_rate) {
			case 5512:
				rates |= SNDRV_PCM_RATE_5512;
				break;
			case 8000:
				rates |= SNDRV_PCM_RATE_8000;
				break;
			case 11025:
				rates |= SNDRV_PCM_RATE_11025;
				break;
			case 16000:
				rates |= SNDRV_PCM_RATE_16000;
				break;
			case 22050:
				rates |= SNDRV_PCM_RATE_22050;
				break;
			case 32000:
				rates |= SNDRV_PCM_RATE_32000;
				break;
			case 44100:
				rates |= SNDRV_PCM_RATE_44100;
				break;
			case 48000:
				rates |= SNDRV_PCM_RATE_48000;
				break;
			case 64000:
				rates |= SNDRV_PCM_RATE_64000;
				break;
			case 88200:
				rates |= SNDRV_PCM_RATE_88200;
				break;
			case 96000:
				rates |= SNDRV_PCM_RATE_96000;
				break;
			case 176400:
				rates |= SNDRV_PCM_RATE_176400;
				break;
			case 192000:
				rates |= SNDRV_PCM_RATE_192000;
				break;
			default: /* some other rate */
				rates |= SNDRV_PCM_RATE_KNOT;
			}
		}
	}

	pcmhw->rates = rates;
	pcmhw->rate_min = rate_min;
	pcmhw->rate_max = rate_max;
}

static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream,
					 struct snd_pcm_hw_params *params)
{
	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	int err;
	u16 format;
	int width;
	unsigned int bytes_per_sec;
	size_t requested_buffer_size = params_buffer_bytes(params);
	size_t allocated_buffer_size = 0;
	dma_addr_t buffer_phy_addr;
	uint8_t *buf_virt_addr;
	struct hpi_hostbuffer_status *phb_status;

	err = snd_card_asihpi_format_alsa2hpi(params_format(params), &format);
	if (err)
		return err;

	hpi_handle_error(hpi_format_create(&dpcm->format,
			params_channels(params),
			format, params_rate(params), 0, 0));

	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		if (hpi_instream_reset(dpcm->h_stream) != 0)
			return -EINVAL;

		if (hpi_instream_set_format(
			dpcm->h_stream, &dpcm->format) != 0)
			return -EINVAL;
	}

	err = hpi_stream_host_buffer_allocate(dpcm->h_stream, requested_buffer_size, &allocated_buffer_size, NULL, &buffer_phy_addr);
	hpi_handle_error(err);
	if (err) {
		dev_err(&card->pci->dev, "hpi_stream_host_buffer_allocate(%X, %lu): %hu.\n", dpcm->h_stream, requested_buffer_size, err);
		return -ENOMEM;
	}

	if (requested_buffer_size != allocated_buffer_size) {
		dev_err(&card->pci->dev, "hpi_stream_host_buffer_allocate(%X) requested %lu, got %lu.\n", dpcm->h_stream, requested_buffer_size, allocated_buffer_size);
		return -ENOMEM;
	}

	err = hpi_stream_host_buffer_get_info(dpcm->h_stream, &buf_virt_addr, &phb_status);
	hpi_handle_error(err);
	if (err) {
		dev_err(&card->pci->dev, "hpi_stream_host_buffer_getinfo(%X, %lu): %hu.\n", dpcm->h_stream, requested_buffer_size, err);
		return -ENOMEM;
	}

	runtime->dma_area = buf_virt_addr;
	runtime->dma_addr = buffer_phy_addr;
	runtime->dma_bytes = requested_buffer_size;
	snd_printdd(
		"stream_host_buffer_attach success %lu %u %lu\n",
		requested_buffer_size,
		dpcm->buf_status->dwSizeInBytes,
		(unsigned long)runtime->dma_addr);

	bytes_per_sec = params_rate(params) * params_channels(params);
	width = snd_pcm_format_width(params_format(params));
	bytes_per_sec *= width;
	bytes_per_sec /= 8;
	if (width < 0 || bytes_per_sec == 0)
		return -EINVAL;

	dpcm->bytes_per_sec = bytes_per_sec;
	dpcm->buffer_bytes = requested_buffer_size;
	dpcm->period_bytes = params_period_bytes(params);
	dpcm->poll_time_ns = 1000000000UL / params_rate(params) *
				card->update_interval_frames;

	return 0;
}

static int
snd_card_asihpi_hw_free(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	u16 err;

	err = hpi_stream_host_buffer_free(dpcm->h_stream);
	hpi_handle_error(err);
	return 0;
}

static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime)
{
	kfree(runtime->private_data);
}

static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream *
					    substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	atomic_set(&dpcm->respawn_timer, 1);
	if (!hrtimer_is_queued(&dpcm->hrt)) {
		hrtimer_start(&dpcm->hrt, ns_to_ktime(1UL), HRTIMER_MODE_REL);
	}
}

static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

	atomic_set(&dpcm->respawn_timer, 0);
}

static int snd_card_asihpi_pcm_trigger(struct snd_pcm_substream *substream,
					   int cmd)
{
	struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;
	struct hpi_hostbuffer_status *hhs = dpcm->buf_status;
	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
	struct snd_pcm_substream *s;
	u16 e;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		snd_pcm_group_for_each_entry(s, substream) {
			struct snd_pcm_runtime *runtime = s->runtime;
			struct snd_card_asihpi_pcm *ds = runtime->private_data;

			if (snd_pcm_substream_chip(s) != card)
				continue;

			/* don't link Cap and Play */
			if (substream->stream != s->stream)
				continue;

			trace_asihpi_stream_trigger(s, cmd);
			trace_asihpi_stream_state("trig", substream, hhs);

			if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
				/* FIXME: doesn't this belong in prepare() ? */
				hpi_handle_error(
					hpi_outstream_set_format(ds->h_stream, &ds->format));
			}

			if (card->support_grouping) {
				e = hpi_stream_group_add(
					dpcm->h_stream,
					ds->h_stream);
				if (!e) {
					snd_pcm_trigger_done(s, substream);
				} else {
					hpi_handle_error(e);
					break;
				}
			} else
				break;
		}
		/* start the master stream */
		card->pcm_start(substream);
		hpi_handle_error(hpi_stream_start(dpcm->h_stream));
		break;

	case SNDRV_PCM_TRIGGER_STOP:
		card->pcm_stop(substream);
		snd_pcm_group_for_each_entry(s, substream) {
			if (snd_pcm_substream_chip(s) != card)
				continue;
			/* don't link Cap and Play */
			if (substream->stream != s->stream)
				continue;

			trace_asihpi_stream_trigger(s, cmd);
			trace_asihpi_stream_state("trig", substream, hhs);

			/*? workaround linked streams don't
			transition to SETUP 20070706*/
			s->runtime->status->state = SNDRV_PCM_STATE_SETUP;

			if (card->support_grouping)
				snd_pcm_trigger_done(s, substream);
			else
				break;
		}

		/* _prepare and _hwparams reset the stream */
		hpi_handle_error(hpi_stream_stop(dpcm->h_stream));
		/* FIXME: Is reset needed or useful here? It is already done in prepare() */
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
			hpi_handle_error(
				hpi_outstream_reset(dpcm->h_stream));

		if (card->support_grouping)
			hpi_handle_error(hpi_stream_group_reset(dpcm->h_stream));
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		card->pcm_start(substream);
		hpi_handle_error(hpi_stream_start(dpcm->h_stream));
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		card->pcm_stop(substream);
		hpi_handle_error(hpi_stream_stop(dpcm->h_stream));
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

/** Timer function, equivalent to interrupt service routine for cards */
static enum hrtimer_restart snd_card_asihpi_pcm_timer_f(struct hrtimer *hrt)
{
	struct snd_card_asihpi_pcm *dpcm = container_of(hrt, struct snd_card_asihpi_pcm, hrt);
	struct snd_pcm_substream *substream = dpcm->substream;
	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
	struct snd_pcm_substream *s;

	hrtimer_set_expires(hrt, ktime_add_safe(hrt->base->get_time(), ns_to_ktime(dpcm->poll_time_ns)));

	/* Without linking degenerates to getting single stream pos etc */
	snd_pcm_group_for_each_entry(s, substream) {
		struct snd_pcm_runtime *runtime = s->runtime;
		struct snd_card_asihpi_pcm *ds = runtime->private_data;
		struct hpi_hostbuffer_status *hhs = ds->buf_status;

		if (snd_pcm_substream_chip(s) != card)
			continue;

		/* Don't link capture and playnack streams */
		if (substream->stream != s->stream)
			continue;

		trace_asihpi_stream_state(".tmr", s, hhs);
		{
			/*
				Make the ALSA PCM runtime update the hw_ptr instead of
				calling the pointer callback followed by snd_pcm_period_elapsed()
				which would result in calling the pointer callback twice.
			*/
			snd_pcm_period_elapsed(s);
		}
	}

	if (!atomic_read(&dpcm->respawn_timer)) {
		/* this PCM was stopped don't restart timer */
		return HRTIMER_NORESTART;
	}

	return HRTIMER_RESTART;
}

static void snd_card_asihpi_setup_pcm_hardware_struct(struct snd_pcm_substream *substream, struct snd_pcm_hardware *pcm_hw_ptr);

/***************************** PLAYBACK OPS ****************/
static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream *
					    substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	struct hpi_hostbuffer_status *hhs = dpcm->buf_status;

	hpi_handle_error(hpi_outstream_reset(dpcm->h_stream));

	memset(&dpcm->indirect_pcm_state, 0, sizeof(dpcm->indirect_pcm_state));
	dpcm->indirect_pcm_state.hw_buffer_size = dpcm->buffer_bytes;
	dpcm->indirect_pcm_state.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);

	dpcm->prev_appl_ptr = 0;

	trace_asihpi_stream_state("prep", substream, hhs);
	return 0;
}

static snd_pcm_uframes_t
snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	struct hpi_hostbuffer_status *hhs = dpcm->buf_status;
	/*
		With DSP code prior to 4.20.4? and 4.30.?? hhs->dwDspIndex - hhs->dwAuxiliaryDataAvailable
		will not always increase motonically ultimately resulting in XRUNs.
	*/
	snd_pcm_uframes_t hw_ptr = bytes_to_frames(runtime, ((READ_ONCE(hhs->dwDspIndex) - READ_ONCE(hhs->dwAuxiliaryDataAvailable)) % dpcm->buffer_bytes));

	trace_asihpi_stream_state(".ptr", substream, hhs);
	trace_asihpi_hwptr_update(substream, hw_ptr);
	return hw_ptr;
}

static int
snd_card_asihpi_playback_ack(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	struct hpi_hostbuffer_status *hhs = dpcm->buf_status;
	snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
	snd_pcm_sframes_t diff = appl_ptr - dpcm->prev_appl_ptr;

	if (diff) {
		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
			diff += runtime->boundary;
		if (diff < 0) {
			snd_printk(KERN_ERR "appl_ptr EINVAL %lu - %lu = %ld\n", appl_ptr, dpcm->prev_appl_ptr, diff);
			return -EINVAL;
		} else {
			dpcm->prev_appl_ptr = appl_ptr;
			hhs->dwHostIndex += frames_to_bytes(runtime, diff);
		}
	}
	trace_asihpi_stream_xfer(substream, frames_to_bytes(runtime, diff));
	trace_asihpi_stream_state(".ack", substream, hhs);
	return 0;
}

static u64 snd_card_asihpi_playback_formats(struct snd_card_asihpi *asihpi,
						u32 h_stream)
{
	struct hpi_format hpi_format;
	u16 format;
	u16 err;
	u32 h_control;
	u32 sample_rate = 48000;
	u64 formats = 0;

	/* on cards without SRC, must query at valid rate,
	* maybe set by external sync
	*/
	err = hpi_mixer_get_control(asihpi->h_mixer,
				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
				  HPI_CONTROL_SAMPLECLOCK, &h_control);

	if (!err)
		err = hpi_sample_clock_get_sample_rate(h_control,
				&sample_rate);

	for (format = HPI_FORMAT_PCM8_UNSIGNED;
	     format <= HPI_FORMAT_PCM24_SIGNED; format++) {
		err = hpi_format_create(&hpi_format, asihpi->out_max_chans,
					format, sample_rate, 128000, 0);
		if (!err)
			err = hpi_outstream_query_format(h_stream, &hpi_format);
		if (!err && (hpi_to_alsa_formats[format] != -1))
			formats |= pcm_format_to_bits(hpi_to_alsa_formats[format]);
	}
	return formats;
}

static int snd_card_asihpi_playback_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm;
	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
	struct snd_pcm_hardware snd_card_asihpi_playback;
	int stream_idx = split_adapter ? card->split_device_idx : substream->number;
	int err;

	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
	if (!dpcm)
		return -ENOMEM;

	err = hpi_outstream_open(card->hpi->index, stream_idx, &dpcm->h_stream);
	hpi_handle_error(err);
	if (err) {
		kfree(dpcm);
		if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
			return -EBUSY;
		else
			return -EIO;
	}

	/*? also check ASI5000 samplerate source
	    If external, only support external rate.
	    If internal and other stream playing, can't switch
	*/

	dpcm->substream = substream;
	if (card->hpi->aOutStreamHostBufferStatus)
		dpcm->buf_status = &card->hpi->aOutStreamHostBufferStatus[stream_idx];

	runtime->private_data = dpcm;
	runtime->private_free = snd_card_asihpi_runtime_free;

	memset(&snd_card_asihpi_playback, 0, sizeof(snd_card_asihpi_playback));
	hrtimer_init(&dpcm->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	dpcm->hrt.function = snd_card_asihpi_pcm_timer_f;

	snd_card_asihpi_setup_pcm_hardware_struct(substream, &snd_card_asihpi_playback);

	/* struct is copied, so can create initializer dynamically */
	runtime->hw = snd_card_asihpi_playback;

	err = snd_pcm_hw_constraint_pow2(runtime, 0,
				SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
	if (err < 0)
		return err;

	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
		card->update_interval_frames);

	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
		card->update_interval_frames, UINT_MAX);

	return 0;
}

static int snd_card_asihpi_playback_close(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;

	hrtimer_cancel(&dpcm->hrt);
	hpi_handle_error(hpi_outstream_close(dpcm->h_stream));
	return 0;
}

static struct snd_pcm_ops snd_card_asihpi_playback_mmap_ops = {
	.open = snd_card_asihpi_playback_open,
	.close = snd_card_asihpi_playback_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = snd_card_asihpi_pcm_hw_params,
	.hw_free = snd_card_asihpi_hw_free,
	.prepare = snd_card_asihpi_playback_prepare,
	.trigger = snd_card_asihpi_pcm_trigger,
	.pointer = snd_card_asihpi_playback_pointer,
	.ack = snd_card_asihpi_playback_ack,
};

/***************************** CAPTURE OPS ****************/
static snd_pcm_uframes_t
snd_card_asihpi_capture_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	struct hpi_hostbuffer_status *hhs = dpcm->buf_status;
	snd_pcm_uframes_t hw_ptr;

	hw_ptr = bytes_to_frames(runtime, READ_ONCE(hhs->dwDspIndex) % dpcm->buffer_bytes);
	trace_asihpi_stream_state(".ptr", substream, hhs);
	trace_asihpi_hwptr_update(substream, hw_ptr);
	return hw_ptr;
}

static int
snd_card_asihpi_capture_ack(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
	snd_pcm_sframes_t diff = appl_ptr - dpcm->prev_appl_ptr;

	if (diff) {
		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
			diff += runtime->boundary;
		if (diff < 0) {
			snd_printk(KERN_ERR "appl_ptr EINVAL %lu - %lu = %ld\n", appl_ptr, dpcm->prev_appl_ptr, diff);
			return -EINVAL;
		} else {
			struct hpi_hostbuffer_status *hhs = dpcm->buf_status;
			dpcm->prev_appl_ptr = appl_ptr;
			trace_asihpi_stream_xfer(substream, frames_to_bytes(runtime, diff));
			hhs->dwHostIndex += frames_to_bytes(runtime, diff);
			trace_asihpi_stream_state(".ptr", substream, hhs);
		}
	}
	return 0;
}

static int snd_card_asihpi_capture_prepare(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	struct hpi_hostbuffer_status *hhs = dpcm->buf_status;

	hpi_handle_error(hpi_instream_reset(dpcm->h_stream));

	memset(&dpcm->indirect_pcm_state, 0, sizeof(dpcm->indirect_pcm_state));
	dpcm->indirect_pcm_state.hw_buffer_size = dpcm->buffer_bytes;
	dpcm->indirect_pcm_state.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);

	dpcm->prev_appl_ptr = 0;

	trace_asihpi_stream_state("prep", substream, hhs);
	return 0;
}

static u64 snd_card_asihpi_capture_formats(struct snd_card_asihpi *asihpi,
					u32 h_stream)
{
  struct hpi_format hpi_format;
	u16 format;
	u16 err;
	u32 h_control;
	u32 sample_rate = 48000;
	u64 formats = 0;

	/* on cards without SRC, must query at valid rate,
		maybe set by external sync */
	err = hpi_mixer_get_control(asihpi->h_mixer,
				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
				  HPI_CONTROL_SAMPLECLOCK, &h_control);

	if (!err)
		err = hpi_sample_clock_get_sample_rate(h_control,
			&sample_rate);

	for (format = HPI_FORMAT_PCM8_UNSIGNED;
		format <= HPI_FORMAT_PCM24_SIGNED; format++) {

		err = hpi_format_create(&hpi_format, asihpi->in_max_chans,
					format, sample_rate, 128000, 0);
		if (!err)
			err = hpi_instream_query_format(h_stream, &hpi_format);
		if (!err && (hpi_to_alsa_formats[format] != -1))
			formats |= pcm_format_to_bits(hpi_to_alsa_formats[format]);
	}
	return formats;
}

static int snd_card_asihpi_capture_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
	struct snd_card_asihpi_pcm *dpcm;
	struct snd_pcm_hardware snd_card_asihpi_capture;
	int stream_idx = split_adapter ? card->split_device_idx : substream->number;
	int err;

	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
	if (!dpcm)
		return -ENOMEM;

	err = hpi_handle_error(
	    hpi_instream_open(card->hpi->index, stream_idx, &dpcm->h_stream));
	if (err) {
		kfree(dpcm);
		if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
			return -EBUSY;
		else
			return -EIO;
	}

	dpcm->substream = substream;
	if (card->hpi->aInStreamHostBufferStatus)
		dpcm->buf_status = &card->hpi->aInStreamHostBufferStatus[stream_idx];
	runtime->private_data = dpcm;
	runtime->private_free = snd_card_asihpi_runtime_free;

	memset(&snd_card_asihpi_capture, 0, sizeof(snd_card_asihpi_capture));
	hrtimer_init(&dpcm->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	dpcm->hrt.function = snd_card_asihpi_pcm_timer_f;

	snd_card_asihpi_setup_pcm_hardware_struct(substream, &snd_card_asihpi_capture);

	runtime->hw = snd_card_asihpi_capture;

	err = snd_pcm_hw_constraint_pow2(runtime, 0,
				SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
	if (err < 0)
		return err;

	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
		card->update_interval_frames);
	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
		card->update_interval_frames, UINT_MAX);

	snd_pcm_set_sync(substream);

	return 0;
}

static int snd_card_asihpi_capture_close(struct snd_pcm_substream *substream)
{
	struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;

	hrtimer_cancel(&dpcm->hrt);
	hpi_handle_error(hpi_instream_close(dpcm->h_stream));
	return 0;
}

static struct snd_pcm_ops snd_card_asihpi_capture_mmap_ops = {
	.open = snd_card_asihpi_capture_open,
	.close = snd_card_asihpi_capture_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = snd_card_asihpi_pcm_hw_params,
	.hw_free = snd_card_asihpi_hw_free,
	.prepare = snd_card_asihpi_capture_prepare,
	.trigger = snd_card_asihpi_pcm_trigger,
	.pointer = snd_card_asihpi_capture_pointer,
	.ack = snd_card_asihpi_capture_ack,
};

static void snd_card_asihpi_setup_pcm_hardware_struct(struct snd_pcm_substream *substream, struct snd_pcm_hardware *pcm_hw_ptr)
{
	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
	size_t max_buffer_size;

	u16 err = hpi_stream_host_buffer_allocate(dpcm->h_stream, 0, NULL, &max_buffer_size, NULL);
	if (err != HPI_ERROR_INVALID_DATASIZE) {
		dev_err(&card->pci->dev, "hpi_stream_host_buffer_allocate(0) expected retval %hu, got %hu\n", HPI_ERROR_INVALID_DATASIZE, err);
		pcm_hw_ptr->buffer_bytes_max = BUFFER_BYTES_MAX;	
	} else {
		pcm_hw_ptr->buffer_bytes_max = max_buffer_size;
	}

	pcm_hw_ptr->period_bytes_min = min_period_bytes;
	pcm_hw_ptr->period_bytes_max = pcm_hw_ptr->buffer_bytes_max / min_periods;
	pcm_hw_ptr->periods_min = min_periods;
	pcm_hw_ptr->periods_max = pcm_hw_ptr->buffer_bytes_max / min_period_bytes;

	/* pcm_hw_ptr->fifo_size = 0; */
	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		pcm_hw_ptr->channels_max = card->in_max_chans;
		pcm_hw_ptr->channels_min = card->in_min_chans;
		pcm_hw_ptr->formats =
			snd_card_asihpi_capture_formats(card, dpcm->h_stream);
	} else {
		pcm_hw_ptr->channels_max = card->out_max_chans;
		pcm_hw_ptr->channels_min = card->out_min_chans;
		pcm_hw_ptr->formats =
				snd_card_asihpi_playback_formats(card, dpcm->h_stream);		
	}

	snd_card_asihpi_pcm_samplerates(card, pcm_hw_ptr);

	pcm_hw_ptr->info = SNDRV_PCM_INFO_INTERLEAVED |
					SNDRV_PCM_INFO_DOUBLE |
					SNDRV_PCM_INFO_BLOCK_TRANSFER |
					SNDRV_PCM_INFO_PAUSE |
					SNDRV_PCM_INFO_MMAP |
					SNDRV_PCM_INFO_MMAP_VALID |
					SNDRV_PCM_INFO_SYNC_APPLPTR;

	#ifdef SNDRV_PCM_INFO_NO_REWINDS
	pcm_hw_ptr->info |= SNDRV_PCM_INFO_NO_REWINDS;
	#endif

	if (card->support_grouping) {
		pcm_hw_ptr->info |= SNDRV_PCM_INFO_SYNC_START;
		snd_pcm_set_sync(substream);
	}
}

static int snd_card_asihpi_pcm_new(struct snd_card_asihpi *asihpi, int device)
{
	struct snd_pcm *pcm;
	int err;

	err = snd_pcm_new(asihpi->card, "Asihpi PCM", device,
			asihpi->outstreams, asihpi->instreams, &pcm);
	if (err < 0)
		return err;

	/* pointer to ops struct is stored, dont change ops afterwards! */
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
			&snd_card_asihpi_playback_mmap_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
			&snd_card_asihpi_capture_mmap_ops);

	pcm->private_data = asihpi;
	pcm->info_flags = 0;
	// pcm->nonatomic = 0;
	strcpy(pcm->name, "Asihpi PCM");

	return 0;
}

/***************************** MIXER CONTROLS ****************/
struct hpi_control {
	u32 h_control;
	u16 control_type;
	u16 src_node_type;
	u16 src_node_index;
	u16 dst_node_type;
	u16 dst_node_index;
	u16 band;
	char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* copied to snd_ctl_elem_id.name[44]; */
};

static const char * const asihpi_tuner_band_names[] = {
	"invalid",
	"AM",
	"FM mono",
	"TV NTSC-M",
	"FM stereo",
	"AUX",
	"TV PAL BG",
	"TV PAL I",
	"TV PAL DK",
	"TV SECAM",
	"TV DAB",
};
/* Number of strings must match the enumerations for HPI_TUNER_BAND in hpi.h */
compile_time_assert(
	(ARRAY_SIZE(asihpi_tuner_band_names) ==
		(HPI_TUNER_BAND_LAST+1)),
	assert_tuner_band_names_size);

static const char * const asihpi_src_names[] = {
	"no source",
	"PCM",
	"Line",
	"Digital",
	"Tuner",
	"RF",
	"Clock",
	"Bitstream",
	"Mic",
	"Net",
	"Analog",
	"Adapter",
	"RTP",
	"Internal",
	"AVB_stream",
	"BLU-Link",
	"AVB_audio"
};
/* Number of strings must match the enumerations for HPI_SOURCENODES in hpi.h */
compile_time_assert(
	(ARRAY_SIZE(asihpi_src_names) ==
		(HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_NONE+1)),
	assert_src_names_size);

static const char * const asihpi_dst_names[] = {
	"no destination",
	"PCM",
	"Line",
	"Digital",
	"RF",
	"Speaker",
	"Net",
	"Analog",
	"RTP",
	"AVB_stream",
 	"Internal",
	"BLU-Link",
	"AVB_audio"
};
/* Number of strings must match the enumerations for HPI_DESTNODES in hpi.h */
compile_time_assert(
	(ARRAY_SIZE(asihpi_dst_names) ==
		(HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_NONE+1)),
	assert_dst_names_size);

static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl,
				struct snd_card_asihpi *asihpi)
{
	int err;

	err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi));
	if (err < 0)
		return err;
	else if (mixer_dump)
		dev_info(&asihpi->pci->dev, "added %s(%d)\n", ctl->name, ctl->index);

	return 0;
}

/* Convert HPI control name and location into ALSA control name */
static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control,
				struct hpi_control *hpi_ctl,
				char *name)
{
	char *dir;
	memset(snd_control, 0, sizeof(*snd_control));
	snd_control->name = hpi_ctl->name;
	snd_control->private_value = hpi_ctl->h_control;
	snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
	snd_control->index = 0;

	if (hpi_ctl->src_node_type + HPI_SOURCENODE_NONE == HPI_SOURCENODE_CLOCK_SOURCE)
		dir = ""; /* clock is neither capture nor playback */
	else if (hpi_ctl->dst_node_type + HPI_DESTNODE_NONE == HPI_DESTNODE_ISTREAM)
		dir = "Capture ";  /* On or towards a PCM capture destination*/
	else if ((hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) &&
		(!hpi_ctl->dst_node_type))
		dir = "Capture "; /* On a source node that is not PCM playback */
	else if (hpi_ctl->src_node_type &&
		(hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) &&
		(hpi_ctl->dst_node_type))
		dir = "Monitor Playback "; /* Between an input and an output */
	else
		dir = "Playback "; /* PCM Playback source, or  output node */

	if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type)
		sprintf(hpi_ctl->name, "%s %d %s %d %s%s",
			asihpi_src_names[hpi_ctl->src_node_type],
			hpi_ctl->src_node_index,
			asihpi_dst_names[hpi_ctl->dst_node_type],
			hpi_ctl->dst_node_index,
			dir, name);
	else if (hpi_ctl->dst_node_type) {
		sprintf(hpi_ctl->name, "%s %d %s%s",
		asihpi_dst_names[hpi_ctl->dst_node_type],
		hpi_ctl->dst_node_index,
		dir, name);
	} else {
		sprintf(hpi_ctl->name, "%s %d %s%s",
		asihpi_src_names[hpi_ctl->src_node_type],
		hpi_ctl->src_node_index,
		dir, name);
	}
	/* snd_printk(KERN_INFO "Adding %s %d to %d ",  hpi_ctl->name,
		hpi_ctl->wSrcNodeType, hpi_ctl->wDstNodeType); */
}

/*------------------------------------------------------------
   Volume controls
 ------------------------------------------------------------*/
#define VOL_STEP_mB 1
static int snd_asihpi_volume_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	u32 h_control = kcontrol->private_value;
	u32 count;
	u16 err;
	/* native gains are in millibels */
	short min_gain_mB;
	short max_gain_mB;
	short step_gain_mB;

	err = hpi_volume_query_range(h_control,
			&min_gain_mB, &max_gain_mB, &step_gain_mB);
	if (err) {
		max_gain_mB = 0;
		min_gain_mB = -10000;
		step_gain_mB = VOL_STEP_mB;
	}

	err = hpi_volume_query_channels(h_control, &count);
	if (err)
		count = HPI_MAX_CHANNELS;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = count;
	uinfo->value.integer.min = min_gain_mB / VOL_STEP_mB;
	uinfo->value.integer.max = max_gain_mB / VOL_STEP_mB;
	uinfo->value.integer.step = step_gain_mB / VOL_STEP_mB;
	return 0;
}

static int snd_asihpi_volume_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	short an_gain_mB[HPI_MAX_CHANNELS];

	hpi_handle_error(hpi_volume_get_gain(h_control, an_gain_mB));
	ucontrol->value.integer.value[0] = an_gain_mB[0] / VOL_STEP_mB;
	ucontrol->value.integer.value[1] = an_gain_mB[1] / VOL_STEP_mB;

	return 0;
}

static int snd_asihpi_volume_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	int change;
	u32 h_control = kcontrol->private_value;
	short an_gain_mB[HPI_MAX_CHANNELS];

	an_gain_mB[0] =
	    (ucontrol->value.integer.value[0]) * VOL_STEP_mB;
	an_gain_mB[1] =
	    (ucontrol->value.integer.value[1]) * VOL_STEP_mB;
	/*  change = asihpi->mixer_volume[addr][0] != left ||
	   asihpi->mixer_volume[addr][1] != right;
	 */
	change = 1;
	hpi_handle_error(hpi_volume_set_gain(h_control, an_gain_mB));
	return change;
}

static const DECLARE_TLV_DB_SCALE(db_scale_100, -10000, VOL_STEP_mB, 1);
static const DECLARE_TLV_DB_SCALE(db_scale_20_60_mic, 2000, 1, 0);

#define snd_asihpi_volume_mute_info	snd_ctl_boolean_mono_info

static int snd_asihpi_volume_mute_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u32 mute;

	hpi_handle_error(hpi_volume_get_mute(h_control, &mute));
	ucontrol->value.integer.value[0] = mute ? 0 : 1;

	return 0;
}

static int snd_asihpi_volume_mute_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	int change = 1;
	/* HPI currently only supports all or none muting of multichannel volume
	ALSA Switch element has opposite sense to HPI mute: on==unmuted, off=muted
	*/
	int mute =  ucontrol->value.integer.value[0] ? 0 : HPI_BITMASK_ALL_CHANNELS;
	hpi_handle_error(hpi_volume_set_mute(h_control, mute));
	return change;
}

static int snd_asihpi_volume_add(struct snd_card_asihpi *asihpi,
				 struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;
	int err;
	u32 mute;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Volume");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
				SNDRV_CTL_ELEM_ACCESS_TLV_READ;
	snd_control.info = snd_asihpi_volume_info;
	snd_control.get = snd_asihpi_volume_get;
	snd_control.put = snd_asihpi_volume_put;

	if ((hpi_ctl->src_node_type + HPI_SOURCENODE_NONE) == HPI_SOURCENODE_MICROPHONE) {
		snd_control.tlv.p = db_scale_20_60_mic;
	} else {
		snd_control.tlv.p = db_scale_100;
	}

	err = ctl_add(card, &snd_control, asihpi);
	if (err)
		return err;

	if (hpi_volume_get_mute(hpi_ctl->h_control, &mute) == 0) {
		asihpi_ctl_init(&snd_control, hpi_ctl, "Switch");
		snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
		snd_control.info = snd_asihpi_volume_mute_info;
		snd_control.get = snd_asihpi_volume_mute_get;
		snd_control.put = snd_asihpi_volume_mute_put;
		err = ctl_add(card, &snd_control, asihpi);
	}

	return err;
}

/*------------------------------------------------------------
   Level controls
 ------------------------------------------------------------*/
static int snd_asihpi_level_info(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_info *uinfo)
{
	u32 h_control = kcontrol->private_value;
	u16 err;
	short min_gain_mB;
	short max_gain_mB;
	short step_gain_mB;

	err =
	    hpi_level_query_range(h_control, &min_gain_mB,
			       &max_gain_mB, &step_gain_mB);
	if (err) {
		max_gain_mB = 2400;
		min_gain_mB = -1000;
		step_gain_mB = 100;
	}

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = min_gain_mB / HPI_UNITS_PER_dB;
	uinfo->value.integer.max = max_gain_mB / HPI_UNITS_PER_dB;
	uinfo->value.integer.step = step_gain_mB / HPI_UNITS_PER_dB;
	return 0;
}

static int snd_asihpi_level_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	short an_gain_mB[HPI_MAX_CHANNELS];

	hpi_handle_error(hpi_level_get_gain(h_control, an_gain_mB));
	ucontrol->value.integer.value[0] =
	    an_gain_mB[0] / HPI_UNITS_PER_dB;

	return 0;
}

static int snd_asihpi_level_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	int change;
	u32 h_control = kcontrol->private_value;
	short an_gain_mB[HPI_MAX_CHANNELS];

	an_gain_mB[0] = an_gain_mB[1] =
	    (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;

	/*  change = asihpi->mixer_level[addr][0] != left  */
	change = 1;
	hpi_handle_error(hpi_level_set_gain(h_control, an_gain_mB));
	return change;
}

static const DECLARE_TLV_DB_SCALE(db_scale_level, -1000, 100, 0);

static int snd_asihpi_level_add(struct snd_card_asihpi *asihpi,
				struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	/* can't use 'volume' cos some nodes have volume as well */
	asihpi_ctl_init(&snd_control, hpi_ctl, "Level");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
				SNDRV_CTL_ELEM_ACCESS_TLV_READ;
	snd_control.info = snd_asihpi_level_info;
	snd_control.get = snd_asihpi_level_get;
	snd_control.put = snd_asihpi_level_put;
	snd_control.tlv.p = db_scale_level;

	return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   AESEBU controls
 ------------------------------------------------------------*/

/* AESEBU format */
static const char * const asihpi_aesebu_format_names[] = {
	"N/A", "S/PDIF", "AES/EBU" };

static int snd_asihpi_aesebu_format_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	return snd_ctl_enum_info(uinfo, 1, 3, asihpi_aesebu_format_names);
}

static int snd_asihpi_aesebu_format_get(struct snd_kcontrol *kcontrol,
			struct snd_ctl_elem_value *ucontrol,
			u16 (*func)(u32, u16 *))
{
	u32 h_control = kcontrol->private_value;
	u16 source, err;

	err = func(h_control, &source);

	/* default to N/A */
	ucontrol->value.enumerated.item[0] = 0;
	/* return success but set the control to N/A */
	if (err)
		return 0;
	if (source == HPI_AESEBU_FORMAT_SPDIF)
		ucontrol->value.enumerated.item[0] = 1;
	if (source == HPI_AESEBU_FORMAT_AESEBU)
		ucontrol->value.enumerated.item[0] = 2;

	return 0;
}

static int snd_asihpi_aesebu_format_put(struct snd_kcontrol *kcontrol,
			struct snd_ctl_elem_value *ucontrol,
			 u16 (*func)(u32, u16))
{
	u32 h_control = kcontrol->private_value;

	/* default to S/PDIF */
	u16 source = HPI_AESEBU_FORMAT_SPDIF;

	if (ucontrol->value.enumerated.item[0] == 1)
		source = HPI_AESEBU_FORMAT_SPDIF;
	if (ucontrol->value.enumerated.item[0] == 2)
		source = HPI_AESEBU_FORMAT_AESEBU;

	if (func(h_control, source) != 0)
		return -EINVAL;

	return 1;
}

static int snd_asihpi_aesebu_rx_format_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol) {
	return snd_asihpi_aesebu_format_get(kcontrol, ucontrol,
					hpi_aesebu_receiver_get_format);
}

static int snd_asihpi_aesebu_rx_format_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol) {
	return snd_asihpi_aesebu_format_put(kcontrol, ucontrol,
					hpi_aesebu_receiver_set_format);
}

static int snd_asihpi_aesebu_rxstatus_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;

	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 0X1F;
	uinfo->value.integer.step = 1;

	return 0;
}

static int snd_asihpi_aesebu_rxstatus_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol) {

	u32 h_control = kcontrol->private_value;
	u16 status;

	hpi_handle_error(hpi_aesebu_receiver_get_error_status(
					 h_control, &status));
	ucontrol->value.integer.value[0] = status;
	return 0;
}

static int snd_asihpi_aesebu_rx_add(struct snd_card_asihpi *asihpi,
				    struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Format");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
	snd_control.info = snd_asihpi_aesebu_format_info;
	snd_control.get = snd_asihpi_aesebu_rx_format_get;
	snd_control.put = snd_asihpi_aesebu_rx_format_put;


	if (ctl_add(card, &snd_control, asihpi) < 0)
		return -EINVAL;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Status");
	snd_control.access =
	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
	snd_control.info = snd_asihpi_aesebu_rxstatus_info;
	snd_control.get = snd_asihpi_aesebu_rxstatus_get;

	return ctl_add(card, &snd_control, asihpi);
}

static int snd_asihpi_aesebu_tx_format_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol) {
	return snd_asihpi_aesebu_format_get(kcontrol, ucontrol,
					hpi_aesebu_transmitter_get_format);
}

static int snd_asihpi_aesebu_tx_format_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol) {
	return snd_asihpi_aesebu_format_put(kcontrol, ucontrol,
					hpi_aesebu_transmitter_set_format);
}


static int snd_asihpi_aesebu_tx_add(struct snd_card_asihpi *asihpi,
				    struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Format");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
	snd_control.info = snd_asihpi_aesebu_format_info;
	snd_control.get = snd_asihpi_aesebu_tx_format_get;
	snd_control.put = snd_asihpi_aesebu_tx_format_put;

	return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   Tuner controls
 ------------------------------------------------------------*/

/* Gain */

static int snd_asihpi_tuner_gain_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	u32 h_control = kcontrol->private_value;
	u16 err;
	short idx;
	u16 gain_range[3];

	for (idx = 0; idx < 3; idx++) {
		err = hpi_tuner_query_gain(h_control,
					  idx, &gain_range[idx]);
		if (err != 0)
			return err;
	}

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = ((int)gain_range[0]) / HPI_UNITS_PER_dB;
	uinfo->value.integer.max = ((int)gain_range[1]) / HPI_UNITS_PER_dB;
	uinfo->value.integer.step = ((int) gain_range[2]) / HPI_UNITS_PER_dB;
	return 0;
}

static int snd_asihpi_tuner_gain_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	/*
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	*/
	u32 h_control = kcontrol->private_value;
	short gain;

	hpi_handle_error(hpi_tuner_get_gain(h_control, &gain));
	ucontrol->value.integer.value[0] = gain / HPI_UNITS_PER_dB;

	return 0;
}

static int snd_asihpi_tuner_gain_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	/*
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	*/
	u32 h_control = kcontrol->private_value;
	short gain;

	gain = (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
	hpi_handle_error(hpi_tuner_set_gain(h_control, gain));

	return 1;
}

/* Band  */

static int asihpi_tuner_band_query(struct snd_kcontrol *kcontrol,
					u16 *band_list, u32 len) {
	u32 h_control = kcontrol->private_value;
	u16 err = 0;
	u32 i;

	for (i = 0; i < len; i++) {
		err = hpi_tuner_query_band(
				h_control, i, &band_list[i]);
		if (err != 0)
			break;
	}

	if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX))
		return -EIO;

	return i;
}

static int snd_asihpi_tuner_band_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	u16 tuner_bands[HPI_TUNER_BAND_LAST];
	u16 hpi_band_idx;
	int num_bands = 0;

	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
				ARRAY_SIZE(tuner_bands));
	if (num_bands < 0)
		return num_bands;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = num_bands;
	if (!num_bands)
		return 0;
	if (uinfo->value.enumerated.item >= num_bands)
		uinfo->value.enumerated.item = num_bands - 1;
	hpi_band_idx = tuner_bands[uinfo->value.enumerated.item];
	strscpy(uinfo->value.enumerated.name,
		asihpi_tuner_band_names[hpi_band_idx],
		sizeof(uinfo->value.enumerated.name));
	return 0;
}

static int snd_asihpi_tuner_band_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	/*
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	*/
	u16 band, idx;
	u16 tuner_bands[HPI_TUNER_BAND_LAST];
	u32 num_bands;

	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
				ARRAY_SIZE(tuner_bands));
	if (num_bands < 0)
		return num_bands;

	hpi_handle_error(hpi_tuner_get_band(h_control, &band));

	ucontrol->value.enumerated.item[0] = -1;
	for (idx = 0; idx < num_bands; idx++)
		if (tuner_bands[idx] == band) {
			ucontrol->value.enumerated.item[0] = idx;
			break;
		}

	return 0;
}

static int snd_asihpi_tuner_band_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	/*
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	*/
	u32 h_control = kcontrol->private_value;
	unsigned int idx;
	u16 tuner_bands[HPI_TUNER_BAND_LAST];
	u32 num_bands;

	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
			ARRAY_SIZE(tuner_bands));

	idx = ucontrol->value.enumerated.item[0];
	if (idx >= num_bands)
		idx = num_bands - 1;
	hpi_handle_error(hpi_tuner_set_band(h_control, tuner_bands[idx]));

	return 1;
}

/* Freq */

static int snd_asihpi_tuner_freq_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	u32 h_control = kcontrol->private_value;
	u16 err;
	u16 tuner_bands[HPI_TUNER_BAND_LAST];
	u16 num_bands = 0, band_iter, idx;
	u32 freq_range[3], temp_freq_range[3];

	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
			HPI_TUNER_BAND_LAST);

	freq_range[0] = INT_MAX;
	freq_range[1] = 0;
	freq_range[2] = INT_MAX;

	for (band_iter = 0; band_iter < num_bands; band_iter++) {
		for (idx = 0; idx < 3; idx++) {
			err = hpi_tuner_query_frequency(h_control,
				idx, tuner_bands[band_iter],
				&temp_freq_range[idx]);
			if (err != 0)
				return err;
		}

		/* skip band with bogus stepping */
		if (temp_freq_range[2] <= 0)
			continue;

		if (temp_freq_range[0] < freq_range[0])
			freq_range[0] = temp_freq_range[0];
		if (temp_freq_range[1] > freq_range[1])
			freq_range[1] = temp_freq_range[1];
		if (temp_freq_range[2] < freq_range[2])
			freq_range[2] = temp_freq_range[2];
	}

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = ((int)freq_range[0]);
	uinfo->value.integer.max = ((int)freq_range[1]);
	uinfo->value.integer.step = ((int)freq_range[2]);
	return 0;
}

static int snd_asihpi_tuner_freq_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u32 freq;

	hpi_handle_error(hpi_tuner_get_frequency(h_control, &freq));
	ucontrol->value.integer.value[0] = freq;

	return 0;
}

static int snd_asihpi_tuner_freq_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u32 freq;

	freq = ucontrol->value.integer.value[0];
	hpi_handle_error(hpi_tuner_set_frequency(h_control, freq));

	return 1;
}

/* Tuner control group initializer  */
static int snd_asihpi_tuner_add(struct snd_card_asihpi *asihpi,
				struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	snd_control.private_value = hpi_ctl->h_control;
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;

	if (!hpi_tuner_get_gain(hpi_ctl->h_control, NULL)) {
		asihpi_ctl_init(&snd_control, hpi_ctl, "Gain");
		snd_control.info = snd_asihpi_tuner_gain_info;
		snd_control.get = snd_asihpi_tuner_gain_get;
		snd_control.put = snd_asihpi_tuner_gain_put;

		if (ctl_add(card, &snd_control, asihpi) < 0)
			return -EINVAL;
	}

	asihpi_ctl_init(&snd_control, hpi_ctl, "Band");
	snd_control.info = snd_asihpi_tuner_band_info;
	snd_control.get = snd_asihpi_tuner_band_get;
	snd_control.put = snd_asihpi_tuner_band_put;

	if (ctl_add(card, &snd_control, asihpi) < 0)
		return -EINVAL;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Freq");
	snd_control.info = snd_asihpi_tuner_freq_info;
	snd_control.get = snd_asihpi_tuner_freq_get;
	snd_control.put = snd_asihpi_tuner_freq_put;

	return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   Meter controls
 ------------------------------------------------------------*/
static int snd_asihpi_meter_info(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_info *uinfo)
{
	u32 h_control = kcontrol->private_value;
	u32 count;
	u16 err;
	err = hpi_meter_query_channels(h_control, &count);
	if (err)
		count = HPI_MAX_CHANNELS;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = count;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 0x7FFFFFFF;
	return 0;
}

/* linear values for 10dB steps */
static int log2lin[] = {
	0x7FFFFFFF, /* 0dB */
	679093956,
	214748365,
	 67909396,
	 21474837,
	  6790940,
	  2147484, /* -60dB */
	   679094,
	   214748, /* -80 */
	    67909,
	    21475, /* -100 */
	     6791,
	     2147,
	      679,
	      214,
	       68,
	       21,
		7,
		2
};

static int snd_asihpi_meter_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	short an_gain_mB[HPI_MAX_CHANNELS], i;
	u16 err;

	err = hpi_meter_get_peak(h_control, an_gain_mB);

	for (i = 0; i < HPI_MAX_CHANNELS; i++) {
		if (err) {
			ucontrol->value.integer.value[i] = 0;
		} else if (an_gain_mB[i] >= 0) {
			ucontrol->value.integer.value[i] =
				an_gain_mB[i] << 16;
		} else {
			/* -ve is log value in millibels < -60dB,
			* convert to (roughly!) linear,
			*/
			ucontrol->value.integer.value[i] =
					log2lin[an_gain_mB[i] / -1000];
		}
	}
	return 0;
}

static int snd_asihpi_meter_add(struct snd_card_asihpi *asihpi,
				struct hpi_control *hpi_ctl, int subidx)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Meter");
	snd_control.access =
	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
	snd_control.info = snd_asihpi_meter_info;
	snd_control.get = snd_asihpi_meter_get;

	snd_control.index = subidx;

	return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   Multiplexer controls
 ------------------------------------------------------------*/
static int snd_card_asihpi_mux_count_sources(struct snd_kcontrol *snd_control)
{
	u32 h_control = snd_control->private_value;
	struct hpi_control hpi_ctl;
	int s, err;
	for (s = 0; s < 32; s++) {
		err = hpi_multiplexer_query_source(h_control, s,
						  &hpi_ctl.
						  src_node_type,
						  &hpi_ctl.
						  src_node_index);
		if (err)
			break;
	}
	return s;
}

static int snd_asihpi_mux_info(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_info *uinfo)
{
	int err;
	u16 src_node_type, src_node_index;
	u32 h_control = 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_query_source(h_control,
					uinfo->value.enumerated.item,
					&src_node_type, &src_node_index);

	sprintf(uinfo->value.enumerated.name, "%s %d",
		asihpi_src_names[src_node_type - HPI_SOURCENODE_NONE],
		src_node_index);
	return 0;
}

static int snd_asihpi_mux_get(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u16 source_type, source_index;
	u16 src_node_type, src_node_index;
	int s;

	hpi_handle_error(hpi_multiplexer_get_source(h_control,
				&source_type, &source_index));
	/* Should cache this search result! */
	for (s = 0; s < 256; s++) {
		if (hpi_multiplexer_query_source(h_control, s,
					    &src_node_type, &src_node_index))
			break;

		if ((source_type == src_node_type)
		    && (source_index == src_node_index)) {
			ucontrol->value.enumerated.item[0] = s;
			return 0;
		}
	}
	snd_printd(KERN_WARNING
		"Control %x failed to match mux source %hu %hu\n",
		h_control, source_type, source_index);
	ucontrol->value.enumerated.item[0] = 0;
	return 0;
}

static int snd_asihpi_mux_put(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	int change;
	u32 h_control = kcontrol->private_value;
	u16 source_type, source_index;
	u16 e;

	change = 1;

	e = hpi_multiplexer_query_source(h_control,
				    ucontrol->value.enumerated.item[0],
				    &source_type, &source_index);
	if (!e)
		hpi_handle_error(
			hpi_multiplexer_set_source(h_control,
						source_type, source_index));
	return change;
}


static int  snd_asihpi_mux_add(struct snd_card_asihpi *asihpi,
			       struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Route");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
	snd_control.info = snd_asihpi_mux_info;
	snd_control.get = snd_asihpi_mux_get;
	snd_control.put = snd_asihpi_mux_put;

	return ctl_add(card, &snd_control, asihpi);

}

/*------------------------------------------------------------
   Channel mode controls
 ------------------------------------------------------------*/

static int asihpi_cmode_query(struct snd_kcontrol *kcontrol,
					u16 *cmode_list, u32 len) {
	u32 h_control = kcontrol->private_value;
	u16 err = 0;
	u32 i;

	for (i = 0; i < len; i++) {
		err = hpi_channel_mode_query_mode(
				h_control, i, &cmode_list[i]);
		if (err != 0)
			break;
	}

	if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX))
		return -EIO;

	return i;
}

static int snd_asihpi_cmode_info(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_info *uinfo)
{
	static const char * const mode_names[HPI_CHANNEL_MODE_LAST + 1] = {
		"invalid",
		"Normal", "Swap",
		"From Left", "From Right",
		"To Left", "To Right"
	};

	u16 cmode_list[HPI_CHANNEL_MODE_LAST];
	u16 mode;
	int valid_modes;

	valid_modes = asihpi_cmode_query(kcontrol, cmode_list,
				ARRAY_SIZE(cmode_list));
	if (valid_modes < 0)
		return valid_modes;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = valid_modes;
	if (!valid_modes)
		return -EINVAL;
	if (uinfo->value.enumerated.item >= valid_modes)
		uinfo->value.enumerated.item = valid_modes - 1;
	mode = cmode_list[uinfo->value.enumerated.item];
	strscpy(uinfo->value.enumerated.name,
		mode_names[mode],
		sizeof(uinfo->value.enumerated.name));
	return 0;
}

static int snd_asihpi_cmode_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u16 cmode_list[HPI_CHANNEL_MODE_LAST];
	u16 mode;
	int valid_modes, idx;

	valid_modes = asihpi_cmode_query(kcontrol, cmode_list,
				ARRAY_SIZE(cmode_list));
	if (valid_modes < 0)
		return valid_modes;

	if (hpi_channel_mode_get(h_control, &mode))
		mode = 1;

	ucontrol->value.enumerated.item[0] = -1;
	for (idx = 0; idx < valid_modes; idx++)
		if (cmode_list[idx] == mode) {
			ucontrol->value.enumerated.item[0] = idx;
			break;
		}

	return 0;
}

static int snd_asihpi_cmode_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	unsigned int idx;
	u16 cmode_list[HPI_CHANNEL_MODE_LAST];
	int valid_modes;

	valid_modes = asihpi_cmode_query(kcontrol, cmode_list,
				ARRAY_SIZE(cmode_list));
	if (valid_modes < 0)
		return valid_modes;

	idx = ucontrol->value.enumerated.item[0];
	if (idx >= valid_modes)
		idx = valid_modes - 1;
	hpi_handle_error(hpi_channel_mode_set(h_control, cmode_list[idx]));

	return 1;
}


static int snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi,
				struct hpi_control *hpi_ctl)
{
	struct snd_card *card = asihpi->card;
	struct snd_kcontrol_new snd_control;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Mode");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
	snd_control.info = snd_asihpi_cmode_info;
	snd_control.get = snd_asihpi_cmode_get;
	snd_control.put = snd_asihpi_cmode_put;

	return ctl_add(card, &snd_control, asihpi);
}

/*------------------------------------------------------------
   Sampleclock source  controls
 ------------------------------------------------------------*/
static const char * const sampleclock_sources[] = {
	"N/A", "Local PLL", "Digital Sync", "Word External", "Word Header",
	"SMPTE", "Digital1", "Auto", "Network", "Invalid",
	"Prev Module", "BLU-Link",
	"Digital2", "Digital3", "Digital4", "Digital5",
	"Digital6", "Digital7", "Digital8"};

	/* Number of strings must match expected enumerated values */
	compile_time_assert(
		(ARRAY_SIZE(sampleclock_sources) == MAX_CLOCKSOURCES),
		assert_sampleclock_sources_size);

static int snd_asihpi_clksrc_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	struct snd_card_asihpi *asihpi =
			(struct snd_card_asihpi *)(kcontrol->private_data);
	struct clk_cache *clkcache = &asihpi->cc;
	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = clkcache->count;

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

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

static int snd_asihpi_clksrc_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct snd_card_asihpi *asihpi =
			(struct snd_card_asihpi *)(kcontrol->private_data);
	struct clk_cache *clkcache = &asihpi->cc;
	u32 h_control = kcontrol->private_value;
	u16 source, srcindex = 0;
	int i;

	ucontrol->value.enumerated.item[0] = 0;
	if (hpi_sample_clock_get_source(h_control, &source))
		source = 0;

	if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
		if (hpi_sample_clock_get_source_index(h_control, &srcindex))
			srcindex = 0;

	for (i = 0; i < clkcache->count; i++)
		if ((clkcache->s[i].source == source) &&
			(clkcache->s[i].index == srcindex))
			break;

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

	return 0;
}

static int snd_asihpi_clksrc_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct snd_card_asihpi *asihpi =
			(struct snd_card_asihpi *)(kcontrol->private_data);
	struct clk_cache *clkcache = &asihpi->cc;
	unsigned int item;
	int change;
	u32 h_control = kcontrol->private_value;

	change = 1;
	item = ucontrol->value.enumerated.item[0];
	if (item >= clkcache->count)
		item = clkcache->count-1;

	hpi_handle_error(hpi_sample_clock_set_source(
				h_control, clkcache->s[item].source));

	if (clkcache->s[item].source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
		hpi_handle_error(hpi_sample_clock_set_source_index(
				h_control, clkcache->s[item].index));
	return change;
}

/*------------------------------------------------------------
   Clkrate controls
 ------------------------------------------------------------*/
/* Need to change this to enumerated control with list of rates */
static int snd_asihpi_clklocal_info(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 8000;
	uinfo->value.integer.max = 192000;
	uinfo->value.integer.step = 100;

	return 0;
}

static int snd_asihpi_clklocal_get(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u32 rate;
	u16 e;

	e = hpi_sample_clock_get_local_rate(h_control, &rate);
	if (!e)
		ucontrol->value.integer.value[0] = rate;
	else
		ucontrol->value.integer.value[0] = 0;
	return 0;
}

static int snd_asihpi_clklocal_put(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	int change;
	u32 h_control = kcontrol->private_value;

	/*  change = asihpi->mixer_clkrate[addr][0] != left ||
	   asihpi->mixer_clkrate[addr][1] != right;
	 */
	change = 1;
	hpi_handle_error(hpi_sample_clock_set_local_rate(h_control,
				      ucontrol->value.integer.value[0]));
	return change;
}

static int snd_asihpi_clkrate_info(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 8000;
	uinfo->value.integer.max = 192000;
	uinfo->value.integer.step = 100;

	return 0;
}

static int snd_asihpi_clkrate_get(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	u32 h_control = kcontrol->private_value;
	u32 rate;
	u16 e;

	e = hpi_sample_clock_get_sample_rate(h_control, &rate);
	if (!e)
		ucontrol->value.integer.value[0] = rate;
	else
		ucontrol->value.integer.value[0] = 0;
	return 0;
}

static int snd_asihpi_clkcache_init(struct snd_card_asihpi *asihpi,
			u32 h_control)
{
	struct clk_cache *clkcache;
	int has_aes_in = 0;
	int i, j;
	u16 source;

	if (snd_BUG_ON(!asihpi))
		return -EINVAL;
	clkcache = &asihpi->cc;
	clkcache->has_local = 0;

	for (i = 0; i <= HPI_SAMPLECLOCK_SOURCE_LAST; i++) {
		if  (hpi_sample_clock_query_source(h_control,
				i, &source))
			break;
		clkcache->s[i].source = source;
		clkcache->s[i].index = 0;
		clkcache->s[i].name = sampleclock_sources[source];
		if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
			has_aes_in = 1;
		if (source == HPI_SAMPLECLOCK_SOURCE_LOCAL)
			clkcache->has_local = 1;
	}
	if (has_aes_in)
		/* already will have picked up index 0 above */
		for (j = 1; j < 8; j++) {
			if (hpi_sample_clock_query_source_index(h_control,
				j, HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT,
				&source))
				break;
			clkcache->s[i].source =
				HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT;
			clkcache->s[i].index = j;
			clkcache->s[i].name = sampleclock_sources[
					j+HPI_SAMPLECLOCK_SOURCE_LAST];
			i++;
		}
	clkcache->count = i;
	return 0;
}

static int snd_asihpi_sampleclock_add(struct snd_card_asihpi *asihpi,
				      struct hpi_control *hpi_ctl)
{
	struct snd_card *card;
	struct snd_kcontrol_new snd_control;
	struct clk_cache *clkcache;

	if (snd_BUG_ON(!asihpi))
		return -EINVAL;

	card = asihpi->card;
	clkcache = &asihpi->cc;
	snd_control.private_value = hpi_ctl->h_control;

	asihpi_ctl_init(&snd_control, hpi_ctl, "Source");
	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ;
	snd_control.info = snd_asihpi_clksrc_info;
	snd_control.get = snd_asihpi_clksrc_get;
	snd_control.put = snd_asihpi_clksrc_put;
	if (ctl_add(card, &snd_control, asihpi) < 0)
		return -EINVAL;


	if (clkcache->has_local) {
		asihpi_ctl_init(&snd_control, hpi_ctl, "Localrate");
		snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ;
		snd_control.info = snd_asihpi_clklocal_info;
		snd_control.get = snd_asihpi_clklocal_get;
		snd_control.put = snd_asihpi_clklocal_put;


		if (ctl_add(card, &snd_control, asihpi) < 0)
			return -EINVAL;
	}

	asihpi_ctl_init(&snd_control, hpi_ctl, "Rate");
	snd_control.access =
	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
	snd_control.info = snd_asihpi_clkrate_info;
	snd_control.get = snd_asihpi_clkrate_get;

	return ctl_add(card, &snd_control, asihpi);
}
/*------------------------------------------------------------
   Mixer
 ------------------------------------------------------------*/

static struct snd_kcontrol_new snd_asihpi_ctl_analog_level = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ),
	.info =	snd_asihpi_level_info,
	.get = snd_asihpi_level_get,
	.put = snd_asihpi_level_put,
	.tlv = { .p = db_scale_level },
};

static int hpi_mixer_get_analog_control(hpi_handle_t h_mixer, bool is_input,
	uint16_t idx, uint16_t control_type,
	hpi_handle_t *h_control)
{
	int err;
	uint16_t src_node = is_input ? HPI_SOURCENODE_ANALOG : 0;
	uint16_t dst_node = is_input ? 0 : HPI_DESTNODE_ANALOG;
	err = hpi_mixer_get_control(h_mixer, src_node, is_input ? idx : 0, dst_node,
		is_input ? 0 : idx, control_type, h_control);
	if (!err)
		return 0;

	src_node = is_input ? HPI_SOURCENODE_LINEIN : 0;
	dst_node = is_input ? 0 : HPI_DESTNODE_LINEOUT;
	err = hpi_mixer_get_control(h_mixer, src_node, is_input ? idx : 0, dst_node,
		is_input ? 0 : idx, control_type, h_control);
	if (!err)
		return 0;

	*h_control = 0;
	return -err;
}

static uint16_t io_src_node_types[] = {HPI_SOURCENODE_LINEIN, HPI_SOURCENODE_ANALOG, HPI_SOURCENODE_AESEBU_IN, HPI_SOURCENODE_COBRANET};
static uint16_t io_dst_node_types[] = {HPI_DESTNODE_LINEOUT, HPI_DESTNODE_ANALOG, HPI_DESTNODE_AESEBU_OUT, HPI_DESTNODE_COBRANET};

static int hpi_mixer_get_node_volume(hpi_handle_t h_mixer, bool is_input,
	uint16_t other_node_type, uint16_t other_node_idx, uint16_t io_idx, hpi_handle_t *h_control)
{
	int nt, err = -1;

	if (is_input) {
		for (nt = 0; nt < ARRAY_SIZE(io_src_node_types); nt++) {
			err = hpi_mixer_get_control(h_mixer,
				io_src_node_types[nt], io_idx,
				other_node_type, other_node_idx,
				HPI_CONTROL_VOLUME, h_control);
			if (!err)
				break;
		}
	} else {
		for (nt = 0; nt < ARRAY_SIZE(io_dst_node_types); nt++) {
			err = hpi_mixer_get_control(h_mixer,
				other_node_type, other_node_idx,
				io_dst_node_types[nt], io_idx,
				HPI_CONTROL_VOLUME, h_control);
			if (!err)
				break;
		}
	}
	return err;
}

static int hpi_mixer_get_io_loopback_volume(hpi_handle_t h_mixer,
	uint16_t io_idx, hpi_handle_t *h_control)
{
	int snt, dnt, err = -1;

	for (snt = 0; snt < ARRAY_SIZE(io_src_node_types); snt++) {
		for (dnt = 0; dnt < ARRAY_SIZE(io_dst_node_types); dnt++) {
			err = hpi_mixer_get_control(h_mixer,
				io_src_node_types[snt], io_idx,
				io_dst_node_types[dnt], io_idx,
				HPI_CONTROL_VOLUME, h_control);
			if (!err)
				return 0;
		}
	}
	return err;
}

static int hpi_mixer_get_pcm_loopback_volume(hpi_handle_t h_mixer,
	uint16_t io_idx, hpi_handle_t *h_control)
{
	return hpi_mixer_get_control(h_mixer, HPI_SOURCENODE_OSTREAM, io_idx,
		HPI_DESTNODE_ISTREAM, io_idx, HPI_CONTROL_VOLUME, h_control);
}

static int hpi_mixer_get_mux(hpi_handle_t h_mixer,
	uint16_t io_idx, hpi_handle_t *h_control)
{
	int snt, err = -1;

	for (snt = 0; snt < ARRAY_SIZE(io_src_node_types); snt++) {
		err = hpi_mixer_get_control(h_mixer,
			io_src_node_types[snt], io_idx,
			0, 0, HPI_CONTROL_MULTIPLEXER, h_control);
		if (!err)
			break;
	}
	return err;
}

static inline int lookup_pcm_volume_and_call(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol,
				 int (*func)(struct snd_kcontrol *, struct snd_ctl_elem_value *))
{
	u32 h_control, is_input = kcontrol->private_value;
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	struct snd_kcontrol c = *kcontrol;
	int pcm_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
	int io_idx = asihpi->split_device_idx;
	int err = hpi_mixer_get_node_volume(asihpi->h_mixer, is_input,
		is_input ? HPI_DESTNODE_ISTREAM : HPI_SOURCENODE_OSTREAM,
		pcm_idx, io_idx,
		&h_control);
	if (err) {
		snd_printdd("%s volume ctrl between stream %d and I/O %d is not present. Skipped.",
			is_input ? "input" : "output", pcm_idx, io_idx);
		return -err;
	}

	c.private_value = h_control;
	return func(&c, ucontrol);
}

static int snd_asihpi_pcm_vol_info(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_info *uinfo)
{
	u32 h_control, is_input = kcontrol->private_value;
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	struct snd_kcontrol c = *kcontrol;
	int pcm_idx = snd_ctl_get_ioffidx(kcontrol, &uinfo->id);
	int io_idx = asihpi->split_device_idx;
	int err = hpi_mixer_get_node_volume(asihpi->h_mixer, is_input,
		is_input ? HPI_DESTNODE_ISTREAM : HPI_SOURCENODE_OSTREAM,
		pcm_idx, io_idx, &h_control);
        if (err) {
                snd_printdd("%s volume ctrl between stream %d and I/O %d is not present. Skipped.",
                        is_input ? "input" : "output", pcm_idx, io_idx);
                return -err;
        }

	c.private_value = h_control;
	return snd_asihpi_volume_info(&c, uinfo);
}

#define DEFINE_PCM_VOL_FUNC(name, delegate) \
	static int name(struct snd_kcontrol *kc, struct snd_ctl_elem_value *uc) \
		{ return lookup_pcm_volume_and_call(kc, uc, delegate); }

DEFINE_PCM_VOL_FUNC(snd_asihpi_pcm_vol_get, snd_asihpi_volume_get);
DEFINE_PCM_VOL_FUNC(snd_asihpi_pcm_vol_put, snd_asihpi_volume_put);

static struct snd_kcontrol_new snd_asihpi_ctl_pcm_vol_gain =
{
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
	.info = snd_asihpi_pcm_vol_info,
	.get = snd_asihpi_pcm_vol_get,
	.put = snd_asihpi_pcm_vol_put,
	.tlv = { .p = db_scale_100 },
};

DEFINE_PCM_VOL_FUNC(snd_asihpi_pcm_mute_get, snd_asihpi_volume_mute_get);
DEFINE_PCM_VOL_FUNC(snd_asihpi_pcm_mute_put, snd_asihpi_volume_mute_put);

static struct snd_kcontrol_new snd_asihpi_ctl_pcm_vol_mute = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.info = snd_asihpi_volume_mute_info,
	.get = snd_asihpi_pcm_mute_get,
	.put = snd_asihpi_pcm_mute_put
};

static int snd_asihpi_aesebu_status_info(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
	uinfo->count = 1;
	return 0;
}

static int snd_asihpi_aesebu_status_mask_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	ucontrol->value.iec958.status[0] = 0xff;
	return 0;
}

static struct snd_kcontrol_new snd_asihpi_ctl_aesebu_rxstatus_mask = {
	.access = SNDRV_CTL_ELEM_ACCESS_READ,
	.name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK),
	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
	.info =	snd_asihpi_aesebu_status_info,
	.get = snd_asihpi_aesebu_status_mask_get
};

static int snd_asihpi_aesebu_rxerrstatus_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol) {
	u32 h_control = kcontrol->private_value;
	u16 status;

	hpi_handle_error(hpi_aesebu_receiver_get_error_status(h_control, &status));
	ucontrol->value.iec958.status[0] = status;
	return 0;
}

static struct snd_kcontrol_new snd_asihpi_ctl_aesebu_rxstatus = {
	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE),
	.name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),
	.info = snd_asihpi_aesebu_status_info,
	.get = snd_asihpi_aesebu_rxerrstatus_get,
};

static int snd_asihpi_audio_src_info(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_info *uinfo)
{
	static const char *mux_audio_src_names[] = {"Line", "Digital"};
	return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(mux_audio_src_names),
		mux_audio_src_names);
}

static int snd_asihpi_audio_src_get(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	u32 h_control = kcontrol->private_value;
	u16 source_type, source_index;

	/* default to 'Line' */
	ucontrol->value.enumerated.item[0] = 0;

	hpi_handle_error(hpi_multiplexer_get_source(h_control,
				&source_type, &source_index));
	if (source_type == HPI_SOURCENODE_LINEIN) {
		ucontrol->value.enumerated.item[0] = 0;
	} else if (source_type == HPI_SOURCENODE_AESEBU_IN) {
		ucontrol->value.enumerated.item[0] = 1;
	} else {
		snd_printd(KERN_WARNING
			"Control %x unsupported source node type %hu %hu\n",
			h_control, source_type, source_index);
	}
	if (asihpi->split_device_idx != source_index) {
		snd_printd(KERN_WARNING
			"Control %x unexpected source node index %hu %hu\n",
			h_control, source_type, source_index);
	}
	return 0;
}

static int snd_asihpi_audio_src_put(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	int change = 1;
	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
	u32 h_control = kcontrol->private_value;
	u16 err = hpi_multiplexer_set_source(h_control,
		ucontrol->value.enumerated.item[0] ? HPI_SOURCENODE_AESEBU_IN : HPI_SOURCENODE_LINEIN,
		asihpi->split_device_idx);
	hpi_handle_error(err);
	return change;
}

static struct snd_kcontrol_new snd_asihpi_ctl_audio_src = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Capture Source",
	.info = snd_asihpi_audio_src_info,
	.get = snd_asihpi_audio_src_get,
	.put = snd_asihpi_audio_src_put,
};

static struct snd_kcontrol_new snd_asihpi_ctl_vol_gain =
{
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
	.info = snd_asihpi_volume_info,
	.get = snd_asihpi_volume_get,
	.put = snd_asihpi_volume_put,
	.tlv = { .p = db_scale_100 },
};

static struct snd_kcontrol_new snd_asihpi_ctl_vol_mute = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.info = snd_asihpi_volume_mute_info,
	.get = snd_asihpi_volume_mute_get,
	.put = snd_asihpi_volume_mute_put
};

static struct snd_kcontrol_new snd_asihpi_ctl_clock_type = {
	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
	.name = "Sample Clock Source",
	.info = snd_asihpi_clksrc_info,
	.get = snd_asihpi_clksrc_get,
	.put = snd_asihpi_clksrc_put,
};

static struct snd_kcontrol_new snd_asihpi_ctl_clock_rate = {
	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE),
	.name = "Sample Clock Rate",
	.info = snd_asihpi_clkrate_info,
	.get = snd_asihpi_clkrate_get,
	.put = snd_asihpi_clklocal_put,
};

static int snd_card_asihpi_splitmixer_new(int idx, struct snd_card_asihpi *asihpi)
{
	int err;
	u32 h_control = 0;
	struct snd_card *card;
	struct snd_kcontrol_new snd_ctl;

	if (snd_BUG_ON(!asihpi))
		return -EINVAL;

	card = asihpi->hpi->os.snd_card[idx];
	err = hpi_mixer_open(asihpi->hpi->index, &asihpi->h_mixer);
	hpi_handle_error(err);
	if (err)
		return -err;

	if (asihpi->outstreams) {
		/* analog out level */
		err = hpi_mixer_get_analog_control(asihpi->h_mixer, false, idx,
			HPI_CONTROL_LEVEL, &h_control);
		if (!err) {
			snd_ctl = snd_asihpi_ctl_analog_level;
			snd_ctl.name = "Line Out Playback Level";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
			/* if there's a volume on the same input then add it */
			err = hpi_mixer_get_analog_control(asihpi->h_mixer, false, idx,
				HPI_CONTROL_VOLUME, &h_control);
			if (!err) {
				snd_ctl = snd_asihpi_ctl_vol_gain;
				snd_ctl.name = "Line Out Playback Volume";
				snd_ctl.private_value = h_control;
				err = ctl_add(card, &snd_ctl, asihpi);
				if (err < 0)
					return err;
				snd_ctl = snd_asihpi_ctl_vol_mute;
				snd_ctl.name = "Line Out Playback Switch";
				snd_ctl.private_value = h_control;
				err = ctl_add(card, &snd_ctl, asihpi);
				if (err < 0)
					return err;
			}
		}

		/* no output mute switch */

		/* PCM playback volumes */
		snd_ctl = snd_asihpi_ctl_pcm_vol_gain;
		snd_ctl.name = "PCM Playback Volume";
		snd_ctl.count = asihpi->outstreams;
		snd_ctl.private_value = 0; /* 0 = playback */
		err = ctl_add(card, &snd_ctl, asihpi);
		if (err < 0)
			return err;

		/* PCM playback mute switch */
		snd_ctl = snd_asihpi_ctl_pcm_vol_mute;
		snd_ctl.name = "PCM Playback Switch";
		snd_ctl.count = asihpi->outstreams;
		snd_ctl.private_value = 0; /* 0 = playback */
		err = ctl_add(card, &snd_ctl, asihpi);
		if (err < 0)
			return err;

		/* AES/EBU transmitter get/set status operations are not supported */
	}

	if (asihpi->instreams) {
		/* analog in level */
		err = hpi_mixer_get_analog_control(asihpi->h_mixer, true, idx,
			HPI_CONTROL_LEVEL, &h_control);
		if (!err) {
			snd_ctl = snd_asihpi_ctl_analog_level;
			snd_ctl.name = "Line Capture Level";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}

		/* try looking up a volume on any physical input with this index */
		err = hpi_mixer_get_node_volume(asihpi->h_mixer, true, 0, 0, idx,
			&h_control);
		if (err) {
			/* if that fails lookup a volume between any physical input and the input stream */
			err = hpi_mixer_get_node_volume(asihpi->h_mixer, true,
				HPI_DESTNODE_ISTREAM, idx, idx, &h_control);
		}
		if (!err) {
			/* physical input volume */
			snd_ctl = snd_asihpi_ctl_vol_gain;
			snd_ctl.name = "Line Capture Volume";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
			/* physical input volume mute */
			snd_ctl = snd_asihpi_ctl_vol_mute;
			snd_ctl.name = "Line Capture Switch";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}

		/* Capture source */
		err = hpi_mixer_get_mux(asihpi->h_mixer, idx, &h_control);
		if (!err) {
			snd_ctl = snd_asihpi_ctl_audio_src;
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}

		err = hpi_mixer_get_control(asihpi->h_mixer,
			HPI_SOURCENODE_AESEBU_IN, idx,
			0, 0, HPI_CONTROL_AESEBU_RECEIVER, &h_control);
		if (!err) {
			/* AES/EBU status mask */
			snd_ctl = snd_asihpi_ctl_aesebu_rxstatus_mask;
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
			/* AES/EBU status */
			snd_ctl = snd_asihpi_ctl_aesebu_rxstatus;
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}
	}

	/* I/O and PCM monitoring */
	if (asihpi->outstreams && asihpi->instreams) {
		err = hpi_mixer_get_io_loopback_volume(asihpi->h_mixer, idx, &h_control);
		if (!err) {
			/* Monitoring volume */
			snd_ctl = snd_asihpi_ctl_vol_gain;
			snd_ctl.name = "Line Loopback Volume";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
			/* Monitoring mute switch */
			snd_ctl = snd_asihpi_ctl_vol_mute;
			snd_ctl.name = "Line Loopback Switch";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}
		err = hpi_mixer_get_pcm_loopback_volume(asihpi->h_mixer, idx, &h_control);
		if (!err) {
			/* Monitoring volume */
			snd_ctl = snd_asihpi_ctl_vol_gain;
			snd_ctl.name = "Digital Loopback Volume";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
			/* Monitoring mute switch */
			snd_ctl = snd_asihpi_ctl_vol_mute;
			snd_ctl.name = "Digital Loopback Switch";
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}
	}

	/* clock source controls present only on card 0 */
	if (idx == 0) {
		err = hpi_mixer_get_control(asihpi->h_mixer,
			HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
			HPI_CONTROL_SAMPLECLOCK, &h_control);
		if (!err) {
			snd_asihpi_clkcache_init(asihpi, h_control);
			/* Sample clock source */
			snd_ctl = snd_asihpi_ctl_clock_type;
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
			/* Sample clock rate */
			snd_ctl = snd_asihpi_ctl_clock_rate;
			snd_ctl.private_value = h_control;
			err = ctl_add(card, &snd_ctl, asihpi);
			if (err < 0)
				return err;
		}
	}
	return 0;
}


static int snd_card_asihpi_mixer_new(int device_index, struct snd_card_asihpi *asihpi)
{
	struct snd_card *card;
	unsigned int idx = 0;
	unsigned int subindex = 0;
	int err;
	struct hpi_control hpi_ctl, prev_ctl;

	if (snd_BUG_ON(!asihpi))
		return -EINVAL;
	card = asihpi->card;
	strcpy(card->mixername, "Asihpi Mixer");

	err =
	    hpi_mixer_open(asihpi->hpi->index,
			  &asihpi->h_mixer);
	hpi_handle_error(err);
	if (err)
		return -err;

	memset(&prev_ctl, 0, sizeof(prev_ctl));
	prev_ctl.control_type = -1;

	for (idx = 0; idx < 2000; idx++) {
		err = hpi_mixer_get_control_by_index(
				asihpi->h_mixer,
				idx,
				&hpi_ctl.src_node_type,
				&hpi_ctl.src_node_index,
				&hpi_ctl.dst_node_type,
				&hpi_ctl.dst_node_index,
				&hpi_ctl.control_type,
				&hpi_ctl.h_control);
		if (err) {
			if (err == HPI_ERROR_CONTROL_DISABLED) {
				if (mixer_dump)
					dev_info(&asihpi->pci->dev,
						   "Disabled HPI Control(%d)\n",
						   idx);
				continue;
			} else
				break;

		}

		hpi_ctl.src_node_type -= HPI_SOURCENODE_NONE;
		hpi_ctl.dst_node_type -= HPI_DESTNODE_NONE;

		/* ASI50xx in SSX mode has multiple meters on the same node.
		   Use subindex to create distinct ALSA controls
		   for any duplicated controls.
		*/
		if ((hpi_ctl.control_type == prev_ctl.control_type) &&
		    (hpi_ctl.src_node_type == prev_ctl.src_node_type) &&
		    (hpi_ctl.src_node_index == prev_ctl.src_node_index) &&
		    (hpi_ctl.dst_node_type == prev_ctl.dst_node_type) &&
		    (hpi_ctl.dst_node_index == prev_ctl.dst_node_index))
			subindex++;
		else
			subindex = 0;

		prev_ctl = hpi_ctl;

		switch (hpi_ctl.control_type) {
		case HPI_CONTROL_VOLUME:
			err = snd_asihpi_volume_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_LEVEL:
			err = snd_asihpi_level_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_MULTIPLEXER:
			err = snd_asihpi_mux_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_CHANNEL_MODE:
			err = snd_asihpi_cmode_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_METER:
			err = snd_asihpi_meter_add(asihpi, &hpi_ctl, subindex);
			break;
		case HPI_CONTROL_SAMPLECLOCK:
			snd_asihpi_clkcache_init(asihpi, hpi_ctl.h_control);
			err = snd_asihpi_sampleclock_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_CONNECTION:	/* ignore these */
			continue;
		case HPI_CONTROL_TUNER:
			err = snd_asihpi_tuner_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_AESEBU_TRANSMITTER:
			err = snd_asihpi_aesebu_tx_add(asihpi, &hpi_ctl);
			break;
		case HPI_CONTROL_AESEBU_RECEIVER:
			err = snd_asihpi_aesebu_rx_add(asihpi, &hpi_ctl);
			break;
		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)
				dev_info(&asihpi->pci->dev,
					"Untranslated HPI Control (%d) %d %d %d %d %d\n",
					idx,
					hpi_ctl.control_type,
					hpi_ctl.src_node_type,
					hpi_ctl.src_node_index,
					hpi_ctl.dst_node_type,
					hpi_ctl.dst_node_index);
			continue;
		}
		if (err < 0)
			return err;
	}
	if (HPI_ERROR_INVALID_OBJ_INDEX != err)
		hpi_handle_error(err);

	dev_info(&asihpi->pci->dev, "%d mixer controls found\n", idx);

	return 0;
}

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

static void
snd_asihpi_proc_read(struct snd_info_entry *entry,
			struct snd_info_buffer *buffer)
{
	struct snd_card_asihpi *asihpi = entry->private_data;
	u32 h_control;
	u32 rate = 0;
	u16 source = 0;

	u16 num_outstreams;
	u16 num_instreams;
	u16 version;
	u32 serial_number;
	u16 type;

	int err;

	snd_iprintf(buffer, "ASIHPI\n");

	hpi_handle_error(hpi_adapter_get_info(asihpi->hpi->index,
			&num_outstreams, &num_instreams,
			&version, &serial_number, &type));

	snd_iprintf(buffer,
			"Type: ASI%4X\nHardware Index: %d\n"
			"OutStreams: %d\nInStreams: %d\n",
			type, asihpi->hpi->index,
			num_outstreams, num_instreams);

	snd_iprintf(buffer,
		"Serial#: %d\nHardware version: %c%d\nDSP code version: %03d\n",
		serial_number, ((version >> 3) & 0xf) + 'A', version & 0x7,
		((version >> 13) * 100) + ((version >> 7) & 0x3f));

	snd_iprintf(buffer,
		"DMA: %d\nMRX: %d\nGROUP: %d\nUse IRQ: %d\nUpdate interval: %d\n",
		asihpi->can_dma, asihpi->support_mrx, asihpi->support_grouping,
		asihpi->use_interrupts, asihpi->update_interval_frames);

	err = hpi_mixer_get_control(asihpi->h_mixer,
				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
				  HPI_CONTROL_SAMPLECLOCK, &h_control);

	if (!err) {
		err = hpi_sample_clock_get_sample_rate(h_control, &rate);
		err += hpi_sample_clock_get_source(h_control, &source);

		if (!err)
			snd_iprintf(buffer, "Clock frequency: %d\nClock source: %s\n",
			rate, sampleclock_sources[source]);
	}
}

static void snd_asihpi_proc_init(struct snd_card_asihpi *asihpi)
{
	struct snd_info_entry *entry;

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

/*------------------------------------------------------------
   HWDEP
 ------------------------------------------------------------*/

static int snd_asihpi_hpi_open(struct snd_hwdep *hw, struct file *file)
{
	if (enable_hpi_hwdep)
		return 0;
	else
		return -ENODEV;

}

static int snd_asihpi_hpi_release(struct snd_hwdep *hw, struct file *file)
{
	if (enable_hpi_hwdep)
		return asihpi_hpi_release(file);
	else
		return -ENODEV;
}

static int snd_asihpi_hpi_ioctl(struct snd_hwdep *hw, struct file *file,
				unsigned int cmd, unsigned long arg)
{
	if (!enable_hpi_hwdep)
		return -ENODEV;

	return asihpi_hpi_ioctl(file, cmd, arg);
}

/* results in /dev/snd/hwC#D0 file for each card with index #
   also /proc/asound/hwdep will contain '#-00: asihpi (HPI) for each card'
*/
static int snd_asihpi_hpi_new(struct snd_card_asihpi *asihpi)
{
	struct snd_hwdep *hw;
	int err;

	err = snd_hwdep_new(asihpi->card, "HPI", 0, &hw);
	if (err < 0)
		return err;
	strcpy(hw->name, "asihpi (HPI)");
	hw->iface = SNDRV_HWDEP_IFACE_LAST;
	hw->ops.open = snd_asihpi_hpi_open;
	hw->ops.ioctl = snd_asihpi_hpi_ioctl;
	hw->ops.release = snd_asihpi_hpi_release;
	hw->private_data = asihpi;
	return 0;
}

/*------------------------------------------------------------
   CARD
 ------------------------------------------------------------*/
#if (HAVE_SND_CARD_NEW == 1)
#define snd_asihpi_card_new snd_card_new
#else
static int snd_asihpi_card_new(struct device *parent, int idx, const char *xid,
	struct module *module, int extra_size, struct snd_card **card_ret)
{
	int err = snd_card_create(idx, xid, THIS_MODULE, extra_size, card_ret);
	if (err >= 0)
		snd_card_set_dev(*card_ret, parent);
	return err;
}
#endif

static void snd_asihpi_adapter_info(struct hpi_adapter_obj *hpi, struct snd_card_asihpi *asihpi)
{
	int err;
	u32 h_stream;
	u16 u;
	u32 adapter_index = hpi->index;

	memset(asihpi, 0, sizeof(*asihpi));
	err = hpi_adapter_get_property(adapter_index,
		HPI_ADAPTER_PROPERTY_CAPS1, NULL, &u);
	asihpi->support_grouping = !err && !!u;

	err = hpi_adapter_get_property(adapter_index,
		HPI_ADAPTER_PROPERTY_CAPS2, &u, NULL);
	asihpi->support_mrx = !err && !!u;

	err = hpi_adapter_get_property(adapter_index,
		HPI_ADAPTER_PROPERTY_INTERVAL,
		NULL, &asihpi->update_interval_frames);
	if (err)
		asihpi->update_interval_frames = 512;

#if 0
	if (hpi->os.irq && (hpi->instreams < 2) && (hpi->outstreams < 2))
		asihpi->use_interrupts = true;
#endif

	asihpi->pcm_start = snd_card_asihpi_pcm_timer_start;
	asihpi->pcm_stop = snd_card_asihpi_pcm_timer_stop;

	hpi_handle_error(hpi_instream_open(adapter_index, 0, &h_stream));

	err = hpi_instream_host_buffer_free(h_stream);
	/*
		Recent HPI backends return HPI_ERROR_HOST_BUFFER_NOT_ALLOCATED when
		the buffer can be allocated but wasn't allocated at the time of the call.
	*/
	asihpi->can_dma = (!err) || (err == HPI_ERROR_HOST_BUFFER_NOT_ALLOCATED);

	hpi_handle_error(hpi_instream_close(h_stream));

	err = hpi_adapter_get_property(adapter_index,
		HPI_ADAPTER_PROPERTY_CURCHANNELS,
		&asihpi->in_max_chans, &asihpi->out_max_chans);
	if (err) {
		asihpi->in_max_chans = 2;
		asihpi->out_max_chans = 2;
	}

	if (asihpi->out_max_chans > 2) { /* assume LL mode */
		asihpi->out_min_chans = asihpi->out_max_chans;
		asihpi->in_min_chans = asihpi->in_max_chans;
		asihpi->support_grouping = 0;
	} else {
		asihpi->out_min_chans = 1;
		asihpi->in_min_chans = 1;
	}

	asihpi->instreams = hpi->instreams;
	asihpi->outstreams = hpi->outstreams;
}

/* Private version of snd_card_free() which accepts a NULL pointer
 *
 * Since this [1] commit (kernel >= 3.15) it isn't safe anymore to call snd_card_free() with a NULL pointer.
 * [1] <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/sound/core/init.c?h=v4.12-rc6&id=f24640648186b59bd39f8cd640d631bdb61a3197>
 */
static void snd_asihpi_card_free(struct snd_card *card)
{
	if (card) {
		snd_card_free(card);
	}
}

static int snd_asihpi_new_device(int idx, struct pci_dev *pci_dev,
		struct hpi_adapter_obj *hpi, struct snd_card_asihpi *base_asihpi,
		int (*mixer_new_func)(int, struct snd_card_asihpi *))
{
	int err;
	u32 h_control;
	struct snd_card_asihpi *asihpi;
	struct snd_card *card = NULL;
	char card_shortname[16];

	snprintf(card_shortname, sizeof(card_shortname), "ASI%4X-%i", hpi->type, idx);
	err = snd_asihpi_card_new(&pci_dev->dev, index[dev],
			id[dev] ? id[dev] : card_shortname, THIS_MODULE,
			sizeof(struct snd_card_asihpi), &hpi->os.snd_card[idx]);
	if (err < 0)
		return err;

	card = hpi->os.snd_card[idx];
	asihpi = card->private_data;
	/* set device settings from the underlying adapter */
	*asihpi = *base_asihpi;
	if (split_adapter) {
		/* each virtual device publishes a PCM for each available outstreams */
		/* each virtual device publishes a single PCM for each available instream */
		if (idx < asihpi->instreams) {
			asihpi->instreams = 1;
		} else {
			asihpi->instreams = 0;
		}
	}
	asihpi->split_device_idx = idx;
	asihpi->card = card;
	asihpi->pci = pci_dev;
	asihpi->hpi = hpi;

	err = snd_card_asihpi_pcm_new(asihpi, 0);
	if (err < 0) {
		dev_err(&pci_dev->dev, "pcm_new failed\n");
		goto __nodev;
	}
	err = mixer_new_func(idx, asihpi);
	if (err < 0) {
		dev_err(&pci_dev->dev, "mixer_new failed\n");
		goto __nodev;
	}

	err = hpi_mixer_get_control(asihpi->h_mixer,
				    HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
				    HPI_CONTROL_SAMPLECLOCK, &h_control);
	if (!err)
		err = hpi_sample_clock_set_local_rate(h_control, adapter_fs);

	strcpy(card->driver, "ASIHPI");
	strcpy(card->shortname, card_shortname);
	sprintf(card->longname, "%s %i", card->shortname, hpi->index);
	snd_asihpi_proc_init(asihpi);
	if (!idx) {
		/* always create, can be enabled or disabled dynamically
		    by enable_hwdep module param */
		snd_asihpi_hpi_new(card->private_data);
	}

	err = snd_card_register(card);

	if (!err) {
		dev++;
		return 0;
	}

__nodev:
	snd_asihpi_card_free(card);
	hpi->os.snd_card[idx] = NULL;
	dev_err(&pci_dev->dev, "snd_asihpi_probe error %d\n", err);
	return err;
}

static int snd_asihpi_probe(struct pci_dev *pci_dev,
			    const struct pci_device_id *pci_id)
{
	int err, idx;
	struct hpi_adapter_obj *hpi;
	struct snd_card_asihpi base_asihpi;

	if (dev >= SNDRV_CARDS)
		return -ENODEV;

	/* Should this be enable[hpi->index] ? */
	if (!enable[dev]) {
		dev++;
		return -ENOENT;
	}

	/* Initialise low-level HPI driver */
	err = asihpi_adapter_probe(pci_dev, pci_id);
	if (err < 0)
		return err;

	hpi = pci_get_drvdata(pci_dev);

	/* Stop interrupts */
	if (hpi->os.irq) {
		hpi_handle_error(hpi_adapter_set_property(hpi->index,
			HPI_ADAPTER_PROPERTY_IRQ_RATE, 0, 0));
		hpi->os.irq_thread = NULL;
	}

	/* gather information about the adapter */
	snd_asihpi_adapter_info(hpi, &base_asihpi);

	if (!base_asihpi.can_dma) {
		dev_err(&pci_dev->dev, "snd_asihpi_probe failed, non-BBM cards are no longer supported.\n");
		err = -ENODEV;
		goto __nodev;
	}

	hpi->os.num_cards = max(hpi->instreams, hpi->outstreams);
	hpi->os.num_cards = min_t(size_t, hpi->os.num_cards, ARRAY_SIZE(hpi->os.snd_card));

	if (split_adapter) {
		for (idx = 0; idx < hpi->os.num_cards; idx++) {
			err = snd_asihpi_new_device(idx, pci_dev, hpi, &base_asihpi,
				snd_card_asihpi_splitmixer_new);
			if (err < 0)
				goto __nodev;
		}
	} else {
		err = snd_asihpi_new_device(0, pci_dev, hpi, &base_asihpi,
			snd_card_asihpi_mixer_new);
		if (err < 0)
			goto __nodev;
	}
	return 0;

__nodev:
	/* free all registered devices */
	for (idx = 0; idx < hpi->os.num_cards; idx++) {
		/* it's safe to call snd_asihpi_card_free() with a NULL pointer */
		snd_asihpi_card_free(hpi->os.snd_card[idx]);
		hpi->os.snd_card[idx] = NULL;
	}
	dev_err(&pci_dev->dev, "snd_asihpi_probe error %d\n", err);
	return err;

}

static void snd_asihpi_remove(struct pci_dev *pci_dev)
{
	int idx;
	struct hpi_adapter_obj *hpi = pci_get_drvdata(pci_dev);

	/* Stop interrupts */
	if (hpi->os.irq) {
		hpi_handle_error(hpi_adapter_set_property(hpi->index,
			HPI_ADAPTER_PROPERTY_IRQ_RATE, 0, 0));
		hpi->os.irq_thread = NULL;
	}

	for (idx = 0; idx < hpi->os.num_cards; idx++) {
		snd_asihpi_card_free(hpi->os.snd_card[idx]);
		hpi->os.snd_card[idx] = NULL;
	}
	asihpi_adapter_remove(pci_dev);
}

static const struct pci_device_id asihpi_pci_tbl[] = {
	{PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DM8147,
		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
		(kernel_ulong_t)HPI6700_adapter_init},
	{PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205,
		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
		(kernel_ulong_t)HPI6205_adapter_init},
	{PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040,
		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
		(kernel_ulong_t)HPI6000_adapter_init},
	{0,}
};
MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl);

static struct pci_driver driver = {
	.name = KBUILD_MODNAME,
	.id_table = asihpi_pci_tbl,
	.probe = snd_asihpi_probe,
	.remove = snd_asihpi_remove,
};

static int __init snd_asihpi_init(void)
{
	asihpi_init();
	return pci_register_driver(&driver);
}

static void __exit snd_asihpi_exit(void)
{

	pci_unregister_driver(&driver);
	asihpi_exit();
}

module_init(snd_asihpi_init)
module_exit(snd_asihpi_exit)

