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
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020 Samsung Electronics Co., Ltd.
* Copyright 2020 Google LLC.
* Copyright 2024 Linaro Ltd.
*/
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/mailbox_controller.h>
#include <linux/mailbox/exynos-message.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define EXYNOS_MBOX_MCUCTRL 0x0 /* Mailbox Control Register */
#define EXYNOS_MBOX_INTCR0 0x24 /* Interrupt Clear Register 0 */
#define EXYNOS_MBOX_INTMR0 0x28 /* Interrupt Mask Register 0 */
#define EXYNOS_MBOX_INTSR0 0x2c /* Interrupt Status Register 0 */
#define EXYNOS_MBOX_INTMSR0 0x30 /* Interrupt Mask Status Register 0 */
#define EXYNOS_MBOX_INTGR1 0x40 /* Interrupt Generation Register 1 */
#define EXYNOS_MBOX_INTMR1 0x48 /* Interrupt Mask Register 1 */
#define EXYNOS_MBOX_INTSR1 0x4c /* Interrupt Status Register 1 */
#define EXYNOS_MBOX_INTMSR1 0x50 /* Interrupt Mask Status Register 1 */
#define EXYNOS_MBOX_INTMR0_MASK GENMASK(15, 0)
#define EXYNOS_MBOX_INTGR1_MASK GENMASK(15, 0)
#define EXYNOS_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS_MBOX_INTGR1_MASK)
/**
* struct exynos_mbox - driver's private data.
* @regs: mailbox registers base address.
* @mbox: pointer to the mailbox controller.
* @pclk: pointer to the mailbox peripheral clock.
*/
struct exynos_mbox {
void __iomem *regs;
struct mbox_controller *mbox;
struct clk *pclk;
};
static int exynos_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct device *dev = chan->mbox->dev;
struct exynos_mbox *exynos_mbox = dev_get_drvdata(dev);
struct exynos_mbox_msg *msg = data;
if (msg->chan_id >= exynos_mbox->mbox->num_chans) {
dev_err(dev, "Invalid channel ID %d\n", msg->chan_id);
return -EINVAL;
}
if (msg->chan_type != EXYNOS_MBOX_CHAN_TYPE_DOORBELL) {
dev_err(dev, "Unsupported channel type [%d]\n", msg->chan_type);
return -EINVAL;
};
writel(BIT(msg->chan_id), exynos_mbox->regs + EXYNOS_MBOX_INTGR1);
return 0;
}
static const struct mbox_chan_ops exynos_mbox_chan_ops = {
.send_data = exynos_mbox_send_data,
};
static struct mbox_chan *exynos_mbox_of_xlate(struct mbox_controller *mbox,
const struct of_phandle_args *sp)
{
int i;
if (sp->args_count != 0)
return ERR_PTR(-EINVAL);
/*
* Return the first available channel. When we don't pass the
* channel ID from device tree, each channel populated by the driver is
* just a software construct or a virtual channel. We use 'void *data'
* in send_data() to pass the channel identifiers.
*/
for (i = 0; i < mbox->num_chans; i++)
if (mbox->chans[i].cl == NULL)
return &mbox->chans[i];
return ERR_PTR(-EINVAL);
}
static const struct of_device_id exynos_mbox_match[] = {
{ .compatible = "google,gs101-mbox" },
{},
};
MODULE_DEVICE_TABLE(of, exynos_mbox_match);
static int exynos_mbox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct exynos_mbox *exynos_mbox;
struct mbox_controller *mbox;
struct mbox_chan *chans;
int i;
exynos_mbox = devm_kzalloc(dev, sizeof(*exynos_mbox), GFP_KERNEL);
if (!exynos_mbox)
return -ENOMEM;
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
if (!mbox)
return -ENOMEM;
chans = devm_kcalloc(dev, EXYNOS_MBOX_CHAN_COUNT, sizeof(*chans),
GFP_KERNEL);
if (!chans)
return -ENOMEM;
exynos_mbox->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(exynos_mbox->regs))
return PTR_ERR(exynos_mbox->regs);
exynos_mbox->pclk = devm_clk_get_enabled(dev, "pclk");
if (IS_ERR(exynos_mbox->pclk))
return dev_err_probe(dev, PTR_ERR(exynos_mbox->pclk),
"Failed to enable clock.\n");
mbox->num_chans = EXYNOS_MBOX_CHAN_COUNT;
mbox->chans = chans;
mbox->dev = dev;
mbox->ops = &exynos_mbox_chan_ops;
mbox->of_xlate = exynos_mbox_of_xlate;
for (i = 0; i < EXYNOS_MBOX_CHAN_COUNT; i++)
chans[i].mbox = mbox;
exynos_mbox->mbox = mbox;
platform_set_drvdata(pdev, exynos_mbox);
/* Mask out all interrupts. We support just polling channels for now. */
writel(EXYNOS_MBOX_INTMR0_MASK, exynos_mbox->regs + EXYNOS_MBOX_INTMR0);
return devm_mbox_controller_register(dev, mbox);
}
static struct platform_driver exynos_mbox_driver = {
.probe = exynos_mbox_probe,
.driver = {
.name = "exynos-acpm-mbox",
.of_match_table = exynos_mbox_match,
},
};
module_platform_driver(exynos_mbox_driver);
MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@linaro.org>");
MODULE_DESCRIPTION("Samsung Exynos mailbox driver");
MODULE_LICENSE("GPL");
|