// SPDX-License-Identifier: GPL-2.0-only OR MIT /* * Apple mailbox driver * * Copyright (C) 2021 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. */ #include #include #include #include #include #include #include #include #include #include #define APPLE_ASC_MBOX_CONTROL_FULL BIT(16) #define APPLE_ASC_MBOX_CONTROL_EMPTY BIT(17) #define APPLE_ASC_MBOX_A2I_CONTROL 0x110 #define APPLE_ASC_MBOX_A2I_SEND0 0x800 #define APPLE_ASC_MBOX_A2I_SEND1 0x808 #define APPLE_ASC_MBOX_A2I_RECV0 0x810 #define APPLE_ASC_MBOX_A2I_RECV1 0x818 #define APPLE_ASC_MBOX_I2A_CONTROL 0x114 #define APPLE_ASC_MBOX_I2A_SEND0 0x820 #define APPLE_ASC_MBOX_I2A_SEND1 0x828 #define APPLE_ASC_MBOX_I2A_RECV0 0x830 #define APPLE_ASC_MBOX_I2A_RECV1 0x838 #define APPLE_M3_MBOX_CONTROL_FULL BIT(16) #define APPLE_M3_MBOX_CONTROL_EMPTY BIT(17) #define APPLE_M3_MBOX_A2I_CONTROL 0x50 #define APPLE_M3_MBOX_A2I_SEND0 0x60 #define APPLE_M3_MBOX_A2I_SEND1 0x68 #define APPLE_M3_MBOX_A2I_RECV0 0x70 #define APPLE_M3_MBOX_A2I_RECV1 0x78 #define APPLE_M3_MBOX_I2A_CONTROL 0x80 #define APPLE_M3_MBOX_I2A_SEND0 0x90 #define APPLE_M3_MBOX_I2A_SEND1 0x98 #define APPLE_M3_MBOX_I2A_RECV0 0xa0 #define APPLE_M3_MBOX_I2A_RECV1 0xa8 #define APPLE_M3_MBOX_IRQ_ENABLE 0x48 #define APPLE_M3_MBOX_IRQ_ACK 0x4c #define APPLE_M3_MBOX_IRQ_A2I_EMPTY BIT(0) #define APPLE_M3_MBOX_IRQ_A2I_NOT_EMPTY BIT(1) #define APPLE_M3_MBOX_IRQ_I2A_EMPTY BIT(2) #define APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY BIT(3) #define APPLE_MBOX_MSG1_OUTCNT GENMASK(56, 52) #define APPLE_MBOX_MSG1_INCNT GENMASK(51, 48) #define APPLE_MBOX_MSG1_OUTPTR GENMASK(47, 44) #define APPLE_MBOX_MSG1_INPTR GENMASK(43, 40) #define APPLE_MBOX_MSG1_MSG GENMASK(31, 0) struct apple_mbox_hw { unsigned int control_full; unsigned int control_empty; unsigned int a2i_control; unsigned int a2i_send0; unsigned int a2i_send1; unsigned int i2a_control; unsigned int i2a_recv0; unsigned int i2a_recv1; bool has_irq_controls; unsigned int irq_enable; unsigned int irq_ack; unsigned int irq_bit_recv_not_empty; unsigned int irq_bit_send_empty; }; struct apple_mbox { void __iomem *regs; const struct apple_mbox_hw *hw; int irq_recv_not_empty; int irq_send_empty; struct mbox_chan chan; struct device *dev; struct mbox_controller controller; }; static const struct of_device_id apple_mbox_of_match[]; static bool apple_mbox_hw_can_send(struct apple_mbox *apple_mbox) { u32 mbox_ctrl = readl_relaxed(apple_mbox->regs + apple_mbox->hw->a2i_control); return !(mbox_ctrl & apple_mbox->hw->control_full); } static int apple_mbox_hw_send(struct apple_mbox *apple_mbox, struct apple_mbox_msg *msg) { if (!apple_mbox_hw_can_send(apple_mbox)) return -EBUSY; dev_dbg(apple_mbox->dev, "> TX %016llx %08x\n", msg->msg0, msg->msg1); writeq_relaxed(msg->msg0, apple_mbox->regs + apple_mbox->hw->a2i_send0); writeq_relaxed(FIELD_PREP(APPLE_MBOX_MSG1_MSG, msg->msg1), apple_mbox->regs + apple_mbox->hw->a2i_send1); return 0; } static bool apple_mbox_hw_can_recv(struct apple_mbox *apple_mbox) { u32 mbox_ctrl = readl_relaxed(apple_mbox->regs + apple_mbox->hw->i2a_control); return !(mbox_ctrl & apple_mbox->hw->control_empty); } static int apple_mbox_hw_recv(struct apple_mbox *apple_mbox, struct apple_mbox_msg *msg) { if (!apple_mbox_hw_can_recv(apple_mbox)) return -ENOMSG; msg->msg0 = readq_relaxed(apple_mbox->regs + apple_mbox->hw->i2a_recv0); msg->msg1 = FIELD_GET( APPLE_MBOX_MSG1_MSG, readq_relaxed(apple_mbox->regs + apple_mbox->hw->i2a_recv1)); dev_dbg(apple_mbox->dev, "< RX %016llx %08x\n", msg->msg0, msg->msg1); return 0; } static int apple_mbox_chan_send_data(struct mbox_chan *chan, void *data) { struct apple_mbox *apple_mbox = chan->con_priv; struct apple_mbox_msg *msg = data; int ret; ret = apple_mbox_hw_send(apple_mbox, msg); if (ret) return ret; /* * 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 (apple_mbox->hw->has_irq_controls) { writel_relaxed(apple_mbox->hw->irq_bit_send_empty, apple_mbox->regs + apple_mbox->hw->irq_ack); } enable_irq(apple_mbox->irq_send_empty); return 0; } static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data) { struct apple_mbox *apple_mbox = data; /* * 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_chan_send_data will acknowledge it before enabling * it at the main controller again. */ disable_irq_nosync(apple_mbox->irq_send_empty); mbox_chan_txdone(&apple_mbox->chan, 0); return IRQ_HANDLED; } static irqreturn_t apple_mbox_recv_irq(int irq, void *data) { struct apple_mbox *apple_mbox = data; struct apple_mbox_msg msg; while (apple_mbox_hw_recv(apple_mbox, &msg) == 0) mbox_chan_received_data(&apple_mbox->chan, (void *)&msg); /* * 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 (apple_mbox->hw->has_irq_controls) { writel_relaxed(apple_mbox->hw->irq_bit_recv_not_empty, apple_mbox->regs + apple_mbox->hw->irq_ack); } return IRQ_HANDLED; } static int apple_mbox_chan_startup(struct mbox_chan *chan) { struct apple_mbox *apple_mbox = chan->con_priv; /* * 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 (apple_mbox->hw->has_irq_controls) { writel_relaxed(apple_mbox->hw->irq_bit_recv_not_empty | apple_mbox->hw->irq_bit_send_empty, apple_mbox->regs + apple_mbox->hw->irq_enable); } enable_irq(apple_mbox->irq_recv_not_empty); return 0; } static void apple_mbox_chan_shutdown(struct mbox_chan *chan) { struct apple_mbox *apple_mbox = chan->con_priv; disable_irq(apple_mbox->irq_recv_not_empty); } static const struct mbox_chan_ops apple_mbox_ops = { .send_data = apple_mbox_chan_send_data, .startup = apple_mbox_chan_startup, .shutdown = apple_mbox_chan_shutdown, }; static struct mbox_chan *apple_mbox_of_xlate(struct mbox_controller *mbox, const struct of_phandle_args *args) { if (args->args_count != 0) return ERR_PTR(-EINVAL); return &mbox->chans[0]; } static int apple_mbox_probe(struct platform_device *pdev) { int ret; const struct of_device_id *match; char *irqname; struct apple_mbox *mbox; struct device *dev = &pdev->dev; match = of_match_node(apple_mbox_of_match, pdev->dev.of_node); if (!match) return -EINVAL; if (!match->data) return -EINVAL; mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); if (!mbox) return -ENOMEM; platform_set_drvdata(pdev, mbox); mbox->dev = dev; mbox->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(mbox->regs)) return PTR_ERR(mbox->regs); mbox->hw = match->data; mbox->irq_recv_not_empty = platform_get_irq_byname(pdev, "recv-not-empty"); if (mbox->irq_recv_not_empty < 0) return -ENODEV; mbox->irq_send_empty = platform_get_irq_byname(pdev, "send-empty"); if (mbox->irq_send_empty < 0) return -ENODEV; mbox->controller.dev = mbox->dev; mbox->controller.num_chans = 1; mbox->controller.chans = &mbox->chan; mbox->controller.ops = &apple_mbox_ops; mbox->controller.txdone_irq = true; mbox->controller.of_xlate = apple_mbox_of_xlate; mbox->chan.con_priv = mbox; irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-recv", dev_name(dev)); if (!irqname) return -ENOMEM; ret = devm_request_threaded_irq(dev, mbox->irq_recv_not_empty, NULL, apple_mbox_recv_irq, IRQF_NO_AUTOEN | IRQF_ONESHOT, irqname, mbox); if (ret) return ret; irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-send", dev_name(dev)); if (!irqname) return -ENOMEM; ret = devm_request_irq(dev, mbox->irq_send_empty, apple_mbox_send_empty_irq, IRQF_NO_AUTOEN, irqname, mbox); if (ret) return ret; return devm_mbox_controller_register(dev, &mbox->controller); } static const struct apple_mbox_hw apple_mbox_asc_hw = { .control_full = APPLE_ASC_MBOX_CONTROL_FULL, .control_empty = APPLE_ASC_MBOX_CONTROL_EMPTY, .a2i_control = APPLE_ASC_MBOX_A2I_CONTROL, .a2i_send0 = APPLE_ASC_MBOX_A2I_SEND0, .a2i_send1 = APPLE_ASC_MBOX_A2I_SEND1, .i2a_control = APPLE_ASC_MBOX_I2A_CONTROL, .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0, .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1, .has_irq_controls = false, }; static const struct apple_mbox_hw apple_mbox_m3_hw = { .control_full = APPLE_M3_MBOX_CONTROL_FULL, .control_empty = APPLE_M3_MBOX_CONTROL_EMPTY, .a2i_control = APPLE_M3_MBOX_A2I_CONTROL, .a2i_send0 = APPLE_M3_MBOX_A2I_SEND0, .a2i_send1 = APPLE_M3_MBOX_A2I_SEND1, .i2a_control = APPLE_M3_MBOX_I2A_CONTROL, .i2a_recv0 = APPLE_M3_MBOX_I2A_RECV0, .i2a_recv1 = APPLE_M3_MBOX_I2A_RECV1, .has_irq_controls = true, .irq_enable = APPLE_M3_MBOX_IRQ_ENABLE, .irq_ack = APPLE_M3_MBOX_IRQ_ACK, .irq_bit_recv_not_empty = APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY, .irq_bit_send_empty = APPLE_M3_MBOX_IRQ_A2I_EMPTY, }; static const struct of_device_id apple_mbox_of_match[] = { { .compatible = "apple,t8103-asc-mailbox", .data = &apple_mbox_asc_hw }, { .compatible = "apple,t8103-m3-mailbox", .data = &apple_mbox_m3_hw }, {} }; MODULE_DEVICE_TABLE(of, apple_mbox_of_match); static struct platform_driver apple_mbox_driver = { .driver = { .name = "apple-mailbox", .of_match_table = apple_mbox_of_match, }, .probe = apple_mbox_probe, }; module_platform_driver(apple_mbox_driver); MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("Sven Peter "); MODULE_DESCRIPTION("Apple Mailbox driver");