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

//------------------------------------------------------------------------------
//  Include Files
//------------------------------------------------------------------------------
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.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/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/time.h>
#include <sound/soc.h>
#include "mtk_dbg.h"
#include "voc_common.h"
#include "voc_common_reg.h"
#include "voc_drv_paganini.h"
#include "voc_hal_paganini.h"
#include "voc_mic_cap_iface.h"

//------------------------------------------------------------------------------
//  Macros
//------------------------------------------------------------------------------
#define ENABLE_DUMP_PCM_BUFFER (0)
#define ENABLE_INT_CONTROL     (1)

struct voc_paganini_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;
	uint64_t time_interval;
	int handler;
	int irq;
	u32 irqtype;
	u32 mute_enable;
	u32 voc_delta_time;
	bool block_mode;
	bool dma_seamless_mode;
	bool dma_buf_wr_enable;
	bool keyword_flag;
	bool timer_int_enable;
	bool overrun_trigger;
	bool full_trigger;
	u16 bck_mode;
	u16 bck_drive;
};


#define CONFIG_PCM_SAMPLE_RATE      48000 //16000
#define CONFIG_PCM_CHANNEL              2
#define CONFIG_PCM_STREAM_BUFFER_MS   125
#define CONFIG_PCM_STREAM_CHANNEL       4
#define VOICE_DMA_BUFFER_PERIOD_MS      (64) //0.5s

#define VOICE_UNDERRUN_MS           (10) //10 ms
#define VOICE_OVERRUN_MS            (VOICE_DMA_BUFFER_PERIOD_MS/4) //1/4 buffer size



#define VOC_MAX_PCM_BIT_WIDTH   (sizeof(int32_t))
#define VOC_TASK_PCM_BUF_MS     (16)
#define VOC_TASK_PCM_BUF_COUNT  ((CONFIG_PCM_SAMPLE_RATE * CONFIG_PCM_STREAM_CHANNEL *\
					VOC_TASK_PCM_BUF_MS * VOC_MAX_PCM_BIT_WIDTH)/1000)
#define VOC_TASK_READ_PCM_FRAME ((CONFIG_PCM_SAMPLE_RATE * VOC_TASK_PCM_BUF_MS)/1000)
#define VOC_BIT_TO_BYTE(_x_)    ((_x_)/8)
#define VOC_MAX_VDMA_SIZE       (0xFF000)
#define VOC_PERIOD_MS           (16)
#define VOC_PERIOD_SIZE         (2048) //temporary
#define VOC_GAIN_CONST          (-1)

#define VOC_CHANNEL_2             (2)
#define VOC_CHANNEL_4             (4)
#define VOC_CHANNEL_6             (6)
#define VOC_CHANNEL_8             (8)
#define VOC_SAMPLE_RATE_8K     (8000)
#define VOC_SAMPLE_RATE_16K   (16000)
#define VOC_SAMPLE_RATE_32K   (32000)
#define VOC_SAMPLE_RATE_48K   (48000)
#define VOC_BIT_WIDTH_16         (16)
#define VOC_BIT_WIDTH_24         (24)
#define VOC_BIT_WIDTH_32         (32)

#define VOC_BCK_MODE_MIN         (0)
#define VOC_BCK_MODE_MAX         (10)
#define VOC_DEFAULT_BCK_MODE     (9) //3M

#define VOC_BCK_DRIVE_MIN        (0)
#define VOC_BCK_DRIVE_MAX        (3)
#define VOC_DEFAULT_BCK_DRIVE    (2) //4mA

#define MAILBOX_ELEM_ID_02       (2)

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
//------------------------------------------------------------------------------
//  Variables
//------------------------------------------------------------------------------
static struct voc_communication_operater voc_paganini_op;
struct voc_paganini_data g_paganini;

static uint8_t voc_task_pcm_buf[VOC_TASK_PCM_BUF_COUNT]; //12K
//------------------------------------------------------------------------------
//  Function
//------------------------------------------------------------------------------
#if ENABLE_DUMP_PCM_BUFFER
void dump_buffer(void)
{
	#define DUMP_LEN  0x10
	#define DUMP_ADDR 0x200

	int i;
	unsigned char *ptr = NULL;

	ptr = (unsigned char *)g_voc->pcm->streams[1].substream->dma_buffer.area;

	voc_info("[%s]ptr=[%lx]\n", __func__, (unsigned long)ptr);
	//rmb();
	for (i = 0; i < DUMP_LEN; i++)
		voc_info("val[%x]:%x\n", i, ptr[i]);

	for (i = DUMP_ADDR; i < (DUMP_ADDR + DUMP_LEN); i++)
		voc_info("val[%x]:%x\n", i, ptr[i]);
}
#endif
//==============================================================================
int voc_drv_paganini_bind(struct mtk_snd_voc *snd_voc)
{
	UNUSED(snd_voc);
	return 0;
}

static uint64_t voc_drv_paganini_ktime_to_us(struct timespec64 *ktime)
{
	uint64_t value;

	value = ktime->tv_sec * VOICE_US_PER_SECOND;
	value +=  ktime->tv_nsec / VOICE_NS_PER_US;
	return value;
}
//==============================================================================
static void voc_drv_paganini_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_paganini.timer_int_enable;
		ctrl.timer_flag = g_paganini.timer_int_enable;
		voc_mic_cap_iface_enable_interrupt(g_paganini.handler, ctrl);
		g_paganini.full_trigger = status.full;
		g_paganini.overrun_trigger = status.overrun;
		schedule_work(&g_paganini.work_dma_status);
	}

	if (status.timer)	{
		voc_mic_cap_iface_clear_interrupt_status(g_paganini.handler, MIC_INT_TIMER);
		timespec64_add_ns(&g_paganini.timestamp, VOC_PERIOD_MS*VOICE_NS_PER_MS);
	}
}
//==============================================================================
static int voc_drv_paganini_check_seamless_full(uint32_t period, uint32_t period_size)
{
	struct voc_pcm_data *pcm_data = NULL;
	uint64_t interval;
	uint32_t pcm_full;
	uint32_t pcm_buf_size;
	uint32_t pcm_level_cnt;
	uint32_t pcm_last_period_num;
	int ret = 0;

	mutex_lock(&g_voc->pcm_lock);
	pcm_data = g_voc->voc_pcm;
	if (pcm_data != NULL) {
		pcm_last_period_num = pcm_data->pcm_last_period_num;
		if (period < pcm_data->pcm_last_period_num)
			interval = (uint64_t)period + MAX_CM4_PERIOD - pcm_last_period_num;
		else
			interval = (period - pcm_last_period_num);
		pcm_level_cnt = pcm_data->pcm_level_cnt + (interval * period_size);
		pcm_buf_size = pcm_data->pcm_buf_size;
		pcm_full = pcm_buf_size - period_size*SEMLESS_FULL_THRES;
		if (pcm_buf_size > 0 && pcm_level_cnt >= pcm_full)
			ret = 1;
	}
	mutex_unlock(&g_voc->pcm_lock);
	return ret;
}
//==============================================================================
static int voc_drv_paganini_read_seamless_pcm(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;

	channels = voc_hal_paganini_get_channel();
	bytes_of_bit_width = VOC_BIT_TO_BYTE(voc_hal_paganini_get_bit_width());
	sample_per_ms = voc_hal_paganini_get_sample_rate()/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 * VOC_PERIOD_MS;
	buf_size = frame_count * bytes_of_bit_width * channels;
	underrun_size = sample_per_ms * bytes_of_bit_width * channels *
		VOICE_UNDERRUN_MS;

	if (voc_drv_paganini_check_seamless_full((*period_cnt), buf_size)) {
		usleep_range(VOICE_US_PER_MS, VOICE_US_PER_MS + 1);
		return -EAGAIN;
	}

	if (underrun_size > buf_size)
		underrun_size = buf_size;

	//read
	byte_buf = voc_task_pcm_buf;
	ret_size = 0;
	do {
		if (g_paganini.dma_buf_wr_enable == 0) {
			(*period_cnt) = 0;
			return -EAGAIN;
		} else if (voc_hal_paganini_dma_wr_level_cnt() > underrun_size ||
			!g_paganini.block_mode) {
			rd_size = voc_hal_paganini_read_dma_data((uint8_t *)byte_buf, buf_size);
			byte_buf += rd_size;
			buf_size -= rd_size;
			ret_size += rd_size;

			if ((g_paganini.block_mode && (buf_size == 0)) || !g_paganini.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_paganini.time_interval;
	g_paganini.time_interval += VOC_PERIOD_MS * VOICE_US_PER_MS;
	return 0;
}
//==============================================================================
static int voc_drv_paganini_read_normal_pcm(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 = VOC_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 * VOC_PERIOD_MS;
	buf_size = frame_count * bytes_of_bit_width * channels;
	underrun_size = sample_per_ms * bytes_of_bit_width * channels *
			VOICE_UNDERRUN_MS;

	if (underrun_size > buf_size)
		underrun_size = buf_size;

	//read
	byte_buf = voc_task_pcm_buf;
	ret_size = 0;
	do {
		if (g_paganini.dma_buf_wr_enable == 0) {
			(*period_cnt) = 0;
			return -EAGAIN;
		} else if (voc_mic_cap_iface_get_level_count(g_paganini.handler) > underrun_size ||
			!g_paganini.block_mode) {
			rd_size = voc_mic_cap_iface_read_data(g_paganini.handler,
						(uint8_t *)byte_buf, buf_size);
			byte_buf += rd_size;
			buf_size -= rd_size;
			ret_size += rd_size;

			if ((g_paganini.block_mode && (buf_size == 0)) || !g_paganini.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_paganini.time_interval;
	g_paganini.time_interval += VOC_PERIOD_MS * VOICE_US_PER_MS;
	return 0;
}
//==============================================================================
int voc_drv_paganini_pcm_handler_thread(void *param)
{
	struct snd_kcontrol *kctl = NULL;
	struct snd_ctl_elem_id elem_id;
	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_paganini.voice_msg.payload;

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

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

		if (g_paganini.keyword_flag) {
			memset(&elem_id, 0, sizeof(elem_id));
			elem_id.numid = MAILBOX_ELEM_ID_02;
			kctl = snd_ctl_find_id(g_voc->card, &elem_id);
			if (kctl != NULL) {
				snd_ctl_notify(g_voc->card,
				SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
			}
			voc_info("[%s]HOTWORD DETECTED send event notify\n", __func__);
			g_paganini.keyword_flag = 0;
			if (!g_paganini.dma_buf_wr_enable)
				continue;
		}

		if (g_paganini.dma_seamless_mode)
			ret = voc_drv_paganini_read_seamless_pcm(
				&period_cnt, &period_size, &time_interval);
		else
			ret = voc_drv_paganini_read_normal_pcm(
				&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(&g_voc->pcm_lock);
		if (g_paganini.dma_buf_wr_enable) {
			payload->period_count = period_cnt;
			payload->byte_count = period_size;
			payload->time_interval = time_interval;

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

	} while (1);

	return 0;
}
//==============================================================================
//CM4 notification interval is based on the fixed frame size(16kHz,16bits),
//and it's irrelevant to bitwidth and sample rate.
//So we should update notification frequency
//in case the notification is frequent.
int voc_drv_paganini_dma_init_channel(
		uint64_t cpu_paddr,
		uint32_t buf_size,
		uint32_t channels,
		uint32_t bit_width,
		uint32_t sample_rate)
{
	void *pPAddr;
	void *pVAddr;
	int ret;

	if (!g_voc)
		return -EINVAL;

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

	if (cpu_paddr < g_voc->miu_base) {
		voc_err("[%s] dma_paddr is small than miu_base\n", __func__);
		return -EINVAL;
	}
	cpu_paddr -= g_voc->miu_base;

	voc_info("u32DmaAddr:0x%llx\n", cpu_paddr);
	voc_info("buf_size:%d\n", buf_size);
	voc_info("channels:%d\n", channels);
	voc_info("bit_width:%d\n", bit_width);
	voc_info("sample_rate:%d\n", sample_rate);

	g_paganini.dma_seamless_mode = 0;
	if (voc_hal_paganini_open(sample_rate, bit_width, channels) == 0) {
		voc_hal_paganini_set_dma_buf_addr(cpu_paddr, buf_size);
		pVAddr = (void *)g_voc->pcm->streams[1].substream->dma_buffer.area;
		voc_hal_paganini_set_virtual_buf_addr(pVAddr, buf_size);
		g_paganini.dma_seamless_mode = 1;
		return 0;
	}

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

	ret = voc_mic_cap_iface_open(sample_rate, bit_width, channels);
	if (ret < 0) {
		voc_err("open failed(%d)\n", ret);
		return ret;
	}
	g_paganini.handler = ret;

	if (buf_size > VOC_MAX_VDMA_SIZE) {
		//to do:fix it
		voc_info("buf_size = 0x%x: out of range\n", buf_size);
		buf_size = VOC_MAX_VDMA_SIZE;
	}

	pPAddr = (void *)cpu_paddr;
	pVAddr = (void *)g_voc->pcm->streams[1].substream->dma_buffer.area;
	ret = voc_mic_cap_iface_set_buf_addr(g_paganini.handler, 1,
		pPAddr, pVAddr, buf_size);
	if (ret < 0) {
		voc_err("set buffer address failed(%d)\n", ret);
		voc_mic_cap_iface_close(g_paganini.handler);
		return ret;
	}

	ret = voc_mic_cap_iface_register_interrupt_callback(g_paganini.handler,
				voc_drv_paganini_irq_handler);
	if (ret < 0) {
		voc_err("register interrupt callback failed(%d)\n", ret);
		voc_mic_cap_iface_close(g_paganini.handler);
	}
	return ret;
}
//==============================================================================
int voc_drv_paganini_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;

	if (g_paganini.dma_seamless_mode) {
		if (!g_paganini.dma_buf_wr_enable) {
			voc_hal_paganini_dma_start_channel();
			g_paganini.dma_buf_wr_enable = TRUE;
			ktime_get_ts64(&g_paganini.timestamp);
			g_paganini.time_interval = voc_drv_paganini_ktime_to_us(
					&g_paganini.timestamp);
			wake_up_interruptible(&g_paganini.kthread_waitq);
		}
	} else {
		ret = voc_mic_cap_iface_get_attr(MIC_BIT_WIDTH, &value);
		if (ret < 0)
			return ret;
		sample_byte = VOC_BIT_TO_BYTE(value);

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

		overrun_thres = (CONFIG_PCM_SAMPLE_RATE *
						sample_byte *
						mic_num *
						VOICE_OVERRUN_MS) / VOICE_MS_PER_SECOND;

		if (!g_paganini.dma_buf_wr_enable) {
			memset(&ctrl, 0, sizeof(struct mic_iface_intclr_t));
			#if ENABLE_INT_CONTROL
			g_paganini.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_paganini.handler, ctrl);
			#endif
			ret = voc_mic_cap_iface_start_channel(g_paganini.handler);
			g_paganini.dma_buf_wr_enable = TRUE;
			ktime_get_ts64(&g_paganini.timestamp);
			g_paganini.time_interval = voc_drv_paganini_ktime_to_us(
					&g_paganini.timestamp);

			#if ENABLE_DUMP_PCM_BUFFER
			dump_buffer();
			#endif
			wake_up_interruptible(&g_paganini.kthread_waitq);
		}
	}
	return ret;
}

//==============================================================================
int voc_drv_paganini_dma_stop_channel(void)
{
	int ret = 0;
	struct mic_iface_intclr_t ctrl;

	#if ENABLE_DUMP_PCM_BUFFER
	dump_buffer();
	#endif
	if (g_paganini.dma_buf_wr_enable) {
		if (g_paganini.dma_seamless_mode) {
			voc_hal_paganini_dma_stop_channel();
		} else {
			memset(&ctrl, 0, sizeof(struct mic_iface_intclr_t));
			voc_mic_cap_iface_enable_interrupt(g_paganini.handler, ctrl);
			ret = voc_mic_cap_iface_stop_channel(g_paganini.handler);
		}
		g_paganini.dma_buf_wr_enable = FALSE;
	}
	g_paganini.voc_delta_time = 0;
	//[cm4]voc_state = VOICE_STATE_STOP;

	return ret;

}
//==============================================================================
int voc_drv_paganini_dma_enable_sinegen(bool en)
{
	int32_t value = (int32_t)en;

	return voc_mic_cap_iface_set_attr(MIC_SINEGEN_MODE, &value);
}
//==============================================================================
int voc_drv_paganini_enable_vq(bool en)
{
	return voc_hal_paganini_enable_vq(en);
}
//==============================================================================
int voc_drv_paganini_config_vq(uint8_t mode)
{
	if (mode == VOICE_VQ_FORCE_WAKE_ID)
		g_voc->sysfs_notify_test = 1;
	else
		voc_hal_paganini_config_vq(mode);
	return 0;
}
//==============================================================================
int voc_drv_paganini_enable_hpf(int32_t stage)
{
	return voc_mic_cap_iface_set_attr(MIC_HPF_MODE, &stage);
}
//==============================================================================
int voc_drv_paganini_config_hpf(int32_t coeff)
{
	return voc_mic_cap_iface_set_attr(MIC_HPF_CONFIG, &coeff);
}
//==============================================================================
int voc_drv_paganini_dmic_gain(int16_t gain)
{
	int32_t value = (int32_t)(VOC_GAIN_CONST * gain);

	return voc_mic_cap_iface_set_attr(MIC_GAIN_STEP, &value);
}
//==============================================================================
int voc_drv_paganini_dmic_mute(bool en)
{
	int32_t value = (int32_t)en;

	return voc_mic_cap_iface_set_attr(MIC_MUTE_MODE, &value);
}
//==============================================================================
int voc_drv_paganini_dmic_switch(bool en)
{
	int32_t value = (int32_t)en;

	return voc_mic_cap_iface_set_attr(MIC_SWITCH, &value);
}
//==============================================================================
int voc_drv_paganini_reset_voice(void)
{
	int ret = 0;

	if (g_paganini.dma_seamless_mode) {
		voc_hal_paganini_dma_reset();
		g_paganini.dma_seamless_mode = 0;
	} else
		ret = voc_mic_cap_iface_close(g_paganini.handler);
	return ret;
}
//==============================================================================
int voc_drv_paganini_update_snd_model(
				dma_addr_t snd_model_paddr,
				uint32_t size)
{
	UNUSED(snd_model_paddr);
	UNUSED(size);
	return 0;
}
//==============================================================================
int voc_drv_paganini_get_hotword_ver(void)
{
	return 0;
}
//==============================================================================
int voc_drv_paganini_get_hotword_identifier(void)
{
	if (g_voc && g_voc->voc_card) {
		if (g_voc->voc_card->uuid_array)
			memset(g_voc->voc_card->uuid_array, 1,
					g_voc->voc_card->uuid_size);

	}
	return 0;
}
//==============================================================================
void voc_drv_paganini_slt_init(uint32_t buf_size)
{
	uint16_t *ptr;

	ptr = (uint16_t *)g_voc->pcm->streams[1].substream->dma_buffer.area;
	memset(ptr, 0, buf_size);
}
//==============================================================================
int voc_drv_paganini_check_slt_result(void)
{
	#define SLT_CHECK_COUNT 0x100
	#define SLT_CHECK_START 0x10
	#define SLT_NEXT_ADDR   2
	int i;
	int result = 1; // 0:fail, 1:pass
	uint16_t *ptr;

	ptr = (uint16_t *)g_voc->pcm->streams[1].substream->dma_buffer.area;
	wmb(); // dma
	for (i = SLT_CHECK_START; i < (SLT_CHECK_COUNT + SLT_CHECK_START); i++) {
		if (((ptr[i] + 1) & U16_MAX) != ptr[i + SLT_NEXT_ADDR])
			result = 0;
	}

	return result;
}

//==============================================================================
int voc_drv_paganini_enable_slt_test(int32_t loop, dma_addr_t addr)
{
	struct mic_iface_intclr_t mode;
	void *pVAddr;
	int32_t value = 0;

	#define VOC_LTP_BUF_SIZE  ((VOC_SAMPLE_RATE_16K/1000) * \
								VOC_CHANNEL_2 * \
								VOC_TASK_PCM_BUF_MS * \
								VOC_BIT_TO_BYTE(VOC_BIT_WIDTH_16))

	if (loop == 0)
		return 0;

	if (g_paganini.dma_buf_wr_enable) {
		voc_err("[%s] paganini is running\n", __func__);
		return 0;
	}

	memset(&mode, 0, sizeof(struct mic_iface_intclr_t));
	voc_drv_paganini_slt_init(VOC_TASK_PCM_BUF_COUNT);
	voc_drv_paganini_dma_init_channel((uint64_t)addr + g_voc->miu_base,
					VOC_TASK_PCM_BUF_COUNT,
					VOC_CHANNEL_2,
					VOC_BIT_WIDTH_16,
					VOC_SAMPLE_RATE_16K);
	voc_mic_cap_iface_enable_interrupt(g_paganini.handler, mode);

	value = 1; //enable incgen
	voc_mic_cap_iface_set_attr(MIC_INCGEN_MODE, &value);
	voc_mic_cap_iface_start_channel(g_paganini.handler);
	mdelay(VOC_TASK_PCM_BUF_MS);
	mdelay(VOC_TASK_PCM_BUF_MS);
	voc_mic_cap_iface_stop_channel(g_paganini.handler);

	value = 0; //disable incgen
	voc_mic_cap_iface_set_attr(MIC_INCGEN_MODE, &value);
	#if ENABLE_DUMP_PCM_BUFFER
	dump_buffer();
	#endif

	g_voc->voc_card->status = voc_drv_paganini_check_slt_result();
	voc_drv_paganini_reset_voice();

	//enhance
	voc_drv_paganini_slt_init(VOC_TASK_PCM_BUF_COUNT);
	voc_hal_paganini_set_dma_buf_addr((uint64_t)addr, VOC_TASK_PCM_BUF_COUNT);
	pVAddr = (void *)g_voc->pcm->streams[1].substream->dma_buffer.area;
	voc_hal_paganini_set_virtual_buf_addr(pVAddr, VOC_TASK_PCM_BUF_COUNT);
	if (g_voc->voc_card->status == 1 &&
		voc_hal_paganini_slt_test((VOC_TASK_PCM_BUF_MS << 1)) == 0)
		g_voc->voc_card->status = voc_drv_paganini_check_slt_result();
	return 0;
}
//==============================================================================
int voc_drv_paganini_ts_sync_start(void)
{
	return 0;
}
//==============================================================================
int voc_drv_paganini_enable_seamless(bool en)
{
	return voc_hal_paganini_enable_seamless(en);
}
//==============================================================================
int voc_drv_paganini_notify_status(int32_t status)
{
	int ret = voc_hal_paganini_notify_status(status);

	if (status == 1 && voc_hal_paganini_get_keyword_status()) {
		g_paganini.keyword_flag = 1;
		wake_up_interruptible(&g_paganini.kthread_waitq);
	}
	return ret;
}
//==============================================================================
int voc_drv_paganini_power_status(int32_t status)
{
	UNUSED(status);
	return 0;
}
//==============================================================================
int voc_drv_paganini_dma_get_reset_status(void)
{
	int status = voc_mic_cap_iface_get_status(g_paganini.handler);

	return (status == MIC_STATUS_STARTED) || (status == MIC_STATUS_XRUN);
}
//==============================================================================
int voc_drv_paganini_vd_task_state(void)
{
	return 0;
}
//==============================================================================
void *voc_drv_paganini_get_voice_packet_addr(void)
{
	return (void *)&g_paganini.voice_msg;
}

//==============================================================================
static int voc_drv_paganini_get_handler(uint8_t cmd, void *params)
{
	UNUSED(cmd);
	UNUSED(params);
	return 0;
}

//==============================================================================
static int voc_drv_paganini_set_handler(uint8_t cmd, void *params)
{
	int32_t value;

	switch (cmd) {
	case VOC_CMD_SET_MIC_INPUT_MUX:
		value = (int32_t)(*((unsigned int *)params));
		voc_mic_cap_iface_set_attr(MIC_INPUT_MODE, &value);
		break;
	case VOC_CMD_SET_MIC_SEQUENCE:
		voc_dev_warn(NULL, "%s:set mic sequence is not support\n", __func__);
		break;
	case VOC_CMD_SET_MIC_LOG_LEVEL:
		value = (int32_t)(*((unsigned long *)params));
		voc_mic_cap_iface_set_log_level(value);
		break;
	case VOC_CMD_SET_MIC_OWL_INIT:
		voc_hal_paganini_dma_config_owl();
		break;
	default:
		voc_err("%s:command 0x%x is not support\n", __func__, cmd);
		return 0;
	}
	return 0;
}
//==============================================================================
static void voc_drv_paganini_dma_status_handler(struct work_struct *work)
{
	if (g_paganini.dma_buf_wr_enable) {
		if (g_paganini.full_trigger) {
			voc_info("[%s] full detected\n", __func__);
			voc_mic_cap_iface_stop_channel(g_paganini.handler);
			g_paganini.dma_buf_wr_enable = FALSE;
			voc_mic_cap_iface_clear_interrupt_status(g_paganini.handler, MIC_INT_FULL);
			g_paganini.voc_delta_time = 0;
			//[cm4]voc_state = VOICE_STATE_XRUN;
		} else if (g_paganini.overrun_trigger) {
			//no need to handle overrun.
		}
	}
}

//==============================================================================
static int voc_drv_paganini_dts_init(void)
{
	int ret = 0;
	struct device_node *np;
	u32 u32Tmp;

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

	ret = of_property_read_u32(np, "bck_mode", &u32Tmp);

	if ((ret >= 0) && (u32Tmp <= VOC_BCK_MODE_MAX))
		g_paganini.bck_mode = (u16)u32Tmp;
	else
		g_paganini.bck_mode = VOC_DEFAULT_BCK_MODE;

	ret = of_property_read_u32(np, "bck_drive", &u32Tmp);

	if ((ret >= 0) && (u32Tmp <= VOC_BCK_DRIVE_MAX))
		g_paganini.bck_drive = (u16)u32Tmp;
	else
		g_paganini.bck_drive = VOC_DEFAULT_BCK_DRIVE;


	of_node_put(np);

	return ret;
}
//==============================================================================
void voc_drv_paganini_exit(void)
{
	kthread_stop(g_paganini.thread_task);
}
//==============================================================================
void voc_drv_paganini_init(void)
{
	int32_t value;

	g_paganini.voc_delta_time = 0;
	g_paganini.dma_buf_wr_enable = 0;
	g_paganini.dma_seamless_mode = 0;
	g_paganini.block_mode = 1;
	voc_common_dts_init();
	voc_common_timer2_init();

	voc_drv_paganini_dts_init();
	voc_mic_cap_iface_init();
	voc_hal_paganini_init(); //For seamless feature

	value = (int32_t)g_paganini.bck_mode;
	if (voc_mic_cap_iface_set_attr(MIC_BCK_MODE, &value) < 0)
		voc_warn("[%s] bck mode(%d) set error, use default\n", __func__, value);
	value = (int32_t)g_paganini.bck_drive;
	if (voc_mic_cap_iface_set_attr(MIC_BCK_DRIVE, &value) < 0)
		voc_warn("[%s] bck drive(%d) set error, use default\n", __func__, value);

	INIT_WORK(&g_paganini.work_dma_status, voc_drv_paganini_dma_status_handler);

	init_waitqueue_head(&g_paganini.kthread_waitq);
	g_paganini.thread_task = kthread_run(voc_drv_paganini_pcm_handler_thread,
										0, "paganini_task");
	if (IS_ERR(g_paganini.thread_task)) {
		voc_err("create thread for paganini failed!\n");
		return;
	}
}
//==============================================================================
struct voc_communication_operater *voc_drv_paganini_get_op(void)
{
	return &voc_paganini_op;
}
EXPORT_SYMBOL_GPL(voc_drv_paganini_get_op);

static struct voc_communication_operater voc_paganini_op = {
	.bind = voc_drv_paganini_bind,
	.dma_start_channel = voc_drv_paganini_dma_start_channel,
	.dma_stop_channel = voc_drv_paganini_dma_stop_channel,
	.dma_init_channel = voc_drv_paganini_dma_init_channel,
	.dma_enable_sinegen =  voc_drv_paganini_dma_enable_sinegen,
	.dma_get_reset_status = voc_drv_paganini_dma_get_reset_status,
	.enable_vq = voc_drv_paganini_enable_vq,
	.config_vq = voc_drv_paganini_config_vq,
	.enable_hpf = voc_drv_paganini_enable_hpf,
	.config_hpf = voc_drv_paganini_config_hpf,
	.dmic_gain = voc_drv_paganini_dmic_gain,
	.dmic_mute = voc_drv_paganini_dmic_mute,
	.dmic_switch = voc_drv_paganini_dmic_switch,
	.reset_voice = voc_drv_paganini_reset_voice,
	.update_snd_model = voc_drv_paganini_update_snd_model,
	.get_hotword_ver = voc_drv_paganini_get_hotword_ver,
	.get_hotword_identifier = voc_drv_paganini_get_hotword_identifier,
	.enable_slt_test = voc_drv_paganini_enable_slt_test,
	.enable_seamless = voc_drv_paganini_enable_seamless,
	.notify_status = voc_drv_paganini_notify_status,
	.power_status = voc_drv_paganini_power_status,
	.ts_sync_start = voc_drv_paganini_ts_sync_start,
	.get_voice_packet_addr = voc_drv_paganini_get_voice_packet_addr,
	.get = voc_drv_paganini_get_handler,
	.set = voc_drv_paganini_set_handler,
};

