1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
24020f2d7SAlex Dubov /*
34020f2d7SAlex Dubov * tifm_core.c - TI FlashMedia driver
44020f2d7SAlex Dubov *
54020f2d7SAlex Dubov * Copyright (C) 2006 Alex Dubov <oakad@yahoo.com>
64020f2d7SAlex Dubov */
74020f2d7SAlex Dubov
84020f2d7SAlex Dubov #include <linux/tifm.h>
95a0e3ad6STejun Heo #include <linux/slab.h>
104020f2d7SAlex Dubov #include <linux/init.h>
114020f2d7SAlex Dubov #include <linux/idr.h>
12eb12a679SPaul Gortmaker #include <linux/module.h>
134020f2d7SAlex Dubov
144020f2d7SAlex Dubov #define DRIVER_NAME "tifm_core"
154552f0cbSAlex Dubov #define DRIVER_VERSION "0.8"
164020f2d7SAlex Dubov
173540af8fSAlex Dubov static struct workqueue_struct *workqueue;
184020f2d7SAlex Dubov static DEFINE_IDR(tifm_adapter_idr);
194020f2d7SAlex Dubov static DEFINE_SPINLOCK(tifm_adapter_lock);
204020f2d7SAlex Dubov
tifm_media_type_name(unsigned char type,unsigned char nt)21e23f2b8aSAlex Dubov static const char *tifm_media_type_name(unsigned char type, unsigned char nt)
224020f2d7SAlex Dubov {
23e23f2b8aSAlex Dubov const char *card_type_name[3][3] = {
24e23f2b8aSAlex Dubov { "SmartMedia/xD", "MemoryStick", "MMC/SD" },
25e23f2b8aSAlex Dubov { "XD", "MS", "SD"},
26e23f2b8aSAlex Dubov { "xd", "ms", "sd"}
27e23f2b8aSAlex Dubov };
28e23f2b8aSAlex Dubov
29e23f2b8aSAlex Dubov if (nt > 2 || type < 1 || type > 3)
304020f2d7SAlex Dubov return NULL;
31e23f2b8aSAlex Dubov return card_type_name[nt][type - 1];
324020f2d7SAlex Dubov }
334020f2d7SAlex Dubov
tifm_dev_match(struct tifm_dev * sock,struct tifm_device_id * id)34e23f2b8aSAlex Dubov static int tifm_dev_match(struct tifm_dev *sock, struct tifm_device_id *id)
354020f2d7SAlex Dubov {
36e23f2b8aSAlex Dubov if (sock->type == id->type)
374020f2d7SAlex Dubov return 1;
38e23f2b8aSAlex Dubov return 0;
39e23f2b8aSAlex Dubov }
40e23f2b8aSAlex Dubov
tifm_bus_match(struct device * dev,struct device_driver * drv)41e23f2b8aSAlex Dubov static int tifm_bus_match(struct device *dev, struct device_driver *drv)
42e23f2b8aSAlex Dubov {
43e23f2b8aSAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
44e23f2b8aSAlex Dubov struct tifm_driver *fm_drv = container_of(drv, struct tifm_driver,
45e23f2b8aSAlex Dubov driver);
46e23f2b8aSAlex Dubov struct tifm_device_id *ids = fm_drv->id_table;
47e23f2b8aSAlex Dubov
48e23f2b8aSAlex Dubov if (ids) {
49e23f2b8aSAlex Dubov while (ids->type) {
50e23f2b8aSAlex Dubov if (tifm_dev_match(sock, ids))
51e23f2b8aSAlex Dubov return 1;
52e23f2b8aSAlex Dubov ++ids;
53e23f2b8aSAlex Dubov }
54e23f2b8aSAlex Dubov }
55e23f2b8aSAlex Dubov return 0;
564020f2d7SAlex Dubov }
574020f2d7SAlex Dubov
tifm_uevent(const struct device * dev,struct kobj_uevent_env * env)58*2a81ada3SGreg Kroah-Hartman static int tifm_uevent(const struct device *dev, struct kobj_uevent_env *env)
594020f2d7SAlex Dubov {
60*2a81ada3SGreg Kroah-Hartman const struct tifm_dev *sock = container_of_const(dev, struct tifm_dev, dev);
614020f2d7SAlex Dubov
627eff2e7aSKay Sievers if (add_uevent_var(env, "TIFM_CARD_TYPE=%s", tifm_media_type_name(sock->type, 1)))
634020f2d7SAlex Dubov return -ENOMEM;
644020f2d7SAlex Dubov
654020f2d7SAlex Dubov return 0;
664020f2d7SAlex Dubov }
674020f2d7SAlex Dubov
tifm_device_probe(struct device * dev)688dc4a61eSAlex Dubov static int tifm_device_probe(struct device *dev)
698dc4a61eSAlex Dubov {
708dc4a61eSAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
718dc4a61eSAlex Dubov struct tifm_driver *drv = container_of(dev->driver, struct tifm_driver,
728dc4a61eSAlex Dubov driver);
738dc4a61eSAlex Dubov int rc = -ENODEV;
748dc4a61eSAlex Dubov
758dc4a61eSAlex Dubov get_device(dev);
768dc4a61eSAlex Dubov if (dev->driver && drv->probe) {
778dc4a61eSAlex Dubov rc = drv->probe(sock);
788dc4a61eSAlex Dubov if (!rc)
798dc4a61eSAlex Dubov return 0;
808dc4a61eSAlex Dubov }
818dc4a61eSAlex Dubov put_device(dev);
828dc4a61eSAlex Dubov return rc;
838dc4a61eSAlex Dubov }
848dc4a61eSAlex Dubov
tifm_dummy_event(struct tifm_dev * sock)858dc4a61eSAlex Dubov static void tifm_dummy_event(struct tifm_dev *sock)
868dc4a61eSAlex Dubov {
878dc4a61eSAlex Dubov return;
888dc4a61eSAlex Dubov }
898dc4a61eSAlex Dubov
tifm_device_remove(struct device * dev)90fc7a6209SUwe Kleine-König static void tifm_device_remove(struct device *dev)
918dc4a61eSAlex Dubov {
928dc4a61eSAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
938dc4a61eSAlex Dubov struct tifm_driver *drv = container_of(dev->driver, struct tifm_driver,
948dc4a61eSAlex Dubov driver);
958dc4a61eSAlex Dubov
968dc4a61eSAlex Dubov if (dev->driver && drv->remove) {
978dc4a61eSAlex Dubov sock->card_event = tifm_dummy_event;
988dc4a61eSAlex Dubov sock->data_event = tifm_dummy_event;
998dc4a61eSAlex Dubov drv->remove(sock);
1008dc4a61eSAlex Dubov sock->dev.driver = NULL;
1018dc4a61eSAlex Dubov }
1028dc4a61eSAlex Dubov
1038dc4a61eSAlex Dubov put_device(dev);
1048dc4a61eSAlex Dubov }
1058dc4a61eSAlex Dubov
10641d78f74SAlex Dubov #ifdef CONFIG_PM
10741d78f74SAlex Dubov
tifm_device_suspend(struct device * dev,pm_message_t state)10841d78f74SAlex Dubov static int tifm_device_suspend(struct device *dev, pm_message_t state)
10941d78f74SAlex Dubov {
11091f8d011SAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
1118dc4a61eSAlex Dubov struct tifm_driver *drv = container_of(dev->driver, struct tifm_driver,
1128dc4a61eSAlex Dubov driver);
11341d78f74SAlex Dubov
1148dc4a61eSAlex Dubov if (dev->driver && drv->suspend)
11591f8d011SAlex Dubov return drv->suspend(sock, state);
11641d78f74SAlex Dubov return 0;
11741d78f74SAlex Dubov }
11841d78f74SAlex Dubov
tifm_device_resume(struct device * dev)11941d78f74SAlex Dubov static int tifm_device_resume(struct device *dev)
12041d78f74SAlex Dubov {
12191f8d011SAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
1228dc4a61eSAlex Dubov struct tifm_driver *drv = container_of(dev->driver, struct tifm_driver,
1238dc4a61eSAlex Dubov driver);
12441d78f74SAlex Dubov
1258dc4a61eSAlex Dubov if (dev->driver && drv->resume)
12691f8d011SAlex Dubov return drv->resume(sock);
12741d78f74SAlex Dubov return 0;
12841d78f74SAlex Dubov }
12941d78f74SAlex Dubov
13041d78f74SAlex Dubov #else
13141d78f74SAlex Dubov
13241d78f74SAlex Dubov #define tifm_device_suspend NULL
13341d78f74SAlex Dubov #define tifm_device_resume NULL
13441d78f74SAlex Dubov
13541d78f74SAlex Dubov #endif /* CONFIG_PM */
13641d78f74SAlex Dubov
type_show(struct device * dev,struct device_attribute * attr,char * buf)1374e64f223SAlex Dubov static ssize_t type_show(struct device *dev, struct device_attribute *attr,
1384e64f223SAlex Dubov char *buf)
1394e64f223SAlex Dubov {
1404e64f223SAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
1414e64f223SAlex Dubov return sprintf(buf, "%x", sock->type);
1424e64f223SAlex Dubov }
14306cf261bSGreg Kroah-Hartman static DEVICE_ATTR_RO(type);
1444e64f223SAlex Dubov
14506cf261bSGreg Kroah-Hartman static struct attribute *tifm_dev_attrs[] = {
14606cf261bSGreg Kroah-Hartman &dev_attr_type.attr,
14706cf261bSGreg Kroah-Hartman NULL,
1484e64f223SAlex Dubov };
14906cf261bSGreg Kroah-Hartman ATTRIBUTE_GROUPS(tifm_dev);
1504e64f223SAlex Dubov
1514020f2d7SAlex Dubov static struct bus_type tifm_bus_type = {
1524020f2d7SAlex Dubov .name = "tifm",
15306cf261bSGreg Kroah-Hartman .dev_groups = tifm_dev_groups,
154e23f2b8aSAlex Dubov .match = tifm_bus_match,
1554020f2d7SAlex Dubov .uevent = tifm_uevent,
1568dc4a61eSAlex Dubov .probe = tifm_device_probe,
1578dc4a61eSAlex Dubov .remove = tifm_device_remove,
15841d78f74SAlex Dubov .suspend = tifm_device_suspend,
15941d78f74SAlex Dubov .resume = tifm_device_resume
1604020f2d7SAlex Dubov };
1614020f2d7SAlex Dubov
tifm_free(struct device * dev)1627dd817d0STony Jones static void tifm_free(struct device *dev)
1634020f2d7SAlex Dubov {
1647dd817d0STony Jones struct tifm_adapter *fm = container_of(dev, struct tifm_adapter, dev);
1654020f2d7SAlex Dubov
1664020f2d7SAlex Dubov kfree(fm);
1674020f2d7SAlex Dubov }
1684020f2d7SAlex Dubov
1694020f2d7SAlex Dubov static struct class tifm_adapter_class = {
1704020f2d7SAlex Dubov .name = "tifm_adapter",
1717dd817d0STony Jones .dev_release = tifm_free
1724020f2d7SAlex Dubov };
1734020f2d7SAlex Dubov
tifm_alloc_adapter(unsigned int num_sockets,struct device * dev)1746113ed73SAlex Dubov struct tifm_adapter *tifm_alloc_adapter(unsigned int num_sockets,
1756113ed73SAlex Dubov struct device *dev)
1764020f2d7SAlex Dubov {
1774020f2d7SAlex Dubov struct tifm_adapter *fm;
1784020f2d7SAlex Dubov
179aee1bbf6SLen Baker fm = kzalloc(struct_size(fm, sockets, num_sockets), GFP_KERNEL);
1804020f2d7SAlex Dubov if (fm) {
1817dd817d0STony Jones fm->dev.class = &tifm_adapter_class;
1827dd817d0STony Jones fm->dev.parent = dev;
1837dd817d0STony Jones device_initialize(&fm->dev);
1846113ed73SAlex Dubov spin_lock_init(&fm->lock);
1856113ed73SAlex Dubov fm->num_sockets = num_sockets;
1864020f2d7SAlex Dubov }
1874020f2d7SAlex Dubov return fm;
1884020f2d7SAlex Dubov }
1894020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_alloc_adapter);
1904020f2d7SAlex Dubov
tifm_add_adapter(struct tifm_adapter * fm)1913540af8fSAlex Dubov int tifm_add_adapter(struct tifm_adapter *fm)
1924020f2d7SAlex Dubov {
1934020f2d7SAlex Dubov int rc;
1944020f2d7SAlex Dubov
19557f2667cSTejun Heo idr_preload(GFP_KERNEL);
1964020f2d7SAlex Dubov spin_lock(&tifm_adapter_lock);
19757f2667cSTejun Heo rc = idr_alloc(&tifm_adapter_idr, fm, 0, 0, GFP_NOWAIT);
19857f2667cSTejun Heo if (rc >= 0)
19957f2667cSTejun Heo fm->id = rc;
2004020f2d7SAlex Dubov spin_unlock(&tifm_adapter_lock);
20157f2667cSTejun Heo idr_preload_end();
20257f2667cSTejun Heo if (rc < 0)
2036113ed73SAlex Dubov return rc;
2046113ed73SAlex Dubov
2050bad16aaSKay Sievers dev_set_name(&fm->dev, "tifm%u", fm->id);
2067dd817d0STony Jones rc = device_add(&fm->dev);
2073540af8fSAlex Dubov if (rc) {
2084020f2d7SAlex Dubov spin_lock(&tifm_adapter_lock);
2094020f2d7SAlex Dubov idr_remove(&tifm_adapter_idr, fm->id);
2104020f2d7SAlex Dubov spin_unlock(&tifm_adapter_lock);
2113540af8fSAlex Dubov }
2126113ed73SAlex Dubov
2134020f2d7SAlex Dubov return rc;
2144020f2d7SAlex Dubov }
2154020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_add_adapter);
2164020f2d7SAlex Dubov
tifm_remove_adapter(struct tifm_adapter * fm)2174020f2d7SAlex Dubov void tifm_remove_adapter(struct tifm_adapter *fm)
2184020f2d7SAlex Dubov {
2196113ed73SAlex Dubov unsigned int cnt;
2206113ed73SAlex Dubov
2213540af8fSAlex Dubov flush_workqueue(workqueue);
2226113ed73SAlex Dubov for (cnt = 0; cnt < fm->num_sockets; ++cnt) {
2236113ed73SAlex Dubov if (fm->sockets[cnt])
2246113ed73SAlex Dubov device_unregister(&fm->sockets[cnt]->dev);
2256113ed73SAlex Dubov }
2264020f2d7SAlex Dubov
2274020f2d7SAlex Dubov spin_lock(&tifm_adapter_lock);
2284020f2d7SAlex Dubov idr_remove(&tifm_adapter_idr, fm->id);
2294020f2d7SAlex Dubov spin_unlock(&tifm_adapter_lock);
2307dd817d0STony Jones device_del(&fm->dev);
2314020f2d7SAlex Dubov }
2324020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_remove_adapter);
2334020f2d7SAlex Dubov
tifm_free_adapter(struct tifm_adapter * fm)2346113ed73SAlex Dubov void tifm_free_adapter(struct tifm_adapter *fm)
2356113ed73SAlex Dubov {
2367dd817d0STony Jones put_device(&fm->dev);
2376113ed73SAlex Dubov }
2386113ed73SAlex Dubov EXPORT_SYMBOL(tifm_free_adapter);
2396113ed73SAlex Dubov
tifm_free_device(struct device * dev)2404020f2d7SAlex Dubov void tifm_free_device(struct device *dev)
2414020f2d7SAlex Dubov {
2422428a8feSAlex Dubov struct tifm_dev *sock = container_of(dev, struct tifm_dev, dev);
2432428a8feSAlex Dubov kfree(sock);
2444020f2d7SAlex Dubov }
2454020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_free_device);
2464020f2d7SAlex Dubov
tifm_alloc_device(struct tifm_adapter * fm,unsigned int id,unsigned char type)2472428a8feSAlex Dubov struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm, unsigned int id,
2482428a8feSAlex Dubov unsigned char type)
2494020f2d7SAlex Dubov {
2502428a8feSAlex Dubov struct tifm_dev *sock = NULL;
2514020f2d7SAlex Dubov
2522428a8feSAlex Dubov if (!tifm_media_type_name(type, 0))
2532428a8feSAlex Dubov return sock;
2548e02f858SAlex Dubov
2552428a8feSAlex Dubov sock = kzalloc(sizeof(struct tifm_dev), GFP_KERNEL);
2562428a8feSAlex Dubov if (sock) {
2572428a8feSAlex Dubov spin_lock_init(&sock->lock);
2582428a8feSAlex Dubov sock->type = type;
2592428a8feSAlex Dubov sock->socket_id = id;
2602428a8feSAlex Dubov sock->card_event = tifm_dummy_event;
2612428a8feSAlex Dubov sock->data_event = tifm_dummy_event;
2622428a8feSAlex Dubov
2637dd817d0STony Jones sock->dev.parent = fm->dev.parent;
2642428a8feSAlex Dubov sock->dev.bus = &tifm_bus_type;
2657dd817d0STony Jones sock->dev.dma_mask = fm->dev.parent->dma_mask;
2662428a8feSAlex Dubov sock->dev.release = tifm_free_device;
2672428a8feSAlex Dubov
2680bad16aaSKay Sievers dev_set_name(&sock->dev, "tifm_%s%u:%u",
2690bad16aaSKay Sievers tifm_media_type_name(type, 2), fm->id, id);
2702428a8feSAlex Dubov printk(KERN_INFO DRIVER_NAME
2712428a8feSAlex Dubov ": %s card detected in socket %u:%u\n",
2722428a8feSAlex Dubov tifm_media_type_name(type, 0), fm->id, id);
2734020f2d7SAlex Dubov }
2742428a8feSAlex Dubov return sock;
2754020f2d7SAlex Dubov }
2764020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_alloc_device);
2774020f2d7SAlex Dubov
tifm_eject(struct tifm_dev * sock)2784020f2d7SAlex Dubov void tifm_eject(struct tifm_dev *sock)
2794020f2d7SAlex Dubov {
2804020f2d7SAlex Dubov struct tifm_adapter *fm = dev_get_drvdata(sock->dev.parent);
2814020f2d7SAlex Dubov fm->eject(fm, sock);
2824020f2d7SAlex Dubov }
2834020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_eject);
2844020f2d7SAlex Dubov
tifm_has_ms_pif(struct tifm_dev * sock)285baf8532aSAlex Dubov int tifm_has_ms_pif(struct tifm_dev *sock)
286baf8532aSAlex Dubov {
287baf8532aSAlex Dubov struct tifm_adapter *fm = dev_get_drvdata(sock->dev.parent);
288baf8532aSAlex Dubov return fm->has_ms_pif(fm, sock);
289baf8532aSAlex Dubov }
290baf8532aSAlex Dubov EXPORT_SYMBOL(tifm_has_ms_pif);
291baf8532aSAlex Dubov
tifm_map_sg(struct tifm_dev * sock,struct scatterlist * sg,int nents,int direction)2924020f2d7SAlex Dubov int tifm_map_sg(struct tifm_dev *sock, struct scatterlist *sg, int nents,
2934020f2d7SAlex Dubov int direction)
2944020f2d7SAlex Dubov {
295639fd77eSChristophe JAILLET return dma_map_sg(&to_pci_dev(sock->dev.parent)->dev, sg, nents,
296639fd77eSChristophe JAILLET direction);
2974020f2d7SAlex Dubov }
2984020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_map_sg);
2994020f2d7SAlex Dubov
tifm_unmap_sg(struct tifm_dev * sock,struct scatterlist * sg,int nents,int direction)3004020f2d7SAlex Dubov void tifm_unmap_sg(struct tifm_dev *sock, struct scatterlist *sg, int nents,
3014020f2d7SAlex Dubov int direction)
3024020f2d7SAlex Dubov {
303639fd77eSChristophe JAILLET dma_unmap_sg(&to_pci_dev(sock->dev.parent)->dev, sg, nents, direction);
3044020f2d7SAlex Dubov }
3054020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_unmap_sg);
3064020f2d7SAlex Dubov
tifm_queue_work(struct work_struct * work)3073540af8fSAlex Dubov void tifm_queue_work(struct work_struct *work)
3083540af8fSAlex Dubov {
3093540af8fSAlex Dubov queue_work(workqueue, work);
3103540af8fSAlex Dubov }
3113540af8fSAlex Dubov EXPORT_SYMBOL(tifm_queue_work);
3123540af8fSAlex Dubov
tifm_register_driver(struct tifm_driver * drv)3134020f2d7SAlex Dubov int tifm_register_driver(struct tifm_driver *drv)
3144020f2d7SAlex Dubov {
3154020f2d7SAlex Dubov drv->driver.bus = &tifm_bus_type;
3164020f2d7SAlex Dubov
3174020f2d7SAlex Dubov return driver_register(&drv->driver);
3184020f2d7SAlex Dubov }
3194020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_register_driver);
3204020f2d7SAlex Dubov
tifm_unregister_driver(struct tifm_driver * drv)3214020f2d7SAlex Dubov void tifm_unregister_driver(struct tifm_driver *drv)
3224020f2d7SAlex Dubov {
3234020f2d7SAlex Dubov driver_unregister(&drv->driver);
3244020f2d7SAlex Dubov }
3254020f2d7SAlex Dubov EXPORT_SYMBOL(tifm_unregister_driver);
3264020f2d7SAlex Dubov
tifm_init(void)3274020f2d7SAlex Dubov static int __init tifm_init(void)
3284020f2d7SAlex Dubov {
3293540af8fSAlex Dubov int rc;
3304020f2d7SAlex Dubov
33158a69cb4STejun Heo workqueue = create_freezable_workqueue("tifm");
3323540af8fSAlex Dubov if (!workqueue)
3333540af8fSAlex Dubov return -ENOMEM;
3343540af8fSAlex Dubov
3353540af8fSAlex Dubov rc = bus_register(&tifm_bus_type);
3363540af8fSAlex Dubov
3374020f2d7SAlex Dubov if (rc)
3383540af8fSAlex Dubov goto err_out_wq;
3393540af8fSAlex Dubov
3403540af8fSAlex Dubov rc = class_register(&tifm_adapter_class);
3413540af8fSAlex Dubov if (!rc)
3423540af8fSAlex Dubov return 0;
3433540af8fSAlex Dubov
3444020f2d7SAlex Dubov bus_unregister(&tifm_bus_type);
3453540af8fSAlex Dubov
3463540af8fSAlex Dubov err_out_wq:
3473540af8fSAlex Dubov destroy_workqueue(workqueue);
3484020f2d7SAlex Dubov
3494020f2d7SAlex Dubov return rc;
3504020f2d7SAlex Dubov }
3514020f2d7SAlex Dubov
tifm_exit(void)3524020f2d7SAlex Dubov static void __exit tifm_exit(void)
3534020f2d7SAlex Dubov {
3544020f2d7SAlex Dubov class_unregister(&tifm_adapter_class);
3554020f2d7SAlex Dubov bus_unregister(&tifm_bus_type);
3563540af8fSAlex Dubov destroy_workqueue(workqueue);
3574020f2d7SAlex Dubov }
3584020f2d7SAlex Dubov
3594020f2d7SAlex Dubov subsys_initcall(tifm_init);
3604020f2d7SAlex Dubov module_exit(tifm_exit);
3614020f2d7SAlex Dubov
3624020f2d7SAlex Dubov MODULE_LICENSE("GPL");
3634020f2d7SAlex Dubov MODULE_AUTHOR("Alex Dubov");
3644020f2d7SAlex Dubov MODULE_DESCRIPTION("TI FlashMedia core driver");
3654020f2d7SAlex Dubov MODULE_LICENSE("GPL");
3664020f2d7SAlex Dubov MODULE_VERSION(DRIVER_VERSION);
367