18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * SCR24x PCMCIA Smart Card Reader Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2005-2006 TL Sudheendran 68c2ecf20Sopenharmony_ci * Copyright (C) 2016 Lubomir Rintel 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Derived from "scr24x_v4.2.6_Release.tar.gz" driver by TL Sudheendran. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/device.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/cdev.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/fs.h> 178c2ecf20Sopenharmony_ci#include <linux/io.h> 188c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <pcmcia/cistpl.h> 218c2ecf20Sopenharmony_ci#include <pcmcia/ds.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define CCID_HEADER_SIZE 10 248c2ecf20Sopenharmony_ci#define CCID_LENGTH_OFFSET 1 258c2ecf20Sopenharmony_ci#define CCID_MAX_LEN 271 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define SCR24X_DATA(n) (1 + n) 288c2ecf20Sopenharmony_ci#define SCR24X_CMD_STATUS 7 298c2ecf20Sopenharmony_ci#define CMD_START 0x40 308c2ecf20Sopenharmony_ci#define CMD_WRITE_BYTE 0x41 318c2ecf20Sopenharmony_ci#define CMD_READ_BYTE 0x42 328c2ecf20Sopenharmony_ci#define STATUS_BUSY 0x80 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct scr24x_dev { 358c2ecf20Sopenharmony_ci struct device *dev; 368c2ecf20Sopenharmony_ci struct cdev c_dev; 378c2ecf20Sopenharmony_ci unsigned char buf[CCID_MAX_LEN]; 388c2ecf20Sopenharmony_ci int devno; 398c2ecf20Sopenharmony_ci struct mutex lock; 408c2ecf20Sopenharmony_ci struct kref refcnt; 418c2ecf20Sopenharmony_ci u8 __iomem *regs; 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define SCR24X_DEVS 8 458c2ecf20Sopenharmony_cistatic DECLARE_BITMAP(scr24x_minors, SCR24X_DEVS); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic struct class *scr24x_class; 488c2ecf20Sopenharmony_cistatic dev_t scr24x_devt; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic void scr24x_delete(struct kref *kref) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct scr24x_dev *dev = container_of(kref, struct scr24x_dev, 538c2ecf20Sopenharmony_ci refcnt); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci kfree(dev); 568c2ecf20Sopenharmony_ci} 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic int scr24x_wait_ready(struct scr24x_dev *dev) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci u_char status; 618c2ecf20Sopenharmony_ci int timeout = 100; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci do { 648c2ecf20Sopenharmony_ci status = ioread8(dev->regs + SCR24X_CMD_STATUS); 658c2ecf20Sopenharmony_ci if (!(status & STATUS_BUSY)) 668c2ecf20Sopenharmony_ci return 0; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci msleep(20); 698c2ecf20Sopenharmony_ci } while (--timeout); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci return -EIO; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int scr24x_open(struct inode *inode, struct file *filp) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct scr24x_dev *dev = container_of(inode->i_cdev, 778c2ecf20Sopenharmony_ci struct scr24x_dev, c_dev); 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci kref_get(&dev->refcnt); 808c2ecf20Sopenharmony_ci filp->private_data = dev; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci return stream_open(inode, filp); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int scr24x_release(struct inode *inode, struct file *filp) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct scr24x_dev *dev = filp->private_data; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* We must not take the dev->lock here as scr24x_delete() 908c2ecf20Sopenharmony_ci * might be called to remove the dev structure altogether. 918c2ecf20Sopenharmony_ci * We don't need the lock anyway, since after the reference 928c2ecf20Sopenharmony_ci * acquired in probe() is released in remove() the chrdev 938c2ecf20Sopenharmony_ci * is already unregistered and noone can possibly acquire 948c2ecf20Sopenharmony_ci * a reference via open() anymore. */ 958c2ecf20Sopenharmony_ci kref_put(&dev->refcnt, scr24x_delete); 968c2ecf20Sopenharmony_ci return 0; 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic int read_chunk(struct scr24x_dev *dev, size_t offset, size_t limit) 1008c2ecf20Sopenharmony_ci{ 1018c2ecf20Sopenharmony_ci size_t i, y; 1028c2ecf20Sopenharmony_ci int ret; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci for (i = offset; i < limit; i += 5) { 1058c2ecf20Sopenharmony_ci iowrite8(CMD_READ_BYTE, dev->regs + SCR24X_CMD_STATUS); 1068c2ecf20Sopenharmony_ci ret = scr24x_wait_ready(dev); 1078c2ecf20Sopenharmony_ci if (ret < 0) 1088c2ecf20Sopenharmony_ci return ret; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci for (y = 0; y < 5 && i + y < limit; y++) 1118c2ecf20Sopenharmony_ci dev->buf[i + y] = ioread8(dev->regs + SCR24X_DATA(y)); 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return 0; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic ssize_t scr24x_read(struct file *filp, char __user *buf, size_t count, 1188c2ecf20Sopenharmony_ci loff_t *ppos) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci struct scr24x_dev *dev = filp->private_data; 1218c2ecf20Sopenharmony_ci int ret; 1228c2ecf20Sopenharmony_ci int len; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (count < CCID_HEADER_SIZE) 1258c2ecf20Sopenharmony_ci return -EINVAL; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&dev->lock)) 1288c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (!dev->dev) { 1318c2ecf20Sopenharmony_ci ret = -ENODEV; 1328c2ecf20Sopenharmony_ci goto out; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci ret = scr24x_wait_ready(dev); 1368c2ecf20Sopenharmony_ci if (ret < 0) 1378c2ecf20Sopenharmony_ci goto out; 1388c2ecf20Sopenharmony_ci len = CCID_HEADER_SIZE; 1398c2ecf20Sopenharmony_ci ret = read_chunk(dev, 0, len); 1408c2ecf20Sopenharmony_ci if (ret < 0) 1418c2ecf20Sopenharmony_ci goto out; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci len += le32_to_cpu(*(__le32 *)(&dev->buf[CCID_LENGTH_OFFSET])); 1448c2ecf20Sopenharmony_ci if (len > sizeof(dev->buf)) { 1458c2ecf20Sopenharmony_ci ret = -EIO; 1468c2ecf20Sopenharmony_ci goto out; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci ret = read_chunk(dev, CCID_HEADER_SIZE, len); 1498c2ecf20Sopenharmony_ci if (ret < 0) 1508c2ecf20Sopenharmony_ci goto out; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (len < count) 1538c2ecf20Sopenharmony_ci count = len; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (copy_to_user(buf, dev->buf, count)) { 1568c2ecf20Sopenharmony_ci ret = -EFAULT; 1578c2ecf20Sopenharmony_ci goto out; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci ret = count; 1618c2ecf20Sopenharmony_ciout: 1628c2ecf20Sopenharmony_ci mutex_unlock(&dev->lock); 1638c2ecf20Sopenharmony_ci return ret; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic ssize_t scr24x_write(struct file *filp, const char __user *buf, 1678c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci struct scr24x_dev *dev = filp->private_data; 1708c2ecf20Sopenharmony_ci size_t i, y; 1718c2ecf20Sopenharmony_ci int ret; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&dev->lock)) 1748c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci if (!dev->dev) { 1778c2ecf20Sopenharmony_ci ret = -ENODEV; 1788c2ecf20Sopenharmony_ci goto out; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci if (count > sizeof(dev->buf)) { 1828c2ecf20Sopenharmony_ci ret = -EINVAL; 1838c2ecf20Sopenharmony_ci goto out; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (copy_from_user(dev->buf, buf, count)) { 1878c2ecf20Sopenharmony_ci ret = -EFAULT; 1888c2ecf20Sopenharmony_ci goto out; 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ret = scr24x_wait_ready(dev); 1928c2ecf20Sopenharmony_ci if (ret < 0) 1938c2ecf20Sopenharmony_ci goto out; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci iowrite8(CMD_START, dev->regs + SCR24X_CMD_STATUS); 1968c2ecf20Sopenharmony_ci ret = scr24x_wait_ready(dev); 1978c2ecf20Sopenharmony_ci if (ret < 0) 1988c2ecf20Sopenharmony_ci goto out; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci for (i = 0; i < count; i += 5) { 2018c2ecf20Sopenharmony_ci for (y = 0; y < 5 && i + y < count; y++) 2028c2ecf20Sopenharmony_ci iowrite8(dev->buf[i + y], dev->regs + SCR24X_DATA(y)); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci iowrite8(CMD_WRITE_BYTE, dev->regs + SCR24X_CMD_STATUS); 2058c2ecf20Sopenharmony_ci ret = scr24x_wait_ready(dev); 2068c2ecf20Sopenharmony_ci if (ret < 0) 2078c2ecf20Sopenharmony_ci goto out; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci ret = count; 2118c2ecf20Sopenharmony_ciout: 2128c2ecf20Sopenharmony_ci mutex_unlock(&dev->lock); 2138c2ecf20Sopenharmony_ci return ret; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic const struct file_operations scr24x_fops = { 2178c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2188c2ecf20Sopenharmony_ci .read = scr24x_read, 2198c2ecf20Sopenharmony_ci .write = scr24x_write, 2208c2ecf20Sopenharmony_ci .open = scr24x_open, 2218c2ecf20Sopenharmony_ci .release = scr24x_release, 2228c2ecf20Sopenharmony_ci .llseek = no_llseek, 2238c2ecf20Sopenharmony_ci}; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistatic int scr24x_config_check(struct pcmcia_device *link, void *priv_data) 2268c2ecf20Sopenharmony_ci{ 2278c2ecf20Sopenharmony_ci if (resource_size(link->resource[PCMCIA_IOPORT_0]) != 0x11) 2288c2ecf20Sopenharmony_ci return -ENODEV; 2298c2ecf20Sopenharmony_ci return pcmcia_request_io(link); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int scr24x_probe(struct pcmcia_device *link) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct scr24x_dev *dev; 2358c2ecf20Sopenharmony_ci int ret; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 2388c2ecf20Sopenharmony_ci if (!dev) 2398c2ecf20Sopenharmony_ci return -ENOMEM; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci dev->devno = find_first_zero_bit(scr24x_minors, SCR24X_DEVS); 2428c2ecf20Sopenharmony_ci if (dev->devno >= SCR24X_DEVS) { 2438c2ecf20Sopenharmony_ci ret = -EBUSY; 2448c2ecf20Sopenharmony_ci goto err; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci mutex_init(&dev->lock); 2488c2ecf20Sopenharmony_ci kref_init(&dev->refcnt); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci link->priv = dev; 2518c2ecf20Sopenharmony_ci link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci ret = pcmcia_loop_config(link, scr24x_config_check, NULL); 2548c2ecf20Sopenharmony_ci if (ret < 0) 2558c2ecf20Sopenharmony_ci goto err; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci dev->dev = &link->dev; 2588c2ecf20Sopenharmony_ci dev->regs = devm_ioport_map(&link->dev, 2598c2ecf20Sopenharmony_ci link->resource[PCMCIA_IOPORT_0]->start, 2608c2ecf20Sopenharmony_ci resource_size(link->resource[PCMCIA_IOPORT_0])); 2618c2ecf20Sopenharmony_ci if (!dev->regs) { 2628c2ecf20Sopenharmony_ci ret = -EIO; 2638c2ecf20Sopenharmony_ci goto err; 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci cdev_init(&dev->c_dev, &scr24x_fops); 2678c2ecf20Sopenharmony_ci dev->c_dev.owner = THIS_MODULE; 2688c2ecf20Sopenharmony_ci dev->c_dev.ops = &scr24x_fops; 2698c2ecf20Sopenharmony_ci ret = cdev_add(&dev->c_dev, MKDEV(MAJOR(scr24x_devt), dev->devno), 1); 2708c2ecf20Sopenharmony_ci if (ret < 0) 2718c2ecf20Sopenharmony_ci goto err; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci ret = pcmcia_enable_device(link); 2748c2ecf20Sopenharmony_ci if (ret < 0) { 2758c2ecf20Sopenharmony_ci pcmcia_disable_device(link); 2768c2ecf20Sopenharmony_ci goto err; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci device_create(scr24x_class, NULL, MKDEV(MAJOR(scr24x_devt), dev->devno), 2808c2ecf20Sopenharmony_ci NULL, "scr24x%d", dev->devno); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci dev_info(&link->dev, "SCR24x Chip Card Interface\n"); 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_cierr: 2868c2ecf20Sopenharmony_ci if (dev->devno < SCR24X_DEVS) 2878c2ecf20Sopenharmony_ci clear_bit(dev->devno, scr24x_minors); 2888c2ecf20Sopenharmony_ci kfree (dev); 2898c2ecf20Sopenharmony_ci return ret; 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_cistatic void scr24x_remove(struct pcmcia_device *link) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci struct scr24x_dev *dev = (struct scr24x_dev *)link->priv; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci device_destroy(scr24x_class, MKDEV(MAJOR(scr24x_devt), dev->devno)); 2978c2ecf20Sopenharmony_ci mutex_lock(&dev->lock); 2988c2ecf20Sopenharmony_ci pcmcia_disable_device(link); 2998c2ecf20Sopenharmony_ci cdev_del(&dev->c_dev); 3008c2ecf20Sopenharmony_ci clear_bit(dev->devno, scr24x_minors); 3018c2ecf20Sopenharmony_ci dev->dev = NULL; 3028c2ecf20Sopenharmony_ci mutex_unlock(&dev->lock); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci kref_put(&dev->refcnt, scr24x_delete); 3058c2ecf20Sopenharmony_ci} 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_cistatic const struct pcmcia_device_id scr24x_ids[] = { 3088c2ecf20Sopenharmony_ci PCMCIA_DEVICE_PROD_ID12("HP", "PC Card Smart Card Reader", 3098c2ecf20Sopenharmony_ci 0x53cb94f9, 0xbfdf89a5), 3108c2ecf20Sopenharmony_ci PCMCIA_DEVICE_PROD_ID1("SCR241 PCMCIA", 0x6271efa3), 3118c2ecf20Sopenharmony_ci PCMCIA_DEVICE_PROD_ID1("SCR243 PCMCIA", 0x2054e8de), 3128c2ecf20Sopenharmony_ci PCMCIA_DEVICE_PROD_ID1("SCR24x PCMCIA", 0x54a33665), 3138c2ecf20Sopenharmony_ci PCMCIA_DEVICE_NULL 3148c2ecf20Sopenharmony_ci}; 3158c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pcmcia, scr24x_ids); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_cistatic struct pcmcia_driver scr24x_driver = { 3188c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3198c2ecf20Sopenharmony_ci .name = "scr24x_cs", 3208c2ecf20Sopenharmony_ci .probe = scr24x_probe, 3218c2ecf20Sopenharmony_ci .remove = scr24x_remove, 3228c2ecf20Sopenharmony_ci .id_table = scr24x_ids, 3238c2ecf20Sopenharmony_ci}; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_cistatic int __init scr24x_init(void) 3268c2ecf20Sopenharmony_ci{ 3278c2ecf20Sopenharmony_ci int ret; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci scr24x_class = class_create(THIS_MODULE, "scr24x"); 3308c2ecf20Sopenharmony_ci if (IS_ERR(scr24x_class)) 3318c2ecf20Sopenharmony_ci return PTR_ERR(scr24x_class); 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci ret = alloc_chrdev_region(&scr24x_devt, 0, SCR24X_DEVS, "scr24x"); 3348c2ecf20Sopenharmony_ci if (ret < 0) { 3358c2ecf20Sopenharmony_ci class_destroy(scr24x_class); 3368c2ecf20Sopenharmony_ci return ret; 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci ret = pcmcia_register_driver(&scr24x_driver); 3408c2ecf20Sopenharmony_ci if (ret < 0) { 3418c2ecf20Sopenharmony_ci unregister_chrdev_region(scr24x_devt, SCR24X_DEVS); 3428c2ecf20Sopenharmony_ci class_destroy(scr24x_class); 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci return ret; 3468c2ecf20Sopenharmony_ci} 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_cistatic void __exit scr24x_exit(void) 3498c2ecf20Sopenharmony_ci{ 3508c2ecf20Sopenharmony_ci pcmcia_unregister_driver(&scr24x_driver); 3518c2ecf20Sopenharmony_ci unregister_chrdev_region(scr24x_devt, SCR24X_DEVS); 3528c2ecf20Sopenharmony_ci class_destroy(scr24x_class); 3538c2ecf20Sopenharmony_ci} 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_cimodule_init(scr24x_init); 3568c2ecf20Sopenharmony_cimodule_exit(scr24x_exit); 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lubomir Rintel"); 3598c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SCR24x PCMCIA Smart Card Reader Driver"); 3608c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 361