3672 lines
101 KiB
C
3672 lines
101 KiB
C
|
// 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 <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/pm_wakeup.h>
|
||
|
#include <linux/sched/clock.h>
|
||
|
#include <linux/miscdevice.h>
|
||
|
#include <linux/pinctrl/consumer.h>
|
||
|
#include <linux/suspend.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/vts.h>
|
||
|
#include <sound/sounddev_vts.h>
|
||
|
|
||
|
#include <sound/samsung/mailbox.h>
|
||
|
#include <sound/samsung/vts.h>
|
||
|
#include <soc/samsung/exynos-pmu-if.h>
|
||
|
#include <soc/samsung/exynos-el3_mon.h>
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_ITMON)
|
||
|
#include <soc/samsung/exynos-itmon.h>
|
||
|
#endif
|
||
|
#include <soc/samsung/imgloader.h>
|
||
|
#include <soc/samsung/exynos-s2mpu.h>
|
||
|
#include <soc/samsung/debug-snapshot.h>
|
||
|
|
||
|
#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, <gt82.lee@samsung.com>");
|
||
|
MODULE_AUTHOR("Palli Satish Kumar Reddy, <palli.satish@samsung.com>");
|
||
|
MODULE_AUTHOR("Jaewon Kim, <jaewons.kim@samsung.com>");
|
||
|
MODULE_DESCRIPTION("Samsung Voice Trigger System");
|
||
|
MODULE_ALIAS("platform:samsung-vts");
|
||
|
MODULE_LICENSE("GPL");
|