forked from torvalds/linux
Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
mailbox: add driver for Apple ASC mailboxes
Apple ASC mailboxes are used on their M1 SoC to communicate with various co-processors. They are backed by a single hardware FIFO which can buffer up to eight 96bit messages.
- Loading branch information
Showing
4 changed files
with
293 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-only | ||
| /* | ||
| * Apple ASC mailbox driver | ||
| * | ||
| * Copyright (C) 2021 The Asahi Linux Contributors | ||
| * | ||
| * This mailbox hardware consists of two FIFOs used to exchange 64+32 bit | ||
| * messages between the main CPU and a co-processor. Multiple instances | ||
| * of this mailbox can be found on Apple SoCs. | ||
| * Various clients 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 <linux/apple-mailbox.h> | ||
| #include <linux/clk.h> | ||
| #include <linux/device.h> | ||
| #include <linux/gfp.h> | ||
| #include <linux/interrupt.h> | ||
| #include <linux/io.h> | ||
| #include <linux/lockdep.h> | ||
| #include <linux/mailbox_controller.h> | ||
| #include <linux/module.h> | ||
| #include <linux/of.h> | ||
| #include <linux/platform_device.h> | ||
| #include <linux/types.h> | ||
|
|
||
| #define APPLE_ASC_MBOX_A2I_CONTROL 0x110 | ||
| #define APPLE_ASC_MBOX_I2A_CONTROL 0x114 | ||
| #define APPLE_ASC_MBOX_CONTROL_FULL BIT(16) | ||
| #define APPLE_ASC_MBOX_CONTROL_EMPTY BIT(17) | ||
|
|
||
| #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_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_ASC_MBOX_MSG1_OUTCNT GENMASK(56, 52) | ||
| #define APPLE_ASC_MBOX_MSG1_INCNT GENMASK(51, 48) | ||
| #define APPLE_ASC_MBOX_MSG1_OUTPTR GENMASK(47, 44) | ||
| #define APPLE_ASC_MBOX_MSG1_INPTR GENMASK(43, 40) | ||
| #define APPLE_ASC_MBOX_MSG1_MSG GENMASK(31, 0) | ||
|
|
||
| struct apple_mbox { | ||
| void __iomem *regs; | ||
| int irq_recv_not_empty; | ||
| int irq_send_empty; | ||
|
|
||
| struct clk_bulk_data *clks; | ||
| int num_clks; | ||
|
|
||
| struct mbox_chan chan; | ||
|
|
||
| struct device *dev; | ||
| struct mbox_controller controller; | ||
| }; | ||
|
|
||
| static bool apple_mbox_hw_can_send(struct apple_mbox *apple_mbox) | ||
| { | ||
| u32 mbox_ctrl = | ||
| readl_relaxed(apple_mbox->regs + APPLE_ASC_MBOX_A2I_CONTROL); | ||
|
|
||
| return !(mbox_ctrl & APPLE_ASC_MBOX_CONTROL_FULL); | ||
| } | ||
|
|
||
| static void apple_mbox_hw_send(struct apple_mbox *apple_mbox, | ||
| struct apple_mbox_msg *msg) | ||
| { | ||
| WARN_ON(!apple_mbox_hw_can_send(apple_mbox)); | ||
| writeq_relaxed(msg->msg0, apple_mbox->regs + APPLE_ASC_MBOX_A2I_SEND0); | ||
|
|
||
| /* | ||
| * we need a DMA barrier here since this message might be related to | ||
| * data stored in a shared memory buffer. | ||
| */ | ||
| writeq(FIELD_PREP(APPLE_ASC_MBOX_MSG1_MSG, msg->msg1), | ||
| apple_mbox->regs + APPLE_ASC_MBOX_A2I_SEND1); | ||
| } | ||
|
|
||
| static bool apple_mbox_hw_can_recv(struct apple_mbox *apple_mbox) | ||
| { | ||
| u32 mbox_ctrl = | ||
| readl_relaxed(apple_mbox->regs + APPLE_ASC_MBOX_I2A_CONTROL); | ||
|
|
||
| return !(mbox_ctrl & APPLE_ASC_MBOX_CONTROL_EMPTY); | ||
| } | ||
|
|
||
| static void apple_mbox_hw_recv(struct apple_mbox *apple_mbox, | ||
| struct apple_mbox_msg *msg) | ||
| { | ||
| WARN_ON(!apple_mbox_hw_can_recv(apple_mbox)); | ||
| msg->msg0 = readq_relaxed(apple_mbox->regs + APPLE_ASC_MBOX_I2A_RECV0); | ||
|
|
||
| /* | ||
| * we need a DMA barrier here since this message might be related to | ||
| * data stored in a shared memory buffer. | ||
| */ | ||
| msg->msg1 = | ||
| FIELD_GET(APPLE_ASC_MBOX_MSG1_MSG, | ||
| readq(apple_mbox->regs + APPLE_ASC_MBOX_I2A_RECV1)); | ||
| } | ||
|
|
||
| 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; | ||
|
|
||
| if (!apple_mbox_hw_can_send(apple_mbox)) | ||
| return -EBUSY; | ||
| apple_mbox_hw_send(apple_mbox, msg); | ||
| return 0; | ||
| } | ||
|
|
||
| static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data) | ||
| { | ||
| struct apple_mbox *apple_mbox = data; | ||
|
|
||
| disable_irq_nosync(apple_mbox->irq_send_empty); | ||
| mbox_chan_txready(&apple_mbox->chan); | ||
| 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_can_recv(apple_mbox)) { | ||
| apple_mbox_hw_recv(apple_mbox, &msg); | ||
| mbox_chan_received_data(&apple_mbox->chan, (void *)&msg); | ||
| } | ||
|
|
||
| return IRQ_HANDLED; | ||
| } | ||
|
|
||
| static struct mbox_chan *apple_mbox_of_xlate(struct mbox_controller *mbox, | ||
| const struct of_phandle_args *spec) | ||
| { | ||
| struct apple_mbox *apple_mbox = dev_get_drvdata(mbox->dev); | ||
|
|
||
| if (spec->args_count != 0) | ||
| return ERR_PTR(-EINVAL); | ||
| if (apple_mbox->chan.con_priv) | ||
| return ERR_PTR(-EBUSY); | ||
|
|
||
| apple_mbox->chan.con_priv = apple_mbox; | ||
| return &apple_mbox->chan; | ||
| } | ||
|
|
||
| static int apple_mbox_chan_startup(struct mbox_chan *chan) | ||
| { | ||
| struct apple_mbox *apple_mbox = chan->con_priv; | ||
| 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 void apple_mbox_chan_request_irq(struct mbox_chan *chan) | ||
| { | ||
| struct apple_mbox *apple_mbox = chan->con_priv; | ||
| enable_irq(apple_mbox->irq_send_empty); | ||
| } | ||
|
|
||
| static const struct mbox_chan_ops apple_mbox_ops = { | ||
| .send_data = apple_mbox_chan_send_data, | ||
| .request_irq = apple_mbox_chan_request_irq, | ||
| .startup = apple_mbox_chan_startup, | ||
| .shutdown = apple_mbox_chan_shutdown, | ||
| }; | ||
|
|
||
| static int apple_mbox_probe(struct platform_device *pdev) | ||
| { | ||
| int ret; | ||
| struct apple_mbox *mbox; | ||
| struct device *dev = &pdev->dev; | ||
|
|
||
| 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->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; | ||
|
|
||
| ret = devm_clk_bulk_get_all(dev, &mbox->clks); | ||
| if (ret) | ||
| return ret; | ||
| mbox->num_clks = ret; | ||
|
|
||
| ret = clk_bulk_prepare_enable(mbox->num_clks, mbox->clks); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| mbox->controller.dev = mbox->dev; | ||
| mbox->controller.num_chans = 1; | ||
| mbox->controller.chans = &mbox->chan; | ||
| mbox->controller.ops = &apple_mbox_ops; | ||
| mbox->controller.of_xlate = &apple_mbox_of_xlate; | ||
| mbox->controller.txdone_fifo = true; | ||
|
|
||
| ret = devm_request_irq(dev, mbox->irq_recv_not_empty, | ||
| apple_mbox_recv_irq, IRQF_NO_AUTOEN, | ||
| dev_name(dev), mbox); | ||
| if (ret) | ||
| goto err_clk_disable; | ||
| ret = devm_request_irq(dev, mbox->irq_send_empty, | ||
| apple_mbox_send_empty_irq, IRQF_NO_AUTOEN, | ||
| dev_name(dev), mbox); | ||
| if (ret) | ||
| goto err_clk_disable; | ||
|
|
||
| ret = devm_mbox_controller_register(dev, &mbox->controller); | ||
| if (ret) | ||
| goto err_clk_disable; | ||
| return ret; | ||
|
|
||
| err_clk_disable: | ||
| clk_bulk_disable_unprepare(mbox->num_clks, mbox->clks); | ||
| return ret; | ||
| } | ||
|
|
||
| static const struct of_device_id apple_mbox_of_match[] = { | ||
| { .compatible = "apple,t8103-asc-mailbox", }, | ||
| {} | ||
| }; | ||
| MODULE_DEVICE_TABLE(of, apple_mbox_of_match); | ||
|
|
||
| static struct platform_driver apple_mbox_driver = { | ||
| .driver = { | ||
| .name = "apple-asc-mailbox", | ||
| .of_match_table = apple_mbox_of_match, | ||
| }, | ||
| .probe = apple_mbox_probe, | ||
| }; | ||
| module_platform_driver(apple_mbox_driver); | ||
|
|
||
| MODULE_LICENSE("GPL v2"); | ||
| MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); | ||
| MODULE_DESCRIPTION("Apple ASC Mailbox driver"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| /* | ||
| * Apple mailbox message format | ||
| * | ||
| * Copyright (C) 2021 The Asahi Linux Contributors | ||
| */ | ||
|
|
||
| #ifndef _LINUX_APPLE_MAILBOX_H_ | ||
| #define _LINUX_APPLE_MAILBOX_H_ | ||
|
|
||
| #include <linux/types.h> | ||
|
|
||
| struct apple_mbox_msg { | ||
| u64 msg0; | ||
| u32 msg1; | ||
| }; | ||
|
|
||
| #endif |