356 lines
7.7 KiB
C
Executable file
356 lines
7.7 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox Inter-Processor Communication driver
|
|
*
|
|
* Copyright (c) 2017 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/sched/clock.h>
|
|
|
|
#include <sound/samsung/abox.h>
|
|
|
|
#include "abox.h"
|
|
#include "abox_gic.h"
|
|
#include "abox_msg.h"
|
|
#include "abox_ipc.h"
|
|
#include "abox_memlog.h"
|
|
|
|
#define ABOX_IPC_IRQ 15
|
|
#define ABOX_IPC_SIZE SZ_64K
|
|
|
|
static struct device *dev_abox;
|
|
static struct device *dev_gic;
|
|
static char ipc_buf[ABOX_IPC_SIZE];
|
|
|
|
static DEFINE_SPINLOCK(lock_tx);
|
|
static DEFINE_SPINLOCK(lock_rx);
|
|
|
|
static DEFINE_SPINLOCK(lock_ipc_actions);
|
|
static LIST_HEAD(ipc_actions);
|
|
|
|
static void abox_ipc_print_log(const char *fmt, ...)
|
|
{
|
|
char *log;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
log = kvasprintf(GFP_ATOMIC, fmt, ap);
|
|
abox_info(dev_abox, "%s", log);
|
|
kfree(log);
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
static void abox_ipc_lock_tx(void)
|
|
{
|
|
spin_lock(&lock_tx);
|
|
}
|
|
|
|
static void abox_ipc_unlock_tx(void)
|
|
{
|
|
spin_unlock(&lock_tx);
|
|
}
|
|
|
|
static void abox_ipc_lock_rx(void)
|
|
{
|
|
spin_lock(&lock_rx);
|
|
}
|
|
|
|
static void abox_ipc_unlock_rx(void)
|
|
{
|
|
spin_unlock(&lock_rx);
|
|
}
|
|
|
|
static uint64_t abox_ipc_get_time(void)
|
|
{
|
|
return sched_clock();
|
|
}
|
|
|
|
static size_t abox_ipc_fix_size(const ABOX_IPC_MSG *ipc, size_t size)
|
|
{
|
|
const size_t offset_msg = offsetof(ABOX_IPC_MSG, msg);
|
|
size_t size_msg = 0;
|
|
|
|
|
|
switch (ipc->ipcid) {
|
|
case IPC_RECEIVED:
|
|
size_msg = 0;
|
|
break;
|
|
case IPC_SYSTEM:
|
|
size_msg = sizeof(ipc->msg.system);
|
|
break;
|
|
case IPC_PCMPLAYBACK:
|
|
case IPC_PCMCAPTURE:
|
|
case IPC_USBPLAYBACK:
|
|
case IPC_USBCAPTURE:
|
|
size_msg = sizeof(ipc->msg.pcmtask);
|
|
break;
|
|
case IPC_OFFLOAD:
|
|
size_msg = sizeof(ipc->msg.offload);
|
|
break;
|
|
case IPC_ERAP:
|
|
size_msg = sizeof(ipc->msg.erap);
|
|
break;
|
|
case IPC_ASB_TEST:
|
|
size_msg = sizeof(ipc->msg.asb);
|
|
break;
|
|
default:
|
|
return size;
|
|
}
|
|
|
|
if (size > offset_msg + size_msg) {
|
|
pr_debug("abox: %s(%d, %d, %d): %zu > %zu + %zu\n", __func__,
|
|
ipc->ipcid, ipc->task_id, ipc->msg.system.msgtype,
|
|
size, offset_msg, size_msg);
|
|
size = offset_msg + size_msg;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
int abox_ipc_send(struct device *dev, const ABOX_IPC_MSG *ipc, size_t size,
|
|
const void *bundle, size_t bundle_size)
|
|
{
|
|
struct abox_msg_cmd cmd;
|
|
struct abox_msg_send_data data[2];
|
|
int ret = 0;
|
|
int count = 1;
|
|
|
|
abox_dbg(dev, "%s(%d, %d, %d, %zu, %zu)\n", __func__,
|
|
ipc->ipcid, ipc->task_id, ipc->msg.system.msgtype,
|
|
size, bundle_size);
|
|
|
|
data[0].data = ipc;
|
|
data[0].size = abox_ipc_fix_size(ipc, size);
|
|
data[1].data = bundle;
|
|
data[1].size = bundle_size;
|
|
|
|
cmd.id = ipc->ipcid;
|
|
switch (ipc->ipcid) {
|
|
case IPC_SYSTEM:
|
|
{
|
|
const struct IPC_SYSTEM_MSG *ipc_msg = &ipc->msg.system;
|
|
|
|
cmd.cmd = ipc_msg->msgtype;
|
|
cmd.arg[0] = ipc_msg->param1;
|
|
cmd.arg[1] = ipc_msg->param2;
|
|
cmd.arg[2] = ipc_msg->param3;
|
|
|
|
if (bundle) {
|
|
data[0].size = offsetof(ABOX_IPC_MSG, msg) +
|
|
offsetof(struct IPC_SYSTEM_MSG, bundle);
|
|
count = 2;
|
|
}
|
|
break;
|
|
}
|
|
case IPC_PCMPLAYBACK:
|
|
case IPC_PCMCAPTURE:
|
|
{
|
|
const struct IPC_PCMTASK_MSG *ipc_msg = &ipc->msg.pcmtask;
|
|
|
|
cmd.cmd = ipc_msg->msgtype;
|
|
cmd.arg[0] = ipc_msg->channel_id;
|
|
cmd.arg[1] = 0;
|
|
cmd.arg[2] = 0;
|
|
break;
|
|
}
|
|
case IPC_OFFLOAD:
|
|
{
|
|
const struct IPC_OFFLOADTASK_MSG *ipc_msg = &ipc->msg.offload;
|
|
|
|
cmd.cmd = ipc_msg->msgtype;
|
|
cmd.arg[0] = ipc_msg->channel_id;
|
|
cmd.arg[1] = 0;
|
|
cmd.arg[2] = 0;
|
|
break;
|
|
}
|
|
case IPC_ERAP:
|
|
{
|
|
const struct IPC_ERAP_MSG *ipc_msg = &ipc->msg.erap;
|
|
|
|
cmd.cmd = ipc_msg->msgtype;
|
|
cmd.arg[0] = 0;
|
|
cmd.arg[1] = 0;
|
|
cmd.arg[2] = 0;
|
|
|
|
if (bundle) {
|
|
data[0].size = offsetof(ABOX_IPC_MSG, msg) +
|
|
offsetof(struct IPC_ERAP_MSG, param);
|
|
count = 2;
|
|
}
|
|
break;
|
|
}
|
|
case IPC_ABOX_CONFIG:
|
|
{
|
|
const struct IPC_ABOX_CONFIG_MSG *ipc_msg = &ipc->msg.config;
|
|
|
|
cmd.cmd = ipc_msg->msgtype;
|
|
cmd.arg[0] = ipc_msg->param1;
|
|
cmd.arg[1] = ipc_msg->param2;
|
|
cmd.arg[2] = 0;
|
|
break;
|
|
}
|
|
case IPC_ASB_TEST:
|
|
{
|
|
const struct IPC_ABOX_TEST_MSG *ipc_msg = &ipc->msg.asb;
|
|
|
|
cmd.cmd = ipc_msg->msgtype;
|
|
cmd.arg[0] = ipc_msg->param1;
|
|
cmd.arg[1] = ipc_msg->param2;
|
|
cmd.arg[2] = 0;
|
|
break;
|
|
}
|
|
default:
|
|
cmd.cmd = 0;
|
|
cmd.arg[0] = 0;
|
|
cmd.arg[1] = 0;
|
|
cmd.arg[2] = 0;
|
|
break;
|
|
}
|
|
|
|
ret = abox_msg_send(&cmd, data, count);
|
|
abox_gic_generate_interrupt(dev_gic, ABOX_IPC_IRQ);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void abox_ipc_retry(void)
|
|
{
|
|
abox_gic_generate_interrupt(dev_gic, ABOX_IPC_IRQ);
|
|
}
|
|
|
|
int abox_ipc_flush(struct device *dev)
|
|
{
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
return abox_msg_flush();
|
|
}
|
|
|
|
int abox_ipc_register_handler(struct device *dev, int ipc_id,
|
|
abox_ipc_handler_t handler, void *data)
|
|
{
|
|
struct abox_ipc_action *action;
|
|
unsigned long flags;
|
|
|
|
abox_dbg(dev, "%s(%d, %ps)\n", __func__, ipc_id, handler);
|
|
|
|
spin_lock_irqsave(&lock_ipc_actions, flags);
|
|
list_for_each_entry(action, &ipc_actions, list) {
|
|
if (action->handler != handler || action->ipc_id != ipc_id ||
|
|
action->dev != dev)
|
|
continue;
|
|
|
|
action->data = data;
|
|
spin_unlock_irqrestore(&lock_ipc_actions, flags);
|
|
abox_info(dev, "%s(%d, %ps) updating data\n",
|
|
__func__, ipc_id, handler);
|
|
return 0;
|
|
}
|
|
spin_unlock_irqrestore(&lock_ipc_actions, flags);
|
|
|
|
action = devm_kmalloc(dev_abox, sizeof(*action), GFP_KERNEL);
|
|
action->dev = dev;
|
|
action->ipc_id = ipc_id;
|
|
action->handler = handler;
|
|
action->data = data;
|
|
spin_lock_irqsave(&lock_ipc_actions, flags);
|
|
list_add_tail(&action->list, &ipc_actions);
|
|
spin_unlock_irqrestore(&lock_ipc_actions, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_ipc_unregister_handler(struct device *dev, int ipc_id,
|
|
abox_ipc_handler_t handler)
|
|
{
|
|
struct abox_ipc_action *action;
|
|
unsigned long flags;
|
|
|
|
abox_dbg(dev, "%s(%d, %ps)\n", __func__, ipc_id, handler);
|
|
|
|
spin_lock_irqsave(&lock_ipc_actions, flags);
|
|
list_for_each_entry(action, &ipc_actions, list) {
|
|
if (action->handler != handler || action->ipc_id != ipc_id ||
|
|
action->dev != dev)
|
|
continue;
|
|
|
|
list_del(&action->list);
|
|
spin_unlock_irqrestore(&lock_ipc_actions, flags);
|
|
devm_kfree(dev_abox, action);
|
|
return 0;
|
|
}
|
|
spin_unlock_irqrestore(&lock_ipc_actions, flags);
|
|
|
|
abox_err(dev, "%s(%d, %ps) handler not exist\n",
|
|
__func__, ipc_id, handler);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static irqreturn_t abox_ipc_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct device *dev = dev_id;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
while (abox_msg_recv(NULL, ipc_buf, sizeof(ipc_buf)) >= 0) {
|
|
ABOX_IPC_MSG *ipc = (ABOX_IPC_MSG *)ipc_buf;
|
|
enum IPC_ID ipc_id = ipc->ipcid;
|
|
struct abox_ipc_action *action;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&lock_ipc_actions, flags);
|
|
list_for_each_entry(action, &ipc_actions, list) {
|
|
if (action->ipc_id != ipc_id)
|
|
continue;
|
|
|
|
ret |= action->handler(ipc_id, action->data, ipc);
|
|
}
|
|
spin_unlock_irqrestore(&lock_ipc_actions, flags);
|
|
if (ret == IRQ_NONE)
|
|
abox_warn(dev, "unknown ipc: %d(%d, %d, %d)\n", ipc_id,
|
|
ipc->msg.system.param1,
|
|
ipc->msg.system.param2,
|
|
ipc->msg.system.param3);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int abox_ipc_init(struct device *dev, void *tx, size_t tx_size,
|
|
void *rx, size_t rx_size)
|
|
{
|
|
struct abox_data *data = dev_get_drvdata(dev);
|
|
struct abox_msg_cfg cfg;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%pK, %zu, %pK, %zu)\n", __func__,
|
|
tx, tx_size, rx, rx_size);
|
|
|
|
dev_abox = dev;
|
|
dev_gic = data->dev_gic;
|
|
|
|
cfg.tx_addr = tx;
|
|
cfg.tx_size = tx_size;
|
|
cfg.rx_addr = rx;
|
|
cfg.rx_size = rx_size;
|
|
cfg.tx_lock_f = abox_ipc_lock_tx;
|
|
cfg.tx_unlock_f = abox_ipc_unlock_tx;
|
|
cfg.rx_lock_f = abox_ipc_lock_rx;
|
|
cfg.rx_unlock_f = abox_ipc_unlock_rx;
|
|
cfg.get_time_f = abox_ipc_get_time;
|
|
cfg.print_log_f = abox_ipc_print_log;
|
|
cfg.ret_ok = 0;
|
|
cfg.ret_err = -EIO;
|
|
ret = abox_msg_init(&cfg);
|
|
|
|
abox_gic_register_irq_handler(dev_gic, ABOX_IPC_IRQ,
|
|
abox_ipc_irq_handler, dev_abox);
|
|
|
|
return ret;
|
|
}
|