// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */

//------------------------------------------------------------------------------
//  Include Files
//------------------------------------------------------------------------------
#include "asm-generic/errno-base.h"
#include <linux/version.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <sound/soc.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/snd_mediatek.h>
#include <asm-generic/div64.h>

#include "audio_loopback.h"
#include "audio_loopback_dma.h"
#include "audio_mtk_dbg.h"
#include "linux/mod_devicetable.h"
#include "mtk-reserved-memory.h"
#include "clk-dtv.h"
//------------------------------------------------------------------------------
//  Defines
//------------------------------------------------------------------------------
#define CAP_SOURCE_IDX0         (0)
#define CAP_SOURCE_IDX1         (1)
#define CAP_SOURCE_IDX2         (2)
#define CAP_SOURCE_IDX3         (3)
//------------------------------------------------------------------------------
//  Macros
//------------------------------------------------------------------------------
#define SND_AUDIO_LOOPBACK_CARD "mtkaloopsndcard"
#define SND_AUDIO_LOOPBACK_DRIVER "mtk-audio-loopback"
#define SND_AUDIO_LOOPBACK_PCM_NAME "mtk-audio-loopback-platform"
#define SND_AUDIO_LOOPBACK_IRQ_NAME "mediatek,mtk-audio-loopback-platform"
#define SND_AUDIO_LOOPBACK_CAPTURE_CARD_NUM 1
#define SND_AUDIO_LOOPBACK_PLAYBACK_CARD_NUM 1
#define SND_AUDIO_LOOPBACK_DEVICE_NUM_MAX 1

#define SND_AUDIO_BITS_TO_BYTES(bits)      (bits >> 3)
#define SND_AUDIO_REG_READ_WORD(reg)       (readw(reg))

#define	SND_AUDIO_REG_WRITE_BYTE(reg, val)	(writeb(val, reg))
#define	SND_AUDIO_REG_WRITE_WORD(reg, val)	(writew(val, reg))

#define SND_AUDIO_DO_ALIGNMENT(val, alignment_size)			\
	(val = (val / alignment_size) * alignment_size)

#define	SND_AUDIO_REG_WRITE_MASK_BYTE(reg, mask, val)			\
	SND_AUDIO_REG_WRITE_BYTE(reg, (readb(reg) & ~(mask)) | (val & mask))

#define	SND_AUDIO_REG_WRITE_MASK_2BYTE(reg, mask, val)			\
	SND_AUDIO_REG_WRITE_WORD(reg, (readw(reg) & ~(mask)) | (val & mask))

#define MAX_LEN                (512)
#define EXPIRATION_TIME        (500)
#define DMA_XRUN               (-1)

#define CHANNEL_LAYOUT_2_0_0   (2)
#define CHANNEL_LAYOUT_5_1_2   (18)
#define CHANNEL_LAYOUT_MAX     (32)

//hardware parameter define
#define CH_2                   (2)
#define CH_4                   (4)
#define CH_8                   (8)
#define CH_12                  (12)
#define CH_14                  (14)

#define BITS_16                (16)
#define RATE_16000             (16000)
#define RATE_48000             (48000)

#define FORMAT_S16             (2)
#define FORMAT_S32             (4)

#define MAX_BUFFER_SIZE        (1024*1024)
#define MIN_PERIOD_BYTE        (64)
#define MAX_PERIOD_BYTE        (MAX_BUFFER_SIZE >> 2)

#define MIN_PERIOD_NUM         (2)
#define MAX_PERIOD_NUM         (150)

#define FIFO_SIZE              (0)
#define LINE_SIZE              (16)
#define ALIGN_SIZE             (4096)
#define BYTE_ALIGN_SIZE        (4)
#define FIFO_ALIGN_SIZE        (8)
#define MS_UNIT                (1000)
#define INTERVAL_UNIT          (256) //256 frames
#define PERIOD_NS_UNIT         (16000000)
#define DIGITAL_DELAY_NS_UNIT  (80000000)
#define TIMER_UNIT             (1)
#define TIMER_SHIFT            (2)

#define PERIOD_SHIFT           (2)
#define THRESHOLD_SHIFT        (3)

//delay unit
#define WAIT_1MS               (1)
#define WAIT_2MS               (2)
#define WAIT_5MS               (5)
#define WAIT_50US              (50)

//src parameter define
#define AEC_RATE               RATE_16000
//------------------------------------------------------------------------------
//  Variables
//------------------------------------------------------------------------------
//ALSA card related
static unsigned long mtk_log_level = LOGLEVEL_ERR;// LOGLEVEL_ERR LOGLEVEL_CRIT
static LIST_HEAD(audio_loopback_card_dev_list);
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
static int pcm_capture_substreams[SNDRV_CARDS] = {
	[0 ... (SNDRV_CARDS - 1)] = SND_AUDIO_LOOPBACK_CAPTURE_CARD_NUM
};
static int pcm_playback_substreams[SNDRV_CARDS] = {
	[0 ... (SNDRV_CARDS - 1)] = SND_AUDIO_LOOPBACK_PLAYBACK_CARD_NUM
};

module_param_array(pcm_capture_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_capture_substreams,
"PCM capture substreams # (1) for mediatek audio loopback driver.");
module_param_array(pcm_playback_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_playback_substreams,
"PCM playback substreams # (1) for mediatek audio loopback driver.");

static struct platform_device *device;
static struct timespec64 _stTimeStamp;
//------------------------------------------------------------------------------
//  Enumerate And Structure
//------------------------------------------------------------------------------
static unsigned int card_reg[LOOPBACK_REG_LEN] = {
	0x0,
	0x0,
	0x0,
	0x0,
	0x0,
	0x0,
	0x0,
};

static const char *device_name = { "Loopback" };

static const char * const aec_sigen_onoff_text[] = { "Off", "On" };
static const char * const pcm_slave_active_onoff_text[] = { "Off", "On" };
static const char * const pcm_capture_source_text[] = {
	"Speaker",
	"ADC2",
	"ADC1",
	"I2S RX 2CH",
	"I2S RX 4CH",
	"I2S RX 8CH",
	"Digital out",
	"Passthrough"};

static const char * const pcm_channel_layout_text[] = {
	"1.1.0", "1.0.0", "2.0.0", "2.1.0",
	"2.0.2", "2.1.2", "2.2.0", "3.0.0",
	"3.1.0", "3.0.2", "3.1.2", "4.0.0",
	"4.1.0", "4.0.2", "4.1.2", "5.0.0",
	"5.1.0", "5.0.2", "5.1.2", "2.0.4",
	"2.1.4", "3.0.4", "3.1.4", "3.2.0",
	"3.4.0", "3.2.2", "3.2.4", "4.0.4",
	"4.1.4", "5.0.4", "5.1.4", "7.1.0"};

static const struct soc_enum aec_sigen_onoff_enum =
SOC_ENUM_SINGLE(LOOPBACK_REG_AEC_SIGEN_ONOFF, 0, 2, aec_sigen_onoff_text);

static const struct soc_enum pcm_slave_active_onoff_enum =
SOC_ENUM_SINGLE(LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF, 0, 2, pcm_slave_active_onoff_text);

static const struct soc_enum pcm_capture_source_enum =
SOC_ENUM_SINGLE(LOOPBACK_REG_PCM_CAPTURE_SOURCE, 0, LOOPBACK_FROM_MAX, pcm_capture_source_text);

static const struct soc_enum pcm_channel_layout_enum =
SOC_ENUM_SINGLE(LOOPBACK_REG_PCM_CHANNEL_LAYOUT, 0, CHANNEL_LAYOUT_MAX, pcm_channel_layout_text);

static const unsigned int pcm_capture_source_mapping[LOOPBACK_FROM_MAX] = {
	AUDIO_LOOPBACK_DMA_SOURCE_SPK_DELAY_SE,
	AUDIO_LOOPBACK_DMA_SOURCE_ADC2,
	AUDIO_LOOPBACK_DMA_SOURCE_ADC1,
	AUDIO_LOOPBACK_DMA_SOURCE_I2S_RX,
	AUDIO_LOOPBACK_DMA_SOURCE_I2S_RX_4ch,
	AUDIO_LOOPBACK_DMA_SOURCE_I2S_RX_8ch,
	AUDIO_LOOPBACK_DMA_SOURCE_ENC_FRONT_END_8ch,
	AUDIO_LOOPBACK_DMA_SOURCE_DEC_SRC_OUT_8ch};

//PCM related
static const struct snd_pcm_hardware audio_loopback_pcm_hardware = {
	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER,
	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
	.rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000,
	.rate_min = RATE_16000,
	.rate_max = RATE_48000,
	.channels_min = CH_2,
	.channels_max = CH_14,
	.buffer_bytes_max = MAX_BUFFER_SIZE,
	.period_bytes_min = MIN_PERIOD_BYTE,
	.period_bytes_max = MAX_PERIOD_BYTE,
	.periods_min = MIN_PERIOD_NUM,
	.periods_max = MAX_PERIOD_NUM,
	.fifo_size = FIFO_SIZE,
};

/* Default hardware settings of ALSA PCM playback */
static struct snd_pcm_hardware audio_loopback_pcm_pb_hardware = {
	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER,
	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
	.rates = SNDRV_PCM_RATE_48000,
	.rate_min = RATE_48000,
	.rate_max = RATE_48000,
	.channels_min = CH_2,
	.channels_max = CH_14,
	.buffer_bytes_max = MAX_BUFFER_SIZE,
	.period_bytes_min = MIN_PERIOD_BYTE,
	.period_bytes_max = MAX_PERIOD_BYTE,
	.periods_min = MIN_PERIOD_NUM,
	.periods_max = MAX_PERIOD_NUM,
	.fifo_size = FIFO_SIZE,
};
//------------------------------------------------------------------------------
// Thread Function
//------------------------------------------------------------------------------
static void audio_loopback_delaytask_us(unsigned int u32Us)
{
/*
 * Refer to Documentation/timers/timers-howto.txt
 * Non-atomic context
 *		< 10us	:	udelay()
 *      10us ~ 20ms     :       usleep_range()
 *              > 10ms  :       msleep() or msleep_interruptible()
 */

	if (u32Us < 10UL)
		udelay(u32Us);
	else if (u32Us < 20UL * 1000UL)
		usleep_range(u32Us, u32Us + 1);
	else
		msleep_interruptible((unsigned int)(u32Us / 1000UL));
}

static void audio_loopback_delaytask(unsigned int u32Ms)
{
	audio_loopback_delaytask_us(u32Ms * 1000UL);
}

static int audio_loopback_trigger(void *arg)
{
	struct mtk_runtime_struct *dev_runtime = (struct mtk_runtime_struct *)arg;
	struct snd_pcm_substream *substream = NULL;

	if (dev_runtime == NULL) {
		audio_err("[%s]Invalid loopback runtime\n", __func__);
		return -EINVAL;
	}

	for (;;) {
		wait_event_interruptible(dev_runtime->kthread_waitq,
				kthread_should_stop() || dev_runtime->timer_notify);

		if (kthread_should_stop() ||  dev_runtime->timer_notify == TIMER_STOP_EVENT) {
			audio_info("[%s]Receive kthread_should_stop\n", __func__);
			break;
		}

		dev_runtime->timer_notify = TIMER_WAIT_EVENT;
		mutex_lock(&dev_runtime->kthread_lock);
		substream = dev_runtime->substream;
		if (substream == NULL) {
			mutex_unlock(&dev_runtime->kthread_lock);
			audio_warn("[%s]Invalid substream\n", __func__);
			continue;
		}
		snd_pcm_period_elapsed(substream);
		mutex_unlock(&dev_runtime->kthread_lock);
	}

	return 0;
}

//------------------------------------------------------------------------------
// Timer Function
//------------------------------------------------------------------------------
static void audio_loopback_timer_monitor(struct timer_list *t)
{
	struct mtk_runtime_struct *dev_runtime = NULL;
	struct mtk_monitor_struct *monitor = NULL;
	struct snd_pcm_substream *substream = NULL;
	struct snd_pcm_runtime *runtime = NULL;
	unsigned int expiration_counter = EXPIRATION_TIME;

	dev_runtime = from_timer(dev_runtime, t, timer);

	if (dev_runtime == NULL) {
		audio_err("[%s]Invalid parameters!\n", __func__);
		return;
	}

	monitor = &dev_runtime->monitor;

	if (monitor->monitor_status == CMD_STOP) {
		audio_info("[%s] No action is required, exit Monitor\n", __func__);
		spin_lock(&dev_runtime->spin_lock);
		memset(monitor, 0x00, sizeof(struct mtk_monitor_struct));
		spin_unlock(&dev_runtime->spin_lock);
		return;
	}

	substream = dev_runtime->substream;
	if (substream != NULL) {
		runtime = substream->runtime;
		if (runtime != NULL) {
			/* If nothing to do, increase "expiration_counter" */
			if ((monitor->last_appl_ptr == runtime->control->appl_ptr) &&
				(monitor->last_hw_ptr == runtime->status->hw_ptr)) {
				monitor->expiration_counter++;

				if (monitor->expiration_counter >= expiration_counter) {
					audio_warn("[%s] Expire, exit Monitor\n", __func__);
					spin_lock(&(dev_runtime->spin_lock));
					memset(monitor, 0x00, sizeof(struct mtk_monitor_struct));
					spin_unlock(&(dev_runtime->spin_lock));
					return;
				}
			} else {
				monitor->last_appl_ptr = runtime->control->appl_ptr;
				monitor->last_hw_ptr = runtime->status->hw_ptr;
				monitor->expiration_counter = 0;
			}

			dev_runtime->timer_notify = TIMER_TRIGGER_EVENT;
			wake_up_interruptible(&dev_runtime->kthread_waitq);
		}
		mod_timer(&(dev_runtime->timer),
			(jiffies + msecs_to_jiffies(TIMER_UNIT) / TIMER_SHIFT));
	}
}

//------------------------------------------------------------------------------
//  Function
//------------------------------------------------------------------------------
static void dma_irq_handler(void *priv)
{
	struct mtk_snd_loopback *loopback = (struct mtk_snd_loopback *)priv;

	if (loopback != NULL && loopback->loopback_aec_enabled == 0) {
		loopback->dev_runtime.timer_notify = TIMER_TRIGGER_EVENT;
		wake_up_interruptible(&loopback->dev_runtime.kthread_waitq);
	}
}

static void audio_loopback_capture_source_update(struct mtk_snd_loopback *loopback)
{
	if (loopback == NULL) {
		audio_err("[%s]Invalid loopback\n", __func__);
		return;
	}

	card_reg[LOOPBACK_REG_PCM_CAPTURE_SOURCE] = loopback->dsp_capture_source;
	card_reg[LOOPBACK_REG_PCM_SLAVE_FORMAT] = FORMAT_S16;
	card_reg[LOOPBACK_REG_PCM_SLAVE_RATE] = RATE_48000;
	card_reg[LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF] = 0;
	switch (loopback->dsp_capture_source) {
	case LOOPBACK_FROM_I2S_RX_2CH:
		card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = CH_2;
		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = loopback->i2s_input_layout;
		break;
	case LOOPBACK_FROM_I2S_RX_4CH:
		card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = CH_4;
		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = loopback->i2s_input_layout;
		break;
	case LOOPBACK_FROM_I2S_RX_8CH:
		card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = CH_8;
		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = loopback->i2s_input_layout;
		break;
	case LOOPBACK_FROM_DIGITAL_OUT_8CH:
		card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = CH_8;
		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = CHANNEL_LAYOUT_5_1_2;
		card_reg[LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF] = 1;
		break;
	default:
		card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = CH_2;
		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = CHANNEL_LAYOUT_2_0_0;
	}
}

static unsigned int audio_loopback_card_read(struct mtk_snd_loopback *loopback,
			unsigned int reg)
{
	if (loopback == NULL) {
		audio_err("[%s]Invalid loopback\n", __func__);
		return -EINVAL;
	}

	audio_info("%s: reg = 0x%x, val = 0x%x\n", __func__, reg, card_reg[reg]);
	return card_reg[reg];
}

static int audio_loopback_card_write(struct mtk_snd_loopback *loopback, unsigned int reg,
				unsigned int value)
{
	int ret = 0;
	struct snd_kcontrol *kctl = NULL;
	struct snd_pcm_substream *substream = NULL;
	struct snd_ctl_elem_id elem_id;

	if (loopback == NULL) {
		audio_err("[%s]Invalid loopback\n", __func__);
		return -EINVAL;
	}

	switch (reg) {
	case LOOPBACK_REG_AEC_SIGEN_ONOFF: {
		audio_info("%s: aec sigen reg: x%x, val = 0x%x\n",
			__func__, reg, value);
		ret = audio_loopback_dma_set_attr(AUDIO_LOOPBACK_DMA_ATTR_AEC_SINE_GEN, value);
		break;
	}
	case LOOPBACK_REG_PCM_SLAVE_FORMAT:
	case LOOPBACK_REG_PCM_SLAVE_RATE: {
		audio_info("%s: pcm slave reg: x%x, val = 0x%x\n",
			__func__, reg, value);
		if (loopback->dsp_mixing_mode) {
			audio_err("%s: format/rate not support without playback\n",
				__func__);
			ret = -1;
			break;
		}
		break;
	}
	case LOOPBACK_REG_PCM_SLAVE_CHANNELS: {
		audio_info("%s: pcm slave reg: x%x, val = 0x%x\n",
			__func__, reg, value);
		break;
	}
	case LOOPBACK_REG_PCM_CAPTURE_SOURCE: {
		audio_info("%s: pcm capture source reg: x%x, val = 0x%x\n",
			__func__, reg, value);
		if (value >= LOOPBACK_FROM_MAX) {
			ret = -1;
			break;
		}
		loopback->dsp_capture_source = value;
		audio_loopback_capture_source_update(loopback);

		substream = loopback->dev_runtime.substream;
		if (loopback->dsp_mixing_mode && loopback->loopback_pcm_running &&
			substream != NULL) {
			memset(&elem_id, 0, sizeof(elem_id));
			elem_id.numid = LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF;
			substream = loopback->dev_runtime.substream;
			kctl = snd_ctl_find_id(substream->pcm->card, &elem_id);
			if (kctl != NULL)
				snd_ctl_notify(loopback->card,
						SNDRV_CTL_EVENT_MASK_VALUE,
						&kctl->id);
		}
		break;
	}
	case LOOPBACK_REG_PCM_CHANNEL_LAYOUT: {
		audio_info("%s: pcm channel layout reg: x%x, val = 0x%x\n",
			__func__, reg, value);
		if (value >= CHANNEL_LAYOUT_MAX) {
			ret = -1;
			break;
		}

		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = value;

		substream = loopback->dev_runtime.substream;
		if (loopback->loopback_pcm_running && substream != NULL) {
			memset(&elem_id, 0, sizeof(elem_id));
			elem_id.numid = LOOPBACK_REG_PCM_CHANNEL_LAYOUT;
			substream = loopback->dev_runtime.substream;
			kctl = snd_ctl_find_id(substream->pcm->card, &elem_id);
			if (kctl != NULL)
				snd_ctl_notify(loopback->card,
						SNDRV_CTL_EVENT_MASK_VALUE,
						&kctl->id);
		}
		break;
	}
	default:
		audio_err("%s: error parameter, reg = 0x%x, val = 0x%x\n",
			__func__, reg, value);
		return -1;
	}

	if (ret) {
		audio_err("%s: update error , reg = 0x%x, val = 0x%x\n",
				__func__, reg, value);
		ret = -1;
	} else {
		audio_info("%s: reg = 0x%x, val = 0x%x\n", __func__, reg, value);
		card_reg[reg] = value;
		ret = 0;
	}

	return ret;
}

static int audio_loopbck_card_get_int(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_loopback *loopback = snd_kcontrol_chip(kcontrol);
	struct soc_mixer_control *mc =
		(struct soc_mixer_control *)kcontrol->private_value;

	ucontrol->value.integer.value[0] = audio_loopback_card_read(loopback, mc->reg);
	return 0;
}

static int audio_loopback_card_put_int(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_loopback *loopback = snd_kcontrol_chip(kcontrol);
	struct soc_mixer_control *mc =
	    (struct soc_mixer_control *)kcontrol->private_value;

	if ((ucontrol->value.integer.value[0] < 0)
		|| (ucontrol->value.integer.value[0] > mc->max)) {
		audio_info("reg:%d range MUST is (0->%d), %ld is overflow\r\n",
		       mc->reg, mc->max, ucontrol->value.integer.value[0]);
		return -1;
	}
	return audio_loopback_card_write(loopback, mc->reg,
					ucontrol->value.integer.value[0]);
}

static int audio_loopback_card_get_enum(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_loopback *loopback = snd_kcontrol_chip(kcontrol);
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;

	ucontrol->value.integer.value[0] = audio_loopback_card_read(loopback, e->reg);
	return 0;
}

static int audio_loopback_card_put_enum(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_loopback *loopback = snd_kcontrol_chip(kcontrol);
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
	unsigned int *item = ucontrol->value.enumerated.item;

	if (item[0] >= e->items)
		return -EINVAL;

	return audio_loopback_card_write(loopback, e->reg,
					ucontrol->value.integer.value[0]);
}

static const struct snd_kcontrol_new audio_loopback_snd_controls[] = {
	SOC_ENUM_EXT("AEC Sigen Switch", aec_sigen_onoff_enum,
			audio_loopback_card_get_enum, audio_loopback_card_put_enum),
	SOC_ENUM_EXT("PCM Slave Active", pcm_slave_active_onoff_enum,
			audio_loopback_card_get_enum, audio_loopback_card_put_enum),
	SOC_SINGLE_EXT("PCM Slave Format", LOOPBACK_REG_PCM_SLAVE_FORMAT, 0, FORMAT_S16, 0,
			audio_loopbck_card_get_int, audio_loopback_card_put_int),
	SOC_SINGLE_EXT("PCM Slave Rate", LOOPBACK_REG_PCM_SLAVE_RATE, 0, RATE_48000, 0,
			audio_loopbck_card_get_int, audio_loopback_card_put_int),
	SOC_SINGLE_EXT("PCM Slave Channels", LOOPBACK_REG_PCM_SLAVE_CHANNELS, 0, CH_12, 0,
			audio_loopbck_card_get_int, audio_loopback_card_put_int),
	SOC_ENUM_EXT("PCM Capture Source", pcm_capture_source_enum,
			audio_loopback_card_get_enum, audio_loopback_card_put_enum),
	SOC_ENUM_EXT("PCM Channel Layout", pcm_channel_layout_enum,
			audio_loopback_card_get_enum, audio_loopback_card_put_enum),
};

static int audio_loopback_card_mixer_new(struct mtk_snd_loopback *loopback)
{
	struct snd_card *card = loopback->card;
	struct snd_kcontrol *kcontrol;
	unsigned int idx;
	int err;

	audio_info("[%s][%d]\n", __func__, __LINE__);
	strscpy(card->mixername, "audio_loopback_snd_card", sizeof(card->mixername));
	for (idx = 0; idx < ARRAY_SIZE(audio_loopback_snd_controls); idx++) {
		kcontrol = snd_ctl_new1(&audio_loopback_snd_controls[idx], loopback);
		err = snd_ctl_add(card, kcontrol);
		if (err < 0)
			return err;
	}

	return 0;
}

static void audio_loopback_check_capture_support(struct mtk_snd_loopback *loopback,
		int sample_rate, int channels, int bits, int *aec_support, int *dsp_support)
{
	int i = 0;

	//initial support
	(*aec_support) = 0;
	(*dsp_support) = 0;

	//Check aec support
	if (loopback->dsp_mixing_mode && !loopback->loopback_aec_bypass &&
		channels <= loopback->aec_max_channel)
		(*aec_support) = 1; //Can enable aec

	//Check dsp support
	if (bits == BITS_16 && sample_rate == RATE_48000) {
		for (i = 0 ; i < loopback->dsp_channels_num; i++) {
			if (loopback->dsp_channel_list[i] == channels) {
				(*dsp_support) = 1;
				break;
			}
		}
	}
}

static int audio_loopback_get_copy_buffer(
	struct mtk_snd_loopback *loopback, struct audio_loopback_dma_struct **dma_buf,
	int is_playback, int is_nonblock, unsigned int request_size, unsigned char **buffer)
{
	struct mtk_runtime_struct *dev_runtime;
	unsigned int avail_size = 0;
	unsigned long flags;
	int monitor_status = 0;
	int timer_enable = 0;


	if (loopback == NULL) {
		audio_err("[%s]Err! Invalid Loopback\n", __func__);
		return -EINVAL;
	}

	if (dma_buf == NULL || buffer == NULL) {
		audio_err("[%s]Err! Invalid buffer/size\n", __func__);
		return -EINVAL;
	}

	if (is_playback) {
		(*dma_buf) = &loopback->dsp_pb_dma_buf;
		(*buffer) = loopback->playback_buf.addr;
		dev_runtime = (struct mtk_runtime_struct *)&loopback->dev_play_runtime;
	} else {
		if (loopback->loopback_aec_enabled)
			(*dma_buf) = &loopback->aec_dma_buf;
		else
			(*dma_buf) = &loopback->dsp_dma_buf;

		(*buffer) = loopback->capture_buf.addr;
		dev_runtime = (struct mtk_runtime_struct *)&loopback->dev_runtime;
	}

	if ((*buffer) == NULL) {
		audio_err("[%s]Err! need to allocate copy buffer\n", __func__);
		return -ENXIO;
	}

	avail_size = audio_loopback_dma_get_avail_size((*dma_buf));
	if ((is_nonblock > 0) && (request_size > avail_size)) {
		audio_err("[%s]Err! request_size = %u but avail_size = %u!!!\n",
		__func__, request_size, avail_size);
		return -EAGAIN;
	}

	/* Wake up Monitor if necessary */
	timer_enable = (loopback->dsp_mixing_mode ||
			loopback->loopback_pcm_playback_running ||
			loopback->loopback_pcm_sw_running);

	spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
	monitor_status = dev_runtime->monitor.monitor_status;
	spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);

	if (monitor_status == CMD_STOP && timer_enable) {
		spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
		dev_runtime->monitor.monitor_status = CMD_START;
		spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);

		mod_timer(&(dev_runtime->timer),
			(jiffies + msecs_to_jiffies(TIMER_UNIT) / TIMER_SHIFT));

		if (is_playback)
			audio_loopback_dma_set_attr(
				AUDIO_LOOPBACK_DMA_ATTR_SW_WRITE_TIMESTAMP_UPDATE, 0);
	}

	return 0;
}

static int audio_loopback_playback_status(struct mtk_snd_loopback *loopback, int cmd)
{
	struct mtk_runtime_struct *dev_runtime;
	unsigned long flags;
	int monitor_status = 0;
	int ret = 0;

	dev_runtime = (struct mtk_runtime_struct *)&loopback->dev_play_runtime;
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	case SNDRV_PCM_TRIGGER_SUSPEND:
		ret = audio_loopback_dma_stop(&loopback->dsp_pb_dma_buf);
		if (ret == 0) {
			spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
			dev_runtime->monitor.monitor_status = CMD_STOP;
			spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);
			if (loopback->loopback_pcm_sw_running)
				loopback->loopback_pcm_sw_running_stopped = 1;
			card_reg[LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF] = 0;
		}
		break;
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_RESUME:
		ret = audio_loopback_dma_start(&loopback->dsp_pb_dma_buf);
		if (ret == 0) {
			spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
			monitor_status = dev_runtime->monitor.monitor_status;
			spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);
			if (monitor_status == CMD_STOP) {
				spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
				dev_runtime->monitor.monitor_status = CMD_START;
				spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);

				mod_timer(&(dev_runtime->timer),
					(jiffies + msecs_to_jiffies(TIMER_UNIT) / TIMER_SHIFT));

				audio_loopback_dma_set_attr(
					AUDIO_LOOPBACK_DMA_ATTR_SW_WRITE_TIMESTAMP_UPDATE, 0);
			}
			card_reg[LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF] = 1;
		}
		break;
	default:
		audio_err("[%s]Invalid trigger cmd: %d\n", __func__, cmd);
		ret = -EINVAL;
		break;
	}
	return ret;
}

static int audio_loopback_capture_status(struct mtk_snd_loopback *loopback, int cmd)
{
	struct mtk_runtime_struct *dev_runtime;
	struct audio_loopback_dma_struct *dma_buf;
	unsigned long flags;
	int xrun_trigger = 0;
	int monitor_status = 0;
	int timer_enable = 0;
	int ret = 0;

	if (loopback->loopback_aec_enabled)
		dma_buf = &loopback->aec_dma_buf;
	else
		dma_buf = &loopback->dsp_dma_buf;

	dev_runtime = (struct mtk_runtime_struct *)&loopback->dev_runtime;
	timer_enable = (loopback->dsp_mixing_mode || loopback->loopback_pcm_sw_running);
	xrun_trigger = loopback->loopback_pcm_xrun;
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	case SNDRV_PCM_TRIGGER_SUSPEND:
		ret = audio_loopback_dma_stop(dma_buf);
		if ((ret == 0) && (xrun_trigger == 0)) {
			spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
			dev_runtime->monitor.monitor_status = CMD_STOP;
			spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);
		}
		break;
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_RESUME:
		ret = audio_loopback_dma_start(dma_buf);
		if (ret == 0) {
			spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
			monitor_status = dev_runtime->monitor.monitor_status;
			spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);
			if (monitor_status == CMD_STOP && timer_enable) {
				spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
				dev_runtime->monitor.monitor_status = CMD_START;
				spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);

				mod_timer(&(dev_runtime->timer),
					(jiffies + msecs_to_jiffies(TIMER_UNIT) / TIMER_SHIFT));
			}
			ktime_get_ts64(&_stTimeStamp);

			if (loopback->dsp_mixing_mode &&
				loopback->dsp_capture_source == LOOPBACK_FROM_DIGITAL_OUT_8CH)
				timespec64_add_ns(&_stTimeStamp, DIGITAL_DELAY_NS_UNIT);
		}
		break;
	default:
		audio_err("[%s]Invalid trigger cmd: %d\n", __func__, cmd);
		ret = -EINVAL;
		break;
	}

	return ret;
}

static int audio_loopback_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_loopback *loopback;
	struct snd_pcm *pcm;
	int playback = 0;
	int ret = 0;

	if (substream == NULL) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	loopback = (struct mtk_snd_loopback *)substream->private_data;
	runtime = (struct snd_pcm_runtime *)substream->runtime;
	if (loopback == NULL || runtime == NULL) {
		audio_err("[%s]Invalid loopback or runtime\n", __func__);
		return -EINVAL;
	}

	pcm = loopback->pcm;
	if (pcm == NULL) {
		audio_err("[%s]Invalid pcm\n", __func__);
		return -EINVAL;
	}

	/* Set specified information */
	playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
	if (playback == 1) {
		memcpy(&runtime->hw, &audio_loopback_pcm_pb_hardware,
					sizeof(struct snd_pcm_hardware));

		/* Allocate system memory for Specific Copy */
		loopback->playback_buf.addr = vmalloc(MAX_BUFFER_SIZE);
		if (loopback->playback_buf.addr == NULL)
			return -ENOMEM;

		memset((void *)loopback->playback_buf.addr, 0x00, MAX_BUFFER_SIZE);
		loopback->playback_buf.size = MAX_BUFFER_SIZE;

		loopback->dev_play_runtime.substream = substream;
		loopback->dev_play_runtime.timer_notify = TIMER_WAIT_EVENT;

		mutex_lock(&loopback->loopback_pcm_lock);

		loopback->loopback_pcm_playback_running = 1;
	} else {
		memcpy(&runtime->hw, &audio_loopback_pcm_hardware,
					sizeof(struct snd_pcm_hardware));

		if (!loopback->loopback_pcm_playback_running)
			runtime->hw.channels_max = loopback->dsp_max_channel;

		/* Allocate system memory for Specific Copy */
		loopback->capture_buf.addr = vmalloc(MAX_BUFFER_SIZE);
		if (loopback->capture_buf.addr == NULL)
			return -ENOMEM;

		memset((void *)loopback->capture_buf.addr, 0x00, MAX_BUFFER_SIZE);

		loopback->capture_buf.size = MAX_BUFFER_SIZE;
		loopback->dev_runtime.substream = substream;
		loopback->dev_runtime.timer_notify = TIMER_WAIT_EVENT;

		mutex_lock(&loopback->loopback_pcm_lock);

		//SW loopback case
		if (loopback->loopback_pcm_playback_running) {
			loopback->loopback_pcm_sw_running = 1;
			loopback->loopback_pcm_sw_running_stopped = 0;
		} else {
			loopback->loopback_pcm_sw_running = 0;
			loopback->loopback_pcm_sw_running_stopped = 0;
		}

		loopback->loopback_aec_enabled = 0;
		loopback->loopback_pcm_running = 1;
	}

	mutex_unlock(&loopback->loopback_pcm_lock);

	audio_info("%s: stream = %s, pcmC%dD%d (substream = %s)\n",
				__func__,
				(substream->stream ==
				SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"),
				pcm->card->number,
				pcm->device, substream->name
				);

	return ret;
}

static int audio_loopback_pcm_close(struct snd_pcm_substream *substream)
{
	struct mtk_snd_loopback *loopback;
	struct mtk_runtime_struct *dev_runtime;
	struct audio_loopback_dma_struct *dma_buf;
	unsigned long flags;
	int playback = 0;
	int ret = 0;

	audio_info("%s\n", __func__);
	if (substream == NULL) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	loopback = (struct mtk_snd_loopback *)substream->private_data;
	if (loopback == NULL) {
		audio_err("[%s]Invalid loopback\n", __func__);
		return -EINVAL;
	}

	playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
	dev_runtime = (playback == 1) ? (struct mtk_runtime_struct *)&loopback->dev_play_runtime :
		(struct mtk_runtime_struct *)&loopback->dev_runtime;

	mutex_lock(&loopback->loopback_pcm_lock);
	if (playback == 1) {
		loopback->loopback_pcm_sw_running_stopped = 1;
		ret = audio_loopback_dma_stop(&loopback->dsp_pb_dma_buf);
		if (ret < 0)
			audio_err("[%s]Stop dma failed:%d\n", __func__, ret);

		ret = audio_loopback_dma_exit(&loopback->dsp_pb_dma_buf);
		if (ret < 0)
			audio_err("[%s]Exit dma failed:%d\n", __func__, ret);

		if (loopback->playback_buf.addr != NULL) {
			vfree(loopback->playback_buf.addr);
			loopback->playback_buf.addr = NULL;
		}

		// Init default setting (i2s)
		card_reg[LOOPBACK_REG_PCM_SLAVE_FORMAT] = FORMAT_S16;
		card_reg[LOOPBACK_REG_PCM_SLAVE_RATE] = RATE_48000;
		card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = loopback->i2s_input_channel;
		card_reg[LOOPBACK_REG_PCM_CHANNEL_LAYOUT] = loopback->i2s_input_layout;

		loopback->loopback_pcm_playback_running = 0;
	} else {
		if (loopback->loopback_aec_enabled)
			dma_buf = &loopback->aec_dma_buf;
		else
			dma_buf = &loopback->dsp_dma_buf;

		ret = audio_loopback_dma_stop(dma_buf);
		if (ret < 0)
			audio_err("[%s]Stop dma failed:%d\n", __func__, ret);

		ret = audio_loopback_dma_exit(dma_buf);
		if (ret < 0)
			audio_err("[%s]Exit dma failed:%d\n", __func__, ret);

		if (loopback->capture_buf.addr != NULL) {
			vfree(loopback->capture_buf.addr);
			loopback->capture_buf.addr = NULL;
		}

		loopback->loopback_pcm_running = 0;
	}
	mutex_unlock(&loopback->loopback_pcm_lock);

	spin_lock_irqsave(&(dev_runtime->spin_lock), flags);
	memset(&(dev_runtime->monitor), 0x00, sizeof(struct mtk_monitor_struct));
	spin_unlock_irqrestore(&(dev_runtime->spin_lock), flags);

	mutex_lock(&dev_runtime->kthread_lock);
	dev_runtime->substream = NULL;
	mutex_unlock(&dev_runtime->kthread_lock);

	audio_debug("%s: %s close done\n", __func__,
			(substream->stream ==
			SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"));
	return ret;
}

static int audio_loopback_pcm_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *params)
{
	struct mtk_snd_loopback *loopback;
	int aec_support = 0;
	int dsp_support = 0;
	int ret = 0;

	if (substream == NULL || substream->private_data == NULL) {
		audio_err("[%s]Invalid substream or private date\n", __func__);
		return -EINVAL;
	}

	audio_debug("[%s]%s substream\n", __func__,
		(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "PLAYBACK" : "CAPTURE");
	audio_debug("[%s]params_channels     = %d\n", __func__, params_channels(params));
	audio_debug("[%s]params_rate         = %d\n", __func__, params_rate(params));
	audio_debug("[%s]params_period_size  = %d\n", __func__, params_period_size(params));
	audio_debug("[%s]params_periods      = %d\n", __func__, params_periods(params));
	audio_debug("[%s]params_buffer_size  = %d\n", __func__, params_buffer_size(params));
	audio_debug("[%s]params_buffer_bytes = %d\n", __func__, params_buffer_bytes(params));
	audio_debug("[%s]params_sample_width = %d\n", __func__,
			snd_pcm_format_physical_width(params_format(params)));
	audio_debug("[%s]params_access       = %d\n", __func__, params_access(params));
	audio_debug("[%s]params_format       = %d\n", __func__, params_format(params));
	audio_debug("[%s]params_subformat    = %d\n", __func__, params_subformat(params));

	loopback = (struct mtk_snd_loopback *)substream->private_data;
	audio_loopback_check_capture_support(loopback,
		params_rate(params), params_channels(params),
		snd_pcm_format_physical_width(params_format(params)),
		&aec_support, &dsp_support);
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		ret = audio_loopback_dma_set(&loopback->dsp_pb_dma_buf,
			AUDIO_LOOPBACK_SW_WRITE_DMA,
			params_channels(params),
			params_rate(params),
			snd_pcm_format_physical_width(params_format(params)),
			loopback->dsp_pb_mem_info,
			loopback->dsp_pb_dma_offset,
			loopback->dsp_pb_dma_size,
			loopback->playback_buf.addr,
			loopback->playback_buf.size);
	} else if (loopback->loopback_pcm_sw_running) {
		if (params_channels(params) != card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] ||
			params_rate(params) != card_reg[LOOPBACK_REG_PCM_SLAVE_RATE] ||
			params_format(params) != card_reg[LOOPBACK_REG_PCM_SLAVE_FORMAT]) {
			audio_warn("[%s]channel should match in sw loopback\n", __func__);
			return -EINVAL;
		}

		ret = audio_loopback_dma_set(&loopback->dsp_dma_buf,
			AUDIO_LOOPBACK_SW_READ_DMA,
			params_channels(params),
			params_rate(params),
			snd_pcm_format_physical_width(params_format(params)),
			loopback->dsp_pb_mem_info,
			loopback->dsp_pb_dma_offset,
			loopback->dsp_pb_dma_size,
			loopback->capture_buf.addr,
			loopback->capture_buf.size);
	} else if (aec_support) {
		loopback->loopback_aec_enabled = aec_support;
		ret = audio_loopback_dma_set(&loopback->aec_dma_buf,
			AUDIO_LOOPBACK_AEC_DMA,
			params_channels(params),
			params_rate(params),
			snd_pcm_format_physical_width(params_format(params)),
			loopback->aec_mem_info,
			loopback->aec_dma_offset,
			loopback->aec_dma_size,
			loopback->capture_buf.addr,
			loopback->capture_buf.size);
	} else if (dsp_support) {
		loopback->loopback_aec_enabled = 0;
		ret = audio_loopback_dma_set(&loopback->dsp_dma_buf,
			(loopback->dsp_mixing_mode) ?	AUDIO_LOOPBACK_DSP_DIRECT_DMA :
			AUDIO_LOOPBACK_DSP_I2S_DMA,
			params_channels(params),
			params_rate(params),
			snd_pcm_format_physical_width(params_format(params)),
			loopback->dsp_mem_info,
			loopback->dsp_dma_offset,
			loopback->dsp_dma_size,
			loopback->capture_buf.addr,
			loopback->capture_buf.size);
	} else {
		//For this case, if it is not dsp can accept, it should be return error
		return -EINVAL;
	}

	if (ret < 0) {
		audio_err("[%s]hw params dma set failed(%d)\n", __func__, ret);
		return ret;
	}

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		ret = audio_loopback_dma_init(&loopback->dsp_pb_dma_buf);
		if (ret < 0)
			audio_err("[%s]hw playback init dma failed(%d)\n", __func__, ret);
		else {
			card_reg[LOOPBACK_REG_PCM_SLAVE_FORMAT] = params_format(params);
			card_reg[LOOPBACK_REG_PCM_SLAVE_RATE] = params_rate(params);
			card_reg[LOOPBACK_REG_PCM_SLAVE_CHANNELS] = params_channels(params);
		}
	} else if (dsp_support && !loopback->dsp_mixing_mode)
		card_reg[LOOPBACK_REG_PCM_CAPTURE_SOURCE] = (params_channels(params) == CH_8) ?
			LOOPBACK_FROM_I2S_RX_8CH : LOOPBACK_FROM_I2S_RX_2CH;

	return ret;
}

static int audio_loopback_pcm_hw_free(struct snd_pcm_substream *substream)
{
	if (substream == NULL) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	audio_info("%s: stream = %s\n", __func__,
			(substream->stream ==
		SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"));

	return 0;
}

static int audio_loopback_pcm_prepare(struct snd_pcm_substream *substream)
{
	struct mtk_snd_loopback *loopback;
	struct audio_loopback_dma_struct *dma_buf;
	int ignore_irq = 0;
	int source = 0;
	int ret = 0;

	if ((substream == NULL) ||
		(substream->private_data == NULL)) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	loopback = (struct mtk_snd_loopback *)substream->private_data;
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		ret = audio_loopback_playback_status(loopback, SNDRV_PCM_TRIGGER_START);
	else {
		//dma init
		if (loopback->loopback_aec_enabled)
			dma_buf = &loopback->aec_dma_buf;
		else {
			dma_buf = &loopback->dsp_dma_buf;
			if (loopback->dsp_mixing_mode)
				ignore_irq = 1;
		}

		if (!loopback->loopback_pcm_sw_running) {
			source = pcm_capture_source_mapping[loopback->dsp_capture_source];
			if (loopback->slt_test_config)
				source = AUDIO_LOOPBACK_DMA_SOURCE_DBG_SINE_TONE;
			ret = audio_loopback_dma_set_attr(AUDIO_LOOPBACK_DMA_ATTR_SOURCE, source);
			if (ret < 0) {
				audio_err("[%s]Prepare capture source failed(%d)\n", __func__, ret);
				return -EFAULT;
			}

			ret = audio_loopback_dma_set_inturrupt_callback(dma_buf,
						dma_irq_handler, (void  *)loopback);
			if ((ret < 0) && (ignore_irq == 0)) {
				audio_err("[%s]Prepare request interrupt failed(%d)\n",
					__func__, ret);
				return -EFAULT;
			}
		}

		ret = audio_loopback_dma_init(dma_buf);
	}

	if (ret < 0) {
		audio_err("[%s]Prepare %s failed(%d)\n", __func__,
		((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "playback start" : "init"),
		ret);
		return -EFAULT;
	}

	audio_info("%s: stream = %s prepare\n", __func__,
			(substream->stream ==
			SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"));

	return 0;
}

static int audio_loopback_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct mtk_snd_loopback *loopback;
	struct snd_kcontrol *kctl = NULL;
	struct snd_ctl_elem_id elem_id;
	int playback = 0;
	int ret = 0;

	if (substream == NULL) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
	loopback = (struct mtk_snd_loopback *)substream->private_data;

	audio_info("%s : cmd(%d)\n", __func__, cmd);

	mutex_lock(&loopback->loopback_pcm_lock);
	if (playback)
		ret = audio_loopback_playback_status(loopback, cmd);
	else
		ret = audio_loopback_capture_status(loopback, cmd);
	mutex_unlock(&loopback->loopback_pcm_lock);

	if (playback == 1) {
		memset(&elem_id, 0, sizeof(elem_id));
		elem_id.numid = LOOPBACK_REG_PCM_SLAVE_ACTIVE_ONOFF;
		kctl = snd_ctl_find_id(substream->pcm->card, &elem_id);
		if (kctl != NULL)
			snd_ctl_notify(loopback->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
	}

	audio_info("%s : result(%d)\n", __func__, ret);
	return ret;
}

static snd_pcm_uframes_t audio_loopback_pcm_pointer(struct snd_pcm_substream *substream)
{
	struct mtk_snd_loopback *loopback;
	struct snd_pcm_runtime *runtime;
	snd_pcm_uframes_t offset_pcm_samples = 0;
	snd_pcm_uframes_t hw_ptr = 0;
	snd_pcm_uframes_t new_hw_ptr = 0;
	snd_pcm_uframes_t new_hw_ptr_pos = 0;
	int offset_bytes = 0;

	if (substream == NULL) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	loopback = (struct mtk_snd_loopback *)substream->private_data;
	runtime = (struct snd_pcm_runtime *)substream->runtime;
	if (loopback == NULL || runtime == NULL) {
		audio_err("[%s]Invalid loopback or runtime\n", __func__);
		return -EINVAL;
	}

	hw_ptr = runtime->status->hw_ptr;
	mutex_lock(&loopback->loopback_pcm_lock);
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		offset_bytes = audio_loopback_dma_get_offset(&loopback->dsp_pb_dma_buf);
		audio_loopback_dma_get_avail_size(&loopback->dsp_pb_dma_buf);
	} else  {
		if (loopback->loopback_aec_enabled) {
			offset_bytes = audio_loopback_dma_get_offset(&loopback->aec_dma_buf);
			audio_loopback_dma_get_avail_size(&loopback->aec_dma_buf);
		} else {
			offset_bytes = audio_loopback_dma_get_offset(&loopback->dsp_dma_buf);
			audio_loopback_dma_get_avail_size(&loopback->dsp_dma_buf);
		}

		if (offset_bytes <= 0) {
			loopback->loopback_pcm_xrun = (offset_bytes < 0) ? 1 : 0;
			mutex_unlock(&loopback->loopback_pcm_lock);
			goto _audio_loopback_pcm_pointer_update_pos;
		}

		loopback->pcm_capture_ts.tv_sec = _stTimeStamp.tv_sec;
		loopback->pcm_capture_ts.tv_nsec = _stTimeStamp.tv_nsec;
		timespec64_add_ns(&_stTimeStamp, PERIOD_NS_UNIT);
	}
	mutex_unlock(&loopback->loopback_pcm_lock);

_audio_loopback_pcm_pointer_update_pos:
	if (offset_bytes < 0) {
		audio_err("%s: stream = %s xrun\n", __func__,
			(substream->stream ==
			SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"));

		new_hw_ptr_pos = SNDRV_PCM_POS_XRUN;
	} else {
		offset_pcm_samples = bytes_to_frames(runtime, offset_bytes);
		new_hw_ptr = hw_ptr + offset_pcm_samples;
		new_hw_ptr_pos = new_hw_ptr % runtime->buffer_size;

		audio_info("%s: stream = %s hw_ptr = %u new_hw_ptr = %u offset = %u\n", __func__,
			(substream->stream ==
			SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE")
			, (unsigned int)hw_ptr
			, (unsigned int)new_hw_ptr
			, (unsigned int)new_hw_ptr_pos);
	}
	return new_hw_ptr_pos;
}

#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
static int audio_loopback_pcm_copy(struct snd_pcm_substream *substream,
			int channel, unsigned long pos,
			void __user *dst, unsigned long count)
#else
static int audio_loopback_pcm_copy(struct snd_pcm_substream *substream,
			int channel, unsigned long pos,
			struct iov_iter *dst, unsigned long count)
#endif
#else
static int audio_loopback_pcm_copy(struct snd_pcm_substream *substream,
			int channel, unsigned long pos,
			void __user *dst, unsigned long count)
#endif
{
	struct mtk_snd_loopback *loopback;
	struct audio_loopback_dma_struct *dma_buf;
	unsigned char *buffer_tmp = NULL;
	unsigned int period_size = 0;
	unsigned int request_size = 0;
	unsigned int size_to_copy = 0;
	unsigned int copied_size = 0;
	unsigned int size = 0;
	int retry_counter = 0;
	int ret = 0;

	if (substream == NULL || dst == NULL) {
		audio_err("[%s]Invalid parameter\n", __func__);
		return -EINVAL;
	}

	request_size = count;
	loopback = (struct mtk_snd_loopback *)substream->private_data;
	ret = audio_loopback_get_copy_buffer(loopback, &dma_buf,
			(substream->stream == SNDRV_PCM_STREAM_PLAYBACK),
			(substream->f_flags & O_NONBLOCK),
			request_size, &buffer_tmp);

	if (ret < 0)
		return ret;

	audio_info("%s: stream = %s\n", __func__,
			(substream->stream ==
			SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"));

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		period_size = loopback->dsp_pb_dma_size >> PERIOD_SHIFT;

		do {
			size_to_copy = request_size - copied_size;
			if (size_to_copy > period_size)
				size_to_copy = period_size;

#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
			ret = copy_from_user(buffer_tmp,
						(dst + copied_size), size_to_copy);
#else
			//iter will handle offset for non-copyed size
			ret = copy_from_iter_toio(buffer_tmp, dst, size_to_copy);
#endif
#else
			ret = copy_from_user(buffer_tmp,
						(dst + copied_size), size_to_copy);
#endif

			size_to_copy = size_to_copy - ret;

			audio_debug("[%s]%d byte not copied from.\n", __func__, ret);

			while (1) {
				size = audio_loopback_dma_write(dma_buf,
					(void *)buffer_tmp, size_to_copy);
				if (size == 0) {
					if ((++retry_counter) > EXPIRATION_TIME) {
						audio_err("[%s]Retry write\n", __func__);
						return -ENXIO;
					}
					audio_loopback_delaytask(WAIT_5MS);
				} else
					break;
			}

			copied_size += size;
		} while (copied_size < request_size);
	} else {
		do {
			period_size = request_size - copied_size;

			size = audio_loopback_dma_read(dma_buf,
					(void *)buffer_tmp, period_size);
			if (size > 0) {
#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
				ret = copy_to_user((dst + copied_size), buffer_tmp, size);
#else
				//iter will handle offset for non-copyed size
				ret = copy_to_iter_fromio(dst, buffer_tmp, size);
#endif
#else
				ret = copy_to_user((dst + copied_size), buffer_tmp, size);
#endif
				copied_size += (size - ret);

				audio_debug("[%s]%d byte not copied.\n", __func__, ret);
			}

			audio_debug("%s: size(%u), period(%u), copy(%u), request(%u)\n",
				__func__, size, period_size, copied_size, request_size);

		} while (copied_size < request_size);
	}

	return 0;
}

static int audio_loopback_pcm_get_time_info(struct snd_pcm_substream *substream,
			struct timespec64 *system_ts, struct timespec64 *audio_ts,
			struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
			struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
{
	struct mtk_snd_loopback *loopback;
	struct timespec64 systemts;

	if (substream == NULL) {
		audio_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}

	loopback = (struct mtk_snd_loopback *)substream->private_data;
	if (loopback == NULL) {
		audio_err("[%s]Invalid loopback\n", __func__);
		return -EINVAL;
	}

	ktime_get_ts64(&systemts);

	audio_ts->tv_sec = systemts.tv_sec;
	audio_ts->tv_nsec = systemts.tv_nsec;
	system_ts->tv_sec = loopback->pcm_capture_ts.tv_sec;
	system_ts->tv_nsec = loopback->pcm_capture_ts.tv_nsec;

	audio_info("%s: audio(%lld, %ld) system(%lld, %ld)\n",
		__func__,
		audio_ts->tv_sec, audio_ts->tv_nsec,
		system_ts->tv_sec, system_ts->tv_nsec);

	return 0;
}

static struct snd_pcm_ops audio_loopback_pcm_ops = {
	.open = audio_loopback_pcm_open,
	.close = audio_loopback_pcm_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = audio_loopback_pcm_hw_params,
	.hw_free = audio_loopback_pcm_hw_free,
	.prepare = audio_loopback_pcm_prepare,
	.trigger = audio_loopback_pcm_trigger,
	.pointer = audio_loopback_pcm_pointer,
#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
	.copy_user = audio_loopback_pcm_copy,
#else
	.copy = audio_loopback_pcm_copy,
#endif
#else
	.copy_user = audio_loopback_pcm_copy,
#endif
	.get_time_info = audio_loopback_pcm_get_time_info
};

///sys/devices/platform/audio-mcu.0/mtk_dbg//audio_test_cmd
static ssize_t slt_test_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct snd_card *card = dev_get_drvdata(dev);
	struct mtk_snd_loopback *loopback = (struct mtk_snd_loopback *)card->private_data;

	return scnprintf(buf, MAX_LEN, "%u\n", loopback->slt_test_config);
}

static ssize_t slt_test_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	struct snd_card *card = dev_get_drvdata(dev);
	struct mtk_snd_loopback *loopback = (struct mtk_snd_loopback *)card->private_data;
	long item = 0;
	int ret = 0;

	ret = kstrtol(buf, 10, &item);
	if (ret != 0)
		return ret;

	loopback->slt_test_config = (uint32_t)item;

	return count;
}
static DEVICE_ATTR_RW(slt_test);

///sys/devices/platform/audio-mcu.0/mtk_dbg/log_level
static ssize_t log_level_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return scnprintf(buf, MAX_LEN, "%lu\n", mtk_log_level);
}

static ssize_t log_level_store(struct device *dev,
				  struct device_attribute *attr,
				  const char *buf, size_t count)
{
	unsigned long level = 0;
	int ret;

	ret = kstrtoul(buf, 10, &level);

	if (ret != 0)
		return ret;
	if (level <= LOGLEVEL_DEBUG) {
		mtk_log_level = level;
		audio_loopback_dma_set_attr(AUDIO_LOOPBACK_DMA_ATTR_DBG_LEVEL, mtk_log_level);
	}
	return count;

}
static DEVICE_ATTR_RW(log_level);

///sys/devices/platform/audio-loopback-src.0/mtk_dbg/help
static ssize_t help_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return scnprintf(buf, MAX_LEN, "Debug Help:\n"
				"- log_level <RW>: To control debug log level.\n"
				"                  Read log_level value.\n"
				"- slt_test <RW>: To enable slt test mode.\n"
				"                  Enable: 1; Disable: 0\n");
}

static DEVICE_ATTR_RO(help);

static struct attribute *mtk_dtv_audio_loopback_attrs[] = {
	&dev_attr_slt_test.attr,
	&dev_attr_log_level.attr,
	&dev_attr_help.attr,
	NULL,
};

static const struct attribute_group mtk_dtv_audio_loopback_attr_group = {
	.name = "mtk_dbg",
	.attrs = mtk_dtv_audio_loopback_attrs
};

static int mtk_dtv_audio_loopback_create_sysfs_attr(struct platform_device *pdev)
{
	int ret = 0;

	/* Create device attribute files */
	audio_info("Create device attribute files\n");
	ret = sysfs_create_group(&pdev->dev.kobj, &mtk_dtv_audio_loopback_attr_group);
	if (ret)
		audio_err("[%d]Fail to create sysfs files: %d\n", __LINE__, ret);
	return ret;
}

static int mtk_dtv_audio_loopback_remove_sysfs_attr(struct platform_device *pdev)
{
	/* Remove device attribute files */
	audio_info("Remove device attribute files\n");
	sysfs_remove_group(&pdev->dev.kobj, &mtk_dtv_audio_loopback_attr_group);
	return 0;
}

static int audio_loopback_card_pcm_new(struct mtk_snd_loopback *loopback,
			    int device, int playback_substreams, int capture_substreams)
{
	struct device_node *np;
	struct snd_pcm *pcm;
	const char *name;
	int err;

	if (loopback == NULL) {
		audio_err("[%s]Invalid argument\n", __func__);
		return -EINVAL;
	}

	np = of_find_node_by_name(NULL, "mtk-audio-loopback");
	if (!np) {
		audio_err("[%s]Fail to find device node\n", __func__);
		return -ENODEV;
	}

	if (of_property_read_string(np, "loopback_name", &name))
		name = device_name;

	err = snd_pcm_new(loopback->card, name, device,
			  playback_substreams, capture_substreams, &pcm);

	if (err < 0) {
		audio_err("[%s][%d]Fail to create pcm instance err=%d\n"
			, __func__, __LINE__, err);
		return err;
	}

	if (playback_substreams)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_loopback_pcm_ops);

	if (capture_substreams)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &audio_loopback_pcm_ops);

	pcm->private_data = loopback;
	pcm->info_flags = 0;
	pcm->nonatomic = true;
	strlcpy(pcm->name, SND_AUDIO_LOOPBACK_PCM_NAME, sizeof(pcm->name));

	loopback->pcm = pcm;

	return err;
}

static unsigned int audio_loopback_get_dts_u32(struct device_node *np, const char *name)
{
	unsigned int value = 0;
	int ret;

	ret = of_property_read_u32(np, name, (u32 *)&value);
	if (ret)
		audio_warn("Can't get DTS property\n");

	return value;
}

static int audio_loopback_get_memory_info(u32 *addr)
{
	struct device_node *target_memory_np = NULL;
	uint32_t len = 0;
	__be32 *p = NULL;

	// find memory_info node in dts
	target_memory_np = of_find_node_by_name(NULL, "memory_info");
	if (!target_memory_np)
		return -ENODEV;

	// query cpu_emi0_base value from memory_info node
	p = (__be32 *)of_get_property(target_memory_np, "cpu_emi0_base", &len);
	if (p != NULL) {
		*addr = be32_to_cpup(p);
		of_node_put(target_memory_np);
		p = NULL;
	} else {
		audio_info("can not find cpu_emi0_base info\n");
		of_node_put(target_memory_np);
		return -EINVAL;
	}
	return 0;
}

static int audio_loopback_parse_memory(struct mtk_snd_loopback *loopback)
{
	struct of_mmap_info_data of_mmap_info = {0};
	struct device_node *loop_np, *dsp_np;
	uint64_t dma_dram_write_base = 0;
	int mixing_mode;
	int ret;
	u32 memory_bus_base = 0;

	if (!loopback)
		return -EINVAL;

	mixing_mode = loopback->dsp_mixing_mode;
	//audio loopback register from DMA and DSP
	loop_np = of_find_node_by_name(NULL, "mtk-audio-loopback");
	dsp_np = of_find_node_by_name(NULL, "mtk-sound-capture-platform");
	if (!loop_np || (!mixing_mode && !dsp_np))
		return -ENODEV;

	ret = of_property_read_u32(loop_np, "aec_dma_offset", &loopback->aec_dma_offset);
	ret |= of_property_read_u32(loop_np, "aec_dma_size", &loopback->aec_dma_size);
	if (mixing_mode) {
		ret |= of_property_read_u32(loop_np, "dsp_mix_dma_offset",
							&loopback->dsp_dma_offset);
		ret |= of_property_read_u32(loop_np, "dsp_mix_dma_size",
							&loopback->dsp_dma_size);
	} else {
		ret |= of_property_read_u32(dsp_np, "capture9_dma_offset",
							&loopback->dsp_dma_offset);
		ret |= of_property_read_u32(dsp_np, "capture9_dma_size",
							&loopback->dsp_dma_size);
	}
	if (ret) {
		audio_err("[%s]get dma info fail(%u,%u), (%d,%u,%u)\n", __func__,
			loopback->aec_dma_offset, loopback->aec_dma_size,
			mixing_mode, loopback->dsp_dma_offset, loopback->dsp_dma_size);
		return -EINVAL;
	}

	ret = audio_loopback_get_memory_info(&memory_bus_base);
	loopback->aec_mem_info.memory_bus_base = memory_bus_base;
	loopback->dsp_mem_info.memory_bus_base = memory_bus_base;

	if (ret) {
		audio_err("[%s]can't get miu bus base (%lu, %d,%lu)\n", __func__,
			loopback->aec_mem_info.memory_bus_base,
			mixing_mode, loopback->dsp_mem_info.memory_bus_base);
		return -EINVAL;
	}

	if (of_property_read_u64(loop_np, "dma-dram-write-base", &dma_dram_write_base)) {
		audio_err("[%s]can't get dma dram write base\n", __func__);
		return -EINVAL;
	}
	loopback->aec_mem_info.dma_dram_write_base = dma_dram_write_base;
	loopback->dsp_mem_info.dma_dram_write_base = 0;

	ret = of_mtk_get_reserved_memory_info_by_idx(loop_np, 0, &of_mmap_info);
	loopback->aec_mem_info.miu = of_mmap_info.miu;
	loopback->aec_mem_info.bus_addr = of_mmap_info.start_bus_address;
	loopback->aec_mem_info.buffer_size = of_mmap_info.buffer_size;

	if (mixing_mode) {
		ret |= of_mtk_get_reserved_memory_info_by_idx(loop_np, 1, &of_mmap_info);
		loopback->dsp_mem_info.miu = of_mmap_info.miu;
		loopback->dsp_mem_info.bus_addr = of_mmap_info.start_bus_address;
		loopback->dsp_mem_info.buffer_size = of_mmap_info.buffer_size;
	} else {
		ret |= of_mtk_get_reserved_memory_info_by_idx(dsp_np, 0, &of_mmap_info);
		loopback->dsp_mem_info.miu = of_mmap_info.miu;
		loopback->dsp_mem_info.bus_addr = of_mmap_info.start_bus_address;
		loopback->dsp_mem_info.buffer_size = of_mmap_info.buffer_size;
	}
	if (ret) {
		audio_err("[%s]get mmap fail(%u,%lu,%lu), (%d,%u,%lu,%lu)\n", __func__,
			loopback->aec_mem_info.miu,
			loopback->aec_mem_info.bus_addr,
			loopback->aec_mem_info.buffer_size,
			mixing_mode,
			loopback->dsp_mem_info.miu,
			loopback->dsp_mem_info.bus_addr,
			loopback->dsp_mem_info.buffer_size);
		return -EINVAL;
	}

	return ret;
}

static int audio_loopback_parse_capture_source(
	struct mtk_snd_loopback *loopback, const char *source)
{
	int capture_source = LOOPBACK_FROM_I2S_RX_2CH;

	if (loopback == NULL)
		return capture_source;

	if (source != NULL) {
		if (source[CAP_SOURCE_IDX0] == 'S' && source[CAP_SOURCE_IDX1] == 'W')
			capture_source = LOOPBACK_FROM_SPEAKER;
		else if (source[CAP_SOURCE_IDX0] == 'A' && source[CAP_SOURCE_IDX1] == 'D' &&
			source[CAP_SOURCE_IDX2] == 'C' && source[CAP_SOURCE_IDX3] == '2')
			capture_source = LOOPBACK_FROM_ADC2;
		else if (source[CAP_SOURCE_IDX0] == 'A' && source[CAP_SOURCE_IDX1] == 'D' &&
			source[CAP_SOURCE_IDX2] == 'C' && source[CAP_SOURCE_IDX3] == '1')
			capture_source = LOOPBACK_FROM_ADC1;
	}

	if (capture_source == LOOPBACK_FROM_I2S_RX_2CH) {
		switch (loopback->i2s_input_channel) {
		case CH_2:
			capture_source = LOOPBACK_FROM_I2S_RX_2CH;
			break;
		case CH_4:
			capture_source = LOOPBACK_FROM_I2S_RX_4CH;
			break;
		case CH_8:
			capture_source = LOOPBACK_FROM_I2S_RX_8CH;
			break;
		default:
			audio_warn("[%s] channel is not match, use default(%d)!\n",
				__func__, loopback->i2s_input_channel);
			loopback->i2s_input_channel = CH_2;
			capture_source = LOOPBACK_FROM_I2S_RX_2CH;
		}
	}

	return capture_source;
}

static int audio_loopback_card_machine_init(
	struct platform_device *pdev, struct mtk_snd_loopback *loopback)
{
	struct device_node *loop_np, *dsp_np;
	struct device_node *soc_np;
	struct device_node *voc_np, *voc_loop_np;
	const char *cap_source = NULL;
	int mixing_mode = 0;
	int aec_bypass = 0;
	int aec_regen_bypass = 0;
	int ret = 0;

	if (!loopback)
		return -EINVAL;

	mixing_mode = loopback->dsp_mixing_mode;
	//audio capture platform from DSP
	loop_np = of_find_node_by_name(NULL, "mtk-audio-loopback");
	dsp_np = of_find_node_by_name(NULL, "mtk-sound-capture-platform");
	soc_np = of_find_node_by_name(NULL, "mtk-sound-soc");
	voc_np = of_find_node_by_name(NULL, "voc-audio");
	if (!loop_np || (!mixing_mode && (!dsp_np | !soc_np)))
		return -ENODEV;

	ret = of_property_read_u32(loop_np, "aec_bypass",
			(u32 *)&aec_bypass);
	ret |= of_property_read_u32(loop_np, "aec_regen_bypass",
			(u32 *)&aec_regen_bypass);
	ret |= of_property_read_u32(loop_np, "aec_max_channel",
			(u32 *)&loopback->aec_max_channel);
	ret |= of_property_read_u32(loop_np, "dsp_channels_num",
			(u32 *)&loopback->dsp_channels_num);
	ret |= of_property_read_u32(loop_np, "i2s_input_channel",
			(u32 *)&loopback->i2s_input_channel);
	ret |= of_property_read_u32(loop_np, "i2s_input_layout",
			(u32 *)&loopback->i2s_input_layout);
	if (ret)
		return -EINVAL;

	loopback->dsp_channel_list = devm_kzalloc(&pdev->dev,
		sizeof(int) * loopback->dsp_channels_num, GFP_KERNEL);
	if (!loopback->dsp_channel_list)
		return -ENOMEM;

	ret = of_property_read_u32_array(loop_np, "dsp_channel_list",
			(u32 *)loopback->dsp_channel_list, loopback->dsp_channels_num);
	if (ret)
		return -EINVAL;

	if (voc_np) {
		voc_loop_np = of_get_child_by_name(voc_np, "loopback");
		if (voc_loop_np)
			ret = of_property_read_string(voc_loop_np, "source", &cap_source);
	}

	//update capture source and aec path
	loopback->dsp_capture_source = audio_loopback_parse_capture_source(loopback, cap_source);
	loopback->loopback_aec_bypass = (mixing_mode) ? aec_bypass : 1;

	return ret;
}

static int audio_loopback_card_module_init(
	struct platform_device *pdev, struct mtk_snd_loopback *loopback)
{
	int ret;
	int max_ch;
	int i;

	if (!pdev || !loopback) {
		audio_err("Invalid pointer\n");
		return -EINVAL;
	}

	// 1. memory initial
	ret = audio_loopback_parse_memory(loopback);
	if (ret < 0) {
		audio_err("Fail to parse memory:%d\n", ret);
		return ret;
	}

	// 2. MACHINE initial
	ret = audio_loopback_card_machine_init(pdev, loopback);
	if (ret < 0) {
		audio_err("Fail to init MACHINE:%d\n", ret);
		return ret;
	}

	// 3. DMA initial
	ret = audio_loopback_dma_probe();
	if (ret < 0) {
		audio_err("Fail to init dma:%d\n", ret);
		return ret;
	}

	// 3. other init (ex: mutex, timer valid)
	loopback->dsp_max_channel = CH_2;
	if (loopback->dsp_channels_num > 0) {
		max_ch = loopback->dsp_channel_list[0];
		for (i = 0 ; i < loopback->dsp_channels_num ; i++) {
			if (loopback->dsp_channel_list[i] > max_ch)
				max_ch = loopback->dsp_channel_list[i];
		}
		loopback->dsp_max_channel = max_ch;
	}
	mutex_init(&loopback->loopback_pcm_lock);
	mutex_init(&(loopback->dev_runtime.kthread_lock));
	mutex_init(&(loopback->dev_play_runtime.kthread_lock));
	spin_lock_init(&(loopback->dev_runtime.spin_lock));
	spin_lock_init(&(loopback->dev_play_runtime.spin_lock));
	init_waitqueue_head(&(loopback->dev_runtime.kthread_waitq));
	init_waitqueue_head(&(loopback->dev_play_runtime.kthread_waitq));

	// Timer setup
	timer_setup(&(loopback->dev_play_runtime.timer), audio_loopback_timer_monitor, 0);
	timer_setup(&(loopback->dev_runtime.timer), audio_loopback_timer_monitor, 0);
	loopback->dev_runtime.timer.expires = 0;
	loopback->dev_play_runtime.timer.expires = 0;

	/* Start playback thread */
	loopback->capture_task = kthread_run(audio_loopback_trigger,
		(void *)&loopback->dev_runtime, "audio loopback capture pcm trigger");
	if (IS_ERR(loopback->capture_task)) {
		audio_err("create capture thread for mtk loopback snd card failed!\n");
		return IS_ERR(loopback->capture_task);
	}

	if (!loopback->dsp_mixing_mode) {
		loopback->play_task = kthread_run(audio_loopback_trigger,
		(void *)&loopback->dev_play_runtime, "audio loopback playback pcm trigger");
		if (IS_ERR(loopback->play_task)) {
			audio_err("create playback thread for mtk loopback snd card failed!\n");
			return IS_ERR(loopback->play_task);
		}

		loopback->dsp_pb_dma_offset = loopback->aec_dma_offset;
		loopback->dsp_pb_dma_size = loopback->aec_dma_size;
		loopback->dsp_pb_mem_info.miu = loopback->aec_mem_info.miu;
		loopback->dsp_pb_mem_info.bus_addr = loopback->aec_mem_info.bus_addr;
		loopback->dsp_pb_mem_info.buffer_size = loopback->aec_mem_info.buffer_size;
		loopback->dsp_pb_mem_info.memory_bus_base =
				loopback->aec_mem_info.memory_bus_base;
	}

	// Init default setting (i2s)
	audio_loopback_capture_source_update(loopback);

	// add to mtk_snd_audio list
	INIT_LIST_HEAD(&loopback->list);
	list_add_tail(&loopback->list, &audio_loopback_card_dev_list);

	audio_info("[%s][%d]Initial success\n", __func__, __LINE__);
	return ret;
}

static int audio_loopback_card_probe(struct platform_device *pdev)
{
	struct snd_card *card;
	struct mtk_snd_loopback *loopback;
	struct device_node *np;
	uint16_t i = 0;
	int dev = pdev->id;
	int ret;

	//Should not PLATFORM_DEVID_NONE or PLATFORM_DEVID_AUTO, return not match
	if (dev < 0)
		return -ENODEV;

	np = of_find_compatible_node(NULL, NULL, "mediatek,mtk-audio-loopback");
	if (!of_device_is_available(np)) {
		audio_warn("[%s][%d]node status disable\n",
			__func__, __LINE__);
		ret = -ENODEV;
		return ret;
	}

	// Start to new card
	ret = snd_card_new(&pdev->dev, index[dev], id[dev],
		THIS_MODULE, sizeof(struct mtk_snd_loopback), &card);
	if (ret < 0) {
		audio_err("[%s][%d]Fail to create card error:%d\n",
			__func__, __LINE__, ret);
		return ret;
	}

	loopback = card->private_data;
	loopback->card = card;
	loopback->dsp_mixing_mode = audio_loopback_get_dts_u32(np, "dsp_mixing_mode");
	if (loopback->dsp_mixing_mode)
		pcm_playback_substreams[dev] = 0;

	// new pcm
	for (i = 0 ; i < SND_AUDIO_LOOPBACK_DEVICE_NUM_MAX ; i++) {
		ret = audio_loopback_card_pcm_new(loopback, i,
			pcm_playback_substreams[dev], pcm_capture_substreams[dev]);
		if (ret < 0)
			audio_err("[%s][%d]audio_card_pcm_new: %d fail: %d\n",
				__func__, __LINE__, i, ret);
	}

	audio_info("[%s][%d] pcm_substreams[%d] = %d %d\n", __func__, __LINE__,
		dev,
		pcm_playback_substreams[dev],
		pcm_capture_substreams[dev]);

	// new mixer
	ret = audio_loopback_card_mixer_new(loopback);
	if (ret < 0) {
		audio_err("[%s][%d]Fail to create mixer error:%d\n",
			__func__, __LINE__, ret);
		goto nodev;
	}

	strlcpy(card->driver, SND_AUDIO_LOOPBACK_CARD, sizeof(card->driver));
	strlcpy(card->shortname, SND_AUDIO_LOOPBACK_CARD, sizeof(card->shortname));

	ret = snprintf(card->longname, sizeof(card->longname), "%s %i",
			SND_AUDIO_LOOPBACK_CARD, dev + 1);
	if (ret < 0)
		audio_warn("[%s][%d]Warning to set long lanme:%d\n",
			__func__, __LINE__, ret);

	snd_card_set_dev(card, &pdev->dev);
	loopback->dev = &pdev->dev;

	ret = snd_card_register(card);
	if (!ret)
		platform_set_drvdata(pdev, card);

	ret = audio_loopback_card_module_init(pdev, loopback);
	if (ret < 0) {
		audio_err("[%s][%d]Fail to initial module:%d\n",
			__func__, __LINE__, ret);
		goto nodev;
	}

	mtk_dtv_audio_loopback_create_sysfs_attr(pdev);

	return 0;

nodev:
	snd_card_free(card);
	return ret;
}

static int audio_loopback_card_remove(struct platform_device *pdev)
{
	struct snd_card *card = platform_get_drvdata(pdev);
	struct mtk_snd_loopback *loopback = (struct mtk_snd_loopback *)card->private_data;

	//Stop timer
	del_timer_sync(&(loopback->dev_runtime.timer));
	del_timer_sync(&(loopback->dev_play_runtime.timer));

	//Stop thread
	loopback->dev_runtime.timer_notify = TIMER_STOP_EVENT;
	loopback->dev_play_runtime.timer_notify = TIMER_STOP_EVENT;
	wake_up_interruptible(&(loopback->dev_runtime.kthread_waitq));
	wake_up_interruptible(&(loopback->dev_play_runtime.kthread_waitq));

	list_del(&loopback->list);
	mutex_destroy(&loopback->loopback_pcm_lock);
	mutex_destroy(&(loopback->dev_runtime.kthread_lock));
	mutex_destroy(&(loopback->dev_play_runtime.kthread_lock));
	mtk_dtv_audio_loopback_remove_sysfs_attr(pdev);
	snd_card_free(platform_get_drvdata(pdev));
	platform_set_drvdata(pdev, NULL);
	audio_loopback_dma_destroy();
	return 0;
}

static struct platform_driver audio_loopback_card_driver = {
	.probe = audio_loopback_card_probe,
	.remove = audio_loopback_card_remove,
	.driver = {
			.name = SND_AUDIO_LOOPBACK_DRIVER,
			.owner = THIS_MODULE,
			},
};

static int __init audio_loopback_card_init(void)
{
	int err;

	err = platform_driver_register(&audio_loopback_card_driver);
	if (err < 0)
		return err;

	device = platform_device_register_simple(SND_AUDIO_LOOPBACK_DRIVER, 0, NULL, 0);

	return 0;
}

static void __exit audio_loopback_card_exit(void)
{
	audio_info("[%s]\n", __func__);
	if (device != NULL) {
		platform_device_unregister(device);
		device = NULL;
	}
	platform_driver_unregister(&audio_loopback_card_driver);
}

module_init(audio_loopback_card_init);
module_exit(audio_loopback_card_exit);

/* Module information */
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Audio ALSA Loopback driver");
