1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2016-2017, Linaro Ltd |
4 | */ |
5 | |
6 | #include <linux/idr.h> |
7 | #include <linux/interrupt.h> |
8 | #include <linux/io.h> |
9 | #include <linux/list.h> |
10 | #include <linux/mfd/syscon.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/of_irq.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/rpmsg.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/workqueue.h> |
20 | #include <linux/mailbox_client.h> |
21 | |
22 | #include "rpmsg_internal.h" |
23 | #include "qcom_glink_native.h" |
24 | |
25 | #define RPM_TOC_SIZE 256 |
26 | #define RPM_TOC_MAGIC 0x67727430 /* grt0 */ |
27 | #define RPM_TOC_MAX_ENTRIES ((RPM_TOC_SIZE - sizeof(struct rpm_toc)) / \ |
28 | sizeof(struct rpm_toc_entry)) |
29 | |
30 | #define RPM_TX_FIFO_ID 0x61703272 /* ap2r */ |
31 | #define RPM_RX_FIFO_ID 0x72326170 /* r2ap */ |
32 | |
33 | #define to_rpm_pipe(p) container_of(p, struct glink_rpm_pipe, native) |
34 | |
35 | struct rpm_toc_entry { |
36 | __le32 id; |
37 | __le32 offset; |
38 | __le32 size; |
39 | } __packed; |
40 | |
41 | struct rpm_toc { |
42 | __le32 magic; |
43 | __le32 count; |
44 | |
45 | struct rpm_toc_entry entries[]; |
46 | } __packed; |
47 | |
48 | struct glink_rpm_pipe { |
49 | struct qcom_glink_pipe native; |
50 | |
51 | void __iomem *tail; |
52 | void __iomem *head; |
53 | |
54 | void __iomem *fifo; |
55 | }; |
56 | |
57 | struct glink_rpm { |
58 | struct qcom_glink *glink; |
59 | |
60 | int irq; |
61 | |
62 | struct mbox_client mbox_client; |
63 | struct mbox_chan *mbox_chan; |
64 | |
65 | struct glink_rpm_pipe rx_pipe; |
66 | struct glink_rpm_pipe tx_pipe; |
67 | }; |
68 | |
69 | static size_t glink_rpm_rx_avail(struct qcom_glink_pipe *glink_pipe) |
70 | { |
71 | struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe); |
72 | unsigned int head; |
73 | unsigned int tail; |
74 | |
75 | head = readl(addr: pipe->head); |
76 | tail = readl(addr: pipe->tail); |
77 | |
78 | if (head < tail) |
79 | return pipe->native.length - tail + head; |
80 | else |
81 | return head - tail; |
82 | } |
83 | |
84 | static void glink_rpm_rx_peek(struct qcom_glink_pipe *glink_pipe, |
85 | void *data, unsigned int offset, size_t count) |
86 | { |
87 | struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe); |
88 | unsigned int tail; |
89 | size_t len; |
90 | |
91 | tail = readl(addr: pipe->tail); |
92 | tail += offset; |
93 | if (tail >= pipe->native.length) |
94 | tail -= pipe->native.length; |
95 | |
96 | len = min_t(size_t, count, pipe->native.length - tail); |
97 | if (len) { |
98 | __ioread32_copy(to: data, from: pipe->fifo + tail, |
99 | count: len / sizeof(u32)); |
100 | } |
101 | |
102 | if (len != count) { |
103 | __ioread32_copy(to: data + len, from: pipe->fifo, |
104 | count: (count - len) / sizeof(u32)); |
105 | } |
106 | } |
107 | |
108 | static void glink_rpm_rx_advance(struct qcom_glink_pipe *glink_pipe, |
109 | size_t count) |
110 | { |
111 | struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe); |
112 | unsigned int tail; |
113 | |
114 | tail = readl(addr: pipe->tail); |
115 | |
116 | tail += count; |
117 | if (tail >= pipe->native.length) |
118 | tail -= pipe->native.length; |
119 | |
120 | writel(val: tail, addr: pipe->tail); |
121 | } |
122 | |
123 | static size_t glink_rpm_tx_avail(struct qcom_glink_pipe *glink_pipe) |
124 | { |
125 | struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe); |
126 | unsigned int head; |
127 | unsigned int tail; |
128 | |
129 | head = readl(addr: pipe->head); |
130 | tail = readl(addr: pipe->tail); |
131 | |
132 | if (tail <= head) |
133 | return pipe->native.length - head + tail; |
134 | else |
135 | return tail - head; |
136 | } |
137 | |
138 | static unsigned int glink_rpm_tx_write_one(struct glink_rpm_pipe *pipe, |
139 | unsigned int head, |
140 | const void *data, size_t count) |
141 | { |
142 | size_t len; |
143 | |
144 | len = min_t(size_t, count, pipe->native.length - head); |
145 | if (len) { |
146 | __iowrite32_copy(to: pipe->fifo + head, from: data, |
147 | count: len / sizeof(u32)); |
148 | } |
149 | |
150 | if (len != count) { |
151 | __iowrite32_copy(to: pipe->fifo, from: data + len, |
152 | count: (count - len) / sizeof(u32)); |
153 | } |
154 | |
155 | head += count; |
156 | if (head >= pipe->native.length) |
157 | head -= pipe->native.length; |
158 | |
159 | return head; |
160 | } |
161 | |
162 | static void glink_rpm_tx_write(struct qcom_glink_pipe *glink_pipe, |
163 | const void *hdr, size_t hlen, |
164 | const void *data, size_t dlen) |
165 | { |
166 | struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe); |
167 | size_t tlen = hlen + dlen; |
168 | size_t aligned_dlen; |
169 | unsigned int head; |
170 | char padding[8] = {0}; |
171 | size_t pad; |
172 | |
173 | /* Header length comes from glink native and is always 4 byte aligned */ |
174 | if (WARN(hlen % 4, "Glink Header length must be 4 bytes aligned\n" )) |
175 | return; |
176 | |
177 | /* |
178 | * Move the unaligned tail of the message to the padding chunk, to |
179 | * ensure word aligned accesses |
180 | */ |
181 | aligned_dlen = ALIGN_DOWN(dlen, 4); |
182 | if (aligned_dlen != dlen) |
183 | memcpy(padding, data + aligned_dlen, dlen - aligned_dlen); |
184 | |
185 | head = readl(addr: pipe->head); |
186 | head = glink_rpm_tx_write_one(pipe, head, data: hdr, count: hlen); |
187 | head = glink_rpm_tx_write_one(pipe, head, data, count: aligned_dlen); |
188 | |
189 | pad = ALIGN(tlen, 8) - ALIGN_DOWN(tlen, 4); |
190 | if (pad) |
191 | head = glink_rpm_tx_write_one(pipe, head, data: padding, count: pad); |
192 | writel(val: head, addr: pipe->head); |
193 | } |
194 | |
195 | static void glink_rpm_tx_kick(struct qcom_glink_pipe *glink_pipe) |
196 | { |
197 | struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe); |
198 | struct glink_rpm *rpm = container_of(pipe, struct glink_rpm, tx_pipe); |
199 | |
200 | mbox_send_message(chan: rpm->mbox_chan, NULL); |
201 | mbox_client_txdone(chan: rpm->mbox_chan, r: 0); |
202 | } |
203 | |
204 | static irqreturn_t qcom_glink_rpm_intr(int irq, void *data) |
205 | { |
206 | struct glink_rpm *rpm = data; |
207 | |
208 | qcom_glink_native_rx(glink: rpm->glink); |
209 | |
210 | return IRQ_HANDLED; |
211 | } |
212 | |
213 | static int glink_rpm_parse_toc(struct device *dev, |
214 | void __iomem *msg_ram, |
215 | size_t msg_ram_size, |
216 | struct glink_rpm_pipe *rx, |
217 | struct glink_rpm_pipe *tx) |
218 | { |
219 | struct rpm_toc *toc; |
220 | int num_entries; |
221 | unsigned int id; |
222 | size_t offset; |
223 | size_t size; |
224 | void *buf; |
225 | int i; |
226 | |
227 | buf = kzalloc(RPM_TOC_SIZE, GFP_KERNEL); |
228 | if (!buf) |
229 | return -ENOMEM; |
230 | |
231 | __ioread32_copy(to: buf, from: msg_ram + msg_ram_size - RPM_TOC_SIZE, |
232 | RPM_TOC_SIZE / sizeof(u32)); |
233 | |
234 | toc = buf; |
235 | |
236 | if (le32_to_cpu(toc->magic) != RPM_TOC_MAGIC) { |
237 | dev_err(dev, "RPM TOC has invalid magic\n" ); |
238 | goto err_inval; |
239 | } |
240 | |
241 | num_entries = le32_to_cpu(toc->count); |
242 | if (num_entries > RPM_TOC_MAX_ENTRIES) { |
243 | dev_err(dev, "Invalid number of toc entries\n" ); |
244 | goto err_inval; |
245 | } |
246 | |
247 | for (i = 0; i < num_entries; i++) { |
248 | id = le32_to_cpu(toc->entries[i].id); |
249 | offset = le32_to_cpu(toc->entries[i].offset); |
250 | size = le32_to_cpu(toc->entries[i].size); |
251 | |
252 | if (offset > msg_ram_size || offset + size > msg_ram_size) { |
253 | dev_err(dev, "TOC entry with invalid size\n" ); |
254 | continue; |
255 | } |
256 | |
257 | switch (id) { |
258 | case RPM_RX_FIFO_ID: |
259 | rx->native.length = size; |
260 | |
261 | rx->tail = msg_ram + offset; |
262 | rx->head = msg_ram + offset + sizeof(u32); |
263 | rx->fifo = msg_ram + offset + 2 * sizeof(u32); |
264 | break; |
265 | case RPM_TX_FIFO_ID: |
266 | tx->native.length = size; |
267 | |
268 | tx->tail = msg_ram + offset; |
269 | tx->head = msg_ram + offset + sizeof(u32); |
270 | tx->fifo = msg_ram + offset + 2 * sizeof(u32); |
271 | break; |
272 | } |
273 | } |
274 | |
275 | if (!rx->fifo || !tx->fifo) { |
276 | dev_err(dev, "Unable to find rx and tx descriptors\n" ); |
277 | goto err_inval; |
278 | } |
279 | |
280 | kfree(objp: buf); |
281 | return 0; |
282 | |
283 | err_inval: |
284 | kfree(objp: buf); |
285 | return -EINVAL; |
286 | } |
287 | |
288 | static int glink_rpm_probe(struct platform_device *pdev) |
289 | { |
290 | struct qcom_glink *glink; |
291 | struct glink_rpm *rpm; |
292 | struct device_node *np; |
293 | void __iomem *msg_ram; |
294 | size_t msg_ram_size; |
295 | struct device *dev = &pdev->dev; |
296 | struct resource r; |
297 | int ret; |
298 | |
299 | rpm = devm_kzalloc(dev: &pdev->dev, size: sizeof(*rpm), GFP_KERNEL); |
300 | if (!rpm) |
301 | return -ENOMEM; |
302 | |
303 | np = of_parse_phandle(np: dev->of_node, phandle_name: "qcom,rpm-msg-ram" , index: 0); |
304 | ret = of_address_to_resource(dev: np, index: 0, r: &r); |
305 | of_node_put(node: np); |
306 | if (ret) |
307 | return ret; |
308 | |
309 | msg_ram = devm_ioremap(dev, offset: r.start, size: resource_size(res: &r)); |
310 | msg_ram_size = resource_size(res: &r); |
311 | if (!msg_ram) |
312 | return -ENOMEM; |
313 | |
314 | ret = glink_rpm_parse_toc(dev, msg_ram, msg_ram_size, |
315 | rx: &rpm->rx_pipe, tx: &rpm->tx_pipe); |
316 | if (ret) |
317 | return ret; |
318 | |
319 | rpm->irq = of_irq_get(dev: dev->of_node, index: 0); |
320 | ret = devm_request_irq(dev, irq: rpm->irq, handler: qcom_glink_rpm_intr, |
321 | IRQF_NO_SUSPEND | IRQF_NO_AUTOEN, |
322 | devname: "glink-rpm" , dev_id: rpm); |
323 | if (ret) { |
324 | dev_err(dev, "failed to request IRQ\n" ); |
325 | return ret; |
326 | } |
327 | |
328 | rpm->mbox_client.dev = dev; |
329 | rpm->mbox_client.knows_txdone = true; |
330 | rpm->mbox_chan = mbox_request_channel(cl: &rpm->mbox_client, index: 0); |
331 | if (IS_ERR(ptr: rpm->mbox_chan)) |
332 | return dev_err_probe(dev, err: PTR_ERR(ptr: rpm->mbox_chan), fmt: "failed to acquire IPC channel\n" ); |
333 | |
334 | /* Pipe specific accessors */ |
335 | rpm->rx_pipe.native.avail = glink_rpm_rx_avail; |
336 | rpm->rx_pipe.native.peek = glink_rpm_rx_peek; |
337 | rpm->rx_pipe.native.advance = glink_rpm_rx_advance; |
338 | rpm->tx_pipe.native.avail = glink_rpm_tx_avail; |
339 | rpm->tx_pipe.native.write = glink_rpm_tx_write; |
340 | rpm->tx_pipe.native.kick = glink_rpm_tx_kick; |
341 | |
342 | writel(val: 0, addr: rpm->tx_pipe.head); |
343 | writel(val: 0, addr: rpm->rx_pipe.tail); |
344 | |
345 | glink = qcom_glink_native_probe(dev, |
346 | features: 0, |
347 | rx: &rpm->rx_pipe.native, |
348 | tx: &rpm->tx_pipe.native, |
349 | intentless: true); |
350 | if (IS_ERR(ptr: glink)) { |
351 | mbox_free_channel(chan: rpm->mbox_chan); |
352 | return PTR_ERR(ptr: glink); |
353 | } |
354 | |
355 | rpm->glink = glink; |
356 | |
357 | platform_set_drvdata(pdev, data: rpm); |
358 | |
359 | enable_irq(irq: rpm->irq); |
360 | |
361 | return 0; |
362 | } |
363 | |
364 | static void glink_rpm_remove(struct platform_device *pdev) |
365 | { |
366 | struct glink_rpm *rpm = platform_get_drvdata(pdev); |
367 | struct qcom_glink *glink = rpm->glink; |
368 | |
369 | disable_irq(irq: rpm->irq); |
370 | |
371 | qcom_glink_native_remove(glink); |
372 | |
373 | mbox_free_channel(chan: rpm->mbox_chan); |
374 | } |
375 | |
376 | static const struct of_device_id glink_rpm_of_match[] = { |
377 | { .compatible = "qcom,glink-rpm" }, |
378 | {} |
379 | }; |
380 | MODULE_DEVICE_TABLE(of, glink_rpm_of_match); |
381 | |
382 | static struct platform_driver glink_rpm_driver = { |
383 | .probe = glink_rpm_probe, |
384 | .remove_new = glink_rpm_remove, |
385 | .driver = { |
386 | .name = "qcom_glink_rpm" , |
387 | .of_match_table = glink_rpm_of_match, |
388 | }, |
389 | }; |
390 | |
391 | static int __init glink_rpm_init(void) |
392 | { |
393 | return platform_driver_register(&glink_rpm_driver); |
394 | } |
395 | subsys_initcall(glink_rpm_init); |
396 | |
397 | static void __exit glink_rpm_exit(void) |
398 | { |
399 | platform_driver_unregister(&glink_rpm_driver); |
400 | } |
401 | module_exit(glink_rpm_exit); |
402 | |
403 | MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>" ); |
404 | MODULE_DESCRIPTION("Qualcomm GLINK RPM driver" ); |
405 | MODULE_LICENSE("GPL v2" ); |
406 | |