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