// SPDX-License-Identifier: GPL-2.0
/*
 *  vow.c  --  VoW platform driver
 *
 *  Copyright (c) 2020 MediaTek Inc.
 *  Author: Michael HSiao <michael.hsiao@mediatek.com>
 */

/*****************************************************************************
 * Header Files
 *****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/mm_types.h>
#include <linux/mm.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <asm/page.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/printk.h>
#include <linux/miscdevice.h>
/* #include <linux/wakelock.h> */
#include <linux/pm_wakeup.h>
#include <linux/compat.h>
#include <linux/uaccess.h>
#include <linux/sysfs.h>
#include <linux/notifier.h>  /* FOR SCP REVOCER */
#ifdef SIGTEST
#include <asm/siginfo.h>
#endif
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/kthread.h>
#include <linux/pinctrl/consumer.h>
#include <uapi/linux/sched/types.h>

#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
#include "scp_helper.h"
#include "scp_ipi.h"
#include "scp_excep.h"
#include "audio_task_manager.h"
#include "audio_ipi_queue.h"
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */
#include "vow.h"
#include "vow_scp.h"
#include "vow_assert.h"


/*****************************************************************************
 * Variable Definition
 ****************************************************************************/
static unsigned int VowDrv_Wait_Queue_flag;
static unsigned int VoiceData_Wait_Queue_flag;
static DECLARE_WAIT_QUEUE_HEAD(VowDrv_Wait_Queue);
static DECLARE_WAIT_QUEUE_HEAD(VoiceData_Wait_Queue);
static DEFINE_SPINLOCK(vowdrv_lock);
static struct wakeup_source *vow_suspend_lock;
static int init_flag = -1;

static struct file *file_recog_data;
static uint32_t recog_dump_data_routine_cnt_pass;
static struct wakeup_source pcm_dump_wake_lock;
static struct dump_queue_t *dump_queue;
static struct task_struct *pcm_dump_task;
static bool b_enable_dump;
static struct dump_work_t dump_work[NUM_DUMP_DATA];
static struct workqueue_struct *dump_workqueue[NUM_DUMP_DATA];
static wait_queue_head_t wq_dump_pcm;
struct pcm_dump_t {
	char decode_pcm[FRAME_BUF_SIZE];
};
#define DUMP_PCM_DATA_PATH "/data/vendor/audiohal/audio_dump"
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
static struct file *file_bargein_pcm_input;
static struct file *file_bargein_echo_ref;
static struct file *file_bargein_delay_info;
static uint32_t bargein_dump_data_routine_cnt_pass;
static bool bargein_dump_info_flag;
static bool file_bargein_pcm_input_open;
static bool file_bargein_echo_ref_open;
static bool file_bargein_delay_info_open;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
static bool file_recog_data_open;

/*****************************************************************************
 * Function  Declaration
 ****************************************************************************/
static void vow_service_getVoiceData(void);
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
static void vow_ipi_reg_ok(short uuid, int confidence_lv);
static bool vow_IPICmd_Send(uint8_t data_type,
			    uint8_t ack_type,
			    uint16_t msg_id,
			    uint32_t param1,
			    uint32_t param2,
			    char *payload);
static void vow_IPICmd_Received(struct ipi_msg_t *ipi_msg);
static bool vow_IPICmd_ReceiveAck(struct ipi_msg_t *ipi_msg);
static void vow_Task_Unloaded_Handling(void);
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */
static bool VowDrv_SetFlag(int type, unsigned int set);
static int VowDrv_GetHWStatus(void);
static void vow_service_OpenDumpFile(void);
static void vow_service_CloseDumpFile(void);
static void vow_service_OpenDumpFile_internal(void);
static void vow_service_CloseDumpFile_internal(void);
static void vow_pcm_dump_init(void);
static void vow_pcm_dump_deinit(void);
static int vow_pcm_dump_kthread(void *data);
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
static bool VowDrv_SetBargeIn(unsigned int set, unsigned int irq_id);
static void bargein_dump_routine(struct work_struct *ws);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
static void recog_dump_routine(struct work_struct *ws);
static int vow_service_SearchSpeakerModelWithUuid(int uuid);
static int vow_service_SearchSpeakerModelWithId(int id);

/*****************************************************************************
 * VOW SERVICES
 *****************************************************************************/

static struct
{
	struct vow_speaker_model_t  vow_speaker_model[MAX_VOW_SPEAKER_MODEL];
	unsigned long        vow_info_apuser[MAX_VOW_INFO_LEN];
	unsigned long        voicedata_user_addr;
	unsigned long        voicedata_user_size;
	short                *voicedata_kernel_ptr;
	char                 *voicddata_scp_ptr;
	dma_addr_t           voicedata_scp_addr;
	unsigned int         voicedata_idx;
	bool                 scp_command_flag;
	bool                 recording_flag;
	int                  scp_command_id;
	int                  confidence_level;
	int                  eint_status;
	int                  pwr_status;
	bool                 suspend_lock;
	bool                 firstRead;
	unsigned long        voicedata_user_return_size_addr;
	unsigned int         voice_buf_offset;
	unsigned int         voice_length;
	unsigned int         transfer_length;
	struct device_node   *node;
	struct pinctrl       *pinctrl;
	struct pinctrl_state *pins_eint_on;
	struct pinctrl_state *pins_eint_off;
	bool                 bypass_enter_phase3;
	unsigned int         enter_phase3_cnt;
	unsigned int         force_phase_stage;
	bool                 swip_log_enable;
	struct vow_eint_data_struct_t  vow_eint_data_struct;
	unsigned long long   scp_recognize_ok_cycle;
	unsigned long long   ap_received_ipi_cycle;
	bool                 tx_keyword_start;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	unsigned int         dump_frm_cnt;
	unsigned int         voice_sample_delay;
	unsigned int         bargein_dump_cnt1;
	unsigned int         bargein_dump_cnt2;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	short                *interleave_pcmdata_ptr;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
	bool                 dump_pcm_flag;
	bool                 scp_vow_lch;
	bool                 scp_recovering;
	bool                 vow_recovering;
	unsigned int         recog_dump_cnt1;
	unsigned int         recog_dump_cnt2;
	bool                 split_dumpfile_flag;
	unsigned int         mtkif_type;
	unsigned int         google_engine_version;
	char                 alexa_engine_version[VOW_ENGINE_INFO_LENGTH_BYTE];
	char                 google_engine_arch[VOW_ENGINE_INFO_LENGTH_BYTE];
} vowserv;

#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
static struct
{
	dma_addr_t    phy_addr;
	char          *vir_addr;
	uint32_t      size;
} bargein_resv_dram;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */

static struct
{
	dma_addr_t    phy_addr;
	char          *vir_addr;
	uint32_t      size;
} recog_resv_dram;

/*****************************************************************************
 * DSP IPI HANDELER
 *****************************************************************************/
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
static void vow_Task_Unloaded_Handling(void)
{
	VOWDRV_DEBUG("%s()\n", __func__);
}

static bool vow_IPICmd_ReceiveAck(struct ipi_msg_t *ipi_msg)
{
	bool result = false;


	switch (ipi_msg->msg_id) {
	case IPIMSG_VOW_ENABLE:
	case IPIMSG_VOW_DISABLE:
	case IPIMSG_VOW_SET_MODEL:
	case IPIMSG_VOW_SET_SMART_DEVICE:
	case IPIMSG_VOW_APREGDATA_ADDR:
	case IPIMSG_VOW_PCM_DUMP_ON:
	case IPIMSG_VOW_PCM_DUMP_OFF:
		if (ipi_msg->param1 == VOW_IPI_SUCCESS)
			result = true;
		break;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	case IPIMSG_VOW_SET_BARGEIN_ON:
	case IPIMSG_VOW_SET_BARGEIN_OFF:
		if (ipi_msg->param1 == VOW_IPI_SUCCESS)
			result = true;
		break;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	case IPIMSG_VOW_SET_FLAG:
		if (ipi_msg->param1 == VOW_IPI_SUCCESS) {
			unsigned int return_id;
			unsigned int return_value;

			result = true;
			return_id    = (ipi_msg->param2 >> WORD_H);
			return_value = (ipi_msg->param2 & WORD_L_MASK);
			switch (return_id) {
			case VOW_FLAG_FORCE_PHASE1_DEBUG:
			case VOW_FLAG_FORCE_PHASE2_DEBUG:
				vowserv.force_phase_stage = return_value;
				break;
			case VOW_FLAG_SWIP_LOG_PRINT:
				vowserv.swip_log_enable = return_value;
				break;
			default:
				break;
			}
		}
		break;
	default:
		break;
	}
	return result;
}

static void vow_IPICmd_Received(struct ipi_msg_t *ipi_msg)
{
	unsigned int *ptr32;

	switch (ipi_msg->msg_id) {
	case IPIMSG_VOW_COMBINED_INFO: {
		struct vow_ipi_combined_info_t *ipi_ptr;
		bool bypass_flag;

		ipi_ptr = (struct vow_ipi_combined_info_t *)ipi_msg->payload;
		/* IPIMSG_VOW_RECOGNIZE_OK */
		/*VOWDRV_DEBUG("[vow] IPIMSG_VOW_COMBINED_INFO\n");*/
		bypass_flag = false;
		if (ipi_ptr->ipi_type_flag & RECOG_OK_IDX_MASK) {
			if ((vowserv.recording_flag == true) &&
			    (vowserv.tx_keyword_start == true)) {
				VOWDRV_DEBUG("%s(), bypass this recog ok\n",
					__func__);
				bypass_flag = true;
			}
			if (bypass_flag == false) {
				vowserv.ap_received_ipi_cycle =
					get_cycles();
				vowserv.scp_recognize_ok_cycle =
					ipi_ptr->recog_ok_os_timer;
				vowserv.enter_phase3_cnt++;
				if (vowserv.bypass_enter_phase3 == false) {
					vow_ipi_reg_ok(
					    (short)ipi_ptr->recog_ok_uuid,
					    ipi_ptr->confidence_lv);
				}
			}
		}
		/* IPIMSG_VOW_DATAREADY */
		if ((ipi_ptr->ipi_type_flag & DEBUG_DUMP_IDX_MASK) &&
		    (vowserv.recording_flag)) {
			ptr32 = (unsigned int *)ipi_msg->payload;
			vowserv.voice_buf_offset = ipi_ptr->voice_buf_offset;
			vowserv.voice_length = ipi_ptr->voice_length;
			if (vowserv.voice_length > 320)
				VOWDRV_DEBUG("vow,v_len=%x\n",
					     vowserv.voice_length);
			vow_service_getVoiceData();
		}
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
		/* IPIMSG_VOW_BARGEIN_DUMP_INFO */
		if (ipi_ptr->ipi_type_flag & BARGEIN_DUMP_INFO_IDX_MASK) {
			vowserv.dump_frm_cnt = ipi_ptr->dump_frm_cnt;
			vowserv.voice_sample_delay =
				ipi_ptr->voice_sample_delay;
			VOWDRV_DEBUG("[BargeIn] dump write0 %d %d\n",
				     vowserv.dump_frm_cnt,
				     vowserv.voice_sample_delay);
			bargein_dump_info_flag = true;
		}
		/* IPIMSG_VOW_BARGEIN_PCMDUMP_OK */
		if ((ipi_ptr->ipi_type_flag & BARGEIN_DUMP_IDX_MASK) &&
		    (vowserv.dump_pcm_flag)) {
			int ret = 0;
			uint8_t idx = 0; /* dump_data_t */

			idx = DUMP_BARGEIN;
			dump_work[idx].mic_data_size = ipi_ptr->mic_dump_size;
			dump_work[idx].mic_offset = ipi_ptr->mic_offset;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
			dump_work[idx].mic_data_size_R =
					ipi_ptr->mic_dump_size_R;
			dump_work[idx].mic_offset_R = ipi_ptr->mic_offset_R;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
			dump_work[idx].echo_data_size = ipi_ptr->echo_dump_size;
			dump_work[idx].echo_offset = ipi_ptr->echo_offset;

			ret = queue_work(dump_workqueue[idx],
					 &dump_work[idx].work);
			if (ret == 0)
				bargein_dump_data_routine_cnt_pass++;
		}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
		if ((ipi_ptr->ipi_type_flag & RECOG_DUMP_IDX_MASK) &&
		    (vowserv.dump_pcm_flag)) {
			int ret = 0;
			uint8_t idx = 0; /* dump_data_t */

			idx = DUMP_RECOG;
			dump_work[idx].recog_data_size =
				ipi_ptr->recog_dump_size;
			dump_work[idx].recog_data_offset =
				ipi_ptr->recog_dump_offset;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
			dump_work[idx].recog_data_size_R =
				ipi_ptr->recog_dump_size_R;
			dump_work[idx].recog_data_offset_R =
				ipi_ptr->recog_dump_offset_R;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
			ret = queue_work(dump_workqueue[idx],
					 &dump_work[idx].work);
			if (ret == 0)
				recog_dump_data_routine_cnt_pass++;
		}
		break;
	}
	case IPIMSG_VOW_ALEXA_ENGINE_VER: {
		VOWDRV_DEBUG("%s(), IPIMSG_VOW_ALEXA_ENGINE_VER %s\r",
			__func__, ipi_msg->payload);
		memcpy(vowserv.alexa_engine_version, ipi_msg->payload,
				 sizeof(vowserv.alexa_engine_version));
		}
		break;
	case IPIMSG_VOW_GOOGLE_ENGINE_VER: {
		unsigned int *temp = (unsigned int *)ipi_msg->payload;

		VOWDRV_DEBUG("%s(), IPIMSG_VOW_GOOGLE_ENGINE_VER 0x%x\r",
			__func__, temp[0]);
		vowserv.google_engine_version = temp[0];
		}
		break;
	case IPIMSG_VOW_GOOGLE_ARCH: {
		VOWDRV_DEBUG("%s(), IPIMSG_VOW_GOOGLE_ARCH %s\r",
			__func__, ipi_msg->payload);
		memcpy(vowserv.google_engine_arch, ipi_msg->payload,
				 sizeof(vowserv.google_engine_arch));
		}
		break;
	default:
		break;
	}
}

static bool vow_IPICmd_Send(uint8_t data_type,
			    uint8_t ack_type, uint16_t msg_id, uint32_t param1,
			    uint32_t param2, char *payload)
{
	bool ret = false;
	struct ipi_msg_t ipi_msg;
	int ipi_result = -1;
	unsigned int retry_time = VOW_IPI_SEND_CNT_TIMEOUT;
	unsigned int retry_cnt;

	if (!vow_check_scp_status()) {
		VOWDRV_DEBUG("SCP is off, bypass send ipi id(%d)\n", msg_id);
		return false;
	}
	if (vowserv.scp_recovering == true) {
		VOWDRV_DEBUG("scp is recovering, then break\n");
		return false;
	}
	for (retry_cnt = 0; retry_cnt <= retry_time; retry_cnt++) {
		ipi_result = audio_send_ipi_msg(&ipi_msg,
						TASK_SCENE_VOW,
						AUDIO_IPI_LAYER_TO_DSP,
						data_type,
						ack_type,
						msg_id,
						param1,
						param2,
						payload);
		if (ipi_result == 0)
			break;
		if (vowserv.scp_recovering == true) {
			VOWDRV_DEBUG("scp is recovering, then break\n");
			break;
		}
		VOW_ASSERT(retry_cnt != retry_time);
		msleep(VOW_WAITCHECK_INTERVAL_MS);
	}
	if (ipi_result == 0) {
		/* ipi send pass */
		if (ipi_msg.ack_type == AUDIO_IPI_MSG_ACK_BACK)
			ret = vow_IPICmd_ReceiveAck(&ipi_msg);
		else
			ret = true;
	}
	return ret;
}

static void vow_ipi_reg_ok(short uuid, int confidence_lv)
{
	int slot;

	vowserv.scp_command_flag = true;
	/* transfer uuid to model handle id */
	slot = vow_service_SearchSpeakerModelWithUuid(uuid);
	if (slot < 0)
		return;
	vowserv.scp_command_id = vowserv.vow_speaker_model[slot].id;
	vowserv.confidence_level = confidence_lv;
	VowDrv_Wait_Queue_flag = 1;
	wake_up_interruptible(&VowDrv_Wait_Queue);
}

static void vow_register_feature(enum feature_id id)
{
	if (!vow_check_scp_status()) {
		VOWDRV_DEBUG("SCP is off, bypass register id(%d)\n", id);
		return;
	}
	scp_register_feature(id);
}

static void vow_deregister_feature(enum feature_id id)
{
	if (!vow_check_scp_status()) {
		VOWDRV_DEBUG("SCP is off, bypass deregister id(%d)\n", id);
		return;
	}
	scp_deregister_feature(id);
}
#endif
static void vow_service_getVoiceData(void)
{
	if (VoiceData_Wait_Queue_flag == 0) {
		VoiceData_Wait_Queue_flag = 1;
		wake_up_interruptible(&VoiceData_Wait_Queue);
	} else {
		/* VOWDRV_DEBUG("getVoiceData but no one wait for it, */
		/* may lost it!!\n"); */
	}
}

/*****************************************************************************
 * DSP SERVICE FUNCTIONS
 *****************************************************************************/
static void vow_service_Init(void)
{
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	int I;
	bool ret;
	unsigned int vow_ipi_buf[1];

	VOWDRV_DEBUG("%s():%x\n", __func__, init_flag);
	audio_load_task(TASK_SCENE_VOW);
	if (init_flag != 1) {

		/*register IPI handler*/
		audio_task_register_callback(TASK_SCENE_VOW,
					     vow_IPICmd_Received,
					     vow_Task_Unloaded_Handling);
		/*Initialization*/
		VowDrv_Wait_Queue_flag = 0;
		VoiceData_Wait_Queue_flag = 0;
		vowserv.scp_command_flag = false;
		vowserv.recording_flag = false;
		vowserv.suspend_lock = 0;
		vowserv.voice_length = 0;
		vowserv.firstRead = false;
		vowserv.voice_buf_offset = 0;
		vowserv.bypass_enter_phase3 = false;
		vowserv.enter_phase3_cnt = 0;
		vowserv.scp_recovering = false;
		vowserv.vow_recovering = false;
		spin_lock(&vowdrv_lock);
		vowserv.pwr_status = VOW_PWR_OFF;
		vowserv.eint_status = VOW_EINT_DISABLE;
		spin_unlock(&vowdrv_lock);
		vowserv.force_phase_stage = NO_FORCE;
		vowserv.swip_log_enable = true;
		vowserv.voicedata_user_addr = 0;
		vowserv.voicedata_user_size = 0;
		vowserv.voicedata_user_return_size_addr = 0;
		vowserv.tx_keyword_start = false;
		for (I = 0; I < MAX_VOW_SPEAKER_MODEL; I++) {
			vowserv.vow_speaker_model[I].model_ptr = NULL;
			vowserv.vow_speaker_model[I].id = -1;
			vowserv.vow_speaker_model[I].keyword = -1;
			vowserv.vow_speaker_model[I].uuid = 0;
			vowserv.vow_speaker_model[I].flag = 0;
			vowserv.vow_speaker_model[I].enabled = 0;
		}
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
		vowserv.voicddata_scp_ptr =
		    (char *)(scp_get_reserve_mem_virt(VOW_MEM_ID))
		    + VOW_VOICEDATA_OFFSET;
		vowserv.voicedata_scp_addr =
		    scp_get_reserve_mem_phys(VOW_MEM_ID)
		    + VOW_VOICEDATA_OFFSET;
#else
		VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
		vowserv.voicedata_kernel_ptr = NULL;
		vowserv.voicedata_idx = 0;
		init_flag = 1;
		vowserv.dump_pcm_flag = false;
		vowserv.split_dumpfile_flag = false;
		dump_queue = NULL;
		vow_pcm_dump_init();
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
		bargein_dump_info_flag = false;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
		vowserv.scp_vow_lch = true;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
		vowserv.interleave_pcmdata_ptr = NULL;
#endif
		vowserv.google_engine_version = 254868980;
		memset(vowserv.alexa_engine_version, 0, VOW_ENGINE_INFO_LENGTH_BYTE);
	} else {
		/*Initialization*/
		vowserv.voicddata_scp_ptr =
		    (char *)(scp_get_reserve_mem_virt(VOW_MEM_ID))
		    + VOW_VOICEDATA_OFFSET;
		vowserv.voicedata_scp_addr =
		    scp_get_reserve_mem_phys(VOW_MEM_ID) + VOW_VOICEDATA_OFFSET;
		vow_ipi_buf[0] = vowserv.voicedata_scp_addr;
		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_BYPASS_ACK,
				      IPIMSG_VOW_APREGDATA_ADDR,
				      sizeof(unsigned int) * 1, 0,
				      (char *)&vow_ipi_buf[0]);
		if (ret == 0) {
			VOWDRV_DEBUG(
			"IPIMSG_VOW_APREGDATA_ADDR ipi send error\n");
		}
#if VOW_PRE_LEARN_MODE
		VowDrv_SetFlag(VOW_FLAG_PRE_LEARN, true);
#endif
	}
	vow_IPICmd_Send(AUDIO_IPI_MSG_ONLY,
		      AUDIO_IPI_MSG_BYPASS_ACK,
		      IPIMSG_VOW_GET_ALEXA_ENGINE_VER,
		      0, 0,
		      NULL);
	vow_IPICmd_Send(AUDIO_IPI_MSG_ONLY,
		      AUDIO_IPI_MSG_BYPASS_ACK,
		      IPIMSG_VOW_GET_GOOGLE_ENGINE_VER,
		      0, 0,
		      NULL);
	vow_IPICmd_Send(AUDIO_IPI_MSG_ONLY,
		      AUDIO_IPI_MSG_BYPASS_ACK,
		      IPIMSG_VOW_GET_GOOGLE_ARCH,
		      0, 0,
		      NULL);
#else  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */
}

int vow_service_GetParameter(unsigned long arg)
{
	if (copy_from_user((void *)(&vowserv.vow_info_apuser[0]),
			   (const void __user *)(arg),
			   sizeof(vowserv.vow_info_apuser))) {
		VOWDRV_DEBUG("vow get parameter fail\n");
		return -EFAULT;
	}
	VOWDRV_DEBUG("vow get parameter: %lu %lu %lu %lu %lu %lu %lu\n",
		     vowserv.vow_info_apuser[0],
		     vowserv.vow_info_apuser[1],
		     vowserv.vow_info_apuser[2],
		     vowserv.vow_info_apuser[3],
		     vowserv.vow_info_apuser[4],
		     vowserv.vow_info_apuser[5],
		     vowserv.vow_info_apuser[6]);

	return 0;
}

#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
static int vow_service_CopyModel(int slot)
{
	if (vowserv.vow_info_apuser[3] > VOW_MODEL_SIZE) {
		VOWDRV_DEBUG("vow DMA Size Too Large\n");
		return -EFAULT;
	}
	if (copy_from_user((void *)(vowserv.vow_speaker_model[slot].model_ptr),
			   (const void __user *)(vowserv.vow_info_apuser[2]),
			   vowserv.vow_info_apuser[3])) {
		VOWDRV_DEBUG("vow Copy Speaker Model Fail\n");
		return -EFAULT;
	}
	vowserv.vow_speaker_model[slot].flag = 1;
	vowserv.vow_speaker_model[slot].enabled = 0;
	vowserv.vow_speaker_model[slot].id = vowserv.vow_info_apuser[0];
	vowserv.vow_speaker_model[slot].keyword = vowserv.vow_info_apuser[1];
	vowserv.vow_speaker_model[slot].uuid = vowserv.vow_info_apuser[5];
	vowserv.vow_speaker_model[slot].model_size = vowserv.vow_info_apuser[3];

	return 0;
}
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */

static int vow_service_FindFreeSpeakerModel(void)
{
	int I;

	I = 0;
	do {
		if (vowserv.vow_speaker_model[I].flag == 0)
			break;
		I++;
	} while (I < MAX_VOW_SPEAKER_MODEL);

	VOWDRV_DEBUG("vow FindFreeSpeakerModel:%d\n", I);

	if (I == MAX_VOW_SPEAKER_MODEL) {
		VOWDRV_DEBUG("vow Find Free Speaker Model Fail\n");
		return -1;
	}
	return I;
}

static int vow_service_SearchSpeakerModelWithId(int id)
{
	int I, J;

	I = 0;
	do {
		if (vowserv.vow_speaker_model[I].id == id)
			break;
		I++;
	} while (I < MAX_VOW_SPEAKER_MODEL);

	if (I == MAX_VOW_SPEAKER_MODEL) {
		VOWDRV_DEBUG("vow Search Speaker Model By ID Fail:%x\n", id);
		for (J = 0; J < MAX_VOW_SPEAKER_MODEL; J++) {
			/* print all id */
			VOWDRV_DEBUG("id[%d]=%d\n",
				     J, vowserv.vow_speaker_model[J].id);
		}
		return -1;
	}
	return I;
}

static int vow_service_SearchSpeakerModelWithUuid(int uuid)
{
	int I, J;

	I = 0;
	do {
		if (vowserv.vow_speaker_model[I].uuid == uuid)
			break;
		I++;
	} while (I < MAX_VOW_SPEAKER_MODEL);

	if (I == MAX_VOW_SPEAKER_MODEL) {
		VOWDRV_DEBUG("vow Search Speaker Model By UUID Fail:%x\n",
			     uuid);
		for (J = 0; J < MAX_VOW_SPEAKER_MODEL; J++) {
			/* print all uuid */
			VOWDRV_DEBUG("uuid[%d]=%d\n",
				     J, vowserv.vow_speaker_model[J].uuid);
		}
		return -1;
	}
	return I;
}

static bool vow_service_SendSpeakerModel(int slot, bool release_flag)
{
	bool ret = false;
	unsigned int vow_ipi_buf[5];
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	if (slot >= MAX_VOW_SPEAKER_MODEL) {
		VOWDRV_DEBUG("%s(), slot id=%d, over range\n", __func__, slot);
		return ret;
	}

	if (release_flag == VOW_CLEAN_MODEL) {
		if (vowserv.vow_speaker_model[slot].flag == 0) {
			VOWDRV_DEBUG("%s(), slot:%d, no model need to clean\n",
				     __func__, slot);
			return ret;
		}
		vow_ipi_buf[0] = VOW_MODEL_CLEAR;
	} else {  /* VOW_SET_MODEL */
		if ((vowserv.vow_speaker_model[slot].flag == 0) ||
		    (vowserv.vow_speaker_model[slot].id == -1) ||
		    (vowserv.vow_speaker_model[slot].keyword == -1) ||
		    (vowserv.vow_speaker_model[slot].uuid == 0) ||
		    (vowserv.vow_speaker_model[slot].model_size == 0)) {
			VOWDRV_DEBUG("%s(), slot:%d, no model need to set\n",
				     __func__, slot);
			return ret;
		}
		vow_ipi_buf[0] = VOW_MODEL_SPEAKER;
	}
	vow_ipi_buf[1] = vowserv.vow_speaker_model[slot].uuid;
	vow_ipi_buf[2] = scp_get_reserve_mem_phys(VOW_MEM_ID) +
			 (VOW_MODEL_SIZE * slot);
	vow_ipi_buf[3] = vowserv.vow_speaker_model[slot].model_size;
	vow_ipi_buf[4] = vowserv.vow_speaker_model[slot].keyword;

	VOWDRV_DEBUG(
	    "Model:slot_%d, model_%x, addr_%x, id_%x, size_%x, keyword_%x\n",
		      slot,
		      vow_ipi_buf[0],
		      vow_ipi_buf[2],
		      vow_ipi_buf[1],
		      vow_ipi_buf[3],
		      vow_ipi_buf[4]);

	ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
			      AUDIO_IPI_MSG_BYPASS_ACK,
			      IPIMSG_VOW_SET_MODEL,
			      sizeof(unsigned int) * 4, 0,
			      (char *)&vow_ipi_buf[0]);
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	return ret;
}

static bool vow_service_ReleaseSpeakerModel(int id)
{
	int I;
	bool ret = false;

	I = vow_service_SearchSpeakerModelWithId(id);

	if (I == -1) {
		VOWDRV_DEBUG("vow release Speaker Model Fail, id:%x\n", id);
		return false;
	}
	VOWDRV_DEBUG("vow ReleaseSpeakerModel:id_%x, slot_%d\n", id, I);

	ret = vow_service_SendSpeakerModel(I, VOW_CLEAN_MODEL);

	vowserv.vow_speaker_model[I].model_ptr = NULL;
	vowserv.vow_speaker_model[I].uuid = 0;
	vowserv.vow_speaker_model[I].id = -1;
	vowserv.vow_speaker_model[I].keyword = -1;
	vowserv.vow_speaker_model[I].flag = 0;
	vowserv.vow_speaker_model[I].enabled = 0;

	return ret;
}

static bool vow_service_SetSpeakerModel(unsigned long arg)
{
	bool ret = false;
	int I;
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	char *ptr8;
#endif

	I = vow_service_FindFreeSpeakerModel();
	if (I == -1)
		return false;

	vow_service_GetParameter(arg);
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	vowserv.vow_speaker_model[I].model_ptr =
	   (void *)(scp_get_reserve_mem_virt(VOW_MEM_ID))
	   + (VOW_MODEL_SIZE * I);

	if (vow_service_CopyModel(I) != 0)
		return false;

	ptr8 = (char *)vowserv.vow_speaker_model[I].model_ptr;
	VOWDRV_DEBUG("SetSPKModel:slot: %d, ID: %x, UUID: %x, flag: %x, keyword: %x\n",
		      I,
		      vowserv.vow_speaker_model[I].id,
		      vowserv.vow_speaker_model[I].uuid,
		      vowserv.vow_speaker_model[I].flag,
		      vowserv.vow_speaker_model[I].keyword);
	VOWDRV_DEBUG("vow SetSPKModel:CheckValue:%x %x %x %x %x %x\n",
		      *(char *)&ptr8[0], *(char *)&ptr8[1],
		      *(char *)&ptr8[2], *(char *)&ptr8[3],
		      *(short *)&ptr8[160], *(int *)&ptr8[7960]);

	ret = vow_service_SendSpeakerModel(I, VOW_SET_MODEL);
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	return ret;
}

static bool vow_service_SendModelStatus(int slot, bool enable)
{
	bool ret = false;
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	unsigned int vow_ipi_buf[2];

	if (slot >= MAX_VOW_SPEAKER_MODEL) {
		VOWDRV_DEBUG("%s(), slot id=%d, over range\n", __func__, slot);
		return ret;
	}
	if (!vowserv.vow_speaker_model[slot].flag) {
		VOWDRV_DEBUG("%s(), this speaker model isn't load\n", __func__);
		return ret;
	}

	vow_ipi_buf[0] = vowserv.vow_speaker_model[slot].uuid;
	vow_ipi_buf[1] = vowserv.vow_speaker_model[slot].confidence_lv;

	if (enable == VOW_MODEL_STATUS_START) {
		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_BYPASS_ACK,
				      IPIMSG_VOW_MODEL_START,
				      sizeof(unsigned int) * 2, 0,
				      (char *)&vow_ipi_buf[0]);
	} else {  /* VOW_MODEL_STATUS_STOP */
		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_BYPASS_ACK,
				      IPIMSG_VOW_MODEL_STOP,
				      sizeof(unsigned int) * 2, 0,
				      (char *)&vow_ipi_buf[0]);
	}
#else
	(void)enable;
	(void)slot;
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	return ret;
}

static void vow_register_vendor_feature(int uuid)
{
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	switch (uuid) {
	case VENDOR_ID_MTK:
		vow_register_feature(VOW_VENDOR_M_FEATURE_ID);
		break;
	case VENDOR_ID_AMAZON:
		vow_register_feature(VOW_VENDOR_A_FEATURE_ID);
		break;
	case VENDOR_ID_OTHERS:
		vow_register_feature(VOW_VENDOR_G_FEATURE_ID);
		break;
	default:
		VOWDRV_DEBUG("VENDOR ID not support\n");
		break;
	}
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
}

static void vow_deregister_vendor_feature(int uuid)
{
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	switch (uuid) {
	case VENDOR_ID_MTK:
		vow_deregister_feature(VOW_VENDOR_M_FEATURE_ID);
		break;
	case VENDOR_ID_AMAZON:
		vow_deregister_feature(VOW_VENDOR_A_FEATURE_ID);
		break;
	case VENDOR_ID_OTHERS:
		vow_deregister_feature(VOW_VENDOR_G_FEATURE_ID);
		break;
	default:
		VOWDRV_DEBUG("VENDOR ID not support\n");
		break;
	}
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
}

static bool vow_service_SetModelStatus(bool enable, unsigned long arg)
{
	bool ret = false;
	int slot;
	struct vow_model_start_t model_start;

	if (copy_from_user((void *)&model_start,
			   (const void __user *)(arg),
			   sizeof(struct vow_model_start_t))) {
		VOWDRV_DEBUG("vow get vow_model_start data fail\n");
		return false;
	}

	slot = vow_service_SearchSpeakerModelWithId(model_start.handle);
	if (slot < 0) {
		VOWDRV_DEBUG("%s(), no match id\n", __func__);
		return false;
	}
	if (enable == VOW_MODEL_STATUS_START) {
		if (vowserv.vow_speaker_model[slot].enabled == 0) {
			int uuid;

			uuid = vowserv.vow_speaker_model[slot].uuid;
			vow_register_vendor_feature(uuid);
		}
		vowserv.vow_speaker_model[slot].enabled = 1;
		vowserv.vow_speaker_model[slot].confidence_lv =
			model_start.confidence_level;
		VOWDRV_DEBUG("VOW_MODEL_START, id:%d\n",
			     (int)model_start.handle);
		/* send model status to scp */
		ret = vow_service_SendModelStatus(slot, enable);
		if (ret == false)
			VOWDRV_DEBUG("vow_service_SendModelStatus, err\n");
	} else {  /* VOW_MODEL_STATUS_STOP */
		vowserv.vow_speaker_model[slot].confidence_lv =
			model_start.confidence_level;
		/* send model status to scp */
		ret = vow_service_SendModelStatus(slot, enable);
		if (ret == false)
			VOWDRV_DEBUG("vow_service_SendModelStatus, err\n");
		if (vowserv.vow_speaker_model[slot].enabled == 1) {
			int uuid;

			uuid = vowserv.vow_speaker_model[slot].uuid;
			vow_deregister_vendor_feature(uuid);
		}
		vowserv.vow_speaker_model[slot].enabled = 0;
		VOWDRV_DEBUG("VOW_MODEL_STOP, id:%d\n",
			     (int)model_start.handle);
	}
	return ret;
}

static bool vow_service_SetApAddr(unsigned long arg)
{
	unsigned long vow_info[MAX_VOW_INFO_LEN];

	if (copy_from_user((void *)(&vow_info[0]), (const void __user *)(arg),
			   sizeof(vowserv.vow_info_apuser))) {
		VOWDRV_DEBUG("vow check parameter fail\n");
		return false;
	}

	/* add return condition */
	if ((vow_info[2] == 0) || (vow_info[3] != VOW_VBUF_LENGTH) ||
	    (vow_info[4] == 0)) {
		VOWDRV_DEBUG("vow SetVBufAddr:addr_%x, size_%x, addr_%x\n",
		 (unsigned int)vow_info[2],
		 (unsigned int)vow_info[3],
		 (unsigned int)vow_info[4]);
		return false;
	}

	vowserv.voicedata_user_addr = vow_info[2];
	vowserv.voicedata_user_size = vow_info[3];
	vowserv.voicedata_user_return_size_addr = vow_info[4];

	return true;
}

static bool vow_service_SetVBufAddr(unsigned long arg)
{
	if (!(vow_service_SetApAddr(arg)))
		return false;

	VOWDRV_DEBUG("vow SetVBufAddr:addr_%x, size_%x\n",
		 (unsigned int)vowserv.vow_info_apuser[2],
		 (unsigned int)vowserv.vow_info_apuser[3]);

	/* add return condition */
	if ((vowserv.vow_info_apuser[2] == 0) ||
	    (vowserv.vow_info_apuser[3] != VOW_VBUF_LENGTH) ||
	    (vowserv.vow_info_apuser[4] == 0))
		return false;

	if (vowserv.voicedata_kernel_ptr != NULL) {
		vfree(vowserv.voicedata_kernel_ptr);
		vowserv.voicedata_kernel_ptr = NULL;
	}

	vowserv.voicedata_user_addr = vowserv.vow_info_apuser[2];
	vowserv.voicedata_user_size = vowserv.vow_info_apuser[3];
	vowserv.voicedata_user_return_size_addr = vowserv.vow_info_apuser[4];

	if (vowserv.voicedata_user_size > 0) {
		vowserv.voicedata_kernel_ptr =
		    vmalloc(vowserv.voicedata_user_size);
		return true;
	} else {
		return false;
	}
}

static bool vow_service_Enable(void)
{
	bool ret = false;

	VOWDRV_DEBUG("+%s()\n", __func__);
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	ret = vow_IPICmd_Send(AUDIO_IPI_MSG_ONLY,
			      AUDIO_IPI_MSG_BYPASS_ACK,
			      IPIMSG_VOW_ENABLE,
			      0, 0,
			      NULL);
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	VOWDRV_DEBUG("-%s():%d\n", __func__, ret);
	return ret;
}

static bool vow_service_Disable(void)
{
	bool ret = false;

	VOWDRV_DEBUG("+%s()\n", __func__);
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	ret = vow_IPICmd_Send(AUDIO_IPI_MSG_ONLY,
			      AUDIO_IPI_MSG_BYPASS_ACK,
			      IPIMSG_VOW_DISABLE,
			      0, 0,
			      NULL);
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	/* release lock */
	if (vowserv.suspend_lock == 1) {
		vowserv.suspend_lock = 0;
		/* Let AP will suspend */
		__pm_relax(vow_suspend_lock);
	}
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	vow_deregister_feature(VOW_FEATURE_ID);
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	return ret;
}

static void vow_check_boundary(unsigned int copy_len, unsigned int bound_len)
{
	if (copy_len > bound_len) {
		VOWDRV_DEBUG("[vow check]copy_len=0x%x, bound_len=0x%x\n",
			     copy_len, bound_len);
		VOW_ASSERT(0);
	}
}

#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
static void vow_interleaving(short *out_buf,
			     short *l_sample,
			     short *r_sample,
			     unsigned int buf_length)
{
	int i;
	int smpl_max;

	smpl_max = buf_length / 2;
	for (i = 0; i < smpl_max; i++) {
		*out_buf++ = *l_sample++;
		*out_buf++ = *r_sample++;
	}
}
#endif

static int vow_service_ReadVoiceData_Internal(unsigned int buf_offset,
					      unsigned int buf_length)
{
	int stop_condition = 0;

	VOW_ASSERT(vowserv.voicedata_kernel_ptr != NULL);

	if (buf_length != 0) {
		if ((vowserv.voicedata_idx + (buf_length >> 1))
		  > (vowserv.voicedata_user_size >> 1)) {
			VOWDRV_DEBUG(
			    "[vow check]data_idx=0x%x(W),buf_length=0x%x(B)\n",
			    vowserv.voicedata_idx, buf_length);
			VOWDRV_DEBUG(
		    "[vow check] user_size=0x%x(B), voice_buf_offset=0x%x(B)\n",
			    (unsigned int)vowserv.voicedata_user_size,
			    buf_offset);
			/* VOW_ASSERT(0); */
			vowserv.voicedata_idx = 0;
		}
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
		/* start interleaving L+R */
		vow_interleaving(
			&vowserv.voicedata_kernel_ptr[vowserv.voicedata_idx],
			(short *)(vowserv.voicddata_scp_ptr + buf_offset),
			(short *)(vowserv.voicddata_scp_ptr + buf_offset +
			    VOW_VOICEDATA_SIZE),
			buf_length);
		/* end interleaving*/
#else  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
		memcpy(&vowserv.voicedata_kernel_ptr[vowserv.voicedata_idx],
		       vowserv.voicddata_scp_ptr + buf_offset, buf_length);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */

		if (buf_length > VOW_VOICE_RECORD_BIG_THRESHOLD) {
			/* means now is start to transfer */
			/* keyword buffer(64kB) to AP */
			VOWDRV_DEBUG("%s(), start tx keyword, 0x%x/0x%x\n",
				     __func__,
				     vowserv.voicedata_idx,
				     buf_length);
			vowserv.tx_keyword_start = true;
		}

#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
		/* 2 Channels */
		vowserv.voicedata_idx += buf_length;
#else
		/* 1 Channel */
		vowserv.voicedata_idx += (buf_length >> 1);
#endif
	}
	if (vowserv.voicedata_idx >= (VOW_VOICE_RECORD_BIG_THRESHOLD >> 1))
		vowserv.transfer_length = VOW_VOICE_RECORD_BIG_THRESHOLD;
	else
		vowserv.transfer_length = VOW_VOICE_RECORD_THRESHOLD;

	if (vowserv.voicedata_idx >= (vowserv.transfer_length >> 1)) {
		unsigned int ret;

		/*VOWDRV_DEBUG("TX Leng:%d, %d, [%d]\n",*/
		/*	     vowserv.voicedata_idx,*/
		/*	     buf_length,*/
		/*	     vowserv.transfer_length);*/

		ret = copy_to_user(
		      (void __user *)(vowserv.voicedata_user_return_size_addr),
		      &vowserv.transfer_length,
		      4);

		ret = copy_to_user(
		      (void __user *)vowserv.voicedata_user_addr,
		      vowserv.voicedata_kernel_ptr,
		      vowserv.transfer_length);

		/* move left data to buffer's head */
		if (vowserv.voicedata_idx > (vowserv.transfer_length >> 1)) {
			unsigned int tmp;
			unsigned int idx;

			tmp = (vowserv.voicedata_idx << 1)
			      - vowserv.transfer_length;
			vow_check_boundary(tmp, vowserv.voicedata_user_size);
			idx = (vowserv.transfer_length >> 1);
			memcpy(&vowserv.voicedata_kernel_ptr[0],
			       &vowserv.voicedata_kernel_ptr[idx],
			       tmp);
			vowserv.voicedata_idx -= idx;
		} else
			vowserv.voicedata_idx = 0;

		/* speed up voicedata transfer to hal */
		if (vowserv.voicedata_idx >= VOW_VOICE_RECORD_THRESHOLD)
			vow_service_getVoiceData();

		stop_condition = 1;
	}
	if ((vowserv.tx_keyword_start == true)
	 && (vowserv.voicedata_idx < VOW_VOICE_RECORD_THRESHOLD)) {
		/* means now is end of transfer keyword buffer(64kB) to AP */
		vowserv.tx_keyword_start = false;
		VOWDRV_DEBUG("%s(), end tx keyword, 0x%x\n",
			     __func__,
			     vowserv.voicedata_idx);
	}

	return stop_condition;
}

static void vow_service_ReadVoiceData(void)
{
	int stop_condition = 0;

	/*int rdata;*/
	while (1) {
		if (VoiceData_Wait_Queue_flag == 0)
			wait_event_interruptible(VoiceData_Wait_Queue,
						 VoiceData_Wait_Queue_flag);

		if (VoiceData_Wait_Queue_flag == 1) {
			VoiceData_Wait_Queue_flag = 0;
			if ((VowDrv_GetHWStatus() == VOW_PWR_OFF)
			 || (vowserv.recording_flag == false)) {
				vowserv.voicedata_idx = 0;
				stop_condition = 1;
				VOWDRV_DEBUG(
				    "stop read vow voice data: %d, %d\n",
				    VowDrv_GetHWStatus(),
				    vowserv.recording_flag);
			} else {
				/* To Read Voice Data from Kernel to User */
				stop_condition =
				    vow_service_ReadVoiceData_Internal(
					vowserv.voice_buf_offset,
					vowserv.voice_length);
				vowserv.voice_buf_offset = 0;
				vowserv.voice_length = 0;
			}
			if (stop_condition == 1)
				break;
		}
	}
}

static void vow_service_reset(void)
{
	int I;
	bool need_disable_vow = false;
	bool ret = false;

	VOWDRV_DEBUG("+%s()\n", __func__);
	for (I = 0; I < MAX_VOW_SPEAKER_MODEL; I++) {
		if (vowserv.vow_speaker_model[I].enabled  == 1) {
			int uuid;

			/* need to close it */
			ret = vow_service_SendModelStatus(
				I, VOW_MODEL_STATUS_STOP);
			if (ret == false)
				VOWDRV_DEBUG("%s(), err\n", __func__);

			uuid = vowserv.vow_speaker_model[I].uuid;
			vow_deregister_vendor_feature(uuid);
			vowserv.vow_speaker_model[I].enabled = 0;
			need_disable_vow = true;
		}
		if (vowserv.vow_speaker_model[I].flag  == 1) {
			int id;

			/* need to clear model */
			id = vowserv.vow_speaker_model[I].id;
			vow_service_ReleaseSpeakerModel(id);
		}
	}
	if (need_disable_vow)
		vow_service_Disable();

	VOWDRV_DEBUG("-%s()\n", __func__);
}

static bool vow_stop_dump_wait(void)
{
	int timeout = 0;

	while (1) {
		msleep(VOW_WAITCHECK_INTERVAL_MS);
		if (timeout++ >= VOW_STOP_DUMP_WAIT)
			return false;
	}
	return true;
}

static int vow_pcm_dump_notify(bool enable)
{
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	unsigned int vow_ipi_buf[5] = {0};
	bool ret;

	/* if scp reset happened, need re-send PCM dump IPI to SCP again */
	if (enable == true) {
		/* dump flag */
		vow_ipi_buf[1] = vowserv.dump_pcm_flag;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
		/* TOTAL dram resrved size for barge in dump */
		vow_ipi_buf[0] = bargein_resv_dram.size;
		/* address for SCP using */
		vow_ipi_buf[2] = bargein_resv_dram.phy_addr;

		VOWDRV_DEBUG(
		"[BargeIn]dump on, dump flag:%d, resv sz:0x%x, addr:0x%x\n",
			     vow_ipi_buf[1],
			     vow_ipi_buf[0],
			     vow_ipi_buf[2]);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
		/* TOTAL dram resrved size for recog data dump */
		vow_ipi_buf[3] = recog_resv_dram.size;
		/* address for SCP using */
		vow_ipi_buf[4] = recog_resv_dram.phy_addr;

		VOWDRV_DEBUG(
		"[Recog]dump on, dump flag:%d, resv sz:0x%x, addr:0x%x\n",
			    vow_ipi_buf[1],
			    vow_ipi_buf[3],
			    vow_ipi_buf[4]);

		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_BYPASS_ACK,
				      IPIMSG_VOW_PCM_DUMP_ON,
				      sizeof(unsigned int) * 5, 0,
				      (char *)&vow_ipi_buf[0]);

		if (ret == 0)
			VOWDRV_DEBUG("PCM_DUMP_ON ipi send error\n");
	} else {
		/* dump flag */
		vow_ipi_buf[1] = vowserv.dump_pcm_flag;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
		/* TOTAL dram resrved size for barge in dump */
		vow_ipi_buf[0] = bargein_resv_dram.size;
		/* address for SCP using */
		vow_ipi_buf[2] = bargein_resv_dram.phy_addr;

		VOWDRV_DEBUG(
		"[BargeIn]dump off, dump flag:%d, resv sz:0x%x, addr:0x%x\n",
			     vow_ipi_buf[1],
			     vow_ipi_buf[0],
			     vow_ipi_buf[2]);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
		/* TOTAL dram resrved size for recog data dump */
		vow_ipi_buf[3] = recog_resv_dram.size;
		/* address for SCP using */
		vow_ipi_buf[4] = recog_resv_dram.phy_addr;

		VOWDRV_DEBUG(
		"[Recog]dump off, dump flag:%d, resv sz:0x%x, addr:0x%x\n",
			    vow_ipi_buf[1],
			    vow_ipi_buf[3],
			    vow_ipi_buf[4]);

		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_BYPASS_ACK,
				      IPIMSG_VOW_PCM_DUMP_OFF,
				      sizeof(unsigned int) * 5, 0,
				      (char *)&vow_ipi_buf[0]);

		if (ret == 0)
			VOWDRV_DEBUG("PCM_DUMP_OFF ipi send error\n");
	}
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */
	return 0;
}

static int vow_pcm_dump_set(bool enable)
{
	VOWDRV_DEBUG("%s = %d, %d\n", __func__,
		     vowserv.dump_pcm_flag,
		     (unsigned int)enable);
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	bargein_resv_dram.vir_addr =
	    (char *)(scp_get_reserve_mem_virt(VOW_BARGEIN_MEM_ID))
	    + VOW_BARGEIN_DUMP_OFFSET;
	bargein_resv_dram.phy_addr =
	    scp_get_reserve_mem_phys(VOW_BARGEIN_MEM_ID)
	    + VOW_BARGEIN_DUMP_OFFSET;
	bargein_resv_dram.size = VOW_BARGEIN_DUMP_SIZE;

	VOWDRV_DEBUG("[Barge]vir: %p, phys: 0x%x\n",
		     bargein_resv_dram.vir_addr,
		     (unsigned int)bargein_resv_dram.phy_addr);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	recog_resv_dram.vir_addr =
	    (char *)(scp_get_reserve_mem_virt(VOW_MEM_ID))
	    + VOW_RECOGDATA_OFFSET;
	recog_resv_dram.phy_addr =
	    scp_get_reserve_mem_phys(VOW_MEM_ID)
	    + VOW_RECOGDATA_OFFSET;
	recog_resv_dram.size = VOW_RECOGDATA_SIZE;

	VOWDRV_DEBUG("[Recog]vir: %p, phys: 0x%x\n",
		     recog_resv_dram.vir_addr,
		     (unsigned int)recog_resv_dram.phy_addr);

	if ((vowserv.dump_pcm_flag == false) && (enable == true)) {
		vowserv.dump_pcm_flag = true;
		vow_service_OpenDumpFile();

		vow_pcm_dump_notify(true);
	} else if ((vowserv.dump_pcm_flag == true) && (enable == false)) {
		vowserv.dump_pcm_flag = false;

		vow_pcm_dump_notify(false);

		vow_stop_dump_wait();
		vow_service_CloseDumpFile();
	}
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */
	return 0;
}

static void vow_service_OpenDumpFile(void)
{
	VOWDRV_DEBUG("+%s() %d\n", __func__, b_enable_dump);
	/* only enable when debug pcm dump on */
	__pm_stay_awake(&pcm_dump_wake_lock);
	vow_service_OpenDumpFile_internal();

	if (dump_queue == NULL) {
		dump_queue = kmalloc(sizeof(struct dump_queue_t), GFP_KERNEL);
		if (dump_queue != NULL)
			memset_io(dump_queue, 0, sizeof(struct dump_queue_t));
	}
	if (!pcm_dump_task) {
		pcm_dump_task = kthread_create(vow_pcm_dump_kthread,
					       NULL,
					       "vow_pcm_dump_kthread");
		if (IS_ERR(pcm_dump_task))
			VOWDRV_DEBUG("can not create pcm dump kthread\n");

		b_enable_dump = true;
		wake_up_process(pcm_dump_task);
	}
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	bargein_dump_data_routine_cnt_pass = 0;
	vowserv.bargein_dump_cnt1 = 0;
	vowserv.bargein_dump_cnt2 = 0;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	recog_dump_data_routine_cnt_pass = 0;
	vowserv.recog_dump_cnt1 = 0;
	vowserv.recog_dump_cnt2 = 0;
	VOWDRV_DEBUG("-%s() %d\n", __func__, b_enable_dump);
}

static void vow_service_CloseDumpFile(void)
{
	VOWDRV_DEBUG("+%s() %d\n", __func__, b_enable_dump);
	if (b_enable_dump == false)
		return;

	b_enable_dump = false;
	if (pcm_dump_task) {
		kthread_stop(pcm_dump_task);
		pcm_dump_task = NULL;
	}
	VOWDRV_DEBUG("[Recog] dump_queue = %p\n", dump_queue);
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	VOWDRV_DEBUG("[BargeIn] bargein_pass: %d\n",
		bargein_dump_data_routine_cnt_pass);
	VOWDRV_DEBUG("[BargeIn] bargein dump cnt %d %d\n",
		     vowserv.bargein_dump_cnt1,
		     vowserv.bargein_dump_cnt2);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	VOWDRV_DEBUG("[Recog] recog_pass: %d\n",
		recog_dump_data_routine_cnt_pass);
	VOWDRV_DEBUG("[Recog] recog dump cnt %d %d\n",
		     vowserv.recog_dump_cnt1,
		     vowserv.recog_dump_cnt2);
	if (dump_queue != NULL) {
		kfree(dump_queue);
		dump_queue = NULL;
	}

	vow_service_CloseDumpFile_internal();

	__pm_relax(&pcm_dump_wake_lock);
	VOWDRV_DEBUG("-%s() %d\n", __func__, b_enable_dump);
}

static void vow_service_OpenDumpFile_internal(void)
{
	struct timespec curr_tm;
	char string_time[16];
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	char string_input_pcm[16] = "input_pcm.pcm";
	char string_echo_pcm[16] = "echo_ref.pcm";
	char string_delay_info[16] = "delay_info";
	char path_input_pcm[64];
	char path_echo_ref[64];
	char path_delay_info[64];
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	char string_recog[16] = "recog.pcm";
	char path_recog[64];

	VOWDRV_DEBUG("+%s()\n", __func__);
	memset(&curr_tm, 0, sizeof(struct timespec));
	getnstimeofday(&curr_tm);

	memset(string_time, '\0', 16);
	sprintf(string_time, "%.2lu_%.2lu_%.2lu_%.3lu",
		(8 + (curr_tm.tv_sec / 3600)) % (24),
		(curr_tm.tv_sec / 60) % (60),
		(curr_tm.tv_sec % 60),
		(curr_tm.tv_nsec % 1000));
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	sprintf(path_input_pcm, "%s/%s_%s",
		DUMP_PCM_DATA_PATH, string_time, string_input_pcm);
	VOWDRV_DEBUG("[BargeIn] %s path_input_pcm= %s\n", __func__,
		     path_input_pcm);
	sprintf(path_echo_ref, "%s/%s_%s",
		DUMP_PCM_DATA_PATH, string_time, string_echo_pcm);
	VOWDRV_DEBUG("[BargeIn] %s path_input_pcm= %s\n", __func__,
		     path_echo_ref);
	sprintf(path_delay_info, "%s/%s_%s",
		DUMP_PCM_DATA_PATH, string_time, string_delay_info);
	VOWDRV_DEBUG("[BargeIn] %s path_input_pcm= %s\n", __func__,
		     path_delay_info);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	sprintf(path_recog, "%s/%s_%s",
		DUMP_PCM_DATA_PATH, string_time, string_recog);
	VOWDRV_DEBUG("[Recog] %s path_recog= %s\n", __func__,
		     path_recog);

	file_recog_data = NULL;
	file_recog_data_open = false;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	file_bargein_pcm_input = NULL;
	file_bargein_pcm_input_open = false;
	file_bargein_echo_ref = NULL;
	file_bargein_echo_ref_open = false;
	file_bargein_delay_info = NULL;
	file_bargein_delay_info_open = false;

	file_bargein_pcm_input = filp_open(path_input_pcm,
					   O_CREAT | O_WRONLY | O_LARGEFILE,
					   0);
	if (IS_ERR(file_bargein_pcm_input)) {
		VOWDRV_DEBUG("[BargeIn] pcm_input:%d, path_input_pcm=%s\n",
			     (int)PTR_ERR(file_bargein_pcm_input),
			     path_input_pcm);
		return;
	}
	file_bargein_pcm_input_open = true;

	file_bargein_echo_ref = filp_open(path_echo_ref,
					  O_CREAT | O_WRONLY | O_LARGEFILE,
					  0);
	if (IS_ERR(file_bargein_echo_ref)) {
		VOWDRV_DEBUG("[BargeIn] echo_ref:%d, path_echo_ref=%s\n",
			     (int)PTR_ERR(file_bargein_echo_ref),
			     path_echo_ref);
		return;
	}
	file_bargein_echo_ref_open = true;

	file_bargein_delay_info = filp_open(path_delay_info,
					    O_CREAT | O_WRONLY | O_LARGEFILE,
					    0);
	if (IS_ERR(file_bargein_delay_info)) {
		VOWDRV_DEBUG(
		"[BargeIn] file_bargein_delay_info:%d, path_delay_info = %s\n",
		(int)PTR_ERR(file_bargein_delay_info),
		path_delay_info);
		return;
	}
	file_bargein_delay_info_open = true;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	file_recog_data = filp_open(path_recog,
				    O_CREAT | O_WRONLY | O_LARGEFILE,
				    0);
	if (IS_ERR(file_recog_data)) {
		VOWDRV_DEBUG(
		"[BargeIn] file_recog_data:%d, path_recog = %s\n",
		(int)PTR_ERR(file_recog_data),
		path_recog);
		return;
	}
	file_recog_data_open = true;
	VOWDRV_DEBUG("-%s()\n", __func__);
}

static void vow_service_CloseDumpFile_internal(void)
{
	VOWDRV_DEBUG("+%s()\n", __func__);
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	if (file_bargein_pcm_input_open) {
		file_bargein_pcm_input_open = false;
		if (!IS_ERR(file_bargein_pcm_input)) {
			filp_close(file_bargein_pcm_input, NULL);
			file_bargein_pcm_input = NULL;
		}
	}
	if (file_bargein_echo_ref_open) {
		file_bargein_echo_ref_open = false;
		if (!IS_ERR(file_bargein_echo_ref)) {
			filp_close(file_bargein_echo_ref, NULL);
			file_bargein_echo_ref = NULL;
		}
	}
	if (file_bargein_delay_info_open) {
		file_bargein_delay_info_open = false;
		if (!IS_ERR(file_bargein_delay_info)) {
			filp_close(file_bargein_delay_info, NULL);
			file_bargein_delay_info = NULL;
		}
	}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	if (file_recog_data_open) {
		file_recog_data_open = false;
		if (!IS_ERR(file_recog_data)) {
			filp_close(file_recog_data, NULL);
			file_recog_data = NULL;
		}
	}
	VOWDRV_DEBUG("-%s()\n", __func__);
}

static int vow_pcm_dump_kthread(void *data)
{
	int ret = 0;
	int size = 0, writedata = 0;
	uint8_t current_idx = 0;
	struct pcm_dump_t *pcm_dump = NULL;
	struct dump_package_t *dump_package = NULL;
	mm_segment_t old_fs;

	struct sched_param param = {.sched_priority = 85 };

	sched_setscheduler(current, SCHED_RR, &param);

	/* VOWDRV_DEBUG("[BargeIn] dump_queue = %p\n", dump_queue); */

	while (b_enable_dump && !kthread_should_stop()) {
		spin_lock(&vowdrv_lock);
		if (dump_queue->idx_r != dump_queue->idx_w) {
			current_idx = dump_queue->idx_r;
			dump_queue->idx_r++;
			spin_unlock(&vowdrv_lock);
		} else {
			spin_unlock(&vowdrv_lock);
			ret = wait_event_interruptible(
			      wq_dump_pcm,
			      (dump_queue->idx_r != dump_queue->idx_w) ||
			      (b_enable_dump == false));
			if (ret == -ERESTARTSYS) {
				ret = -EINTR;
				break;
			}
			if (b_enable_dump == false)
				break;

			current_idx = dump_queue->idx_r;
			dump_queue->idx_r++;
		}

		if (vowserv.split_dumpfile_flag) {
			vow_service_CloseDumpFile_internal();
			vow_service_OpenDumpFile_internal();
			VOWDRV_DEBUG("Split audio dump done\n");
			vowserv.split_dumpfile_flag = false;
		}

		dump_package = &dump_queue->dump_package[current_idx];

		/* VOWDRV_DEBUG("[BargeIn] current_idx = %d\n", current_idx); */
		switch (dump_package->dump_data_type) {
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
		case DUMP_BARGEIN: {
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
			short *out_buf;
			/* DRAM to kernel buffer and sample interleaving */
			vow_interleaving(
				vowserv.interleave_pcmdata_ptr,
				(short *)(bargein_resv_dram.vir_addr +
				    dump_package->mic_offset),
				(short *)(bargein_resv_dram.vir_addr +
				    dump_package->mic_offset_R),
				dump_package->mic_data_size);

			size = (dump_package->mic_data_size) * 2;
			writedata = size;

			out_buf = vowserv.interleave_pcmdata_ptr;
			while (size > 0) {
				if (file_bargein_pcm_input_open &&
				    !IS_ERR(file_bargein_pcm_input)) {
					old_fs = get_fs();
					set_fs(KERNEL_DS);
					ret = vfs_write(file_bargein_pcm_input,
					    (char __user *)out_buf,
					    writedata,
					    &file_bargein_pcm_input->f_pos);
					set_fs(old_fs);
					if (!ret) {
						VOWDRV_DEBUG(
						"[Bargein]vfs write failed\n");
					}
				}
				size -= writedata;
				pcm_dump++;
			}
#else  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
			/* Bargein dump Mic input data */
			size = dump_package->mic_data_size;
			writedata = size;
			pcm_dump = (struct pcm_dump_t *)
				   (bargein_resv_dram.vir_addr
				   + dump_package->mic_offset);
			while (size > 0) {
				if (file_bargein_pcm_input_open &&
				    !IS_ERR(file_bargein_pcm_input)) {
					old_fs = get_fs();
					set_fs(KERNEL_DS);
					ret = vfs_write(file_bargein_pcm_input,
					    (char __user *)pcm_dump->decode_pcm,
					    writedata,
					    &file_bargein_pcm_input->f_pos);
					set_fs(old_fs);
					if (!ret) {
						VOWDRV_DEBUG(
						"[Bargein]vfs write failed\n");
					}
				}
				size -= writedata;
				pcm_dump++;
			}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
			/* Bargein dump echo data */
			size = dump_package->echo_data_size;
			writedata = size;
			pcm_dump = (struct pcm_dump_t *)
				   (bargein_resv_dram.vir_addr
				   + dump_package->echo_offset);
			vowserv.bargein_dump_cnt2++;
			while (size > 0) {
				if (file_bargein_echo_ref_open &&
				    !IS_ERR(file_bargein_echo_ref)) {
					old_fs = get_fs();
					set_fs(KERNEL_DS);
					ret = vfs_write(file_bargein_echo_ref,
					    (char __user *)pcm_dump->decode_pcm,
					    writedata,
					    &file_bargein_echo_ref->f_pos);
					set_fs(old_fs);
					if (!ret) {
						VOWDRV_DEBUG(
						"[Bargein]vfs write failed\n");
					}
				}
				size -= writedata;
				pcm_dump++;
			}
		}
		if (bargein_dump_info_flag) {
			if (file_bargein_delay_info_open &&
			    !IS_ERR(file_bargein_delay_info)) {
				uint32_t *ptr32;

				old_fs = get_fs();
				set_fs(KERNEL_DS);
				ptr32 = &vowserv.dump_frm_cnt;
				ret = vfs_write(file_bargein_delay_info,
					    (char __user *)ptr32,
					    sizeof(uint32_t),
					    &file_bargein_delay_info->f_pos);
				if (!ret)
					VOWDRV_DEBUG("vfs write failed\n");
				ptr32 = &vowserv.voice_sample_delay;
				ret = vfs_write(file_bargein_delay_info,
					    (char __user *)ptr32,
					    sizeof(uint32_t),
					    &file_bargein_delay_info->f_pos);
				if (!ret)
					VOWDRV_DEBUG("vfs write failed\n");
				set_fs(old_fs);
				bargein_dump_info_flag = false;
			}
		}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
			break;
		case DUMP_RECOG: {
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
			short *out_buf;
			/* DRAM to kernel buffer and sample interleaving */
			vow_interleaving(
				vowserv.interleave_pcmdata_ptr,
				(short *)(recog_resv_dram.vir_addr +
				    dump_package->recog_data_offset),
				(short *)(recog_resv_dram.vir_addr +
				    dump_package->recog_data_offset_R),
				dump_package->recog_data_size);

			size = (dump_package->recog_data_size) * 2;
			writedata = size;

			out_buf = vowserv.interleave_pcmdata_ptr;
			while (size > 0) {
				if (file_recog_data_open &&
				    !IS_ERR(file_recog_data)) {
					old_fs = get_fs();
					set_fs(KERNEL_DS);
					ret = vfs_write(file_recog_data,
					    (char __user *)out_buf,
					    writedata,
					    &file_recog_data->f_pos);
					set_fs(old_fs);
					if (!ret) {
						VOWDRV_DEBUG(
						"[Recog]vfs write failed\n");
					}
				}
				size -= writedata;
				pcm_dump++;
			}
#else  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
			/* Recog dump data */
			size = dump_package->recog_data_size;
			writedata = size;
			pcm_dump = (struct pcm_dump_t *)
				   (recog_resv_dram.vir_addr
				   + dump_package->recog_data_offset);
			while (size > 0) {
				if (file_recog_data_open &&
				    !IS_ERR(file_recog_data)) {
					old_fs = get_fs();
					set_fs(KERNEL_DS);
					ret = vfs_write(file_recog_data,
					    (char __user *)pcm_dump->decode_pcm,
					    writedata,
					    &file_recog_data->f_pos);
					set_fs(old_fs);
					if (!ret) {
						VOWDRV_DEBUG(
						"[Recog]vfs write failed\n");
					}
				}
				size -= writedata;
				pcm_dump++;
			}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
		}
			break;
		default:
			break;
		}
	}
	VOWDRV_DEBUG("%s, exit\n", __func__);
	return 0;
}

static void recog_dump_routine(struct work_struct *ws)
{
	struct dump_work_t *dump_work = NULL;
	uint32_t offset = 0;
	uint32_t data_size = 0;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	uint32_t offset_R = 0;
	uint32_t data_size_R = 0;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */

	dump_work = container_of(ws, struct dump_work_t, work);

	offset = dump_work->recog_data_offset;
	data_size = dump_work->recog_data_size;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	offset_R = dump_work->recog_data_offset_R;
	data_size_R = dump_work->recog_data_size_R;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */

	spin_lock(&vowdrv_lock);
	dump_queue->dump_package[dump_queue->idx_w].dump_data_type =
	    DUMP_RECOG;
	dump_queue->dump_package[dump_queue->idx_w].recog_data_offset =
	    offset;
	dump_queue->dump_package[dump_queue->idx_w].recog_data_size =
	    data_size;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	dump_queue->dump_package[dump_queue->idx_w].recog_data_offset_R =
	    offset_R;
	dump_queue->dump_package[dump_queue->idx_w].recog_data_size_R =
	    data_size_R;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */

	dump_queue->idx_w++;
	spin_unlock(&vowdrv_lock);
	vowserv.recog_dump_cnt1++;

	wake_up_interruptible(&wq_dump_pcm);
}

#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
static void bargein_dump_routine(struct work_struct *ws)
{
	struct dump_work_t *dump_work = NULL;
	uint32_t mic_offset = 0;
	uint32_t mic_data_size = 0;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	uint32_t mic_offset_R = 0;
	uint32_t mic_data_size_R = 0;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
	uint32_t echo_offset = 0;
	uint32_t echo_data_size = 0;

	dump_work = container_of(ws, struct dump_work_t, work);

	mic_offset = dump_work->mic_offset;
	mic_data_size = dump_work->mic_data_size;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	mic_offset_R = dump_work->mic_offset_R;
	mic_data_size_R = dump_work->mic_data_size_R;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
	echo_offset = dump_work->echo_offset;
	echo_data_size = dump_work->echo_data_size;

	spin_lock(&vowdrv_lock);
	dump_queue->dump_package[dump_queue->idx_w].dump_data_type =
	    DUMP_BARGEIN;
	dump_queue->dump_package[dump_queue->idx_w].mic_offset =
	    mic_offset;
	dump_queue->dump_package[dump_queue->idx_w].mic_data_size =
	    mic_data_size;
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	dump_queue->dump_package[dump_queue->idx_w].mic_offset_R =
	    mic_offset_R;
	dump_queue->dump_package[dump_queue->idx_w].mic_data_size_R =
	    mic_data_size_R;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */
	dump_queue->dump_package[dump_queue->idx_w].echo_offset =
	    echo_offset;
	dump_queue->dump_package[dump_queue->idx_w].echo_data_size =
	    echo_data_size;

	dump_queue->idx_w++;
	spin_unlock(&vowdrv_lock);
	vowserv.bargein_dump_cnt1++;

	wake_up_interruptible(&wq_dump_pcm);
}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */

static void vow_pcm_dump_init(void)
{
	VOWDRV_DEBUG("[Recog] %s()\n", __func__);

	dump_workqueue[DUMP_RECOG] =
	    create_workqueue("dump_recog_data");
	if (dump_workqueue[DUMP_RECOG] == NULL) {
		VOWDRV_DEBUG("[Recog] dump_workqueue[DUMP_RECOG] = %p\n",
			     dump_workqueue[DUMP_RECOG]);
	}
	VOW_ASSERT(dump_workqueue[DUMP_RECOG] != NULL);
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	dump_workqueue[DUMP_BARGEIN] =
	    create_workqueue("dump_bargein_recho_ref");
	if (dump_workqueue[DUMP_BARGEIN] == NULL) {
		VOWDRV_DEBUG("[BargeIn] dump_workqueue[recho_ref] = %p\n",
			     dump_workqueue[DUMP_BARGEIN]);
	}
	VOW_ASSERT(dump_workqueue[DUMP_BARGEIN] != NULL);

	INIT_WORK(&dump_work[DUMP_BARGEIN].work,
		  bargein_dump_routine);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	INIT_WORK(&dump_work[DUMP_RECOG].work,
		  recog_dump_routine);

	init_waitqueue_head(&wq_dump_pcm);

	pcm_dump_task = NULL;

#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	if (vowserv.interleave_pcmdata_ptr != NULL) {
		vfree(vowserv.interleave_pcmdata_ptr);
		vowserv.interleave_pcmdata_ptr = NULL;
	}
	/* Temp buffer for doing DUALMIC L/R channels interleave */
	vowserv.interleave_pcmdata_ptr =
		vmalloc(VOW_PCM_DUMP_BYTE_SIZE << 1);
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */

}

static void vow_pcm_dump_deinit(void)
{
	int i = 0;

	VOWDRV_DEBUG("[BargeIn] %s()\n", __func__);

	for (i = 0; i < NUM_DUMP_DATA; i++) {
		if (dump_workqueue[i]) {
			flush_workqueue(dump_workqueue[i]);
			destroy_workqueue(dump_workqueue[i]);
			dump_workqueue[i] = NULL;
		}
	}
#if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT)
	if (vowserv.interleave_pcmdata_ptr != NULL) {
		vfree(vowserv.interleave_pcmdata_ptr);
		vowserv.interleave_pcmdata_ptr = NULL;
	}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_DUAL_MIC_SUPPORT) */

}

/*****************************************************************************
 * VOW CONTROL FUNCTIONS
 *****************************************************************************/

static int VowDrv_SetHWStatus(int status)
{
	int ret = 0;

	VOWDRV_DEBUG("%s():set:%x, cur:%x\n",
		     __func__, status, vowserv.pwr_status);
	if ((status < NUM_OF_VOW_PWR_STATUS) && (status >= VOW_PWR_OFF)) {
		spin_lock(&vowdrv_lock);
		vowserv.pwr_status = status;
		spin_unlock(&vowdrv_lock);
	} else {
		VOWDRV_DEBUG("error input:%d\n", status);
		ret = -1;
	}
	return ret;
}

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

	spin_lock(&vowdrv_lock);
	ret = vowserv.pwr_status;
	spin_unlock(&vowdrv_lock);
	return ret;
}

int VowDrv_EnableHW(int status)
{
	int ret = 0;
	int pwr_status = 0;

	VOWDRV_DEBUG("%s():%x\n", __func__, status);

	if (status < 0) {
		VOWDRV_DEBUG("%s(), error input: %x\n", __func__, status);
		ret = -1;
	} else {
		pwr_status = (status == 0)?VOW_PWR_OFF : VOW_PWR_ON;

		if (pwr_status == VOW_PWR_OFF) {
			/* reset the transfer limitation to */
			/* avoid obstructing phase2.5 transferring */
			if (vowserv.tx_keyword_start == true)
				vowserv.tx_keyword_start = false;
		}
		if (pwr_status == VOW_PWR_ON) {
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
			vow_register_feature(VOW_FEATURE_ID);
#else
			VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
			/* clear enter_phase3_cnt */
			vowserv.enter_phase3_cnt = 0;
		}
		VowDrv_SetHWStatus(pwr_status);
	}
	return ret;
}

int VowDrv_ChangeStatus(void)
{
	VowDrv_Wait_Queue_flag = 1;
	wake_up_interruptible(&VowDrv_Wait_Queue);
	return 0;
}

void VowDrv_SetSmartDevice(bool enable)
{
	unsigned int eint_num;
	unsigned int ints[2] = {0, 0};
	unsigned int vow_ipi_buf[2];
	bool ret;

	if (!vow_check_scp_status()) {
		VOWDRV_DEBUG("SCP is off, do not support VOW\n");
		return;
	}

	VOWDRV_DEBUG("%s():%x\n", __func__, enable);
	if (vowserv.node) {
		/* query eint number from device tree */
		ret = of_property_read_u32_array(vowserv.node,
						 "debounce",
						 ints,
						 ARRAY_SIZE(ints));
		if (ret != 0) {
			VOWDRV_DEBUG("%s(), no debounce node, ret=%d\n",
				     __func__, ret);
			return;
		}

		eint_num = ints[0];

		if (enable == false)
			eint_num = 0xFF;
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
		vow_ipi_buf[0] = enable;
		vow_ipi_buf[1] = eint_num;
		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_BYPASS_ACK,
				      IPIMSG_VOW_SET_SMART_DEVICE,
				      sizeof(unsigned int) * 2, 0,
				      (char *)&vow_ipi_buf[0]);
		if (ret == 0) {
			VOWDRV_DEBUG(
			    "IPIMSG_VOW_SET_SMART_DEVICE ipi send error\n\r");
		}
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	} else {
		/* no node here */
		VOWDRV_DEBUG("there is no node\n");
	}
}

void VowDrv_SetSmartDevice_GPIO(bool enable)
{
	int ret = 0;

	if (vowserv.node) {
		if (enable == false) {
			VOWDRV_DEBUG("VowDrv_SetSmartDev_gpio:OFF\n");
			ret = pinctrl_select_state(vowserv.pinctrl,
						   vowserv.pins_eint_off);
			if (ret) {
				/* pinctrl setting error */
				VOWDRV_DEBUG(
				"error, can not set gpio vow pins_eint_off\n");
			}
		} else {
			VOWDRV_DEBUG("VowDrv_SetSmartDev_gpio:ON\n");
			ret = pinctrl_select_state(vowserv.pinctrl,
						   vowserv.pins_eint_on);
			if (ret) {
				/* pinctrl setting error */
				VOWDRV_DEBUG(
				"error, can not set gpio vow pins_eint_on\n");
			}
		}
	} else {
		/* no node here */
		VOWDRV_DEBUG("there is no node\n");
	}
}

static bool VowDrv_SetFlag(int type, unsigned int set)
{
	bool ret = false;
	unsigned int vow_ipi_buf[2];

	VOWDRV_DEBUG("%s(), type:%x, set:%x\n", __func__, type, set);
	vow_ipi_buf[0] = type;
	vow_ipi_buf[1] = set;

#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
			      AUDIO_IPI_MSG_NEED_ACK,
			      IPIMSG_VOW_SET_FLAG,
			      sizeof(unsigned int) * 2, 0,
			      (char *)&vow_ipi_buf[0]);
	if (ret == 0)
		VOWDRV_DEBUG("IPIMSG_VOW_SET_FLAG ipi send error\n\r");
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	return ret;
}

void VowDrv_SetDmicLowPower(bool enable)
{
	VowDrv_SetFlag(VOW_FLAG_DMIC_LOWPOWER, enable);
}

static bool VowDrv_SetMtkifType(unsigned int type)
{
	bool ret = false;

	if (type == 0)
		VOWDRV_DEBUG("error, wrong MTKIF Type\n\r");
	vowserv.mtkif_type = type;
	ret = VowDrv_SetFlag(VOW_FLAG_MTKIF_TYPE, type);

	return ret;
}

void VowDrv_SetPeriodicEnable(bool enable)
{
	VowDrv_SetFlag(VOW_FLAG_PERIODIC_ENABLE, enable);
}

static ssize_t VowDrv_GetPhase1Debug(struct device *kobj,
				     struct device_attribute *attr,
				     char *buf)
{
	unsigned int stat;
	char cstr[35];
	int size = sizeof(cstr);

	stat = (vowserv.force_phase_stage == FORCE_PHASE1) ? 1 : 0;

	return snprintf(buf, size, "Force Phase1 Setting = %s\n",
			(stat == 0x1) ? "YES" : "NO");
}

static ssize_t VowDrv_SetPhase1Debug(struct device *kobj,
				     struct device_attribute *attr,
				     const char *buf,
				     size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	VowDrv_SetFlag(VOW_FLAG_FORCE_PHASE1_DEBUG, enable);
	return n;
}
DEVICE_ATTR(vow_SetPhase1,
	    0644, /*S_IWUSR | S_IRUGO*/
	    VowDrv_GetPhase1Debug,
	    VowDrv_SetPhase1Debug);

static ssize_t VowDrv_GetPhase2Debug(struct device *kobj,
				     struct device_attribute *attr,
				     char *buf)
{
	unsigned int stat;
	char cstr[35];
	int size = sizeof(cstr);

	stat = (vowserv.force_phase_stage == FORCE_PHASE2) ? 1 : 0;

	return snprintf(buf, size, "Force Phase2 Setting = %s\n",
			(stat == 0x1) ? "YES" : "NO");
}

static ssize_t VowDrv_SetPhase2Debug(struct device *kobj,
				     struct device_attribute *attr,
				     const char *buf,
				     size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	VowDrv_SetFlag(VOW_FLAG_FORCE_PHASE2_DEBUG, enable);
	return n;
}
DEVICE_ATTR(vow_SetPhase2,
	    0644, /*S_IWUSR | S_IRUGO*/
	    VowDrv_GetPhase2Debug,
	    VowDrv_SetPhase2Debug);

static ssize_t VowDrv_GetDualMicDebug(struct device *kobj,
				     struct device_attribute *attr,
				     char *buf)
{
	unsigned int stat;
	char cstr[35];
	int size = sizeof(cstr);

	stat = (vowserv.scp_vow_lch == true) ? 1 : 0;

	return snprintf(buf, size, "Dual mic L channel = %s\n",
			(stat == 0x1) ? "YES" : "NO");
}

static ssize_t VowDrv_SetDualMicDebug(struct device *kobj,
				     struct device_attribute *attr,
				     const char *buf,
				     size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	vowserv.scp_vow_lch = (enable == 1) ? true : false;

	VowDrv_SetFlag(VOW_FLAG_DUAL_MIC_LCH, enable);
	return n;
}
DEVICE_ATTR(vow_DualMicLch,
	    0644, /*S_IWUSR | S_IRUGO*/
	    VowDrv_GetDualMicDebug,
	    VowDrv_SetDualMicDebug);

static ssize_t VowDrv_GetSplitDumpFile(struct device *kobj,
				     struct device_attribute *attr,
				     char *buf)
{
	unsigned int stat;
	char cstr[35];
	int size = sizeof(cstr);

	stat = (vowserv.split_dumpfile_flag == true) ? 1 : 0;

	return snprintf(buf, size, "Split dump file = %s\n",
			(stat == 0x1) ? "YES" : "NO");
}

static ssize_t VowDrv_SetSplitDumpFile(struct device *kobj,
				     struct device_attribute *attr,
				     const char *buf,
				     size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	VOWDRV_DEBUG("%s(),enable=%d\n", __func__, enable);
	vowserv.split_dumpfile_flag = (enable == 1) ? true : false;

	return n;
}
DEVICE_ATTR(vow_SplitDumpFile,
	    0644, /*S_IWUSR | S_IRUGO*/
	    VowDrv_GetSplitDumpFile,
	    VowDrv_SetSplitDumpFile);


#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
static ssize_t VowDrv_SetBargeInDebug(struct device *kobj,
				      struct device_attribute *attr,
				      const char *buf,
				      size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	VowDrv_SetBargeIn(enable, 1); /* temp fix irq */
	return n;
}
DEVICE_ATTR(vow_SetBargeIn,
	    0200, /*S_IWUSR*/
	    NULL,
	    VowDrv_SetBargeInDebug);

static bool VowDrv_SetBargeIn(unsigned int set, unsigned int irq_id)
{
	bool ret = false;
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	unsigned int vow_ipi_buf[1];

	vow_ipi_buf[0] = irq_id;

	VOWDRV_DEBUG("%s(), set = %d, irq = %d\n", __func__, set, irq_id);
	if (set == 1) {
		vow_register_feature(VOW_BARGEIN_FEATURE_ID);
		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_NEED_ACK,
				      IPIMSG_VOW_SET_BARGEIN_ON,
				      sizeof(unsigned int) * 1, 0,
				      (char *)&vow_ipi_buf[0]);
	} else if (set == 0) {
		ret = vow_IPICmd_Send(AUDIO_IPI_PAYLOAD,
				      AUDIO_IPI_MSG_NEED_ACK,
				      IPIMSG_VOW_SET_BARGEIN_OFF,
				      sizeof(unsigned int) * 1, 0,
				      (char *)&vow_ipi_buf[0]);
		vow_deregister_feature(VOW_BARGEIN_FEATURE_ID);
	} else {
		VOWDRV_DEBUG("Adb comment error\n\r");
	}
	if (ret == 0)
		VOWDRV_DEBUG("IPIMSG_BARGE_IN(%d) ipi send error\n", set);
#else
	VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
	return ret;
}
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */

static ssize_t VowDrv_GetBypassPhase3Flag(struct device *kobj,
					  struct device_attribute *attr,
					  char *buf)
{
	unsigned int stat;
	char cstr[35];
	int size = sizeof(cstr);

	stat = (vowserv.bypass_enter_phase3 == true) ? 1 : 0;

	return snprintf(buf, size, "Enter Phase3 Setting is %s\n",
			(stat == 0x1) ? "Bypass" : "Allow");
}

static ssize_t VowDrv_SetBypassPhase3Flag(struct device *kobj,
					  struct device_attribute *attr,
					  const char *buf,
					  size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	if (enable == 0) {
		VOWDRV_DEBUG("Allow enter phase3\n");
		vowserv.bypass_enter_phase3 = false;
	} else {
		VOWDRV_DEBUG("Bypass enter phase3\n");
		vowserv.bypass_enter_phase3 = true;
	}
	return n;
}
DEVICE_ATTR(vow_SetBypassPhase3,
	    0644, /*S_IWUSR | S_IRUGO*/
	    VowDrv_GetBypassPhase3Flag,
	    VowDrv_SetBypassPhase3Flag);

static ssize_t VowDrv_GetEnterPhase3Counter(struct device *kobj,
					    struct device_attribute *attr,
					    char *buf)
{
	char cstr[35];
	int size = sizeof(cstr);

	return snprintf(buf, size, "Enter Phase3 Counter is %u\n",
			vowserv.enter_phase3_cnt);
}
DEVICE_ATTR(vow_GetEnterPhase3Counter,
	    0444, /*S_IRUGO*/
	    VowDrv_GetEnterPhase3Counter,
	    NULL);

static ssize_t VowDrv_GetSWIPLog(struct device *kobj,
				 struct device_attribute *attr,
				 char *buf)
{
	unsigned int stat;
	char cstr[20];
	int size = sizeof(cstr);

	stat = (vowserv.swip_log_enable == true) ? 1 : 0;
	return snprintf(buf, size, "SWIP LOG is %s\n",
			(stat == true) ? "YES" : "NO");
}

static ssize_t VowDrv_SetSWIPLog(struct device *kobj,
				 struct device_attribute *attr,
				 const char *buf,
				 size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	VowDrv_SetFlag(VOW_FLAG_SWIP_LOG_PRINT, enable);
	return n;
}
DEVICE_ATTR(vow_SetLibLog,
	    0644, /*S_IWUSR | S_IRUGO*/
	    VowDrv_GetSWIPLog,
	    VowDrv_SetSWIPLog);

static ssize_t VowDrv_SetEnableHW(struct device *kobj,
				  struct device_attribute *attr,
				  const char *buf,
				  size_t n)
{
	unsigned int enable = 0;

	if (kstrtouint(buf, 0, &enable) != 0)
		return -EINVAL;

	VowDrv_EnableHW(enable);
	VowDrv_ChangeStatus();
	return n;
}
DEVICE_ATTR(vow_SetEnableHW,
	    0200, /*S_IWUSR*/
	    NULL,
	    VowDrv_SetEnableHW);

static int VowDrv_SetVowEINTStatus(int status)
{
	int ret = 0;
	int wakeup_event = 0;

	if ((status < NUM_OF_VOW_EINT_STATUS)
	 && (status >= VOW_EINT_DISABLE)) {
		spin_lock(&vowdrv_lock);
		if ((vowserv.eint_status != VOW_EINT_PASS)
		 && (status == VOW_EINT_PASS))
			wakeup_event = 1;
		vowserv.eint_status = status;
		spin_unlock(&vowdrv_lock);
	} else {
		VOWDRV_DEBUG("%s() error input:%x\n",
			     __func__, status);
		ret = -1;
	}
	return ret;
}

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

	spin_lock(&vowdrv_lock);
	ret = vowserv.eint_status;
	spin_unlock(&vowdrv_lock);
	VOWDRV_DEBUG("%s():%d\n", __func__, ret);
	return ret;
}

static int VowDrv_open(struct inode *inode, struct file *fp)
{
	VOWDRV_DEBUG("%s() do nothing inode:%p, file:%p\n",
		    __func__, inode, fp);
	return 0;
}

static int VowDrv_release(struct inode *inode, struct file *fp)
{
	VOWDRV_DEBUG("%s() inode:%p, file:%p\n", __func__, inode, fp);

	if (!(fp->f_mode & FMODE_WRITE || fp->f_mode & FMODE_READ))
		return -ENODEV;
	return 0;
}

static long VowDrv_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	int timeout = 0;

	timeout = 0;
	while (vowserv.vow_recovering) {
		msleep(VOW_WAITCHECK_INTERVAL_MS);
		timeout++;
		VOW_ASSERT(timeout != VOW_RECOVERY_WAIT);
	}

	VOWDRV_DEBUG("%s(), cmd = %u, arg = %lu\n", __func__, cmd, arg);
	switch ((unsigned int)cmd) {
	case VOW_SET_CONTROL:
		switch (arg) {
		case VOWControlCmd_Init:
			VOWDRV_DEBUG("VOW_SET_CONTROL Init");
			vow_service_Init();
			break;
		case VOWControlCmd_Reset:
			VOWDRV_DEBUG("VOW_SET_CONTROL Reset");
			vow_service_reset();
			break;
		case VOWControlCmd_EnableDebug:
			VOWDRV_DEBUG("VOW_SET_CONTROL EnableDebug");
			vowserv.voicedata_idx = 0;
			vowserv.recording_flag = true;
			vowserv.firstRead = true;
			/*VowDrv_SetFlag(VOW_FLAG_DEBUG, true);*/
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
			vow_register_feature(VOW_DUMP_FEATURE_ID);
#else
			VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
			if (vowserv.suspend_lock == 0) {
				vowserv.suspend_lock = 1;
				/* Let AP will not suspend */
				__pm_stay_awake(vow_suspend_lock);
				VOWDRV_DEBUG("==VOW DEBUG MODE START==\n");
			}
			break;
		case VOWControlCmd_DisableDebug:
			VOWDRV_DEBUG("VOW_SET_CONTROL DisableDebug");
			VowDrv_SetFlag(VOW_FLAG_DEBUG, false);
			vowserv.recording_flag = false;
			/* force stop vow_service_ReadVoiceData() 20180906 */
			vow_service_getVoiceData();
			if (vowserv.suspend_lock == 1) {
				vowserv.suspend_lock = 0;
				/* Let AP will suspend */
				__pm_relax(vow_suspend_lock);
				/* lock 1sec for screen on */
				VOWDRV_DEBUG("==VOW DEBUG MODE STOP==\n");
				__pm_wakeup_event(vow_suspend_lock,
						  jiffies_to_msecs(HZ));
			}
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
			vow_deregister_feature(VOW_DUMP_FEATURE_ID);
#else
			VOWDRV_DEBUG("%s(), vow: SCP no support\n\r", __func__);
#endif
			break;
		case VOWControlCmd_EnableSeamlessRecord:
			VOWDRV_DEBUG("VOW_SET_CONTROL EnableSeamlessRecord");
			VowDrv_SetFlag(VOW_FLAG_SEAMLESS, true);
			break;
		case VOWControlCmd_EnableDump:
			vow_pcm_dump_set(true);
			break;
		case VOWControlCmd_DisableDump:
			vow_pcm_dump_set(false);
			break;
		default:
			VOWDRV_DEBUG("VOW_SET_CONTROL no such command = %lu",
				     arg);
			break;
		}
		break;
	case VOW_READ_VOICE_DATA:
		if (!vow_service_SetApAddr(arg))
			ret = -EFAULT;
		if ((vowserv.recording_flag == true)
			&& (vowserv.firstRead == true)) {
			vowserv.firstRead = false;
			VowDrv_SetFlag(VOW_FLAG_DEBUG, true);
		}
		vow_service_ReadVoiceData();
		break;
	case VOW_SET_SPEAKER_MODEL:
		VOWDRV_DEBUG("VOW_SET_SPEAKER_MODEL(%lu)", arg);
		if (!vow_service_SetSpeakerModel(arg))
			ret = -EFAULT;
		break;
	case VOW_CLR_SPEAKER_MODEL:
		VOWDRV_DEBUG("VOW_CLR_SPEAKER_MODEL(%lu)", arg);
		if (!vow_service_ReleaseSpeakerModel((int)arg))
			ret = -EFAULT;
		break;
	case VOW_SET_APREG_INFO:
		VOWDRV_DEBUG("VOW_SET_APREG_INFO(%lu)", arg);
		if (!vow_service_SetVBufAddr(arg))
			ret = -EFAULT;
		break;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	case VOW_BARGEIN_ON:
		VOWDRV_DEBUG("VOW_BARGEIN_ON, irq: %d", (unsigned int)arg);
		if (!VowDrv_SetBargeIn(1, (unsigned int)arg))
			ret = -EFAULT;
		break;
	case VOW_BARGEIN_OFF:
		VOWDRV_DEBUG("VOW_BARGEIN_OFF, irq: %d", (unsigned int)arg);
		if (!VowDrv_SetBargeIn(0, (unsigned int)arg))
			ret = -EFAULT;
		break;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	case VOW_CHECK_STATUS:
		/* VOW disable already, then bypass second one */
		VowDrv_ChangeStatus();
		VOWDRV_DEBUG("VOW_CHECK_STATUS(%lu)", arg);
		break;
	case VOW_RECOG_ENABLE:
		pr_debug("+VOW_RECOG_ENABLE(%lu)+", arg);
		pr_debug("KERNEL_VOW_DRV_VER %s", KERNEL_VOW_DRV_VER);
		VowDrv_SetMtkifType((unsigned int)arg);
		VowDrv_EnableHW(1);
		VowDrv_ChangeStatus();
		vow_service_Enable();
		pr_debug("-VOW_RECOG_ENABLE(%lu)-", arg);
		break;
	case VOW_RECOG_DISABLE:
		pr_debug("+VOW_RECOG_DISABLE(%lu)+", arg);
		VowDrv_SetMtkifType((unsigned int)arg);
		VowDrv_EnableHW(0);
		VowDrv_ChangeStatus();
		vow_service_Disable();
		pr_debug("-VOW_RECOG_DISABLE(%lu)-", arg);
		break;
	case VOW_MODEL_START:
		vow_service_SetModelStatus(VOW_MODEL_STATUS_START, arg);
		break;
	case VOW_MODEL_STOP:
		vow_service_SetModelStatus(VOW_MODEL_STATUS_STOP, arg);
		break;
	case VOW_GET_GOOGLE_ENGINE_VER: {
		copy_to_user((void __user *)arg,
				 &vowserv.google_engine_version,
				 sizeof(vowserv.google_engine_version));
	}
		break;
	case VOW_GET_GOOGLE_ARCH:
	case VOW_GET_ALEXA_ENGINE_VER: {
		struct vow_engine_version_t engine_ver_temp;
		unsigned int length = VOW_ENGINE_INFO_LENGTH_BYTE;

		copy_from_user((void *)&engine_ver_temp,
				 (const void __user *)arg,
				 sizeof(struct vow_engine_version_t));
		if ((unsigned int)cmd == VOW_GET_ALEXA_ENGINE_VER) {
			pr_debug("VOW_GET_ALEXA_ENGINE_VER = %s, %lu, %lu, %d",
				 vowserv.alexa_engine_version,
				 engine_ver_temp.data_addr,
				 engine_ver_temp.return_size_addr,
				 length);
			copy_to_user((void __user *)engine_ver_temp.data_addr,
				 vowserv.alexa_engine_version,
				 length);
		} else if ((unsigned int)cmd == VOW_GET_GOOGLE_ARCH) {
			pr_debug("VOW_GET_GOOGLE_ARCH = %s, %lu, %lu, %d",
					 vowserv.google_engine_arch,
					 engine_ver_temp.data_addr,
					 engine_ver_temp.return_size_addr,
					 length);
			copy_to_user((void __user *)engine_ver_temp.data_addr,
					 vowserv.google_engine_arch,
					 length);
		}
		copy_to_user((void __user *)engine_ver_temp.return_size_addr,
				 &length,
				 sizeof(unsigned int));
	}
		break;
	default:
		VOWDRV_DEBUG("vow WrongParameter(%lu)", arg);
		break;
	}
	return ret;
}

#if IS_ENABLED(CONFIG_COMPAT)
static long VowDrv_compat_ioctl(struct file *fp,
				unsigned int cmd,
				unsigned long arg)
{
	long ret = 0;

	/* VOWDRV_DEBUG("++VowDrv_compat_ioctl cmd = %u, arg = %lu\n" */
	/*		, cmd, arg); */
	if (!fp->f_op || !fp->f_op->unlocked_ioctl) {
		(void)ret;
		return -ENOTTY;
	}
	switch (cmd) {
	case VOW_CLR_SPEAKER_MODEL:
	case VOW_SET_CONTROL:
	case VOW_CHECK_STATUS:
	case VOW_RECOG_ENABLE:
	case VOW_RECOG_DISABLE:
	case VOW_BARGEIN_ON:
	case VOW_BARGEIN_OFF:
		ret = fp->f_op->unlocked_ioctl(fp, cmd, arg);
		break;
	case VOW_GET_GOOGLE_ENGINE_VER:
		ret = fp->f_op->unlocked_ioctl(fp, cmd, arg);
		break;
	case VOW_MODEL_START:
	case VOW_MODEL_STOP: {
		struct vow_model_start_kernel_t __user *data32;
		struct vow_model_start_t __user *data;
		int err;
		compat_size_t l;

		data32 = compat_ptr(arg);
		data = compat_alloc_user_space(sizeof(*data));

		err  = get_user(l, &data32->handle);
		err |= put_user(l, &data->handle);
		err |= get_user(l, &data32->confidence_level);
		err |= put_user(l, &data->confidence_level);

		ret = fp->f_op->unlocked_ioctl(fp, cmd, (unsigned long)data);
	}
		break;
	case VOW_READ_VOICE_DATA:
	case VOW_SET_SPEAKER_MODEL:
	case VOW_SET_APREG_INFO: {
		struct vow_model_info_kernel_t __user *data32;
		struct vow_model_info_t __user *data;
		int err;
		compat_size_t l;
		compat_uptr_t p;

		data32 = compat_ptr(arg);
		data = compat_alloc_user_space(sizeof(*data));

		err  = get_user(l, &data32->id);
		err |= put_user(l, &data->id);
		err |= get_user(l, &data32->keyword);
		err |= put_user(l, &data->keyword);
		err |= get_user(l, &data32->addr);
		err |= put_user(l, &data->addr);
		err |= get_user(l, &data32->size);
		err |= put_user(l, &data->size);
		err |= get_user(l, &data32->return_size_addr);
		err |= put_user(l, &data->return_size_addr);
		err |= get_user(l, &data32->uuid);
		err |= put_user(l, &data->uuid);
		err |= get_user(p, (compat_uptr_t *)&data32->data);
		err |= put_user(p, (compat_uptr_t *)&data->data);

		ret = fp->f_op->unlocked_ioctl(fp, cmd, (unsigned long)data);
	}
		break;
	case VOW_GET_GOOGLE_ARCH:
	case VOW_GET_ALEXA_ENGINE_VER: {
		struct vow_engine_version_kernel_t __user *data32;
		struct vow_engine_version_t __user *data;
		int err;
		compat_size_t l;

		data32 = compat_ptr(arg);
		data = compat_alloc_user_space(sizeof(*data));

		err  = get_user(l, &data32->return_size_addr);
		err |= put_user(l, &data->return_size_addr);
		err |= get_user(l, &data32->data_addr);
		err |= put_user(l, &data->data_addr);

		ret = fp->f_op->unlocked_ioctl(fp, cmd, (unsigned long)data);
	}
		break;
	default:
		break;
	}
	/* VOWDRV_DEBUG("--VowDrv_compat_ioctl\n"); */
	return ret;
}
#endif

static ssize_t VowDrv_write(struct file *fp,
			    const char __user *data,
			    size_t count,
			    loff_t *offset)
{
	/* VOWDRV_DEBUG("+VowDrv_write = %p count = %d\n",fp ,count); */
	return 0;
}

static ssize_t VowDrv_read(struct file *fp,
			   char __user *data,
			   size_t count,
			   loff_t *offset)
{
	unsigned int read_count = 0;
	unsigned int time_diff_scp_ipi = 0;
	unsigned int time_diff_ipi_read = 0;
	unsigned long long vow_read_cycle = 0;
	int ret = 0;

	VOWDRV_DEBUG("+%s()+\n", __func__);
	VowDrv_SetVowEINTStatus(VOW_EINT_RETRY);

	if (VowDrv_Wait_Queue_flag == 0)
		ret = wait_event_interruptible(VowDrv_Wait_Queue,
					       VowDrv_Wait_Queue_flag);
	if (VowDrv_Wait_Queue_flag == 1) {
		VowDrv_Wait_Queue_flag = 0;
		if (VowDrv_GetHWStatus() == VOW_PWR_OFF) {
			VOWDRV_DEBUG("vow Enter_phase3_cnt = %d\n",
				      vowserv.enter_phase3_cnt);
			vowserv.scp_command_flag = false;
			VowDrv_SetVowEINTStatus(VOW_EINT_DISABLE);
		} else {
			if (vowserv.scp_command_flag) {
				VowDrv_SetVowEINTStatus(VOW_EINT_PASS);
				vow_read_cycle = get_cycles();
				time_diff_scp_ipi =
				    (unsigned int)CYCLE_TO_NS *
				    (unsigned int)(vowserv.ap_received_ipi_cycle
				    - vowserv.scp_recognize_ok_cycle);
				time_diff_ipi_read =
				    (unsigned int)CYCLE_TO_NS *
				    (unsigned int)(vow_read_cycle
				    - vowserv.ap_received_ipi_cycle);
				VOWDRV_DEBUG("vow Wakeup by SCP\n");
				VOWDRV_DEBUG("SCP->IPI:%d(ns),IPI->AP:%d(ns)\n",
					     time_diff_scp_ipi,
					     time_diff_ipi_read);
				if (vowserv.suspend_lock == 0) {
					/* lock 1sec for screen on */
					__pm_wakeup_event(vow_suspend_lock,
							  jiffies_to_msecs(HZ));
				}
				vowserv.scp_command_flag = false;
			} else {
				VOWDRV_DEBUG("vow Wakeup by other(%d,%d)\n",
					     VowDrv_Wait_Queue_flag,
					     VowDrv_GetHWStatus());
			}
		}
	}
	vowserv.vow_eint_data_struct.id = vowserv.scp_command_id;
	vowserv.vow_eint_data_struct.eint_status = VowDrv_QueryVowEINTStatus();
	vowserv.vow_eint_data_struct.data[0] = (char)vowserv.confidence_level;
	//reset data
	vowserv.confidence_level = 0;
	read_count = copy_to_user((void __user *)data,
				  &vowserv.vow_eint_data_struct,
				  sizeof(struct vow_eint_data_struct_t));
	VOWDRV_DEBUG("+%s()-, recog id: %d, confidence_lv=%d\n",
		     __func__,
		     vowserv.scp_command_id,
		     vowserv.confidence_level);
	return read_count;
}

static int VowDrv_flush(struct file *flip, fl_owner_t id)
{
	VOWDRV_DEBUG("%s()\n", __func__);
	return 0;
}

static int VowDrv_fasync(int fd, struct file *flip, int mode)
{
	VOWDRV_DEBUG("%s()\n", __func__);
	/*return fasync_helper(fd, flip, mode, &VowDrv_fasync);*/
	return 0;
}

static int VowDrv_remap_mmap(struct file *flip, struct vm_area_struct *vma)
{
	VOWDRV_DEBUG("%s()\n", __func__);
	return -1;
}

int VowDrv_setup_smartdev_eint(struct platform_device *pdev)
{
	int ret;
	unsigned int ints[2] = {0, 0};

	/* gpio setting */
	vowserv.pinctrl = devm_pinctrl_get(&pdev->dev);
	if (IS_ERR(vowserv.pinctrl)) {
		ret = PTR_ERR(vowserv.pinctrl);
		VOWDRV_DEBUG("Cannot find Vow pinctrl!\n");
		return ret;
	}
	vowserv.pins_eint_on = pinctrl_lookup_state(vowserv.pinctrl,
						    "vow_smartdev_eint_on");
	if (IS_ERR(vowserv.pins_eint_on)) {
		ret = PTR_ERR(vowserv.pins_eint_on);
		VOWDRV_DEBUG("Cannot find vow pinctrl eint_on!\n");
		return ret;
	}

	vowserv.pins_eint_off = pinctrl_lookup_state(vowserv.pinctrl,
						     "vow_smartdev_eint_off");
	if (IS_ERR(vowserv.pins_eint_off)) {
		ret = PTR_ERR(vowserv.pins_eint_off);
		VOWDRV_DEBUG("Cannot find vow pinctrl eint_off!\n");
		return ret;
	}
	/* eint setting */
	vowserv.node = pdev->dev.of_node;
	if (vowserv.node) {
		ret = of_property_read_u32_array(vowserv.node,
					   "debounce",
					   ints,
					   ARRAY_SIZE(ints));
		if (ret != 0) {
			VOWDRV_DEBUG("%s(), no debounce node, ret=%d\n",
				      __func__, ret);
			return ret;
		}

		VOWDRV_DEBUG("VOW EINT ID: %x, %x\n", ints[0], ints[1]);
	} else {
		/* no node here */
		VOWDRV_DEBUG("%s(), there is no this node\n", __func__);
	}
	return 0;
}

/*****************************************************************************
 * SCP Recovery Register
 *****************************************************************************/
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
static int vow_scp_recover_event(struct notifier_block *this,
				 unsigned long event,
				 void *ptr)
{
	switch (event) {
	case SCP_EVENT_READY: {
		int I;

		msleep(500);
		vowserv.vow_recovering = true;
		vowserv.scp_recovering = false;
		VOWDRV_DEBUG("%s(), SCP_EVENT_READY\n", __func__);
		if (!vow_check_scp_status()) {
			VOWDRV_DEBUG("SCP is Off, don't recover VOW\n");
			return NOTIFY_DONE;
		}
		if (vowserv.scp_recovering) {
			vowserv.vow_recovering = false;
			VOWDRV_DEBUG("fail: vow recover1\n");
			break;
		}
		vow_service_Init();
		if (vowserv.scp_recovering) {
			vowserv.vow_recovering = false;
			VOWDRV_DEBUG("fail: vow recover2\n");
			break;
		}
		for (I = 0; I < MAX_VOW_SPEAKER_MODEL; I++) {
			if (!vow_service_SendSpeakerModel(I, VOW_SET_MODEL))
				VOWDRV_DEBUG("fail: SendSpeakerModel\n");
		}

		/* if vow is not enable, then return */
		if (VowDrv_GetHWStatus() != VOW_PWR_ON) {
			vowserv.vow_recovering = false;
			break;
		}
		if (vowserv.scp_recovering) {
			vowserv.vow_recovering = false;
			VOWDRV_DEBUG("fail: vow recover3\n");
			break;
		}
		/* pcm dump recover */
		VOWDRV_DEBUG("recording_flag = %d, dump_pcm_flag = %d\n",
				vowserv.recording_flag, vowserv.dump_pcm_flag);
		if (vowserv.recording_flag == true)
			VowDrv_SetFlag(VOW_FLAG_DEBUG, true);

		if (vowserv.dump_pcm_flag == true)
			vow_pcm_dump_notify(true);

		if (vowserv.scp_recovering) {
			vowserv.vow_recovering = false;
			VOWDRV_DEBUG("fail: vow recover4\n");
			break;
		}

		if (!VowDrv_SetMtkifType(vowserv.mtkif_type))
			VOWDRV_DEBUG("fail: vow_SetMtkifType\n");

		if (!vow_service_Enable())
			VOWDRV_DEBUG("fail: vow_service_Enable\n");

		for (I = 0; I < MAX_VOW_SPEAKER_MODEL; I++) {
			if (vowserv.vow_speaker_model[I].enabled) {
				vow_service_SendModelStatus(
					I, VOW_MODEL_STATUS_START);
				VOWDRV_DEBUG("send Model start, slot%d\n", I);
			}
		}
		vowserv.vow_recovering = false;
		break;
	}
	case SCP_EVENT_STOP:
		vowserv.scp_recovering = true;
		VOWDRV_DEBUG("%s(), SCP_EVENT_STOP\n", __func__);
		/* Check if VOW is running phase2.5, then stop this */
		break;
	}
	return NOTIFY_DONE;
}

static struct notifier_block vow_scp_recover_notifier = {
	.notifier_call = vow_scp_recover_event,
};
#endif  /* #if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT) */

/*****************************************************************************
 * VOW platform driver Registration
 *****************************************************************************/
static int VowDrv_probe(struct platform_device *dev)
{
	VOWDRV_DEBUG("%s()\n", __func__);
	vow_suspend_lock = wakeup_source_register(NULL, "vow wakelock");
	if (!vow_suspend_lock)
		pr_warn("wakeup source init failed.\n");
	VowDrv_setup_smartdev_eint(dev);
	return 0;
}

static int VowDrv_remove(struct platform_device *dev)
{
	VOWDRV_DEBUG("%s()\n", __func__);
	wakeup_source_unregister(vow_suspend_lock);
	return 0;
}

static void VowDrv_shutdown(struct platform_device *dev)
{
	VOWDRV_DEBUG("%s()\n", __func__);
}

static int VowDrv_suspend(struct platform_device *dev, pm_message_t state)
{
	/* only one suspend mode */
	VOWDRV_DEBUG("%s()\n", __func__);
	return 0;
}

static int VowDrv_resume(struct platform_device *dev) /* wake up */
{
	VOWDRV_DEBUG("%s()\n", __func__);
	return 0;
}

static const struct file_operations VOW_fops = {
	.owner   = THIS_MODULE,
	.open    = VowDrv_open,
	.release = VowDrv_release,
	.unlocked_ioctl   = VowDrv_ioctl,
#if IS_ENABLED(CONFIG_COMPAT)
	.compat_ioctl = VowDrv_compat_ioctl,
#endif
	.write   = VowDrv_write,
	.read    = VowDrv_read,
	.flush   = VowDrv_flush,
	.fasync  = VowDrv_fasync,
	.mmap    = VowDrv_remap_mmap
};

static struct miscdevice VowDrv_misc_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = VOW_DEVNAME,
	.fops = &VOW_fops,
};

const struct dev_pm_ops VowDrv_pm_ops = {
	.suspend = NULL,
	.resume = NULL,
	.freeze = NULL,
	.thaw = NULL,
	.poweroff = NULL,
	.restore = NULL,
	.restore_noirq = NULL,
};

#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id vow_of_match[] = {
	{.compatible = "mediatek,vow"},
	{},
};
#endif

static struct platform_driver VowDrv_driver = {
	.probe    = VowDrv_probe,
	.remove   = VowDrv_remove,
	.shutdown = VowDrv_shutdown,
	.suspend  = VowDrv_suspend,
	.resume   = VowDrv_resume,
	.driver   = {
#if IS_ENABLED(CONFIG_PM)
	.pm       = &VowDrv_pm_ops,
#endif
	.name     = "VOW_driver_device",
#if IS_ENABLED(CONFIG_OF)
	.of_match_table = vow_of_match,
#endif
	},
};

static int __init VowDrv_mod_init(void)
{
	int ret = 0;

	VOWDRV_DEBUG("+%s()\n", __func__);

	/* Register platform DRIVER */
	ret = platform_driver_register(&VowDrv_driver);
	if (ret != 0) {
		VOWDRV_DEBUG("VowDrv Fail:%d - Register DRIVER\n", ret);
		return ret;
	}

	/* register MISC device */
	ret = misc_register(&VowDrv_misc_device);
	if (ret != 0) {
		VOWDRV_DEBUG("VowDrv_probe misc_register Fail:%d\n", ret);
		return ret;
	}

	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SetPhase1);
	if (unlikely(ret != 0))
		return ret;
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SetPhase2);
	if (unlikely(ret != 0))
		return ret;
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SetBypassPhase3);
	if (unlikely(ret != 0))
		return ret;
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_GetEnterPhase3Counter);
	if (unlikely(ret != 0))
		return ret;
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SetLibLog);
	if (unlikely(ret != 0))
		return ret;
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SetEnableHW);
	if (unlikely(ret != 0))
		return ret;
#if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT)
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SetBargeIn);
	if (unlikely(ret != 0))
		return ret;
#endif  /* #if IS_ENABLED(CONFIG_MTK_VOW_BARGE_IN_SUPPORT) */
	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_DualMicLch);
	if (unlikely(ret != 0))
		return ret;

	ret = device_create_file(VowDrv_misc_device.this_device,
				 &dev_attr_vow_SplitDumpFile);
	if (unlikely(ret != 0))
		return ret;

	VOWDRV_DEBUG("vow_service_Init");
	vow_service_Init();
#if IS_ENABLED(CONFIG_MTK_TINYSYS_SCP_SUPPORT)
	scp_A_register_notify(&vow_scp_recover_notifier);
#endif
	VOWDRV_DEBUG("-%s(): Init Audio WakeLock\n", __func__);

	return 0;
}

static void __exit VowDrv_mod_exit(void)
{
	VOWDRV_DEBUG("+%s()\n", __func__);
	vow_pcm_dump_deinit();
	VOWDRV_DEBUG("-%s()\n", __func__);
}
module_init(VowDrv_mod_init);
module_exit(VowDrv_mod_exit);


/*****************************************************************************
 * License
 *****************************************************************************/
MODULE_AUTHOR("Michael Hsiao <michael.hsiao@mediatek.com>");
MODULE_DESCRIPTION("MediaTek VoW Driver");
MODULE_LICENSE("GPL v2");

