soc: intel_adsp: rework host IPC using IPC service
This reworks the Intel audio DSP host IPC driver as a backend of the IPC service. This is the first step to rework IPC in SOF (Sound Open Firmware) into using a more generic IPC API instead of a SoC specific one. For now, it keeps the old interface to maintain usability as it is going to be a multiple process to rework IPC over there. Also, the structure of the new IPC backend resembles the SoC specific driver to make it easier to compare between them at this first iteration. Future optimizations will probably be needed once we start modifying the SOF side to utilize the IPC interface. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
committed by
Chris Friedt
parent
ec28731c39
commit
cf7e2e63c1
213
include/zephyr/ipc/backends/intel_adsp_host_ipc.h
Normal file
213
include/zephyr/ipc/backends/intel_adsp_host_ipc.h
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 2025 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_IPC_BACKEND_INTEL_ADSP_IPC_H
|
||||
#define ZEPHYR_INCLUDE_IPC_BACKEND_INTEL_ADSP_IPC_H
|
||||
|
||||
#include <intel_adsp_ipc_devtree.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
|
||||
#include <zephyr/ipc/ipc_service_backend.h>
|
||||
|
||||
/** Enum on IPC send length argument to indicate IPC message type. */
|
||||
enum intel_adsp_send_len {
|
||||
/** Normal IPC message. */
|
||||
INTEL_ADSP_IPC_SEND_MSG,
|
||||
|
||||
/** Synchronous IPC message. */
|
||||
INTEL_ADSP_IPC_SEND_MSG_SYNC,
|
||||
|
||||
/** Emergency IPC message. */
|
||||
INTEL_ADSP_IPC_SEND_MSG_EMERGENCY,
|
||||
|
||||
/** Send a DONE message. */
|
||||
INTEL_ADSP_IPC_SEND_DONE,
|
||||
|
||||
/** Query backend to see if IPC is complete. */
|
||||
INTEL_ADSP_IPC_SEND_IS_COMPLETE,
|
||||
};
|
||||
|
||||
/** Enum on callback return values. */
|
||||
enum intel_adsp_cb_ret {
|
||||
/** Callback return to indicate no issue. Must be 0. */
|
||||
INTEL_ADSP_IPC_CB_RET_OKAY = 0,
|
||||
|
||||
/** Callback return to signal needing external completion. */
|
||||
INTEL_ADSP_IPC_CB_RET_EXT_COMPLETE,
|
||||
};
|
||||
|
||||
/** Enum on callback length argument to indicate which triggers the callback. */
|
||||
enum intel_adsp_cb_len {
|
||||
/** Callback length to indicate this is an IPC message. */
|
||||
INTEL_ADSP_IPC_CB_MSG,
|
||||
|
||||
/** Callback length to indicate this is a DONE message. */
|
||||
INTEL_ADSP_IPC_CB_DONE,
|
||||
};
|
||||
|
||||
/** Struct for IPC message descriptor. */
|
||||
struct intel_adsp_ipc_msg {
|
||||
/** Header specific to platform. */
|
||||
uint32_t data;
|
||||
|
||||
/** Extension specific to platform. */
|
||||
uint32_t ext_data;
|
||||
|
||||
/** Timeout for sending synchronuous message. */
|
||||
k_timeout_t timeout;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE
|
||||
|
||||
/**
|
||||
* @brief Intel ADSP IPC Message Handler Callback.
|
||||
*
|
||||
* This function, once registered via intel_adsp_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 intel_adsp_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 intel_adsp_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 (*intel_adsp_ipc_handler_t)(const struct device *dev, void *arg, uint32_t data,
|
||||
uint32_t ext_data);
|
||||
|
||||
/**
|
||||
* @brief Intel ADSP IPC Message Complete Callback.
|
||||
*
|
||||
* This function, once registered via intel_adsp_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 Intel ADSP 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 intel_adsp_ipc_set_done_handler().
|
||||
* @return True if IPC completion will be done externally, otherwise false.
|
||||
* @note Returning True will cause this API to skip writing IPC registers
|
||||
* signalling IPC message completion and those actions should be done by
|
||||
* external code manually. Returning false from the handler will perform
|
||||
* writing to IPC registers signalling message completion normally by this API.
|
||||
*/
|
||||
typedef bool (*intel_adsp_ipc_done_t)(const struct device *dev, void *arg);
|
||||
|
||||
#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
typedef int (*intel_adsp_ipc_resume_handler_t)(const struct device *dev, void *arg);
|
||||
|
||||
typedef int (*intel_adsp_ipc_suspend_handler_t)(const struct device *dev, void *arg);
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
/**
|
||||
* Intel Audio DSP IPC service backend config struct.
|
||||
*/
|
||||
struct intel_adsp_ipc_config {
|
||||
/** Pointer to hardware register block. */
|
||||
volatile struct intel_adsp_ipc *regs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Intel Audio DSP IPC service backend data struct.
|
||||
*/
|
||||
struct intel_adsp_ipc_data {
|
||||
/** Semaphore used to wait for remote acknowledgment of sent message. */
|
||||
struct k_sem sem;
|
||||
|
||||
/** General driver lock. */
|
||||
struct k_spinlock lock;
|
||||
|
||||
/** Pending TX acknowlegement. */
|
||||
bool tx_ack_pending;
|
||||
|
||||
/** Pointer to endpoint configuration. */
|
||||
const struct ipc_ept_cfg *ept_cfg;
|
||||
|
||||
#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE
|
||||
/** Callback for message handler. */
|
||||
intel_adsp_ipc_handler_t handle_message;
|
||||
|
||||
/** Argument for message handler callback. */
|
||||
void *handler_arg;
|
||||
|
||||
/** Callback for done notification. */
|
||||
intel_adsp_ipc_done_t done_notify;
|
||||
|
||||
/** Argument for done notification callback. */
|
||||
void *done_arg;
|
||||
#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
/** Pointer to resume handler. */
|
||||
intel_adsp_ipc_resume_handler_t resume_fn;
|
||||
|
||||
/** Argument for resume handler. */
|
||||
void *resume_fn_args;
|
||||
|
||||
/** Pointer to suspend handler. */
|
||||
intel_adsp_ipc_suspend_handler_t suspend_fn;
|
||||
|
||||
/** Argument for suspend handler. */
|
||||
void *suspend_fn_args;
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
};
|
||||
|
||||
/**
|
||||
* Endpoint private data struct.
|
||||
*/
|
||||
struct intel_adsp_ipc_ept_priv_data {
|
||||
/** Callback return value (enum intel_adsp_cb_ret). */
|
||||
int cb_ret;
|
||||
|
||||
/** Pointer to additional private data. */
|
||||
void *priv;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
|
||||
/**
|
||||
* @brief Registers resume callback handler used to resume Device from suspended state.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
* @param fn Callback function.
|
||||
* @param arg Value to pass as the "arg" parameter to the function.
|
||||
*/
|
||||
void intel_adsp_ipc_set_resume_handler(const struct device *dev, intel_adsp_ipc_resume_handler_t fn,
|
||||
void *arg);
|
||||
|
||||
/**
|
||||
* @brief Registers suspend callback handler used to suspend active Device.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
* @param fn Callback function.
|
||||
* @param arg Value to pass as the "arg" parameter to the function.
|
||||
*/
|
||||
void intel_adsp_ipc_set_suspend_handler(const struct device *dev,
|
||||
intel_adsp_ipc_suspend_handler_t fn, void *arg);
|
||||
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_IPC_BACKEND_INTEL_ADSP_IPC_H */
|
||||
@@ -11,6 +11,7 @@ config SOC_FAMILY_INTEL_ADSP
|
||||
select ARCH_HAS_USERSPACE if XTENSA_MMU
|
||||
imply XTENSA_MMU_DOUBLE_MAP
|
||||
select CPU_CACHE_INCOHERENT
|
||||
select IPC_SERVICE if DT_HAS_INTEL_ADSP_HOST_IPC_ENABLED
|
||||
|
||||
if SOC_FAMILY_INTEL_ADSP
|
||||
|
||||
@@ -32,14 +33,20 @@ config INTEL_ADSP_SIM_NO_SECONDARY_CORE_FLOW
|
||||
|
||||
endif # INTEL_ADSP_SIM
|
||||
|
||||
DT_COMPAT_INTEL_ADSP_HOST_IPC := intel,adsp-host-ipc
|
||||
|
||||
config INTEL_ADSP_IPC
|
||||
bool "Driver for the host IPC interrupt delivery"
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_INTEL_ADSP_HOST_IPC)) if !SOF
|
||||
select DEPRECATED
|
||||
help
|
||||
Driver for the host IPC interrupt delivery mechanism.
|
||||
Currently SOF has its own driver for this hardware.
|
||||
Deprecated config for IPC. Will be removed in the future.
|
||||
|
||||
config INTEL_ADSP_IPC_OLD_INTERFACE
|
||||
bool "Expose old interface for the IPC"
|
||||
depends on IPC_SERVICE_BACKEND_INTEL_ADSP_HOST_IPC
|
||||
default y
|
||||
select INTEL_ADSP_IPC
|
||||
help
|
||||
Expose the old IPC interface (intel_adsp_ipc_* functions) to
|
||||
maintain backward compatibility.
|
||||
|
||||
config MEMORY_WIN_0_SIZE
|
||||
int "Size of memory window 0"
|
||||
|
||||
@@ -9,7 +9,7 @@ zephyr_library_named(intel_adsp_common)
|
||||
zephyr_include_directories(include)
|
||||
zephyr_library_include_directories(${ZEPHYR_BASE}/drivers)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_INTEL_ADSP_IPC ipc.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE ipc.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_GDBSTUB
|
||||
gdbstub_backend_sram.c
|
||||
|
||||
@@ -9,69 +9,9 @@
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
|
||||
struct intel_adsp_ipc_config {
|
||||
volatile struct intel_adsp_ipc *regs;
|
||||
};
|
||||
#include <zephyr/ipc/backends/intel_adsp_host_ipc.h>
|
||||
|
||||
/**
|
||||
* @brief Intel ADSP IPC Message Handler Callback.
|
||||
*
|
||||
* This function, once registered via intel_adsp_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 intel_adsp_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 intel_adsp_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 (*intel_adsp_ipc_handler_t)(const struct device *dev, void *arg,
|
||||
uint32_t data, uint32_t ext_data);
|
||||
|
||||
/**
|
||||
* @brief Intel ADSP IPC Message Complete Callback.
|
||||
*
|
||||
* This function, once registered via intel_adsp_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 Intel ADSP 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 intel_adsp_ipc_set_done_handler().
|
||||
* @return True if IPC completion will be done externally, otherwise false.
|
||||
* @note Returning True will cause this API to skip writing IPC registers
|
||||
* signalling IPC message completion and those actions should be done by
|
||||
* external code manually. Returning false from the handler will perform
|
||||
* writing to IPC registers signalling message completion normally by this API.
|
||||
*/
|
||||
typedef bool (*intel_adsp_ipc_done_t)(const struct device *dev, void *arg);
|
||||
|
||||
struct intel_adsp_ipc_data {
|
||||
struct k_sem sem;
|
||||
struct k_spinlock lock;
|
||||
intel_adsp_ipc_handler_t handle_message;
|
||||
void *handler_arg;
|
||||
intel_adsp_ipc_done_t done_notify;
|
||||
void *done_arg;
|
||||
bool tx_ack_pending;
|
||||
};
|
||||
#if defined(CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE) || defined(__DOXYGEN__)
|
||||
|
||||
/**
|
||||
* @brief Register message callback handler.
|
||||
@@ -98,34 +38,27 @@ void intel_adsp_ipc_set_message_handler(const struct device *dev,
|
||||
void intel_adsp_ipc_set_done_handler(const struct device *dev,
|
||||
intel_adsp_ipc_done_t fn, void *arg);
|
||||
|
||||
/** @brief Initialize Intel ADSP 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 intel_adsp_ipc_init(const struct device *dev);
|
||||
|
||||
/** @brief Complete an in-progress message.
|
||||
/**
|
||||
* @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!
|
||||
* received while an in-progress message remains incomplete!
|
||||
*
|
||||
* @param dev IPC device.
|
||||
*/
|
||||
void intel_adsp_ipc_complete(const struct device *dev);
|
||||
|
||||
/** @brief Message-in-progress predicate.
|
||||
/**
|
||||
* @brief Message-in-progress predicate.
|
||||
*
|
||||
* Returns false if a message has been received but not yet completed
|
||||
* via intel_adsp_ipc_complete(), true otherwise.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
*
|
||||
* @return True if no message is in progress.
|
||||
*/
|
||||
bool intel_adsp_ipc_is_complete(const struct device *dev);
|
||||
@@ -175,38 +108,6 @@ int intel_adsp_ipc_send_message_sync(const struct device *dev,
|
||||
void intel_adsp_ipc_send_message_emergency(const struct device *dev, uint32_t data,
|
||||
uint32_t ext_data);
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */
|
||||
|
||||
typedef int (*intel_adsp_ipc_resume_handler_t)(const struct device *dev, void *arg);
|
||||
|
||||
typedef int (*intel_adsp_ipc_suspend_handler_t)(const struct device *dev, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Registers resume callback handler used to resume Device from suspended state.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
* @param fn Callback function.
|
||||
* @param arg Value to pass as the "arg" parameter to the function.
|
||||
*/
|
||||
void intel_adsp_ipc_set_resume_handler(const struct device *dev,
|
||||
intel_adsp_ipc_resume_handler_t fn, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Registers suspend callback handler used to suspend active Device.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
* @param fn Callback function.
|
||||
* @param arg Value to pass as the "arg" parameter to the function.
|
||||
*/
|
||||
void intel_adsp_ipc_set_suspend_handler(const struct device *dev,
|
||||
intel_adsp_ipc_suspend_handler_t fn, void *arg);
|
||||
|
||||
struct ipc_control_driver_api {
|
||||
intel_adsp_ipc_resume_handler_t resume_fn;
|
||||
void *resume_fn_args;
|
||||
intel_adsp_ipc_suspend_handler_t suspend_fn;
|
||||
void *suspend_fn_args;
|
||||
};
|
||||
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
#endif /* ZEPHYR_INCLUDE_INTEL_ADSP_IPC_H */
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
/* Copyright (c) 2022 Intel Corporation
|
||||
/*
|
||||
* Copyright (c) 2022, 2025 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <intel_adsp_ipc.h>
|
||||
#include <adsp_ipc_regs.h>
|
||||
#include <adsp_interrupt.h>
|
||||
#include <zephyr/irq.h>
|
||||
#include <zephyr/pm/state.h>
|
||||
#include <zephyr/pm/pm.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/policy.h>
|
||||
#include <errno.h>
|
||||
|
||||
void intel_adsp_ipc_set_message_handler(const struct device *dev,
|
||||
intel_adsp_ipc_handler_t fn, void *arg)
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <intel_adsp_ipc.h>
|
||||
#include <zephyr/ipc/backends/intel_adsp_host_ipc.h>
|
||||
|
||||
static struct ipc_ept intel_adsp_ipc_ept;
|
||||
static struct intel_adsp_ipc_ept_priv_data intel_adsp_ipc_priv_data;
|
||||
static struct ipc_ept_cfg intel_adsp_ipc_ept_cfg;
|
||||
|
||||
void intel_adsp_ipc_set_message_handler(const struct device *dev, intel_adsp_ipc_handler_t fn,
|
||||
void *arg)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
@@ -24,8 +27,7 @@ void intel_adsp_ipc_set_message_handler(const struct device *dev,
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
void intel_adsp_ipc_set_done_handler(const struct device *dev,
|
||||
intel_adsp_ipc_done_t fn, void *arg)
|
||||
void intel_adsp_ipc_set_done_handler(const struct device *dev, intel_adsp_ipc_done_t fn, void *arg)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
@@ -35,314 +37,121 @@ void intel_adsp_ipc_set_done_handler(const struct device *dev,
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
static void intel_adsp_ipc_isr(const void *devarg)
|
||||
static void intel_adsp_ipc_receive_cb(const void *data, size_t len, void *priv)
|
||||
{
|
||||
const struct device *dev = devarg;
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
const struct device *dev = INTEL_ADSP_IPC_HOST_DEV;
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
struct intel_adsp_ipc_ept_priv_data *priv_data =
|
||||
(struct intel_adsp_ipc_ept_priv_data *)priv;
|
||||
bool done = true;
|
||||
|
||||
volatile struct intel_adsp_ipc *regs = config->regs;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
if (regs->tdr & INTEL_ADSP_IPC_BUSY) {
|
||||
bool done = true;
|
||||
if (len == INTEL_ADSP_IPC_CB_MSG) {
|
||||
const struct intel_adsp_ipc_msg *msg = (const struct intel_adsp_ipc_msg *)data;
|
||||
|
||||
if (devdata->handle_message != NULL) {
|
||||
uint32_t msg = regs->tdr & ~INTEL_ADSP_IPC_BUSY;
|
||||
uint32_t ext = regs->tdd;
|
||||
|
||||
done = devdata->handle_message(dev, devdata->handler_arg, msg, ext);
|
||||
done = devdata->handle_message(dev, devdata->handler_arg, msg->data,
|
||||
msg->ext_data);
|
||||
}
|
||||
|
||||
regs->tdr = INTEL_ADSP_IPC_BUSY;
|
||||
if (done) {
|
||||
#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE
|
||||
regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE;
|
||||
#else
|
||||
regs->tda = INTEL_ADSP_IPC_DONE;
|
||||
#endif
|
||||
priv_data->cb_ret = INTEL_ADSP_IPC_CB_RET_OKAY;
|
||||
} else {
|
||||
priv_data->cb_ret = -EBADMSG;
|
||||
}
|
||||
}
|
||||
|
||||
/* Same signal, but on different bits in 1.5 */
|
||||
bool done = (regs->ida & INTEL_ADSP_IPC_DONE);
|
||||
|
||||
if (done) {
|
||||
} else if (len == INTEL_ADSP_IPC_CB_DONE) {
|
||||
bool external_completion = false;
|
||||
|
||||
if (devdata->done_notify != NULL) {
|
||||
external_completion = devdata->done_notify(dev, devdata->done_arg);
|
||||
}
|
||||
devdata->tx_ack_pending = false;
|
||||
/* Allow the system to enter the runtime idle state after the IPC acknowledgment
|
||||
* is received.
|
||||
*/
|
||||
pm_policy_state_lock_put(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES);
|
||||
k_sem_give(&devdata->sem);
|
||||
|
||||
/* IPC completion registers will be set externally */
|
||||
if (external_completion) {
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
return;
|
||||
priv_data->cb_ret = INTEL_ADSP_IPC_CB_RET_EXT_COMPLETE;
|
||||
} else {
|
||||
priv_data->cb_ret = INTEL_ADSP_IPC_CB_RET_OKAY;
|
||||
}
|
||||
|
||||
regs->ida = INTEL_ADSP_IPC_DONE;
|
||||
}
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
int intel_adsp_ipc_init(const struct device *dev)
|
||||
{
|
||||
pm_device_busy_set(dev);
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
|
||||
memset(devdata, 0, sizeof(*devdata));
|
||||
|
||||
k_sem_init(&devdata->sem, 0, 1);
|
||||
|
||||
/* ACK any latched interrupts (including TDA to clear IDA on
|
||||
* the other side!), then enable.
|
||||
*/
|
||||
config->regs->tdr = INTEL_ADSP_IPC_BUSY;
|
||||
config->regs->ida = INTEL_ADSP_IPC_DONE;
|
||||
#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE
|
||||
config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE;
|
||||
#else
|
||||
config->regs->tda = INTEL_ADSP_IPC_DONE;
|
||||
#endif
|
||||
config->regs->ctl |= (INTEL_ADSP_IPC_CTL_IDIE | INTEL_ADSP_IPC_CTL_TBIE);
|
||||
pm_device_busy_clear(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void intel_adsp_ipc_complete(const struct device *dev)
|
||||
{
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
int ret;
|
||||
|
||||
#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE
|
||||
config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE;
|
||||
#else
|
||||
config->regs->tda = INTEL_ADSP_IPC_DONE;
|
||||
#endif
|
||||
ret = ipc_service_send(&intel_adsp_ipc_ept, NULL, INTEL_ADSP_IPC_SEND_DONE);
|
||||
|
||||
ARG_UNUSED(ret);
|
||||
}
|
||||
|
||||
bool intel_adsp_ipc_is_complete(const struct device *dev)
|
||||
{
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
const struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
bool not_busy = (config->regs->idr & INTEL_ADSP_IPC_BUSY) == 0;
|
||||
int ret;
|
||||
|
||||
return not_busy && !devdata->tx_ack_pending;
|
||||
ret = ipc_service_send(&intel_adsp_ipc_ept, NULL, INTEL_ADSP_IPC_SEND_IS_COMPLETE);
|
||||
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
int intel_adsp_ipc_send_message(const struct device *dev,
|
||||
uint32_t data, uint32_t ext_data)
|
||||
int intel_adsp_ipc_send_message(const struct device *dev, uint32_t data, uint32_t ext_data)
|
||||
{
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
enum pm_device_state current_state;
|
||||
struct intel_adsp_ipc_msg msg = {.data = data, .ext_data = ext_data};
|
||||
int ret;
|
||||
|
||||
if (pm_device_state_get(INTEL_ADSP_IPC_HOST_DEV, ¤t_state) != 0 ||
|
||||
current_state != PM_DEVICE_STATE_ACTIVE) {
|
||||
return -ESHUTDOWN;
|
||||
}
|
||||
#endif
|
||||
ret = ipc_service_send(&intel_adsp_ipc_ept, &msg, INTEL_ADSP_IPC_SEND_MSG);
|
||||
|
||||
pm_device_busy_set(dev);
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
if ((config->regs->idr & INTEL_ADSP_IPC_BUSY) != 0 || devdata->tx_ack_pending) {
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
return -EBUSY;
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
k_sem_reset(&devdata->sem);
|
||||
/* Prevent entering runtime idle state until IPC acknowledgment is received. */
|
||||
pm_policy_state_lock_get(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES);
|
||||
devdata->tx_ack_pending = true;
|
||||
config->regs->idd = ext_data;
|
||||
config->regs->idr = data | INTEL_ADSP_IPC_BUSY;
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
pm_device_busy_clear(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int intel_adsp_ipc_send_message_sync(const struct device *dev,
|
||||
uint32_t data, uint32_t ext_data,
|
||||
k_timeout_t timeout)
|
||||
int intel_adsp_ipc_send_message_sync(const struct device *dev, uint32_t data, uint32_t ext_data,
|
||||
k_timeout_t timeout)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
struct intel_adsp_ipc_msg msg = {
|
||||
.data = data,
|
||||
.ext_data = ext_data,
|
||||
.timeout = timeout,
|
||||
};
|
||||
int ret;
|
||||
|
||||
int ret = intel_adsp_ipc_send_message(dev, data, ext_data);
|
||||
ret = ipc_service_send(&intel_adsp_ipc_ept, &msg, INTEL_ADSP_IPC_SEND_MSG_SYNC);
|
||||
|
||||
if (!ret) {
|
||||
k_sem_take(&devdata->sem, timeout);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void intel_adsp_ipc_send_message_emergency(const struct device *dev, uint32_t data,
|
||||
uint32_t ext_data)
|
||||
{
|
||||
const struct intel_adsp_ipc_config * const config = dev->config;
|
||||
struct intel_adsp_ipc_msg msg = {.data = data, .ext_data = ext_data};
|
||||
int ret;
|
||||
|
||||
volatile struct intel_adsp_ipc * const regs = config->regs;
|
||||
bool done;
|
||||
ret = ipc_service_send(&intel_adsp_ipc_ept, &msg, INTEL_ADSP_IPC_SEND_MSG_EMERGENCY);
|
||||
|
||||
/* check if host is processing message. */
|
||||
while (regs->idr & INTEL_ADSP_IPC_BUSY) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
/* check if host has pending acknowledge msg
|
||||
* Same signal, but on different bits in 1.5
|
||||
*/
|
||||
done = regs->ida & INTEL_ADSP_IPC_DONE;
|
||||
if (done) {
|
||||
/* IPC completion */
|
||||
regs->ida = INTEL_ADSP_IPC_DONE;
|
||||
}
|
||||
|
||||
regs->idd = ext_data;
|
||||
regs->idr = data | INTEL_ADSP_IPC_BUSY;
|
||||
ARG_UNUSED(ret);
|
||||
}
|
||||
|
||||
#if DT_NODE_EXISTS(INTEL_ADSP_IPC_HOST_DTNODE)
|
||||
static struct ipc_ept_cfg intel_adsp_ipc_ept_cfg = {
|
||||
.name = "intel_adsp_ipc_ept",
|
||||
.cb = {
|
||||
.received = intel_adsp_ipc_receive_cb,
|
||||
},
|
||||
.priv = (void *)&intel_adsp_ipc_priv_data,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE)
|
||||
static inline void ace_ipc_intc_unmask(void)
|
||||
static int intel_adsp_ipc_old_init(void)
|
||||
{
|
||||
ACE_DINT[0].ie[ACE_INTL_HIPC] = BIT(0);
|
||||
}
|
||||
#else
|
||||
static inline void ace_ipc_intc_unmask(void) {}
|
||||
#endif
|
||||
int ret;
|
||||
const struct device *ipc_dev = INTEL_ADSP_IPC_HOST_DEV;
|
||||
|
||||
static int dt_init(const struct device *dev)
|
||||
{
|
||||
IRQ_CONNECT(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE), 0, intel_adsp_ipc_isr,
|
||||
INTEL_ADSP_IPC_HOST_DEV, 0);
|
||||
irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
|
||||
ace_ipc_intc_unmask();
|
||||
|
||||
return intel_adsp_ipc_init(dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
|
||||
void intel_adsp_ipc_set_resume_handler(const struct device *dev,
|
||||
intel_adsp_ipc_resume_handler_t fn, void *arg)
|
||||
{
|
||||
struct ipc_control_driver_api *api =
|
||||
(struct ipc_control_driver_api *)dev->api;
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
api->resume_fn = fn;
|
||||
api->resume_fn_args = arg;
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
void intel_adsp_ipc_set_suspend_handler(const struct device *dev,
|
||||
intel_adsp_ipc_suspend_handler_t fn, void *arg)
|
||||
{
|
||||
struct ipc_control_driver_api *api =
|
||||
(struct ipc_control_driver_api *)dev->api;
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
api->suspend_fn = fn;
|
||||
api->suspend_fn_args = arg;
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Manages IPC driver power state change.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
* @param action Power state to be changed to.
|
||||
* @return int Returns 0 on success or optionaly error code from the
|
||||
* registered ipc_power_control_api callbacks.
|
||||
*
|
||||
* @note PM lock is taken at the start of each power transition to prevent concurrent calls
|
||||
* to @ref pm_device_action_run function.
|
||||
* If IPC Device performs hardware operation and device is busy (what should not happen)
|
||||
* function returns failure. It is API user responsibility to make sure we are not entering
|
||||
* device power transition while device is busy.
|
||||
*/
|
||||
static int ipc_pm_action(const struct device *dev, enum pm_device_action action)
|
||||
{
|
||||
if (pm_device_is_busy(INTEL_ADSP_IPC_HOST_DEV)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
const struct ipc_control_driver_api *api =
|
||||
(const struct ipc_control_driver_api *)dev->api;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
switch (action) {
|
||||
case PM_DEVICE_ACTION_SUSPEND:
|
||||
if (api->suspend_fn) {
|
||||
ret = api->suspend_fn(dev, api->suspend_fn_args);
|
||||
if (!ret) {
|
||||
irq_disable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PM_DEVICE_ACTION_RESUME:
|
||||
irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
if (!irq_is_enabled(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE))) {
|
||||
ret = -EINTR;
|
||||
break;
|
||||
}
|
||||
ace_ipc_intc_unmask();
|
||||
ret = intel_adsp_ipc_init(dev);
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
if (api->resume_fn) {
|
||||
ret = api->resume_fn(dev, api->resume_fn_args);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Return as default value when given PM action is not supported */
|
||||
return -ENOTSUP;
|
||||
}
|
||||
ret = ipc_service_register_endpoint(ipc_dev, &intel_adsp_ipc_ept, &intel_adsp_ipc_ept_cfg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback functions to be executed by Zephyr application
|
||||
* during IPC device suspend and resume.
|
||||
*/
|
||||
static struct ipc_control_driver_api ipc_power_control_api = {
|
||||
.resume_fn = NULL,
|
||||
.resume_fn_args = NULL,
|
||||
.suspend_fn = NULL,
|
||||
.suspend_fn_args = NULL
|
||||
};
|
||||
|
||||
PM_DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, ipc_pm_action);
|
||||
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
static const struct intel_adsp_ipc_config ipc_host_config = {
|
||||
.regs = (void *)INTEL_ADSP_IPC_REG_ADDRESS,
|
||||
};
|
||||
|
||||
static struct intel_adsp_ipc_data ipc_host_data;
|
||||
|
||||
DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, dt_init, PM_DEVICE_DT_GET(INTEL_ADSP_IPC_HOST_DTNODE),
|
||||
&ipc_host_data, &ipc_host_config, PRE_KERNEL_2, 0, COND_CODE_1(CONFIG_PM_DEVICE,
|
||||
(&ipc_power_control_api), (NULL)));
|
||||
|
||||
#endif /* DT_NODE_EXISTS(INTEL_ADSP_IPC_HOST_DTNODE) */
|
||||
/* Backend is at PRE_KERNEL_2:0, so we need to init after that. */
|
||||
SYS_INIT(intel_adsp_ipc_old_init, PRE_KERNEL_2, 1);
|
||||
|
||||
@@ -4,4 +4,5 @@ zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG ipc_icmsg.c)
|
||||
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG_ME_INITIATOR ipc_icmsg_me_initiator.c)
|
||||
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG_ME_FOLLOWER ipc_icmsg_me_follower.c)
|
||||
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICBMSG ipc_icbmsg.c)
|
||||
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_INTEL_ADSP_HOST_IPC ipc_intel_adsp_host_ipc.c)
|
||||
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_RPMSG ipc_rpmsg_static_vrings.c)
|
||||
|
||||
@@ -50,4 +50,5 @@ config IPC_SERVICE_BACKEND_ICMSG_ME_FOLLOWER
|
||||
|
||||
rsource "Kconfig.icmsg_me"
|
||||
rsource "Kconfig.icbmsg"
|
||||
rsource "Kconfig.intel_adsp"
|
||||
rsource "Kconfig.rpmsg"
|
||||
|
||||
11
subsys/ipc/ipc_service/backends/Kconfig.intel_adsp
Normal file
11
subsys/ipc/ipc_service/backends/Kconfig.intel_adsp
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
config IPC_SERVICE_BACKEND_INTEL_ADSP_HOST_IPC
|
||||
bool "Backend for the Intel Audio DSP Host IPC"
|
||||
default DT_HAS_INTEL_ADSP_HOST_IPC_ENABLED
|
||||
help
|
||||
IPC Service Backend for Intel Audio DSP Host IPC.
|
||||
464
subsys/ipc/ipc_service/backends/ipc_intel_adsp_host_ipc.c
Normal file
464
subsys/ipc/ipc_service/backends/ipc_intel_adsp_host_ipc.c
Normal file
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 2025 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* @brief IPC service backend for Intel Audio DSP host IPC
|
||||
*
|
||||
* @note When declaring struct ipt_ept_cfg, the field priv must point to
|
||||
* a struct intel_adsp_ipc_ept_priv_data. This is used for passing
|
||||
* callback returns.
|
||||
*
|
||||
* @note For sending message and the received callback, the data and len
|
||||
* arguments are not used to represent a byte array. Instead,
|
||||
* the data argument points to the descriptor of data to be sent
|
||||
* (of struct intel_adsp_ipc_msg). The len argument represents
|
||||
* the type of message to be sent (enum intel_adsp_send_len) and
|
||||
* the type of callback (enum intel_adsp_cb_len).
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT intel_adsp_host_ipc
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/irq.h>
|
||||
#include <zephyr/ipc/ipc_service.h>
|
||||
#include <zephyr/ipc/ipc_service_backend.h>
|
||||
#include <zephyr/pm/state.h>
|
||||
#include <zephyr/pm/pm.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/policy.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <intel_adsp_ipc.h>
|
||||
#include <adsp_ipc_regs.h>
|
||||
#include <adsp_interrupt.h>
|
||||
|
||||
#include <zephyr/ipc/backends/intel_adsp_host_ipc.h>
|
||||
|
||||
static inline void ace_ipc_intc_mask(void)
|
||||
{
|
||||
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE)
|
||||
ACE_DINT[0].ie[ACE_INTL_HIPC] = ACE_DINT[0].ie[ACE_INTL_HIPC] & ~BIT(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void ace_ipc_intc_unmask(void)
|
||||
{
|
||||
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE)
|
||||
ACE_DINT[0].ie[ACE_INTL_HIPC] = BIT(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void intel_adsp_ipc_isr(const void *devarg)
|
||||
{
|
||||
const struct device *dev = devarg;
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
const struct ipc_ept_cfg *ept_cfg = devdata->ept_cfg;
|
||||
|
||||
struct intel_adsp_ipc_ept_priv_data *priv_data =
|
||||
(struct intel_adsp_ipc_ept_priv_data *)ept_cfg->priv;
|
||||
|
||||
volatile struct intel_adsp_ipc *regs = config->regs;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
if (regs->tdr & INTEL_ADSP_IPC_BUSY) {
|
||||
bool done = true;
|
||||
|
||||
if (ept_cfg->cb.received != NULL) {
|
||||
struct intel_adsp_ipc_msg cb_msg = {
|
||||
.data = regs->tdr & ~INTEL_ADSP_IPC_BUSY,
|
||||
.ext_data = regs->tdd,
|
||||
};
|
||||
|
||||
ept_cfg->cb.received(&cb_msg, INTEL_ADSP_IPC_CB_MSG, ept_cfg->priv);
|
||||
|
||||
done = (priv_data->cb_ret == INTEL_ADSP_IPC_CB_RET_OKAY);
|
||||
}
|
||||
|
||||
regs->tdr = INTEL_ADSP_IPC_BUSY;
|
||||
if (done) {
|
||||
#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE
|
||||
regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE;
|
||||
#else
|
||||
regs->tda = INTEL_ADSP_IPC_DONE;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* Same signal, but on different bits in 1.5 */
|
||||
bool done = (regs->ida & INTEL_ADSP_IPC_DONE);
|
||||
|
||||
if (done) {
|
||||
bool external_completion = false;
|
||||
|
||||
if (ept_cfg->cb.received != NULL) {
|
||||
ept_cfg->cb.received(NULL, INTEL_ADSP_IPC_CB_DONE, ept_cfg->priv);
|
||||
|
||||
if (priv_data->cb_ret == INTEL_ADSP_IPC_CB_RET_EXT_COMPLETE) {
|
||||
external_completion = true;
|
||||
}
|
||||
}
|
||||
|
||||
devdata->tx_ack_pending = false;
|
||||
|
||||
/*
|
||||
* Allow the system to enter the runtime idle state after the IPC acknowledgment
|
||||
* is received.
|
||||
*/
|
||||
pm_policy_state_lock_put(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES);
|
||||
k_sem_give(&devdata->sem);
|
||||
|
||||
/* IPC completion registers will be set externally. */
|
||||
if (external_completion) {
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
return;
|
||||
}
|
||||
|
||||
regs->ida = INTEL_ADSP_IPC_DONE;
|
||||
}
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
int intel_adsp_ipc_init(const struct device *dev)
|
||||
{
|
||||
pm_device_busy_set(dev);
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
|
||||
k_sem_init(&devdata->sem, 0, 1);
|
||||
|
||||
/* ACK any latched interrupts (including TDA to clear IDA on
|
||||
* the other side!), then enable.
|
||||
*/
|
||||
config->regs->tdr = INTEL_ADSP_IPC_BUSY;
|
||||
config->regs->ida = INTEL_ADSP_IPC_DONE;
|
||||
#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE
|
||||
config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE;
|
||||
#else
|
||||
config->regs->tda = INTEL_ADSP_IPC_DONE;
|
||||
#endif
|
||||
config->regs->ctl |= (INTEL_ADSP_IPC_CTL_IDIE | INTEL_ADSP_IPC_CTL_TBIE);
|
||||
pm_device_busy_clear(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_adsp_ipc_register_ept(const struct device *instance, void **token,
|
||||
const struct ipc_ept_cfg *cfg)
|
||||
{
|
||||
struct intel_adsp_ipc_data *data = instance->data;
|
||||
|
||||
data->ept_cfg = cfg;
|
||||
|
||||
irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
ace_ipc_intc_unmask();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_adsp_ipc_deregister_ept(const struct device *instance, void *token)
|
||||
{
|
||||
struct intel_adsp_ipc_data *data = instance->data;
|
||||
|
||||
data->ept_cfg = NULL;
|
||||
|
||||
ace_ipc_intc_mask();
|
||||
irq_disable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ipc_complete(const struct device *dev)
|
||||
{
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
|
||||
#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE
|
||||
config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE;
|
||||
#else
|
||||
config->regs->tda = INTEL_ADSP_IPC_DONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool ipc_is_complete(const struct device *dev)
|
||||
{
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
const struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
bool not_busy = (config->regs->idr & INTEL_ADSP_IPC_BUSY) == 0;
|
||||
|
||||
return not_busy && !devdata->tx_ack_pending;
|
||||
}
|
||||
|
||||
static int ipc_send_message(const struct device *dev, uint32_t data, uint32_t ext_data)
|
||||
{
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
enum pm_device_state current_state;
|
||||
|
||||
if (pm_device_state_get(INTEL_ADSP_IPC_HOST_DEV, ¤t_state) != 0 ||
|
||||
current_state != PM_DEVICE_STATE_ACTIVE) {
|
||||
return -ESHUTDOWN;
|
||||
}
|
||||
#endif
|
||||
|
||||
pm_device_busy_set(dev);
|
||||
const struct intel_adsp_ipc_config *config = dev->config;
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
if ((config->regs->idr & INTEL_ADSP_IPC_BUSY) != 0 || devdata->tx_ack_pending) {
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
k_sem_reset(&devdata->sem);
|
||||
|
||||
/* Prevent entering runtime idle state until IPC acknowledgment is received. */
|
||||
pm_policy_state_lock_get(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES);
|
||||
|
||||
devdata->tx_ack_pending = true;
|
||||
|
||||
config->regs->idd = ext_data;
|
||||
config->regs->idr = data | INTEL_ADSP_IPC_BUSY;
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
|
||||
pm_device_busy_clear(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipc_send_message_sync(const struct device *dev, uint32_t data, uint32_t ext_data,
|
||||
k_timeout_t timeout)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
|
||||
int ret = ipc_send_message(dev, data, ext_data);
|
||||
|
||||
if (ret == 0) {
|
||||
k_sem_take(&devdata->sem, timeout);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ipc_send_message_emergency(const struct device *dev, uint32_t data, uint32_t ext_data)
|
||||
{
|
||||
const struct intel_adsp_ipc_config *const config = dev->config;
|
||||
|
||||
volatile struct intel_adsp_ipc *const regs = config->regs;
|
||||
bool done;
|
||||
|
||||
/* check if host is processing message. */
|
||||
while (regs->idr & INTEL_ADSP_IPC_BUSY) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
/* check if host has pending acknowledge msg
|
||||
* Same signal, but on different bits in 1.5
|
||||
*/
|
||||
done = regs->ida & INTEL_ADSP_IPC_DONE;
|
||||
if (done) {
|
||||
/* IPC completion */
|
||||
regs->ida = INTEL_ADSP_IPC_DONE;
|
||||
}
|
||||
|
||||
regs->idd = ext_data;
|
||||
regs->idr = data | INTEL_ADSP_IPC_BUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send an IPC message.
|
||||
*
|
||||
* This implements the inner working of ipc_service_send().
|
||||
*
|
||||
* @note Arguments @a data and @a len are not used to point to a byte buffer of data
|
||||
* to be sent. Instead, @a data must point to a descriptor of data to be sent,
|
||||
* struct intel_adsp_ipc_msg. And @a len indicates what type of message to send
|
||||
* as described in enum intel_adsp_send_len.
|
||||
*
|
||||
* Return values for various message types:
|
||||
* - For INTEL_ADSP_IPC_SEND_MSG_*, returns 0 when message is sent. Negative errno if
|
||||
* errors.
|
||||
* - For INTEL_ADSP_IPC_SEND_DONE, always returns 0 for sending DONE message.
|
||||
* - For INTEL_ADSP_IPC_SEND_IS_COMPLETE, returns 0 if host has processed the message.
|
||||
* -EAGAIN if not.
|
||||
*
|
||||
* @param[in] dev Pointer to device struct.
|
||||
* @param[in] token Backend-specific token.
|
||||
* @param[in] data Descriptor of IPC message to be sent (as struct intel_adsp_ipc_msg).
|
||||
* @param[in] len Type of message to be sent (described in enum intel_adsp_send_len).
|
||||
*
|
||||
* @return 0 if message is sent successfully or query returns okay.
|
||||
* Negative errno otherwise.
|
||||
*/
|
||||
static int intel_adsp_ipc_send(const struct device *dev, void *token, const void *data, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
const struct intel_adsp_ipc_msg *msg = (const struct intel_adsp_ipc_msg *)data;
|
||||
|
||||
switch (len) {
|
||||
case INTEL_ADSP_IPC_SEND_MSG: {
|
||||
ret = ipc_send_message(dev, msg->data, msg->ext_data);
|
||||
|
||||
break;
|
||||
}
|
||||
case INTEL_ADSP_IPC_SEND_MSG_SYNC: {
|
||||
ret = ipc_send_message_sync(dev, msg->data, msg->ext_data, msg->timeout);
|
||||
|
||||
break;
|
||||
}
|
||||
case INTEL_ADSP_IPC_SEND_MSG_EMERGENCY: {
|
||||
ret = ipc_send_message_emergency(dev, msg->data, msg->ext_data);
|
||||
|
||||
break;
|
||||
}
|
||||
case INTEL_ADSP_IPC_SEND_DONE: {
|
||||
ipc_complete(dev);
|
||||
|
||||
ret = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case INTEL_ADSP_IPC_SEND_IS_COMPLETE: {
|
||||
bool completed = ipc_is_complete(dev);
|
||||
|
||||
if (completed) {
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = -EAGAIN;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = -EBADMSG;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int intel_adsp_ipc_dt_init(const struct device *dev)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
|
||||
memset(devdata, 0, sizeof(*devdata));
|
||||
|
||||
IRQ_CONNECT(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE), 0, intel_adsp_ipc_isr,
|
||||
INTEL_ADSP_IPC_HOST_DEV, 0);
|
||||
|
||||
return intel_adsp_ipc_init(dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
|
||||
void intel_adsp_ipc_set_resume_handler(const struct device *dev, intel_adsp_ipc_resume_handler_t fn,
|
||||
void *arg)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
devdata->resume_fn = fn;
|
||||
devdata->resume_fn_args = arg;
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
void intel_adsp_ipc_set_suspend_handler(const struct device *dev,
|
||||
intel_adsp_ipc_suspend_handler_t fn, void *arg)
|
||||
{
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&devdata->lock);
|
||||
|
||||
devdata->suspend_fn = fn;
|
||||
devdata->suspend_fn_args = arg;
|
||||
|
||||
k_spin_unlock(&devdata->lock, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Manages IPC driver power state change.
|
||||
*
|
||||
* @param dev IPC device.
|
||||
* @param action Power state to be changed to.
|
||||
*
|
||||
* @return int Returns 0 on success or optionaly error code from the
|
||||
* registered ipc_power_control_api callbacks.
|
||||
*
|
||||
* @note PM lock is taken at the start of each power transition to prevent concurrent calls
|
||||
* to @ref pm_device_action_run function.
|
||||
* If IPC Device performs hardware operation and device is busy (what should not happen)
|
||||
* function returns failure. It is API user responsibility to make sure we are not entering
|
||||
* device power transition while device is busy.
|
||||
*/
|
||||
static int ipc_pm_action(const struct device *dev, enum pm_device_action action)
|
||||
{
|
||||
if (pm_device_is_busy(INTEL_ADSP_IPC_HOST_DEV)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
struct intel_adsp_ipc_data *devdata = dev->data;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
switch (action) {
|
||||
case PM_DEVICE_ACTION_SUSPEND:
|
||||
if (devdata->suspend_fn) {
|
||||
ret = devdata->suspend_fn(dev, devdata->suspend_fn_args);
|
||||
if (!ret) {
|
||||
irq_disable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PM_DEVICE_ACTION_RESUME:
|
||||
irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE));
|
||||
if (!irq_is_enabled(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE))) {
|
||||
ret = -EINTR;
|
||||
break;
|
||||
}
|
||||
ace_ipc_intc_unmask();
|
||||
ret = intel_adsp_ipc_init(dev);
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
if (devdata->resume_fn) {
|
||||
ret = devdata->resume_fn(dev, devdata->resume_fn_args);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Return as default value when given PM action is not supported */
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PM_DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, ipc_pm_action);
|
||||
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
static const struct intel_adsp_ipc_config ipc_host_config = {
|
||||
.regs = (void *)INTEL_ADSP_IPC_REG_ADDRESS,
|
||||
};
|
||||
|
||||
static struct intel_adsp_ipc_data ipc_host_data;
|
||||
|
||||
const static struct ipc_service_backend intel_adsp_ipc_backend_api = {
|
||||
.send = intel_adsp_ipc_send,
|
||||
.register_endpoint = intel_adsp_ipc_register_ept,
|
||||
.deregister_endpoint = intel_adsp_ipc_deregister_ept,
|
||||
};
|
||||
|
||||
DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, intel_adsp_ipc_dt_init,
|
||||
PM_DEVICE_DT_GET(INTEL_ADSP_IPC_HOST_DTNODE), &ipc_host_data, &ipc_host_config,
|
||||
PRE_KERNEL_2, 0, &intel_adsp_ipc_backend_api);
|
||||
Reference in New Issue
Block a user