// SPDX-License-Identifier: GPL-2.0-only OR MIT /* * Apple mailbox driver * * Copyright The Asahi Linux Contributors * * This driver adds support for two mailbox variants (called ASC and M3 by * Apple) found in Apple SoCs such as the M1. It consists of two FIFOs used to * exchange 64+32 bit messages between the main CPU and a co-processor. * Various coprocessors implement different IPC protocols based on these simple * messages and shared memory buffers. * * Both the main CPU and the co-processor see the same set of registers but * the first FIFO (A2I) is always used to transfer messages from the application * processor (us) to the I/O processor and the second one (I2A) for the * other direction.
*/
while (mbox_ctrl & mbox->hw->control_full) { if (atomic) {
ret = readl_poll_timeout_atomic(
mbox->regs + mbox->hw->a2i_control, mbox_ctrl,
!(mbox_ctrl & mbox->hw->control_full), 100,
APPLE_MBOX_TX_TIMEOUT * 1000);
if (ret) {
spin_unlock_irqrestore(&mbox->tx_lock, flags); return ret;
}
break;
} /* * The interrupt is level triggered and will keep firing as long as the * FIFO is empty. It will also keep firing if the FIFO was empty * at any point in the past until it has been acknowledged at the * mailbox level. By acknowledging it here we can ensure that we will * only get the interrupt once the FIFO has been cleared again. * If the FIFO is already empty before the ack it will fire again * immediately after the ack.
*/ if (mbox->hw->has_irq_controls) {
writel_relaxed(mbox->hw->irq_bit_send_empty,
mbox->regs + mbox->hw->irq_ack);
}
enable_irq(mbox->irq_send_empty);
reinit_completion(&mbox->tx_empty);
spin_unlock_irqrestore(&mbox->tx_lock, flags);
t = wait_for_completion_interruptible_timeout(
&mbox->tx_empty,
msecs_to_jiffies(APPLE_MBOX_TX_TIMEOUT)); if (t < 0) return t; elseif (t == 0) return -ETIMEDOUT;
/* * We don't need to acknowledge the interrupt at the mailbox level * here even if supported by the hardware. It will keep firing but that * doesn't matter since it's disabled at the main interrupt controller. * apple_mbox_send will acknowledge it before enabling * it at the main controller again.
*/
spin_lock(&mbox->tx_lock);
disable_irq_nosync(mbox->irq_send_empty);
complete(&mbox->tx_empty);
spin_unlock(&mbox->tx_lock);
return IRQ_HANDLED;
}
staticint apple_mbox_poll_locked(struct apple_mbox *mbox)
{ struct apple_mbox_msg msg; int ret = 0;
/* * The interrupt will keep firing even if there are no more messages * unless we also acknowledge it at the mailbox level here. * There's no race if a message comes in between the check in the while * loop above and the ack below: If a new messages arrives inbetween * those two the interrupt will just fire again immediately after the * ack since it's level triggered.
*/ if (mbox->hw->has_irq_controls) {
writel_relaxed(mbox->hw->irq_bit_recv_not_empty,
mbox->regs + mbox->hw->irq_ack);
}
int apple_mbox_poll(struct apple_mbox *mbox)
{ unsignedlong flags; int ret;
spin_lock_irqsave(&mbox->rx_lock, flags);
ret = apple_mbox_poll_locked(mbox);
spin_unlock_irqrestore(&mbox->rx_lock, flags);
return ret;
}
EXPORT_SYMBOL(apple_mbox_poll);
int apple_mbox_start(struct apple_mbox *mbox)
{ int ret;
if (mbox->active) return 0;
ret = pm_runtime_resume_and_get(mbox->dev); if (ret) return ret;
/* * Only some variants of this mailbox HW provide interrupt control * at the mailbox level. We therefore need to handle enabling/disabling * interrupts at the main interrupt controller anyway for hardware that * doesn't. Just always keep the interrupts we care about enabled at * the mailbox level so that both hardware revisions behave almost * the same.
*/ if (mbox->hw->has_irq_controls) {
writel_relaxed(mbox->hw->irq_bit_recv_not_empty |
mbox->hw->irq_bit_send_empty,
mbox->regs + mbox->hw->irq_enable);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.