lineage_kernel_xcoverpro/sound/soc/codecs/tfa9872/tfa_ext_dummy.c

512 lines
12 KiB
C
Executable File

/*
* Copyright 2016 NXP Semiconductors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or later
* as published by the Free Software Foundation.
*/
/*
* tfa_ext_DUMMY.c
*
* external DSP/RPC interface dummy for reference demo
*
* The front-end is tfa_ext pre-fixed code which can be the same for all
* platforms.
* The remote_*() functions abstract the interface of the communication
* between tfa_ext and remote DSP platform. It's a raw buffer interchange.
* In dummy_dsp_*() the remote response is emulated.
* debugfs is available for generating remote events and kernel log will
* shows the behavior.
* Note that ftrace function trace could be used to trace the call flow.
*
*
*/
#define pr_fmt(fmt) "%s(): " fmt, __func__
#include "config.h"
#include "tfa_internal.h"
#include <sound/tfa_ext.h>
#include <linux/module.h>
#define TFA_EXT_DEBUGFS /* enable the debugfs test interface */
#define RCV_BUFFER_SIZE 2048
/*
* module globals
*/
static tfa_event_handler_t tfa_ext_handle_event;
static int remote_connect;
static struct mutex rcv_wait; /* for blocking event read */
static struct work_struct event_work; /* for event handler */
static struct workqueue_struct *tfa_ext_wq; /* work queue for event handler */
static u8 rcv_buffer[RCV_BUFFER_SIZE];
static int rcv_size;
/*
* the trace_level parameter can be used to enabled full buffer payload prints
*/
static int trace_level;
module_param(trace_level, int, S_IRUGO);
MODULE_PARM_DESC(trace_level, "tfa_ext_dummy trace_level (0=off).");
#ifdef TFA_EXT_DEBUGFS
static int remote_rcv(u8 *buf, int length);
/*
* debugfs for event testing
* /sys/kernel/debug/tfa_ext_dummy/event : event input in ascii
* refer to the tfa_ext_event_code() below
* 1=dsp powered up
* 2=cmd ready
* 3=dsp powered down
* /sys/kernel/debug/tfa_ext_dummy/status: returns msg byte count
* in ascii since last event
*/
#include <linux/debugfs.h>
/* This directory entry will point to `/sys/kernel/debug/tfa_ext_dummy`.*/
static struct dentry *dir;
/* File `/sys/kernel/debug/tfa_ext_dummy/sum` points to this variable.*/
static u32 debugfs_status;
/*
* This is called when `/sys/kernel/debug/tfa_ext_dummy/event` is written to.
* Executing `echo 1 >> /sys/kernel/debug/tfa_ext_dummy/event` will call
* `debugfs_event(NULL, 1)`.
*/
static int debugfs_event(void *data, u64 value)
{
debugfs_status = 0;
pr_info("%llx\n", value);
memcpy(rcv_buffer, &value, sizeof(u64));
rcv_size = sizeof(u64);
if (trace_level > 0)
print_hex_dump_bytes("debug event receive ", DUMP_PREFIX_NONE,
rcv_buffer, rcv_size);
/* emulated received event */
if (mutex_is_locked(&rcv_wait))
mutex_unlock(&rcv_wait);
return queue_work(tfa_ext_wq, &event_work);
}
DEFINE_SIMPLE_ATTRIBUTE(add_fops, NULL, debugfs_event, "%llu\n");
/* This is called when the module loads.*/
static int debugfs_init_module(void)
{
struct dentry *junk;
/* Create directory `/sys/kernel/debug/tfa_ext_dummy`.*/
dir = debugfs_create_dir("tfa_ext_dummy", 0);
if (!dir) {
/* Abort module load. */
pr_err("debugfs_tfa_ext_dummy: failed to create /sys/kernel/debug/tfa_ext_dummy\n");
return TFA_ERROR;
}
/* Create file `/sys/kernel/debug/tfa_ext_dummy/event`.*/
junk = debugfs_create_file(
"event",
0222,
dir,
NULL,
&add_fops);
if (!junk) {
/* Abort module load. */
pr_err("debugfs_tfa_ext_dummy: failed to create /sys/kernel/debug/tfa_ext_dummy/event\n");
return TFA_ERROR;
}
/* Create file `/sys/kernel/debug/tfa_ext_dummy/status`. */
junk = debugfs_create_u32("status", 0444, dir, &debugfs_status);
if (!junk) {
pr_err("debugfs_tfa_ext_dummy: failed to create /sys/kernel/debug/tfa_ext_dummy/status\n");
return TFA_ERROR;
}
return 0;
}
/* This is called when the module is removed.*/
static void debugfs_cleanup_module(void)
{
debugfs_remove_recursive(dir);
}
/*
* end debugfs
*/
#endif /*debugfs*/
/************************ start of the remote dummy tfadsp *******************/
/*
* simple remote DSP behavior
*/
static int dummy_dsp_send(u8 *buf, int length)
{
memcpy(rcv_buffer, buf, length);
if (trace_level > 0)
print_hex_dump_bytes("remote DSP sends: ", DUMP_PREFIX_NONE,
rcv_buffer, rcv_size);
/* emulated received event */
if (mutex_is_locked(&rcv_wait))
mutex_unlock(&rcv_wait);
return queue_work(tfa_ext_wq, &event_work);
}
static int dummy_dsp_rcv(u8 *buf, int length)
{
u8 ack_msg_buf[4] = {4/*TFADSP_CMD_ACK*/, 0, 0, 0};
if (trace_level > 0)
print_hex_dump_bytes("remote DSP received: ", DUMP_PREFIX_NONE,
buf, length);
if (length == 2) /* ack */
pr_info("ack:0x%02x%02x\n", buf[1], buf[0]); /* revid */
else { /* assume msg */
pr_info("cmd_id:0x%02x%02x%02x, length:%d\n",
buf[0], buf[1], buf[2], length);
/* Check for the SetRe25 message as last message */
if (buf[1] == 0x81 && buf[2] == 0x05)
pr_info("----Last message received (SetRe25C)----\n");
/* send ack */
dummy_dsp_send(ack_msg_buf, sizeof(ack_msg_buf));
}
return length;
}
/************************ end of the remote dummy tfadsp *********************/
/*
* remote interface wrappers
*/
static int remote_send(u8 *buf, int length)
{
if (trace_level > 0)
print_hex_dump_bytes("remote_send ", DUMP_PREFIX_NONE,
buf, length);
debugfs_status += length;
return dummy_dsp_rcv(buf, length); /* send to dsp */
}
static int remote_rcv(u8 *buf, int length)
{
int size = rcv_size;
memcpy(buf, rcv_buffer, rcv_size);
if (trace_level > 0)
print_hex_dump_bytes("remote_receive ", DUMP_PREFIX_NONE,
buf, length);
return size;
}
static int remote_init(void)
{
pr_info("\n");
return 0; /* 0 if ok */
}
static void remote_exit(void)
{
pr_info("%s\n", __func__);
}
/*
* remote tfa interfacing
*
* TFADSP_EXT_PWRUP : DSP starting
* set cold to receive messages
* TFADSP_CMD_READY : Ready to receive commands
* call tfa_start to send msgs
* TFADSP_SPARSESIG_DETECTED :Sparse signal detected
* call sparse protection
* TFADSP_EXT_PWRDOWN : DSP stopping
* disable DPS msg forwarding
*
*/
static enum tfadsp_event_en tfa_ext_wait_event(void);
static int tfa_ext_event_ack(u16 err);
/*
* return the event code from the raw input
* for this dummy case pick 1st byte
*/
static int tfa_ext_event_code(u8 *buf, int size)
{
int code;
switch (buf[0]) {
case 1:
pr_info("TFADSP_EXT_PWRUP\n");
/**< DSP API has started, powered up */
code = TFADSP_EXT_PWRUP;
break;
case 2:
pr_info("TFADSP_CMD_READY\n");
/**< Ready to receive commands */
code = TFADSP_CMD_READY;
break;
case 3:
pr_info("TFADSP_EXT_PWRDOWN\n");
/**< DSP API stopped, power down */
code = TFADSP_EXT_PWRDOWN;
break;
case 4:
pr_info("TFADSP_CMD_ACK\n");
/**< Sparse signal detected */
code = TFADSP_CMD_ACK;
break;
case 5:
pr_info("TFADSP_SPARSESIG_DETECTED\n");
/**< Sparse signal detected */
code = TFADSP_SPARSESIG_DETECTED;
break;
default:
pr_info("not handled:%d\n", buf[0]);
code = -1;
break;
}
return code;
}
/*
* return the response to an event
* the remote needs to handle this
* in this dummy a bare 16 byte value is used
*/
static int tfa_ext_event_ack(u16 response)
{
u8 event_msg_buf[8];
event_msg_buf[0] = response & 0xFF;
event_msg_buf[1] = response >> 8;
return remote_send(event_msg_buf, 2) == 2 ? 0 : 1;
}
/*
* wait for an event from the remote controller
*/
static enum tfadsp_event_en tfa_ext_wait_event(void)
{
u8 buf[16];
int length;
enum tfadsp_event_en this_dsp_event = 0;
mutex_lock(&rcv_wait);
length = remote_rcv(buf, sizeof(buf));
if ((length) && (trace_level > 0)) {
print_hex_dump_bytes("tfa_ext_wait_event ", DUMP_PREFIX_NONE,
buf, length);
}
if (length) /* ignore if nothing */
this_dsp_event = tfa_ext_event_code(buf, length);
pr_debug("event:%d/0x%04x\n", this_dsp_event, this_dsp_event);
return this_dsp_event;
}
/**
* @brief DSP message interface that sends the RPC to the remote TFADSP
*
* This is the the function that get called by the tfa driver for each
* RPC message that is to be send to the TFADSP.
* The tfa_ext_registry() function will connect this.
*
* @param [in] devidx : tfa device index that owns the TFADSP
* @param [in] length : length in bytes of the message in the buffer
* @param [in] buffer : buffer pointer to the RPC message payload
*
* @return 0 success
*/
static int tfa_ext_dsp_msg(int devidx, int length, const char *buffer)
{
int error = 0, real_length;
u8 *buf = (u8 *) buffer;
if (trace_level > 0)
pr_debug("id:0x%02x%02x%02x, length:%d\n",
buf[0], buf[1], buf[2], length);
pr_info("%s: send_pkt...\n", __func__);
real_length = remote_send((u8 *) buffer, length);
if (real_length != length) {
pr_err("length mismatch: exp:%d, act:%d\n",
length, real_length);
error = 6; /* communication with the DSP failed */
}
if (tfa_ext_wait_event() == TFADSP_CMD_ACK) {
tfa_ext_event_ack(TFADSP_CMD_ACK);
} else {
pr_err("no TFADSP_CMD_ACK event received\n");
tfa_ext_event_ack(0);
error = 6; /* communication with the DSP failed */
}
return error;
}
/**
* @brief Register at tfa driver and instantiate remote interface functions.
*
* This function must be called once at startup after tfa driver module is
* loaded.
* The tfa_ext_register() will be called to get event handler and dsp message
* interface functions and remote TFADSP will be connected after successful
* return.
*
* @param void
*
* @return 0 on success
*/
static int tfa_ext_registry(void)
{
int ret = -1;
pr_debug("%s\n", __func__);
if (tfa_ext_register
(NULL, tfa_ext_dsp_msg, NULL, &tfa_ext_handle_event)) {
pr_err("Cannot register to tfa driver!\n");
return 1;
}
if (tfa_ext_handle_event == NULL) {
pr_err("even callback not registered by tfa driver!\n");
return 1;
}
if (remote_connect == 0) {
remote_connect = (remote_init() == 0);
if (remote_connect == 0)
pr_err("remote_init failed\n");
else
ret = 0;
}
return remote_connect == 0;
}
/**
* @brief Un-register and close the remote interface.
*
* This function must be called once at shutdown of the remote device.
*
* @param void
*
* @return 0 on success
*/
static void tfa_ext_unregister(void)
{
pr_debug("%s\n", __func__);
/* in case remote did not do send shutdown */
tfa_ext_handle_event(0, TFADSP_EXT_PWRDOWN);
if (remote_connect)
remote_exit();
remote_connect = 0;
}
/****************************** event handler *****************************/
/*
* This event handler will be on a work queue and is normally blocking on an
* event read.
* Whenever a remote event comes in it will retrieve the event code, handles
* it and/or passes it on to the tfa98xx driver.
*
* Note that the ack needs to be returned in response of TFADSP_CMD_READY
* before the messages ar started.
*/
static void tfa_ext_event_handler(struct work_struct *work)
{
enum tfadsp_event_en event;
int event_return = -1;
pr_info("\n");
if (tfa_ext_handle_event == NULL)
return; /* -EFAULT; */
event = tfa_ext_wait_event();
if (event < 0) {
pr_info("illegal event %d\n", event);
return;
}
if (event == TFADSP_CMD_READY) {
/* Ack this first, next will be the tfadsp messages loop */
tfa_ext_event_ack(1);
}
/* call the tfa driver here to handle this event */
event_return = tfa_ext_handle_event(0, event);
if (event_return < 0)
return;
tfa_ext_event_ack(event_return);
}
/*
* register at tfa98xx and create the work queue for the events
*/
static int __init tfa_ext_dummy_init(void)
{
/* pr_info("build: %s %s\n", __DATE__, __TIME__); */
pr_info("trace_level:%d\n", trace_level);
debugfs_init_module();
tfa_ext_registry(); /* TODO add error check */
mutex_init(&rcv_wait);
/* setup work queue */
tfa_ext_wq = create_singlethread_workqueue("tfa_ext");
if (!tfa_ext_wq)
return -ENOMEM;
INIT_WORK(&event_work, tfa_ext_event_handler);
pr_info("done\n");
return 0;
}
/*
* cleanup and disappear
*/
static void __exit tfa_ext_dummy_exit(void)
{
mutex_unlock(&rcv_wait);
cancel_work_sync(&event_work);
if (tfa_ext_wq)
destroy_workqueue(tfa_ext_wq);
remote_exit();
mutex_destroy(&rcv_wait);
tfa_ext_unregister();
debugfs_cleanup_module();
pr_info("done\n");
}
module_init(tfa_ext_dummy_init);
module_exit(tfa_ext_dummy_exit);
MODULE_DESCRIPTION("TFA98xx remote dummy DSP driver");
MODULE_LICENSE("GPL");