aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mailbox/rp1-mailbox.c
blob: bf71db1dd9fae5844266718df2fb7396662d16d5 (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
// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 2023 Raspberry Pi Ltd.
 *
 * Parts of this driver are based on:
 *  - bcm2835-mailbox.c
 *    Copyright (C) 2010,2015 Broadcom
 *    Copyright (C) 2013-2014 Lubomir Rintel
 *    Copyright (C) 2013 Craig McGeachie
 */

#include <linux/compat.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/mailbox_controller.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>

/*
 * RP1's PROC_EVENTS register can generate interrupts on the M3 cores (when
 * enabled). The 32-bit register is treated as 32 events, all of which share a
 * common interrupt. HOST_EVENTS is the same in the reverse direction.
 */
#define SYSCFG_PROC_EVENTS		0x00000008
#define SYSCFG_HOST_EVENTS		0x0000000c
#define SYSCFG_HOST_EVENT_IRQ_EN	0x00000010
#define SYSCFG_HOST_EVENT_IRQ		0x00000014

#define HW_SET_BITS			0x00002000
#define HW_CLR_BITS			0x00003000

#define MAX_CHANS	4 /* 32 is the hardware limit */

struct rp1_mbox {
	void __iomem *regs;
	unsigned int irq;
	struct mbox_controller controller;
};

static struct rp1_mbox *rp1_chan_mbox(struct mbox_chan *chan)
{
	return container_of(chan->mbox, struct rp1_mbox, controller);
}

static unsigned int rp1_chan_event(struct mbox_chan *chan)
{
	return (unsigned int)(uintptr_t)chan->con_priv;
}

static irqreturn_t rp1_mbox_irq(int irq, void *dev_id)
{
	struct rp1_mbox *mbox = dev_id;
	struct mbox_chan *chan;
	unsigned int doorbell;
	unsigned int evs;

	evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
	writel(evs, mbox->regs + SYSCFG_HOST_EVENTS + HW_CLR_BITS);

	while (evs) {
		doorbell = __ffs(evs);
		chan = &mbox->controller.chans[doorbell];
		mbox_chan_received_data(chan, NULL);
		evs &= ~(1 << doorbell);
	}
	return IRQ_HANDLED;
}

static int rp1_send_data(struct mbox_chan *chan, void *data)
{
	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
	unsigned int event = rp1_chan_event(chan);

	writel(event, mbox->regs + SYSCFG_PROC_EVENTS + HW_SET_BITS);

	return 0;
}

static int rp1_startup(struct mbox_chan *chan)
{
	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
	unsigned int event = rp1_chan_event(chan);

	writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_SET_BITS);

	return 0;
}

static void rp1_shutdown(struct mbox_chan *chan)
{
	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
	unsigned int event = rp1_chan_event(chan);

	writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS);
}

static bool rp1_last_tx_done(struct mbox_chan *chan)
{
	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
	unsigned int event = rp1_chan_event(chan);
	unsigned int evs;

	evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);

	return !(evs & event);
}

static const struct mbox_chan_ops rp1_mbox_chan_ops = {
	.send_data	= rp1_send_data,
	.startup	= rp1_startup,
	.shutdown	= rp1_shutdown,
	.last_tx_done	= rp1_last_tx_done
};

static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox,
					const struct of_phandle_args *spec)
{
	struct mbox_chan *chan;
	unsigned int doorbell;

	if (spec->args_count != 1)
		return ERR_PTR(-EINVAL);

	doorbell = spec->args[0];
	if (doorbell >= MAX_CHANS)
		return ERR_PTR(-EINVAL);

	chan = &mbox->chans[doorbell];
	if (chan->con_priv)
		return ERR_PTR(-EBUSY);

	chan->con_priv = (void *)(uintptr_t)(1 << doorbell);

	return chan;
}

static int rp1_mbox_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct mbox_chan *chans;
	struct rp1_mbox *mbox;
	int ret = 0;

	mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
	if (mbox == NULL)
		return -ENOMEM;

	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
			       rp1_mbox_irq, 0, dev_name(dev), mbox);
	if (ret) {
		dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
			ret);
		return -ENODEV;
	}

	mbox->regs = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(mbox->regs)) {
		ret = PTR_ERR(mbox->regs);
		return ret;
	}

	chans = devm_kcalloc(dev, MAX_CHANS, sizeof(*chans), GFP_KERNEL);
	if (!chans)
		return -ENOMEM;

	mbox->controller.txdone_poll = true;
	mbox->controller.txpoll_period = 5;
	mbox->controller.ops = &rp1_mbox_chan_ops;
	mbox->controller.of_xlate = &rp1_mbox_xlate;
	mbox->controller.dev = dev;
	mbox->controller.num_chans = MAX_CHANS;
	mbox->controller.chans = chans;

	ret = devm_mbox_controller_register(dev, &mbox->controller);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, mbox);

	return 0;
}

static const struct of_device_id rp1_mbox_of_match[] = {
	{ .compatible = "raspberrypi,rp1-mbox", },
	{},
};
MODULE_DEVICE_TABLE(of, rp1_mbox_of_match);

static struct platform_driver rp1_mbox_driver = {
	.driver = {
		.name = "rp1-mbox",
		.of_match_table = rp1_mbox_of_match,
	},
	.probe = rp1_mbox_probe,
};

module_platform_driver(rp1_mbox_driver);

MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
MODULE_DESCRIPTION("RP1 mailbox IPC driver");
MODULE_LICENSE("GPL v2");