lineage_kernel_xcoverpro/sound/soc/samsung/vts/vts.c

2656 lines
72 KiB
C
Raw Normal View History

2023-06-18 22:53:49 +00:00
/* 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.
*/
//#define DEBUG
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/pm_runtime.h>
#include <linux/firmware.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/wakelock.h>
#include <asm-generic/delay.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include <sound/samsung/mailbox.h>
#include <sound/samsung/vts.h>
#include <soc/samsung/exynos-pmu.h>
#include "vts.h"
#include "vts_log.h"
#include "vts_dump.h"
#undef EMULATOR
#ifdef EMULATOR
static void __iomem *pmu_alive;
#define set_mask_value(id, mask, value) do {id = ((id & ~mask) | (value & mask));} while(0);
static void update_mask_value(volatile 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
#ifdef CONFIG_SOC_EXYNOS8895
#define PAD_RETENTION_VTS_OPTION (0x3148)
#define VTS_CPU_CONFIGURATION (0x24E0)
#define VTS_CPU_LOCAL_PWR_CFG (0x00000001)
#define VTS_CPU_STATUS (0x24E4)
#define VTS_CPU_STATUS_STATUS_MASK (0x00000001)
#define VTS_CPU_STANDBY VTS_CPU_STATUS
#define VTS_CPU_STANDBY_STANDBYWFI_MASK (0x10000000)
#define VTS_CPU_OPTION (0x24E8)
#define VTS_CPU_OPTION_USE_STANDBYWFI_MASK (0x00010000)
#define VTS_CPU_RESET_OPTION VTS_CPU_OPTION
#define VTS_CPU_RESET_OPTION_ENABLE_CPU_MASK (0x00008000)
#elif defined(CONFIG_SOC_EXYNOS9810)
#define PAD_RETENTION_VTS_OPTION (0x4AD8)
#define VTS_CPU_CONFIGURATION (0x4AC4)
#define VTS_CPU_LOCAL_PWR_CFG (0x00000001)
#define VTS_CPU_STATUS (0x4AC8)
#define VTS_CPU_STATUS_STATUS_MASK (0x00000001)
#define VTS_CPU_STANDBY (0x3814)
#define VTS_CPU_STANDBY_STANDBYWFI_MASK (0x10000000)
#define VTS_CPU_OPTION (0x3818)
#define VTS_CPU_OPTION_USE_STANDBYWFI_MASK (0x00010000)
#define VTS_CPU_RESET_OPTION (0x4ACC)
#define VTS_CPU_RESET_OPTION_ENABLE_CPU_MASK (0x10000000)
#endif
#define LIMIT_IN_JIFFIES (msecs_to_jiffies(1000))
#define DMIC_CLK_RATE (768000)
#define VTS_TRIGGERED_TIMEOUT_MS (5000)
/* For only external static functions */
static struct vts_data *p_vts_data;
static int vts_start_ipc_transaction_atomic(struct device *dev, struct vts_data *data, int msg, u32 (*values)[3], int sync)
{
long result = 0;
u32 ack_value = 0;
volatile enum ipc_state *state = &data->ipc_state_ap;
dev_info(dev, "%s:++ msg:%d, values: 0x%08x, 0x%08x, 0x%08x\n",
__func__, msg, (*values)[0],
(*values)[1], (*values)[2]);
if (pm_runtime_suspended(dev)) {
dev_warn(dev, "%s: VTS IP is in suspended state, IPC cann't be processed \n", __func__);
return -EINVAL;
}
if (!data->vts_ready) {
dev_warn(dev, "%s: VTS Firmware Not running\n", __func__);
return -EINVAL;
}
spin_lock(&data->ipc_spinlock);
*state = SEND_MSG;
mailbox_write_shared_register(data->pdev_mailbox, *values, 0, 3);
mailbox_generate_interrupt(data->pdev_mailbox, msg);
data->running_ipc = msg;
if (sync) {
int i;
for (i = 1000; i && (*state != SEND_MSG_OK) &&
(*state != SEND_MSG_FAIL) &&
(ack_value != (0x1 << msg)); i--) {
mailbox_read_shared_register(data->pdev_mailbox, &ack_value, 3, 1);
dev_dbg(dev, "%s ACK-value: 0x%08x\n", __func__, ack_value);
udelay(50);
}
if (!i) {
dev_warn(dev, "Transaction timeout!! Ack_value:0x%x\n",
ack_value);
}
if (*state == SEND_MSG_OK || ack_value == (0x1 << msg)) {
dev_dbg(dev, "Transaction success Ack_value:0x%x\n",
ack_value);
if (ack_value == (0x1 << VTS_IRQ_AP_TEST_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, 0, 2);
dev_info(dev, "%s: offset: 0x%x size:0x%x\n",
__func__, ackvalues[0], ackvalues[1]);
if ((*values)[0] == VTS_ENABLE_DEBUGLOG) {
/* Register debug log buffer */
vts_register_log_buffer(dev,
ackvalues[0],
ackvalues[1]);
dev_dbg(dev, "%s: Log buffer\n",
__func__);
} 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);
dev_dbg(dev, "%s: Dump buffer\n",
__func__);
}
}
} else {
dev_err(dev, "Transaction failed\n");
}
result = (*state == SEND_MSG_OK || ack_value) ? 0 : -EIO;
}
/* Clear running IPC & ACK value */
ack_value = 0x0;
mailbox_write_shared_register(data->pdev_mailbox, &ack_value, 3, 1);
data->running_ipc = 0;
*state = IDLE;
spin_unlock(&data->ipc_spinlock);
dev_info(dev, "%s:-- msg:%d \n", __func__, 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);
}
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);
mailbox_write_shared_register(data->pdev_mailbox, &result, 0, 1);
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);
}
static void vts_cpu_power(bool on)
{
pr_info("%s(%d)\n", __func__, on);
#ifndef EMULATOR
exynos_pmu_update(VTS_CPU_CONFIGURATION, VTS_CPU_LOCAL_PWR_CFG,
on ? VTS_CPU_LOCAL_PWR_CFG : 0);
#else
update_mask_value(pmu_alive + VTS_CPU_CONFIGURATION, VTS_CPU_LOCAL_PWR_CFG,
on ? VTS_CPU_LOCAL_PWR_CFG : 0);
#endif
if (!on) {
#ifndef EMULATOR
exynos_pmu_update(VTS_CPU_OPTION,
VTS_CPU_OPTION_USE_STANDBYWFI_MASK,
VTS_CPU_OPTION_USE_STANDBYWFI_MASK);
#else
update_mask_value(pmu_alive + VTS_CPU_OPTION,
VTS_CPU_OPTION_USE_STANDBYWFI_MASK,
VTS_CPU_OPTION_USE_STANDBYWFI_MASK);
#endif
}
}
static int vts_cpu_enable(bool enable)
{
unsigned int mask = VTS_CPU_RESET_OPTION_ENABLE_CPU_MASK;
unsigned int val = (enable ? mask : 0);
unsigned int status = 0;
unsigned long after;
pr_info("%s(%d)\n", __func__, enable);
#ifndef EMULATOR
exynos_pmu_update(VTS_CPU_RESET_OPTION, mask, val);
#else
update_mask_value(pmu_alive + VTS_CPU_RESET_OPTION, mask, val);
#endif
if (enable) {
after = jiffies + LIMIT_IN_JIFFIES;
do {
schedule();
#ifndef EMULATOR
exynos_pmu_read(VTS_CPU_STATUS, &status);
#else
status = readl(pmu_alive + VTS_CPU_STATUS);
#endif
} while (((status & VTS_CPU_STATUS_STATUS_MASK)
!= VTS_CPU_STATUS_STATUS_MASK)
&& time_is_after_eq_jiffies(after));
if (time_is_before_jiffies(after)) {
pr_err("vts cpu enable timeout\n");
return -ETIME;
}
}
return 0;
}
static void vts_reset_cpu(void)
{
#ifndef EMULATOR
vts_cpu_enable(false);
vts_cpu_power(false);
vts_cpu_power(true);
vts_cpu_enable(true);
#endif
}
static int vts_download_firmware(struct platform_device *pdev)
{
struct vts_data *data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
dev_info(dev, "%s\n", __func__);
if (!data->firmware) {
dev_err(dev, "firmware is not loaded\n");
return -EAGAIN;
}
memcpy(data->sram_base, data->firmware->data, data->firmware->size);
dev_info(dev, "firmware is downloaded to %p (size=%zu)\n", data->sram_base, 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 {
dev_err(dev, "VTS Firmware is not ready\n");
result = -ETIME;
}
return result;
}
static void vts_pad_retention(bool retention)
{
if (!retention) {
exynos_pmu_update(PAD_RETENTION_VTS_OPTION, 0x10000000, 0x10000000);
}
}
static void vts_cfg_gpio(struct device *dev, const char *name)
{
struct vts_data *data = dev_get_drvdata(dev);
struct pinctrl_state *pin_state;
int ret;
dev_info(dev, "%s(%s)\n", __func__, name);
pin_state = pinctrl_lookup_state(data->pinctrl, name);
if (IS_ERR(pin_state)) {
dev_err(dev, "Couldn't find pinctrl %s\n", name);
} else {
ret = pinctrl_select_state(data->pinctrl, pin_state);
if (ret < 0)
dev_err(dev, "Unable to configure pinctrl %s\n", name);
}
}
#ifdef CONFIG_SOC_EXYNOS9810
static u32 vts_set_baaw(void __iomem *sfr_base, u64 base, u32 size)
{
u32 aligned_size = round_up(size, SZ_4M);
u64 aligned_base = round_down(base, aligned_size);
writel(VTS_BAAW_BASE / SZ_4K, sfr_base + VTS_BAAW_SRC_START_ADDRESS);
writel((VTS_BAAW_BASE + aligned_size) / SZ_4K, sfr_base + VTS_BAAW_SRC_END_ADDRESS);
writel(aligned_base / SZ_4K, sfr_base + VTS_BAAW_REMAPPED_ADDRESS);
writel(0x80000003, sfr_base + VTS_BAAW_INIT_DONE);
return base - aligned_base + VTS_BAAW_BASE;
}
#endif
static int vts_clk_set_rate(struct device *dev, unsigned long combination)
{
struct vts_data *data = dev_get_drvdata(dev);
unsigned long dmic_rate, dmic_sync, dmic_if;
int result;
dev_info(dev, "%s(%lu)\n", __func__, combination);
switch (combination) {
case 2:
dmic_rate = 384000;
dmic_sync = 384000;
dmic_if = 768000;
break;
case 0:
dmic_rate = 512000;
dmic_sync = 512000;
dmic_if = 1024000;
break;
case 1:
dmic_rate = 768000;
dmic_sync = 768000;
dmic_if = 1536000;
break;
case 3:
dmic_rate = 4096000;
dmic_sync = 2048000;
dmic_if = 4096000;
break;
default:
result = -EINVAL;
goto out;
}
result = clk_set_rate(data->clk_dmic_if, dmic_if);
if (result < 0) {
dev_err(dev, "Failed to set rate of the clock %s\n", "dmic_if");
goto out;
}
dev_info(dev, "DMIC IF clock rate: %lu\n", clk_get_rate(data->clk_dmic_if));
result = clk_set_rate(data->clk_dmic_sync, dmic_sync);
if (result < 0) {
dev_err(dev, "Failed to set rate of the clock %s\n", "dmic_sync");
goto out;
}
dev_info(dev, "DMIC SYNC clock rate: %lu\n", clk_get_rate(data->clk_dmic_sync));
result = clk_set_rate(data->clk_dmic, dmic_rate);
if (result < 0) {
dev_err(dev, "Failed to set rate of the clock %s\n", "dmic");
goto out;
}
dev_info(dev, "DMIC clock rate: %lu\n", clk_get_rate(data->clk_dmic));
out:
return result;
}
int vts_acquire_sram(struct platform_device *pdev, int vts)
{
#ifdef CONFIG_SOC_EXYNOS8895
struct vts_data *data = platform_get_drvdata(pdev);
int previous;
if (IS_ENABLED(CONFIG_SOC_EXYNOS8895)) {
dev_info(&pdev->dev, "%s(%d)\n", __func__, vts);
if (!vts) {
while(pm_runtime_active(&pdev->dev)) {
dev_warn(&pdev->dev, "%s Clear existing active states\n", __func__);
pm_runtime_put_sync(&pdev->dev);
}
}
previous = test_and_set_bit(0, &data->sram_acquired);
if (previous) {
dev_err(&pdev->dev, "vts sram acquisition failed\n");
return -EBUSY;
}
if (!vts) {
pm_runtime_get_sync(&pdev->dev);
data->voicecall_enabled = true;
data->vts_state = VTS_STATE_VOICECALL;
}
writel((vts ? 0 : 1) << VTS_MEM_SEL_OFFSET, data->sfr_base + VTS_SHARED_MEM_CTRL);
}
#endif
return 0;
}
EXPORT_SYMBOL(vts_acquire_sram);
int vts_release_sram(struct platform_device *pdev, int vts)
{
#ifdef CONFIG_SOC_EXYNOS8895
struct vts_data *data = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "%s(%d)\n", __func__, vts);
if (IS_ENABLED(CONFIG_SOC_EXYNOS8895)) {
if (test_bit(0, &data->sram_acquired) &&
(data->voicecall_enabled || vts)) {
writel(0 << VTS_MEM_SEL_OFFSET,
data->sfr_base + VTS_SHARED_MEM_CTRL);
clear_bit(0, &data->sram_acquired);
if (!vts) {
pm_runtime_put_sync(&pdev->dev);
data->voicecall_enabled = false;
}
dev_info(&pdev->dev, "%s(%d) completed\n",
__func__, vts);
} else
dev_warn(&pdev->dev, "%s(%d) already released\n",
__func__, vts);
}
#endif
return 0;
}
EXPORT_SYMBOL(vts_release_sram);
int vts_clear_sram(struct platform_device *pdev)
{
struct vts_data *data = platform_get_drvdata(pdev);
pr_info("%s\n", __func__);
memset(data->sram_base, 0, data->sram_size);
return 0;
}
EXPORT_SYMBOL(vts_clear_sram);
volatile bool vts_is_on(void)
{
return p_vts_data && p_vts_data->enabled;
}
EXPORT_SYMBOL(vts_is_on);
volatile 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",
.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",
.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 *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 *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 *vts_sys_sel_texts[] = {"512kHz", "768kHz", "384kHz", "2048kHz"};
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;
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 *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 *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 *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 *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 *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", "VOICE_TRIGGER_MODE_ON", "SOUND_DETECT_MODE_ON",
"VT_ALWAYS_ON_MODE_ON", "GOOGLE_TRIGGER_MODE_ON",
"SENSORY_TRIGGER_MODE_ON", "VOICE_TRIGGER_MODE_OFF",
"SOUND_DETECT_MODE_OFF", "VT_ALWAYS_ON_MODE_OFF",
"GOOGLE_TRIGGER_MODE_OFF", "SENSORY_TRIGGER_MODE_OFF"
};
/* Keyphrases svoice: "Hi Galaxy", Google:"Okay Google" Sensory: "Hi Blue Genie" */
static const char * const vtsactive_phrase_text[] = {
"SVOICE", "GOOGLE", "SENSORY"
};
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];
dev_info(dev, "%s for %s\n", __func__, vtsactive_phrase_text[active_trigger]);
start = !!start;
if (start) {
dev_info(dev, "%s for %s G-loaded:%d s-loaded: %d\n", __func__,
vtsactive_phrase_text[active_trigger],
data->google_info.loaded,
data->svoice_info.loaded);
dev_info(dev, "%s exec_mode %d active_trig :%d\n", __func__,
data->exec_mode, active_trigger);
if (!(data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE))) {
if (active_trigger == TRIGGER_SVOICE &&
data->svoice_info.loaded) {
/*
* load svoice model.bin @ offset 0x2A800
* file before starting recognition
*/
memcpy(data->sram_base + 0x2A800, data->svoice_info.data,
data->svoice_info.actual_sz);
dev_info(dev, "svoice.bin Binary uploaded size=%zu\n",
data->svoice_info.actual_sz);
} else if (active_trigger == TRIGGER_GOOGLE &&
data->google_info.loaded) {
/*
* load google model.bin @ offset 0x32B00
* file before starting recognition
*/
memcpy(data->sram_base + 0x32B00, data->google_info.data,
data->google_info.actual_sz);
dev_info(dev, "google.bin Binary uploaded size=%zu\n",
data->google_info.actual_sz);
} else {
dev_err(dev, "%s Model Binary File not Loaded\n", __func__);
return -EINVAL;
}
}
result = vts_set_dmicctrl(data->pdev,
((active_trigger == TRIGGER_SVOICE) ?
VTS_MICCONF_FOR_TRIGGER
: VTS_MICCONF_FOR_GOOGLE), true);
if (result < 0) {
dev_err(dev, "%s: MIC control failed\n",
__func__);
return result;
}
if (data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE)) {
int ctrl_dmicif;
/* set VTS Gain for LPSD mode */
ctrl_dmicif = readl(data->dmic_base +
VTS_DMIC_CONTROL_DMIC_IF);
ctrl_dmicif &= ~(0x7 << VTS_DMIC_GAIN_OFFSET);
writel((ctrl_dmicif |
(data->lpsdgain << VTS_DMIC_GAIN_OFFSET)),
data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF);
}
/* Send Start recognition IPC command to VTS */
values[0] = 1 << 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) {
dev_err(dev, "vts ipc VTS_IRQ_AP_START_RECOGNITION failed: %d\n", result);
return result;
}
data->vts_state = VTS_STATE_RECOG_STARTED;
dev_info(dev, "%s start=%d, active_trigger=%d\n", __func__, start, active_trigger);
} else if (!start) {
values[0] = 1 << 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) {
dev_err(dev, "vts ipc VTS_IRQ_AP_STOP_RECOGNITION failed: %d\n", result);
return result;
}
data->vts_state = VTS_STATE_RECOG_STOPPED;
dev_info(dev, "%s start=%d, active_trigger=%d\n", __func__, start, active_trigger);
result = vts_set_dmicctrl(data->pdev,
((active_trigger == TRIGGER_SVOICE) ?
VTS_MICCONF_FOR_TRIGGER
: VTS_MICCONF_FOR_GOOGLE), false);
if (result < 0) {
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;
dev_dbg(component->dev, "GET VTS Execution mode: %d\n",
data->exec_mode);
return 0;
}
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 vts_data *data = p_vts_data;
struct device *dev = component->dev;
u32 values[3];
int result = 0;
int vcrecognize_mode = 0;
int vcrecognize_start = 0;
u32 keyword_type = 1;
char env[100] = {0,};
char *envp[2] = {env, NULL};
int loopcnt = 10;
pm_runtime_barrier(component->dev);
while (data->voicecall_enabled) {
dev_warn(dev, "%s voicecall (%d)\n", __func__, data->voicecall_enabled);
if (loopcnt <= 0) {
dev_warn(dev, "%s VTS SRAM is Used for CP call\n", __func__);
keyword_type = -EBUSY;
snprintf(env, sizeof(env),
"VOICE_WAKEUP_WORD_ID=%x",
keyword_type);
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
return -EBUSY;
}
loopcnt--;
usleep_range(10000, 10000);
}
dev_warn(dev, "%s voicecall (%d) (End)\n", __func__, data->voicecall_enabled);
vcrecognize_mode = ucontrol->value.integer.value[0];
if (vcrecognize_mode < VTS_VOICE_TRIGGER_MODE ||
vcrecognize_mode >= VTS_MODE_COUNT) {
dev_err(dev,
"Invalid voice control mode =%d", vcrecognize_mode);
return 0;
} else {
dev_info(dev, "%s Current: %d requested %s\n",
__func__, data->exec_mode,
vtsvcrecog_mode_text[vcrecognize_mode]);
if ((vcrecognize_mode < VTS_VOICE_TRIGGER_MODE_OFF &&
data->exec_mode & (0x1 << vcrecognize_mode)) ||
(vcrecognize_mode >= VTS_VOICE_TRIGGER_MODE_OFF &&
!(data->exec_mode & (0x1 << (vcrecognize_mode -
VTS_SENSORY_TRIGGER_MODE))))) {
dev_err(dev,
"Requested Recognition mode=%d already completed",
vcrecognize_mode);
return 0;
}
if (vcrecognize_mode <= VTS_SENSORY_TRIGGER_MODE) {
pm_runtime_get_sync(dev);
vts_clk_set_rate(dev, data->syssel_rate);
vcrecognize_start = true;
} else
vcrecognize_start = false;
if (!pm_runtime_active(dev)) {
dev_warn(dev, "%s wrong state %d req: %d\n",
__func__, data->exec_mode,
vcrecognize_mode);
return 0;
}
values[0] = vcrecognize_mode;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(dev,
data, VTS_IRQ_AP_SET_MODE,
&values, 0, 1);
if (result < 0) {
dev_err(dev, "%s IPC transaction Failed\n",
vtsvcrecog_mode_text[vcrecognize_mode]);
goto err_ipcmode;
}
if (vcrecognize_start)
data->exec_mode |= (0x1 << vcrecognize_mode);
else
data->exec_mode &= ~(0x1 << (vcrecognize_mode -
VTS_SENSORY_TRIGGER_MODE));
/* Start/stop the request Voice recognization mode */
result = vts_start_recognization(dev,
vcrecognize_start);
if (result < 0) {
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);
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);
}
}
return 0;
err_ipcmode:
if (pm_runtime_active(dev) && (vcrecognize_start ||
data->exec_mode & (0x1 << vcrecognize_mode) ||
data->voicerecog_start & (0x1 << data->active_trigger))) {
pm_runtime_put_sync(dev);
}
if (!vcrecognize_start) {
data->exec_mode &= ~(0x1 << (vcrecognize_mode -
VTS_SENSORY_TRIGGER_MODE));
data->voicerecog_start &= ~(0x1 << data->active_trigger);
}
return result;
}
#ifdef CONFIG_SOC_EXYNOS8895
static int set_vtsexec_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;
u32 values[3];
int result = 0;
int vtsexecution_mode;
u32 keyword_type = 1;
char env[100] = {0,};
char *envp[2] = {env, NULL};
struct device *dev = &data->pdev->dev;
int loopcnt = 10;
pm_runtime_barrier(component->dev);
while (data->voicecall_enabled) {
dev_warn(component->dev, "%s voicecall (%d)\n", __func__, data->voicecall_enabled);
if (loopcnt <= 0) {
dev_warn(component->dev, "%s VTS SRAM is Used for CP call\n", __func__);
keyword_type = -EBUSY;
snprintf(env, sizeof(env),
"VOICE_WAKEUP_WORD_ID=%x",
keyword_type);
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
return -EBUSY;
}
loopcnt--;
usleep_range(10000, 10000);
}
dev_warn(component->dev, "%s voicecall (%d) (End)\n", __func__, data->voicecall_enabled);
vtsexecution_mode = ucontrol->value.integer.value[0];
if (vtsexecution_mode >= VTS_MODE_COUNT) {
dev_err(component->dev,
"Invalid voice control mode =%d", vtsexecution_mode);
return 0;
} else {
dev_info(component->dev, "%s Current: %d requested %s\n",
__func__, data->exec_mode,
vtsexec_mode_text[vtsexecution_mode]);
if (data->exec_mode == VTS_OFF_MODE &&
vtsexecution_mode != VTS_OFF_MODE) {
pm_runtime_get_sync(component->dev);
vts_clk_set_rate(component->dev, data->syssel_rate);
}
if (pm_runtime_active(component->dev)) {
values[0] = vtsexecution_mode;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(component->dev,
data, VTS_IRQ_AP_SET_MODE,
&values, 0, 1);
if (result < 0) {
dev_err(component->dev, "%s SET_MODE IPC transaction Failed\n",
vtsexec_mode_text[vtsexecution_mode]);
if (data->exec_mode == VTS_OFF_MODE &&
vtsexecution_mode != VTS_OFF_MODE)
pm_runtime_put_sync(component->dev);
return result;
}
}
if (vtsexecution_mode <= VTS_SENSORY_TRIGGER_MODE)
data->exec_mode |= (0x1 << vtsexecution_mode);
else
data->exec_mode &= ~(0x1 << (vtsexecution_mode -
VTS_SENSORY_TRIGGER_MODE));
dev_info(component->dev, "%s Configured: [%d] %s\n",
__func__, data->exec_mode,
vtsexec_mode_text[vtsexecution_mode]);
if (data->exec_mode == VTS_OFF_MODE &&
pm_runtime_active(component->dev)) {
pm_runtime_put_sync(component->dev);
}
}
return 0;
}
#endif
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;
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;
int vtsactive_phrase;
vtsactive_phrase = ucontrol->value.integer.value[0];
if (vtsactive_phrase > 2) {
dev_err(component->dev,
"Invalid VTS Trigger Key phrase =%d", vtsactive_phrase);
return 0;
} else {
data->active_trigger = vtsactive_phrase;
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;
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);
if (data->voicecall_enabled) {
u32 keyword_type = 1;
char env[100] = {0,};
char *envp[2] = {env, NULL};
struct device *dev = &data->pdev->dev;
dev_warn(component->dev, "%s VTS SRAM is Used for CP call\n", __func__);
keyword_type = -EBUSY;
snprintf(env, sizeof(env),
"VOICE_WAKEUP_WORD_ID=%x",
keyword_type);
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
return -EBUSY;
}
trig_ms = ucontrol->value.integer.value[0];
if (trig_ms > 2000 || trig_ms < 0) {
dev_err(component->dev,
"Invalid Voice Trigger Value = %d (valid range 0~2000ms)", trig_ms);
return 0;
} else {
/* Configure VTS target size */
values[0] = trig_ms * 32; /* 1ms requires (16KHz,16bit,Mono) = 16samples * 2 bytes = 32 bytes*/
values[1] = 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) {
dev_err(component->dev, "Voice Trigger Value setting IPC Transaction Failed: %d\n", result);
return result;
}
data->target_size = trig_ms;
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;
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;
dev_dbg(dev, "VTS RESET: %s\n", __func__);
while (data->running && pm_runtime_active(dev)) {
dev_warn(dev, "%s Clear active models\n", __func__);
pm_runtime_put_sync(dev);
}
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_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),
};
static const char *dmic_sel_texts[] = {"DPDM", "APDM"};
static SOC_ENUM_SINGLE_DECL(dmic_sel_enum, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_DMIC_SEL_OFFSET, dmic_sel_texts);
static const struct snd_kcontrol_new dmic_sel_controls[] = {
SOC_DAPM_ENUM("MUX", dmic_sel_enum),
};
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"},
};
static int vts_component_probe(struct snd_soc_component *component)
{
struct device *dev = component->dev;
struct vts_data *data = dev_get_drvdata(dev);
dev_info(dev, "%s\n", __func__);
data->cmpnt = component;
vts_clk_set_rate(component->dev, 0);
return 0;
}
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),
};
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 dmic_clkctrl = 0;
int ctrl_dmicif = 0;
int select_dmicclk = 0;
dev_dbg(dev, "%s-- flag: %d mictype: %d micusagecnt: %d\n",
__func__, enable, micconf_type, data->micclk_init_cnt);
if (!data->vts_ready) {
dev_warn(dev, "%s: VTS Firmware Not running\n", __func__);
return -EINVAL;
}
if (enable) {
if (!data->micclk_init_cnt) {
ctrl_dmicif = readl(data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF);
if (ctrl_dmicif & (0x1 << VTS_DMIC_DMIC_SEL_OFFSET)) {
vts_cfg_gpio(dev, "amic_default");
select_dmicclk = ((0x1 << VTS_ENABLE_CLK_GEN_OFFSET) |
(0x1 << VTS_SEL_EXT_DMIC_CLK_OFFSET) |
(0x1 << VTS_ENABLE_CLK_CLK_GEN_OFFSET));
writel(select_dmicclk, data->sfr_base + VTS_DMIC_CLK_CON);
/* Set AMIC VTS Gain */
writel((ctrl_dmicif |
(data->amicgain << VTS_DMIC_GAIN_OFFSET)),
data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF);
} else {
vts_cfg_gpio(dev, "dmic_default");
select_dmicclk = ((0x0 << VTS_ENABLE_CLK_GEN_OFFSET) |
(0x0 << VTS_SEL_EXT_DMIC_CLK_OFFSET) |
(0x0 << VTS_ENABLE_CLK_CLK_GEN_OFFSET));
writel(select_dmicclk, data->sfr_base + VTS_DMIC_CLK_CON);
/* Set DMIC VTS Gain */
writel((ctrl_dmicif |
(data->dmicgain << VTS_DMIC_GAIN_OFFSET)),
data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF);
}
dmic_clkctrl = readl(data->sfr_base + VTS_DMIC_CLK_CTRL);
writel(dmic_clkctrl | (0x1 << VTS_CLK_ENABLE_OFFSET),
data->sfr_base + VTS_DMIC_CLK_CTRL);
dev_info(dev, "%s Micclk setting ENABLED\n", __func__);
}
/* check whether Mic is already configure or not based on VTS
option type for MIC configuration book keeping */
if ((!(data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER)) ||
!(data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE))) &&
(micconf_type == VTS_MICCONF_FOR_TRIGGER ||
micconf_type == VTS_MICCONF_FOR_GOOGLE)) {
data->micclk_init_cnt++;
data->mic_ready |= (0x1 << micconf_type);
dev_info(dev, "%s Micclk ENABLED for TRIGGER ++ %d\n",
__func__, data->mic_ready);
} else if (!(data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) &&
micconf_type == VTS_MICCONF_FOR_RECORD) {
data->micclk_init_cnt++;
data->mic_ready |= (0x1 << micconf_type);
dev_info(dev, "%s Micclk ENABLED for RECORD ++ %d\n",
__func__, data->mic_ready);
}
} else {
if (data->micclk_init_cnt)
data->micclk_init_cnt--;
if (!data->micclk_init_cnt) {
vts_cfg_gpio(dev, "idle");
dmic_clkctrl = readl(data->sfr_base + VTS_DMIC_CLK_CTRL);
writel(dmic_clkctrl & ~(0x1 << VTS_CLK_ENABLE_OFFSET),
data->sfr_base + VTS_DMIC_CLK_CTRL);
writel(0x0, data->sfr_base + VTS_DMIC_CLK_CON);
/* reset VTS Gain to default */
writel((ctrl_dmicif & (~(0x7 << VTS_DMIC_GAIN_OFFSET))),
data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF);
dev_info(dev, "%s Micclk setting DISABLED\n", __func__);
}
/* MIC configuration book keeping */
if (((data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER)) ||
(data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE))) &&
(micconf_type == VTS_MICCONF_FOR_TRIGGER ||
micconf_type == VTS_MICCONF_FOR_GOOGLE)) {
data->mic_ready &= ~(0x1 << micconf_type);
dev_info(dev, "%s Micclk DISABLED for TRIGGER -- %d\n",
__func__, data->mic_ready);
} else if ((data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) &&
micconf_type == VTS_MICCONF_FOR_RECORD) {
data->mic_ready &= ~(0x1 << micconf_type);
dev_info(dev, "%s Micclk DISABLED for RECORD -- %d\n",
__func__, data->mic_ready);
}
}
return 0;
}
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_code;
mailbox_read_shared_register(data->pdev_mailbox, &error_code, 3, 1);
vts_ipc_ack(data, 1);
dev_err(dev, "Error occurred on VTS: 0x%x\n", (int)error_code);
vts_reset_cpu();
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);
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);
dev_info(dev, "VTS received IPC: 0x%x\n", result);
switch (data->ipc_state_ap) {
case SEND_MSG:
if (result == (0x1 << data->running_ipc)) {
dev_dbg(dev, "IPC transaction completed\n");
data->ipc_state_ap = SEND_MSG_OK;
} else {
dev_err(dev, "IPC transaction error\n");
data->ipc_state_ap = SEND_MSG_FAIL;
}
break;
default:
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;
u32 keyword_type = 1;
char env[100] = {0,};
char *envp[2] = {env, NULL};
if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER) ||
data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE)) {
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;
dev_info(dev, "VTS triggered: id = %u, score = %u\n",
id, score);
dev_info(dev, "VTS triggered: frame_count = %u\n",
frame_count);
if (!(data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE))) {
keyword_type = id;
snprintf(env, sizeof(env),
"VOICE_WAKEUP_WORD_ID=%x",
keyword_type);
} else if (data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE)) {
snprintf(env, sizeof(env),
"VOICE_WAKEUP_WORD_ID=LPSD");
} else {
dev_warn(dev, "Unknown VTS Execution Mode!!\n");
}
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
wake_lock_timeout(&data->wake_lock,
VTS_TRIGGERED_TIMEOUT_MS);
data->vts_state = VTS_STATE_RECOG_TRIGGERED;
}
return IRQ_HANDLED;
}
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_platform_data *platform_data = platform_get_drvdata(data->pdev_vtsdma[0]);
u32 pointer;
if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER) ||
data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE)) {
mailbox_read_shared_register(data->pdev_mailbox,
&pointer, 2, 1);
dev_dbg(dev, "%s:[%s] Base: %08x pointer:%08x\n",
__func__, (platform_data->id ? "VTS-RECORD" :
"VTS-TRIGGER"), data->dma_area_vts, pointer);
if (pointer)
platform_data->pointer = (pointer -
data->dma_area_vts);
vts_ipc_ack(data, 1);
snd_pcm_period_elapsed(platform_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_platform_data *platform_data = platform_get_drvdata(data->pdev_vtsdma[1]);
u32 pointer;
if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) {
mailbox_read_shared_register(data->pdev_mailbox,
&pointer, 1, 1);
dev_dbg(dev, "%s:[%s] Base: %08x pointer:%08x\n",
__func__,
(platform_data->id ? "VTS-RECORD" : "VTS-TRIGGER"),
(data->dma_area_vts + BUFFER_BYTES_MAX/2), pointer);
if (pointer)
platform_data->pointer = (pointer -
(data->dma_area_vts + BUFFER_BYTES_MAX/2));
vts_ipc_ack(data, 1);
snd_pcm_period_elapsed(platform_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);
dev_dbg(dev, "%s LogBuffer Index: %d\n", __func__, 0);
/* schedule log dump */
vts_log_schedule_flush(dev, 0);
vts_ipc_ack(data, 1);
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);
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);
dev_info(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);
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 {
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);
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;
}
dev_info(&data->pdev->dev, "%s: VTS-DMA id(%u)Registered \n", __func__, id);
} else {
dev_err(&data->pdev->dev, "%s: invalid id(%u)\n", __func__, id);
}
}
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) {
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]);
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]);
dev_info(dev, "%s: Enable VTS Wakeup source irqs\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]);
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]);
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;
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]);
}
}
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);
}
void vts_dbg_print_gpr(struct device *dev, struct vts_data *data)
{
int i;
char buf[10];
unsigned int version = data->vtsfw_version;
buf[0] = (((version >> 24) & 0xFF) + '0');
buf[1] = '.';
buf[2] = (((version >> 16) & 0xFF) + '0');
buf[3] = '.';
buf[4] = (((version >> 8) & 0xFF) + '0');
buf[5] = '.';
buf[6] = ((version & 0xFF) + '0');
buf[7] = '\0';
dev_info(dev, "========================================\n");
dev_info(dev, "VTS CM4 register dump (%s)\n", buf);
dev_info(dev, "----------------------------------------\n");
for (i = 0; i <= 14; i++) {
dev_info(dev, "CM4_R%02d : %08x\n", i,
readl(data->gpr_base + VTS_CM4_R(i)));
}
dev_info(dev, "CM4_PC : %08x\n",
readl(data->gpr_base + VTS_CM4_PC));
dev_info(dev, "========================================\n");
}
void vts_dbg_dump_log_gpr(
struct device *dev,
struct vts_data *data,
unsigned int dbg_type,
const char *reason)
{
struct vts_dbg_dump *p_dump = NULL;
int i;
dev_dbg(dev, "%s\n", __func__);
if (!vts_is_on() || !data->running) {
dev_info(dev, "%s is skipped due to no power\n", __func__);
return;
}
if (dbg_type == RUNTIME_SUSPEND_DUMP)
p_dump = (struct vts_dbg_dump *)data->dmab_log.area;
else
p_dump = (struct vts_dbg_dump *)(data->dmab_log.area +
(LOG_BUFFER_BYTES_MAX/2));
/* Get VTS firmware log msgs */
memcpy_fromio(p_dump->sram, data->sramlog_baseaddr, SZ_2K);
p_dump->time = sched_clock();
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
for (i = 0; i <= 14; i++)
p_dump->gpr[i] = readl(data->gpr_base + VTS_CM4_R(i));
p_dump->gpr[i++] = readl(data->gpr_base + VTS_CM4_PC);
}
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;
dev_dbg(dev, "%s\n", __func__);
if (vts_is_on() && dev) {
if (has_run) {
dev_info(dev, "already dumped\n");
return;
}
has_run = true;
/* Dump VTS GPR register & Log messages */
vts_dbg_dump_log_gpr(dev, data, KERNEL_PANIC_DUMP,
"panic");
} else {
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 */
};
static int vts_runtime_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct vts_data *data = dev_get_drvdata(dev);
int i = 1000;
unsigned int status = 0;
u32 values[3] = {0,0,0};
int result = 0;
dev_info(dev, "%s \n", __func__);
vts_save_register(data);
if (data->running) {
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_TEST_COMMAND,
&values, 0, 1);
if (result < 0)
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_TEST_COMMAND,
&values, 0, 1);
if (result < 0)
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->vtslog_enabled) {
values[0] = VTS_DISABLE_DEBUGLOG;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(dev, data,
VTS_IRQ_AP_TEST_COMMAND,
&values, 0, 1);
if (result < 0)
dev_warn(dev, "Disable_debuglog ipc transaction failed\n");
/* reset VTS SRAM debug log buffer */
vts_register_log_buffer(dev, 0, 0);
}
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) {
dev_warn(dev, "POWER_DOWN IPC transaction Failed\n");
}
/* Dump VTS GPR register & Log messages */
vts_dbg_print_gpr(dev, data);
vts_dbg_dump_log_gpr(dev, data, RUNTIME_SUSPEND_DUMP,
"runtime_suspend");
/* wait for VTS STANDBYWFI in STATUS SFR */
do {
exynos_pmu_read(VTS_CPU_STANDBY, &status);
dev_dbg(dev, "%s Read VTS_CPU_STANDBY for STANDBYWFI status\n", __func__);
} while (i-- && !(status & VTS_CPU_STANDBY_STANDBYWFI_MASK));
if (!i) {
dev_warn(dev, "VTS IP entering WFI time out\n");
}
if (data->irq_state) {
vts_irq_enable(pdev, false);
data->irq_state = false;
}
clk_disable(data->clk_dmic);
vts_cpu_enable(false);
vts_cpu_power(false);
data->running = false;
}
data->enabled = false;
data->exec_mode = VTS_OFF_MODE;
data->active_trigger = TRIGGER_SVOICE;
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;
data->vts_state = VTS_STATE_NONE;
dev_info(dev, "%s Exit \n", __func__);
return 0;
}
static int vts_runtime_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct vts_data *data = dev_get_drvdata(dev);
u32 values[3];
int result;
dev_info(dev, "%s \n", __func__);
data->enabled = true;
vts_restore_register(data);
vts_cfg_gpio(dev, "dmic_default");
vts_cfg_gpio(dev, "idle");
vts_pad_retention(false);
if (!data->irq_state) {
vts_irq_enable(pdev, true);
data->irq_state = true;
}
result = clk_enable(data->clk_dmic);
if (result < 0) {
dev_err(dev, "Failed to enable the clock\n");
goto error_clk;
}
dev_info(dev, "dmic clock rate:%lu\n", clk_get_rate(data->clk_dmic));
vts_cpu_power(true);
result = vts_download_firmware(pdev);
if (result < 0) {
dev_err(dev, "Failed to download firmware\n");
goto error_firmware;
}
vts_cpu_enable(true);
vts_wait_for_fw_ready(dev);
/* Configure select sys clock rate */
vts_clk_set_rate(dev, data->syssel_rate);
#if 1
data->dma_area_vts= vts_set_baaw(data->baaw_base,
data->dmab.addr, BUFFER_BYTES_MAX);
#endif
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) {
dev_err(dev, "DRAM_BUFFER Setting IPC transaction Failed\n");
goto error_firmware;
}
values[0] = VTS_OFF_MODE;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_SET_MODE, &values, 0, 1);
if (result < 0) {
dev_err(dev, "SET_MODE to OFF IPC transaction Failed\n");
goto error_firmware;
}
data->exec_mode = VTS_OFF_MODE;
values[0] = VTS_ENABLE_SRAM_LOG;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1);
if (result < 0) {
dev_err(dev, "Enable_SRAM_log ipc transaction failed\n");
goto error_firmware;
}
if (data->vtslog_enabled) {
values[0] = VTS_ENABLE_DEBUGLOG;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1);
if (result < 0) {
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_TEST_COMMAND, &values,
0, 2);
if (result < 0) {
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_TEST_COMMAND, &values,
0, 2);
if (result < 0) {
dev_err(dev, "Enable_LogDump ipc failed\n");
goto error_firmware;
}
}
/* Enable CM4 GPR Dump */
writel(0x1, data->gpr_base);
dev_dbg(dev, "%s DRAM-setting and VTS-Mode is completed \n", __func__);
dev_info(dev, "%s Exit \n", __func__);
data->running = true;
data->vts_state = VTS_STATE_IDLE;
return 0;
error_firmware:
vts_cpu_power(false);
clk_disable(data->clk_dmic);
error_clk:
if (data->irq_state) {
vts_irq_enable(pdev, false);
data->irq_state = false;
}
data->running = false;
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 vts_google_model_read(struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buffer,
loff_t ppos, size_t count)
{
dev_info(kobj_to_dev(kobj), "%s\n", __func__);
return 0;
}
static ssize_t vts_google_model_write(struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buffer, loff_t ppos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct vts_data *data = dev_get_drvdata(dev);
char *to;
ssize_t available;
dev_info(dev, "%s\n", __func__);
if (!data->google_info.data) {
dev_info(dev, "%s Google binary Buffer not allocated\n", __func__);
return -EINVAL;
}
to = data->google_info.data;
available = data->google_info.max_sz;
dev_dbg(dev, "%s available %zu Cur-pos %lld size-to-copy %zu\n", __func__,
available, ppos, count);
if (ppos < 0) {
dev_info(dev, "%s Error wrong current position\n", __func__);
return -EINVAL;
}
if (ppos >= available || !count) {
dev_info(dev, "%s Error copysize[%lld] greater than available buffer\n",
__func__, ppos);
return 0;
}
if (count > available - ppos)
count = available - ppos;
memcpy(to + ppos, buffer, count);
to = (to + ppos);
data->google_info.actual_sz = ppos + count;
dev_info(dev, "%s updated Size %zu\n", __func__,
data->google_info.actual_sz);
data->google_info.loaded = true;
return count;
}
static ssize_t vts_svoice_model_read(struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buffer,
loff_t ppos, size_t count)
{
dev_info(kobj_to_dev(kobj), "%s\n", __func__);
return 0;
}
static ssize_t vts_svoice_model_write(struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buffer, loff_t ppos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct vts_data *data = dev_get_drvdata(dev);
char *to;
ssize_t available;
dev_info(dev, "%s\n", __func__);
if (!data->svoice_info.data) {
dev_info(dev, "%s Grammar binary Buffer not allocated\n", __func__);
return -EINVAL;
}
to = data->svoice_info.data;
available = data->svoice_info.max_sz;
dev_dbg(dev, "%s available %zu Cur-pos %lld size-to-copy %zu\n", __func__,
available, ppos, count);
if (ppos < 0) {
dev_info(dev, "%s Error wrong current position\n", __func__);
return -EINVAL;
}
if (ppos >= available || !count) {
dev_info(dev, "%s Error copysize[%lld] greater than available buffer\n",
__func__, ppos);
return 0;
}
if (count > available - ppos)
count = available - ppos;
memcpy(to + ppos, buffer, count);
to = (to + ppos);
data->svoice_info.actual_sz = ppos + count;
dev_info(dev, "%s updated Size %zu\n", __func__,
data->svoice_info.actual_sz);
data->svoice_info.loaded = true;
return count;
}
static const BIN_ATTR_RW(vts_google_model, VTS_MODEL_GOOGLE_BIN_MAXSZ);
static const BIN_ATTR_RW(vts_svoice_model, VTS_MODEL_SVOICE_BIN_MAXSZ);
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) + '0');
buf[1] = '.';
buf[2] = (((version >> 16) & 0xFF) + '0');
buf[3] = '.';
buf[4] = (((version >> 8) & 0xFF) + '0');
buf[5] = '.';
buf[6] = ((version & 0xFF) + '0');
buf[7] = '\n';
buf[8] = '\0';
return 9;
}
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_TEST_COMMAND, &values,
0, 2);
if (result < 0) {
dev_err(dev, "AudioDump[%d] ipc failed\n",
data->audiodump_enabled);
}
}
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_TEST_COMMAND, &values,
0, 2);
if (result < 0) {
dev_err(dev, "LogDump[%d] ipc failed\n",
data->logdump_enabled);
}
}
dev_info(dev, "%s: Log dump %sabled\n",
__func__, (val ? "en" : "dis"));
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 void vts_complete_firmware_request(const struct firmware *fw, void *context)
{
struct platform_device *pdev = context;
struct device *dev = &pdev->dev;
struct vts_data *data = platform_get_drvdata(pdev);
unsigned int *pversion = NULL;
if (!fw) {
dev_err(dev, "Failed to request firmware\n");
return;
}
data->firmware = fw;
pversion = (unsigned int*) (fw->data + VTSFW_VERSION_OFFSET);
data->vtsfw_version = *pversion;
pversion = (unsigned int*) (fw->data + DETLIB_VERSION_OFFSET);
data->vtsdetectlib_version = *pversion;
dev_info(dev, "Firmware loaded at %p (%zu)\n", fw->data, fw->size);
dev_info(dev, "Firmware version: 0x%x Detection library version: 0x%x\n", data->vtsfw_version, data->vtsdetectlib_version);
}
static void __iomem *samsung_vts_devm_request_and_map(struct platform_device *pdev, const char *name, size_t *size)
{
struct resource *res;
void __iomem *result;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
if (IS_ERR_OR_NULL(res)) {
dev_err(&pdev->dev, "Failed to get %s\n", name);
return ERR_PTR(-EINVAL);
}
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)) {
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)) {
dev_err(&pdev->dev, "Failed to map %s\n", name);
return ERR_PTR(-EFAULT);
}
dev_info(&pdev->dev, "%s: %s(%p) is mapped on %p with size of %zu",
__func__, name, (void *)res->start, result, (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) {
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) {
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)) {
dev_err(dev, "Failed to get clock %s\n", name);
goto error;
}
result = clk_prepare(clk);
if (result < 0) {
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 samsung_vts_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct vts_data *data;
int result;
int dmic_clkctrl = 0;
dev_info(dev, "%s \n", __func__);
data = devm_kzalloc(dev, sizeof(struct vts_data), GFP_KERNEL);
if (!data) {
dev_err(dev, "Failed to allocate memory\n");
result = -ENOMEM;
goto error;
}
/* Model binary memory allocation */
data->google_info.max_sz = VTS_MODEL_GOOGLE_BIN_MAXSZ;
data->google_info.actual_sz = 0;
data->google_info.loaded = false;
data->google_info.data = vmalloc(VTS_MODEL_GOOGLE_BIN_MAXSZ);
if (!data->google_info.data) {
dev_err(dev, "%s Failed to allocate Grammar Bin memory\n", __func__);
result = -ENOMEM;
goto error;
}
data->svoice_info.max_sz = VTS_MODEL_SVOICE_BIN_MAXSZ;
data->svoice_info.actual_sz = 0;
data->svoice_info.loaded = false;
data->svoice_info.data = vmalloc(VTS_MODEL_SVOICE_BIN_MAXSZ);
if (!data->svoice_info.data) {
dev_err(dev, "%s Failed to allocate Net Bin memory\n", __func__);
result = -ENOMEM;
goto error;
}
/* initialize device structure members */
data->active_trigger = TRIGGER_SVOICE;
/* initialize micbias setting count */
data->micclk_init_cnt = 0;
data->mic_ready = 0;
data->vts_state = VTS_STATE_NONE;
platform_set_drvdata(pdev, data);
data->pdev = pdev;
p_vts_data = data;
init_waitqueue_head(&data->ipc_wait_queue);
spin_lock_init(&data->ipc_spinlock);
mutex_init(&data->ipc_mutex);
wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "vts");
data->pinctrl = devm_pinctrl_get(dev);
if (IS_ERR(data->pinctrl)) {
dev_err(dev, "Couldn't get pins (%li)\n",
PTR_ERR(data->pinctrl));
return PTR_ERR(data->pinctrl);
}
data->sfr_base = samsung_vts_devm_request_and_map(pdev, "sfr", 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);
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_size);
if (IS_ERR(data->sram_base)) {
result = PTR_ERR(data->sram_base);
goto error;
}
data->dmic_base = samsung_vts_devm_request_and_map(pdev, "dmic", NULL);
if (IS_ERR(data->dmic_base)) {
result = PTR_ERR(data->dmic_base);
goto error;
}
data->gpr_base = samsung_vts_devm_request_and_map(pdev, "gpr", NULL);
if (IS_ERR(data->gpr_base)) {
result = PTR_ERR(data->gpr_base);
goto error;
}
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);
dev_info(dev, "VTS Tunned Gain value LPSD: %d DMIC: %d AMIC: %d\n",
data->lpsdgain, data->dmicgain, data->amicgain);
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.bytes = LOG_BUFFER_BYTES_MAX;
data->dmab.dev.dev = dev;
data->dmab.dev.type = SNDRV_DMA_TYPE_DEV;
#ifdef CONFIG_SOC_EXYNOS8895
data->clk_rco = devm_clk_get_and_prepare(dev, "rco");
if (IS_ERR(data->clk_rco)) {
result = PTR_ERR(data->clk_rco);
goto error;
}
result = clk_enable(data->clk_rco);
if (result < 0) {
dev_err(dev, "Failed to enable the rco\n");
goto error;
}
#endif
data->clk_dmic = devm_clk_get_and_prepare(dev, "dmic");
if (IS_ERR(data->clk_dmic)) {
result = PTR_ERR(data->clk_dmic);
goto error;
}
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);
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);
goto error;
}
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) {
dev_err(dev, "Failed to get mailbox\n");
result = -EPROBE_DEFER;
goto error;
}
result = request_firmware_nowait(THIS_MODULE,
FW_ACTION_HOTPLUG,
"vts.bin",
dev,
GFP_KERNEL,
pdev,
vts_complete_firmware_request);
if (result < 0) {
dev_err(dev, "Failed to request firmware\n");
goto error;
}
result = device_create_bin_file(dev, &bin_attr_vts_google_model);
if (result < 0) {
dev_err(dev, "Failed to create attribute %s\n", "vts_google_model");
goto error;
}
result = device_create_bin_file(dev, &bin_attr_vts_svoice_model);
if (result < 0) {
dev_err(dev, "Failed to create attribute %s\n", "vts_svoice_model");
goto error;
}
data->regmap_dmic = devm_regmap_init_mmio_clk(dev,
NULL,
data->dmic_base,
&vts_component_regmap_config);
result = snd_soc_register_component(dev, &vts_component, vts_dai, ARRAY_SIZE(vts_dai));
if (result < 0) {
dev_err(dev, "Failed to register ASoC component\n");
goto error;
}
#ifdef EMULATOR
pmu_alive = ioremap(0x16480000, 0x10000);
#endif
printk("come hear %d\n", __LINE__);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
vts_cfg_gpio(dev, "idle");
data->voicecall_enabled = false;
data->voicerecog_start = 0;
data->syssel_rate = 0;
data->target_size = 0;
data->vtsfw_version = 0x0;
data->vtsdetectlib_version = 0x0;
data->vtslog_enabled = 0;
data->audiodump_enabled = false;
data->logdump_enabled = false;
/* set VTS BAAW config */
writel(0x40300, data->baaw_base);
writel(0x40600, data->baaw_base + 0x4);
writel(0x14100, data->baaw_base + 0x8);
writel(0x80000003, data->baaw_base + 0xC);
dmic_clkctrl = readl(data->sfr_base + VTS_DMIC_CLK_CTRL);
writel(dmic_clkctrl & ~(0x1 << VTS_CLK_ENABLE_OFFSET),
data->sfr_base + VTS_DMIC_CLK_CTRL);
dev_dbg(dev, "DMIC_CLK_CTRL: Before 0x%x After 0x%x \n", dmic_clkctrl,
readl(data->sfr_base + VTS_DMIC_CLK_CTRL));
result = device_create_file(dev, &dev_attr_vtsfw_version);
if (result < 0)
dev_warn(dev, "Failed to create file: %s\n", "vtsfw_version");
result = device_create_file(dev, &dev_attr_vtsdetectlib_version);
if (result < 0)
dev_warn(dev, "Failed to create file: %s\n", "vtsdetectlib_version");
result = device_create_file(dev, &dev_attr_vts_audiodump);
if (result < 0)
dev_warn(dev, "Failed to create file: %s\n", "vts_audiodump");
result = device_create_file(dev, &dev_attr_vts_logdump);
if (result < 0)
dev_warn(dev, "Failed to create file: %s\n", "vts_logdump");
data->sramlog_baseaddr = (char *)(data->sram_base + VTS_SRAMLOG_MSGS_OFFSET);
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);
dev_info(dev, "Probed successfully\n");
error:
return result;
}
static int samsung_vts_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct vts_data *data = platform_get_drvdata(pdev);
pm_runtime_disable(dev);
clk_unprepare(data->clk_dmic);
#ifndef CONFIG_PM
vts_runtime_suspend(dev);
#endif
release_firmware(data->firmware);
if (data->google_info.data)
vfree(data->google_info.data);
if (data->svoice_info.data)
vfree(data->svoice_info.data);
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);
static int __init samsung_vts_late_initcall(void)
{
pr_info("%s\n", __func__);
if (p_vts_data && p_vts_data->pdev) {
pm_runtime_put_sync(&p_vts_data->pdev->dev);
} else {
pr_err("%s: p_vts_data or p_vts_data->pdev is null", __func__);
}
return 0;
}
late_initcall(samsung_vts_late_initcall);
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_AUTHOR("Palli Satish Kumar Reddy, <palli.satish@samsung.com>");
MODULE_DESCRIPTION("Samsung Voice Trigger System");
MODULE_ALIAS("platform:samsung-vts");
MODULE_LICENSE("GPL");