18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2016-2017, Linaro Ltd
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/idr.h>
78c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
88c2ecf20Sopenharmony_ci#include <linux/io.h>
98c2ecf20Sopenharmony_ci#include <linux/list.h>
108c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/of.h>
138c2ecf20Sopenharmony_ci#include <linux/of_address.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/regmap.h>
168c2ecf20Sopenharmony_ci#include <linux/rpmsg.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
198c2ecf20Sopenharmony_ci#include <linux/mailbox_client.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include "rpmsg_internal.h"
228c2ecf20Sopenharmony_ci#include "qcom_glink_native.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define RPM_TOC_SIZE		256
258c2ecf20Sopenharmony_ci#define RPM_TOC_MAGIC		0x67727430 /* grt0 */
268c2ecf20Sopenharmony_ci#define RPM_TOC_MAX_ENTRIES	((RPM_TOC_SIZE - sizeof(struct rpm_toc)) / \
278c2ecf20Sopenharmony_ci				 sizeof(struct rpm_toc_entry))
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define RPM_TX_FIFO_ID		0x61703272 /* ap2r */
308c2ecf20Sopenharmony_ci#define RPM_RX_FIFO_ID		0x72326170 /* r2ap */
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define to_rpm_pipe(p) container_of(p, struct glink_rpm_pipe, native)
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistruct rpm_toc_entry {
358c2ecf20Sopenharmony_ci	__le32 id;
368c2ecf20Sopenharmony_ci	__le32 offset;
378c2ecf20Sopenharmony_ci	__le32 size;
388c2ecf20Sopenharmony_ci} __packed;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistruct rpm_toc {
418c2ecf20Sopenharmony_ci	__le32 magic;
428c2ecf20Sopenharmony_ci	__le32 count;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	struct rpm_toc_entry entries[];
458c2ecf20Sopenharmony_ci} __packed;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistruct glink_rpm_pipe {
488c2ecf20Sopenharmony_ci	struct qcom_glink_pipe native;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	void __iomem *tail;
518c2ecf20Sopenharmony_ci	void __iomem *head;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	void __iomem *fifo;
548c2ecf20Sopenharmony_ci};
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic size_t glink_rpm_rx_avail(struct qcom_glink_pipe *glink_pipe)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
598c2ecf20Sopenharmony_ci	unsigned int head;
608c2ecf20Sopenharmony_ci	unsigned int tail;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	head = readl(pipe->head);
638c2ecf20Sopenharmony_ci	tail = readl(pipe->tail);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (head < tail)
668c2ecf20Sopenharmony_ci		return pipe->native.length - tail + head;
678c2ecf20Sopenharmony_ci	else
688c2ecf20Sopenharmony_ci		return head - tail;
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic void glink_rpm_rx_peak(struct qcom_glink_pipe *glink_pipe,
728c2ecf20Sopenharmony_ci			      void *data, unsigned int offset, size_t count)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
758c2ecf20Sopenharmony_ci	unsigned int tail;
768c2ecf20Sopenharmony_ci	size_t len;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	tail = readl(pipe->tail);
798c2ecf20Sopenharmony_ci	tail += offset;
808c2ecf20Sopenharmony_ci	if (tail >= pipe->native.length)
818c2ecf20Sopenharmony_ci		tail -= pipe->native.length;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	len = min_t(size_t, count, pipe->native.length - tail);
848c2ecf20Sopenharmony_ci	if (len) {
858c2ecf20Sopenharmony_ci		__ioread32_copy(data, pipe->fifo + tail,
868c2ecf20Sopenharmony_ci				len / sizeof(u32));
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (len != count) {
908c2ecf20Sopenharmony_ci		__ioread32_copy(data + len, pipe->fifo,
918c2ecf20Sopenharmony_ci				(count - len) / sizeof(u32));
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic void glink_rpm_rx_advance(struct qcom_glink_pipe *glink_pipe,
968c2ecf20Sopenharmony_ci				 size_t count)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
998c2ecf20Sopenharmony_ci	unsigned int tail;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	tail = readl(pipe->tail);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	tail += count;
1048c2ecf20Sopenharmony_ci	if (tail >= pipe->native.length)
1058c2ecf20Sopenharmony_ci		tail -= pipe->native.length;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	writel(tail, pipe->tail);
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic size_t glink_rpm_tx_avail(struct qcom_glink_pipe *glink_pipe)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
1138c2ecf20Sopenharmony_ci	unsigned int head;
1148c2ecf20Sopenharmony_ci	unsigned int tail;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	head = readl(pipe->head);
1178c2ecf20Sopenharmony_ci	tail = readl(pipe->tail);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (tail <= head)
1208c2ecf20Sopenharmony_ci		return pipe->native.length - head + tail;
1218c2ecf20Sopenharmony_ci	else
1228c2ecf20Sopenharmony_ci		return tail - head;
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic unsigned int glink_rpm_tx_write_one(struct glink_rpm_pipe *pipe,
1268c2ecf20Sopenharmony_ci					   unsigned int head,
1278c2ecf20Sopenharmony_ci					   const void *data, size_t count)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	size_t len;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	len = min_t(size_t, count, pipe->native.length - head);
1328c2ecf20Sopenharmony_ci	if (len) {
1338c2ecf20Sopenharmony_ci		__iowrite32_copy(pipe->fifo + head, data,
1348c2ecf20Sopenharmony_ci				 len / sizeof(u32));
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	if (len != count) {
1388c2ecf20Sopenharmony_ci		__iowrite32_copy(pipe->fifo, data + len,
1398c2ecf20Sopenharmony_ci				 (count - len) / sizeof(u32));
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	head += count;
1438c2ecf20Sopenharmony_ci	if (head >= pipe->native.length)
1448c2ecf20Sopenharmony_ci		head -= pipe->native.length;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	return head;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic void glink_rpm_tx_write(struct qcom_glink_pipe *glink_pipe,
1508c2ecf20Sopenharmony_ci			       const void *hdr, size_t hlen,
1518c2ecf20Sopenharmony_ci			       const void *data, size_t dlen)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
1548c2ecf20Sopenharmony_ci	size_t tlen = hlen + dlen;
1558c2ecf20Sopenharmony_ci	size_t aligned_dlen;
1568c2ecf20Sopenharmony_ci	unsigned int head;
1578c2ecf20Sopenharmony_ci	char padding[8] = {0};
1588c2ecf20Sopenharmony_ci	size_t pad;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	/* Header length comes from glink native and is always 4 byte aligned */
1618c2ecf20Sopenharmony_ci	if (WARN(hlen % 4, "Glink Header length must be 4 bytes aligned\n"))
1628c2ecf20Sopenharmony_ci		return;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	/*
1658c2ecf20Sopenharmony_ci	 * Move the unaligned tail of the message to the padding chunk, to
1668c2ecf20Sopenharmony_ci	 * ensure word aligned accesses
1678c2ecf20Sopenharmony_ci	 */
1688c2ecf20Sopenharmony_ci	aligned_dlen = ALIGN_DOWN(dlen, 4);
1698c2ecf20Sopenharmony_ci	if (aligned_dlen != dlen)
1708c2ecf20Sopenharmony_ci		memcpy(padding, data + aligned_dlen, dlen - aligned_dlen);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	head = readl(pipe->head);
1738c2ecf20Sopenharmony_ci	head = glink_rpm_tx_write_one(pipe, head, hdr, hlen);
1748c2ecf20Sopenharmony_ci	head = glink_rpm_tx_write_one(pipe, head, data, aligned_dlen);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	pad = ALIGN(tlen, 8) - ALIGN_DOWN(tlen, 4);
1778c2ecf20Sopenharmony_ci	if (pad)
1788c2ecf20Sopenharmony_ci		head = glink_rpm_tx_write_one(pipe, head, padding, pad);
1798c2ecf20Sopenharmony_ci	writel(head, pipe->head);
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic int glink_rpm_parse_toc(struct device *dev,
1838c2ecf20Sopenharmony_ci			       void __iomem *msg_ram,
1848c2ecf20Sopenharmony_ci			       size_t msg_ram_size,
1858c2ecf20Sopenharmony_ci			       struct glink_rpm_pipe *rx,
1868c2ecf20Sopenharmony_ci			       struct glink_rpm_pipe *tx)
1878c2ecf20Sopenharmony_ci{
1888c2ecf20Sopenharmony_ci	struct rpm_toc *toc;
1898c2ecf20Sopenharmony_ci	int num_entries;
1908c2ecf20Sopenharmony_ci	unsigned int id;
1918c2ecf20Sopenharmony_ci	size_t offset;
1928c2ecf20Sopenharmony_ci	size_t size;
1938c2ecf20Sopenharmony_ci	void *buf;
1948c2ecf20Sopenharmony_ci	int i;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	buf = kzalloc(RPM_TOC_SIZE, GFP_KERNEL);
1978c2ecf20Sopenharmony_ci	if (!buf)
1988c2ecf20Sopenharmony_ci		return -ENOMEM;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	__ioread32_copy(buf, msg_ram + msg_ram_size - RPM_TOC_SIZE,
2018c2ecf20Sopenharmony_ci			RPM_TOC_SIZE / sizeof(u32));
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	toc = buf;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	if (le32_to_cpu(toc->magic) != RPM_TOC_MAGIC) {
2068c2ecf20Sopenharmony_ci		dev_err(dev, "RPM TOC has invalid magic\n");
2078c2ecf20Sopenharmony_ci		goto err_inval;
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	num_entries = le32_to_cpu(toc->count);
2118c2ecf20Sopenharmony_ci	if (num_entries > RPM_TOC_MAX_ENTRIES) {
2128c2ecf20Sopenharmony_ci		dev_err(dev, "Invalid number of toc entries\n");
2138c2ecf20Sopenharmony_ci		goto err_inval;
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	for (i = 0; i < num_entries; i++) {
2178c2ecf20Sopenharmony_ci		id = le32_to_cpu(toc->entries[i].id);
2188c2ecf20Sopenharmony_ci		offset = le32_to_cpu(toc->entries[i].offset);
2198c2ecf20Sopenharmony_ci		size = le32_to_cpu(toc->entries[i].size);
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci		if (offset > msg_ram_size || offset + size > msg_ram_size) {
2228c2ecf20Sopenharmony_ci			dev_err(dev, "TOC entry with invalid size\n");
2238c2ecf20Sopenharmony_ci			continue;
2248c2ecf20Sopenharmony_ci		}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci		switch (id) {
2278c2ecf20Sopenharmony_ci		case RPM_RX_FIFO_ID:
2288c2ecf20Sopenharmony_ci			rx->native.length = size;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci			rx->tail = msg_ram + offset;
2318c2ecf20Sopenharmony_ci			rx->head = msg_ram + offset + sizeof(u32);
2328c2ecf20Sopenharmony_ci			rx->fifo = msg_ram + offset + 2 * sizeof(u32);
2338c2ecf20Sopenharmony_ci			break;
2348c2ecf20Sopenharmony_ci		case RPM_TX_FIFO_ID:
2358c2ecf20Sopenharmony_ci			tx->native.length = size;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci			tx->tail = msg_ram + offset;
2388c2ecf20Sopenharmony_ci			tx->head = msg_ram + offset + sizeof(u32);
2398c2ecf20Sopenharmony_ci			tx->fifo = msg_ram + offset + 2 * sizeof(u32);
2408c2ecf20Sopenharmony_ci			break;
2418c2ecf20Sopenharmony_ci		}
2428c2ecf20Sopenharmony_ci	}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	if (!rx->fifo || !tx->fifo) {
2458c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to find rx and tx descriptors\n");
2468c2ecf20Sopenharmony_ci		goto err_inval;
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	kfree(buf);
2508c2ecf20Sopenharmony_ci	return 0;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cierr_inval:
2538c2ecf20Sopenharmony_ci	kfree(buf);
2548c2ecf20Sopenharmony_ci	return -EINVAL;
2558c2ecf20Sopenharmony_ci}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_cistatic int glink_rpm_probe(struct platform_device *pdev)
2588c2ecf20Sopenharmony_ci{
2598c2ecf20Sopenharmony_ci	struct qcom_glink *glink;
2608c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *rx_pipe;
2618c2ecf20Sopenharmony_ci	struct glink_rpm_pipe *tx_pipe;
2628c2ecf20Sopenharmony_ci	struct device_node *np;
2638c2ecf20Sopenharmony_ci	void __iomem *msg_ram;
2648c2ecf20Sopenharmony_ci	size_t msg_ram_size;
2658c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
2668c2ecf20Sopenharmony_ci	struct resource r;
2678c2ecf20Sopenharmony_ci	int ret;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	rx_pipe = devm_kzalloc(&pdev->dev, sizeof(*rx_pipe), GFP_KERNEL);
2708c2ecf20Sopenharmony_ci	tx_pipe = devm_kzalloc(&pdev->dev, sizeof(*tx_pipe), GFP_KERNEL);
2718c2ecf20Sopenharmony_ci	if (!rx_pipe || !tx_pipe)
2728c2ecf20Sopenharmony_ci		return -ENOMEM;
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	np = of_parse_phandle(dev->of_node, "qcom,rpm-msg-ram", 0);
2758c2ecf20Sopenharmony_ci	ret = of_address_to_resource(np, 0, &r);
2768c2ecf20Sopenharmony_ci	of_node_put(np);
2778c2ecf20Sopenharmony_ci	if (ret)
2788c2ecf20Sopenharmony_ci		return ret;
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	msg_ram = devm_ioremap(dev, r.start, resource_size(&r));
2818c2ecf20Sopenharmony_ci	msg_ram_size = resource_size(&r);
2828c2ecf20Sopenharmony_ci	if (!msg_ram)
2838c2ecf20Sopenharmony_ci		return -ENOMEM;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	ret = glink_rpm_parse_toc(dev, msg_ram, msg_ram_size,
2868c2ecf20Sopenharmony_ci				  rx_pipe, tx_pipe);
2878c2ecf20Sopenharmony_ci	if (ret)
2888c2ecf20Sopenharmony_ci		return ret;
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	/* Pipe specific accessors */
2918c2ecf20Sopenharmony_ci	rx_pipe->native.avail = glink_rpm_rx_avail;
2928c2ecf20Sopenharmony_ci	rx_pipe->native.peak = glink_rpm_rx_peak;
2938c2ecf20Sopenharmony_ci	rx_pipe->native.advance = glink_rpm_rx_advance;
2948c2ecf20Sopenharmony_ci	tx_pipe->native.avail = glink_rpm_tx_avail;
2958c2ecf20Sopenharmony_ci	tx_pipe->native.write = glink_rpm_tx_write;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	writel(0, tx_pipe->head);
2988c2ecf20Sopenharmony_ci	writel(0, rx_pipe->tail);
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	glink = qcom_glink_native_probe(&pdev->dev,
3018c2ecf20Sopenharmony_ci					0,
3028c2ecf20Sopenharmony_ci					&rx_pipe->native,
3038c2ecf20Sopenharmony_ci					&tx_pipe->native,
3048c2ecf20Sopenharmony_ci					true);
3058c2ecf20Sopenharmony_ci	if (IS_ERR(glink))
3068c2ecf20Sopenharmony_ci		return PTR_ERR(glink);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, glink);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	return 0;
3118c2ecf20Sopenharmony_ci}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_cistatic int glink_rpm_remove(struct platform_device *pdev)
3148c2ecf20Sopenharmony_ci{
3158c2ecf20Sopenharmony_ci	struct qcom_glink *glink = platform_get_drvdata(pdev);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	qcom_glink_native_remove(glink);
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	return 0;
3208c2ecf20Sopenharmony_ci}
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_cistatic const struct of_device_id glink_rpm_of_match[] = {
3238c2ecf20Sopenharmony_ci	{ .compatible = "qcom,glink-rpm" },
3248c2ecf20Sopenharmony_ci	{}
3258c2ecf20Sopenharmony_ci};
3268c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, glink_rpm_of_match);
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_cistatic struct platform_driver glink_rpm_driver = {
3298c2ecf20Sopenharmony_ci	.probe = glink_rpm_probe,
3308c2ecf20Sopenharmony_ci	.remove = glink_rpm_remove,
3318c2ecf20Sopenharmony_ci	.driver = {
3328c2ecf20Sopenharmony_ci		.name = "qcom_glink_rpm",
3338c2ecf20Sopenharmony_ci		.of_match_table = glink_rpm_of_match,
3348c2ecf20Sopenharmony_ci	},
3358c2ecf20Sopenharmony_ci};
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_cistatic int __init glink_rpm_init(void)
3388c2ecf20Sopenharmony_ci{
3398c2ecf20Sopenharmony_ci	return platform_driver_register(&glink_rpm_driver);
3408c2ecf20Sopenharmony_ci}
3418c2ecf20Sopenharmony_cisubsys_initcall(glink_rpm_init);
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_cistatic void __exit glink_rpm_exit(void)
3448c2ecf20Sopenharmony_ci{
3458c2ecf20Sopenharmony_ci	platform_driver_unregister(&glink_rpm_driver);
3468c2ecf20Sopenharmony_ci}
3478c2ecf20Sopenharmony_cimodule_exit(glink_rpm_exit);
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ciMODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>");
3508c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm GLINK RPM driver");
3518c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
352