]>
Commit | Line | Data |
---|---|---|
25831c44 SH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // Copyright (c) 2017-2019 Samuel Holland <[email protected]> | |
4 | ||
5 | #include <linux/bitops.h> | |
6 | #include <linux/clk.h> | |
7 | #include <linux/device.h> | |
8 | #include <linux/err.h> | |
9 | #include <linux/interrupt.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/mailbox_controller.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/of_irq.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/reset.h> | |
18 | #include <linux/spinlock.h> | |
19 | ||
20 | #define NUM_CHANS 8 | |
21 | ||
22 | #define CTRL_REG(n) (0x0000 + 0x4 * ((n) / 4)) | |
23 | #define CTRL_RX(n) BIT(0 + 8 * ((n) % 4)) | |
24 | #define CTRL_TX(n) BIT(4 + 8 * ((n) % 4)) | |
25 | ||
26 | #define REMOTE_IRQ_EN_REG 0x0040 | |
27 | #define REMOTE_IRQ_STAT_REG 0x0050 | |
28 | #define LOCAL_IRQ_EN_REG 0x0060 | |
29 | #define LOCAL_IRQ_STAT_REG 0x0070 | |
30 | ||
31 | #define RX_IRQ(n) BIT(0 + 2 * (n)) | |
32 | #define RX_IRQ_MASK 0x5555 | |
33 | #define TX_IRQ(n) BIT(1 + 2 * (n)) | |
34 | #define TX_IRQ_MASK 0xaaaa | |
35 | ||
36 | #define FIFO_STAT_REG(n) (0x0100 + 0x4 * (n)) | |
37 | #define FIFO_STAT_MASK GENMASK(0, 0) | |
38 | ||
39 | #define MSG_STAT_REG(n) (0x0140 + 0x4 * (n)) | |
40 | #define MSG_STAT_MASK GENMASK(2, 0) | |
41 | ||
42 | #define MSG_DATA_REG(n) (0x0180 + 0x4 * (n)) | |
43 | ||
44 | #define mbox_dbg(mbox, ...) dev_dbg((mbox)->controller.dev, __VA_ARGS__) | |
45 | ||
46 | struct sun6i_msgbox { | |
47 | struct mbox_controller controller; | |
48 | struct clk *clk; | |
49 | spinlock_t lock; | |
50 | void __iomem *regs; | |
51 | }; | |
52 | ||
53 | static bool sun6i_msgbox_last_tx_done(struct mbox_chan *chan); | |
54 | static bool sun6i_msgbox_peek_data(struct mbox_chan *chan); | |
55 | ||
56 | static inline int channel_number(struct mbox_chan *chan) | |
57 | { | |
58 | return chan - chan->mbox->chans; | |
59 | } | |
60 | ||
61 | static inline struct sun6i_msgbox *to_sun6i_msgbox(struct mbox_chan *chan) | |
62 | { | |
63 | return chan->con_priv; | |
64 | } | |
65 | ||
66 | static irqreturn_t sun6i_msgbox_irq(int irq, void *dev_id) | |
67 | { | |
68 | struct sun6i_msgbox *mbox = dev_id; | |
69 | uint32_t status; | |
70 | int n; | |
71 | ||
72 | /* Only examine channels that are currently enabled. */ | |
73 | status = readl(mbox->regs + LOCAL_IRQ_EN_REG) & | |
74 | readl(mbox->regs + LOCAL_IRQ_STAT_REG); | |
75 | ||
76 | if (!(status & RX_IRQ_MASK)) | |
77 | return IRQ_NONE; | |
78 | ||
79 | for (n = 0; n < NUM_CHANS; ++n) { | |
80 | struct mbox_chan *chan = &mbox->controller.chans[n]; | |
81 | ||
82 | if (!(status & RX_IRQ(n))) | |
83 | continue; | |
84 | ||
85 | while (sun6i_msgbox_peek_data(chan)) { | |
86 | uint32_t msg = readl(mbox->regs + MSG_DATA_REG(n)); | |
87 | ||
88 | mbox_dbg(mbox, "Channel %d received 0x%08x\n", n, msg); | |
89 | mbox_chan_received_data(chan, &msg); | |
90 | } | |
91 | ||
92 | /* The IRQ can be cleared only once the FIFO is empty. */ | |
93 | writel(RX_IRQ(n), mbox->regs + LOCAL_IRQ_STAT_REG); | |
94 | } | |
95 | ||
96 | return IRQ_HANDLED; | |
97 | } | |
98 | ||
99 | static int sun6i_msgbox_send_data(struct mbox_chan *chan, void *data) | |
100 | { | |
101 | struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); | |
102 | int n = channel_number(chan); | |
103 | uint32_t msg = *(uint32_t *)data; | |
104 | ||
105 | /* Using a channel backwards gets the hardware into a bad state. */ | |
106 | if (WARN_ON_ONCE(!(readl(mbox->regs + CTRL_REG(n)) & CTRL_TX(n)))) | |
107 | return 0; | |
108 | ||
109 | writel(msg, mbox->regs + MSG_DATA_REG(n)); | |
110 | mbox_dbg(mbox, "Channel %d sent 0x%08x\n", n, msg); | |
111 | ||
112 | return 0; | |
113 | } | |
114 | ||
115 | static int sun6i_msgbox_startup(struct mbox_chan *chan) | |
116 | { | |
117 | struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); | |
118 | int n = channel_number(chan); | |
119 | ||
120 | /* The coprocessor is responsible for setting channel directions. */ | |
121 | if (readl(mbox->regs + CTRL_REG(n)) & CTRL_RX(n)) { | |
122 | /* Flush the receive FIFO. */ | |
123 | while (sun6i_msgbox_peek_data(chan)) | |
124 | readl(mbox->regs + MSG_DATA_REG(n)); | |
125 | writel(RX_IRQ(n), mbox->regs + LOCAL_IRQ_STAT_REG); | |
126 | ||
127 | /* Enable the receive IRQ. */ | |
128 | spin_lock(&mbox->lock); | |
129 | writel(readl(mbox->regs + LOCAL_IRQ_EN_REG) | RX_IRQ(n), | |
130 | mbox->regs + LOCAL_IRQ_EN_REG); | |
131 | spin_unlock(&mbox->lock); | |
132 | } | |
133 | ||
134 | mbox_dbg(mbox, "Channel %d startup complete\n", n); | |
135 | ||
136 | return 0; | |
137 | } | |
138 | ||
139 | static void sun6i_msgbox_shutdown(struct mbox_chan *chan) | |
140 | { | |
141 | struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); | |
142 | int n = channel_number(chan); | |
143 | ||
144 | if (readl(mbox->regs + CTRL_REG(n)) & CTRL_RX(n)) { | |
145 | /* Disable the receive IRQ. */ | |
146 | spin_lock(&mbox->lock); | |
147 | writel(readl(mbox->regs + LOCAL_IRQ_EN_REG) & ~RX_IRQ(n), | |
148 | mbox->regs + LOCAL_IRQ_EN_REG); | |
149 | spin_unlock(&mbox->lock); | |
150 | ||
151 | /* Attempt to flush the FIFO until the IRQ is cleared. */ | |
152 | do { | |
153 | while (sun6i_msgbox_peek_data(chan)) | |
154 | readl(mbox->regs + MSG_DATA_REG(n)); | |
155 | writel(RX_IRQ(n), mbox->regs + LOCAL_IRQ_STAT_REG); | |
156 | } while (readl(mbox->regs + LOCAL_IRQ_STAT_REG) & RX_IRQ(n)); | |
157 | } | |
158 | ||
159 | mbox_dbg(mbox, "Channel %d shutdown complete\n", n); | |
160 | } | |
161 | ||
162 | static bool sun6i_msgbox_last_tx_done(struct mbox_chan *chan) | |
163 | { | |
164 | struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); | |
165 | int n = channel_number(chan); | |
166 | ||
167 | /* | |
168 | * The hardware allows snooping on the remote user's IRQ statuses. | |
169 | * We consider a message to be acknowledged only once the receive IRQ | |
170 | * for that channel is cleared. Since the receive IRQ for a channel | |
171 | * cannot be cleared until the FIFO for that channel is empty, this | |
172 | * ensures that the message has actually been read. It also gives the | |
173 | * recipient an opportunity to perform minimal processing before | |
174 | * acknowledging the message. | |
175 | */ | |
176 | return !(readl(mbox->regs + REMOTE_IRQ_STAT_REG) & RX_IRQ(n)); | |
177 | } | |
178 | ||
179 | static bool sun6i_msgbox_peek_data(struct mbox_chan *chan) | |
180 | { | |
181 | struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); | |
182 | int n = channel_number(chan); | |
183 | ||
184 | return readl(mbox->regs + MSG_STAT_REG(n)) & MSG_STAT_MASK; | |
185 | } | |
186 | ||
187 | static const struct mbox_chan_ops sun6i_msgbox_chan_ops = { | |
188 | .send_data = sun6i_msgbox_send_data, | |
189 | .startup = sun6i_msgbox_startup, | |
190 | .shutdown = sun6i_msgbox_shutdown, | |
191 | .last_tx_done = sun6i_msgbox_last_tx_done, | |
192 | .peek_data = sun6i_msgbox_peek_data, | |
193 | }; | |
194 | ||
195 | static int sun6i_msgbox_probe(struct platform_device *pdev) | |
196 | { | |
197 | struct device *dev = &pdev->dev; | |
198 | struct mbox_chan *chans; | |
199 | struct reset_control *reset; | |
25831c44 SH |
200 | struct sun6i_msgbox *mbox; |
201 | int i, ret; | |
202 | ||
203 | mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); | |
204 | if (!mbox) | |
205 | return -ENOMEM; | |
206 | ||
207 | chans = devm_kcalloc(dev, NUM_CHANS, sizeof(*chans), GFP_KERNEL); | |
208 | if (!chans) | |
209 | return -ENOMEM; | |
210 | ||
211 | for (i = 0; i < NUM_CHANS; ++i) | |
212 | chans[i].con_priv = mbox; | |
213 | ||
214 | mbox->clk = devm_clk_get(dev, NULL); | |
215 | if (IS_ERR(mbox->clk)) { | |
216 | ret = PTR_ERR(mbox->clk); | |
217 | dev_err(dev, "Failed to get clock: %d\n", ret); | |
218 | return ret; | |
219 | } | |
220 | ||
221 | ret = clk_prepare_enable(mbox->clk); | |
222 | if (ret) { | |
223 | dev_err(dev, "Failed to enable clock: %d\n", ret); | |
224 | return ret; | |
225 | } | |
226 | ||
227 | reset = devm_reset_control_get_exclusive(dev, NULL); | |
228 | if (IS_ERR(reset)) { | |
229 | ret = PTR_ERR(reset); | |
230 | dev_err(dev, "Failed to get reset control: %d\n", ret); | |
231 | goto err_disable_unprepare; | |
232 | } | |
233 | ||
234 | /* | |
235 | * NOTE: We rely on platform firmware to preconfigure the channel | |
236 | * directions, and we share this hardware block with other firmware | |
237 | * that runs concurrently with Linux (e.g. a trusted monitor). | |
238 | * | |
239 | * Therefore, we do *not* assert the reset line if probing fails or | |
240 | * when removing the device. | |
241 | */ | |
242 | ret = reset_control_deassert(reset); | |
243 | if (ret) { | |
244 | dev_err(dev, "Failed to deassert reset: %d\n", ret); | |
245 | goto err_disable_unprepare; | |
246 | } | |
247 | ||
f5e2eeb9 | 248 | mbox->regs = devm_platform_ioremap_resource(pdev, 0); |
25831c44 SH |
249 | if (IS_ERR(mbox->regs)) { |
250 | ret = PTR_ERR(mbox->regs); | |
251 | dev_err(dev, "Failed to map MMIO resource: %d\n", ret); | |
252 | goto err_disable_unprepare; | |
253 | } | |
254 | ||
255 | /* Disable all IRQs for this end of the msgbox. */ | |
256 | writel(0, mbox->regs + LOCAL_IRQ_EN_REG); | |
257 | ||
258 | ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), | |
259 | sun6i_msgbox_irq, 0, dev_name(dev), mbox); | |
260 | if (ret) { | |
261 | dev_err(dev, "Failed to register IRQ handler: %d\n", ret); | |
262 | goto err_disable_unprepare; | |
263 | } | |
264 | ||
265 | mbox->controller.dev = dev; | |
266 | mbox->controller.ops = &sun6i_msgbox_chan_ops; | |
267 | mbox->controller.chans = chans; | |
268 | mbox->controller.num_chans = NUM_CHANS; | |
269 | mbox->controller.txdone_irq = false; | |
270 | mbox->controller.txdone_poll = true; | |
271 | mbox->controller.txpoll_period = 5; | |
272 | ||
273 | spin_lock_init(&mbox->lock); | |
274 | platform_set_drvdata(pdev, mbox); | |
275 | ||
276 | ret = mbox_controller_register(&mbox->controller); | |
277 | if (ret) { | |
278 | dev_err(dev, "Failed to register controller: %d\n", ret); | |
279 | goto err_disable_unprepare; | |
280 | } | |
281 | ||
282 | return 0; | |
283 | ||
284 | err_disable_unprepare: | |
285 | clk_disable_unprepare(mbox->clk); | |
286 | ||
287 | return ret; | |
288 | } | |
289 | ||
b8e346bd | 290 | static void sun6i_msgbox_remove(struct platform_device *pdev) |
25831c44 SH |
291 | { |
292 | struct sun6i_msgbox *mbox = platform_get_drvdata(pdev); | |
293 | ||
294 | mbox_controller_unregister(&mbox->controller); | |
295 | /* See the comment in sun6i_msgbox_probe about the reset line. */ | |
296 | clk_disable_unprepare(mbox->clk); | |
25831c44 SH |
297 | } |
298 | ||
299 | static const struct of_device_id sun6i_msgbox_of_match[] = { | |
300 | { .compatible = "allwinner,sun6i-a31-msgbox", }, | |
301 | {}, | |
302 | }; | |
303 | MODULE_DEVICE_TABLE(of, sun6i_msgbox_of_match); | |
304 | ||
305 | static struct platform_driver sun6i_msgbox_driver = { | |
306 | .driver = { | |
307 | .name = "sun6i-msgbox", | |
308 | .of_match_table = sun6i_msgbox_of_match, | |
309 | }, | |
81f939db UKK |
310 | .probe = sun6i_msgbox_probe, |
311 | .remove = sun6i_msgbox_remove, | |
25831c44 SH |
312 | }; |
313 | module_platform_driver(sun6i_msgbox_driver); | |
314 | ||
315 | MODULE_AUTHOR("Samuel Holland <[email protected]>"); | |
316 | MODULE_DESCRIPTION("Allwinner sun6i/sun8i/sun9i/sun50i Message Box"); | |
317 | MODULE_LICENSE("GPL v2"); |