soc/intel_adsp: Add a cavs_ipc driver to manage host IPC

This is a slightly higher level Zephyr device that manages the host
IPC device for applications.  There's an interface to make synchronous
and asynchronous calls, to receive commands via (interrupt context)
callbacks and emit async "done" notifications after processing is
complete.  It should work for pretty much any application
architecture.

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
Andy Ross 2022-01-13 11:56:02 -08:00 committed by Anas Nashif
commit bdce0a5742
9 changed files with 361 additions and 0 deletions

View file

@ -54,6 +54,13 @@
#interrupt-cells = <3>;
};
cavs_host_ipc: cavs_host_ipc@1180 {
compatible = "intel,cavs-host-ipc";
reg = <0x1180 0x30>;
interrupts = <7 0 0>;
interrupt-parent = <&cavs0>;
};
cavs0: cavs@1600 {
compatible = "intel,cavs-intc";
reg = <0x1600 0x10>;

View file

@ -73,6 +73,13 @@
#interrupt-cells = <3>;
};
cavs_host_ipc: cavs_host_ipc@71e00 {
compatible = "intel,cavs-host-ipc";
reg = <0x71e00 0x30>;
interrupts = <7 0 0>;
interrupt-parent = <&cavs0>;
};
cavs0: cavs@78800 {
compatible = "intel,cavs-intc";
reg = <0x78800 0x10>;

View file

@ -73,6 +73,13 @@
#interrupt-cells = <3>;
};
cavs_host_ipc: cavs_host_ipc@71e00 {
compatible = "intel,cavs-host-ipc";
reg = <0x71e00 0x30>;
interrupts = <7 0 0>;
interrupt-parent = <&cavs0>;
};
cavs0: cavs@78800 {
compatible = "intel,cavs-intc";
reg = <0x78800 0x10>;

View file

@ -73,6 +73,13 @@
#interrupt-cells = <3>;
};
cavs_host_ipc: cavs_host_ipc@71e00 {
compatible = "intel,cavs-host-ipc";
reg = <0x71e00 0x30>;
interrupts = <7 0 0>;
interrupt-parent = <&cavs0>;
};
cavs0: cavs@78800 {
compatible = "intel,cavs-intc";
reg = <0x78800 0x10>;

View file

@ -13,6 +13,13 @@ config SOC_FAMILY
string
default "intel_adsp"
config CAVS_IPC
bool
default y if !SOF
help
Driver for the host IPC interrupt delivery mechanism.
Currently SOF has its own driver for this hardware.
config HP_SRAM_RESERVE
int "Bytes to reserve at start of HP-SRAM"
default 65536

View file

@ -13,6 +13,7 @@ zephyr_library_sources(soc.c)
zephyr_library_sources(trace_out.c)
zephyr_library_sources(rimage_modules.c)
zephyr_library_sources(boot.c)
zephyr_library_sources(cavs_ipc.c)
if(CONFIG_MP_NUM_CPUS GREATER 1)
zephyr_library_sources(soc_mp.c)

View file

@ -0,0 +1,158 @@
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include <cavs_ipc.h>
#include <cavs-ipc-regs.h>
#include <spinlock.h>
void cavs_ipc_set_message_handler(const struct device *dev,
cavs_ipc_handler_t fn, void *arg)
{
struct cavs_ipc_data *devdata = dev->data;
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
devdata->handle_message = fn;
devdata->handler_arg = arg;
k_spin_unlock(&devdata->lock, key);
}
void cavs_ipc_set_done_handler(const struct device *dev,
cavs_ipc_done_t fn, void *arg)
{
struct cavs_ipc_data *devdata = dev->data;
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
devdata->done_notify = fn;
devdata->done_arg = arg;
k_spin_unlock(&devdata->lock, key);
}
void z_cavs_ipc_isr(const void *devarg)
{
const struct device *dev = devarg;
const struct cavs_ipc_config *config = dev->config;
struct cavs_ipc_data *devdata = dev->data;
volatile struct cavs_ipc *regs = config->regs;
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
if (regs->tdr & CAVS_IPC_BUSY) {
bool done = true;
if (devdata->handle_message != NULL) {
uint32_t msg = regs->tdr & ~CAVS_IPC_BUSY;
uint32_t ext = regs->tdd;
done = devdata->handle_message(dev, devdata->handler_arg,
msg, ext);
}
regs->tdr = CAVS_IPC_BUSY;
if (done && !IS_ENABLED(CONFIG_SOC_SERIES_INTEL_CAVS_V15)) {
regs->tda = CAVS_IPC_DONE;
}
}
/* Same signal, but on different bits in 1.5 */
bool done = IS_ENABLED(CONFIG_SOC_SERIES_INTEL_CAVS_V15) ?
(regs->idd & CAVS_IPC_IDD15_DONE) : (regs->ida & CAVS_IPC_DONE);
if (done) {
if (devdata->done_notify != NULL) {
devdata->done_notify(dev, devdata->done_arg);
}
k_sem_give(&devdata->sem);
if (IS_ENABLED(CONFIG_SOC_SERIES_INTEL_CAVS_V15)) {
regs->idd = CAVS_IPC_IDD15_DONE;
} else {
regs->ida = CAVS_IPC_DONE;
}
}
k_spin_unlock(&devdata->lock, key);
}
int cavs_ipc_init(const struct device *dev)
{
struct cavs_ipc_data *devdata = dev->data;
const struct cavs_ipc_config *config = dev->config;
memset(devdata, 0, sizeof(*devdata));
/* ACK any latched interrupts (including TDA to clear IDA on
* the other side!), then enable.
*/
config->regs->tdr = CAVS_IPC_BUSY;
if (IS_ENABLED(CONFIG_SOC_SERIES_INTEL_CAVS_V15)) {
config->regs->idd = CAVS_IPC_IDD15_DONE;
} else {
config->regs->ida = CAVS_IPC_DONE;
config->regs->tda = CAVS_IPC_DONE;
}
config->regs->ctl |= (CAVS_IPC_CTL_IDIE | CAVS_IPC_CTL_TBIE);
return 0;
}
void cavs_ipc_complete(const struct device *dev)
{
const struct cavs_ipc_config *config = dev->config;
config->regs->tda = CAVS_IPC_DONE;
}
bool cavs_ipc_is_complete(const struct device *dev)
{
const struct cavs_ipc_config *config = dev->config;
return (config->regs->idr & CAVS_IPC_BUSY) == 0;
}
bool cavs_ipc_send_message(const struct device *dev,
uint32_t data, uint32_t ext_data)
{
const struct cavs_ipc_config *config = dev->config;
struct cavs_ipc_data *devdata = dev->data;
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
if ((config->regs->idr & CAVS_IPC_BUSY) != 0) {
k_spin_unlock(&devdata->lock, key);
return false;
}
k_sem_init(&devdata->sem, 0, 1);
config->regs->idd = ext_data;
config->regs->idr = data | CAVS_IPC_BUSY;
k_spin_unlock(&devdata->lock, key);
return true;
}
bool cavs_ipc_send_message_sync(const struct device *dev,
uint32_t data, uint32_t ext_data,
k_timeout_t timeout)
{
struct cavs_ipc_data *devdata = dev->data;
bool ret = cavs_ipc_send_message(dev, data, ext_data);
if (ret) {
k_sem_take(&devdata->sem, timeout);
}
return ret;
}
#if DT_NODE_EXISTS(CAVS_HOST_DTNODE)
static int dt_init(const struct device *dev)
{
IRQ_CONNECT(DT_IRQN(CAVS_HOST_DTNODE), 0, z_cavs_ipc_isr, CAVS_HOST_DEV, 0);
irq_enable(DT_IRQN(CAVS_HOST_DTNODE));
return cavs_ipc_init(dev);
}
static const struct cavs_ipc_config ipc_host_config = {
.regs = (void *)DT_REG_ADDR(CAVS_HOST_DTNODE),
};
static struct cavs_ipc_data ipc_host_data;
DEVICE_DT_DEFINE(CAVS_HOST_DTNODE, dt_init, NULL, &ipc_host_data, &ipc_host_config,
PRE_KERNEL_2, 0, NULL);
#endif

View file

@ -4,6 +4,8 @@
#ifndef ZEPHYR_SOC_INTEL_ADSP_CAVS_IPC_REGS_H
#define ZEPHYR_SOC_INTEL_ADSP_CAVS_IPC_REGS_H
#include <stdint.h>
/* Inter Processor Communication: a distressingly heavyweight method
* for directing interrupts at software running on other processors.
* Used for sending interrupts to and receiving them from another

View file

@ -0,0 +1,165 @@
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_CAVS_IPC_H
#define ZEPHYR_INCLUDE_CAVS_IPC_H
#include <kernel.h>
#include <device.h>
struct cavs_ipc_config {
volatile struct cavs_ipc *regs;
};
/** @brief cAVS IPC Message Handler Callback
*
* This function, once registered via cavs_ipc_set_message_handler(),
* is invoked in interrupt context to service messages sent from the
* foreign/connected IPC context. The message contents of the TDR and
* TDD registers are provided in the data/ext_data argument.
*
* The function should return true if processing of the message is
* complete and return notification to the other side (via the TDA
* register) is desired immediately. Returning false means that no
* return "DONE" interrupt will occur until cavs_ipc_complete() is
* called on this device at some point in the future.
*
* @note Further messages on the link will not be transmitted or
* received while an in-progress message remains incomplete!
*
* @param dev IPC device
* @param arg Registered argument from cavs_ipc_set_message_handler()
* @param data Message data from other side (low bits of TDR register)
* @param ext_dat Extended message data (TDD register)
* @return true if the message is completely handled
*/
typedef bool (*cavs_ipc_handler_t)(const struct device *dev, void *arg,
uint32_t data, uint32_t ext_data);
/** @brief cAVS IPC Message Complete Callback
*
* This function, once registered via cavs_ipc_set_done_handler(), is
* invoked in interrupt context when a "DONE" return interrupt is
* received from the other side of the connection (indicating that a
* previously sent message is finished processing).
*
* @note On cAVS hardware the DONE interrupt is transmitted
* synchronously with the interrupt being cleared on the remote
* device. It is not possible to delay processing. This callback
* will still occur, but protocols which rely on notification of
* asynchronous command processing will need modification.
*
* @param dev IPC device
* @param arg Registered argument from cavs_ipc_set_done_handler()
*/
typedef void (*cavs_ipc_done_t)(const struct device *dev, void *arg);
struct cavs_ipc_data {
struct k_sem sem;
struct k_spinlock lock;
cavs_ipc_handler_t handle_message;
void *handler_arg;
cavs_ipc_done_t done_notify;
void *done_arg;
};
void z_cavs_ipc_isr(const void *devarg);
/** @brief Register message callback handler
*
* This function registers a handler function for received messages.
*
* @param dev IPC device
* @param fn Callback function
* @param arg Value to pass as the "arg" parameter to the function
*/
void cavs_ipc_set_message_handler(const struct device *dev,
cavs_ipc_handler_t fn, void *arg);
/** @brief Register done callback handler
*
* This function registers a handler function for message completion
* notifications
*
* @param dev IPC device
* @param fn Callback function
* @param arg Value to pass as the "arg" parameter to the function
*/
void cavs_ipc_set_done_handler(const struct device *dev,
cavs_ipc_done_t fn, void *arg);
/** @brief Initialize cavs_ipc device
*
* Initialize the device. Must be called before any API calls or
* interrupts can be serviced.
*
* @param dev IPC device
* @return Zero on success, negative codes for error
*/
int cavs_ipc_init(const struct device *dev);
/** @brief Complete an in-progress message
*
* Notify the other side that the current in-progress message is
* complete. This is a noop if no message is in progress.
*
* @note Further messages on the link will not be transmitted or
* received while an in-progress message remains incomplete!
*
* @param dev IPC device
*/
void cavs_ipc_complete(const struct device *dev);
/** @brief Message-in-progress predicate
*
* Returns false if a message has been received but not yet completed
* via cavs_ipc_complete(), true otherwise.
*
* @param dev IPC device
* @return True if no message is in progress
*/
bool cavs_ipc_is_complete(const struct device *dev);
/** @brief Send an IPC message
*
* Sends a message to the other side of an IPC link. The data and
* ext_data parameters are passed using the IDR/IDD registers.
* Returns true if the message was sent, false if a current message is
* in progress (in the sense of cavs_ipc_is_complete()).
*
* @param dev IPC device
* @param data 30 bits value to transmit with the message (IDR register)
* @param ext_data Extended value to transmit with the message (IDD register)
* @return message successfully transmitted
*/
bool cavs_ipc_send_message(const struct device *dev,
uint32_t data, uint32_t ext_data);
/** @brief Send an IPC message, block until completion
*
* As for cavs_ipc_send_message(), but blocks the current thread until
* the completion of the message or the expiration of the provided
* timeout.
*
* @param dev IPC device
* @param data 30 bits value to transmit with the message (IDR register)
* @param ext_data Extended value to transmit with the message (IDD register)
* @param timeout Maximum time to wait, or K_FOREVER, or K_NO_WAIT
* @return message successfully transmitted
*/
bool cavs_ipc_send_message_sync(const struct device *dev,
uint32_t data, uint32_t ext_data,
k_timeout_t timeout);
#define CAVS_HOST_DTNODE DT_NODELABEL(cavs_host_ipc)
/** @brief Host IPC device pointer
*
* This macro expands to the registered host IPC device from
* devicetree (if one exists!). The device will be initialized and
* ready at system startup.
*/
#define CAVS_HOST_DEV DEVICE_DT_GET(CAVS_HOST_DTNODE)
#endif /* ZEPHYR_INCLUDE_CAVS_IPC_H */