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