// SPDX-License-Identifier: GPL-2.0-or-later /* sound/soc/samsung/vts/vts.c * * ALSA SoC - Samsung VTS driver * * Copyright (c) 2016 Samsung Electronics Co. Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_EXYNOS_ITMON) #include #endif #include #include #include #include "vts.h" #include "vts_res.h" #include "vts_log.h" #include "vts_dump.h" /* #include "vts_s_lif_dump.h" */ #include "vts_dbg.h" /* For DEF_VTS_PCM_DUMP */ #include "vts_pcm_dump.h" #include "vts_proc.h" #undef EMULATOR #ifdef EMULATOR static void __iomem *pmu_alive; #define set_mask_value(id, mask, value) (id = ((id & ~mask) | (value & mask))) static void update_mask_value(void __iomem *sfr, unsigned int mask, unsigned int value) { unsigned int sfr_value = readl(sfr); set_mask_value(sfr_value, mask, value); writel(sfr_value, sfr); } #endif #define clear_bit(data, offset) ((data) &= ~(0x1<<(offset))) #define clear_bits(data, value, offset) ((data) &= ~((value)<<(offset))) #define set_bit(data, offset) ((data) |= (0x1<<(offset))) #define set_bits(data, value, offset) ((data) |= ((value)<<(offset))) #define check_bit(data, offset) (((data)>>(offset)) & (0x1)) /* For only external static functions */ static struct vts_data *p_vts_data; static void vts_dbg_dump_fw_gpr(struct device *dev, struct vts_data *data, unsigned int dbg_type); static void vts_check_version(struct device *dev); static struct platform_driver samsung_vts_driver; bool is_vts(struct device *dev) { return (&samsung_vts_driver.driver) == dev->driver; } struct vts_data *vts_get_data(struct device *dev) { while (dev && !is_vts(dev)) dev = dev->parent; return dev ? dev_get_drvdata(dev) : NULL; } /* vts mailbox interface functions */ int vts_mailbox_generate_interrupt( const struct platform_device *pdev, int hw_irq) { struct vts_data *data = p_vts_data; struct device *dev = data ? (data->pdev ? &data->pdev->dev : NULL) : NULL; unsigned long flag; int result = 0; if (!data || !dev) { vts_dev_warn(dev, "%s: VTS not Initialized\n", __func__); return -EINVAL; } spin_lock_irqsave(&data->state_spinlock, flag); /* Check VTS state before accessing mailbox */ if (data->vts_state == VTS_STATE_RUNTIME_SUSPENDED || data->vts_state == VTS_STATE_VOICECALL || data->vts_state == VTS_STATE_NONE) { vts_dev_warn(dev, "%s: VTS wrong state [%d]\n", __func__, data->vts_state); result = -EINVAL; goto out; } result = mailbox_generate_interrupt(pdev, hw_irq); out: spin_unlock_irqrestore(&data->state_spinlock, flag); return result; } void vts_mailbox_write_shared_register( const struct platform_device *pdev, const u32 *values, int start, int count) { struct vts_data *data = p_vts_data; struct device *dev = data ? (data->pdev ? &data->pdev->dev : NULL) : NULL; unsigned long flag; if (!data || !dev) { vts_dev_warn(dev, "%s: VTS not Initialized\n", __func__); return; } spin_lock_irqsave(&data->state_spinlock, flag); /* Check VTS state before accessing mailbox */ if (data->vts_state == VTS_STATE_RUNTIME_SUSPENDED || data->vts_state == VTS_STATE_VOICECALL || data->vts_state == VTS_STATE_NONE) { vts_dev_warn(dev, "%s: VTS wrong state [%d]\n", __func__, data->vts_state); goto out; } mailbox_write_shared_register(pdev, values, start, count); out: spin_unlock_irqrestore(&data->state_spinlock, flag); } void vts_mailbox_read_shared_register( const struct platform_device *pdev, u32 *values, int start, int count) { struct vts_data *data = p_vts_data; struct device *dev = data ? (data->pdev ? &data->pdev->dev : NULL) : NULL; unsigned long flag; if (!data || !dev) { vts_dev_warn(dev, "%s: VTS not Initialized\n", __func__); return; } spin_lock_irqsave(&data->state_spinlock, flag); /* Check VTS state before accessing mailbox */ if (data && (data->vts_state == VTS_STATE_RUNTIME_SUSPENDED || data->vts_state == VTS_STATE_VOICECALL || data->vts_state == VTS_STATE_NONE)) { vts_dev_warn(dev, "%s: VTS wrong state [%d]\n", __func__, data->vts_state); goto out; } mailbox_read_shared_register(pdev, values, start, count); out: spin_unlock_irqrestore(&data->state_spinlock, flag); } static int vts_start_ipc_transaction_atomic( struct device *dev, struct vts_data *data, int msg, u32 (*values)[3], int sync) { unsigned long flag; long result = 0; u32 ack_value = 0; enum ipc_state *state = &data->ipc_state_ap; vts_dev_info(dev, "%s:[+%d][0x%x,0x%x,0x%x]\n", VTS_TAG_IPC, msg, (*values)[0], (*values)[1], (*values)[2]); /* Check VTS state before processing IPC, * in VTS_STATE_RUNTIME_SUSPENDING state only Power Down IPC * can be processed */ spin_lock_irqsave(&data->state_spinlock, flag); if ((data->vts_state == VTS_STATE_RUNTIME_SUSPENDING && msg != VTS_IRQ_AP_POWER_DOWN) || data->vts_state == VTS_STATE_RUNTIME_SUSPENDED || data->vts_state == VTS_STATE_VOICECALL || data->vts_state == VTS_STATE_NONE) { vts_dev_warn(dev, "%s: VTS IP %s state\n", __func__, (data->vts_state == VTS_STATE_VOICECALL ? "VoiceCall" : "Suspended")); spin_unlock_irqrestore(&data->state_spinlock, flag); return -EINVAL; } spin_unlock_irqrestore(&data->state_spinlock, flag); if (pm_runtime_suspended(dev)) { vts_dev_warn(dev, "%s: VTS IP is in suspended state, IPC cann't be processed\n", VTS_TAG_IPC); return -EINVAL; } if (!data->vts_ready) { vts_dev_warn(dev, "%s: VTS Firmware Not running\n", VTS_TAG_IPC); return -EINVAL; } spin_lock(&data->ipc_spinlock); *state = SEND_MSG; vts_mailbox_write_shared_register(data->pdev_mailbox, *values, 0, 3); vts_mailbox_generate_interrupt(data->pdev_mailbox, msg); data->running_ipc = msg; if (sync) { int i; /* for (i = 50; i && (*state != SEND_MSG_OK) && */ for (i = 200; i && (*state != SEND_MSG_OK) && (*state != SEND_MSG_FAIL) && (ack_value != (0x1 << msg)); i--) { vts_mailbox_read_shared_register(data->pdev_mailbox, &ack_value, 3, 1); vts_dev_dbg(dev, "%s ACK-value: 0x%08x\n", VTS_TAG_IPC, ack_value); mdelay(1); } if (!i) { vts_dev_warn(dev, "Transaction timeout!! Ack_value:0x%x\n", ack_value); vts_dbg_dump_fw_gpr(dev, data, VTS_IPC_TRANS_FAIL); vts_dev_info(dev, "shared_info 0x%x, %d %d", data->shared_info->vendor_data[0], data->shared_info->vendor_data[1], data->shared_info->vendor_data[2]); print_hex_dump(KERN_ERR, "vts-fw-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr, VTS_SRAM_EVENTLOG_SIZE_MAX, true); print_hex_dump(KERN_ERR, "vts-time-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr + VTS_SRAM_EVENTLOG_SIZE_MAX, VTS_SRAM_TIMELOG_SIZE_MAX, true); } if (*state == SEND_MSG_OK || ack_value == (0x1 << msg)) { vts_dev_dbg(dev, "Transaction success Ack_value:0x%x\n", ack_value); if (ack_value == (0x1 << VTS_IRQ_AP_COMMAND) && ((*values)[0] == VTS_ENABLE_DEBUGLOG || (*values)[0] == VTS_ENABLE_AUDIODUMP || (*values)[0] == VTS_ENABLE_LOGDUMP)) { u32 ackvalues[3] = {0, 0, 0}; mailbox_read_shared_register(data->pdev_mailbox, ackvalues, 4, 2); vts_dev_info(dev, "%s: offset: 0x%x size:0x%x\n", VTS_TAG_IPC, ackvalues[0], ackvalues[1]); if ((*values)[0] == VTS_ENABLE_DEBUGLOG) { /* Register debug log buffer */ vts_register_log_buffer(dev, ackvalues[0], ackvalues[1]); vts_dev_dbg(dev, "%s: Log buffer\n", VTS_TAG_IPC); } else { u32 dumpmode = ((*values)[0] == VTS_ENABLE_LOGDUMP ? VTS_LOG_DUMP : VTS_AUDIO_DUMP); /* register dump offset & size */ vts_dump_addr_register(dev, ackvalues[0], ackvalues[1], dumpmode); vts_dev_dbg(dev, "%s: Dump buffer\n", VTS_TAG_IPC); } } else if (ack_value == (0x1 << VTS_IRQ_AP_GET_VERSION)) { u32 version; mailbox_read_shared_register(data->pdev_mailbox, &version, 2, 1); vts_dev_dbg(dev, "google version(%d) : %d %d", __LINE__, version, data->google_version); } } else { vts_dev_err(dev, "Transaction failed\n"); } result = (*state == SEND_MSG_OK || ack_value) ? 0 : -EIO; } /* Clear running IPC & ACK value */ ack_value = 0x0; vts_mailbox_write_shared_register(data->pdev_mailbox, &ack_value, 3, 1); data->running_ipc = 0; *state = IDLE; spin_unlock(&data->ipc_spinlock); vts_dev_info(dev, "%s:[-%d]\n", VTS_TAG_IPC, msg); return (int)result; } int vts_start_ipc_transaction(struct device *dev, struct vts_data *data, int msg, u32 (*values)[3], int atomic, int sync) { return vts_start_ipc_transaction_atomic(dev, data, msg, values, sync); } EXPORT_SYMBOL(vts_start_ipc_transaction); static int vts_ipc_ack(struct vts_data *data, u32 result) { if (!data->vts_ready) return 0; pr_debug("%s(%p, %u)\n", __func__, data, result); vts_mailbox_write_shared_register(data->pdev_mailbox, &result, 0, 1); vts_mailbox_generate_interrupt(data->pdev_mailbox, VTS_IRQ_AP_IPC_RECEIVED); return 0; } int vts_send_ipc_ack(struct vts_data *data, u32 result) { return vts_ipc_ack(data, result); } int vts_imgloader_mem_setup( struct imgloader_desc *desc, const u8 *metadata, size_t size, phys_addr_t *fw_phys_base, size_t *fw_bin_size, size_t *fw_mem_size) { struct vts_data *data = dev_get_drvdata(desc->dev); vts_dev_info(desc->dev, "%s\n", __func__); if (!data) { vts_dev_err(desc->dev, "vts data is null\n"); return -EINVAL; } if (!data->firmware) { vts_dev_err(desc->dev, "fw is not loaded\n"); return -EINVAL; } memcpy(data->sram_base, data->firmware->data, data->firmware->size); *fw_phys_base = data->sram_phys; *fw_bin_size = data->firmware->size; *fw_mem_size = data->sram_size; vts_dev_info(desc->dev, "fw is downloaded(size=%zu)\n", data->firmware->size); return 0; } int vts_imgloader_verify_fw(struct imgloader_desc *desc, phys_addr_t fw_phys_base, size_t fw_bin_size, size_t fw_mem_size) { uint64_t ret64 = 0; if (!IS_ENABLED(CONFIG_EXYNOS_S2MPU)) { vts_dev_warn(desc->dev, "H-Arx is not enabled\n"); return 0; } ret64 = exynos_verify_subsystem_fw(desc->name, desc->fw_id, fw_phys_base, fw_bin_size, fw_mem_size); vts_dev_info(desc->dev, "verify fw(size:%zu)\n", fw_bin_size); if (ret64) { vts_dev_warn(desc->dev, "Failed F/W verification, ret=%llu\n", ret64); return -EIO; } vts_cpu_power(desc->dev, true); ret64 = exynos_request_fw_stage2_ap(desc->name); if (ret64) { vts_dev_warn(desc->dev, "Failed F/W verification to S2MPU, ret=%llu\n", ret64); return -EIO; } return 0; } struct imgloader_ops vts_imgloader_ops = { .mem_setup = vts_imgloader_mem_setup, .verify_fw = vts_imgloader_verify_fw, }; static int vts_core_imgloader_desc_init(struct platform_device *pdev) { struct vts_data *data = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; struct imgloader_desc *desc = &data->vts_imgloader_desc; desc->dev = &pdev->dev; desc->owner = THIS_MODULE; desc->ops = &vts_imgloader_ops; if (IS_ENABLED(CONFIG_SOC_S5E9925_EVT0)) { vts_dev_info(dev, "%s: EVT0 \n", __func__); desc->fw_name = "vts_evt0.bin"; } else { desc->fw_name = "vts.bin"; } desc->name = "VTS"; desc->s2mpu_support = false; desc->skip_request_firmware = true; desc->fw_id = 0; return imgloader_desc_init(desc); } static int vts_download_firmware(struct platform_device *pdev) { struct vts_data *data = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; vts_dev_info(dev, "%s\n", __func__); if (!data->firmware) { vts_dev_err(dev, "fw is not loaded\n"); return -EAGAIN; } memcpy(data->sram_base, data->firmware->data, data->firmware->size); vts_dev_info(dev, "fw is downloaded(size=%zu)\n", data->firmware->size); return 0; } static int vts_wait_for_fw_ready(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); int result; result = wait_event_timeout(data->ipc_wait_queue, data->vts_ready, msecs_to_jiffies(3000)); if (data->vts_ready) { result = 0; } else { vts_dev_err(dev, "VTS Firmware is not ready\n"); vts_dbg_dump_fw_gpr(dev, data, VTS_FW_NOT_READY); result = -ETIME; } return result; } bool vts_is_on(void) { vts_info("[VTS]%s : %d\n", __func__, (p_vts_data && p_vts_data->enabled)); return p_vts_data && p_vts_data->enabled; } EXPORT_SYMBOL(vts_is_on); bool vts_is_recognitionrunning(void) { return p_vts_data && p_vts_data->running; } EXPORT_SYMBOL(vts_is_recognitionrunning); static struct snd_soc_dai_driver vts_dai[] = { { .name = "vts-tri", .id = 0, .capture = { .stream_name = "VTS Trigger Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16, .sig_bits = 16, }, }, { .name = "vts-rec", .id = 1, .capture = { .stream_name = "VTS Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16, .sig_bits = 16, }, }, }; static const char * const vts_hpf_sel_texts[] = {"120Hz", "40Hz"}; static SOC_ENUM_SINGLE_DECL(vts_hpf_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_HPF_SEL_OFFSET, vts_hpf_sel_texts); static const char * const vts_cps_sel_texts[] = {"normal", "absolute"}; static SOC_ENUM_SINGLE_DECL(vts_cps_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_CPS_SEL_OFFSET, vts_cps_sel_texts); static const DECLARE_TLV_DB_SCALE(vts_gain_tlv_array, 0, 6, 0); static const char * const vts_sys_sel_texts[] = {"512kHz", "768kHz", "384kHz", "2048kHz", "3072kHz_48kHz", "3072kHz_96kHz"}; static SOC_ENUM_SINGLE_DECL(vts_sys_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_SYS_SEL_OFFSET, vts_sys_sel_texts); static int vts_sys_sel_put_enum( struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct device *dev = component->dev; unsigned int *item = ucontrol->value.enumerated.item; struct vts_data *data = p_vts_data; vts_dev_dbg(dev, "%s(%u)\n", __func__, item[0]); data->syssel_rate = item[0]; return snd_soc_put_enum_double(kcontrol, ucontrol); } static const char * const vts_polarity_clk_texts[] = {"rising edge of clock", "falling edge of clock"}; static SOC_ENUM_SINGLE_DECL(vts_polarity_clk, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_POLARITY_CLK_OFFSET, vts_polarity_clk_texts); static const char * const vts_polarity_output_texts[] = {"right first", "left first"}; static SOC_ENUM_SINGLE_DECL(vts_polarity_output, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_POLARITY_OUTPUT_OFFSET, vts_polarity_output_texts); static const char * const vts_polarity_input_texts[] = {"left PDM on CLK high", "left PDM on CLK low"}; static SOC_ENUM_SINGLE_DECL(vts_polarity_input, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_POLARITY_INPUT_OFFSET, vts_polarity_input_texts); static const char * const vts_ovfw_ctrl_texts[] = {"limit", "reset"}; static SOC_ENUM_SINGLE_DECL(vts_ovfw_ctrl, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_OVFW_CTRL_OFFSET, vts_ovfw_ctrl_texts); static const char * const vts_cic_sel_texts[] = {"Off", "On"}; static SOC_ENUM_SINGLE_DECL(vts_cic_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_CIC_SEL_OFFSET, vts_cic_sel_texts); static const char * const vtsvcrecog_mode_text[] = { "OFF", "ON" }; static const char * const vtsactive_phrase_text[] = { "NONE", "SoundModel_ID_1", "SoundModel_ID_2", "SoundModel_ID_3" }; static const char * const vtsforce_reset_text[] = { "NONE", "RESET" }; static SOC_ENUM_SINGLE_EXT_DECL(vtsvcrecog_mode_enum, vtsvcrecog_mode_text); static SOC_ENUM_SINGLE_EXT_DECL(vtsactive_phrase_enum, vtsactive_phrase_text); static SOC_ENUM_SINGLE_EXT_DECL(vtsforce_reset_enum, vtsforce_reset_text); static int vts_start_recognization(struct device *dev, int start) { struct vts_data *data = dev_get_drvdata(dev); int active_trigger = data->active_trigger; int result; u32 values[3]; vts_dev_info(dev, "%s for %s start %d\n", __func__, vtsactive_phrase_text[active_trigger], start); start = !!start; if (start) { vts_dev_info(dev, "%s: for %s, sm_loaded: %d\n", __func__, vtsactive_phrase_text[active_trigger], data->sm_loaded); vts_dev_info(dev, "%s exec_mode %d active_trig :%d\n", __func__, data->exec_mode, active_trigger); if (data->sm_loaded) { /* * load sound model data before starting recognition */ memcpy(data->sram_base + data->sm_info.offset, data->sm_data, data->sm_info.actual_sz); vts_dev_info(dev, "Binary uploaded size=%zu\n", data->sm_info.actual_sz); data->sm_loaded = false; } else { vts_dev_err(dev, "%s: Model Binary File not Loaded\n", __func__); return -EINVAL; } result = vts_set_dmicctrl(data->pdev, active_trigger, true); if (result < 0) { vts_dev_err(dev, "%s: MIC control failed\n", __func__); return result; } /* Send Start recognition IPC command to VTS */ values[0] = active_trigger; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_START_RECOGNITION, &values, 0, 1); if (result < 0) { vts_dev_err(dev, "vts ipc VTS_IRQ_AP_START_RECOGNITION failed: %d\n", result); return result; } #ifdef DEF_VTS_PCM_DUMP if (vts_pcm_dump_get_file_started(0)) { values[0] = 0; values[1] = 0; values[2] = 0; dev_info(dev, "VTS Recoding DUMP Start\n"); vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_START_REC, &values, 1, 1); vts_pcm_dump_reset(0); vts_pcm_dump_reset(1); vts_set_dmicctrl(data->pdev, VTS_MICCONF_FOR_RECORD, true); } #endif data->vts_state = VTS_STATE_RECOG_STARTED; data->poll_event_type = EVENT_NONE; vts_dev_info(dev, "%s start=%d, active_trigger=%d\n", __func__, start, active_trigger); } else if (!start) { values[0] = active_trigger; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_STOP_RECOGNITION, &values, 0, 1); if (result < 0) { vts_dev_err(dev, "vts ipc VTS_IRQ_AP_STOP_RECOGNITION failed: %d\n", result); /* HACK */ /* return result; */ } #ifdef DEF_VTS_PCM_DUMP if (vts_pcm_dump_get_file_started(0)) { values[0] = 0; values[1] = 0; values[2] = 0; dev_info(dev, "VTS Recoding DUMP Stop\n"); vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_STOP_REC, &values, 1, 1); vts_pcm_dump_reset(0); vts_pcm_dump_reset(1); vts_set_dmicctrl(data->pdev, VTS_MICCONF_FOR_RECORD, false); } #endif data->vts_state = VTS_STATE_RECOG_STOPPED; vts_dev_info(dev, "%s: start=%d, active_trigger=%d\n", __func__, start, active_trigger); if (data->clk_path == VTS_CLK_SRC_RCO || IS_ENABLED(CONFIG_SOC_S5E8825) || IS_ENABLED(CONFIG_SOC_S5E9925)) { result = vts_set_dmicctrl(data->pdev, active_trigger, false); if (result < 0) { vts_dev_err(dev, "%s: MIC control failed\n", __func__); return result; } } } return 0; } static int get_vtsvoicerecognize_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct vts_data *data = p_vts_data; ucontrol->value.integer.value[0] = data->exec_mode; vts_dev_dbg(component->dev, "GET VTS Execution mode: %d\n", data->exec_mode); return 0; } static int set_voicerecognize_mode(struct device *dev, int vcrecognize_mode) { struct vts_data *data = dev_get_drvdata(dev); int result = 0; int vcrecognize_start = 0; pm_runtime_barrier(dev); vts_dev_info(dev, "%s: requested id %d %s, current active recognition: 0x%x\n", __func__, data->active_trigger, vtsvcrecog_mode_text[vcrecognize_mode], data->voicerecog_start); if (vcrecognize_mode == VTS_RECOGNIZE_START) { if (data->voicerecog_start & (0x1 << data->active_trigger)) { vts_dev_warn(dev, "%s: requested trigger id %d is already completed", __func__, data->active_trigger); return 0; } pm_runtime_get_sync(dev); vts_start_runtime_resume(dev, 0); vts_clk_set_rate(dev, data->syssel_rate); vcrecognize_start = true; } else { if (!(data->voicerecog_start & (0x1 << data->active_trigger))) { vts_dev_warn(dev, "%s: requested trigger id %d is already off", __func__, data->active_trigger); return 0; } vcrecognize_start = false; } if (!pm_runtime_active(dev)) { vts_dev_warn(dev, "%s wrong state %d req: %d\n", __func__, data->exec_mode, vcrecognize_mode); return 0; } data->exec_mode = vcrecognize_mode; /* Start/stop the request Voice recognization mode */ result = vts_start_recognization(dev, vcrecognize_start); if (result < 0) { vts_dev_err(dev, "Start Recognization Failed: %d\n", result); goto err_ipcmode; } if (vcrecognize_start) data->voicerecog_start |= (0x1 << data->active_trigger); else data->voicerecog_start &= ~(0x1 << data->active_trigger); vts_dev_info(dev, "%s Configured: [%d] %s started\n", __func__, data->exec_mode, vtsvcrecog_mode_text[vcrecognize_mode]); if (!vcrecognize_start && pm_runtime_active(dev)) { pm_runtime_put_sync(dev); if (data->mic_ready == 0x0 && pm_runtime_active(dev)) { vts_dev_info(dev, "%s: VTS is off. Power off vts cpu\n", __func__); pm_runtime_get_sync(dev); vts_cpu_enable(dev, false); vts_cpu_power(dev, false); data->running = false; pm_runtime_put_sync(dev); } } return 0; err_ipcmode: if (pm_runtime_active(dev) && vcrecognize_start){ pm_runtime_put_sync(dev); } if (!vcrecognize_start && pm_runtime_active(dev)) { pm_runtime_put_sync(dev); data->voicerecog_start &= ~(0x1 << data->active_trigger); } return result; } static int set_vtsvoicerecognize_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct device *dev = component->dev; struct vts_data *data = dev_get_drvdata(dev); int vcrecognize_mode = 0; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; int ret = 0; vcrecognize_mode = ucontrol->value.integer.value[0]; vts_dev_info(dev, "%s state %d %d (%d)\n", __func__, data->vts_state, data->clk_path, data->micclk_init_cnt); if (item[0] >= e->items) { vts_dev_err(dev, "%s try to set %d but should be under %d\n", __func__, item[0], e->items); return -EINVAL; } /* check dmic_if clk */ if (data->clk_path == VTS_CLK_SRC_AUD0) { data->syssel_rate = 4; vts_dev_info(dev, "changed as 3072000Hz mode\n"); } ret = set_voicerecognize_mode(dev, vcrecognize_mode); return ret; } int vts_chk_dmic_clk_mode(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); int ret = 0; int syssel_rate_value = 1; u32 values[3] = {0, 0, 0}; vts_dev_info(dev, "%s state %d %d (%d)\n", __func__, data->vts_state, data->clk_path, data->micclk_init_cnt); /* already started recognization mode and dmic_if clk is 3.072MHz*/ /* VTS worked + Serial LIF */ if (data->clk_path == VTS_CLK_SRC_RCO) syssel_rate_value = 1; else syssel_rate_value = 4; if (data->vts_state == VTS_STATE_RECOG_STARTED) { data->syssel_rate = syssel_rate_value; /* restart VTS to update mic and clock setting */ data->poll_event_type |= EVENT_RESTART|EVENT_READY; wake_up(&data->poll_wait_queue); } if (data->vts_state == VTS_STATE_RECOG_TRIGGERED) { ret = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_STOP_COPY, &values, 1, 1); data->syssel_rate = syssel_rate_value; vts_clk_set_rate(dev, data->syssel_rate); if (data->micclk_init_cnt) data->micclk_init_cnt--; ret = vts_set_dmicctrl(data->pdev, data->active_trigger, true); if (ret < 0) vts_dev_err(dev, "%s: MIC control failed\n", __func__); vts_dev_info(dev, "VTS Triggered Fail : Serial LIF ON\n"); ret = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_START_COPY, &values, 1, 1); } if (data->vts_rec_state == VTS_REC_STATE_START) { ret = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_STOP_REC, &values, 1, 1); data->syssel_rate = syssel_rate_value; vts_clk_set_rate(dev, data->syssel_rate); if (data->micclk_init_cnt) data->micclk_init_cnt--; ret = vts_set_dmicctrl(data->pdev, VTS_MICCONF_FOR_RECORD, true); if (ret < 0) vts_dev_err(dev, "%s: MIC control failed\n", __func__); vts_dev_info(dev, "VTS Recoding : Change SYS_SEL : %d\n", syssel_rate_value); ret = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_START_REC, &values, 1, 1); } return ret; } EXPORT_SYMBOL(vts_chk_dmic_clk_mode); static int get_vtsactive_phrase(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct vts_data *data = p_vts_data; ucontrol->value.integer.value[0] = data->active_trigger; vts_dev_dbg(component->dev, "GET VTS Active Phrase: %s\n", vtsactive_phrase_text[data->active_trigger]); return 0; } static int set_vtsactive_phrase(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct vts_data *data = p_vts_data; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; int vtsactive_phrase; pm_runtime_barrier(component->dev); if (item[0] >= e->items) { vts_dev_err(component->dev, "%s try to set %d but shuld be under %d\n", __func__, item[0], e->items); return -EINVAL; } vtsactive_phrase = ucontrol->value.integer.value[0]; if (vtsactive_phrase < 0) { vts_dev_err(component->dev, "Invalid VTS Trigger Key phrase =%d", vtsactive_phrase); return 0; } data->active_trigger = vtsactive_phrase; vts_dev_info(component->dev, "VTS Active phrase: %s\n", vtsactive_phrase_text[vtsactive_phrase]); return 0; } static int get_voicetrigger_value(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct vts_data *data = p_vts_data; ucontrol->value.integer.value[0] = data->target_size; vts_dev_info(component->dev, "GET Voice Trigger Value: %d\n", data->target_size); return 0; } static int set_voicetrigger_value(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct vts_data *data = p_vts_data; int active_trigger = data->active_trigger; u32 values[3]; int result = 0; int trig_ms; pm_runtime_barrier(component->dev); trig_ms = ucontrol->value.integer.value[0]; if (trig_ms > 2000 || trig_ms < 0) { vts_dev_err(component->dev, "Invalid Voice Trigger Value = %d (valid range 0~2000ms)", trig_ms); return 0; } /* Configure VTS target size */ /* 1ms requires (16KHz,16bit,Mono) = 16samples * 2 bytes = 32 bytes*/ values[0] = trig_ms * 32; values[1] = active_trigger; values[2] = 0; result = vts_start_ipc_transaction(component->dev, data, VTS_IRQ_AP_TARGET_SIZE, &values, 0, 1); if (result < 0) { vts_dev_err(component->dev, "Voice Trigger Value setting IPC Transaction Failed: %d\n", result); return result; } data->target_size = trig_ms; vts_dev_info(component->dev, "SET Voice Trigger Value: %dms\n", data->target_size); return 0; } static int get_vtsforce_reset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct vts_data *data = p_vts_data; ucontrol->value.integer.value[0] = data->running; vts_dev_dbg(component->dev, "GET VTS Force Reset: %s\n", (data->running ? "VTS Running" : "VTS Not Running")); return 0; } static int set_vtsforce_reset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct device *dev = component->dev; struct vts_data *data = p_vts_data; vts_dev_info(dev, "%s: VTS RESET\n", __func__); while (data->running && pm_runtime_active(dev)) { vts_dev_warn(dev, "%s: clear active models\n", __func__); pm_runtime_put_sync(dev); } return 0; } static int get_force_trigger(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { /* Nothing To Do */ return 0; } static int set_force_trigger(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct device *dev = component->dev; struct vts_data *data = p_vts_data; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; u32 values[3]; int result = 0; int val = ucontrol->value.integer.value[0]; if (item[0] >= e->items) { vts_dev_err(dev, "%s try to set %d but should be under %d\n", __func__, item[0], e->items); return -EINVAL; } values[0] = VTS_FORCE_TRIGGER; values[1] = val; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 0); if (result < 0) { vts_dev_err(dev, "Force Trigger setting IPC Transaction Failed: %d\n", result); return result; } vts_dev_info(dev, "Force Trigger Command, command num : %d\n", val); return 0; } static int get_supported_mic_num(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct vts_data *data = p_vts_data; ucontrol->value.integer.value[0] = data->supported_mic_num; return 0; } static int set_supported_mic_num(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct device *dev = component->dev; struct vts_data *data = p_vts_data; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; if (item[0] >= e->items) { vts_dev_err(dev, "%s try to set %d but should be under %d\n", __func__, item[0], e->items); return -EINVAL; } data->supported_mic_num = (unsigned int)ucontrol->value.integer.value[0]; vts_dev_info(dev, "%s: %d\n", __func__, data->supported_mic_num); return 0; } static int get_sysclk_div(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct vts_data *data = p_vts_data; ucontrol->value.integer.value[0] = data->sysclk_div; return 0; } static int set_sysclk_div(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct device *dev = component->dev; struct vts_data *data = p_vts_data; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; if (item[0] >= e->items) { vts_dev_err(dev, "%s try to set %d but should be under %d\n", __func__, item[0], e->items); return -EINVAL; } data->sysclk_div = (unsigned int)ucontrol->value.integer.value[0]; data->target_sysclk = VTS_SYS_CLOCK_MAX / data->sysclk_div; vts_dev_info(dev, "%s: sysclk div(%d) target_sysclk(%d)\n", __func__, data->sysclk_div, data->target_sysclk); return 0; } static const struct snd_kcontrol_new vts_controls[] = { SOC_SINGLE("PERIOD DATA2REQ", VTS_DMIC_ENABLE_DMIC_IF, VTS_DMIC_PERIOD_DATA2REQ_OFFSET, 3, 0), SOC_SINGLE("HPF EN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_HPF_EN_OFFSET, 1, 0), SOC_ENUM("HPF SEL", vts_hpf_sel), SOC_ENUM("CPS SEL", vts_cps_sel), SOC_SINGLE_TLV("GAIN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_GAIN_OFFSET, 4, 0, vts_gain_tlv_array), SOC_SINGLE("1DB GAIN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_1DB_GAIN_OFFSET, 5, 0), SOC_ENUM_EXT("SYS SEL", vts_sys_sel, snd_soc_get_enum_double, vts_sys_sel_put_enum), SOC_ENUM("POLARITY CLK", vts_polarity_clk), SOC_ENUM("POLARITY OUTPUT", vts_polarity_output), SOC_ENUM("POLARITY INPUT", vts_polarity_input), SOC_ENUM("OVFW CTRL", vts_ovfw_ctrl), SOC_ENUM("CIC SEL", vts_cic_sel), SOC_ENUM_EXT("VoiceRecognization Mode", vtsvcrecog_mode_enum, get_vtsvoicerecognize_mode, set_vtsvoicerecognize_mode), SOC_ENUM_EXT("Active Keyphrase", vtsactive_phrase_enum, get_vtsactive_phrase, set_vtsactive_phrase), SOC_SINGLE_EXT("VoiceTrigger Value", SND_SOC_NOPM, 0, 2000, 0, get_voicetrigger_value, set_voicetrigger_value), SOC_ENUM_EXT("Force Reset", vtsforce_reset_enum, get_vtsforce_reset, set_vtsforce_reset), SOC_SINGLE_EXT("Force Trigger", SND_SOC_NOPM, 0, 1000, 0, get_force_trigger, set_force_trigger), SOC_SINGLE_EXT("Supported Mic Num", SND_SOC_NOPM, 0, 3, 0, get_supported_mic_num, set_supported_mic_num), SOC_SINGLE_EXT("SYSCLK DIV", SND_SOC_NOPM, 0, 10, 0, get_sysclk_div, set_sysclk_div), }; static int vts_dmic_sel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct vts_data *data = p_vts_data; unsigned int dmic_sel; dmic_sel = ucontrol->value.enumerated.item[0]; if (dmic_sel > 1) return -EINVAL; vts_info("[VTS]%s : VTS DMIC SEL: %d\n", __func__, dmic_sel); data->dmic_if = dmic_sel; return 0; } static const char * const dmic_sel_texts[] = {"DPDM", "APDM"}; static SOC_ENUM_SINGLE_EXT_DECL(dmic_sel_enum, dmic_sel_texts); static const struct snd_kcontrol_new dmic_sel_controls[] = { SOC_DAPM_ENUM_EXT("MUX", dmic_sel_enum, snd_soc_dapm_get_enum_double, vts_dmic_sel_put), }; static const struct snd_kcontrol_new dmic_if_controls[] = { SOC_DAPM_SINGLE("RCH EN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_RCH_EN_OFFSET, 1, 0), SOC_DAPM_SINGLE("LCH EN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_LCH_EN_OFFSET, 1, 0), }; static const struct snd_soc_dapm_widget vts_dapm_widgets[] = { SND_SOC_DAPM_INPUT("PAD APDM"), SND_SOC_DAPM_INPUT("PAD DPDM"), SND_SOC_DAPM_MUX("DMIC SEL", SND_SOC_NOPM, 0, 0, dmic_sel_controls), SOC_MIXER_ARRAY("DMIC IF", SND_SOC_NOPM, 0, 0, dmic_if_controls), }; static const struct snd_soc_dapm_route vts_dapm_routes[] = { // sink, control, source {"DMIC SEL", "APDM", "PAD APDM"}, {"DMIC SEL", "DPDM", "PAD DPDM"}, {"DMIC IF", "RCH EN", "DMIC SEL"}, {"DMIC IF", "LCH EN", "DMIC SEL"}, {"VTS Capture", NULL, "DMIC IF"}, }; int vts_set_dmicctrl(struct platform_device *pdev, int micconf_type, bool enable) { struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); int ctrl_dmicif = 0; int select_dmicclk = 0; vts_dev_dbg(dev, "%s-- flag: %d mictype: %d micusagecnt: %d\n", __func__, enable, micconf_type, data->micclk_init_cnt); if (!data->vts_ready) { vts_dev_warn(dev, "%s: VTS Firmware Not running\n", __func__); return -EINVAL; } if (enable) { if (!data->micclk_init_cnt) { ctrl_dmicif = readl(data->dmic_if0_base + VTS_DMIC_CONTROL_DMIC_IF); if (IS_ENABLED(CONFIG_SOC_EXYNOS2100) || IS_ENABLED(CONFIG_SOC_S5E9925) || IS_ENABLED(CONFIG_SOC_S5E8825)) { clear_bits(ctrl_dmicif, 7, VTS_DMIC_SYS_SEL_OFFSET); set_bits(ctrl_dmicif, data->syssel_rate, VTS_DMIC_SYS_SEL_OFFSET); vts_dev_info(dev, "%s DMIC IF SYS_SEL : %d\n", __func__, data->syssel_rate); } if (data->dmic_if == APDM) { vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, APDM, true); set_bit(select_dmicclk, VTS_ENABLE_CLK_GEN_OFFSET); set_bit(select_dmicclk, VTS_SEL_EXT_DMIC_CLK_OFFSET); set_bit(select_dmicclk, VTS_ENABLE_CLK_CLK_GEN_OFFSET); /* Set AMIC VTS Gain */ set_bits(ctrl_dmicif, data->dmic_if, VTS_DMIC_DMIC_SEL_OFFSET); set_bits(ctrl_dmicif, data->amicgain, VTS_DMIC_GAIN_OFFSET); } else { vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, DPDM, true); if (data->supported_mic_num == MIC_NUM2) vts_port_cfg(dev, VTS_PORT_PAD1, VTS_PORT_ID_VTS, DPDM, true); clear_bit(select_dmicclk, VTS_ENABLE_CLK_GEN_OFFSET); clear_bit(select_dmicclk, VTS_SEL_EXT_DMIC_CLK_OFFSET); clear_bit(select_dmicclk, VTS_ENABLE_CLK_CLK_GEN_OFFSET); /* Set DMIC VTS Gain */ set_bits(ctrl_dmicif, data->dmic_if, VTS_DMIC_DMIC_SEL_OFFSET); set_bits(ctrl_dmicif, data->dmicgain, VTS_DMIC_GAIN_OFFSET); } writel(select_dmicclk, data->sfr_base + VTS_DMIC_CLK_CON); writel(ctrl_dmicif, data->dmic_if0_base + VTS_DMIC_CONTROL_DMIC_IF); if (data->supported_mic_num == MIC_NUM2) writel(ctrl_dmicif, data->dmic_if1_base + VTS_DMIC_CONTROL_DMIC_IF); } /* check whether Mic is already configure or not based on VTS * option type for MIC configuration book keeping */ if (!(data->mic_ready & (0x1 << micconf_type))) { data->micclk_init_cnt++; data->mic_ready |= (0x1 << micconf_type); vts_dev_info(dev, "%s Micclk ENABLED for mic_ready ++ %d\n", __func__, data->mic_ready); } } else { if (data->micclk_init_cnt) data->micclk_init_cnt--; if (!data->micclk_init_cnt) { if (data->clk_path != VTS_CLK_SRC_AUD0) { vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, DPDM, false); if (data->supported_mic_num == MIC_NUM2) vts_port_cfg(dev, VTS_PORT_PAD1, VTS_PORT_ID_VTS, DPDM, false); } /* reset VTS Gain to default */ writel(0x0, data->dmic_if0_base + VTS_DMIC_CONTROL_DMIC_IF); if (data->supported_mic_num == MIC_NUM2) writel(0x0, data->dmic_if1_base + VTS_DMIC_CONTROL_DMIC_IF); vts_dev_info(dev, "%s Micclk setting DISABLED\n", __func__); } /* MIC configuration book keeping */ if (data->mic_ready & (0x1 << micconf_type)) { data->mic_ready &= ~(0x1 << micconf_type); vts_dev_info(dev, "%s Micclk DISABLED for mic_ready -- %d\n", __func__, data->mic_ready); } } return 0; } EXPORT_SYMBOL(vts_set_dmicctrl); static irqreturn_t vts_error_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); u32 error_cfsr, error_hfsr, error_dfsr, error_afsr, error_bfsr; u32 error_code; vts_mailbox_read_shared_register(data->pdev_mailbox, &error_code, 3, 1); switch (error_code) { case VTS_ERR_HARD_FAULT: vts_mailbox_read_shared_register(data->pdev_mailbox, &error_cfsr, 1, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_hfsr, 2, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_dfsr, 4, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_afsr, 5, 1); vts_dev_err(dev, "HardFault CFSR 0x%x\n", (int)error_cfsr); vts_dev_err(dev, "HardFault HFSR 0x%x\n", (int)error_hfsr); vts_dev_err(dev, "HardFault DFSR 0x%x\n", (int)error_dfsr); vts_dev_err(dev, "HardFault AFSR 0x%x\n", (int)error_afsr); break; case VTS_ERR_BUS_FAULT: vts_mailbox_read_shared_register(data->pdev_mailbox, &error_cfsr, 1, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_hfsr, 2, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_dfsr, 4, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_bfsr, 5, 1); vts_dev_err(dev, "BusFault CFSR 0x%x\n", (int)error_cfsr); vts_dev_err(dev, "BusFault HFSR 0x%x\n", (int)error_hfsr); vts_dev_err(dev, "BusFault DFSR 0x%x\n", (int)error_dfsr); vts_dev_err(dev, "BusFault BFSR 0x%x\n", (int)error_bfsr); break; default: vts_dev_warn(dev, "undefined error_code: %d\n", error_code); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_cfsr, 1, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_hfsr, 2, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_dfsr, 4, 1); vts_mailbox_read_shared_register(data->pdev_mailbox, &error_afsr, 5, 1); vts_dev_err(dev, "0x%x 0x%x 0x%x 0x%x\n", (int)error_cfsr, (int)error_hfsr, (int)error_dfsr, (int)error_afsr); break; } vts_ipc_ack(data, 1); vts_dev_err(dev, "Error occurred on VTS: 0x%x\n", (int)error_code); #ifdef VTS_SICD_CHECK vts_dev_err(dev, "SOC down : %d, MIF down : %d", readl(data->sicd_base + SICD_SOC_DOWN_OFFSET), readl(data->sicd_base + SICD_MIF_DOWN_OFFSET)); #endif /* Dump VTS GPR register & SRAM */ vts_dbg_dump_fw_gpr(dev, data, VTS_FW_ERROR); vts_dev_err(dev, "shared_info0x%x, 0x%x 0x%x", data->shared_info->vendor_data[0], data->shared_info->vendor_data[1], data->shared_info->vendor_data[2]); /* log dump */ print_hex_dump(KERN_ERR, "vts-fw-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr, VTS_SRAM_EVENTLOG_SIZE_MAX, true); print_hex_dump(KERN_ERR, "vts-time-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr + VTS_SRAM_EVENTLOG_SIZE_MAX, VTS_SRAM_TIMELOG_SIZE_MAX, true); switch (error_code) { case VTS_ERR_HARD_FAULT: case VTS_ERR_BUS_FAULT: dbg_snapshot_expire_watchdog(); break; default: vts_reset_cpu(dev); break; } return IRQ_HANDLED; } static irqreturn_t vts_boot_completed_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); data->vts_ready = 1; vts_ipc_ack(data, 1); wake_up(&data->ipc_wait_queue); vts_dev_info(dev, "VTS boot completed\n"); return IRQ_HANDLED; } static irqreturn_t vts_ipc_received_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); u32 result; mailbox_read_shared_register(data->pdev_mailbox, &result, 3, 1); vts_dev_info(dev, "VTS received IPC: 0x%x\n", result); switch (data->ipc_state_ap) { case SEND_MSG: if (result == (0x1 << data->running_ipc)) { vts_dev_dbg(dev, "IPC transaction completed\n"); data->ipc_state_ap = SEND_MSG_OK; } else { vts_dev_err(dev, "IPC transaction error\n"); data->ipc_state_ap = SEND_MSG_FAIL; } break; default: vts_dev_warn(dev, "State fault: %d Ack_value:0x%x\n", data->ipc_state_ap, result); break; } return IRQ_HANDLED; } static irqreturn_t vts_voice_triggered_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); u32 id, score, frame_count; vts_dev_info(dev, "%s: enter\n", __func__); vts_mailbox_read_shared_register(data->pdev_mailbox, &id, 3, 1); vts_ipc_ack(data, 1); frame_count = (u32)(id & GENMASK(15, 0)); score = (u32)((id & GENMASK(27, 16)) >> 16); id >>= 28; if (data->mic_ready & (1 << id)) { vts_dev_info(dev, "VTS triggered: id = %u, score = %u\n", id, score); vts_dev_info(dev, "VTS triggered: frame_count = %u\n", frame_count); /* trigger event should be sent with triggered id */ data->poll_event_type |= (EVENT_TRIGGERED|EVENT_READY) + id; wake_up(&data->poll_wait_queue); __pm_wakeup_event(data->wake_lock, VTS_TRIGGERED_TIMEOUT_MS); data->vts_state = VTS_STATE_RECOG_TRIGGERED; } return IRQ_HANDLED; } static void vts_update_kernel_time(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); unsigned long long kernel_time = sched_clock(); data->shared_info->kernel_sec = (u32)(kernel_time / 1000000000); data->shared_info->kernel_msec = (u32)(kernel_time % 1000000000 / 1000000); } static irqreturn_t vts_trigger_period_elapsed_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); struct vts_dma_data *dma_data = platform_get_drvdata(data->pdev_vtsdma[0]); u32 pointer; if (data->mic_ready && (data->mic_ready & ~(1 << VTS_MICCONF_FOR_RECORD))) { vts_mailbox_read_shared_register(data->pdev_mailbox, &pointer, 2, 1); vts_dev_dbg(dev, "%s:[%s] Base: %08x pointer:%08x\n", __func__, (dma_data->id ? "VTS-RECORD" : "VTS-TRIGGER"), data->dma_area_vts, pointer); if (pointer) dma_data->pointer = (pointer - data->dma_area_vts); vts_ipc_ack(data, 1); #ifdef DEF_VTS_PCM_DUMP if (vts_pcm_dump_get_file_started(1)) vts_pcm_dump_period_elapsed(1, dma_data->pointer); #endif snd_pcm_period_elapsed(dma_data->substream); } return IRQ_HANDLED; } static irqreturn_t vts_record_period_elapsed_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); struct vts_dma_data *dma_data = platform_get_drvdata(data->pdev_vtsdma[1]); u32 pointer; if (data->vts_state == VTS_STATE_RUNTIME_SUSPENDING || data->vts_state == VTS_STATE_RUNTIME_SUSPENDED || data->vts_state == VTS_STATE_NONE) { vts_dev_warn(dev, "%s: VTS wrong state\n", __func__); return IRQ_HANDLED; } if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) { vts_mailbox_read_shared_register(data->pdev_mailbox, &pointer, 1, 1); vts_dev_dbg(dev, "%s:[%s] 0x%x:0x%x\n", __func__, (dma_data->id ? "REC" : "TRI"), (data->dma_area_vts + BUFFER_BYTES_MAX/2), pointer); if (pointer) dma_data->pointer = (pointer - (data->dma_area_vts + BUFFER_BYTES_MAX/2)); vts_ipc_ack(data, 1); #ifdef DEF_VTS_PCM_DUMP if (vts_pcm_dump_get_file_started(0)) vts_pcm_dump_period_elapsed(0, dma_data->pointer); #endif snd_pcm_period_elapsed(dma_data->substream); } return IRQ_HANDLED; } static irqreturn_t vts_debuglog_bufzero_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); if (!data->running) { vts_dev_err(dev, "%s: wrong status\n", __func__); return IRQ_HANDLED; } vts_dev_dbg(dev, "%s LogBuffer Index: %d\n", __func__, 0); /* schedule log dump */ vts_log_schedule_flush(dev, 0); return IRQ_HANDLED; } static irqreturn_t vts_debuglog_bufone_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); if (!data->running) { vts_dev_err(dev, "%s: wrong status\n", __func__); return IRQ_HANDLED; } vts_dev_dbg(dev, "%s LogBuffer Index: %d\n", __func__, 1); /* schedule log dump */ vts_log_schedule_flush(dev, 1); vts_ipc_ack(data, 1); return IRQ_HANDLED; } static irqreturn_t vts_audiodump_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); /* u32 pointer; */ vts_dev_dbg(dev, "%s\n", __func__); if (data->vts_ready && data->audiodump_enabled) { u32 ackvalues[3] = {0, 0, 0}; mailbox_read_shared_register(data->pdev_mailbox, ackvalues, 0, 2); vts_dev_info(dev, "%sDump offset: 0x%x size:0x%x\n", __func__, ackvalues[0], ackvalues[1]); /* register audio dump offset & size */ vts_dump_addr_register(dev, ackvalues[0], ackvalues[1], VTS_AUDIO_DUMP); /* schedule pcm dump */ vts_audiodump_schedule_flush(dev); /* vts_ipc_ack should be sent once dump is completed */ } else if (IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_SLIF) && data->vts_ready && data->slif_dump_enabled) { // vts_mailbox_read_shared_register(data->pdev_mailbox, // &pointer, 1, 1); // vts_dev_info(dev, "audiodump[%08x:%08x][p:%08x]\n", // data->dma_area_vts, // (data->dma_area_vts + BUFFER_BYTES_MAX/2), // pointer); // vts_ipc_ack(data, 1); /* vts_s_lif_dump_period_elapsed(1, pointer); */ } else { vts_ipc_ack(data, 1); } return IRQ_HANDLED; } static irqreturn_t vts_logdump_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); vts_dev_info(dev, "%s\n", __func__); if (data->vts_ready && data->logdump_enabled) { /* schedule pcm dump */ vts_logdump_schedule_flush(dev); /* vts_ipc_ack should be sent once dump is completed */ } else { vts_ipc_ack(data, 1); } return IRQ_HANDLED; } void vts_register_dma(struct platform_device *pdev_vts, struct platform_device *pdev_vts_dma, unsigned int id) { struct vts_data *data = platform_get_drvdata(pdev_vts); if (id < ARRAY_SIZE(data->pdev_vtsdma)) { data->pdev_vtsdma[id] = pdev_vts_dma; if (id > data->vtsdma_count) data->vtsdma_count = id + 1; vts_dev_info(&data->pdev->dev, "%s: VTS-DMA id(%u)Registered\n", __func__, id); } else { vts_dev_err(&data->pdev->dev, "%s: invalid id(%u)\n", __func__, id); } } EXPORT_SYMBOL(vts_register_dma); static int vts_suspend(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); u32 values[3] = {0, 0, 0}; int result = 0; if (data->vts_ready) { if (data->running && data->vts_state == VTS_STATE_RECOG_TRIGGERED) { result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_RESTART_RECOGNITION, &values, 0, 1); if (result < 0) { vts_dev_err(dev, "%s restarted trigger failed\n", __func__); goto error_ipc; } data->vts_state = VTS_STATE_RECOG_STARTED; } /* enable vts wakeup source interrupts */ enable_irq_wake(data->irq[VTS_IRQ_VTS_VOICE_TRIGGERED]); enable_irq_wake(data->irq[VTS_IRQ_VTS_ERROR]); #ifdef TEST_WAKEUP enable_irq_wake(data->irq[VTS_IRQ_VTS_CP_WAKEUP]); #endif if (data->audiodump_enabled) enable_irq_wake(data->irq[VTS_IRQ_VTS_AUDIO_DUMP]); if (data->logdump_enabled) enable_irq_wake(data->irq[VTS_IRQ_VTS_LOG_DUMP]); vts_dev_info(dev, "%s: Enable VTS Wakeup source irqs\n", __func__); } vts_dev_info(dev, "%s: TEST\n", __func__); error_ipc: return result; } static int vts_resume(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); if (data->vts_ready) { /* disable vts wakeup source interrupts */ disable_irq_wake(data->irq[VTS_IRQ_VTS_VOICE_TRIGGERED]); disable_irq_wake(data->irq[VTS_IRQ_VTS_ERROR]); #ifdef TEST_WAKEUP disable_irq_wake(data->irq[VTS_IRQ_VTS_CP_WAKEUP]); #endif if (data->audiodump_enabled) disable_irq_wake(data->irq[VTS_IRQ_VTS_AUDIO_DUMP]); if (data->logdump_enabled) disable_irq_wake(data->irq[VTS_IRQ_VTS_LOG_DUMP]); vts_dev_info(dev, "%s: Disable VTS Wakeup source irqs\n", __func__); } return 0; } static void vts_irq_enable(struct platform_device *pdev, bool enable) { struct vts_data *data = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; int irqidx; vts_dev_info(dev, "%s IRQ Enable: [%s]\n", __func__, (enable ? "TRUE" : "FALSE")); for (irqidx = 0; irqidx < VTS_IRQ_COUNT; irqidx++) { if (enable) enable_irq(data->irq[irqidx]); else disable_irq(data->irq[irqidx]); } #ifdef TEST_WAKEUP if (enable) enable_irq(data->irq[VTS_IRQ_VTS_CP_WAKEUP]); else disable_irq(data->irq[VTS_IRQ_VTS_CP_WAKEUP]); #endif } static void vts_save_register(struct vts_data *data) { regcache_cache_only(data->regmap_dmic, true); regcache_mark_dirty(data->regmap_dmic); } static void vts_restore_register(struct vts_data *data) { regcache_cache_only(data->regmap_dmic, false); regcache_sync(data->regmap_dmic); } static void vts_memlog_sync_to_file(struct device *dev, struct vts_data *data) { if (data->kernel_log_obj) vts_dev_info(dev, "%s kernel_log_obj sync : %d", __func__, memlog_sync_to_file(data->kernel_log_obj)); if (data->fw_log_obj) vts_dev_info(dev, "%s fw_log_obj sync : %d", __func__, memlog_sync_to_file(data->fw_log_obj)); } void vts_dbg_dump_fw_gpr(struct device *dev, struct vts_data *data, unsigned int dbg_type) { #if defined(CONFIG_SOC_S5E8825) int i; #endif vts_dev_dbg(dev, "%s\n", __func__); switch (dbg_type) { case RUNTIME_SUSPEND_DUMP: if (!vts_is_on() || !data->running) { vts_dev_info(dev, "%s is skipped due to no power\n", __func__); return; } #if defined(CONFIG_SOC_S5E9925) if (data->dump_obj) memlog_do_dump(data->dump_obj, MEMLOG_LEVEL_INFO); #endif vts_memlog_sync_to_file(dev, data); /* Save VTS firmware log msgs */ if (!IS_ERR_OR_NULL(data->p_dump[dbg_type].sram_log) && !IS_ERR_OR_NULL(data->sramlog_baseaddr)) { memcpy(data->p_dump[dbg_type].sram_log, VTS_DUMP_MAGIC, sizeof(VTS_DUMP_MAGIC)); memcpy_fromio(data->p_dump[dbg_type].sram_log + sizeof(VTS_DUMP_MAGIC), data->sramlog_baseaddr, VTS_SRAMLOG_SIZE_MAX); } break; case KERNEL_PANIC_DUMP: case VTS_ITMON_ERROR: if (!data->running) { vts_dev_info(dev, "%s is skipped due to not running\n", __func__); return; } case VTS_FW_NOT_READY: case VTS_IPC_TRANS_FAIL: case VTS_FW_ERROR: #if defined(CONFIG_SOC_S5E8825) for (i = 0; i <= 15; i++) { vts_dev_info(dev, "R%d: %x\n", i, readl(data->gpr_base + VTS_CM4_R(i))); data->p_dump[dbg_type].gpr[i] = readl(data->gpr_base + VTS_CM4_R(i)); } vts_dev_info(dev, "PC: %x\n", readl(data->gpr_base + VTS_CM4_PC)); data->p_dump[dbg_type].gpr[i++] = readl(data->gpr_base + VTS_CM4_PC); #else vts_dev_info(dev, "PC: 0x%x\n", readl(data->gpr_base + 0x0)); data->p_dump[dbg_type].gpr[0] = readl(data->gpr_base + 0x0); #endif if (data->dump_obj && (dbg_type == VTS_IPC_TRANS_FAIL || dbg_type == VTS_FW_ERROR)) { memlog_do_dump(data->dump_obj, MEMLOG_LEVEL_ERR); vts_memlog_sync_to_file(dev, data); } /* Save VTS firmware log msgs */ if (!IS_ERR_OR_NULL(data->p_dump[dbg_type].sram_log) && !IS_ERR_OR_NULL(data->sramlog_baseaddr)) { memcpy(data->p_dump[dbg_type].sram_log, VTS_DUMP_MAGIC, sizeof(VTS_DUMP_MAGIC)); memcpy_fromio(data->p_dump[dbg_type].sram_log + sizeof(VTS_DUMP_MAGIC), data->sramlog_baseaddr, VTS_SRAMLOG_SIZE_MAX); } /* Save VTS firmware all */ if (!IS_ERR_OR_NULL(data->p_dump[dbg_type].sram_fw) && !IS_ERR_OR_NULL(data->sram_base)) { memcpy_fromio(data->p_dump[dbg_type].sram_fw, data->sram_base, data->sram_size); } break; default: vts_dev_info(dev, "%s is skipped due to invalid debug type\n", __func__); return; } data->p_dump[dbg_type].time = sched_clock(); data->p_dump[dbg_type].dbg_type = dbg_type; } static void exynos_vts_panic_handler(void) { static bool has_run; struct vts_data *data = p_vts_data; struct device *dev = data ? (data->pdev ? &data->pdev->dev : NULL) : NULL; vts_dev_dbg(dev, "%s\n", __func__); if (vts_is_on() && dev) { if (has_run) { vts_dev_info(dev, "already dumped\n"); return; } has_run = true; /* Dump VTS GPR register & SRAM */ vts_dbg_dump_fw_gpr(dev, data, KERNEL_PANIC_DUMP); } else { vts_dev_info(dev, "%s: dump is skipped due to no power\n", __func__); } } static int vts_panic_handler(struct notifier_block *nb, unsigned long action, void *data) { exynos_vts_panic_handler(); return NOTIFY_OK; } static struct notifier_block vts_panic_notifier = { .notifier_call = vts_panic_handler, .next = NULL, .priority = 0 /* priority: INT_MAX >= x >= 0 */ }; #define SLIF_SEL_VTS (0x824) /* 0x15510824 */ static int vts_do_reg(struct device *dev, bool enable, int cmd) { struct vts_data *data = dev_get_drvdata(dev); int ret = 0; if (IS_ENABLED(CONFIG_SOC_S5E8825)) { if (enable) writel(0x1, data->gpr_base); /* enable DUMP_GPR */ return ret; } if (!IS_ENABLED(CONFIG_SOC_S5E9925_EVT0)) return ret; /* HACK */ if (enable) { /* SYSREG_VTS + 0x824 */ /*0 : SERIAL_LIF, 1 : DMIC_AHB */ writel(0x0, data->sfr_base + SLIF_SEL_VTS); /* SL_INT_EN_SET : 0x040 */ /* [9:8] Interrupt ENABLE SL_READ_POINTER_SET0 & */ /* SL_READ_POINTER_SET1 */ /* writel(0x300, data->sfr_slif_vts + 0x40); */ /* INPUT_EN: EN0, EN1 */ /* writel(0x3, data->sfr_slif_vts + 0x114); */ /* CONFIG_MASTER: 16bit, 2ch */ /* writel(0x00102009, data->sfr_slif_vts + 0x104); */ /* CHANNEL_MAP: 0 1 0 1 0 1 0 1 */ /* writel(0x20202020, data->sfr_slif_vts + 0x138); */ /* writel(0x1111, data->sfr_slif_vts + 0x100); */ } else { writel(0x0, data->sfr_base + SLIF_SEL_VTS); /* writel(0x0, data->sfr_slif_vts + 0x40); */ /* writel(0x0, data->sfr_slif_vts + 0x114); */ /* writel(0x00102009, data->sfr_slif_vts + 0x104); */ /* writel(0x76543210, data->sfr_slif_vts + 0x138); */ /* writel(0x0, data->sfr_slif_vts + 0x100); */ /* writel(0x0, data->sfr_slif_vts + 0x100); */ /* writel(0x0, data->sfr_base + 0x1000); */ } return ret; } static void vts_update_config(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); /* update forcely, if you want */ if (data->target_sysclk != data->sysclk_rate) { int ret = 0; vts_dev_info(dev, "System Clock: %lu -> %lu\n", clk_get_rate(data->clk_sys), data->target_sysclk); ret = clk_set_rate(data->clk_sys, data->target_sysclk); if (ret < 0) vts_dev_err(dev, "failed set clk_sys: %d\n", ret); } vts_dev_dbg(dev, "System Clock target:%lu current:%lu\n", data->target_sysclk, clk_get_rate(data->clk_sys)); } static void vts_check_version(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); u32 values[3]; u32 ret_value[3]; char *ver; int ret = 0; if (data->google_version == 0) { values[0] = 2; values[1] = 0; values[2] = 0; ret = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_GET_VERSION, &values, 0, 2); if (ret < 0) dev_err(dev, "VTS_IRQ_AP_GET_VERSION ipc failed\n"); vts_mailbox_read_shared_register(data->pdev_mailbox, ret_value, 0, 3); vts_dev_dbg(dev, "get version 0x%x 0x%x 0x%x\n", ret_value[0], ret_value[1], ret_value[2]); data->google_version = ret_value[2]; memcpy(data->google_uuid, data->shared_info->hotword_id, 40); } else { vts_dev_dbg(dev, "google version = %d\n", data->google_version); } if (data->vtsdetectlib_version == 0) { values[0] = 1; values[1] = 0; values[2] = 0; ret = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_GET_VERSION, &values, 0, 2); if (ret < 0) dev_err(dev, "VTS_IRQ_AP_GET_VERSION ipc failed\n"); vts_mailbox_read_shared_register(data->pdev_mailbox, ret_value, 0, 3); vts_dev_dbg(dev, "get version 0x%x 0x%x 0x%x\n", ret_value[0], ret_value[1], ret_value[2]); data->vtsfw_version = ret_value[0]; data->vtsdetectlib_version = ret_value[2]; } ver = (char *)(&data->vtsfw_version); vts_dev_info(dev, "firmware version: (%c%c%c%c) lib: 0x%x 0x%x\n", ver[3], ver[2], ver[1], ver[0], data->vtsdetectlib_version, data->google_version); } static int vts_runtime_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct vts_data *data = dev_get_drvdata(dev); u32 values[3] = {0, 0, 0}; int result = 0; unsigned long flag; vts_dev_info(dev, "%s\n", __func__); if (data->sysevent_dev) sysevent_put((void *)data->sysevent_dev); if (data->running) { vts_dev_info(dev, "RUNNING %s :%d\n", __func__, __LINE__); if (data->audiodump_enabled) { values[0] = VTS_DISABLE_AUDIODUMP; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 1); if (result < 0) vts_dev_warn(dev, "Disable_AudioDump ipc failed\n"); /* reset audio dump offset & size */ vts_dump_addr_register(dev, 0, 0, VTS_AUDIO_DUMP); } if (data->logdump_enabled) { values[0] = VTS_DISABLE_LOGDUMP; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 1); if (result < 0) vts_dev_warn(dev, "Disable_LogDump ipc failed\n"); /* reset audio dump offset & size */ vts_dump_addr_register(dev, 0, 0, VTS_LOG_DUMP); } if (data->fw_logfile_enabled || data->fw_logger_enabled) { values[0] = VTS_DISABLE_DEBUGLOG; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 1); if (result < 0) vts_dev_warn(dev, "Disable_debuglog ipc transaction failed\n"); /* reset VTS SRAM debug log buffer */ vts_register_log_buffer(dev, 0, 0); } } spin_lock_irqsave(&data->state_spinlock, flag); data->vts_state = VTS_STATE_RUNTIME_SUSPENDING; spin_unlock_irqrestore(&data->state_spinlock, flag); vts_dev_info(dev, "%s save register :%d\n", __func__, __LINE__); vts_save_register(data); if (data->running) { values[0] = 0; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_POWER_DOWN, &values, 0, 1); if (result < 0) { vts_dev_warn(dev, "POWER_DOWN IPC transaction Failed\n"); result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_POWER_DOWN, &values, 0, 1); if (result < 0) vts_dev_warn(dev, "POWER_DOWN IPC transaction 2nd Failed\n"); } /* Dump VTS GPR register & Log messages */ vts_dbg_dump_fw_gpr(dev, data, RUNTIME_SUSPEND_DUMP); vts_dev_info(dev, "shared_info 0x%x, %d %d", data->shared_info->vendor_data[0], data->shared_info->vendor_data[1], data->shared_info->vendor_data[2]); vts_cpu_enable(dev, false); if (data->irq_state) { vts_irq_enable(pdev, false); data->irq_state = false; } vts_do_reg(dev, false, 0); result = vts_soc_runtime_suspend(dev); if (result < 0) { vts_dev_warn(dev, "vts_soc_rt_suspend %d\n", result); } vts_log_flush(&data->pdev->dev); spin_lock_irqsave(&data->state_spinlock, flag); data->vts_state = VTS_STATE_RUNTIME_SUSPENDED; spin_unlock_irqrestore(&data->state_spinlock, flag); vts_cpu_power(dev, false); data->running = false; } else { spin_lock_irqsave(&data->state_spinlock, flag); data->vts_state = VTS_STATE_RUNTIME_SUSPENDED; spin_unlock_irqrestore(&data->state_spinlock, flag); } vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, DPDM, false); mailbox_update_vts_is_on(false); data->enabled = false; data->exec_mode = VTS_RECOGNIZE_STOP; data->voicerecog_start = 0; data->target_size = 0; /* reset micbias setting count */ data->micclk_init_cnt = 0; data->mic_ready = 0; data->vts_ready = 0; vts_dev_info(dev, "%s Exit\n", __func__); return 0; } int vts_start_runtime_resume(struct device *dev, int skip_log) { struct platform_device *pdev = to_platform_device(dev); struct vts_data *data = dev_get_drvdata(dev); u32 values[3]; int result; unsigned long long kernel_time; if (data->running) { if (!skip_log) vts_dev_info(dev, "SKIP %s\n", __func__); return 0; } vts_dev_info(dev, "%s\n", __func__); if (!data->clk_sys_mux) { #if defined(CONFIG_SOC_S5E8825) vts_dev_info(dev, "%s: don't have to set clk_sys_mux\n", __func__); #else vts_dev_info(dev, "%s clk_sys_mux is NULL\n", __func__); return 0; #endif } result = vts_soc_runtime_resume(dev); if (result < 0) { vts_dev_warn(dev, "vts_soc_rt_resume %d\n", result); goto error_clk; } data->vts_state = VTS_STATE_RUNTIME_RESUMING; data->enabled = true; mailbox_update_vts_is_on(true); vts_restore_register(data); vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, DPDM, true); vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, DPDM, false); vts_pad_retention(false); if (!data->irq_state) { vts_irq_enable(pdev, true); data->irq_state = true; } vts_do_reg(dev, true, 0); if (IS_ENABLED(CONFIG_EXYNOS_IMGLOADER)) { vts_dev_info(dev, "imgloader_boot : %d\n", data->imgloader); if (data->imgloader) result = imgloader_boot(&data->vts_imgloader_desc); } else result = vts_download_firmware(pdev); if (result < 0) { vts_dev_err(dev, "Failed to download firmware\n"); data->enabled = false; mailbox_update_vts_is_on(false); goto error_firmware; } if (IS_ENABLED(CONFIG_EXYNOS_IMGLOADER)) { vts_dev_info(dev, "VTS IMGLOADER\n"); } else { vts_cpu_power(dev, true); } vts_dev_info(dev, "GPR DUMP BASE Enable\n"); result = vts_wait_for_fw_ready(dev); /* ADD FW READY STATUS */ if (result < 0) { vts_dev_err(dev, "Failed to vts_wait_for_fw_ready\n"); data->enabled = false; mailbox_update_vts_is_on(false); goto error_firmware; } vts_update_config(dev); vts_check_version(dev); /* Configure select sys clock rate */ vts_clk_set_rate(dev, data->syssel_rate); data->dma_area_vts = vts_set_baaw(data->baaw_base, data->dmab.addr, BUFFER_BYTES_MAX); values[0] = data->dma_area_vts; values[1] = 0x140; values[2] = 0x800; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_SET_DRAM_BUFFER, &values, 0, 1); if (result < 0) { vts_dev_err(dev, "DRAM_BUFFER Setting IPC transaction Failed\n"); goto error_firmware; } data->exec_mode = VTS_RECOGNIZE_STOP; values[0] = VTS_ENABLE_SRAM_LOG; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 1); if (result < 0) { vts_dev_err(dev, "Enable_SRAM_log ipc transaction failed\n"); goto error_firmware; } if (data->fw_logfile_enabled || data->fw_logger_enabled) { values[0] = VTS_ENABLE_DEBUGLOG; values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 1); if (result < 0) { vts_dev_err(dev, "Enable_debuglog ipc transaction failed\n"); goto error_firmware; } } /* Enable Audio data Dump */ if (data->audiodump_enabled) { values[0] = VTS_ENABLE_AUDIODUMP; values[1] = (VTS_ADUIODUMP_AFTER_MINS * 60); values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 2); if (result < 0) { vts_dev_err(dev, "Enable_AudioDump ipc failed\n"); goto error_firmware; } } /* Enable VTS FW log Dump */ if (data->logdump_enabled) { values[0] = VTS_ENABLE_LOGDUMP; values[1] = (VTS_LOGDUMP_AFTER_MINS * 60); values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 2); if (result < 0) { vts_dev_err(dev, "Enable_LogDump ipc failed\n"); goto error_firmware; } } vts_update_kernel_time(dev); /* Send Kernel Time */ kernel_time = sched_clock(); values[0] = VTS_KERNEL_TIME; values[1] = (u32)(kernel_time / 1000000000); values[2] = (u32)(kernel_time % 1000000000 / 1000000); vts_dev_info(dev, "Time : %d.%d\n", values[1], values[2]); result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 2); if (result < 0) vts_dev_err(dev, "VTS_KERNEL_TIME ipc failed\n"); vts_dev_dbg(dev, "%s DRAM-setting and VTS-Mode is completed\n", __func__); vts_dev_info(dev, "%s Exit\n", __func__); data->running = true; data->vts_state = VTS_STATE_RUNTIME_RESUMED; return 0; error_firmware: vts_cpu_power(dev, false); vts_soc_runtime_suspend(dev); error_clk: if (data->irq_state) { vts_irq_enable(pdev, false); data->irq_state = false; } data->running = false; data->enabled = false; mailbox_update_vts_is_on(false); return 0; } EXPORT_SYMBOL(vts_start_runtime_resume); static int vts_runtime_resume(struct device *dev) { struct vts_data *data = dev_get_drvdata(dev); void *retval = NULL; if (IS_ENABLED(CONFIG_SOC_EXYNOS2100)) { cmu_vts_rco_400_control(1); vts_dev_info(dev, "%s RCO400 ON\n", __func__); } if (data->sysevent_dev) { retval = sysevent_get(data->sysevent_desc.name); if (!retval) vts_dev_err(dev, "fail in sysevent_get\n"); } data->enabled = true; dev_info(dev, "%s\n", __func__); return 0; } static const struct dev_pm_ops samsung_vts_pm = { SET_SYSTEM_SLEEP_PM_OPS(vts_suspend, vts_resume) SET_RUNTIME_PM_OPS(vts_runtime_suspend, vts_runtime_resume, NULL) }; static const struct of_device_id exynos_vts_of_match[] = { { .compatible = "samsung,vts", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_vts_of_match); static ssize_t vtsfw_version_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vts_data *data = dev_get_drvdata(dev); unsigned int version = data->vtsfw_version; buf[0] = ((version >> 24) & 0xFF); buf[1] = ((version >> 16) & 0xFF); buf[2] = ((version >> 8) & 0xFF); buf[3] = (version & 0xFF); buf[4] = '\n'; buf[5] = '\0'; return 6; } static ssize_t vtsdetectlib_version_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vts_data *data = dev_get_drvdata(dev); unsigned int version = data->vtsdetectlib_version; buf[0] = (((version >> 24) & 0xFF) + '0'); buf[1] = '.'; buf[2] = (((version >> 16) & 0xFF) + '0'); buf[3] = '.'; buf[4] = ((version & 0xFF) + '0'); buf[5] = '\n'; buf[6] = '\0'; return 7; } static ssize_t vts_audiodump_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vts_data *data = dev_get_drvdata(dev); return sprintf(buf, "%d\n", data->audiodump_enabled); } static ssize_t vts_audiodump_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct vts_data *data = dev_get_drvdata(dev); u32 val = 0; u32 values[3] = {0, 0, 0}; int result; int err = kstrtouint(buf, 0, &val); if (err < 0) return err; data->audiodump_enabled = (val ? true : false); if (data->vts_ready) { if (data->audiodump_enabled) { values[0] = VTS_ENABLE_AUDIODUMP; values[1] = (VTS_ADUIODUMP_AFTER_MINS * 60); } else { values[0] = VTS_DISABLE_AUDIODUMP; values[1] = 0; } values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 2); if (result < 0) { vts_dev_err(dev, "AudioDump[%d] ipc failed\n", data->audiodump_enabled); } } vts_dev_info(dev, "%s: Audio dump %sabled\n", __func__, (val ? "en" : "dis")); return count; } static ssize_t vts_logdump_show(struct device *dev, struct device_attribute *attr, char *buf) { struct vts_data *data = dev_get_drvdata(dev); return sprintf(buf, "%d\n", data->logdump_enabled); } static ssize_t vts_logdump_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct vts_data *data = dev_get_drvdata(dev); u32 val = 0; u32 values[3] = {0, 0, 0}; int result; int err = kstrtouint(buf, 0, &val); if (err < 0) return err; data->logdump_enabled = (val ? true : false); if (data->vts_ready) { if (data->logdump_enabled) { values[0] = VTS_ENABLE_LOGDUMP; values[1] = (VTS_LOGDUMP_AFTER_MINS * 60); } else { values[0] = VTS_DISABLE_LOGDUMP; values[1] = 0; } values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 2); if (result < 0) { vts_dev_err(dev, "LogDump[%d] ipc failed\n", data->logdump_enabled); } } vts_dev_info(dev, "%s: Log dump %sabled\n", __func__, (val ? "en" : "dis")); return count; } static ssize_t vts_cmd_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { static const char cmd_test_fwlog[] = "TESTFWLOG"; struct vts_data *data = dev_get_drvdata(dev); vts_dev_info(dev, "%s(%s)\n", __func__, buf); if (!strncmp(cmd_test_fwlog, buf, sizeof(cmd_test_fwlog) - 1)) { vts_dev_info(dev, "%s: %s\n", __func__, buf); if (vts_is_on() && dev) { /* log dump */ print_hex_dump(KERN_ERR, "vts-fw-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr, VTS_SRAM_EVENTLOG_SIZE_MAX, true); print_hex_dump(KERN_ERR, "vts-time-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr + VTS_SRAM_EVENTLOG_SIZE_MAX, VTS_SRAM_TIMELOG_SIZE_MAX, true); } else { vts_dev_info(dev, "%s: no power\n", __func__); } } return count; } static DEVICE_ATTR_RO(vtsfw_version); static DEVICE_ATTR_RO(vtsdetectlib_version); static DEVICE_ATTR_RW(vts_audiodump); static DEVICE_ATTR_RW(vts_logdump); static DEVICE_ATTR_WO(vts_cmd); static void __iomem *samsung_vts_membase_request_and_map( struct platform_device *pdev, const char *name, const char *size) { const __be32 *prop; unsigned int len = 0; unsigned int data_base = 0; unsigned int data_size = 0; void __iomem *result = NULL; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; prop = of_get_property(np, name, &len); if (!prop) { vts_dev_err(&pdev->dev, "Failed to of_get_property %s\n", name); return ERR_PTR(-EFAULT); } data_base = be32_to_cpup(prop); prop = of_get_property(np, size, &len); if (!prop) { vts_dev_err(&pdev->dev, "Failed to of_get_property %s\n", size); return ERR_PTR(-EFAULT); } data_size = be32_to_cpup(prop); if (data_base && data_size) { result = ioremap(data_base, data_size); if (IS_ERR_OR_NULL(result)) { vts_dev_err(&pdev->dev, "Failed to map %s\n", name); return ERR_PTR(-EFAULT); } } return result; } static void __iomem *samsung_vts_devm_request_and_map( struct platform_device *pdev, const char *name, phys_addr_t *phys_addr, size_t *size) { struct resource *res; void __iomem *result; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); if (IS_ERR_OR_NULL(res)) { vts_dev_err(&pdev->dev, "Failed to get %s\n", name); return ERR_PTR(-EINVAL); } if (phys_addr) *phys_addr = res->start; if (size) *size = resource_size(res); res = devm_request_mem_region(&pdev->dev, res->start, resource_size(res), name); if (IS_ERR_OR_NULL(res)) { vts_dev_err(&pdev->dev, "Failed to request %s\n", name); return ERR_PTR(-EFAULT); } result = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (IS_ERR_OR_NULL(result)) { vts_dev_err(&pdev->dev, "Failed to map %s\n", name); return ERR_PTR(-EFAULT); } vts_dev_dbg(&pdev->dev, "%s: %s(%pK) is mapped on %pK with size of %zu", __func__, name, (void *)res->start, result, (size_t)resource_size(res)); vts_dev_info(&pdev->dev, "%s is mapped(size:%zu)", name, (size_t)resource_size(res)); return result; } static int samsung_vts_devm_request_threaded_irq( struct platform_device *pdev, const char *irq_name, unsigned int hw_irq, irq_handler_t thread_fn) { struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); int result; data->irq[hw_irq] = platform_get_irq_byname(pdev, irq_name); if (data->irq[hw_irq] < 0) { vts_dev_err(dev, "Failed to get irq %s: %d\n", irq_name, data->irq[hw_irq]); return data->irq[hw_irq]; } result = devm_request_threaded_irq(dev, data->irq[hw_irq], NULL, thread_fn, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev->init_name, pdev); if (result < 0) vts_dev_err(dev, "Unable to request irq %s: %d\n", irq_name, result); return result; } static struct clk *devm_clk_get_and_prepare( struct device *dev, const char *name) { struct clk *clk; int result; clk = devm_clk_get(dev, name); if (IS_ERR(clk)) { vts_dev_err(dev, "Failed to get clock %s\n", name); goto error; } result = clk_prepare(clk); if (result < 0) { vts_dev_err(dev, "Failed to prepare clock %s\n", name); goto error; } error: return clk; } static const struct reg_default vts_dmic_reg_defaults[] = { {0x0000, 0x00030000}, {0x0004, 0x00000000}, }; static const struct regmap_config vts_component_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = VTS_DMIC_CONTROL_DMIC_IF, .reg_defaults = vts_dmic_reg_defaults, .num_reg_defaults = ARRAY_SIZE(vts_dmic_reg_defaults), .cache_type = REGCACHE_RBTREE, .fast_io = true, }; static int vts_fio_open(struct inode *inode, struct file *file) { file->private_data = p_vts_data; return 0; } static long vts_fio_common_ioctl(struct file *file, unsigned int cmd, int __user *_arg) { struct vts_data *data = (struct vts_data *)file->private_data; struct platform_device *pdev; struct device *dev; int arg; struct vts_ipc_msg ipc_msg; u32 values[3] = {0, 0, 0}; struct vts_model_bin_info sm_info; if (!data || (((cmd >> 8) & 0xff) != 'V')) return -ENOTTY; pdev = data->pdev; dev = &pdev->dev; switch (cmd) { case VTSDRV_MISC_IOCTL_LOAD_SOUND_MODEL: if (copy_from_user(&sm_info, (struct vts_model_bin_info __user *)_arg, sizeof(struct vts_model_bin_info))) { vts_dev_err(dev, "%s: LOAD_SOUND_MODEL failed", __func__); return -EFAULT; } if (sm_info.actual_sz < 0 || sm_info.actual_sz > sm_info.max_sz) return -EINVAL; if (sm_info.actual_sz > VTS_MODEL_BIN_MAXSZ) return -EINVAL; memcpy(data->sm_data, data->dmab_model.area, sm_info.actual_sz); data->sm_loaded = true; data->sm_info.actual_sz = sm_info.actual_sz; data->sm_info.max_sz = sm_info.max_sz; data->sm_info.offset = sm_info.offset; vts_dev_info(dev, "%s: LOAD_SOUND_MODEL actual_sz=%d, max_sz=%d, offset=0x%x", __func__, data->sm_info.actual_sz, data->sm_info.max_sz, data->sm_info.offset); break; case VTSDRV_MISC_IOCTL_READ_GOOGLE_VERSION: if (get_user(arg, _arg)) return -EFAULT; if (data->google_version == 0) { vts_dev_info(dev, "get_sync : Read Google Version"); pm_runtime_get_sync(dev); vts_start_runtime_resume(dev, 0); if (pm_runtime_active(dev)) pm_runtime_put(dev); } vts_dev_info(dev, "Google Version: %d", data->google_version); put_user(data->google_version, (int __user *)_arg); break; case VTSDRV_MISC_IOCTL_READ_GOOGLE_UUID: vts_dev_info(dev, "Google UUID: %s", data->google_uuid); copy_to_user((void __user *)_arg, data->google_uuid, 40); break; case VTSDRV_MISC_IOCTL_READ_EVENT_TYPE: if (get_user(arg, _arg)) return -EFAULT; put_user(data->poll_event_type, (int __user *)_arg); data->poll_event_type = EVENT_NONE; break; case VTSDRV_MISC_IOCTL_WRITE_EXIT_POLLING: if (get_user(arg, _arg)) return -EFAULT; data->poll_event_type |= EVENT_STOP_POLLING|EVENT_READY; wake_up(&data->poll_wait_queue); break; case VTSDRV_MISC_IOCTL_SET_PARAM: if (copy_from_user(&ipc_msg, (struct vts_ipc_msg __user *)_arg, sizeof(struct vts_ipc_msg))) { vts_dev_err(dev, "%s: SET_PARAM failed", __func__); return -EFAULT; } values[0] = ipc_msg.values[0]; values[1] = ipc_msg.values[1]; values[2] = ipc_msg.values[2]; vts_dev_info(dev, "%s: SET_PARAM msg: %d, value: 0x%x, 0x%x, 0x%x", __func__, ipc_msg.msg, values[0], values[1], values[2]); if (vts_start_ipc_transaction(dev, data, ipc_msg.msg, &values, 0, 1) < 0) { vts_dev_err(dev, "%s: SET_PARAM ipc transaction failed\n", __func__); } break; default: pr_err("VTS unknown ioctl = 0x%x\n", cmd); return -EINVAL; } return 0; } static long vts_fio_ioctl(struct file *file, unsigned int cmd, unsigned long _arg) { return vts_fio_common_ioctl(file, cmd, (int __user *)_arg); } #ifdef CONFIG_COMPAT static long vts_fio_compat_ioctl(struct file *file, unsigned int cmd, unsigned long _arg) { return vts_fio_common_ioctl(file, cmd, compat_ptr(_arg)); } #endif /* CONFIG_COMPAT */ static int vts_fio_mmap(struct file *file, struct vm_area_struct *vma) { struct vts_data *data = (struct vts_data *)file->private_data; return dma_mmap_wc(&data->pdev->dev, vma, data->dmab_model.area, data->dmab_model.addr, data->dmab_model.bytes); } static unsigned int vts_fio_poll(struct file *file, poll_table *wait) { struct vts_data *data = (struct vts_data *)file->private_data; int ret = 0; poll_wait(file, &data->poll_wait_queue, wait); if (data->poll_event_type & EVENT_READY) { data->poll_event_type &= ~EVENT_READY; ret = POLLIN | POLLRDNORM; } return ret; } static const struct file_operations vts_fio_fops = { .owner = THIS_MODULE, .open = vts_fio_open, .release = NULL, .unlocked_ioctl = vts_fio_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = vts_fio_compat_ioctl, #endif /* CONFIG_COMPAT */ .mmap = vts_fio_mmap, .poll = vts_fio_poll, }; static struct miscdevice vts_fio_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "vts_fio_dev", .fops = &vts_fio_fops }; static int vts_memlog_file_completed(struct memlog_obj *obj, u32 flags) { /* NOP */ return 0; } static int vts_memlog_status_notify(struct memlog_obj *obj, u32 flags) { /* NOP */ return 0; } static int vts_memlog_level_notify(struct memlog_obj *obj, u32 flags) { /* NOP */ return 0; } static int vts_memlog_enable_notify(struct memlog_obj *obj, u32 flags) { struct vts_data *data = p_vts_data; struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; u32 values[3] = {0, 0, 0}; int result = 0; if (data->kernel_log_obj) vts_dev_info(dev, "vts kernel_log_obj enable : %d", data->kernel_log_obj->enabled); if (data->fw_log_obj) { data->fw_logger_enabled = data->fw_log_obj->enabled; vts_dev_info(dev, "firmware logger enabled : %d", data->fw_logger_enabled); if (data->vts_ready) { if (data->fw_logger_enabled) values[0] = VTS_ENABLE_DEBUGLOG; else { values[0] = VTS_DISABLE_DEBUGLOG; vts_register_log_buffer(dev, 0, 0); } values[1] = 0; values[2] = 0; result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_COMMAND, &values, 0, 1); if (result < 0) vts_dev_err(dev, "%s debuglog ipc transaction failed\n", __func__); } } return 0; } static const struct memlog_ops vts_memlog_ops = { .file_ops_completed = vts_memlog_file_completed, .log_status_notify = vts_memlog_status_notify, .log_level_notify = vts_memlog_level_notify, .log_enable_notify = vts_memlog_enable_notify, }; static int vts_pm_notifier(struct notifier_block *nb, unsigned long action, void *nb_data) { struct vts_data *data = container_of(nb, struct vts_data, pm_nb); struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; vts_dev_info(dev, "%s(%lu)\n", __func__, action); switch (action) { case PM_SUSPEND_PREPARE: /* Nothing to do */ break; default: /* Nothing to do */ break; } return NOTIFY_DONE; } #if IS_ENABLED(CONFIG_EXYNOS_ITMON) static int vts_itmon_notifier(struct notifier_block *nb, unsigned long action, void *nb_data) { struct vts_data *data = container_of(nb, struct vts_data, itmon_nb); struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; struct itmon_notifier *itmon_data = nb_data; vts_dev_info(dev, "%s: action:%ld, master:%s, dest:%s\n", __func__, action, itmon_data->master, itmon_data->dest); if (itmon_data && ((strncmp(itmon_data->master, VTS_ITMON_NAME, sizeof(VTS_ITMON_NAME) - 1) == 0) || (strncmp(itmon_data->dest, VTS_ITMON_NAME, sizeof(VTS_ITMON_NAME) - 1) == 0))) { vts_dev_info(dev, "%s(%lu)\n", __func__, action); vts_dev_info(dev, "%s: shared_info: 0x%x, %d, %d", __func__, data->shared_info->vendor_data[0], data->shared_info->vendor_data[1], data->shared_info->vendor_data[2]); /* Dump VTS GPR register & SRAM */ vts_dbg_dump_fw_gpr(dev, data, VTS_ITMON_ERROR); print_hex_dump(KERN_ERR, "vts-fw-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr, VTS_SRAM_EVENTLOG_SIZE_MAX, true); print_hex_dump(KERN_ERR, "vts-time-log", DUMP_PREFIX_OFFSET, 32, 4, data->sramlog_baseaddr + VTS_SRAM_EVENTLOG_SIZE_MAX, VTS_SRAM_TIMELOG_SIZE_MAX, true); data->enabled = false; mailbox_update_vts_is_on(false); return NOTIFY_BAD; } return NOTIFY_DONE; } #endif static int vts_sysevent_ramdump( int enable, const struct sysevent_desc *sysevent) { struct vts_data *data = p_vts_data; struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; /* * This function is called in syseventtem_put / restart * TODO: Ramdump related */ vts_dev_info(dev, "%s: call-back function\n", __func__); return 0; } static int vts_sysevent_powerup(const struct sysevent_desc *sysevent) { struct vts_data *data = p_vts_data; struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; /* * This function is called in syseventtem_get / restart * TODO: Power up related */ vts_dev_info(dev, "%s: call-back function\n", __func__); return 0; } static int vts_sysevent_shutdown( const struct sysevent_desc *sysevent, bool force_stop) { struct vts_data *data = p_vts_data; struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; /* * This function is called in syseventtem_get / restart * TODO: Shutdown related */ vts_dev_info(dev, "%s: call-back function\n", __func__); return 0; } static void vts_sysevent_crash_shutdown(const struct sysevent_desc *sysevent) { struct vts_data *data = p_vts_data; struct platform_device *pdev = data->pdev; struct device *dev = &pdev->dev; /* * This function is called in syseventtem_get / restart * TODO: Crash Shutdown related */ vts_dev_info(dev, "%s: call-back function\n", __func__); } static int vts_component_probe(struct snd_soc_component *cmpnt) { struct device *dev = cmpnt->dev; struct vts_data *data = dev_get_drvdata(dev); int ret = 0; vts_dev_info(dev, "%s\n", __func__); data->cmpnt = cmpnt; ret = vts_soc_cmpnt_probe(dev); if (ret) { vts_dev_err(dev, "vts_soc_cmpnt_probe err(%d)\n", ret); return ret; } vts_dev_info(dev, "%s(%d)\n", __func__, __LINE__); return ret; } static const struct snd_soc_component_driver vts_component = { .probe = vts_component_probe, .controls = vts_controls, .num_controls = ARRAY_SIZE(vts_controls), .dapm_widgets = vts_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(vts_dapm_widgets), .dapm_routes = vts_dapm_routes, .num_dapm_routes = ARRAY_SIZE(vts_dapm_routes), }; static struct platform_driver * const vts_sub_drivers[] = { &samsung_vts_dma_driver, }; static int samsung_vts_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct vts_data *data; int i, result; vts_dev_info(dev, "%s\n", __func__); data = devm_kzalloc(dev, sizeof(struct vts_data), GFP_KERNEL); if (!data) { vts_dev_err(dev, "Failed to allocate memory\n"); result = -ENOMEM; goto error; } dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); vts_port_init(dev); /* Model binary memory allocation */ data->google_version = 0; data->sm_info.max_sz = VTS_MODEL_BIN_MAXSZ; data->sm_info.actual_sz = 0; data->sm_info.offset = 0; data->sm_loaded = false; data->sm_data = vmalloc(VTS_MODEL_BIN_MAXSZ); if (!data->sm_data) { vts_dev_err(dev, "%s Failed to allocate Grammar Bin memory\n", __func__); result = -ENOMEM; goto error; } /* initialize device structure members */ data->active_trigger = 0; /* initialize micbias setting count */ data->micclk_init_cnt = 0; data->mic_ready = 0; data->vts_state = VTS_STATE_NONE; data->vts_rec_state = VTS_REC_STATE_STOP; data->vts_tri_state = VTS_TRI_STATE_COPY_STOP; platform_set_drvdata(pdev, data); data->pdev = pdev; p_vts_data = data; init_waitqueue_head(&data->ipc_wait_queue); init_waitqueue_head(&data->poll_wait_queue); spin_lock_init(&data->ipc_spinlock); spin_lock_init(&data->state_spinlock); mutex_init(&data->ipc_mutex); mutex_init(&data->mutex_pin); data->wake_lock = wakeup_source_register(dev, "vts"); data->pinctrl = devm_pinctrl_get(dev); if (IS_ERR(data->pinctrl)) { vts_dev_err(dev, "Couldn't get pins (%li)\n", PTR_ERR(data->pinctrl)); data->pinctrl = NULL; } data->sfr_base = samsung_vts_devm_request_and_map(pdev, "sfr", NULL, NULL); if (IS_ERR(data->sfr_base)) { result = PTR_ERR(data->sfr_base); goto error; } data->baaw_base = samsung_vts_devm_request_and_map(pdev, "baaw", NULL, NULL); if (IS_ERR(data->baaw_base)) { result = PTR_ERR(data->baaw_base); goto error; } data->sram_base = samsung_vts_devm_request_and_map(pdev, "sram", &data->sram_phys, &data->sram_size); if (IS_ERR(data->sram_base)) { result = PTR_ERR(data->sram_base); goto error; } data->dmic_if0_base = samsung_vts_devm_request_and_map(pdev, "dmic", NULL, NULL); if (IS_ERR(data->dmic_if0_base)) { result = PTR_ERR(data->dmic_if0_base); goto error; } data->dmic_if1_base = samsung_vts_devm_request_and_map(pdev, "dmic1", NULL, NULL); if (IS_ERR(data->dmic_if1_base)) { result = PTR_ERR(data->dmic_if1_base); goto error; } #if defined(CONFIG_SOC_S5E8825) data->gpr_base = samsung_vts_devm_request_and_map(pdev, "gpr", NULL, NULL); if (IS_ERR(data->gpr_base)) { result = PTR_ERR(data->gpr_base); goto error; } #else data->gpr_base = data->sfr_base + VTS_SYSREG_YAMIN_DUMP; if (IS_ERR(data->gpr_base)) { result = PTR_ERR(data->gpr_base); goto error; } #endif data->sicd_base = samsung_vts_membase_request_and_map(pdev, "sicd-base", "sicd-size"); if (IS_ERR(data->sicd_base)) { result = PTR_ERR(data->sicd_base); goto error; } #if defined(CONFIG_SOC_S5E9925) data->intmem_code = samsung_vts_devm_request_and_map(pdev, "intmem_code", NULL, NULL); if (IS_ERR(data->intmem_code)) { result = PTR_ERR(data->intmem_code); goto error; } data->intmem_data = samsung_vts_devm_request_and_map(pdev, "intmem_data", NULL, NULL); if (IS_ERR(data->intmem_data)) { result = PTR_ERR(data->intmem_data); goto error; } data->intmem_pcm = samsung_vts_devm_request_and_map(pdev, "intmem_pcm", NULL, NULL); if (IS_ERR(data->intmem_pcm)) { result = PTR_ERR(data->intmem_pcm); goto error; } data->intmem_data1 = samsung_vts_devm_request_and_map(pdev, "intmem_data1", NULL, NULL); if (IS_ERR(data->intmem_data1)) { result = PTR_ERR(data->intmem_data1); goto error; } data->sfr_slif_vts = samsung_vts_devm_request_and_map(pdev, "slif_vts", NULL, NULL); if (IS_ERR(data->sfr_slif_vts)) { result = PTR_ERR(data->sfr_slif_vts); goto error; } #endif #if defined(CONFIG_SOC_S5E8825) data->dmic_ahb0_base = samsung_vts_devm_request_and_map(pdev, "dmic_ahb0", NULL, NULL); if (IS_ERR(data->dmic_ahb0_base)) { result = PTR_ERR(data->dmic_ahb0_base); goto error; } data->timer0_base = samsung_vts_devm_request_and_map(pdev, "timer0", NULL, NULL); if (IS_ERR(data->timer0_base)) { result = PTR_ERR(data->timer0_base); goto error; } #endif data->lpsdgain = 0; data->dmicgain = 0; data->amicgain = 0; /* read tunned VTS gain values */ of_property_read_u32(np, "lpsd-gain", &data->lpsdgain); of_property_read_u32(np, "dmic-gain", &data->dmicgain); of_property_read_u32(np, "amic-gain", &data->amicgain); data->imgloader = of_property_read_bool(np, "samsung,imgloader-vts-support"); vts_dev_info(dev, "VTS Tunned Gain value LPSD: %d DMIC: %d AMIC: %d\n", data->lpsdgain, data->dmicgain, data->amicgain); vts_dev_info(dev, "VTS: use dmam_alloc_coherent\n"); data->dmab.area = dmam_alloc_coherent(dev, BUFFER_BYTES_MAX, &data->dmab.addr, GFP_KERNEL); if (data->dmab.area == NULL) { result = -ENOMEM; goto error; } data->dmab.bytes = BUFFER_BYTES_MAX/2; data->dmab.dev.dev = dev; data->dmab.dev.type = SNDRV_DMA_TYPE_DEV; data->dmab_rec.area = (data->dmab.area + BUFFER_BYTES_MAX/2); data->dmab_rec.addr = (data->dmab.addr + BUFFER_BYTES_MAX/2); data->dmab_rec.bytes = BUFFER_BYTES_MAX/2; data->dmab_rec.dev.dev = dev; data->dmab_rec.dev.type = SNDRV_DMA_TYPE_DEV; data->dmab_log.area = dmam_alloc_coherent(dev, LOG_BUFFER_BYTES_MAX, &data->dmab_log.addr, GFP_KERNEL); if (data->dmab_log.area == NULL) { result = -ENOMEM; goto error; } data->dmab_log.bytes = LOG_BUFFER_BYTES_MAX; data->dmab_log.dev.dev = dev; data->dmab_log.dev.type = SNDRV_DMA_TYPE_DEV; data->dmab_model.area = dmam_alloc_coherent(dev, VTSDRV_MISC_MODEL_BIN_MAXSZ, &data->dmab_model.addr, GFP_KERNEL); if (data->dmab_model.area == NULL) { result = -ENOMEM; goto error; } data->dmab_model.bytes = VTSDRV_MISC_MODEL_BIN_MAXSZ; data->dmab_model.dev.dev = dev; data->dmab_model.dev.type = SNDRV_DMA_TYPE_DEV; #if defined(CONFIG_SOC_S5E8825) data->mux_dmic_clk = devm_clk_get_and_prepare(dev, "mux_dmic_clk"); if (IS_ERR(data->mux_dmic_clk)) { result = PTR_ERR(data->mux_dmic_clk); data->mux_dmic_clk = NULL; goto error; } #else data->clk_vts_src = devm_clk_get_and_prepare(dev, "clk_vts_src"); if (IS_ERR(data->clk_vts_src)) { result = PTR_ERR(data->clk_vts_src); goto error; } result = clk_enable(data->clk_vts_src); if (result < 0) { vts_dev_err(dev, "Failed to enable the rco\n"); goto error; } /* HACK */ result = clk_set_rate(data->clk_vts_src, 400000000); if (result < 0) { vts_dev_err(dev, "Failed to enable the rco\n"); goto error; } data->clk_sys_mux = devm_clk_get_and_prepare(dev, "clk_sys_mux"); if (IS_ERR(data->clk_sys_mux)) { result = PTR_ERR(data->clk_sys_mux); data->clk_sys_mux = NULL; goto error; } #endif data->mux_clk_dmic_if = devm_clk_get_and_prepare(dev, "clk_dmic_mux"); if (IS_ERR(data->mux_clk_dmic_if)) { result = PTR_ERR(data->mux_clk_dmic_if); vts_dev_warn(dev, "clk_dmic_mux(%li)\n", result); data->mux_clk_dmic_if = NULL; } data->clk_dmic_if = devm_clk_get_and_prepare(dev, "dmic_if"); if (IS_ERR(data->clk_dmic_if)) { result = PTR_ERR(data->clk_dmic_if); data->clk_dmic_if = NULL; goto error; } data->clk_dmic_sync = devm_clk_get_and_prepare(dev, "dmic_sync"); if (IS_ERR(data->clk_dmic_sync)) { result = PTR_ERR(data->clk_dmic_sync); data->clk_dmic_sync = NULL; goto error; } data->clk_sys = devm_clk_get_and_prepare(dev, "clk_sys"); if (IS_ERR(data->clk_sys)) { result = PTR_ERR(data->clk_sys); data->clk_sys = NULL; goto error; } #if IS_ENABLED(CONFIG_SOC_S5E9925) data->clk_slif_src = devm_clk_get_and_prepare(dev, "clk_slif_src"); if (IS_ERR(data->clk_slif_src)) { result = PTR_ERR(data->clk_slif_src); goto error; } data->clk_slif_src1 = devm_clk_get_and_prepare(dev, "clk_slif_src1"); if (IS_ERR(data->clk_slif_src1)) { result = PTR_ERR(data->clk_slif_src1); vts_dev_warn(dev, "clk_slif_src1(%li)\n", result); data->clk_slif_src1 = NULL; } data->clk_slif_src2 = devm_clk_get_and_prepare(dev, "clk_slif_src2"); if (IS_ERR(data->clk_slif_src2)) { result = PTR_ERR(data->clk_slif_src2); vts_dev_warn(dev, "clk_slif_src2(%li)\n", result); data->clk_slif_src2 = NULL; } data->clk_slif_vts = devm_clk_get_and_prepare(dev, "clk_slif_vts"); if (IS_ERR(data->clk_slif_vts)) { result = PTR_ERR(data->clk_slif_vts); data->clk_slif_vts = NULL; goto error; } #endif result = samsung_vts_devm_request_threaded_irq(pdev, "error", VTS_IRQ_VTS_ERROR, vts_error_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "boot_completed", VTS_IRQ_VTS_BOOT_COMPLETED, vts_boot_completed_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "ipc_received", VTS_IRQ_VTS_IPC_RECEIVED, vts_ipc_received_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "voice_triggered", VTS_IRQ_VTS_VOICE_TRIGGERED, vts_voice_triggered_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "trigger_period_elapsed", VTS_IRQ_VTS_PERIOD_ELAPSED, vts_trigger_period_elapsed_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "record_period_elapsed", VTS_IRQ_VTS_REC_PERIOD_ELAPSED, vts_record_period_elapsed_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "debuglog_bufzero", VTS_IRQ_VTS_DBGLOG_BUFZERO, vts_debuglog_bufzero_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "debuglog_bufone", VTS_IRQ_VTS_DBGLOG_BUFONE, vts_debuglog_bufone_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "audio_dump", VTS_IRQ_VTS_AUDIO_DUMP, vts_audiodump_handler); if (result < 0) goto error; result = samsung_vts_devm_request_threaded_irq(pdev, "log_dump", VTS_IRQ_VTS_LOG_DUMP, vts_logdump_handler); if (result < 0) goto error; data->irq_state = true; data->pdev_mailbox = of_find_device_by_node(of_parse_phandle(np, "mailbox", 0)); if (!data->pdev_mailbox) { vts_dev_err(dev, "Failed to get mailbox\n"); result = -EPROBE_DEFER; goto error; } if (data->imgloader) { result = vts_core_imgloader_desc_init(pdev); if (result < 0) return result; } data->regmap_dmic = devm_regmap_init_mmio_clk(dev, NULL, data->dmic_if0_base, &vts_component_regmap_config); result = snd_soc_register_component(dev, &vts_component, vts_dai, ARRAY_SIZE(vts_dai)); if (result < 0) { vts_dev_err(dev, "Failed to register ASoC component\n"); goto error; } #ifdef EMULATOR pmu_alive = ioremap(0x16480000, 0x10000); #endif data->pm_nb.notifier_call = vts_pm_notifier; register_pm_notifier(&data->pm_nb); #if IS_ENABLED(CONFIG_EXYNOS_ITMON) data->itmon_nb.notifier_call = vts_itmon_notifier; itmon_notifier_chain_register(&data->itmon_nb); #endif data->sysevent_dev = NULL; data->sysevent_desc.name = pdev->name; data->sysevent_desc.owner = THIS_MODULE; data->sysevent_desc.ramdump = vts_sysevent_ramdump; data->sysevent_desc.powerup = vts_sysevent_powerup; data->sysevent_desc.shutdown = vts_sysevent_shutdown; data->sysevent_desc.crash_shutdown = vts_sysevent_crash_shutdown; data->sysevent_desc.dev = &pdev->dev; data->sysevent_dev = sysevent_register(&data->sysevent_desc); if (IS_ERR(data->sysevent_dev)) { result = PTR_ERR(data->sysevent_dev); vts_dev_err(dev, "fail in device_event_probe : %d\n", result); goto error; } vts_proc_probe(); pm_runtime_enable(dev); pm_runtime_get_sync(dev); vts_clk_set_rate(dev, 0); vts_port_cfg(dev, VTS_PORT_PAD0, VTS_PORT_ID_VTS, DPDM, false); data->voicecall_enabled = false; data->voicerecog_start = 0; data->syssel_rate = 0; data->target_sysclk = 0; data->target_size = 0; data->vtsfw_version = 0x0; data->vtsdetectlib_version = 0x0; data->fw_logfile_enabled = 0; data->fw_logger_enabled = 0; data->audiodump_enabled = false; data->logdump_enabled = false; vts_set_clk_src(dev, VTS_CLK_SRC_RCO); result = device_create_file(dev, &dev_attr_vtsfw_version); if (result < 0) vts_dev_warn(dev, "Failed to create file: %s\n", "vtsfw_version"); result = device_create_file(dev, &dev_attr_vtsdetectlib_version); if (result < 0) vts_dev_warn(dev, "Failed to create file: %s\n", "vtsdetectlib_version"); result = device_create_file(dev, &dev_attr_vts_cmd); if (result < 0) vts_dev_warn(dev, "Failed to create file: %s\n", "vts_cmd"); result = device_create_file(dev, &dev_attr_vts_audiodump); if (result < 0) vts_dev_warn(dev, "Failed to create file: %s\n", "vts_audiodump"); result = device_create_file(dev, &dev_attr_vts_logdump); if (result < 0) vts_dev_warn(dev, "Failed to create file: %s\n", "vts_logdump"); data->sramlog_baseaddr = (char *)(data->sram_base + VTS_SRAMLOG_MSGS_OFFSET); data->shared_info = (struct vts_shared_info *)(data->sramlog_baseaddr + VTS_SRAMLOG_SIZE_MAX); atomic_notifier_chain_register(&panic_notifier_list, &vts_panic_notifier); /* initialize log buffer offset as non */ vts_register_log_buffer(dev, 0, 0); device_init_wakeup(dev, true); /* 21.09.08 Disable vts memlogger */ goto skip_memlog; result = memlog_register("VTS_DRV", dev, &data->log_desc); if (result) { vts_dev_err(dev, "memlog_register fail"); goto skip_memlog; } data->log_desc->ops = vts_memlog_ops; data->kernel_log_file_obj = memlog_alloc_file(data->log_desc, "ker-mem.memlog", SZ_256K, SZ_512K, 5000, 1); data->kernel_log_obj = memlog_alloc_printf(data->log_desc, SZ_256K, data->kernel_log_file_obj, "ker-mem", 0); data->dump_file_obj = memlog_alloc_file(data->log_desc, "fw-dmp.memlog", SZ_2K, SZ_4K, 5000, 1); data->dump_obj = memlog_alloc_dump(data->log_desc, SZ_2K, data->sram_phys + VTS_SRAMLOG_MSGS_OFFSET, false, data->dump_file_obj, "fw-dmp"); data->fw_log_file_obj = memlog_alloc_file(data->log_desc, "fw-mem.memlog", SZ_128K, SZ_256K, 5000, 1); data->fw_log_obj = memlog_alloc(data->log_desc, SZ_128K, data->fw_log_file_obj, "fw-mem", MEMLOG_UFALG_NO_TIMESTAMP); /* 20.12.28 Disable fw memlogger if (data->fw_log_obj) data->fw_logger_enabled = data->fw_log_obj->enabled; */ skip_memlog: /* Allocate Memory for error logging */ for (i = 0; i < VTS_DUMP_LAST; i++) { data->p_dump[i].sram_log = kzalloc(VTS_SRAMLOG_SIZE_MAX + sizeof(VTS_DUMP_MAGIC), GFP_KERNEL); if (!data->p_dump[i].sram_log) { vts_dev_info(dev, "%s is skipped due to lack of memory for sram log\n", __func__); continue; } if (i != RUNTIME_SUSPEND_DUMP) { /* Allocate VTS firmware all */ data->p_dump[i].sram_fw = kzalloc(data->sram_size, GFP_KERNEL); if (!data->p_dump[i].sram_fw) { vts_dev_info(dev, "%s is skipped due to lack of memory for sram dump\n", __func__); continue; } } } result = misc_register(&vts_fio_miscdev); if (result) vts_dev_warn(dev, "Failed to create device for sound model download\n"); else vts_dev_info(dev, "misc_register ok"); #ifdef DEF_VTS_PCM_DUMP vts_pcm_dump_init(dev); vts_pcm_dump_register(dev, 0, "vts_rec", data->dmab_rec.area, data->dmab_rec.addr, BUFFER_BYTES_MAX/2); vts_pcm_dump_register(dev, 1, "vts_tri", data->dmab.area, data->dmab.addr, BUFFER_BYTES_MAX/2); #endif vts_dev_info(dev, "register sub drivers\n"); result = platform_register_drivers(vts_sub_drivers, ARRAY_SIZE(vts_sub_drivers)); if (result) vts_dev_warn(dev, "Failed to register sub drivers\n"); else { vts_dev_info(dev, "register sub drivers OK\n"); of_platform_populate(np, NULL, NULL, dev); } if (pm_runtime_active(dev)) pm_runtime_put(dev); if (vts_soc_probe(dev)) vts_dev_warn(dev, "Failed to vts_soc_probe\n"); else vts_dev_dbg(dev, "vts_soc_probe ok"); vts_dev_info(dev, "Probed successfully\n"); error: return result; } static int samsung_vts_remove(struct platform_device *pdev) { int i; struct device *dev = &pdev->dev; struct vts_data *data = platform_get_drvdata(pdev); pm_runtime_disable(dev); vts_soc_remove(dev); #ifndef CONFIG_PM vts_runtime_suspend(dev); #endif release_firmware(data->firmware); if (data->sm_data) vfree(data->sm_data); for (i = 0; i < RUNTIME_SUSPEND_DUMP; i++) { /* Free memory for VTS firmware */ kfree(data->p_dump[i].sram_fw); } snd_soc_unregister_component(dev); #ifdef EMULATOR iounmap(pmu_alive); #endif return 0; } static struct platform_driver samsung_vts_driver = { .probe = samsung_vts_probe, .remove = samsung_vts_remove, .driver = { .name = "samsung-vts", .owner = THIS_MODULE, .of_match_table = of_match_ptr(exynos_vts_of_match), .pm = &samsung_vts_pm, }, }; module_platform_driver(samsung_vts_driver); /* Module information */ MODULE_AUTHOR("Gyeongtaek Lee, "); MODULE_AUTHOR("Palli Satish Kumar Reddy, "); MODULE_AUTHOR("Jaewon Kim, "); MODULE_DESCRIPTION("Samsung Voice Trigger System"); MODULE_ALIAS("platform:samsung-vts"); MODULE_LICENSE("GPL");