xref: /openbmc/linux/drivers/misc/tifm_core.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
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