diff --git a/dts/xtensa/intel/intel_cavs15.dtsi b/dts/xtensa/intel/intel_cavs15.dtsi index 8987da56e02..626f2e61fe1 100644 --- a/dts/xtensa/intel/intel_cavs15.dtsi +++ b/dts/xtensa/intel/intel_cavs15.dtsi @@ -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>; diff --git a/dts/xtensa/intel/intel_cavs18.dtsi b/dts/xtensa/intel/intel_cavs18.dtsi index 3c187ce785e..bc77e5e9baa 100644 --- a/dts/xtensa/intel/intel_cavs18.dtsi +++ b/dts/xtensa/intel/intel_cavs18.dtsi @@ -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>; diff --git a/dts/xtensa/intel/intel_cavs20.dtsi b/dts/xtensa/intel/intel_cavs20.dtsi index 3c187ce785e..bc77e5e9baa 100644 --- a/dts/xtensa/intel/intel_cavs20.dtsi +++ b/dts/xtensa/intel/intel_cavs20.dtsi @@ -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>; diff --git a/dts/xtensa/intel/intel_cavs25.dtsi b/dts/xtensa/intel/intel_cavs25.dtsi index 2aab5e60fbd..67f718eb50b 100644 --- a/dts/xtensa/intel/intel_cavs25.dtsi +++ b/dts/xtensa/intel/intel_cavs25.dtsi @@ -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>; diff --git a/soc/xtensa/intel_adsp/Kconfig b/soc/xtensa/intel_adsp/Kconfig index 9f30fda9812..ecfd5365647 100644 --- a/soc/xtensa/intel_adsp/Kconfig +++ b/soc/xtensa/intel_adsp/Kconfig @@ -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 diff --git a/soc/xtensa/intel_adsp/common/CMakeLists.txt b/soc/xtensa/intel_adsp/common/CMakeLists.txt index 3fad9a8380f..0d78c8a26ac 100644 --- a/soc/xtensa/intel_adsp/common/CMakeLists.txt +++ b/soc/xtensa/intel_adsp/common/CMakeLists.txt @@ -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) diff --git a/soc/xtensa/intel_adsp/common/cavs_ipc.c b/soc/xtensa/intel_adsp/common/cavs_ipc.c new file mode 100644 index 00000000000..94661bcde61 --- /dev/null +++ b/soc/xtensa/intel_adsp/common/cavs_ipc.c @@ -0,0 +1,158 @@ +/* Copyright (c) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +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 diff --git a/soc/xtensa/intel_adsp/common/include/cavs-ipc-regs.h b/soc/xtensa/intel_adsp/common/include/cavs-ipc-regs.h index 3d128ed06b3..aefbd7e2226 100644 --- a/soc/xtensa/intel_adsp/common/include/cavs-ipc-regs.h +++ b/soc/xtensa/intel_adsp/common/include/cavs-ipc-regs.h @@ -4,6 +4,8 @@ #ifndef ZEPHYR_SOC_INTEL_ADSP_CAVS_IPC_REGS_H #define ZEPHYR_SOC_INTEL_ADSP_CAVS_IPC_REGS_H +#include + /* 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 diff --git a/soc/xtensa/intel_adsp/common/include/cavs_ipc.h b/soc/xtensa/intel_adsp/common/include/cavs_ipc.h new file mode 100644 index 00000000000..ec1c650c8aa --- /dev/null +++ b/soc/xtensa/intel_adsp/common/include/cavs_ipc.h @@ -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 +#include + +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 */