Commit | Line | Data |
---|---|---|
1ccea77e | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
f700e84f DD |
2 | /* |
3 | * APM X-Gene SLIMpro MailBox Driver | |
4 | * | |
5 | * Copyright (c) 2015, Applied Micro Circuits Corporation | |
6 | * Author: Feng Kan fkan@apm.com | |
f700e84f DD |
7 | */ |
8 | #include <linux/acpi.h> | |
9 | #include <linux/delay.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/mailbox_controller.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/spinlock.h> | |
17 | ||
18 | #define MBOX_CON_NAME "slimpro-mbox" | |
19 | #define MBOX_REG_SET_OFFSET 0x1000 | |
20 | #define MBOX_CNT 8 | |
21 | #define MBOX_STATUS_AVAIL_MASK BIT(16) | |
22 | #define MBOX_STATUS_ACK_MASK BIT(0) | |
23 | ||
24 | /* Configuration and Status Registers */ | |
25 | #define REG_DB_IN 0x00 | |
26 | #define REG_DB_DIN0 0x04 | |
27 | #define REG_DB_DIN1 0x08 | |
28 | #define REG_DB_OUT 0x10 | |
29 | #define REG_DB_DOUT0 0x14 | |
30 | #define REG_DB_DOUT1 0x18 | |
31 | #define REG_DB_STAT 0x20 | |
32 | #define REG_DB_STATMASK 0x24 | |
33 | ||
34 | /** | |
35 | * X-Gene SlimPRO mailbox channel information | |
36 | * | |
37 | * @dev: Device to which it is attached | |
38 | * @chan: Pointer to mailbox communication channel | |
39 | * @reg: Base address to access channel registers | |
40 | * @irq: Interrupt number of the channel | |
41 | * @rx_msg: Received message storage | |
42 | */ | |
43 | struct slimpro_mbox_chan { | |
44 | struct device *dev; | |
45 | struct mbox_chan *chan; | |
46 | void __iomem *reg; | |
47 | int irq; | |
48 | u32 rx_msg[3]; | |
49 | }; | |
50 | ||
51 | /** | |
52 | * X-Gene SlimPRO Mailbox controller data | |
53 | * | |
54 | * X-Gene SlimPRO Mailbox controller has 8 commnunication channels. | |
55 | * Each channel has a separate IRQ number assgined to it. | |
56 | * | |
57 | * @mb_ctrl: Representation of the commnunication channel controller | |
58 | * @mc: Array of SlimPRO mailbox channels of the controller | |
59 | * @chans: Array of mailbox communication channels | |
60 | * | |
61 | */ | |
62 | struct slimpro_mbox { | |
63 | struct mbox_controller mb_ctrl; | |
64 | struct slimpro_mbox_chan mc[MBOX_CNT]; | |
65 | struct mbox_chan chans[MBOX_CNT]; | |
66 | }; | |
67 | ||
68 | static void mb_chan_send_msg(struct slimpro_mbox_chan *mb_chan, u32 *msg) | |
69 | { | |
70 | writel(msg[1], mb_chan->reg + REG_DB_DOUT0); | |
71 | writel(msg[2], mb_chan->reg + REG_DB_DOUT1); | |
72 | writel(msg[0], mb_chan->reg + REG_DB_OUT); | |
73 | } | |
74 | ||
75 | static void mb_chan_recv_msg(struct slimpro_mbox_chan *mb_chan) | |
76 | { | |
77 | mb_chan->rx_msg[1] = readl(mb_chan->reg + REG_DB_DIN0); | |
78 | mb_chan->rx_msg[2] = readl(mb_chan->reg + REG_DB_DIN1); | |
79 | mb_chan->rx_msg[0] = readl(mb_chan->reg + REG_DB_IN); | |
80 | } | |
81 | ||
82 | static int mb_chan_status_ack(struct slimpro_mbox_chan *mb_chan) | |
83 | { | |
84 | u32 val = readl(mb_chan->reg + REG_DB_STAT); | |
85 | ||
86 | if (val & MBOX_STATUS_ACK_MASK) { | |
87 | writel(MBOX_STATUS_ACK_MASK, mb_chan->reg + REG_DB_STAT); | |
88 | return 1; | |
89 | } | |
90 | return 0; | |
91 | } | |
92 | ||
93 | static int mb_chan_status_avail(struct slimpro_mbox_chan *mb_chan) | |
94 | { | |
95 | u32 val = readl(mb_chan->reg + REG_DB_STAT); | |
96 | ||
97 | if (val & MBOX_STATUS_AVAIL_MASK) { | |
98 | mb_chan_recv_msg(mb_chan); | |
99 | writel(MBOX_STATUS_AVAIL_MASK, mb_chan->reg + REG_DB_STAT); | |
100 | return 1; | |
101 | } | |
102 | return 0; | |
103 | } | |
104 | ||
105 | static irqreturn_t slimpro_mbox_irq(int irq, void *id) | |
106 | { | |
107 | struct slimpro_mbox_chan *mb_chan = id; | |
108 | ||
109 | if (mb_chan_status_ack(mb_chan)) | |
110 | mbox_chan_txdone(mb_chan->chan, 0); | |
111 | ||
112 | if (mb_chan_status_avail(mb_chan)) | |
113 | mbox_chan_received_data(mb_chan->chan, mb_chan->rx_msg); | |
114 | ||
115 | return IRQ_HANDLED; | |
116 | } | |
117 | ||
118 | static int slimpro_mbox_send_data(struct mbox_chan *chan, void *msg) | |
119 | { | |
120 | struct slimpro_mbox_chan *mb_chan = chan->con_priv; | |
121 | ||
122 | mb_chan_send_msg(mb_chan, msg); | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static int slimpro_mbox_startup(struct mbox_chan *chan) | |
127 | { | |
128 | struct slimpro_mbox_chan *mb_chan = chan->con_priv; | |
129 | int rc; | |
130 | u32 val; | |
131 | ||
132 | rc = devm_request_irq(mb_chan->dev, mb_chan->irq, slimpro_mbox_irq, 0, | |
133 | MBOX_CON_NAME, mb_chan); | |
134 | if (unlikely(rc)) { | |
135 | dev_err(mb_chan->dev, "failed to register mailbox interrupt %d\n", | |
136 | mb_chan->irq); | |
137 | return rc; | |
138 | } | |
139 | ||
140 | /* Enable HW interrupt */ | |
141 | writel(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK, | |
142 | mb_chan->reg + REG_DB_STAT); | |
143 | /* Unmask doorbell status interrupt */ | |
144 | val = readl(mb_chan->reg + REG_DB_STATMASK); | |
145 | val &= ~(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK); | |
146 | writel(val, mb_chan->reg + REG_DB_STATMASK); | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | static void slimpro_mbox_shutdown(struct mbox_chan *chan) | |
152 | { | |
153 | struct slimpro_mbox_chan *mb_chan = chan->con_priv; | |
154 | u32 val; | |
155 | ||
156 | /* Mask doorbell status interrupt */ | |
157 | val = readl(mb_chan->reg + REG_DB_STATMASK); | |
158 | val |= (MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK); | |
159 | writel(val, mb_chan->reg + REG_DB_STATMASK); | |
160 | ||
161 | devm_free_irq(mb_chan->dev, mb_chan->irq, mb_chan); | |
162 | } | |
163 | ||
8ce33c6f | 164 | static const struct mbox_chan_ops slimpro_mbox_ops = { |
f700e84f DD |
165 | .send_data = slimpro_mbox_send_data, |
166 | .startup = slimpro_mbox_startup, | |
167 | .shutdown = slimpro_mbox_shutdown, | |
168 | }; | |
169 | ||
170 | static int slimpro_mbox_probe(struct platform_device *pdev) | |
171 | { | |
172 | struct slimpro_mbox *ctx; | |
173 | struct resource *regs; | |
174 | void __iomem *mb_base; | |
175 | int rc; | |
176 | int i; | |
177 | ||
178 | ctx = devm_kzalloc(&pdev->dev, sizeof(struct slimpro_mbox), GFP_KERNEL); | |
a61b37ea AL |
179 | if (!ctx) |
180 | return -ENOMEM; | |
f700e84f DD |
181 | |
182 | platform_set_drvdata(pdev, ctx); | |
183 | ||
184 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
3512a18c GS |
185 | mb_base = devm_ioremap_resource(&pdev->dev, regs); |
186 | if (IS_ERR(mb_base)) | |
187 | return PTR_ERR(mb_base); | |
f700e84f DD |
188 | |
189 | /* Setup mailbox links */ | |
190 | for (i = 0; i < MBOX_CNT; i++) { | |
191 | ctx->mc[i].irq = platform_get_irq(pdev, i); | |
192 | if (ctx->mc[i].irq < 0) { | |
193 | if (i == 0) { | |
194 | dev_err(&pdev->dev, "no available IRQ\n"); | |
195 | return -EINVAL; | |
196 | } | |
197 | dev_info(&pdev->dev, "no IRQ for channel %d\n", i); | |
198 | break; | |
199 | } | |
200 | ||
201 | ctx->mc[i].dev = &pdev->dev; | |
202 | ctx->mc[i].reg = mb_base + i * MBOX_REG_SET_OFFSET; | |
203 | ctx->mc[i].chan = &ctx->chans[i]; | |
204 | ctx->chans[i].con_priv = &ctx->mc[i]; | |
205 | } | |
206 | ||
207 | /* Setup mailbox controller */ | |
208 | ctx->mb_ctrl.dev = &pdev->dev; | |
209 | ctx->mb_ctrl.chans = ctx->chans; | |
210 | ctx->mb_ctrl.txdone_irq = true; | |
211 | ctx->mb_ctrl.ops = &slimpro_mbox_ops; | |
212 | ctx->mb_ctrl.num_chans = i; | |
213 | ||
0b7f5fe8 | 214 | rc = devm_mbox_controller_register(&pdev->dev, &ctx->mb_ctrl); |
f700e84f DD |
215 | if (rc) { |
216 | dev_err(&pdev->dev, | |
217 | "APM X-Gene SLIMpro MailBox register failed:%d\n", rc); | |
218 | return rc; | |
219 | } | |
220 | ||
221 | dev_info(&pdev->dev, "APM X-Gene SLIMpro MailBox registered\n"); | |
222 | return 0; | |
223 | } | |
224 | ||
f700e84f DD |
225 | static const struct of_device_id slimpro_of_match[] = { |
226 | {.compatible = "apm,xgene-slimpro-mbox" }, | |
227 | { }, | |
228 | }; | |
229 | MODULE_DEVICE_TABLE(of, slimpro_of_match); | |
230 | ||
231 | #ifdef CONFIG_ACPI | |
232 | static const struct acpi_device_id slimpro_acpi_ids[] = { | |
233 | {"APMC0D01", 0}, | |
234 | {} | |
235 | }; | |
236 | MODULE_DEVICE_TABLE(acpi, slimpro_acpi_ids); | |
237 | #endif | |
238 | ||
239 | static struct platform_driver slimpro_mbox_driver = { | |
240 | .probe = slimpro_mbox_probe, | |
f700e84f DD |
241 | .driver = { |
242 | .name = "xgene-slimpro-mbox", | |
243 | .of_match_table = of_match_ptr(slimpro_of_match), | |
244 | .acpi_match_table = ACPI_PTR(slimpro_acpi_ids) | |
245 | }, | |
246 | }; | |
247 | ||
248 | static int __init slimpro_mbox_init(void) | |
249 | { | |
250 | return platform_driver_register(&slimpro_mbox_driver); | |
251 | } | |
252 | ||
253 | static void __exit slimpro_mbox_exit(void) | |
254 | { | |
255 | platform_driver_unregister(&slimpro_mbox_driver); | |
256 | } | |
257 | ||
258 | subsys_initcall(slimpro_mbox_init); | |
259 | module_exit(slimpro_mbox_exit); | |
260 | ||
261 | MODULE_DESCRIPTION("APM X-Gene SLIMpro Mailbox Driver"); | |
262 | MODULE_LICENSE("GPL"); |