184369fbeSSuman Anna // SPDX-License-Identifier: GPL-2.0
2b4f8e52bSBjorn Andersson /*
3b4f8e52bSBjorn Andersson  * Copyright (c) 2016-2017, Linaro Ltd
4b4f8e52bSBjorn Andersson  */
5b4f8e52bSBjorn Andersson 
6b4f8e52bSBjorn Andersson #include <linux/idr.h>
7b4f8e52bSBjorn Andersson #include <linux/interrupt.h>
8b4f8e52bSBjorn Andersson #include <linux/io.h>
9b4f8e52bSBjorn Andersson #include <linux/list.h>
10b4f8e52bSBjorn Andersson #include <linux/mfd/syscon.h>
11b4f8e52bSBjorn Andersson #include <linux/module.h>
12b4f8e52bSBjorn Andersson #include <linux/of.h>
13b4f8e52bSBjorn Andersson #include <linux/of_address.h>
14b4f8e52bSBjorn Andersson #include <linux/platform_device.h>
15b4f8e52bSBjorn Andersson #include <linux/regmap.h>
16b4f8e52bSBjorn Andersson #include <linux/rpmsg.h>
17b4f8e52bSBjorn Andersson #include <linux/slab.h>
18b4f8e52bSBjorn Andersson #include <linux/workqueue.h>
19b4f8e52bSBjorn Andersson #include <linux/mailbox_client.h>
20b4f8e52bSBjorn Andersson 
21b4f8e52bSBjorn Andersson #include "rpmsg_internal.h"
22835764ddSBjorn Andersson #include "qcom_glink_native.h"
23b4f8e52bSBjorn Andersson 
24b4f8e52bSBjorn Andersson #define RPM_TOC_SIZE		256
25b4f8e52bSBjorn Andersson #define RPM_TOC_MAGIC		0x67727430 /* grt0 */
26b4f8e52bSBjorn Andersson #define RPM_TOC_MAX_ENTRIES	((RPM_TOC_SIZE - sizeof(struct rpm_toc)) / \
27b4f8e52bSBjorn Andersson 				 sizeof(struct rpm_toc_entry))
28b4f8e52bSBjorn Andersson 
29b4f8e52bSBjorn Andersson #define RPM_TX_FIFO_ID		0x61703272 /* ap2r */
30b4f8e52bSBjorn Andersson #define RPM_RX_FIFO_ID		0x72326170 /* r2ap */
31b4f8e52bSBjorn Andersson 
32e45c5dc2SBjorn Andersson #define to_rpm_pipe(p) container_of(p, struct glink_rpm_pipe, native)
33e45c5dc2SBjorn Andersson 
34b4f8e52bSBjorn Andersson struct rpm_toc_entry {
35b4f8e52bSBjorn Andersson 	__le32 id;
36b4f8e52bSBjorn Andersson 	__le32 offset;
37b4f8e52bSBjorn Andersson 	__le32 size;
38b4f8e52bSBjorn Andersson } __packed;
39b4f8e52bSBjorn Andersson 
40b4f8e52bSBjorn Andersson struct rpm_toc {
41b4f8e52bSBjorn Andersson 	__le32 magic;
42b4f8e52bSBjorn Andersson 	__le32 count;
43b4f8e52bSBjorn Andersson 
44b4f8e52bSBjorn Andersson 	struct rpm_toc_entry entries[];
45b4f8e52bSBjorn Andersson } __packed;
46b4f8e52bSBjorn Andersson 
47b4f8e52bSBjorn Andersson struct glink_rpm_pipe {
48e45c5dc2SBjorn Andersson 	struct qcom_glink_pipe native;
49e45c5dc2SBjorn Andersson 
50b4f8e52bSBjorn Andersson 	void __iomem *tail;
51b4f8e52bSBjorn Andersson 	void __iomem *head;
52b4f8e52bSBjorn Andersson 
53b4f8e52bSBjorn Andersson 	void __iomem *fifo;
54b4f8e52bSBjorn Andersson };
55b4f8e52bSBjorn Andersson 
56e45c5dc2SBjorn Andersson static size_t glink_rpm_rx_avail(struct qcom_glink_pipe *glink_pipe)
57b4f8e52bSBjorn Andersson {
58e45c5dc2SBjorn Andersson 	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
59b4f8e52bSBjorn Andersson 	unsigned int head;
60b4f8e52bSBjorn Andersson 	unsigned int tail;
61b4f8e52bSBjorn Andersson 
62b4f8e52bSBjorn Andersson 	head = readl(pipe->head);
63b4f8e52bSBjorn Andersson 	tail = readl(pipe->tail);
64b4f8e52bSBjorn Andersson 
65b4f8e52bSBjorn Andersson 	if (head < tail)
66e45c5dc2SBjorn Andersson 		return pipe->native.length - tail + head;
67b4f8e52bSBjorn Andersson 	else
68b4f8e52bSBjorn Andersson 		return head - tail;
69b4f8e52bSBjorn Andersson }
70b4f8e52bSBjorn Andersson 
71e45c5dc2SBjorn Andersson static void glink_rpm_rx_peak(struct qcom_glink_pipe *glink_pipe,
72b88eee97SBjorn Andersson 			      void *data, unsigned int offset, size_t count)
73b4f8e52bSBjorn Andersson {
74e45c5dc2SBjorn Andersson 	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
75b4f8e52bSBjorn Andersson 	unsigned int tail;
76b4f8e52bSBjorn Andersson 	size_t len;
77b4f8e52bSBjorn Andersson 
78b4f8e52bSBjorn Andersson 	tail = readl(pipe->tail);
79b88eee97SBjorn Andersson 	tail += offset;
80b88eee97SBjorn Andersson 	if (tail >= pipe->native.length)
81b88eee97SBjorn Andersson 		tail -= pipe->native.length;
82b4f8e52bSBjorn Andersson 
83e45c5dc2SBjorn Andersson 	len = min_t(size_t, count, pipe->native.length - tail);
84b4f8e52bSBjorn Andersson 	if (len) {
85b4f8e52bSBjorn Andersson 		__ioread32_copy(data, pipe->fifo + tail,
86b4f8e52bSBjorn Andersson 				len / sizeof(u32));
87b4f8e52bSBjorn Andersson 	}
88b4f8e52bSBjorn Andersson 
89b4f8e52bSBjorn Andersson 	if (len != count) {
90b4f8e52bSBjorn Andersson 		__ioread32_copy(data + len, pipe->fifo,
91b4f8e52bSBjorn Andersson 				(count - len) / sizeof(u32));
92b4f8e52bSBjorn Andersson 	}
93b4f8e52bSBjorn Andersson }
94b4f8e52bSBjorn Andersson 
95e45c5dc2SBjorn Andersson static void glink_rpm_rx_advance(struct qcom_glink_pipe *glink_pipe,
96b4f8e52bSBjorn Andersson 				 size_t count)
97b4f8e52bSBjorn Andersson {
98e45c5dc2SBjorn Andersson 	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
99b4f8e52bSBjorn Andersson 	unsigned int tail;
100b4f8e52bSBjorn Andersson 
101b4f8e52bSBjorn Andersson 	tail = readl(pipe->tail);
102b4f8e52bSBjorn Andersson 
103b4f8e52bSBjorn Andersson 	tail += count;
104e45c5dc2SBjorn Andersson 	if (tail >= pipe->native.length)
105e45c5dc2SBjorn Andersson 		tail -= pipe->native.length;
106b4f8e52bSBjorn Andersson 
107b4f8e52bSBjorn Andersson 	writel(tail, pipe->tail);
108b4f8e52bSBjorn Andersson }
109b4f8e52bSBjorn Andersson 
110e45c5dc2SBjorn Andersson static size_t glink_rpm_tx_avail(struct qcom_glink_pipe *glink_pipe)
111e45c5dc2SBjorn Andersson {
112e45c5dc2SBjorn Andersson 	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
113b4f8e52bSBjorn Andersson 	unsigned int head;
114b4f8e52bSBjorn Andersson 	unsigned int tail;
115b4f8e52bSBjorn Andersson 
116b4f8e52bSBjorn Andersson 	head = readl(pipe->head);
117b4f8e52bSBjorn Andersson 	tail = readl(pipe->tail);
118b4f8e52bSBjorn Andersson 
119b4f8e52bSBjorn Andersson 	if (tail <= head)
120e45c5dc2SBjorn Andersson 		return pipe->native.length - head + tail;
121b4f8e52bSBjorn Andersson 	else
122b4f8e52bSBjorn Andersson 		return tail - head;
123b4f8e52bSBjorn Andersson }
124b4f8e52bSBjorn Andersson 
125e45c5dc2SBjorn Andersson static unsigned int glink_rpm_tx_write_one(struct glink_rpm_pipe *pipe,
126b4f8e52bSBjorn Andersson 					   unsigned int head,
127b4f8e52bSBjorn Andersson 					   const void *data, size_t count)
128b4f8e52bSBjorn Andersson {
129b4f8e52bSBjorn Andersson 	size_t len;
130b4f8e52bSBjorn Andersson 
131e45c5dc2SBjorn Andersson 	len = min_t(size_t, count, pipe->native.length - head);
132b4f8e52bSBjorn Andersson 	if (len) {
133b4f8e52bSBjorn Andersson 		__iowrite32_copy(pipe->fifo + head, data,
134b4f8e52bSBjorn Andersson 				 len / sizeof(u32));
135b4f8e52bSBjorn Andersson 	}
136b4f8e52bSBjorn Andersson 
137b4f8e52bSBjorn Andersson 	if (len != count) {
138b4f8e52bSBjorn Andersson 		__iowrite32_copy(pipe->fifo, data + len,
139b4f8e52bSBjorn Andersson 				 (count - len) / sizeof(u32));
140b4f8e52bSBjorn Andersson 	}
141b4f8e52bSBjorn Andersson 
142b4f8e52bSBjorn Andersson 	head += count;
143e45c5dc2SBjorn Andersson 	if (head >= pipe->native.length)
144e45c5dc2SBjorn Andersson 		head -= pipe->native.length;
145b4f8e52bSBjorn Andersson 
146b4f8e52bSBjorn Andersson 	return head;
147b4f8e52bSBjorn Andersson }
148b4f8e52bSBjorn Andersson 
149e45c5dc2SBjorn Andersson static void glink_rpm_tx_write(struct qcom_glink_pipe *glink_pipe,
150e45c5dc2SBjorn Andersson 			       const void *hdr, size_t hlen,
151e45c5dc2SBjorn Andersson 			       const void *data, size_t dlen)
152e45c5dc2SBjorn Andersson {
153e45c5dc2SBjorn Andersson 	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
1547339859dSBjorn Andersson 	size_t tlen = hlen + dlen;
1557339859dSBjorn Andersson 	size_t aligned_dlen;
156e45c5dc2SBjorn Andersson 	unsigned int head;
1577339859dSBjorn Andersson 	char padding[8] = {0};
1587339859dSBjorn Andersson 	size_t pad;
1597339859dSBjorn Andersson 
1607339859dSBjorn Andersson 	/* Header length comes from glink native and is always 4 byte aligned */
1617339859dSBjorn Andersson 	if (WARN(hlen % 4, "Glink Header length must be 4 bytes aligned\n"))
1627339859dSBjorn Andersson 		return;
1637339859dSBjorn Andersson 
1647339859dSBjorn Andersson 	/*
1657339859dSBjorn Andersson 	 * Move the unaligned tail of the message to the padding chunk, to
1667339859dSBjorn Andersson 	 * ensure word aligned accesses
1677339859dSBjorn Andersson 	 */
1687339859dSBjorn Andersson 	aligned_dlen = ALIGN_DOWN(dlen, 4);
1697339859dSBjorn Andersson 	if (aligned_dlen != dlen)
1707339859dSBjorn Andersson 		memcpy(padding, data + aligned_dlen, dlen - aligned_dlen);
171e45c5dc2SBjorn Andersson 
172e45c5dc2SBjorn Andersson 	head = readl(pipe->head);
173e45c5dc2SBjorn Andersson 	head = glink_rpm_tx_write_one(pipe, head, hdr, hlen);
1747339859dSBjorn Andersson 	head = glink_rpm_tx_write_one(pipe, head, data, aligned_dlen);
1757339859dSBjorn Andersson 
1767339859dSBjorn Andersson 	pad = ALIGN(tlen, 8) - ALIGN_DOWN(tlen, 4);
1777339859dSBjorn Andersson 	if (pad)
1787339859dSBjorn Andersson 		head = glink_rpm_tx_write_one(pipe, head, padding, pad);
179e45c5dc2SBjorn Andersson 	writel(head, pipe->head);
180e45c5dc2SBjorn Andersson }
181e45c5dc2SBjorn Andersson 
182b4f8e52bSBjorn Andersson static int glink_rpm_parse_toc(struct device *dev,
183b4f8e52bSBjorn Andersson 			       void __iomem *msg_ram,
184b4f8e52bSBjorn Andersson 			       size_t msg_ram_size,
185b4f8e52bSBjorn Andersson 			       struct glink_rpm_pipe *rx,
186b4f8e52bSBjorn Andersson 			       struct glink_rpm_pipe *tx)
187b4f8e52bSBjorn Andersson {
188b4f8e52bSBjorn Andersson 	struct rpm_toc *toc;
189b4f8e52bSBjorn Andersson 	int num_entries;
190b4f8e52bSBjorn Andersson 	unsigned int id;
191b4f8e52bSBjorn Andersson 	size_t offset;
192b4f8e52bSBjorn Andersson 	size_t size;
193b4f8e52bSBjorn Andersson 	void *buf;
194b4f8e52bSBjorn Andersson 	int i;
195b4f8e52bSBjorn Andersson 
196b4f8e52bSBjorn Andersson 	buf = kzalloc(RPM_TOC_SIZE, GFP_KERNEL);
197b4f8e52bSBjorn Andersson 	if (!buf)
198b4f8e52bSBjorn Andersson 		return -ENOMEM;
199b4f8e52bSBjorn Andersson 
200b4f8e52bSBjorn Andersson 	__ioread32_copy(buf, msg_ram + msg_ram_size - RPM_TOC_SIZE,
201b4f8e52bSBjorn Andersson 			RPM_TOC_SIZE / sizeof(u32));
202b4f8e52bSBjorn Andersson 
203b4f8e52bSBjorn Andersson 	toc = buf;
204b4f8e52bSBjorn Andersson 
205b4f8e52bSBjorn Andersson 	if (le32_to_cpu(toc->magic) != RPM_TOC_MAGIC) {
206b4f8e52bSBjorn Andersson 		dev_err(dev, "RPM TOC has invalid magic\n");
207b4f8e52bSBjorn Andersson 		goto err_inval;
208b4f8e52bSBjorn Andersson 	}
209b4f8e52bSBjorn Andersson 
210b4f8e52bSBjorn Andersson 	num_entries = le32_to_cpu(toc->count);
211b4f8e52bSBjorn Andersson 	if (num_entries > RPM_TOC_MAX_ENTRIES) {
212b4f8e52bSBjorn Andersson 		dev_err(dev, "Invalid number of toc entries\n");
213b4f8e52bSBjorn Andersson 		goto err_inval;
214b4f8e52bSBjorn Andersson 	}
215b4f8e52bSBjorn Andersson 
216b4f8e52bSBjorn Andersson 	for (i = 0; i < num_entries; i++) {
217b4f8e52bSBjorn Andersson 		id = le32_to_cpu(toc->entries[i].id);
218b4f8e52bSBjorn Andersson 		offset = le32_to_cpu(toc->entries[i].offset);
219b4f8e52bSBjorn Andersson 		size = le32_to_cpu(toc->entries[i].size);
220b4f8e52bSBjorn Andersson 
221b4f8e52bSBjorn Andersson 		if (offset > msg_ram_size || offset + size > msg_ram_size) {
222b4f8e52bSBjorn Andersson 			dev_err(dev, "TOC entry with invalid size\n");
223b4f8e52bSBjorn Andersson 			continue;
224b4f8e52bSBjorn Andersson 		}
225b4f8e52bSBjorn Andersson 
226b4f8e52bSBjorn Andersson 		switch (id) {
227b4f8e52bSBjorn Andersson 		case RPM_RX_FIFO_ID:
228e45c5dc2SBjorn Andersson 			rx->native.length = size;
229b4f8e52bSBjorn Andersson 
230b4f8e52bSBjorn Andersson 			rx->tail = msg_ram + offset;
231b4f8e52bSBjorn Andersson 			rx->head = msg_ram + offset + sizeof(u32);
232b4f8e52bSBjorn Andersson 			rx->fifo = msg_ram + offset + 2 * sizeof(u32);
233b4f8e52bSBjorn Andersson 			break;
234b4f8e52bSBjorn Andersson 		case RPM_TX_FIFO_ID:
235e45c5dc2SBjorn Andersson 			tx->native.length = size;
236b4f8e52bSBjorn Andersson 
237b4f8e52bSBjorn Andersson 			tx->tail = msg_ram + offset;
238b4f8e52bSBjorn Andersson 			tx->head = msg_ram + offset + sizeof(u32);
239b4f8e52bSBjorn Andersson 			tx->fifo = msg_ram + offset + 2 * sizeof(u32);
240b4f8e52bSBjorn Andersson 			break;
241b4f8e52bSBjorn Andersson 		}
242b4f8e52bSBjorn Andersson 	}
243b4f8e52bSBjorn Andersson 
244b4f8e52bSBjorn Andersson 	if (!rx->fifo || !tx->fifo) {
245b4f8e52bSBjorn Andersson 		dev_err(dev, "Unable to find rx and tx descriptors\n");
246b4f8e52bSBjorn Andersson 		goto err_inval;
247b4f8e52bSBjorn Andersson 	}
248b4f8e52bSBjorn Andersson 
249b4f8e52bSBjorn Andersson 	kfree(buf);
250b4f8e52bSBjorn Andersson 	return 0;
251b4f8e52bSBjorn Andersson 
252b4f8e52bSBjorn Andersson err_inval:
253b4f8e52bSBjorn Andersson 	kfree(buf);
254b4f8e52bSBjorn Andersson 	return -EINVAL;
255b4f8e52bSBjorn Andersson }
256b4f8e52bSBjorn Andersson 
2576799c434SBjorn Andersson static int glink_rpm_probe(struct platform_device *pdev)
2586799c434SBjorn Andersson {
2596799c434SBjorn Andersson 	struct qcom_glink *glink;
2606799c434SBjorn Andersson 	struct glink_rpm_pipe *rx_pipe;
2616799c434SBjorn Andersson 	struct glink_rpm_pipe *tx_pipe;
2626799c434SBjorn Andersson 	struct device_node *np;
2636799c434SBjorn Andersson 	void __iomem *msg_ram;
2646799c434SBjorn Andersson 	size_t msg_ram_size;
2656799c434SBjorn Andersson 	struct device *dev = &pdev->dev;
2666799c434SBjorn Andersson 	struct resource r;
2676799c434SBjorn Andersson 	int ret;
2686799c434SBjorn Andersson 
2696799c434SBjorn Andersson 	rx_pipe = devm_kzalloc(&pdev->dev, sizeof(*rx_pipe), GFP_KERNEL);
2706799c434SBjorn Andersson 	tx_pipe = devm_kzalloc(&pdev->dev, sizeof(*tx_pipe), GFP_KERNEL);
2716799c434SBjorn Andersson 	if (!rx_pipe || !tx_pipe)
2726799c434SBjorn Andersson 		return -ENOMEM;
2736799c434SBjorn Andersson 
274b4f8e52bSBjorn Andersson 	np = of_parse_phandle(dev->of_node, "qcom,rpm-msg-ram", 0);
275b4f8e52bSBjorn Andersson 	ret = of_address_to_resource(np, 0, &r);
276b4f8e52bSBjorn Andersson 	of_node_put(np);
277b4f8e52bSBjorn Andersson 	if (ret)
278b4f8e52bSBjorn Andersson 		return ret;
279b4f8e52bSBjorn Andersson 
280b4f8e52bSBjorn Andersson 	msg_ram = devm_ioremap(dev, r.start, resource_size(&r));
281b4f8e52bSBjorn Andersson 	msg_ram_size = resource_size(&r);
282b4f8e52bSBjorn Andersson 	if (!msg_ram)
283b4f8e52bSBjorn Andersson 		return -ENOMEM;
284b4f8e52bSBjorn Andersson 
285b4f8e52bSBjorn Andersson 	ret = glink_rpm_parse_toc(dev, msg_ram, msg_ram_size,
286e45c5dc2SBjorn Andersson 				  rx_pipe, tx_pipe);
287b4f8e52bSBjorn Andersson 	if (ret)
288b4f8e52bSBjorn Andersson 		return ret;
289b4f8e52bSBjorn Andersson 
290e45c5dc2SBjorn Andersson 	/* Pipe specific accessors */
291e45c5dc2SBjorn Andersson 	rx_pipe->native.avail = glink_rpm_rx_avail;
292e45c5dc2SBjorn Andersson 	rx_pipe->native.peak = glink_rpm_rx_peak;
293e45c5dc2SBjorn Andersson 	rx_pipe->native.advance = glink_rpm_rx_advance;
294e45c5dc2SBjorn Andersson 	tx_pipe->native.avail = glink_rpm_tx_avail;
295e45c5dc2SBjorn Andersson 	tx_pipe->native.write = glink_rpm_tx_write;
296e45c5dc2SBjorn Andersson 
297e45c5dc2SBjorn Andersson 	writel(0, tx_pipe->head);
298e45c5dc2SBjorn Andersson 	writel(0, rx_pipe->tail);
299b4f8e52bSBjorn Andersson 
300d31ad615SSricharan R 	glink = qcom_glink_native_probe(&pdev->dev,
301d31ad615SSricharan R 					0,
302d31ad615SSricharan R 					&rx_pipe->native,
303933b45daSSricharan R 					&tx_pipe->native,
304933b45daSSricharan R 					true);
3056799c434SBjorn Andersson 	if (IS_ERR(glink))
3066799c434SBjorn Andersson 		return PTR_ERR(glink);
307b4f8e52bSBjorn Andersson 
308b4f8e52bSBjorn Andersson 	platform_set_drvdata(pdev, glink);
309b4f8e52bSBjorn Andersson 
310b4f8e52bSBjorn Andersson 	return 0;
311b4f8e52bSBjorn Andersson }
312b4f8e52bSBjorn Andersson 
313b4f8e52bSBjorn Andersson static int glink_rpm_remove(struct platform_device *pdev)
314b4f8e52bSBjorn Andersson {
315d7101febSBjorn Andersson 	struct qcom_glink *glink = platform_get_drvdata(pdev);
316b4f8e52bSBjorn Andersson 
317835764ddSBjorn Andersson 	qcom_glink_native_remove(glink);
318b4f8e52bSBjorn Andersson 
319b4f8e52bSBjorn Andersson 	return 0;
320b4f8e52bSBjorn Andersson }
321b4f8e52bSBjorn Andersson 
322b4f8e52bSBjorn Andersson static const struct of_device_id glink_rpm_of_match[] = {
323b4f8e52bSBjorn Andersson 	{ .compatible = "qcom,glink-rpm" },
324b4f8e52bSBjorn Andersson 	{}
325b4f8e52bSBjorn Andersson };
326b4f8e52bSBjorn Andersson MODULE_DEVICE_TABLE(of, glink_rpm_of_match);
327b4f8e52bSBjorn Andersson 
328b4f8e52bSBjorn Andersson static struct platform_driver glink_rpm_driver = {
329b4f8e52bSBjorn Andersson 	.probe = glink_rpm_probe,
330b4f8e52bSBjorn Andersson 	.remove = glink_rpm_remove,
331b4f8e52bSBjorn Andersson 	.driver = {
332b4f8e52bSBjorn Andersson 		.name = "qcom_glink_rpm",
333b4f8e52bSBjorn Andersson 		.of_match_table = glink_rpm_of_match,
334b4f8e52bSBjorn Andersson 	},
335b4f8e52bSBjorn Andersson };
336b4f8e52bSBjorn Andersson 
337b4f8e52bSBjorn Andersson static int __init glink_rpm_init(void)
338b4f8e52bSBjorn Andersson {
339b4f8e52bSBjorn Andersson 	return platform_driver_register(&glink_rpm_driver);
340b4f8e52bSBjorn Andersson }
341b4f8e52bSBjorn Andersson subsys_initcall(glink_rpm_init);
342b4f8e52bSBjorn Andersson 
343b4f8e52bSBjorn Andersson static void __exit glink_rpm_exit(void)
344b4f8e52bSBjorn Andersson {
345b4f8e52bSBjorn Andersson 	platform_driver_unregister(&glink_rpm_driver);
346b4f8e52bSBjorn Andersson }
347b4f8e52bSBjorn Andersson module_exit(glink_rpm_exit);
348b4f8e52bSBjorn Andersson 
349b4f8e52bSBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>");
350b4f8e52bSBjorn Andersson MODULE_DESCRIPTION("Qualcomm GLINK RPM driver");
351b4f8e52bSBjorn Andersson MODULE_LICENSE("GPL v2");
352