summaryrefslogtreecommitdiffstats
path: root/drivers/mailbox/mailbox-altera.c
blob: a266265677d3ebf533290a01a1a0af6212f2e292 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/*
 * Copyright Altera Corporation (C) 2013-2014. All rights reserved
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#define DRIVER_NAME	"altera-mailbox"

#define MAILBOX_CMD_REG			0x00
#define MAILBOX_PTR_REG			0x04
#define MAILBOX_STS_REG			0x08
#define MAILBOX_INTMASK_REG		0x0C

#define INT_PENDING_MSK			0x1
#define INT_SPACE_MSK			0x2

#define STS_PENDING_MSK			0x1
#define STS_FULL_MSK			0x2
#define STS_FULL_OFT			0x1

#define MBOX_PENDING(status)	(((status) & STS_PENDING_MSK))
#define MBOX_FULL(status)	(((status) & STS_FULL_MSK) >> STS_FULL_OFT)

enum altera_mbox_msg {
	MBOX_CMD = 0,
	MBOX_PTR,
};

#define MBOX_POLLING_MS		5	/* polling interval 5ms */

struct altera_mbox {
	bool is_sender;		/* 1-sender, 0-receiver */
	bool intr_mode;
	int irq;
	void __iomem *mbox_base;
	struct device *dev;
	struct mbox_controller controller;

	/* If the controller supports only RX polling mode */
	struct timer_list rxpoll_timer;
};

static struct altera_mbox *mbox_chan_to_altera_mbox(struct mbox_chan *chan)
{
	if (!chan || !chan->con_priv)
		return NULL;

	return (struct altera_mbox *)chan->con_priv;
}

static inline int altera_mbox_full(struct altera_mbox *mbox)
{
	u32 status;

	status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG);
	return MBOX_FULL(status);
}

static inline int altera_mbox_pending(struct altera_mbox *mbox)
{
	u32 status;

	status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG);
	return MBOX_PENDING(status);
}

static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable)
{
	u32 mask;

	mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG);
	if (enable)
		mask |= INT_PENDING_MSK;
	else
		mask &= ~INT_PENDING_MSK;
	writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG);
}

static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable)
{
	u32 mask;

	mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG);
	if (enable)
		mask |= INT_SPACE_MSK;
	else
		mask &= ~INT_SPACE_MSK;
	writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG);
}

static bool altera_mbox_is_sender(struct altera_mbox *mbox)
{
	u32 reg;
	/* Write a magic number to PTR register and read back this register.
	 * This register is read-write if it is a sender.
	 */
	#define MBOX_MAGIC	0xA5A5AA55
	writel_relaxed(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG);
	reg = readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG);
	if (reg == MBOX_MAGIC) {
		/* Clear to 0 */
		writel_relaxed(0, mbox->mbox_base + MAILBOX_PTR_REG);
		return true;
	}
	return false;
}

static void altera_mbox_rx_data(struct mbox_chan *chan)
{
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);
	u32 data[2];

	if (altera_mbox_pending(mbox)) {
		data[MBOX_PTR] =
			readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG);
		data[MBOX_CMD] =
			readl_relaxed(mbox->mbox_base + MAILBOX_CMD_REG);
		mbox_chan_received_data(chan, (void *)data);
	}
}

static void altera_mbox_poll_rx(unsigned long data)
{
	struct mbox_chan *chan = (struct mbox_chan *)data;
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	altera_mbox_rx_data(chan);

	mod_timer(&mbox->rxpoll_timer,
		  jiffies + msecs_to_jiffies(MBOX_POLLING_MS));
}

static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p)
{
	struct mbox_chan *chan = (struct mbox_chan *)p;
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	altera_mbox_tx_intmask(mbox, false);
	mbox_chan_txdone(chan, 0);

	return IRQ_HANDLED;
}

static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p)
{
	struct mbox_chan *chan = (struct mbox_chan *)p;

	altera_mbox_rx_data(chan);
	return IRQ_HANDLED;
}

static int altera_mbox_startup_sender(struct mbox_chan *chan)
{
	int ret;
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	if (mbox->intr_mode) {
		ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0,
				  DRIVER_NAME, chan);
		if (unlikely(ret)) {
			dev_err(mbox->dev,
				"failed to register mailbox interrupt:%d\n",
				ret);
			return ret;
		}
	}

	return 0;
}

static int altera_mbox_startup_receiver(struct mbox_chan *chan)
{
	int ret;
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	if (mbox->intr_mode) {
		ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0,
				  DRIVER_NAME, chan);
		if (unlikely(ret)) {
			mbox->intr_mode = false;
			goto polling; /* use polling if failed */
		}

		altera_mbox_rx_intmask(mbox, true);
		return 0;
	}

polling:
	/* Setup polling timer */
	setup_timer(&mbox->rxpoll_timer, altera_mbox_poll_rx,
		    (unsigned long)chan);
	mod_timer(&mbox->rxpoll_timer,
		  jiffies + msecs_to_jiffies(MBOX_POLLING_MS));

	return 0;
}

static int altera_mbox_send_data(struct mbox_chan *chan, void *data)
{
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);
	u32 *udata = (u32 *)data;

	if (!mbox || !data)
		return -EINVAL;
	if (!mbox->is_sender) {
		dev_warn(mbox->dev,
			 "failed to send. This is receiver mailbox.\n");
		return -EINVAL;
	}

	if (altera_mbox_full(mbox))
		return -EBUSY;

	/* Enable interrupt before send */
	if (mbox->intr_mode)
		altera_mbox_tx_intmask(mbox, true);

	/* Pointer register must write before command register */
	writel_relaxed(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG);
	writel_relaxed(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG);

	return 0;
}

static bool altera_mbox_last_tx_done(struct mbox_chan *chan)
{
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	/* Return false if mailbox is full */
	return altera_mbox_full(mbox) ? false : true;
}

static bool altera_mbox_peek_data(struct mbox_chan *chan)
{
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	return altera_mbox_pending(mbox) ? true : false;
}

static int altera_mbox_startup(struct mbox_chan *chan)
{
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);
	int ret = 0;

	if (!mbox)
		return -EINVAL;

	if (mbox->is_sender)
		ret = altera_mbox_startup_sender(chan);
	else
		ret = altera_mbox_startup_receiver(chan);

	return ret;
}

static void altera_mbox_shutdown(struct mbox_chan *chan)
{
	struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan);

	if (mbox->intr_mode) {
		/* Unmask all interrupt masks */
		writel_relaxed(~0, mbox->mbox_base + MAILBOX_INTMASK_REG);
		free_irq(mbox->irq, chan);
	} else if (!mbox->is_sender) {
		del_timer_sync(&mbox->rxpoll_timer);
	}
}

static struct mbox_chan_ops altera_mbox_ops = {
	.send_data = altera_mbox_send_data,
	.startup = altera_mbox_startup,
	.shutdown = altera_mbox_shutdown,
	.last_tx_done = altera_mbox_last_tx_done,
	.peek_data = altera_mbox_peek_data,
};

static int altera_mbox_probe(struct platform_device *pdev)
{
	struct altera_mbox *mbox;
	struct resource	*regs;
	struct mbox_chan *chans;
	int ret;

	mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox),
			    GFP_KERNEL);
	if (!mbox)
		return -ENOMEM;

	/* Allocated one channel */
	chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL);
	if (!chans)
		return -ENOMEM;

	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);

	mbox->mbox_base = devm_ioremap_resource(&pdev->dev, regs);
	if (IS_ERR(mbox->mbox_base))
		return PTR_ERR(mbox->mbox_base);

	/* Check is it a sender or receiver? */
	mbox->is_sender = altera_mbox_is_sender(mbox);

	mbox->irq = platform_get_irq(pdev, 0);
	if (mbox->irq >= 0)
		mbox->intr_mode = true;

	mbox->dev = &pdev->dev;

	/* Hardware supports only one channel. */
	chans[0].con_priv = mbox;
	mbox->controller.dev = mbox->dev;
	mbox->controller.num_chans = 1;
	mbox->controller.chans = chans;
	mbox->controller.ops = &altera_mbox_ops;

	if (mbox->is_sender) {
		if (mbox->intr_mode) {
			mbox->controller.txdone_irq = true;
		} else {
			mbox->controller.txdone_poll = true;
			mbox->controller.txpoll_period = MBOX_POLLING_MS;
		}
	}

	ret = mbox_controller_register(&mbox->controller);
	if (ret) {
		dev_err(&pdev->dev, "Register mailbox failed\n");
		goto err;
	}

	platform_set_drvdata(pdev, mbox);
err:
	return ret;
}

static int altera_mbox_remove(struct platform_device *pdev)
{
	struct altera_mbox *mbox = platform_get_drvdata(pdev);

	if (!mbox)
		return -EINVAL;

	mbox_controller_unregister(&mbox->controller);

	return 0;
}

static const struct of_device_id altera_mbox_match[] = {
	{ .compatible = "altr,mailbox-1.0" },
	{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, altera_mbox_match);

static struct platform_driver altera_mbox_driver = {
	.probe	= altera_mbox_probe,
	.remove	= altera_mbox_remove,
	.driver	= {
		.name	= DRIVER_NAME,
		.of_match_table	= altera_mbox_match,
	},
};

module_platform_driver(altera_mbox_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Altera mailbox specific functions");
MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>");
MODULE_ALIAS("platform:altera-mailbox");