| /* Copyright (c) 2022 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> |
| |
| |
| 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); |
| |
| devdata->handle_message = fn; |
| devdata->handler_arg = arg; |
| 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) |
| { |
| struct intel_adsp_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_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; |
| |
| 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 (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); |
| } |
| |
| regs->tdr = INTEL_ADSP_IPC_BUSY; |
| if (done && !IS_ENABLED(CONFIG_SOC_INTEL_CAVS_V15)) { |
| #ifdef CONFIG_SOC_SERIES_INTEL_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 = IS_ENABLED(CONFIG_SOC_INTEL_CAVS_V15) ? |
| (regs->idd & INTEL_ADSP_IPC_DONE) : (regs->ida & INTEL_ADSP_IPC_DONE); |
| |
| if (done) { |
| bool external_completion = false; |
| |
| if (devdata->done_notify != NULL) { |
| external_completion = devdata->done_notify(dev, devdata->done_arg); |
| } |
| devdata->tx_ack_pending = false; |
| k_sem_give(&devdata->sem); |
| |
| /* IPC completion registers will be set externally */ |
| if (external_completion) { |
| k_spin_unlock(&devdata->lock, key); |
| return; |
| } |
| |
| if (IS_ENABLED(CONFIG_SOC_INTEL_CAVS_V15)) { |
| regs->idd = INTEL_ADSP_IPC_DONE; |
| } else { |
| regs->ida = INTEL_ADSP_IPC_DONE; |
| } |
| } |
| |
| k_spin_unlock(&devdata->lock, key); |
| } |
| |
| int intel_adsp_ipc_init(const struct device *dev) |
| { |
| struct intel_adsp_ipc_data *devdata = dev->data; |
| const struct intel_adsp_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 = INTEL_ADSP_IPC_BUSY; |
| if (IS_ENABLED(CONFIG_SOC_INTEL_CAVS_V15)) { |
| config->regs->idd = INTEL_ADSP_IPC_DONE; |
| } else { |
| config->regs->ida = INTEL_ADSP_IPC_DONE; |
| #ifdef CONFIG_SOC_SERIES_INTEL_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); |
| return 0; |
| } |
| |
| void intel_adsp_ipc_complete(const struct device *dev) |
| { |
| const struct intel_adsp_ipc_config *config = dev->config; |
| |
| #ifdef CONFIG_SOC_SERIES_INTEL_ACE |
| config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; |
| #else |
| config->regs->tda = INTEL_ADSP_IPC_DONE; |
| #endif |
| } |
| |
| bool intel_adsp_ipc_is_complete(const struct device *dev) |
| { |
| const struct intel_adsp_ipc_config *config = dev->config; |
| |
| return (config->regs->idr & INTEL_ADSP_IPC_BUSY) == 0; |
| } |
| |
| bool intel_adsp_ipc_send_message(const struct device *dev, |
| uint32_t data, uint32_t ext_data) |
| { |
| 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 false; |
| } |
| |
| k_sem_init(&devdata->sem, 0, 1); |
| devdata->tx_ack_pending = true; |
| config->regs->idd = ext_data; |
| config->regs->idr = data | INTEL_ADSP_IPC_BUSY; |
| k_spin_unlock(&devdata->lock, key); |
| return true; |
| } |
| |
| bool 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; |
| |
| bool ret = intel_adsp_ipc_send_message(dev, data, ext_data); |
| |
| if (ret) { |
| k_sem_take(&devdata->sem, timeout); |
| } |
| return ret; |
| } |
| |
| #if DT_NODE_EXISTS(INTEL_ADSP_IPC_HOST_DTNODE) |
| |
| #if defined(CONFIG_SOC_SERIES_INTEL_ACE) |
| static inline void ace_ipc_intc_unmask(void) |
| { |
| ACE_DINT[0].ie[ACE_INTL_HIPC] = BIT(0); |
| } |
| #else |
| static inline void ace_ipc_intc_unmask(void) {} |
| #endif |
| |
| static int dt_init(const struct device *dev) |
| { |
| IRQ_CONNECT(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE), 0, z_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); |
| } |
| |
| 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, NULL, &ipc_host_data, &ipc_host_config, |
| PRE_KERNEL_2, 0, NULL); |
| #endif |