drivers/ipm: Add ipm_cavs_host: host/DSP communication on adsp_intel
Intel Audio DSPs have "IPC" interrupt delivery and shared memory window hardware. The Sound Open Firmware project has historically used the combination of these to implement a bidirectional message-passing interface. As it happens, this protocol is an excellent fit for Zephyr's somewhat geriatric but still useful IPM interface. This implements a SOF-protocol-compatible transport that will hopefully prove a bit more futureproof for non-Intel SOF architectures. It is a software-only device, built on top of the underlying SOC APIs for the SRAM windows (in cavs-shim) and IPC (cavs_ipc). Note that SOF actually has two protocol variants (ipc3 and ipc4): in both, the command header (passed as the "id" parameter in IPM) is sent via the hardware doorbell register. But in ipc4, the second hardware scratch register is used to transmit the first four bytes of the command before involving the SRAM window (in ipc3, it's ignored). Both modes are supported by this driver, set IPM_CAVS_HOST_REGWORD to choose the "ipc4" variant. Finally: note that the memory layout for the windows in question is inherited from SOF, and for compatibility (with both SOF and with the offsets used by the host!) these can't be changed without major surgery. They're defined in kconfig, but should be treated as read-only until we get a chance to rework the way Zephyr does its SRAM window management (and probably in concert with the host drivers). Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
ebf48d7112
commit
27a59ec9d5
3 changed files with 229 additions and 0 deletions
|
@ -10,5 +10,6 @@ zephyr_library_sources_ifdef(CONFIG_IPM_STM32_IPCC ipm_stm32_ipcc.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_IPM_NRFX ipm_nrfx_ipc.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IPM_CAVS_IDC ipm_cavs_idc.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IPM_STM32_HSEM ipm_stm32_hsem.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IPM_CAVS_HOST ipm_cavs_host.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_USERSPACE ipm_handlers.c)
|
||||
|
|
|
@ -131,12 +131,54 @@ config IPM_STM32_HSEM_CPU
|
|||
|
||||
config IPM_CALLBACK_ASYNC
|
||||
bool "Deliver callbacks asynchronously"
|
||||
default y if IPM_CAVS_HOST
|
||||
help
|
||||
When selected, the driver supports "asynchronous" command
|
||||
delivery. Commands will stay active after the ISR returns,
|
||||
until the application expressly "completes" the command
|
||||
later.
|
||||
|
||||
config IPM_CAVS_HOST
|
||||
bool "cAVS DSP/host communication"
|
||||
select CAVS_IPC
|
||||
help
|
||||
Driver for host/DSP communication on intel_adsp devices
|
||||
|
||||
if IPM_CAVS_HOST
|
||||
|
||||
config IPM_CAVS_HOST_INBOX_OFFSET
|
||||
hex "Byte offset of cAVS inbox window"
|
||||
depends on CAVS_IPC
|
||||
default 0x6000
|
||||
help
|
||||
Location of the host-writable inbox window within the
|
||||
HP_SRAM_RESERVE region. This location must be synchronized
|
||||
with host driver and SOF source code (must match
|
||||
SRAM_INBOX_BASE). Be careful.
|
||||
|
||||
config IPM_CAVS_HOST_OUTBOX_OFFSET
|
||||
hex "Byte offset of cAVS outbox memory"
|
||||
depends on CAVS_IPC
|
||||
default 0x1000
|
||||
help
|
||||
Location of the "outbox" region for SOF IPC3/4 message
|
||||
within the pre-existing window 0 (this is not the same as
|
||||
the HP_SRAM_RESERVE region used for INBOX_OFFSET). This
|
||||
location must be synchronized with host driver and SOF
|
||||
source code (where it must equal SRAM_SW_REG_SIZE). Be
|
||||
careful.
|
||||
|
||||
config IPM_CAVS_HOST_REGWORD
|
||||
bool "Store first 4 bytes in IPC register"
|
||||
depends on CAVS_IPC
|
||||
depends on !SOC_SERIES_INTEL_CAVS_V15
|
||||
help
|
||||
Protocol variant. When true, the first four bytes of a
|
||||
message are passed in the cAVS IDR/TDR register pair instead
|
||||
of in the SRAM window. Only available on cAVS 1.8+.
|
||||
|
||||
endif # IPM_CAVS_HOST
|
||||
|
||||
module = IPM
|
||||
module-str = ipm
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
|
186
drivers/ipm/ipm_cavs_host.c
Normal file
186
drivers/ipm/ipm_cavs_host.c
Normal file
|
@ -0,0 +1,186 @@
|
|||
/* Copyright (c) 2022, Intel Corporation
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <kernel.h>
|
||||
#include <drivers/ipm.h>
|
||||
#include <cavs-mem.h>
|
||||
#include <cavs-shim.h>
|
||||
#include <cavs_ipc.h>
|
||||
|
||||
/* Matches SOF_IPC_MSG_MAX_SIZE, though in practice nothing anywhere
|
||||
* near that big is ever sent. Should maybe consider making this a
|
||||
* kconfig to avoid waste.
|
||||
*/
|
||||
#define MAX_MSG 384
|
||||
|
||||
/* Note: these addresses aren't flexible! We require that they match
|
||||
* current SOF ipc3/4 layout, which means that:
|
||||
*
|
||||
* + Buffer addresses are 4k-aligned (this is a hardware requirement)
|
||||
* + Inbuf must be 4k after outbuf, with no use of the intervening memory
|
||||
* + Oubuf must be 4k after the start of win0 (this is where the host driver looks)
|
||||
*
|
||||
* One side effect is that the word "before" MSG_INBUF is owned by our
|
||||
* code too, and can be used for a nice trick below.
|
||||
*/
|
||||
#define BUFPTR(ptr, off) ((uint32_t *) \
|
||||
arch_xtensa_uncached_ptr((void *)((uint32_t)ptr + off)))
|
||||
#define MSG_INBUF BUFPTR(L2_SRAM_BASE, CONFIG_IPM_CAVS_HOST_INBOX_OFFSET)
|
||||
#define MSG_OUTBUF BUFPTR(HP_SRAM_WIN0_BASE, CONFIG_IPM_CAVS_HOST_OUTBOX_OFFSET)
|
||||
|
||||
struct ipm_cavs_host_data {
|
||||
ipm_callback_t callback;
|
||||
void *user_data;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
/* Note: this call is unsynchronized. The IPM docs are silent as to
|
||||
* whether this is required, and the SOF code that will be using this
|
||||
* is externally synchronized already.
|
||||
*/
|
||||
static int send(const struct device *ipmdev, int wait, uint32_t id,
|
||||
const void *data, int size)
|
||||
{
|
||||
if (!cavs_ipc_is_complete(CAVS_HOST_DEV)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (size > MAX_MSG) {
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
if ((id & 0xc0000000) != 0) {
|
||||
/* cAVS IDR register has only 30 usable bits */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint32_t ext_data = 0;
|
||||
|
||||
/* Protocol variant (used by SOF "ipc4"): store the first word
|
||||
* of the message in the IPC scratch registers
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_IPM_CAVS_HOST_REGWORD) && size >= 4) {
|
||||
ext_data = ((uint32_t *)data)[0];
|
||||
data = &((const uint32_t *)data)[1];
|
||||
size -= 4;
|
||||
}
|
||||
|
||||
memcpy(MSG_OUTBUF, data, size);
|
||||
|
||||
bool ok = cavs_ipc_send_message(CAVS_HOST_DEV, id, ext_data);
|
||||
|
||||
/* The IPM docs call for "busy waiting" here, but in fact
|
||||
* there's a blocking synchrnous call available that might be
|
||||
* better. But then we'd have to check whether we're in
|
||||
* interrupt context, and it's not clear to me that SOF would
|
||||
* benefit anyway as all its usage is async. This is OK for
|
||||
* now.
|
||||
*/
|
||||
if (ok && wait) {
|
||||
while (!cavs_ipc_is_complete(CAVS_HOST_DEV)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
}
|
||||
|
||||
return ok ? 0 : -EBUSY;
|
||||
}
|
||||
|
||||
static bool ipc_handler(const struct device *dev, void *arg,
|
||||
uint32_t data, uint32_t ext_data)
|
||||
{
|
||||
ARG_UNUSED(arg);
|
||||
struct device *ipmdev = arg;
|
||||
struct ipm_cavs_host_data *devdata = ipmdev->data;
|
||||
uint32_t *msg = MSG_INBUF;
|
||||
|
||||
/* We play tricks to leave one word available before the
|
||||
* beginning of the SRAM window, this way the host can see the
|
||||
* same offsets it does with the original ipc4 protocol
|
||||
* implementation, but here in the firmware we see a single
|
||||
* contiguous buffer. See above.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_IPM_CAVS_HOST_REGWORD)) {
|
||||
msg = &msg[-1];
|
||||
msg[0] = ext_data;
|
||||
}
|
||||
|
||||
if (devdata->enabled && (devdata->callback != NULL)) {
|
||||
devdata->callback(ipmdev, devdata->user_data,
|
||||
data & 0x3fffffff, msg);
|
||||
}
|
||||
|
||||
/* Return false for async handling */
|
||||
return !IS_ENABLED(IPM_CALLBACK_ASYNC);
|
||||
}
|
||||
|
||||
static int max_data_size_get(const struct device *ipmdev)
|
||||
{
|
||||
return MAX_MSG;
|
||||
}
|
||||
|
||||
static uint32_t max_id_val_get(const struct device *ipmdev)
|
||||
{
|
||||
/* 30 user-writable bits in cAVS IDR register */
|
||||
return 0x3fffffff;
|
||||
}
|
||||
|
||||
static void register_callback(const struct device *port,
|
||||
ipm_callback_t cb,
|
||||
void *user_data)
|
||||
{
|
||||
struct ipm_cavs_host_data *data = port->data;
|
||||
|
||||
data->callback = cb;
|
||||
data->user_data = user_data;
|
||||
}
|
||||
|
||||
static int set_enabled(const struct device *ipmdev, int enable)
|
||||
{
|
||||
/* This protocol doesn't support any kind of queuing, and in
|
||||
* fact will stall if a message goes unacknowledged. Support
|
||||
* it as best we can by gating the callbacks only. That will
|
||||
* allow the DONE notifications to proceed as normal, at the
|
||||
* cost of dropping any messages received while not "enabled"
|
||||
* of course.
|
||||
*/
|
||||
struct ipm_cavs_host_data *data = ipmdev->data;
|
||||
|
||||
data->enabled = enable;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void complete(const struct device *ipmdev)
|
||||
{
|
||||
cavs_ipc_complete(CAVS_HOST_DEV);
|
||||
}
|
||||
|
||||
static int init(const struct device *dev)
|
||||
{
|
||||
struct ipm_cavs_host_data *data = dev->data;
|
||||
|
||||
/* Initialize hardware SRAM window. SOF will give the host 8k
|
||||
* here, let's limit it to just the memory we're using for
|
||||
* futureproofing.
|
||||
*/
|
||||
CAVS_WIN[1].dmwlo = ROUND_UP(MAX_MSG, 8);
|
||||
CAVS_WIN[1].dmwba = ((uint32_t) MSG_INBUF) | CAVS_DMWBA_ENABLE;
|
||||
|
||||
cavs_ipc_set_message_handler(CAVS_HOST_DEV, ipc_handler, (void *)dev);
|
||||
|
||||
data->enabled = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ipm_driver_api api = {
|
||||
.send = send,
|
||||
.max_data_size_get = max_data_size_get,
|
||||
.max_id_val_get = max_id_val_get,
|
||||
.register_callback = register_callback,
|
||||
.set_enabled = set_enabled,
|
||||
.complete = complete,
|
||||
};
|
||||
|
||||
static struct ipm_cavs_host_data data;
|
||||
|
||||
DEVICE_DEFINE(ipm_cavs_host, "ipm_cavs_host", init, NULL, &data, NULL,
|
||||
PRE_KERNEL_2, 1, &api);
|
Loading…
Add table
Add a link
Reference in a new issue