1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * APM X-Gene SLIMpro MailBox Driver |
4 | * |
5 | * Copyright (c) 2015, Applied Micro Circuits Corporation |
6 | * Author: Feng Kan fkan@apm.com |
7 | */ |
8 | #include <linux/acpi.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/io.h> |
12 | #include <linux/mailbox_controller.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/spinlock.h> |
17 | |
18 | #define MBOX_CON_NAME "slimpro-mbox" |
19 | #define MBOX_REG_SET_OFFSET 0x1000 |
20 | #define MBOX_CNT 8 |
21 | #define MBOX_STATUS_AVAIL_MASK BIT(16) |
22 | #define MBOX_STATUS_ACK_MASK BIT(0) |
23 | |
24 | /* Configuration and Status Registers */ |
25 | #define REG_DB_IN 0x00 |
26 | #define REG_DB_DIN0 0x04 |
27 | #define REG_DB_DIN1 0x08 |
28 | #define REG_DB_OUT 0x10 |
29 | #define REG_DB_DOUT0 0x14 |
30 | #define REG_DB_DOUT1 0x18 |
31 | #define REG_DB_STAT 0x20 |
32 | #define REG_DB_STATMASK 0x24 |
33 | |
34 | /** |
35 | * X-Gene SlimPRO mailbox channel information |
36 | * |
37 | * @dev: Device to which it is attached |
38 | * @chan: Pointer to mailbox communication channel |
39 | * @reg: Base address to access channel registers |
40 | * @irq: Interrupt number of the channel |
41 | * @rx_msg: Received message storage |
42 | */ |
43 | struct slimpro_mbox_chan { |
44 | struct device *dev; |
45 | struct mbox_chan *chan; |
46 | void __iomem *reg; |
47 | int irq; |
48 | u32 rx_msg[3]; |
49 | }; |
50 | |
51 | /** |
52 | * X-Gene SlimPRO Mailbox controller data |
53 | * |
54 | * X-Gene SlimPRO Mailbox controller has 8 communication channels. |
55 | * Each channel has a separate IRQ number assigned to it. |
56 | * |
57 | * @mb_ctrl: Representation of the communication channel controller |
58 | * @mc: Array of SlimPRO mailbox channels of the controller |
59 | * @chans: Array of mailbox communication channels |
60 | * |
61 | */ |
62 | struct slimpro_mbox { |
63 | struct mbox_controller mb_ctrl; |
64 | struct slimpro_mbox_chan mc[MBOX_CNT]; |
65 | struct mbox_chan chans[MBOX_CNT]; |
66 | }; |
67 | |
68 | static void mb_chan_send_msg(struct slimpro_mbox_chan *mb_chan, u32 *msg) |
69 | { |
70 | writel(val: msg[1], addr: mb_chan->reg + REG_DB_DOUT0); |
71 | writel(val: msg[2], addr: mb_chan->reg + REG_DB_DOUT1); |
72 | writel(val: msg[0], addr: mb_chan->reg + REG_DB_OUT); |
73 | } |
74 | |
75 | static void mb_chan_recv_msg(struct slimpro_mbox_chan *mb_chan) |
76 | { |
77 | mb_chan->rx_msg[1] = readl(addr: mb_chan->reg + REG_DB_DIN0); |
78 | mb_chan->rx_msg[2] = readl(addr: mb_chan->reg + REG_DB_DIN1); |
79 | mb_chan->rx_msg[0] = readl(addr: mb_chan->reg + REG_DB_IN); |
80 | } |
81 | |
82 | static int mb_chan_status_ack(struct slimpro_mbox_chan *mb_chan) |
83 | { |
84 | u32 val = readl(addr: mb_chan->reg + REG_DB_STAT); |
85 | |
86 | if (val & MBOX_STATUS_ACK_MASK) { |
87 | writel(MBOX_STATUS_ACK_MASK, addr: mb_chan->reg + REG_DB_STAT); |
88 | return 1; |
89 | } |
90 | return 0; |
91 | } |
92 | |
93 | static int mb_chan_status_avail(struct slimpro_mbox_chan *mb_chan) |
94 | { |
95 | u32 val = readl(addr: mb_chan->reg + REG_DB_STAT); |
96 | |
97 | if (val & MBOX_STATUS_AVAIL_MASK) { |
98 | mb_chan_recv_msg(mb_chan); |
99 | writel(MBOX_STATUS_AVAIL_MASK, addr: mb_chan->reg + REG_DB_STAT); |
100 | return 1; |
101 | } |
102 | return 0; |
103 | } |
104 | |
105 | static irqreturn_t slimpro_mbox_irq(int irq, void *id) |
106 | { |
107 | struct slimpro_mbox_chan *mb_chan = id; |
108 | |
109 | if (mb_chan_status_ack(mb_chan)) |
110 | mbox_chan_txdone(chan: mb_chan->chan, r: 0); |
111 | |
112 | if (mb_chan_status_avail(mb_chan)) |
113 | mbox_chan_received_data(chan: mb_chan->chan, data: mb_chan->rx_msg); |
114 | |
115 | return IRQ_HANDLED; |
116 | } |
117 | |
118 | static int slimpro_mbox_send_data(struct mbox_chan *chan, void *msg) |
119 | { |
120 | struct slimpro_mbox_chan *mb_chan = chan->con_priv; |
121 | |
122 | mb_chan_send_msg(mb_chan, msg); |
123 | return 0; |
124 | } |
125 | |
126 | static int slimpro_mbox_startup(struct mbox_chan *chan) |
127 | { |
128 | struct slimpro_mbox_chan *mb_chan = chan->con_priv; |
129 | int rc; |
130 | u32 val; |
131 | |
132 | rc = devm_request_irq(dev: mb_chan->dev, irq: mb_chan->irq, handler: slimpro_mbox_irq, irqflags: 0, |
133 | MBOX_CON_NAME, dev_id: mb_chan); |
134 | if (unlikely(rc)) { |
135 | dev_err(mb_chan->dev, "failed to register mailbox interrupt %d\n" , |
136 | mb_chan->irq); |
137 | return rc; |
138 | } |
139 | |
140 | /* Enable HW interrupt */ |
141 | writel(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK, |
142 | addr: mb_chan->reg + REG_DB_STAT); |
143 | /* Unmask doorbell status interrupt */ |
144 | val = readl(addr: mb_chan->reg + REG_DB_STATMASK); |
145 | val &= ~(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK); |
146 | writel(val, addr: mb_chan->reg + REG_DB_STATMASK); |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static void slimpro_mbox_shutdown(struct mbox_chan *chan) |
152 | { |
153 | struct slimpro_mbox_chan *mb_chan = chan->con_priv; |
154 | u32 val; |
155 | |
156 | /* Mask doorbell status interrupt */ |
157 | val = readl(addr: mb_chan->reg + REG_DB_STATMASK); |
158 | val |= (MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK); |
159 | writel(val, addr: mb_chan->reg + REG_DB_STATMASK); |
160 | |
161 | devm_free_irq(dev: mb_chan->dev, irq: mb_chan->irq, dev_id: mb_chan); |
162 | } |
163 | |
164 | static const struct mbox_chan_ops slimpro_mbox_ops = { |
165 | .send_data = slimpro_mbox_send_data, |
166 | .startup = slimpro_mbox_startup, |
167 | .shutdown = slimpro_mbox_shutdown, |
168 | }; |
169 | |
170 | static int slimpro_mbox_probe(struct platform_device *pdev) |
171 | { |
172 | struct slimpro_mbox *ctx; |
173 | void __iomem *mb_base; |
174 | int rc; |
175 | int i; |
176 | |
177 | ctx = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct slimpro_mbox), GFP_KERNEL); |
178 | if (!ctx) |
179 | return -ENOMEM; |
180 | |
181 | platform_set_drvdata(pdev, data: ctx); |
182 | |
183 | mb_base = devm_platform_ioremap_resource(pdev, index: 0); |
184 | if (IS_ERR(ptr: mb_base)) |
185 | return PTR_ERR(ptr: mb_base); |
186 | |
187 | /* Setup mailbox links */ |
188 | for (i = 0; i < MBOX_CNT; i++) { |
189 | ctx->mc[i].irq = platform_get_irq(pdev, i); |
190 | if (ctx->mc[i].irq < 0) { |
191 | if (i == 0) { |
192 | dev_err(&pdev->dev, "no available IRQ\n" ); |
193 | return -EINVAL; |
194 | } |
195 | dev_info(&pdev->dev, "no IRQ for channel %d\n" , i); |
196 | break; |
197 | } |
198 | |
199 | ctx->mc[i].dev = &pdev->dev; |
200 | ctx->mc[i].reg = mb_base + i * MBOX_REG_SET_OFFSET; |
201 | ctx->mc[i].chan = &ctx->chans[i]; |
202 | ctx->chans[i].con_priv = &ctx->mc[i]; |
203 | } |
204 | |
205 | /* Setup mailbox controller */ |
206 | ctx->mb_ctrl.dev = &pdev->dev; |
207 | ctx->mb_ctrl.chans = ctx->chans; |
208 | ctx->mb_ctrl.txdone_irq = true; |
209 | ctx->mb_ctrl.ops = &slimpro_mbox_ops; |
210 | ctx->mb_ctrl.num_chans = i; |
211 | |
212 | rc = devm_mbox_controller_register(dev: &pdev->dev, mbox: &ctx->mb_ctrl); |
213 | if (rc) { |
214 | dev_err(&pdev->dev, |
215 | "APM X-Gene SLIMpro MailBox register failed:%d\n" , rc); |
216 | return rc; |
217 | } |
218 | |
219 | dev_info(&pdev->dev, "APM X-Gene SLIMpro MailBox registered\n" ); |
220 | return 0; |
221 | } |
222 | |
223 | static const struct of_device_id slimpro_of_match[] = { |
224 | {.compatible = "apm,xgene-slimpro-mbox" }, |
225 | { }, |
226 | }; |
227 | MODULE_DEVICE_TABLE(of, slimpro_of_match); |
228 | |
229 | #ifdef CONFIG_ACPI |
230 | static const struct acpi_device_id slimpro_acpi_ids[] = { |
231 | {"APMC0D01" , 0}, |
232 | {} |
233 | }; |
234 | MODULE_DEVICE_TABLE(acpi, slimpro_acpi_ids); |
235 | #endif |
236 | |
237 | static struct platform_driver slimpro_mbox_driver = { |
238 | .probe = slimpro_mbox_probe, |
239 | .driver = { |
240 | .name = "xgene-slimpro-mbox" , |
241 | .of_match_table = of_match_ptr(slimpro_of_match), |
242 | .acpi_match_table = ACPI_PTR(slimpro_acpi_ids) |
243 | }, |
244 | }; |
245 | |
246 | static int __init slimpro_mbox_init(void) |
247 | { |
248 | return platform_driver_register(&slimpro_mbox_driver); |
249 | } |
250 | |
251 | static void __exit slimpro_mbox_exit(void) |
252 | { |
253 | platform_driver_unregister(&slimpro_mbox_driver); |
254 | } |
255 | |
256 | subsys_initcall(slimpro_mbox_init); |
257 | module_exit(slimpro_mbox_exit); |
258 | |
259 | MODULE_DESCRIPTION("APM X-Gene SLIMpro Mailbox Driver" ); |
260 | MODULE_LICENSE("GPL" ); |
261 | |