Commit | Line | Data |
---|---|---|
9952f691 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
f62092f6 LFT |
2 | /* |
3 | * Copyright Altera Corporation (C) 2013-2014. All rights reserved | |
f62092f6 LFT |
4 | */ |
5 | ||
6 | #include <linux/device.h> | |
7 | #include <linux/interrupt.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/kernel.h> | |
10 | #include <linux/mailbox_controller.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/of.h> | |
13 | #include <linux/platform_device.h> | |
14 | ||
15 | #define DRIVER_NAME "altera-mailbox" | |
16 | ||
17 | #define MAILBOX_CMD_REG 0x00 | |
18 | #define MAILBOX_PTR_REG 0x04 | |
19 | #define MAILBOX_STS_REG 0x08 | |
20 | #define MAILBOX_INTMASK_REG 0x0C | |
21 | ||
22 | #define INT_PENDING_MSK 0x1 | |
23 | #define INT_SPACE_MSK 0x2 | |
24 | ||
25 | #define STS_PENDING_MSK 0x1 | |
26 | #define STS_FULL_MSK 0x2 | |
27 | #define STS_FULL_OFT 0x1 | |
28 | ||
29 | #define MBOX_PENDING(status) (((status) & STS_PENDING_MSK)) | |
30 | #define MBOX_FULL(status) (((status) & STS_FULL_MSK) >> STS_FULL_OFT) | |
31 | ||
32 | enum altera_mbox_msg { | |
33 | MBOX_CMD = 0, | |
34 | MBOX_PTR, | |
35 | }; | |
36 | ||
37 | #define MBOX_POLLING_MS 5 /* polling interval 5ms */ | |
38 | ||
39 | struct altera_mbox { | |
40 | bool is_sender; /* 1-sender, 0-receiver */ | |
41 | bool intr_mode; | |
42 | int irq; | |
43 | void __iomem *mbox_base; | |
44 | struct device *dev; | |
45 | struct mbox_controller controller; | |
46 | ||
47 | /* If the controller supports only RX polling mode */ | |
48 | struct timer_list rxpoll_timer; | |
c6f15047 | 49 | struct mbox_chan *chan; |
f62092f6 LFT |
50 | }; |
51 | ||
52 | static struct altera_mbox *mbox_chan_to_altera_mbox(struct mbox_chan *chan) | |
53 | { | |
54 | if (!chan || !chan->con_priv) | |
55 | return NULL; | |
56 | ||
57 | return (struct altera_mbox *)chan->con_priv; | |
58 | } | |
59 | ||
60 | static inline int altera_mbox_full(struct altera_mbox *mbox) | |
61 | { | |
62 | u32 status; | |
63 | ||
64 | status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); | |
65 | return MBOX_FULL(status); | |
66 | } | |
67 | ||
68 | static inline int altera_mbox_pending(struct altera_mbox *mbox) | |
69 | { | |
70 | u32 status; | |
71 | ||
72 | status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); | |
73 | return MBOX_PENDING(status); | |
74 | } | |
75 | ||
76 | static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable) | |
77 | { | |
78 | u32 mask; | |
79 | ||
80 | mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); | |
81 | if (enable) | |
82 | mask |= INT_PENDING_MSK; | |
83 | else | |
84 | mask &= ~INT_PENDING_MSK; | |
85 | writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); | |
86 | } | |
87 | ||
88 | static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable) | |
89 | { | |
90 | u32 mask; | |
91 | ||
92 | mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); | |
93 | if (enable) | |
94 | mask |= INT_SPACE_MSK; | |
95 | else | |
96 | mask &= ~INT_SPACE_MSK; | |
97 | writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); | |
98 | } | |
99 | ||
100 | static bool altera_mbox_is_sender(struct altera_mbox *mbox) | |
101 | { | |
102 | u32 reg; | |
103 | /* Write a magic number to PTR register and read back this register. | |
104 | * This register is read-write if it is a sender. | |
105 | */ | |
106 | #define MBOX_MAGIC 0xA5A5AA55 | |
107 | writel_relaxed(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG); | |
108 | reg = readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); | |
109 | if (reg == MBOX_MAGIC) { | |
110 | /* Clear to 0 */ | |
111 | writel_relaxed(0, mbox->mbox_base + MAILBOX_PTR_REG); | |
112 | return true; | |
113 | } | |
114 | return false; | |
115 | } | |
116 | ||
117 | static void altera_mbox_rx_data(struct mbox_chan *chan) | |
118 | { | |
119 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
120 | u32 data[2]; | |
121 | ||
122 | if (altera_mbox_pending(mbox)) { | |
123 | data[MBOX_PTR] = | |
124 | readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); | |
125 | data[MBOX_CMD] = | |
126 | readl_relaxed(mbox->mbox_base + MAILBOX_CMD_REG); | |
127 | mbox_chan_received_data(chan, (void *)data); | |
128 | } | |
129 | } | |
130 | ||
c6f15047 | 131 | static void altera_mbox_poll_rx(struct timer_list *t) |
f62092f6 | 132 | { |
c6f15047 | 133 | struct altera_mbox *mbox = from_timer(mbox, t, rxpoll_timer); |
f62092f6 | 134 | |
c6f15047 | 135 | altera_mbox_rx_data(mbox->chan); |
f62092f6 LFT |
136 | |
137 | mod_timer(&mbox->rxpoll_timer, | |
138 | jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); | |
139 | } | |
140 | ||
141 | static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p) | |
142 | { | |
143 | struct mbox_chan *chan = (struct mbox_chan *)p; | |
144 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
145 | ||
146 | altera_mbox_tx_intmask(mbox, false); | |
147 | mbox_chan_txdone(chan, 0); | |
148 | ||
149 | return IRQ_HANDLED; | |
150 | } | |
151 | ||
152 | static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p) | |
153 | { | |
154 | struct mbox_chan *chan = (struct mbox_chan *)p; | |
155 | ||
156 | altera_mbox_rx_data(chan); | |
157 | return IRQ_HANDLED; | |
158 | } | |
159 | ||
160 | static int altera_mbox_startup_sender(struct mbox_chan *chan) | |
161 | { | |
162 | int ret; | |
163 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
164 | ||
165 | if (mbox->intr_mode) { | |
166 | ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0, | |
167 | DRIVER_NAME, chan); | |
168 | if (unlikely(ret)) { | |
169 | dev_err(mbox->dev, | |
170 | "failed to register mailbox interrupt:%d\n", | |
171 | ret); | |
172 | return ret; | |
173 | } | |
174 | } | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | static int altera_mbox_startup_receiver(struct mbox_chan *chan) | |
180 | { | |
181 | int ret; | |
182 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
183 | ||
184 | if (mbox->intr_mode) { | |
185 | ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0, | |
186 | DRIVER_NAME, chan); | |
187 | if (unlikely(ret)) { | |
188 | mbox->intr_mode = false; | |
189 | goto polling; /* use polling if failed */ | |
190 | } | |
191 | ||
192 | altera_mbox_rx_intmask(mbox, true); | |
193 | return 0; | |
194 | } | |
195 | ||
196 | polling: | |
197 | /* Setup polling timer */ | |
c6f15047 KC |
198 | mbox->chan = chan; |
199 | timer_setup(&mbox->rxpoll_timer, altera_mbox_poll_rx, 0); | |
f62092f6 LFT |
200 | mod_timer(&mbox->rxpoll_timer, |
201 | jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | static int altera_mbox_send_data(struct mbox_chan *chan, void *data) | |
207 | { | |
208 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
209 | u32 *udata = (u32 *)data; | |
210 | ||
211 | if (!mbox || !data) | |
212 | return -EINVAL; | |
213 | if (!mbox->is_sender) { | |
214 | dev_warn(mbox->dev, | |
215 | "failed to send. This is receiver mailbox.\n"); | |
216 | return -EINVAL; | |
217 | } | |
218 | ||
219 | if (altera_mbox_full(mbox)) | |
220 | return -EBUSY; | |
221 | ||
222 | /* Enable interrupt before send */ | |
223 | if (mbox->intr_mode) | |
224 | altera_mbox_tx_intmask(mbox, true); | |
225 | ||
226 | /* Pointer register must write before command register */ | |
227 | writel_relaxed(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG); | |
228 | writel_relaxed(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG); | |
229 | ||
230 | return 0; | |
231 | } | |
232 | ||
233 | static bool altera_mbox_last_tx_done(struct mbox_chan *chan) | |
234 | { | |
235 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
236 | ||
237 | /* Return false if mailbox is full */ | |
238 | return altera_mbox_full(mbox) ? false : true; | |
239 | } | |
240 | ||
241 | static bool altera_mbox_peek_data(struct mbox_chan *chan) | |
242 | { | |
243 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
244 | ||
245 | return altera_mbox_pending(mbox) ? true : false; | |
246 | } | |
247 | ||
248 | static int altera_mbox_startup(struct mbox_chan *chan) | |
249 | { | |
250 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
251 | int ret = 0; | |
252 | ||
253 | if (!mbox) | |
254 | return -EINVAL; | |
255 | ||
256 | if (mbox->is_sender) | |
257 | ret = altera_mbox_startup_sender(chan); | |
258 | else | |
259 | ret = altera_mbox_startup_receiver(chan); | |
260 | ||
261 | return ret; | |
262 | } | |
263 | ||
264 | static void altera_mbox_shutdown(struct mbox_chan *chan) | |
265 | { | |
266 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | |
267 | ||
268 | if (mbox->intr_mode) { | |
269 | /* Unmask all interrupt masks */ | |
270 | writel_relaxed(~0, mbox->mbox_base + MAILBOX_INTMASK_REG); | |
271 | free_irq(mbox->irq, chan); | |
272 | } else if (!mbox->is_sender) { | |
273 | del_timer_sync(&mbox->rxpoll_timer); | |
274 | } | |
275 | } | |
276 | ||
05ae7975 | 277 | static const struct mbox_chan_ops altera_mbox_ops = { |
f62092f6 LFT |
278 | .send_data = altera_mbox_send_data, |
279 | .startup = altera_mbox_startup, | |
280 | .shutdown = altera_mbox_shutdown, | |
281 | .last_tx_done = altera_mbox_last_tx_done, | |
282 | .peek_data = altera_mbox_peek_data, | |
283 | }; | |
284 | ||
285 | static int altera_mbox_probe(struct platform_device *pdev) | |
286 | { | |
287 | struct altera_mbox *mbox; | |
288 | struct resource *regs; | |
289 | struct mbox_chan *chans; | |
290 | int ret; | |
291 | ||
292 | mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), | |
293 | GFP_KERNEL); | |
294 | if (!mbox) | |
295 | return -ENOMEM; | |
296 | ||
297 | /* Allocated one channel */ | |
298 | chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL); | |
299 | if (!chans) | |
300 | return -ENOMEM; | |
301 | ||
302 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
303 | ||
304 | mbox->mbox_base = devm_ioremap_resource(&pdev->dev, regs); | |
305 | if (IS_ERR(mbox->mbox_base)) | |
306 | return PTR_ERR(mbox->mbox_base); | |
307 | ||
308 | /* Check is it a sender or receiver? */ | |
309 | mbox->is_sender = altera_mbox_is_sender(mbox); | |
310 | ||
311 | mbox->irq = platform_get_irq(pdev, 0); | |
312 | if (mbox->irq >= 0) | |
313 | mbox->intr_mode = true; | |
314 | ||
315 | mbox->dev = &pdev->dev; | |
316 | ||
317 | /* Hardware supports only one channel. */ | |
318 | chans[0].con_priv = mbox; | |
319 | mbox->controller.dev = mbox->dev; | |
320 | mbox->controller.num_chans = 1; | |
321 | mbox->controller.chans = chans; | |
322 | mbox->controller.ops = &altera_mbox_ops; | |
323 | ||
324 | if (mbox->is_sender) { | |
325 | if (mbox->intr_mode) { | |
326 | mbox->controller.txdone_irq = true; | |
327 | } else { | |
328 | mbox->controller.txdone_poll = true; | |
329 | mbox->controller.txpoll_period = MBOX_POLLING_MS; | |
330 | } | |
331 | } | |
332 | ||
87f63f57 | 333 | ret = devm_mbox_controller_register(&pdev->dev, &mbox->controller); |
f62092f6 LFT |
334 | if (ret) { |
335 | dev_err(&pdev->dev, "Register mailbox failed\n"); | |
336 | goto err; | |
337 | } | |
338 | ||
339 | platform_set_drvdata(pdev, mbox); | |
340 | err: | |
341 | return ret; | |
342 | } | |
343 | ||
f62092f6 LFT |
344 | static const struct of_device_id altera_mbox_match[] = { |
345 | { .compatible = "altr,mailbox-1.0" }, | |
346 | { /* Sentinel */ } | |
347 | }; | |
348 | ||
349 | MODULE_DEVICE_TABLE(of, altera_mbox_match); | |
350 | ||
351 | static struct platform_driver altera_mbox_driver = { | |
352 | .probe = altera_mbox_probe, | |
f62092f6 LFT |
353 | .driver = { |
354 | .name = DRIVER_NAME, | |
355 | .of_match_table = altera_mbox_match, | |
356 | }, | |
357 | }; | |
358 | ||
359 | module_platform_driver(altera_mbox_driver); | |
360 | ||
361 | MODULE_LICENSE("GPL v2"); | |
362 | MODULE_DESCRIPTION("Altera mailbox specific functions"); | |
363 | MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); | |
364 | MODULE_ALIAS("platform:altera-mailbox"); |