18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2017 Linaro Ltd. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/kernel.h> 78c2ecf20Sopenharmony_ci#include <linux/cdev.h> 88c2ecf20Sopenharmony_ci#include <linux/err.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/of.h> 128c2ecf20Sopenharmony_ci#include <linux/of_reserved_mem.h> 138c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/qcom_scm.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define QCOM_RMTFS_MEM_DEV_MAX (MINORMASK + 1) 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic dev_t qcom_rmtfs_mem_major; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistruct qcom_rmtfs_mem { 248c2ecf20Sopenharmony_ci struct device dev; 258c2ecf20Sopenharmony_ci struct cdev cdev; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci void *base; 288c2ecf20Sopenharmony_ci phys_addr_t addr; 298c2ecf20Sopenharmony_ci phys_addr_t size; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci unsigned int client_id; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci unsigned int perms; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic ssize_t qcom_rmtfs_mem_show(struct device *dev, 378c2ecf20Sopenharmony_ci struct device_attribute *attr, 388c2ecf20Sopenharmony_ci char *buf); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic DEVICE_ATTR(phys_addr, 0444, qcom_rmtfs_mem_show, NULL); 418c2ecf20Sopenharmony_cistatic DEVICE_ATTR(size, 0444, qcom_rmtfs_mem_show, NULL); 428c2ecf20Sopenharmony_cistatic DEVICE_ATTR(client_id, 0444, qcom_rmtfs_mem_show, NULL); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic ssize_t qcom_rmtfs_mem_show(struct device *dev, 458c2ecf20Sopenharmony_ci struct device_attribute *attr, 468c2ecf20Sopenharmony_ci char *buf) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = container_of(dev, 498c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem, 508c2ecf20Sopenharmony_ci dev); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci if (attr == &dev_attr_phys_addr) 538c2ecf20Sopenharmony_ci return sprintf(buf, "%pa\n", &rmtfs_mem->addr); 548c2ecf20Sopenharmony_ci if (attr == &dev_attr_size) 558c2ecf20Sopenharmony_ci return sprintf(buf, "%pa\n", &rmtfs_mem->size); 568c2ecf20Sopenharmony_ci if (attr == &dev_attr_client_id) 578c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", rmtfs_mem->client_id); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci return -EINVAL; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic struct attribute *qcom_rmtfs_mem_attrs[] = { 638c2ecf20Sopenharmony_ci &dev_attr_phys_addr.attr, 648c2ecf20Sopenharmony_ci &dev_attr_size.attr, 658c2ecf20Sopenharmony_ci &dev_attr_client_id.attr, 668c2ecf20Sopenharmony_ci NULL 678c2ecf20Sopenharmony_ci}; 688c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(qcom_rmtfs_mem); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic int qcom_rmtfs_mem_open(struct inode *inode, struct file *filp) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = container_of(inode->i_cdev, 738c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem, 748c2ecf20Sopenharmony_ci cdev); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci get_device(&rmtfs_mem->dev); 778c2ecf20Sopenharmony_ci filp->private_data = rmtfs_mem; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci return 0; 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_cistatic ssize_t qcom_rmtfs_mem_read(struct file *filp, 828c2ecf20Sopenharmony_ci char __user *buf, size_t count, loff_t *f_pos) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = filp->private_data; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (*f_pos >= rmtfs_mem->size) 878c2ecf20Sopenharmony_ci return 0; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (*f_pos + count >= rmtfs_mem->size) 908c2ecf20Sopenharmony_ci count = rmtfs_mem->size - *f_pos; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci if (copy_to_user(buf, rmtfs_mem->base + *f_pos, count)) 938c2ecf20Sopenharmony_ci return -EFAULT; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci *f_pos += count; 968c2ecf20Sopenharmony_ci return count; 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic ssize_t qcom_rmtfs_mem_write(struct file *filp, 1008c2ecf20Sopenharmony_ci const char __user *buf, size_t count, 1018c2ecf20Sopenharmony_ci loff_t *f_pos) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = filp->private_data; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (*f_pos >= rmtfs_mem->size) 1068c2ecf20Sopenharmony_ci return 0; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (*f_pos + count >= rmtfs_mem->size) 1098c2ecf20Sopenharmony_ci count = rmtfs_mem->size - *f_pos; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (copy_from_user(rmtfs_mem->base + *f_pos, buf, count)) 1128c2ecf20Sopenharmony_ci return -EFAULT; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci *f_pos += count; 1158c2ecf20Sopenharmony_ci return count; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic int qcom_rmtfs_mem_release(struct inode *inode, struct file *filp) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = filp->private_data; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci put_device(&rmtfs_mem->dev); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci return 0; 1258c2ecf20Sopenharmony_ci} 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic struct class rmtfs_class = { 1288c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1298c2ecf20Sopenharmony_ci .name = "rmtfs", 1308c2ecf20Sopenharmony_ci}; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int qcom_rmtfs_mem_mmap(struct file *filep, struct vm_area_struct *vma) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = filep->private_data; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (vma->vm_end - vma->vm_start > rmtfs_mem->size) { 1378c2ecf20Sopenharmony_ci dev_dbg(&rmtfs_mem->dev, 1388c2ecf20Sopenharmony_ci "vm_end[%lu] - vm_start[%lu] [%lu] > mem->size[%pa]\n", 1398c2ecf20Sopenharmony_ci vma->vm_end, vma->vm_start, 1408c2ecf20Sopenharmony_ci (vma->vm_end - vma->vm_start), &rmtfs_mem->size); 1418c2ecf20Sopenharmony_ci return -EINVAL; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); 1458c2ecf20Sopenharmony_ci return remap_pfn_range(vma, 1468c2ecf20Sopenharmony_ci vma->vm_start, 1478c2ecf20Sopenharmony_ci rmtfs_mem->addr >> PAGE_SHIFT, 1488c2ecf20Sopenharmony_ci vma->vm_end - vma->vm_start, 1498c2ecf20Sopenharmony_ci vma->vm_page_prot); 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic const struct file_operations qcom_rmtfs_mem_fops = { 1538c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1548c2ecf20Sopenharmony_ci .open = qcom_rmtfs_mem_open, 1558c2ecf20Sopenharmony_ci .read = qcom_rmtfs_mem_read, 1568c2ecf20Sopenharmony_ci .write = qcom_rmtfs_mem_write, 1578c2ecf20Sopenharmony_ci .release = qcom_rmtfs_mem_release, 1588c2ecf20Sopenharmony_ci .llseek = default_llseek, 1598c2ecf20Sopenharmony_ci .mmap = qcom_rmtfs_mem_mmap, 1608c2ecf20Sopenharmony_ci}; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic void qcom_rmtfs_mem_release_device(struct device *dev) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = container_of(dev, 1658c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem, 1668c2ecf20Sopenharmony_ci dev); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci kfree(rmtfs_mem); 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic int qcom_rmtfs_mem_probe(struct platform_device *pdev) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 1748c2ecf20Sopenharmony_ci struct qcom_scm_vmperm perms[2]; 1758c2ecf20Sopenharmony_ci struct reserved_mem *rmem; 1768c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem; 1778c2ecf20Sopenharmony_ci u32 client_id; 1788c2ecf20Sopenharmony_ci u32 vmid; 1798c2ecf20Sopenharmony_ci int ret; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci rmem = of_reserved_mem_lookup(node); 1828c2ecf20Sopenharmony_ci if (!rmem) { 1838c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire memory region\n"); 1848c2ecf20Sopenharmony_ci return -EINVAL; 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci ret = of_property_read_u32(node, "qcom,client-id", &client_id); 1888c2ecf20Sopenharmony_ci if (ret) { 1898c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to parse \"qcom,client-id\"\n"); 1908c2ecf20Sopenharmony_ci return ret; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci rmtfs_mem = kzalloc(sizeof(*rmtfs_mem), GFP_KERNEL); 1958c2ecf20Sopenharmony_ci if (!rmtfs_mem) 1968c2ecf20Sopenharmony_ci return -ENOMEM; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci rmtfs_mem->addr = rmem->base; 1998c2ecf20Sopenharmony_ci rmtfs_mem->client_id = client_id; 2008c2ecf20Sopenharmony_ci rmtfs_mem->size = rmem->size; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci device_initialize(&rmtfs_mem->dev); 2038c2ecf20Sopenharmony_ci rmtfs_mem->dev.parent = &pdev->dev; 2048c2ecf20Sopenharmony_ci rmtfs_mem->dev.groups = qcom_rmtfs_mem_groups; 2058c2ecf20Sopenharmony_ci rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci rmtfs_mem->base = devm_memremap(&rmtfs_mem->dev, rmtfs_mem->addr, 2088c2ecf20Sopenharmony_ci rmtfs_mem->size, MEMREMAP_WC); 2098c2ecf20Sopenharmony_ci if (IS_ERR(rmtfs_mem->base)) { 2108c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to remap rmtfs_mem region\n"); 2118c2ecf20Sopenharmony_ci ret = PTR_ERR(rmtfs_mem->base); 2128c2ecf20Sopenharmony_ci goto put_device; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci cdev_init(&rmtfs_mem->cdev, &qcom_rmtfs_mem_fops); 2168c2ecf20Sopenharmony_ci rmtfs_mem->cdev.owner = THIS_MODULE; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci dev_set_name(&rmtfs_mem->dev, "qcom_rmtfs_mem%d", client_id); 2198c2ecf20Sopenharmony_ci rmtfs_mem->dev.id = client_id; 2208c2ecf20Sopenharmony_ci rmtfs_mem->dev.class = &rmtfs_class; 2218c2ecf20Sopenharmony_ci rmtfs_mem->dev.devt = MKDEV(MAJOR(qcom_rmtfs_mem_major), client_id); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci ret = cdev_device_add(&rmtfs_mem->cdev, &rmtfs_mem->dev); 2248c2ecf20Sopenharmony_ci if (ret) { 2258c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to add cdev: %d\n", ret); 2268c2ecf20Sopenharmony_ci goto put_device; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci ret = of_property_read_u32(node, "qcom,vmid", &vmid); 2308c2ecf20Sopenharmony_ci if (ret < 0 && ret != -EINVAL) { 2318c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to parse qcom,vmid\n"); 2328c2ecf20Sopenharmony_ci goto remove_cdev; 2338c2ecf20Sopenharmony_ci } else if (!ret) { 2348c2ecf20Sopenharmony_ci if (!qcom_scm_is_available()) { 2358c2ecf20Sopenharmony_ci ret = -EPROBE_DEFER; 2368c2ecf20Sopenharmony_ci goto remove_cdev; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci perms[0].vmid = QCOM_SCM_VMID_HLOS; 2408c2ecf20Sopenharmony_ci perms[0].perm = QCOM_SCM_PERM_RW; 2418c2ecf20Sopenharmony_ci perms[1].vmid = vmid; 2428c2ecf20Sopenharmony_ci perms[1].perm = QCOM_SCM_PERM_RW; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci rmtfs_mem->perms = BIT(QCOM_SCM_VMID_HLOS); 2458c2ecf20Sopenharmony_ci ret = qcom_scm_assign_mem(rmtfs_mem->addr, rmtfs_mem->size, 2468c2ecf20Sopenharmony_ci &rmtfs_mem->perms, perms, 2); 2478c2ecf20Sopenharmony_ci if (ret < 0) { 2488c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "assign memory failed\n"); 2498c2ecf20Sopenharmony_ci goto remove_cdev; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci } 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci dev_set_drvdata(&pdev->dev, rmtfs_mem); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci return 0; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ciremove_cdev: 2588c2ecf20Sopenharmony_ci cdev_device_del(&rmtfs_mem->cdev, &rmtfs_mem->dev); 2598c2ecf20Sopenharmony_ciput_device: 2608c2ecf20Sopenharmony_ci put_device(&rmtfs_mem->dev); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci return ret; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic int qcom_rmtfs_mem_remove(struct platform_device *pdev) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci struct qcom_rmtfs_mem *rmtfs_mem = dev_get_drvdata(&pdev->dev); 2688c2ecf20Sopenharmony_ci struct qcom_scm_vmperm perm; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci if (rmtfs_mem->perms) { 2718c2ecf20Sopenharmony_ci perm.vmid = QCOM_SCM_VMID_HLOS; 2728c2ecf20Sopenharmony_ci perm.perm = QCOM_SCM_PERM_RW; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci qcom_scm_assign_mem(rmtfs_mem->addr, rmtfs_mem->size, 2758c2ecf20Sopenharmony_ci &rmtfs_mem->perms, &perm, 1); 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci cdev_device_del(&rmtfs_mem->cdev, &rmtfs_mem->dev); 2798c2ecf20Sopenharmony_ci put_device(&rmtfs_mem->dev); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci return 0; 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic const struct of_device_id qcom_rmtfs_mem_of_match[] = { 2858c2ecf20Sopenharmony_ci { .compatible = "qcom,rmtfs-mem" }, 2868c2ecf20Sopenharmony_ci {} 2878c2ecf20Sopenharmony_ci}; 2888c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_rmtfs_mem_of_match); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cistatic struct platform_driver qcom_rmtfs_mem_driver = { 2918c2ecf20Sopenharmony_ci .probe = qcom_rmtfs_mem_probe, 2928c2ecf20Sopenharmony_ci .remove = qcom_rmtfs_mem_remove, 2938c2ecf20Sopenharmony_ci .driver = { 2948c2ecf20Sopenharmony_ci .name = "qcom_rmtfs_mem", 2958c2ecf20Sopenharmony_ci .of_match_table = qcom_rmtfs_mem_of_match, 2968c2ecf20Sopenharmony_ci }, 2978c2ecf20Sopenharmony_ci}; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cistatic int __init qcom_rmtfs_mem_init(void) 3008c2ecf20Sopenharmony_ci{ 3018c2ecf20Sopenharmony_ci int ret; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci ret = class_register(&rmtfs_class); 3048c2ecf20Sopenharmony_ci if (ret) 3058c2ecf20Sopenharmony_ci return ret; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci ret = alloc_chrdev_region(&qcom_rmtfs_mem_major, 0, 3088c2ecf20Sopenharmony_ci QCOM_RMTFS_MEM_DEV_MAX, "qcom_rmtfs_mem"); 3098c2ecf20Sopenharmony_ci if (ret < 0) { 3108c2ecf20Sopenharmony_ci pr_err("qcom_rmtfs_mem: failed to allocate char dev region\n"); 3118c2ecf20Sopenharmony_ci goto unregister_class; 3128c2ecf20Sopenharmony_ci } 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci ret = platform_driver_register(&qcom_rmtfs_mem_driver); 3158c2ecf20Sopenharmony_ci if (ret < 0) { 3168c2ecf20Sopenharmony_ci pr_err("qcom_rmtfs_mem: failed to register rmtfs_mem driver\n"); 3178c2ecf20Sopenharmony_ci goto unregister_chrdev; 3188c2ecf20Sopenharmony_ci } 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci return 0; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ciunregister_chrdev: 3238c2ecf20Sopenharmony_ci unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); 3248c2ecf20Sopenharmony_ciunregister_class: 3258c2ecf20Sopenharmony_ci class_unregister(&rmtfs_class); 3268c2ecf20Sopenharmony_ci return ret; 3278c2ecf20Sopenharmony_ci} 3288c2ecf20Sopenharmony_cimodule_init(qcom_rmtfs_mem_init); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_cistatic void __exit qcom_rmtfs_mem_exit(void) 3318c2ecf20Sopenharmony_ci{ 3328c2ecf20Sopenharmony_ci platform_driver_unregister(&qcom_rmtfs_mem_driver); 3338c2ecf20Sopenharmony_ci unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); 3348c2ecf20Sopenharmony_ci class_unregister(&rmtfs_class); 3358c2ecf20Sopenharmony_ci} 3368c2ecf20Sopenharmony_cimodule_exit(qcom_rmtfs_mem_exit); 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Linaro Ltd"); 3398c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Remote Filesystem memory driver"); 3408c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 341