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

//------------------------------------------------------------------------------
//  Include Files
//------------------------------------------------------------------------------
#include <linux/version.h>
#include <linux/module.h>
#include <linux/moduleparam.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/string.h>
#include <linux/pinctrl/consumer.h> /* pinctrl API header*/
#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 <linux/input.h>
#include <asm-generic/div64.h>
#include <soc/mediatek/mtk-pm.h>  //pm_set_wakeup_config
#include "asm-generic/errno-base.h"
#include "linux/err.h"
#include "mtk-reserved-memory.h"

#include "voc_common.h"
#include "voc_common_reg.h"
#include "voc_mic_cap_iface.h"
#include "audio_loopback_dma.h"
//------------------------------------------------------------------------------
//  Macros
//------------------------------------------------------------------------------
#define SND_VOC_MERGED_DRIVER		"voc-merged-card"
#define SND_PCM_MERGED_NAME			"voc-merged-platform"
#define MAX_PCM_PLAYBACK_SUBSTREAMS		(0)
#define MAX_PCM_CAPTURE_SUBSTREAMS		(1)
#define DEFAULT_SUBSTREAMS			(8)
#define DEFAULT_CHANNELS			(2)
#define DEFAULT_BITWIDTHS			(16)
#define DEFAULT_RATES				(16000)
#define DEFAULT_PERIOD_MS			(16)
#define STR2LONG_BASE_DECIMAL		(10)
#define DMA_BIT_32					(32)

#define WAIT_TIME_MS   (1)
#define WAIT_TIMEOUT   (100)

#define OVERRUN_SAMPLE_RATE               48000 //16000
#define OVERRUN_DMA_BUFFER_PERIOD_MS      (64) //0.5s
#define OVERRUN_MS                        (OVERRUN_DMA_BUFFER_PERIOD_MS/4) //1/4 buffer size
#define UNDERRUN_MS                       (10) //10 ms

#define TASK_PCM_BUF_COUNT  ((DEFAULT_RATES * DEFAULT_CHANNELS * \
					DEFAULT_PERIOD_MS * BIT_TO_BYTE(DEFAULT_BITWIDTHS))/1000)

#define BIT_TO_BYTE(_x_)    ((_x_)/8)
#define KTIME_TO_US(_x_)    ((_x_)->tv_sec * VOICE_US_PER_SECOND + (_x_)->tv_nsec/VOICE_NS_PER_US)

/* sound card controls */
#define SOC_ENUM_VOC_MERGED(xname, xenum) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
	.info = snd_soc_info_enum_double, \
	.get = voc_merged_card_get_enum, .put = voc_merged_card_put_enum, \
	.private_value = (unsigned long)&xenum }

//------------------------------------------------------------------------------
//  Structure
//------------------------------------------------------------------------------
struct voc_merged_data {
	wait_queue_head_t kthread_waitq;
	struct task_struct *thread_task;
	struct voice_packet voice_msg;
	struct work_struct work_dma_status;
	struct timespec64 timestamp;
	struct mem_info aec_mem_info;
	struct audio_loopback_dma_struct aec_dma_buf;
	uint32_t aec_dma_offset;
	uint32_t aec_dma_size;
	uint64_t time_interval;
	int handler;
	int aec_channel;
	bool block_mode;
	bool dma_buf_wr_enable;
	bool timer_int_enable;
	bool overrun_trigger;
	bool full_trigger;
	bool extends_card;
};

//------------------------------------------------------------------------------
//  Variables
//------------------------------------------------------------------------------
//ALSA card related
unsigned long mtk_log_level = LOGLEVEL_ERR;
//unsigned long mtk_log_level = LOGLEVEL_INFO;

static unsigned int g_pcm_resume;
static unsigned int g_pcm_xrun;
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] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
static int pcm_capture_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = DEFAULT_SUBSTREAMS};
static int pcm_playback_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = DEFAULT_SUBSTREAMS};
static struct platform_device *devices[SNDRV_CARDS];
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for vod merged soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for voc merged soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable this voc merged soundcard.");
module_param_array(pcm_capture_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_capture_substreams, "PCM capture substreams # (1) for voc merged driver.");
module_param_array(pcm_playback_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_playback_substreams, "PCM playback substreams # (1) for voc merged driver.");

static unsigned int card_merged_reg[VOC_MERGED_REG_LEN] = {
	0x0,			//VOC_MERGED_REG_MIC_ONOFF
	0x0,			//VOC_MERGED_REG_REF_CHANNEL
};

static const char * const mic_onoff_text[] = { "Off", "On" };

static const struct soc_enum mic_onoff_enum =
SOC_ENUM_SINGLE(VOC_MERGED_REG_MIC_ONOFF, 0, 2, mic_onoff_text);

//PCM related
static const struct snd_pcm_hardware voc_merged_pcm_capture_hardware = {
	.info =
#if defined(CONFIG_MS_VOC_MMAP_SUPPORT)
		SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
#endif
		SNDRV_PCM_INFO_INTERLEAVED,

	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
	.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
		SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000,
	.rate_min = 8000,
	.rate_max = 48000,
	.channels_min = 2,
	.channels_max = 16,
	.buffer_bytes_max = 1512 * 1024,
	.period_bytes_min = 1 * 1024,
	.period_bytes_max = 64 * 1024,
	.periods_min = 4,
	.periods_max = 1512,	//32,
	.fifo_size = 0,
};

static struct voc_merged_data g_voc_merged_data;
static u64 voc_merged_pcm_dmamask = DMA_BIT_MASK(DMA_BIT_32);
static struct voc_pcm_dma_data voc_merged_pcm_dma_wr = {
	.name = "DMA writer",
	.channel = 0,
};

static uint8_t voc_merged_task_pcm_buf[TASK_PCM_BUF_COUNT];
//------------------------------------------------------------------------------
//  Function
//------------------------------------------------------------------------------
static int voc_merged_dma_update_level_cnt(struct mtk_snd_voc *voc,
	uint32_t period_num, uint32_t period_size)
{
	struct voc_pcm_data *pcm_data = voc->voc_pcm;
	uint64_t interval;

	if (pcm_data->pcm_cm4_period == 0) {
		pcm_data->pcm_cm4_period = period_size;
		//g_level_cnt = period_size; //1st frame, period num = 0
	}

	if (period_num <= pcm_data->pcm_last_period_num) {
		voc_err("wrap around last= %u, new %u\n",
		pcm_data->pcm_last_period_num, period_num);
		interval =
			(uint64_t)period_num + MAX_CM4_PERIOD - pcm_data->pcm_last_period_num;
	} else {
		interval = (period_num - pcm_data->pcm_last_period_num);
		//if(interval>1)
	}

	pcm_data->pcm_level_cnt += interval*pcm_data->pcm_cm4_period;

	/* xrun threshold */
	if (pcm_data->pcm_level_cnt
			> pcm_data->pcm_buf_size - pcm_data->pcm_cm4_period*FULL_THRES) {
		voc_err("%s XRUN\n", __func__);
		pcm_data->pcm_status = PCM_XRUN;
		pcm_data->pcm_level_cnt = 0;
		pcm_data->pcm_last_period_num = 0;
	}

	pcm_data->pcm_last_period_num = period_num;
	return pcm_data->pcm_level_cnt;
}

static int voc_merged_card_task_thread(void *param)
{
	struct mtk_snd_voc *voc = (struct mtk_snd_voc *)param;

	if (voc == NULL) {
		voc_err("%s Invalid voc structure\n", __func__);
		return 0;
	}

	for (;;) {

		wait_event_interruptible(voc->kthread_waitq,
						kthread_should_stop() || voc->vd_notify);

		if (kthread_should_stop()) {
			voc_err("%s Receive kthread_should_stop\n", __func__);
			break;
		}

		voc->task_state = true;

		if (voc->voc_pcm_running) {
			struct voice_packet *voice_msg =
				(struct voice_packet *)&g_voc_merged_data.voice_msg;
			struct snd_pcm_substream *substream = NULL;
			struct snd_pcm_runtime *runtime = NULL;
			struct voc_pcm_data *pcm_data = NULL;
			struct voc_pcm_runtime_data *prtd = NULL;
			uint32_t period_cnt, period_size, level_cnt;
			uint64_t time_interval;

			if (voc->voc_pcm == NULL) {
				voc_err("%s Invalid voc pcm structure\n", __func__);
				goto exit_thread;
			}

			substream =
				(struct snd_pcm_substream *)voc->voc_pcm->capture_stream;
			runtime = substream->runtime;
			pcm_data = runtime->private_data;

			if (pcm_data->prtd == NULL) {
				voc_err("%s Invalid runtime data structure\n", __func__);
				goto exit_thread;
			}
			prtd = pcm_data->prtd;

			period_cnt =
				((struct data_moving_notify *)voice_msg->payload)->period_count;
			period_size =
				((struct data_moving_notify *)voice_msg->payload)->byte_count;
			time_interval =
				((struct data_moving_notify *)voice_msg->payload)->time_interval;
			#if SHOW_BUSY_LOG
			voc_debug("period_cnt = %d, period_size = %d, time_interval = %llu us\n",
				period_cnt,
				period_size,
				time_interval);
			#endif
			mutex_lock(&voc->pcm_lock);
			level_cnt =
				voc_merged_dma_update_level_cnt(voc, period_cnt, period_size);
			voc->voc_pcm->pcm_time = time_interval;//us
			mutex_unlock(&voc->pcm_lock);

			voc_debug("updated level count %d\n", level_cnt);

			mutex_lock(&voc->pcm_lock);
			if (prtd->state != DMA_FULL) {
				pcm_data = voc->voc_pcm;
				if (pcm_data->pcm_status == PCM_XRUN) {
					prtd->state = DMA_FULL;
					/* Ensure that the DMA done */
					wmb();

					mutex_unlock(&voc->pcm_lock);
					snd_pcm_period_elapsed(substream);
					mutex_lock(&voc->pcm_lock);
				} else if ((prtd->state != DMA_OVERRUN) &&
					(pcm_data->pcm_level_cnt >= pcm_data->pcm_period_size)) {
					prtd->state = DMA_OVERRUN;
					/* Ensure that the DMA done */
					wmb();
					mutex_unlock(&voc->pcm_lock);
					snd_pcm_period_elapsed(substream);
					mutex_lock(&voc->pcm_lock);
				}
			}
			mutex_unlock(&voc->pcm_lock);
		}
		voc->vd_notify = 0;
		voc->task_state = false;
	}
exit_thread:
	return 0;
}

static int voc_merged_dma_read(uint32_t *period_cnt,
	uint32_t *period_size, uint64_t *time_interval)
{
	uint8_t *byte_buf;
	uint8_t channels;
	uint32_t bytes_of_bit_width;
	uint32_t sample_per_ms;
	uint32_t frame_count;
	uint32_t buf_size;
	uint32_t underrun_size;
	uint32_t rd_size;
	uint32_t ret_size;
	int32_t value;
	int ret = 0;

	ret = voc_mic_cap_iface_get_attr(MIC_CHANNELS, &value);
	if (ret < 0) {
		voc_err("%s Get channels error stop(%d)\n", __func__, ret);
		return ret;
	}
	channels = (uint8_t)value;

	ret = voc_mic_cap_iface_get_attr(MIC_BIT_WIDTH, &value);
	if (ret < 0) {
		voc_err("%s Get bitwidth error stop(%d)\n", __func__, ret);
		return ret;
	}
	bytes_of_bit_width = BIT_TO_BYTE(value);

	ret = voc_mic_cap_iface_get_attr(MIC_SAMPLE_RATE, &value);
	if (ret < 0) {
		voc_err("%s Get sample rate error stop(%d)\n", __func__, ret);
		return ret;
	}
	sample_per_ms = value / VOICE_MS_PER_SECOND;
	if ((sample_per_ms == 0) ||
		(bytes_of_bit_width == 0) ||
		(channels == 0)) {
		(*period_cnt) = 0;
		return -EAGAIN;
	}
	frame_count = sample_per_ms * DEFAULT_PERIOD_MS;
	buf_size = frame_count * bytes_of_bit_width * channels;
	underrun_size = sample_per_ms * bytes_of_bit_width * channels * UNDERRUN_MS;

	if (underrun_size > buf_size)
		underrun_size = buf_size;

	//read
	//If byte_buf is mapping mode, doesn't use but needs to set for coverity
	byte_buf = voc_merged_task_pcm_buf;
	ret_size = 0;
	do {
		if (g_voc_merged_data.dma_buf_wr_enable == 0) {
			(*period_cnt) = 0;
			return -EAGAIN;
		} else if ((voc_mic_cap_iface_get_level_count(g_voc_merged_data.handler) >
			underrun_size) || !g_voc_merged_data.block_mode) {
			rd_size = voc_mic_cap_iface_read_data(g_voc_merged_data.handler,
						(uint8_t *)byte_buf, buf_size);
			byte_buf += rd_size;
			buf_size -= rd_size;
			ret_size += rd_size;

			if ((g_voc_merged_data.block_mode && (buf_size == 0))
				|| !g_voc_merged_data.block_mode)
				break;
		} else
			usleep_range(VOICE_US_PER_MS, VOICE_US_PER_MS + 1);

	} while (1);

	//voc_info("time_interval:%llu\n",  time_interval);

	(*period_cnt)++;
	(*period_size) = ret_size;
	(*time_interval) = g_voc_merged_data.time_interval;
	g_voc_merged_data.time_interval += DEFAULT_PERIOD_MS * VOICE_US_PER_MS;
	return 0;
}

static int voc_merged_dma_handler_thread(void *param)
{
	struct mtk_snd_voc *voc = (struct mtk_snd_voc *)param;
	struct data_moving_notify *payload;
	uint32_t period_cnt = 0;
	uint32_t period_size = 0;
	uint64_t time_interval = 0;
	int ret = 0;

	UNUSED(param);
	payload = (struct data_moving_notify *)&g_voc_merged_data.voice_msg.payload;

	do {
		wait_event_interruptible(g_voc_merged_data.kthread_waitq,
			kthread_should_stop() || g_voc_merged_data.dma_buf_wr_enable);

		if (kthread_should_stop()) {
			voc_err("[%s]Receive kthread_should_stop\n", __func__);
			break;
		}

		ret = voc_merged_dma_read(
			&period_cnt, &period_size, &time_interval);

		if (ret < 0) {
			if (ret == -EAGAIN)
				continue;
			voc_err("[%s]Read error!(%d) stop\n", __func__, ret);
			break;
		}

		mutex_lock(&voc->pcm_lock);
		if (g_voc_merged_data.dma_buf_wr_enable) {
			payload->period_count = period_cnt;
			payload->byte_count = period_size;
			payload->time_interval = time_interval;

			voc->vd_notify = 1;
			wake_up_interruptible(&voc->kthread_waitq);
		} else {
			period_cnt = 0;
			voc_info("[%d] g_paganini.dma_buf_wr_enable = false\n", __LINE__);
		}
		mutex_unlock(&voc->pcm_lock);

	} while (1);

	return 0;
}
//------------------------------------------------------------------------------
static void voc_merged_card_shutdown(struct platform_device *pdev)
{
	int32_t value = 0;

	voc_info("%s, shutdown\n", __func__);

	if (g_voc_merged_data.extends_card == 0 &&
		voc_mic_cap_iface_set_attr(MIC_SWITCH, &value) < 0)
		voc_err("set mic switch off failed\n");
}

#ifdef CONFIG_PM_SLEEP
static int voc_merged_audio_dapm_suspend(struct device *dev)
{
	voc_info("%s, suspend\n", __func__);
	return 0;
}

static int voc_merged_audio_dapm_resume(struct device *dev)
{
	voc_info("%s, resume\n", __func__);
	g_pcm_resume = 1;
	return 0;
}

static const struct dev_pm_ops voc_merged_card_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(voc_merged_audio_dapm_suspend, voc_merged_audio_dapm_resume)
};
#endif

///sys/devices/platform/voc-card.0/mtk_dbg/event_log
static ssize_t event_log_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct snd_card *card = dev_get_drvdata(dev);
	struct mtk_snd_voc *voc = (struct mtk_snd_voc *)card->private_data;
	int count = 0, ret = 0;
	int i = 0;

	for (i = 0; i < voc->dbgInfo.writed; i++) {
		if (strlen(voc->dbgInfo.log[i].event) != 0) {
			ret = snprintf(buf+count, MAX_LEN, "%d:%lld.%.9ld, %s\n", i,
				(long long)voc->dbgInfo.log[i].tstamp.tv_sec,
				voc->dbgInfo.log[i].tstamp.tv_nsec,
				voc->dbgInfo.log[i].event);
			if (ret > 0)
				count += ret;
		}
	}
	return count;
}

static ssize_t event_log_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_voc *voc = (struct mtk_snd_voc *)card->private_data;
	unsigned long item = 0;
	int ret = 0;

	ret = kstrtol(buf, 10, &item);
	if (ret != 0)
		return ret;
	if (item > 0) { //clear log
		voc->dbgInfo.writed = 0;
		memset(voc->dbgInfo.log, 0, sizeof(struct operation_log) * MAX_LOG_LEN);
	}
	return count;
}
static DEVICE_ATTR_RW(event_log);

///sys/devices/platform/voc-card.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 <= 7)
		mtk_log_level = level;

	voc_mic_cap_iface_set_log_level(mtk_log_level);
	return count;

}
static DEVICE_ATTR_RW(log_level);

/*
 * Purpose: use this function to get result
 * HOW to test: set 1 run suspend function and get the result from show function.
 * set 2 run resume function and get the result from show function.
 * otherwise, the resule is cleared.
 * how to usage :
 * get result
 * (e.g. cat /sys/devices/platform/voc-card.0/mtk_dbg/suspend_resume_test)
 */
//sys/devices/platform/voc-card.0/mtk_dbg/suspend_resume_test
static ssize_t suspend_resume_test_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct snd_card *card = dev_get_drvdata(dev);
	struct mtk_snd_voc *voc = (struct mtk_snd_voc *)card->private_data;
	unsigned long tmp = 0;

	if ((voc->resume_duration_time.tv_nsec != 0) ||
		(voc->resume_duration_time.tv_sec != 0)) {
		tmp = voc->resume_duration_time.tv_sec * VOICE_NS_PER_SECOND;
		tmp += voc->resume_duration_time.tv_nsec;
	} else if ((voc->suspend_duration_time.tv_nsec != 0) ||
			(voc->suspend_duration_time.tv_sec != 0)) {
		tmp = voc->suspend_duration_time.tv_sec * VOICE_NS_PER_SECOND;
		tmp += voc->suspend_duration_time.tv_nsec;
	}
	return scnprintf(buf, MAX_LEN, "%lu\n", tmp);
}
/*
 * Purpose: use this function to detect the time spent on suspend and resume
 * HOW to test: set 1 run suspend function and get the result from show function.
 * set 2 run resume function and get the result from show function.
 * otherwise, the resule is cleared.
 * how to usage :
 * run suspend
 * (e.g. echo "1" > /sys/devices/platform/voc-card.0/mtk_dbg/suspend_resume_test)
 * run resume
 * (e.g. echo "2" > /sys/devices/platform/voc-card.0/mtk_dbg/suspend_resume_test)
 * clear
 * (e.g. echo "0" > /sys/devices/platform/voc-card.0/mtk_dbg/suspend_resume_test)
 */
static ssize_t suspend_resume_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_voc *voc = (struct mtk_snd_voc *)card->private_data;
	struct timespec64 t64begin, t64end;
	unsigned long item = 0;
	int ret = 0;

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

	voc->resume_duration_time.tv_sec = 0;
	voc->resume_duration_time.tv_nsec = 0;
	voc->suspend_duration_time.tv_sec = 0;
	voc->suspend_duration_time.tv_nsec = 0;
	if (item == 1) { // trigger test
		ktime_get_ts64(&t64begin);
		voc_merged_audio_dapm_suspend(dev);
		ktime_get_ts64(&t64end);
		voc->suspend_duration_time.tv_nsec = t64end.tv_nsec - t64begin.tv_nsec;
		voc->suspend_duration_time.tv_sec = t64end.tv_sec - t64begin.tv_sec;
	} else if (item == 2) {
		ktime_get_ts64(&t64begin);
		voc_merged_audio_dapm_resume(dev);
		ktime_get_ts64(&t64end);
		voc->resume_duration_time.tv_nsec = t64end.tv_nsec - t64begin.tv_nsec;
		voc->resume_duration_time.tv_sec = t64end.tv_sec - t64begin.tv_sec;
	}
	return count;
}
static DEVICE_ATTR_RW(suspend_resume_test);

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"
				"- event_log <RW>: dump voc card event (max : 30)\n"
				"                  Enter a number greater than 0 to clear the log\n"
				"- suspend_resume_test <RW>: write 1 to calculation suspend time. write 2 to calculation resume time.\n"
				"                  get result.\n");
}
static DEVICE_ATTR_RO(help);
static struct attribute *mtk_dtv_voc_merged_card_attrs[] = {
	&dev_attr_help.attr,
	&dev_attr_log_level.attr,
	&dev_attr_event_log.attr,
	&dev_attr_suspend_resume_test.attr,
	NULL,
};

static const struct attribute_group mtk_dtv_voc_merged_card_attr_group = {
	.name = "mtk_dbg",
	.attrs = mtk_dtv_voc_merged_card_attrs
};

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

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

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

static void voc_merged_dma_status_handler(struct work_struct *work)
{
	if (g_voc_merged_data.dma_buf_wr_enable) {
		if (g_voc_merged_data.full_trigger) {
			voc_info("[%s] full detected\n", __func__);
			voc_mic_cap_iface_stop_channel(g_voc_merged_data.handler);
			g_voc_merged_data.dma_buf_wr_enable = 0;
			voc_mic_cap_iface_clear_interrupt_status(g_voc_merged_data.handler,
				MIC_INT_FULL);
			//[cm4]voc_state = VOICE_STATE_XRUN;
		} else if (g_voc_merged_data.overrun_trigger) {
			//no need to handle overrun.
		}
	}
}

static void voc_merged_dma_irq_handler(struct mic_iface_intsts_t status)
{
	struct mic_iface_intclr_t ctrl;

	if (status.full || status.overrun) {
		memset(&ctrl, 0, sizeof(struct mic_iface_intclr_t));
		ctrl.mode |= g_voc_merged_data.timer_int_enable;
		ctrl.timer_flag = g_voc_merged_data.timer_int_enable;
		voc_mic_cap_iface_enable_interrupt(g_voc_merged_data.handler, ctrl);
		g_voc_merged_data.full_trigger = status.full;
		g_voc_merged_data.overrun_trigger = status.overrun;
		schedule_work(&g_voc_merged_data.work_dma_status);
	}

	if (status.timer)	{
		voc_mic_cap_iface_clear_interrupt_status(g_voc_merged_data.handler, MIC_INT_TIMER);
		timespec64_add_ns(&g_voc_merged_data.timestamp, DEFAULT_PERIOD_MS*VOICE_NS_PER_MS);
	}
}

static int voc_merged_dma_params_init(struct mtk_snd_voc *voc,
	struct snd_pcm_runtime *runtime, struct voc_pcm_data *pcm_data,
	u32 dma_buf_size, u32 period_size)
{
	void *pPAddr;
	void *pVAddr;
	int ret = 0;
	int32_t tmp_value;

	pcm_data->pcm_level_cnt = 0;
	pcm_data->pcm_last_period_num = 0;
	pcm_data->pcm_period_size = period_size;//frames_to_bytes(runtime, runtime->period_size);
	pcm_data->pcm_buf_size = dma_buf_size;//runtime->dma_bytes;
	pcm_data->pcm_cm4_period = 0;
	pcm_data->pcm_status = PCM_NORMAL;

	if (runtime->dma_addr > U32_MAX) {
		voc_err("[%s] only support 32bit address\n", __func__);
		return -EINVAL;
	}

	if (runtime->dma_addr < voc->miu_base) {
		voc_err("[%s] dma_paddr is small than miu_base\n", __func__);
		return -EINVAL;
	}
	pPAddr = (void *)(runtime->dma_addr - voc->miu_base);
	pVAddr = (void *)voc->pcm->streams[1].substream->dma_buffer.area;

	if (voc_mic_cap_iface_get_status(g_voc_merged_data.handler)
		== MIC_STATUS_STOPPED) {
		voc_mic_cap_iface_close(g_voc_merged_data.handler);
		g_voc_merged_data.handler = 0;
	}

	if (g_voc_merged_data.aec_channel > 0) {
		ret = audio_loopback_dma_set(&g_voc_merged_data.aec_dma_buf,
			AUDIO_LOOPBACK_MERGED_DMA,
			g_voc_merged_data.aec_channel,
			runtime->rate,
			snd_pcm_format_physical_width(runtime->format),
			g_voc_merged_data.aec_mem_info,
			g_voc_merged_data.aec_dma_offset,
			g_voc_merged_data.aec_dma_size,
			NULL, 0);

		ret = (ret == 0) ?
			audio_loopback_dma_init(&g_voc_merged_data.aec_dma_buf) : ret;

		if (ret < 0) {
			voc_err("Fail to init loopback dma(%d)\n", ret);
			return ret;
		}

		tmp_value = (int32_t)card_merged_reg[VOC_MERGED_REG_REF_CHANNEL];
		ret = voc_mic_cap_iface_set_attr(MIC_MERGED_CHANNEL, &tmp_value);
		if (ret < 0) {
			voc_err("set merged channel failed(%d)\n", ret);
			goto voc_merged_dma_params_init_failed;
		}
	}

	ret = voc_mic_cap_iface_open(
		runtime->rate,
		snd_pcm_format_physical_width(runtime->format),
		runtime->channels);
	if (ret < 0) {
		voc_err("open failed(%d)\n", ret);
		goto voc_merged_dma_params_init_failed;
	}
	g_voc_merged_data.handler = ret;

	ret = voc_mic_cap_iface_set_buf_addr(g_voc_merged_data.handler, 1,
		pPAddr, pVAddr, runtime->dma_bytes);
	if (ret < 0) {
		voc_err("set buffer address failed(%d)\n", ret);
		voc_mic_cap_iface_close(g_voc_merged_data.handler);
		g_voc_merged_data.handler = 0;
		goto voc_merged_dma_params_init_failed;
	}

	ret = voc_mic_cap_iface_register_interrupt_callback(g_voc_merged_data.handler,
				voc_merged_dma_irq_handler);
	if (ret < 0) {
		voc_err("register interrupt callback failed(%d)\n", ret);
		voc_mic_cap_iface_close(g_voc_merged_data.handler);
		g_voc_merged_data.handler = 0;
		goto voc_merged_dma_params_init_failed;
	}

	return 0;
voc_merged_dma_params_init_failed:
	if (g_voc_merged_data.aec_channel > 0)
		audio_loopback_dma_exit(&g_voc_merged_data.aec_dma_buf);

	return ret;
}

static int voc_merged_dma_start_channel(void)
{
	struct mic_iface_intclr_t ctrl;
	uint32_t overrun_thres;
	uint32_t sample_byte;
	uint8_t mic_num;
	int32_t value;
	int ret = 0;

	ret = voc_mic_cap_iface_get_attr(MIC_BIT_WIDTH, &value);
	if (ret < 0)
		return ret;
	sample_byte = BIT_TO_BYTE(value);

	ret = voc_mic_cap_iface_get_attr(MIC_CHANNELS, &value);
	if (ret < 0)
		return ret;
	mic_num = value;

	overrun_thres = (OVERRUN_SAMPLE_RATE *
					sample_byte *
					mic_num *
					OVERRUN_MS) / VOICE_MS_PER_SECOND;

	if (!g_voc_merged_data.dma_buf_wr_enable) {
		memset(&ctrl, 0, sizeof(struct mic_iface_intclr_t));
		g_voc_merged_data.timer_int_enable = 1;
		ctrl.mode = 1;
		ctrl.full_flag = 1;
		ctrl.timer_flag = 1;
		ctrl.overrun_threshold = overrun_thres;
		voc_mic_cap_iface_enable_interrupt(g_voc_merged_data.handler, ctrl);

		ret = voc_mic_cap_iface_start_channel(g_voc_merged_data.handler);
		if (g_voc_merged_data.aec_channel > 0)
			ret |= audio_loopback_dma_start(&g_voc_merged_data.aec_dma_buf);
		g_voc_merged_data.dma_buf_wr_enable = 1;
		ktime_get_ts64(&g_voc_merged_data.timestamp);
		g_voc_merged_data.time_interval = KTIME_TO_US(&g_voc_merged_data.timestamp);

		wake_up_interruptible(&g_voc_merged_data.kthread_waitq);
	}
	return ret;
}

static int voc_merged_dma_stop_channel(void)
{
	int ret = 0;
	struct mic_iface_intclr_t ctrl;

	if (g_voc_merged_data.dma_buf_wr_enable) {
		memset(&ctrl, 0, sizeof(struct mic_iface_intclr_t));
		voc_mic_cap_iface_enable_interrupt(g_voc_merged_data.handler, ctrl);
		ret = voc_mic_cap_iface_stop_channel(g_voc_merged_data.handler);
		if (g_voc_merged_data.aec_channel > 0)
			ret |= audio_loopback_dma_stop(&g_voc_merged_data.aec_dma_buf);
		g_voc_merged_data.dma_buf_wr_enable = 0;
	}
	//[cm4]voc_state = VOICE_STATE_STOP;

	return ret;

}

static int voc_merged_dma_reset_voice(void)
{
	int ret = 0;

	if (g_voc_merged_data.handler != 0) {
		ret = voc_mic_cap_iface_close(g_voc_merged_data.handler);
		g_voc_merged_data.handler = 0;
	}
	return ret;
}

//------------------------------------------------------------------------------
//  Voc PCM Function
//------------------------------------------------------------------------------
#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
int voc_merged_dma_copy_data(void *dst, const void *src, unsigned long count)
#else
int voc_merged_dma_copy_data(struct iov_iter *dst, const void *src, unsigned long count)
#endif
#else
int voc_merged_dma_copy_data(void *dst, const void *src, unsigned long count)
#endif
{
#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
	if (copy_to_user(dst, src, count))
		return -EFAULT;
#else
	if (copy_to_iter_fromio(dst, src, count))
		return -EFAULT;
#endif
#else
	if (copy_to_user(dst, src, count))
		return -EFAULT;
#endif
	return 0;
}


u32 voc_merged_dma_trig_level_cnt(struct voc_pcm_data *pcm_data, u32 data_size)
{
	int ret = 0;

	if (pcm_data->pcm_level_cnt >= data_size) {
		pcm_data->pcm_level_cnt -= data_size;
		ret = data_size;
	} else {
		voc_err("%s size %u too large %u\n", __func__
		, data_size, pcm_data->pcm_level_cnt);
	}
	return ret;
}

void voc_merged_pcm_write2Log(struct mtk_snd_voc *voc, char *str)
{
	if (voc->dbgInfo.writed < MAX_LOG_LEN) {
		if (scnprintf(voc->dbgInfo.log[voc->dbgInfo.writed].event,
								MAX_LEN, "%s", str) > 0) {
			ktime_get_ts64(&(voc->dbgInfo.log[voc->dbgInfo.writed].tstamp));
			voc->dbgInfo.writed++;
		} else {
			voc_err("snprintf fail, event: %s\n",
					voc->dbgInfo.log[voc->dbgInfo.writed].event);
		}
	}
}

static void voc_merged_pcm_runtime_free(struct snd_pcm_runtime *runtime)
{
	struct voc_pcm_data *pcm_data = runtime->private_data;

	kfree(pcm_data->prtd);
	kfree(pcm_data);
}

static int voc_merged_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct snd_pcm *pcm;
	struct voc_pcm_data *pcm_data = NULL;
	struct voc_pcm_runtime_data *prtd = NULL;
	int ret = 0;

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

	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm = voc->pcm;

	voc_debug("%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
			);

	pcm_data = kzalloc(sizeof(*pcm_data), GFP_KERNEL);
	if (!pcm_data) {
		ret = -ENOMEM;
		goto out;
	}

	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
	if (prtd == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	pcm_data->prtd = prtd;
	pcm_data->capture_stream = substream;	// for snd_pcm_period_elapsed
	pcm_data->instance = substream->number;

#if defined(CONFIG_MS_VOC_MMAP_SUPPORT)
	pcm_data->mmap_mode = 0;
#endif

	voc->voc_pcm = pcm_data;
	voc->sync_time = 0;
	runtime = substream->runtime;
	runtime->private_data = pcm_data;
	runtime->private_free = voc_merged_pcm_runtime_free;
	runtime->hw = voc_merged_pcm_capture_hardware;

	g_pcm_resume = 0;
	g_pcm_xrun = 0;
	g_voc_merged_data.aec_channel = card_merged_reg[VOC_MERGED_REG_REF_CHANNEL];

	voc_merged_pcm_write2Log(voc, "pcm open");

	return ret;

out:
	kfree(prtd);
	kfree(pcm_data);
	return ret;
}

static int voc_merged_pcm_close(struct snd_pcm_substream *substream)
{
	struct mtk_snd_voc *voc;
	int ret = 0;
	int result = 0;
	int retry_times = 0;

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

	voc = (struct mtk_snd_voc *)substream->private_data;

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

	if (g_voc_merged_data.aec_channel > 0) {
		result = audio_loopback_dma_exit(&g_voc_merged_data.aec_dma_buf);
		if (result < 0)
			voc_warn("[%s]Fail to loopback kdma failed = %d\n", __func__, result);
	}

	result = voc_merged_dma_reset_voice();
	if (result != 0)
		voc_warn("[%s]Fail to reset voice, result = %d\n", __func__, result);

	//Check VD merged task thread whether is stop
	do {
		if (voc->task_state == true) {
			msleep(WAIT_TIME_MS);
			retry_times++;

			if (retry_times >= WAIT_TIMEOUT) {
				voc_err("Wait timeout\n");
				ret = -EINVAL;
				break;
			}
			continue;
		} else {
			//vd task already finish
			break;
		}
	} while (1);

	if (retry_times > 0) {
		voc_warn("[%s][%d] Retry:%d, pcm_status:%d\n"
			, __func__, __LINE__, retry_times, voc->voc_pcm_status);
	}

	voc_merged_pcm_write2Log(voc, "pcm close");
	return ret;
}

/* this may get called several times by oss emulation */
//hw params: hw argument for user space, including
//channels, sample rate, pcm format, period size,
//period count. These are constrained by HW constraints.
static int voc_merged_pcm_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct voc_pcm_runtime_data *prtd;
	int32_t value;
	int ret = 0;
	unsigned int frame_bits;

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

	runtime = substream->runtime;
	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = substream->runtime->private_data;
	prtd = pcm_data->prtd;

	runtime->format = params_format(params);
	runtime->subformat = params_subformat(params);
	runtime->channels = params_channels(params);
	runtime->rate = params_rate(params);

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

	frame_bits =
		BIT_TO_BYTE(snd_pcm_format_physical_width(params_format(params))) *
		params_channels(params);

	voc_debug("frame_bits    = %d\n", frame_bits);

	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
	runtime->dma_bytes = params_buffer_bytes(params);

	if (prtd->dma_data) {
		voc_warn("[%s]dma_data already exist\n", __func__);
		return 0;
	}

	prtd->dma_data = &voc_merged_pcm_dma_wr;

	/*
	 * Link channel with itself so DMA doesn't need any
	 * reprogramming while looping the buffer
	 */
	voc_debug("dma name = %s, channel id = %lu\n",
			prtd->dma_data->name,
			prtd->dma_data->channel);

	voc_debug("dma buf vir addr = %p\n", runtime->dma_area);
	voc_debug("dma buf size = %zu\n", runtime->dma_bytes);

	// Re-set up the underrun and overrun
	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		ret = voc_mic_cap_iface_get_attr(MIC_SWITCH, &value);
		if (ret < 0 || value == 0) {
			voc_err("[%s][%d]Please switch MIC on before capture(%d)\n",
				__func__, __LINE__, ret);
			return -EINVAL;
		}

		ret = voc_merged_dma_params_init(voc, runtime, pcm_data,
				params_buffer_bytes(params),
				params_period_bytes(params));
		if (ret) {
			voc_err("[%s][%d]Fail to initial channel\n", __func__, __LINE__);
			return -EINVAL;
		}

		if (g_voc_merged_data.aec_channel > 0)
			voc_merged_pcm_write2Log(voc, "initial channel with merged aec");
		else
			voc_merged_pcm_write2Log(voc, "initial channel");
	}

	memset(runtime->dma_area, 0, runtime->dma_bytes);

	return ret;
}


static int voc_merged_pcm_hw_free(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct voc_pcm_runtime_data *prtd;

	if (substream == NULL) {
		voc_err("[%s]Invalid substream\n", __func__);
		return -EINVAL;
	}
	runtime = substream->runtime;
	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = runtime->private_data;
	prtd = pcm_data->prtd;

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

	if (prtd->dma_data == NULL) {
		voc_err("[%s][%d]Invalid dma_data\n", __func__, __LINE__);
		return 0;
	}

	//free_irq(VOC_IRQ_ID, (void *)substream);

	prtd->dma_data = NULL;
	snd_pcm_set_runtime_buffer(substream, NULL);

	voc_merged_pcm_write2Log(voc, "voc_pcm_hw_free");

	return 0;
}


static int voc_merged_pcm_prepare(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct voc_pcm_runtime_data *prtd;
	struct voc_pcm_dma_data *dma_data;
	int ret = 0;

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

	runtime = substream->runtime;
	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = runtime->private_data;
	prtd = pcm_data->prtd;
	dma_data = prtd->dma_data;

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

	voc_merged_pcm_write2Log(voc, "voc_pcm_prepare");

	return ret;
}


static int voc_merged_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct voc_pcm_runtime_data *prtd;
	int ret = 0;

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

	runtime = substream->runtime;
	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = substream->runtime->private_data;
	prtd = pcm_data->prtd;

	//voc_debug("%s: stream = %s, channel = %s, cmd = %d\n", __func__,
	//		(substream->stream ==
	//	SNDRV_PCM_STREAM_PLAYBACK ? "PLAYBACK" : "CAPTURE"),
	//		dma_data->name, cmd);

	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
		ret = -EINVAL;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		if (g_pcm_resume || g_pcm_xrun) {
			voc_merged_dma_params_init(voc, runtime, pcm_data, pcm_data->pcm_buf_size,
						pcm_data->pcm_period_size);
			g_pcm_resume = 0;
			g_pcm_xrun = 0;
		}

		mutex_lock(&voc->pcm_lock);

		voc->voc_pcm_running = 1;
		pcm_data->pcm_status = PCM_NORMAL;
		voc_merged_dma_start_channel();

		if (cmd == SNDRV_PCM_TRIGGER_START)
			voc_merged_pcm_write2Log(voc, "alsa trigger vd start");
		else if (cmd == SNDRV_PCM_TRIGGER_RESUME)
			voc_merged_pcm_write2Log(voc, "alsa trigger vd resume");
		else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
			voc_merged_pcm_write2Log(voc, "alsa trigger PAUSE_RELEASE:");

		mutex_unlock(&voc->pcm_lock);

		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:

		mutex_lock(&voc->pcm_lock);

		if (prtd->state == DMA_FULL || prtd->state == DMA_LOCALFULL)
			g_pcm_xrun = 1;

		memset(runtime->dma_area, 0, runtime->dma_bytes);
		prtd->dma_level_count = 0;
		prtd->state = DMA_EMPTY;


		/* Ensure that the DMA done */
		wmb();
		voc->voc_pcm_running = 0;
		voc_merged_dma_stop_channel();

		if (cmd == SNDRV_PCM_TRIGGER_STOP)
			voc_merged_pcm_write2Log(voc, "alsa trigger vd stop");
		else if (cmd == SNDRV_PCM_TRIGGER_SUSPEND)
			voc_merged_pcm_write2Log(voc, "alsa trigger vd suspend");
		else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
			voc_merged_pcm_write2Log(voc, "alsa trigger PAUSE_PUSH");

		mutex_unlock(&voc->pcm_lock);

		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}


static snd_pcm_uframes_t voc_merged_pcm_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct voc_pcm_runtime_data *prtd;
	snd_pcm_uframes_t offset = 0;
	size_t last_dma_count_level = 0;

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

	runtime = substream->runtime;
	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = substream->runtime->private_data;
	prtd = pcm_data->prtd;

	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {

		mutex_lock(&voc->pcm_lock);
		/* Ensure that the DMA done */
		rmb();

		if (prtd->state == DMA_FULL || prtd->state == DMA_LOCALFULL) {
			mutex_unlock(&voc->pcm_lock);
			voc_warn("BUFFER FULL : %d!!\n", prtd->state);
			return SNDRV_PCM_POS_XRUN;
		}

		//Update water level: point will call this function to
		//get current address of transmitted data every transmission,
		//then pcm native can calculate point address of dma buffer
		//and available space
		last_dma_count_level = pcm_data->pcm_level_cnt;
		if (prtd->dma_level_count > (runtime->dma_bytes << 1))
			prtd->dma_level_count -= runtime->dma_bytes;
		offset = bytes_to_frames(runtime,
				((prtd->dma_level_count +
					last_dma_count_level) %
					runtime->dma_bytes));

		mutex_unlock(&voc->pcm_lock);
	}
#if SHOW_BUSY_LOG
	voc_debug("%s: stream id = %d, channel id = %lu, frame offset = 0x%x\n",
			__func__, substream->stream, prtd->dma_data->channel,
			(unsigned int)offset);
#endif
	return offset;
}

#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
static int voc_merged_pcm_copy(struct snd_pcm_substream *substream,
			int channel, unsigned long pos,
			void __user *dst, unsigned long count)
#else
static int voc_merged_pcm_copy(struct snd_pcm_substream *substream,
			int channel, unsigned long pos,
			struct iov_iter *dst, unsigned long count)
#endif
#else
static int voc_merged_pcm_copy(struct snd_pcm_substream *substream,
			int channel, unsigned long pos,
			void __user *dst, unsigned long count)
#endif
{
	struct snd_pcm_runtime *runtime;
	struct mtk_snd_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct voc_pcm_runtime_data *prtd;
	unsigned long flags;
	unsigned char *hwbuf;
	unsigned int state;

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

	runtime = substream->runtime;
	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = substream->runtime->private_data;
	prtd = pcm_data->prtd;
	hwbuf = runtime->dma_area + pos;

	//voc_info("[%s]count:%ld, addr:0x%lx\n", __func__, count, (unsigned long)hwbuf);
	/* Ensure that the DMA done */
	rmb();
	state = prtd->state;

	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {

		/* Ensure that the DMA done */
		rmb();

		if (prtd->state == DMA_FULL) {
			voc_err("BUFFER FULL!!\n");
			snd_pcm_stream_lock_irqsave(substream, flags);
			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
			snd_pcm_stream_unlock_irqrestore(substream, flags);
			return -EPIPE;
		}

		if (voc_merged_dma_copy_data(dst, hwbuf, count))
			return -EFAULT;

		mutex_lock(&voc->pcm_lock);
		if (prtd->state == DMA_EMPTY && state != prtd->state) {
			mutex_unlock(&voc->pcm_lock);
			return -EPIPE;
		}
		voc_merged_dma_trig_level_cnt(pcm_data, count);

		prtd->dma_level_count += count;
		mutex_unlock(&voc->pcm_lock);

		prtd->state = DMA_NORMAL;

		/* Ensure that the DMA done */
		wmb();
	}

#if SHOW_BUSY_LOG
	voc_debug("frame_pos = 0x%lx, frame_count = 0x%lx, framLevelCnt = %u\n",
			pos, count, pcm_data->pcm_level_cnt);
#endif

	return 0;
}


static int voc_merged_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_voc *voc;
	struct voc_pcm_data *pcm_data;
	struct timespec64 systemts;
	uint64_t audio_time = 0;
	uint32_t mod = 0;

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

	voc = (struct mtk_snd_voc *)substream->private_data;
	pcm_data = substream->runtime->private_data;

	ktime_get_ts64(&systemts);

	audio_ts->tv_sec = systemts.tv_sec;
	audio_ts->tv_nsec = systemts.tv_nsec;

	// Get timestamp from PMU set to audio_ts
	// Time of PMU + Time of clock sync
	audio_time = (pcm_data->pcm_time * VOICE_NS_PER_US //us->ns
				+ voc->sync_time); //ns
	mod = do_div(audio_time, VOICE_NS_PER_SECOND);
	system_ts->tv_sec = audio_time; // ns->sec
	system_ts->tv_nsec = mod; // total ns remove sec
#if SHOW_BUSY_LOG
	voc_debug("[%s][%d]audio ts: %ld sec %ld nsec, system ts: %ld sec %ld nsec\n",
				__func__, __LINE__, audio_ts->tv_sec, audio_ts->tv_nsec,
				system_ts->tv_sec, system_ts->tv_nsec);
#endif
	return 0;
}


static struct snd_pcm_ops voc_merged_pcm_ops = {
	.open = voc_merged_pcm_open,
	.close = voc_merged_pcm_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = voc_merged_pcm_hw_params,
	.hw_free = voc_merged_pcm_hw_free,
	.prepare = voc_merged_pcm_prepare,
	.trigger = voc_merged_pcm_trigger,
	.pointer = voc_merged_pcm_pointer,
#ifdef LINUX_VERSION_CODE
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
	.copy_user = voc_merged_pcm_copy,
#else
	.copy = voc_merged_pcm_copy,
#endif
#else
	.copy_user = voc_merged_pcm_copy,
#endif
	.get_time_info = voc_merged_pcm_get_time_info
};

//------------------------------------------------------------------------------
//  Voc Card Function
//------------------------------------------------------------------------------
void voc_merged_free_dmem(void *addr)
{
	iounmap(addr);
}

static void voc_merged_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
	struct snd_pcm_substream *substream;
	struct snd_dma_buffer *buf;

	substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
	if (!substream)
		return;

	buf = &substream->dma_buffer;
	if (!buf->area)
		return;

	voc_merged_free_dmem((void *)buf->area);

	buf->area = NULL;

}

unsigned char *voc_merged_alloc_dmem(struct mtk_snd_voc *voc,
			int size, dma_addr_t *addr)
{
	unsigned char *ptr = NULL;
	u32 mem_free_size = voc->voc_card->voc_pcm_size;
	phys_addr_t mem_cur_pos = voc->voc_card->voc_buf_paddr;

	if (!addr)
		return NULL;

	if (mem_free_size && mem_free_size > size) {
		ptr = (unsigned char *)ioremap_wc(mem_cur_pos + voc->miu_base, size);
		if (!ptr) {
			voc_err("ioremap NULL address,phys : %pa\n", &mem_cur_pos);
			*addr = 0x0;
		} else
			*addr = (dma_addr_t)(mem_cur_pos + voc->miu_base);
	} else {
		*addr = 0x0;
		voc_err("No enough memory, free = %u\n", mem_free_size);
	}

	return ptr;
}

static int voc_merged_pcm_preallocate_dma_buffer(struct mtk_snd_voc *voc, int stream)
{
	struct snd_pcm_substream *substream;
	struct snd_dma_buffer *buf;
	unsigned int iIndex;
	size_t size = 0;

	if (!voc) {
		voc_err("Invalid voc argument\n");
		return -EINVAL;
	}

	if (stream < 0) {
		voc_err("Invalid stream argument\n");
		return -EINVAL;
	}
	iIndex = stream;

	substream = voc->pcm->streams[iIndex].substream;
	buf = &substream->dma_buffer;

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

	if (stream == SNDRV_PCM_STREAM_CAPTURE)  // CAPTURE device
		size = voc_merged_pcm_capture_hardware.buffer_bytes_max;
	else
		return -EINVAL;

	buf->dev.type = SNDRV_DMA_TYPE_DEV;
	buf->dev.dev = voc->card->dev;
	buf->private_data = NULL;
	buf->area = voc_merged_alloc_dmem(voc, PAGE_ALIGN(size), &buf->addr);

	if (!buf->area)
		return -ENOMEM;
	buf->bytes = PAGE_ALIGN(size);

	voc_debug("dma buffer align size 0x%lx, size 0x%zx\n", (unsigned long)buf->bytes, size);
	voc_debug("physical dma address %pad\n", &buf->addr);
	voc_debug("virtual dma address,%lx\n", (unsigned long)buf->area);

	return 0;
}

static int voc_merged_pcm_dma_new(struct mtk_snd_voc *voc)
{
	struct snd_card *card;
	struct snd_pcm *pcm;
	struct snd_pcm_substream *substream;
	int ret = 0;

	if (!voc) {
		voc_err("Invalid voc argument\n");
		return -EINVAL;
	}

	card = voc->card;
	pcm = voc->pcm;

	voc_info("%s: snd_pcm device id = %d\r\n", __func__, pcm->device);

	substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;

	if (!card->dev->dma_mask)
		card->dev->dma_mask = &voc_merged_pcm_dmamask;
	if (!card->dev->coherent_dma_mask)
		card->dev->coherent_dma_mask = DMA_BIT_MASK(DMA_BIT_32);

	if (substream) {
		ret = voc_merged_pcm_preallocate_dma_buffer(voc, SNDRV_PCM_STREAM_CAPTURE);
		if (ret)
			goto out;
	}

out:
	/* free preallocated buffers in case of error */
	if (ret)
		voc_merged_pcm_free_dma_buffers(pcm);

	return ret;
}

static int voc_merged_card_dma_init(struct platform_device *pdev, struct mtk_snd_voc *voc)
{
	int ret = 0;
	struct device_node *np;
	struct voc_card_drvdata *card_drvdata = voc->voc_card;
	u32 voc_merged_pcm_size;

	if (pdev == NULL) {
		voc_err("pdev is NULL\n");
		return -ENODEV;
	}

	if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(DMA_BIT_32))) {
		voc_dev_err(&pdev->dev, "dma_set_mask_and_coherent failed\n");
		return -ENODEV;
	}

	np = of_find_compatible_node(NULL, NULL, "mediatek,mtk-mic-merged-dma");
	ret = of_property_read_u32(np, "voc-pcm-size", &voc_merged_pcm_size);
	card_drvdata->voc_pcm_size = voc_merged_pcm_size;
	memset(&voc->dbgInfo, 0, sizeof(struct voc_dma_dbg));

	if (ret < 0) {
		voc_err("request_irq failed, err = %d!!\n", ret);
		return ret;
	}

	voc->dbgInfo.writed = 0;
	memset(voc->dbgInfo.log, 0, sizeof(struct operation_log) * MAX_LOG_LEN);

	return ret;
}

static int voc_merged_card_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 {
		voc_info("can not find cpu_emi0_base info\n");
		of_node_put(target_memory_np);
		return -EINVAL;
	}
	return 0;
}

static int voc_merged_card_machine_init(struct platform_device *pdev,  struct mtk_snd_voc *voc)
{
	int ret = 0;
	struct voc_card_drvdata *card_drvdata = voc->voc_card;
	struct device_node *np;
	struct device_node *mic_np;
	struct of_mmap_info_data of_mmap_info = {0};
	char *mic_channel = NULL;
	unsigned long mic_ch = DEFAULT_CHANNELS;
	unsigned long ref_ch = DEFAULT_CHANNELS;
	u32 miu_base;
	int i;

	np = of_find_compatible_node(NULL, NULL, "mediatek,mtk-mic-machine");

	ret = voc_merged_card_get_memory_info(&miu_base);
	if (ret < 0) {
		voc_err("Request miu_base property failed, err = %d!!\n", ret);
		return ret;
	}
	voc->miu_base = miu_base;

	if (of_mtk_get_reserved_memory_info_by_idx(np, 0, &of_mmap_info) == 0) {
		card_drvdata->voc_buf_paddr = (u64)of_mmap_info.start_bus_address - voc->miu_base;
		card_drvdata->voc_buf_size = (u32)of_mmap_info.buffer_size;
	}

	mic_np = of_get_child_by_name(np, "microphone");
	if (mic_np != NULL) {
		ret = of_property_read_string(mic_np, "channel", (const char **)&mic_channel);
		if (ret < 0) {
			voc_warn("Request mic channel failed, err = %d!!\n", ret);
		} else if (mic_channel != NULL) {
			ret = kstrtol(mic_channel, STR2LONG_BASE_DECIMAL, &mic_ch);
			if (ret < 0 || mic_ch > VOICE_MIC_SEQUENCE_SIZE) {
				voc_warn("Convert mic channel failed:(%s,%u)!!\n",
					mic_channel, (u32)mic_ch);
				mic_ch = DEFAULT_CHANNELS;
			}
		}
		of_node_put(mic_np);
	}
	voc->mic_channel = (u32)mic_ch;

	memset(card_drvdata->mic_sequence, 0, VOICE_MIC_SEQUENCE_SIZE);
	for (i = 0; i < voc->mic_channel; i++)
		card_drvdata->mic_sequence[i] = i;

	card_merged_reg[VOC_MERGED_REG_REF_CHANNEL] = (unsigned int)ref_ch;
	card_drvdata->card_reg = card_merged_reg;

	return ret;
}

static int voc_merged_card_aec_engine_init(struct platform_device *pdev)
{
	int ret;
	struct device_node *np;
	struct of_mmap_info_data of_mmap_info = {0};
	uint64_t dma_dram_write_base = 0;
	u32 miu_base;

	np = of_find_node_by_name(NULL, "mtk-audio-loopback");
	if (!np)
		return -ENODEV;

	ret = of_property_read_u32(np, "aec_dma_offset", &g_voc_merged_data.aec_dma_offset);
	ret |= of_property_read_u32(np, "aec_dma_size", &g_voc_merged_data.aec_dma_size);
	if (ret) {
		voc_err("[%s]get dma info fail(%u,%u)\n", __func__,
			g_voc_merged_data.aec_dma_offset, g_voc_merged_data.aec_dma_size);
		return -EINVAL;
	}

	if (of_property_read_u64(np, "dma-dram-write-base", &dma_dram_write_base)) {
		voc_err("[%s]can't get dma dram write base\n", __func__);
		return -EINVAL;
	}
	g_voc_merged_data.aec_mem_info.dma_dram_write_base = dma_dram_write_base;

	ret = of_mtk_get_reserved_memory_info_by_idx(np, 0, &of_mmap_info);
	g_voc_merged_data.aec_mem_info.miu = of_mmap_info.miu;
	g_voc_merged_data.aec_mem_info.bus_addr = of_mmap_info.start_bus_address;
	g_voc_merged_data.aec_mem_info.buffer_size = of_mmap_info.buffer_size;
	of_node_put(np);

	if (ret) {
		voc_err("[%s]get mmap fail(%u,%lu,%lu)\n", __func__,
			g_voc_merged_data.aec_mem_info.miu,
			g_voc_merged_data.aec_mem_info.bus_addr,
			g_voc_merged_data.aec_mem_info.buffer_size);
		return -EINVAL;
	}

	ret = voc_merged_card_get_memory_info(&miu_base);
	if (ret < 0) {
		voc_err("Request miu_base property failed, err = %d!!\n", ret);
		return ret;
	}
	g_voc_merged_data.aec_mem_info.memory_bus_base = miu_base;

	ret = audio_loopback_dma_probe();
	if (ret < 0)
		voc_err("Fail to init dma:%d\n", ret);

	return ret;
}

static void voc_merged_card_mic_check(void)
{
	struct device_node *np;
	u32 value;

	np = of_find_compatible_node(NULL, NULL, "mediatek,mtk-mic-dma");
	if (!of_device_is_available(np))
		return;

	if (of_property_read_u32(np, "bck_mode", &value) >= 0) {
		if (value > 0)
			g_voc_merged_data.extends_card = 1;
	}
	voc_info("[%s] value=%d extends_card=%d\n", __func__,
		value, g_voc_merged_data.extends_card);
	of_node_put(np);
}

static int voc_merged_card_module_init(struct platform_device *pdev, struct mtk_snd_voc *voc)
{
	int ret = 0;
	struct voc_card_drvdata *card_drvdata = NULL;

	card_drvdata = voc->voc_card;
	if (!card_drvdata) {
		card_drvdata = kzalloc(sizeof(struct voc_card_drvdata), GFP_KERNEL);
		if (!card_drvdata)
			return -ENOMEM;
		voc->voc_card = card_drvdata;
	}

	//DMA -> MACHINE -> Engine
	//1. DMA initial
	ret = voc_merged_card_dma_init(pdev, voc);
	if (ret < 0) {
		voc_dev_err(&pdev->dev, "Fail to init DMA\n");
		return ret;
	}

	//2. MACHINE initial
	ret = voc_merged_card_machine_init(pdev, voc);
	if (ret < 0) {
		voc_dev_err(&pdev->dev, "Fail to init MACHINE\n");
		return ret;
	}

	//3. Engine initial
	memset(&g_voc_merged_data, 0, sizeof(struct voc_merged_data));
	g_voc_merged_data.block_mode = 1;
	voc_mic_cap_iface_init();
	INIT_WORK(&g_voc_merged_data.work_dma_status, voc_merged_dma_status_handler);
	init_waitqueue_head(&g_voc_merged_data.kthread_waitq);
	g_voc_merged_data.thread_task = kthread_run(voc_merged_dma_handler_thread,
							(void *)voc, "voc_merged_dma_task");
	if (IS_ERR(g_voc_merged_data.thread_task)) {
		voc_err("create thread for paganini merged task failed!\n");
		return ret;
	}
	voc_merged_card_mic_check();
	//3.1 AEC Engine initial
	ret = voc_merged_card_aec_engine_init(pdev);
	if (ret < 0) {
		voc_dev_err(&pdev->dev, "Failt to ini AEC Engine\n");
		return ret;
	}

	//4. New DMA buffer for pcm used
	ret = voc_merged_pcm_dma_new(voc);
	if (ret < 0) {
		voc_dev_err(&pdev->dev, "Fail to new DMA buffer\n");
		return ret;
	}

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

static int voc_merged_card_pcm_new(struct mtk_snd_voc *voc,
			    int device, int playback_substreams, int capture_substreams)
{
	struct snd_pcm *pcm;
	int err;

	if (voc == NULL) {
		voc_err("[%s][%d]Invalid argument\n", __func__, __LINE__);
		return -EINVAL;
	}

	err = snd_pcm_new(voc->card, SND_PCM_MERGED_NAME, device,
			  playback_substreams, capture_substreams, &pcm);

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

	if (playback_substreams) {
		voc_err("[%s][%d]Playback count:(%d>0)\n"
			, __func__, __LINE__, playback_substreams);
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &voc_merged_pcm_ops);
	}

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

	pcm->private_data = voc;
	pcm->info_flags = 0;
	pcm->nonatomic = true;
	strscpy(pcm->name, SND_PCM_MERGED_NAME, sizeof(pcm->name));

	voc->pcm = pcm;

	return err;
}

static unsigned int voc_merged_card_read(struct mtk_snd_voc *voc,
			unsigned int reg)
{
	int32_t value;
	int ret;

	if (!voc) {
		voc_err("Invalid voc argument\n");
		return -EINVAL;
	}

	//All reg should be request engine to get status first
	switch (reg) {
	case VOC_MERGED_REG_MIC_ONOFF:
		voc_info("%s: mic switch reg: x%x\n", __func__, reg);
		ret = voc_mic_cap_iface_get_attr(MIC_SWITCH, &value);
		if (ret < 0)
			goto voc_merged_card_read_failed;
		card_merged_reg[reg] = value;
		break;
	case VOC_MERGED_REG_REF_CHANNEL:
		voc_info("%s: ref merged channel reg: x%x\n", __func__, reg);
		break;
	default:
		voc_err("%s: error parameter, reg = 0x%x\n", __func__, reg);
		return -EINVAL;
	}

	voc_debug("%s: reg = 0x%x, val = 0x%x\n", __func__, reg, card_merged_reg[reg]);
	return card_merged_reg[reg];
voc_merged_card_read_failed:
	return ret;
}

static int voc_merged_card_write(struct mtk_snd_voc *voc, unsigned int reg,
				unsigned int value)
{
	int32_t tmp_value;
	int ret = 0;

	voc_debug("%s: reg = 0x%x, val = 0x%x\n", __func__, reg, value);
	if (!voc) {
		voc_err("Invalid voc argument\n");
		return -EINVAL;
	}

	switch (reg) {
	case VOC_MERGED_REG_MIC_ONOFF:
		voc_info("%s: ref merged channel reg: x%x, val = 0x%x\n", __func__,
				reg, value);
		ret = voc_mic_cap_iface_get_attr(MIC_SWITCH, &tmp_value);
		if (ret == 0) {
			if (!g_voc_merged_data.extends_card) {
				tmp_value = (int32_t)value;
				ret = voc_mic_cap_iface_set_attr(MIC_SWITCH, &tmp_value);
			} else if (tmp_value == 0 && value == 1) {
				tmp_value = (int32_t)value;
				ret = voc_mic_cap_iface_set_attr(MIC_SWITCH, &tmp_value);
			}
		}
		break;
	case VOC_MERGED_REG_REF_CHANNEL:
		voc_info("%s: ref merged channel reg: x%x, val = 0x%x\n", __func__,
				reg, value);
		break;
	default:
		voc_err("%s: error parameter, reg = 0x%x, val = 0x%x\n",
				__func__, reg, value);
		return -1;
	}

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

	return ret;
}

static int voc_merged_card_get_enum(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_voc *voc = snd_kcontrol_chip(kcontrol);
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;

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

static int voc_merged_card_put_enum(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_voc *voc = 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 voc_merged_card_write(voc, e->reg,
					ucontrol->value.integer.value[0]);
}

static int voc_merged_card_get_volsw(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_voc *voc = snd_kcontrol_chip(kcontrol);
	struct soc_mixer_control *mc =
		(struct soc_mixer_control *)kcontrol->private_value;

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

static int voc_merged_card_put_volsw(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	struct mtk_snd_voc *voc = 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)) {
		voc_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 voc_merged_card_write(voc, mc->reg,
					ucontrol->value.integer.value[0]);
}

static const struct snd_kcontrol_new voc_merged_snd_controls[] = {
	SOC_ENUM_VOC_MERGED("Mic switch", mic_onoff_enum),
	SOC_SINGLE_EXT("Ref Channel Num", VOC_MERGED_REG_REF_CHANNEL, 0, 8, 0,
			voc_merged_card_get_volsw, voc_merged_card_put_volsw),
};

static int voc_merged_card_mixer_new(struct mtk_snd_voc *voc)
{
	struct snd_card *card = voc->card;
	struct snd_kcontrol *kcontrol;
	unsigned int idx;
	int err;

	strscpy(card->mixername, "voc_merged_snd_card", sizeof(card->mixername));

	for (idx = 0; idx < ARRAY_SIZE(voc_merged_snd_controls); idx++) {
		kcontrol = snd_ctl_new1(&voc_merged_snd_controls[idx], voc);
		err = snd_ctl_add(card, kcontrol);
		if (err < 0)
			return err;
	}

	return 0;
}

static int voc_merged_card_probe(struct platform_device *pdev)
{
	struct snd_card *card;
	struct mtk_snd_voc *voc;
	struct device_node *np;
	unsigned int dev = (unsigned int)pdev->id;
	int ret;

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

	// Start to new card / pcm / mixer
	ret = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE,
			   sizeof(struct mtk_snd_voc), &card);

	if (ret < 0)
		return ret;
	voc = card->private_data;
	if (pcm_capture_substreams[dev] < 1)
		pcm_capture_substreams[dev] = 1;
	if (pcm_capture_substreams[dev] > MAX_PCM_CAPTURE_SUBSTREAMS)
		pcm_capture_substreams[dev] = MAX_PCM_CAPTURE_SUBSTREAMS;

	if (pcm_playback_substreams[dev] < 1)
		pcm_playback_substreams[dev] = 1;
	if (pcm_playback_substreams[dev] > MAX_PCM_PLAYBACK_SUBSTREAMS)
		pcm_playback_substreams[dev] = MAX_PCM_PLAYBACK_SUBSTREAMS;

	voc->card = card;
	init_waitqueue_head(&voc->kthread_waitq);
	ret = voc_merged_card_pcm_new(voc, 0,
		pcm_playback_substreams[dev], pcm_capture_substreams[dev]);
	voc_info("[%s][%d] pcm playback substreams[%d]=%d, pcm capture substreams[%d]=%d\n"
		, __func__, __LINE__, dev, pcm_playback_substreams[dev]
		, dev, pcm_capture_substreams[dev]);

	if (ret < 0) {
		voc_err("[%s][%d]Fail to create pcm error:%d\n"
			, __func__, __LINE__, ret);
		goto nodev;
	}


	strscpy(card->driver, "vocsndmergedcard", sizeof(card->driver));
	strscpy(card->shortname, "vocsndmcard", sizeof(card->shortname));
	ret = snprintf(card->longname, sizeof(card->longname), "vocsndmcard %i", dev + 1);
	if (ret < 0) {
		voc_err("[%s][%d]Fail to call snprintf():%d\n"
			, __func__, __LINE__, ret);
	}

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

	ret = snd_card_register(card);
	if (!ret) {
		platform_set_drvdata(pdev, card);
		//return 0;
	}

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

	ret = voc_merged_card_mixer_new(voc);
	if (ret < 0) {
		voc_err("[%s][%d]Fail to create mixer error:%d\n"
			, __func__, __LINE__, ret);
		goto nodev;
	}

	voc->vd_task = kthread_run(voc_merged_card_task_thread, (void *)voc, "voc_merged_vd_task");
	if (IS_ERR(voc->vd_task)) {
		voc_err("create thread for add voc_merged_task failed!\n");
		ret = PTR_ERR(voc->vd_task);
		goto nodev;
	}

	mutex_init(&voc->pcm_lock);

	mtk_dtv_voc_merged_card_create_sysfs_attr(pdev);

	return 0;

nodev:
	snd_card_free(card);
	return ret;

}

static int voc_merged_card_remove(struct platform_device *pdev)
{
	struct snd_card *card = platform_get_drvdata(pdev);
	struct mtk_snd_voc *voc = (struct mtk_snd_voc *)card->private_data;

	voc_debug("%s, remove\n", __func__);

	if (voc) {
		if (!IS_ERR(voc->vd_task))
			kthread_stop(voc->vd_task);
		voc_merged_pcm_free_dma_buffers(voc->pcm);
	}
	if (!IS_ERR(g_voc_merged_data.thread_task))
		kthread_stop(g_voc_merged_data.thread_task);
	mtk_dtv_voc_merged_card_remove_sysfs_attr(pdev);
	snd_card_free(platform_get_drvdata(pdev));
	platform_set_drvdata(pdev, NULL);
	return 0;
}

static struct platform_driver voc_merged_card_driver = {
	.probe = voc_merged_card_probe,
	.remove = voc_merged_card_remove,
	.shutdown = voc_merged_card_shutdown,
	.driver = {
			.name = SND_VOC_MERGED_DRIVER,
			.owner = THIS_MODULE,
#ifdef CONFIG_PM_SLEEP
			.pm = &voc_merged_card_pm_ops,
#endif
			},
};

static void voc_merged_card_unregister_all(void)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(devices); ++i)
		platform_device_unregister(devices[i]);
	platform_driver_unregister(&voc_merged_card_driver);
}


static int __init voc_merged_card_init(void)
{
	int i, err, cards;

	voc_info("[%s][%d]\n", __func__, __LINE__);

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

	cards = 0;
	for (i = 0; i < SNDRV_CARDS; i++) {
		struct platform_device *device;

		if (!enable[i])
			continue;
		device = platform_device_register_simple(SND_VOC_MERGED_DRIVER,
							 i, NULL, 0);
		if (IS_ERR(device))
			continue;
		if (!platform_get_drvdata(device)) {
			platform_device_unregister(device);
			continue;
		}
		devices[i] = device;
		cards++;
	}

	voc_info("[%s][%d]:Total cards = %d\n", __func__, __LINE__, cards);

	if (!cards) {
#ifdef MODULE
		voc_err("voc-merged-card: No voc merged driver enabled\n");
#endif
		voc_merged_card_unregister_all();
		return -ENODEV;
	}

	return 0;
}

static void __exit voc_merged_card_exit(void)
{
	voc_merged_card_unregister_all();
}

module_init(voc_merged_card_init);
module_exit(voc_merged_card_exit);


/* Module information */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ying-Jie Chen, ying-jie.chen@mediatek.com");
MODULE_DESCRIPTION("Voc Merged Audio ALSA driver");
