1 /* Copyright 2023 The ChromiumOS Authors
2  * SPDX-License-Identifier: Apache-2.0
3  */
4 
5 #include <zephyr/device.h>
6 #include <zephyr/irq.h>
7 #include <zephyr/devicetree.h>
8 #include <soc.h>
9 
10 #define DT_DRV_COMPAT mediatek_mbox
11 
12 /* Mailbox: a simple interrupt source.  Each direction has a 5-bit
13  * command register and will latch an interrupt if any of the bits are
14  * non-zero.  The interrupt bits get cleared/acknowledged by writing
15  * ones to the corresponding bits of "cmd_clear".  There are five
16  * scratch registers for use as message data in each direction.
17  *
18  * The same device is mapped at the same address by the host and DSP,
19  * and the naming is from the perspective of the DSP: the "in"
20  * registers control interrupts on the DSP, the "out" registers are
21  * for transmitting data to the host.
22  *
23  * There is an array of the devices.  Linux's device-tree defines two.
24  * SOF uses those for IPC, but also implements platform_trace_point()
25  * using the third (no linux driver though?).  The upstream headers
26  * list interrupts for FIVE, and indeed those all seem to be present
27  * and working.
28  *
29  * In practice: The first device (mbox0) is for IPC commands in both
30  * directions.  The cmd register is written with a 1 ("IPI_OP_REQ")
31  * and the command is placed in shared DRAM.
32  *
33  * Note that the 8195 has ten "msg" registers.  These only exist on
34  * this version of the hardware, and act as uninspected r/w scratch
35  * registers to both the host and DSP, with no other behavior.  They
36  * aren't used by the Linux kernel at all (which has a single driver
37  * for all these DSPs), so are described here for completeness but
38  * otherwise ignored.  In practice they don't do anything that simple
39  * shared memory can't.
40  */
41 
42 struct mtk_mbox {
43 #ifdef SOC_SERIES_MT8195
44 	uint32_t in_cmd;
45 	uint32_t in_cmd_clr;
46 	uint32_t in_msg[5];
47 	uint32_t out_cmd;
48 	uint32_t out_cmd_clr;
49 	uint32_t out_msg[5];
50 #else
51 	uint32_t in_cmd;
52 	uint32_t out_cmd;
53 	uint32_t in_cmd_clr;
54 	uint32_t out_cmd_clr;
55 #endif
56 };
57 
58 struct mbox_cfg {
59 	volatile struct mtk_mbox *mbox;
60 	uint32_t irq;
61 };
62 
63 struct mbox_data {
64 	mtk_adsp_mbox_handler_t handlers[MTK_ADSP_MBOX_CHANNELS];
65 	void *handler_arg[MTK_ADSP_MBOX_CHANNELS];
66 };
67 
mtk_adsp_mbox_set_handler(const struct device * mbox,uint32_t chan,mtk_adsp_mbox_handler_t handler,void * arg)68 void mtk_adsp_mbox_set_handler(const struct device *mbox, uint32_t chan,
69 			       mtk_adsp_mbox_handler_t handler, void *arg)
70 {
71 	struct mbox_data *data = ((struct device *)mbox)->data;
72 
73 	if (chan < MTK_ADSP_MBOX_CHANNELS) {
74 		data->handlers[chan] = handler;
75 		data->handler_arg[chan] = arg;
76 	}
77 }
78 
mtk_adsp_mbox_signal(const struct device * mbox,uint32_t chan)79 void mtk_adsp_mbox_signal(const struct device *mbox, uint32_t chan)
80 {
81 	const struct mbox_cfg *cfg = ((struct device *)mbox)->config;
82 
83 	if (chan < MTK_ADSP_MBOX_CHANNELS) {
84 		cfg->mbox->out_cmd |= BIT(chan);
85 	}
86 }
87 
mbox_isr(const void * arg)88 static void mbox_isr(const void *arg)
89 {
90 	const struct mbox_cfg *cfg = ((struct device *)arg)->config;
91 	struct mbox_data *data = ((struct device *)arg)->data;
92 
93 	for (int i = 0; i < MTK_ADSP_MBOX_CHANNELS; i++) {
94 		if (cfg->mbox->in_cmd & BIT(i)) {
95 			if (data->handlers[i] != NULL) {
96 				data->handlers[i](arg, data->handler_arg[i]);
97 			}
98 		}
99 	}
100 
101 	cfg->mbox->in_cmd_clr = cfg->mbox->in_cmd; /* ACK */
102 }
103 
104 #define DEF_IRQ(N)							\
105 	{ IRQ_CONNECT(DT_INST_IRQN(N), 0, mbox_isr, DEVICE_DT_INST_GET(N), 0); \
106 	  irq_enable(DT_INST_IRQN(N)); }
107 
108 
mbox_init(void)109 static int mbox_init(void)
110 {
111 	DT_INST_FOREACH_STATUS_OKAY(DEF_IRQ);
112 	return 0;
113 }
114 
115 SYS_INIT(mbox_init, POST_KERNEL, 0);
116 
117 #define DEF_DEV(N)							\
118 	static struct mbox_data dev_data##N;				\
119 	static const struct mbox_cfg dev_cfg##N =			\
120 		{ .irq  = DT_INST_IRQN(N), .mbox = (void *)DT_INST_REG_ADDR(N), }; \
121 	DEVICE_DT_INST_DEFINE(N, NULL, NULL, &dev_data##N, &dev_cfg##N,	\
122 			      POST_KERNEL, 0, NULL);
123 
124 DT_INST_FOREACH_STATUS_OKAY(DEF_DEV)
125