xref: /OK3568_Linux_fs/kernel/drivers/media/mmc/siano/smssdio.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  *  smssdio.c - Siano 1xxx SDIO interface driver
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  *  Copyright 2008 Pierre Ossman
6*4882a593Smuzhiyun  *
7*4882a593Smuzhiyun  * Based on code by Siano Mobile Silicon, Inc.,
8*4882a593Smuzhiyun  * Copyright (C) 2006-2008, Uri Shkolnik
9*4882a593Smuzhiyun  *
10*4882a593Smuzhiyun  * This hardware is a bit odd in that all transfers should be done
11*4882a593Smuzhiyun  * to/from the SMSSDIO_DATA register, yet the "increase address" bit
12*4882a593Smuzhiyun  * always needs to be set.
13*4882a593Smuzhiyun  *
14*4882a593Smuzhiyun  * Also, buffers from the card are always aligned to 128 byte
15*4882a593Smuzhiyun  * boundaries.
16*4882a593Smuzhiyun  */
17*4882a593Smuzhiyun 
18*4882a593Smuzhiyun /*
19*4882a593Smuzhiyun  * General cleanup notes:
20*4882a593Smuzhiyun  *
21*4882a593Smuzhiyun  * - only typedefs should be name *_t
22*4882a593Smuzhiyun  *
23*4882a593Smuzhiyun  * - use ERR_PTR and friends for smscore_register_device()
24*4882a593Smuzhiyun  *
25*4882a593Smuzhiyun  * - smscore_getbuffer should zero fields
26*4882a593Smuzhiyun  *
27*4882a593Smuzhiyun  * Fix stop command
28*4882a593Smuzhiyun  */
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun #include "smscoreapi.h"
31*4882a593Smuzhiyun 
32*4882a593Smuzhiyun #include <linux/moduleparam.h>
33*4882a593Smuzhiyun #include <linux/slab.h>
34*4882a593Smuzhiyun #include <linux/firmware.h>
35*4882a593Smuzhiyun #include <linux/delay.h>
36*4882a593Smuzhiyun #include <linux/mmc/card.h>
37*4882a593Smuzhiyun #include <linux/mmc/sdio_func.h>
38*4882a593Smuzhiyun #include <linux/mmc/sdio_ids.h>
39*4882a593Smuzhiyun #include <linux/module.h>
40*4882a593Smuzhiyun 
41*4882a593Smuzhiyun #include "sms-cards.h"
42*4882a593Smuzhiyun #include "smsendian.h"
43*4882a593Smuzhiyun 
44*4882a593Smuzhiyun /* Registers */
45*4882a593Smuzhiyun 
46*4882a593Smuzhiyun #define SMSSDIO_DATA		0x00
47*4882a593Smuzhiyun #define SMSSDIO_INT		0x04
48*4882a593Smuzhiyun #define SMSSDIO_BLOCK_SIZE	128
49*4882a593Smuzhiyun 
50*4882a593Smuzhiyun static const struct sdio_device_id smssdio_ids[] = {
51*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_STELLAR),
52*4882a593Smuzhiyun 	 .driver_data = SMS1XXX_BOARD_SIANO_STELLAR},
53*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_NOVA_A0),
54*4882a593Smuzhiyun 	 .driver_data = SMS1XXX_BOARD_SIANO_NOVA_A},
55*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_NOVA_B0),
56*4882a593Smuzhiyun 	 .driver_data = SMS1XXX_BOARD_SIANO_NOVA_B},
57*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_VEGA_A0),
58*4882a593Smuzhiyun 	 .driver_data = SMS1XXX_BOARD_SIANO_VEGA},
59*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_VENICE),
60*4882a593Smuzhiyun 	 .driver_data = SMS1XXX_BOARD_SIANO_VEGA},
61*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_MING),
62*4882a593Smuzhiyun 	.driver_data = SMS1XXX_BOARD_SIANO_MING},
63*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_PELE),
64*4882a593Smuzhiyun 	.driver_data = SMS1XXX_BOARD_SIANO_PELE},
65*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_RIO),
66*4882a593Smuzhiyun 	.driver_data = SMS1XXX_BOARD_SIANO_RIO},
67*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_DENVER_2160),
68*4882a593Smuzhiyun 	.driver_data = SMS1XXX_BOARD_SIANO_DENVER_2160},
69*4882a593Smuzhiyun 	{SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_DENVER_1530),
70*4882a593Smuzhiyun 	.driver_data = SMS1XXX_BOARD_SIANO_DENVER_1530},
71*4882a593Smuzhiyun 	{ /* end: all zeroes */ },
72*4882a593Smuzhiyun };
73*4882a593Smuzhiyun 
74*4882a593Smuzhiyun MODULE_DEVICE_TABLE(sdio, smssdio_ids);
75*4882a593Smuzhiyun 
76*4882a593Smuzhiyun struct smssdio_device {
77*4882a593Smuzhiyun 	struct sdio_func *func;
78*4882a593Smuzhiyun 
79*4882a593Smuzhiyun 	struct smscore_device_t *coredev;
80*4882a593Smuzhiyun 
81*4882a593Smuzhiyun 	struct smscore_buffer_t *split_cb;
82*4882a593Smuzhiyun };
83*4882a593Smuzhiyun 
84*4882a593Smuzhiyun /*******************************************************************/
85*4882a593Smuzhiyun /* Siano core callbacks                                            */
86*4882a593Smuzhiyun /*******************************************************************/
87*4882a593Smuzhiyun 
smssdio_sendrequest(void * context,void * buffer,size_t size)88*4882a593Smuzhiyun static int smssdio_sendrequest(void *context, void *buffer, size_t size)
89*4882a593Smuzhiyun {
90*4882a593Smuzhiyun 	int ret = 0;
91*4882a593Smuzhiyun 	struct smssdio_device *smsdev;
92*4882a593Smuzhiyun 
93*4882a593Smuzhiyun 	smsdev = context;
94*4882a593Smuzhiyun 
95*4882a593Smuzhiyun 	sdio_claim_host(smsdev->func);
96*4882a593Smuzhiyun 
97*4882a593Smuzhiyun 	smsendian_handle_tx_message((struct sms_msg_data *) buffer);
98*4882a593Smuzhiyun 	while (size >= smsdev->func->cur_blksize) {
99*4882a593Smuzhiyun 		ret = sdio_memcpy_toio(smsdev->func, SMSSDIO_DATA,
100*4882a593Smuzhiyun 					buffer, smsdev->func->cur_blksize);
101*4882a593Smuzhiyun 		if (ret)
102*4882a593Smuzhiyun 			goto out;
103*4882a593Smuzhiyun 
104*4882a593Smuzhiyun 		buffer += smsdev->func->cur_blksize;
105*4882a593Smuzhiyun 		size -= smsdev->func->cur_blksize;
106*4882a593Smuzhiyun 	}
107*4882a593Smuzhiyun 
108*4882a593Smuzhiyun 	if (size) {
109*4882a593Smuzhiyun 		ret = sdio_memcpy_toio(smsdev->func, SMSSDIO_DATA,
110*4882a593Smuzhiyun 					buffer, size);
111*4882a593Smuzhiyun 	}
112*4882a593Smuzhiyun 
113*4882a593Smuzhiyun out:
114*4882a593Smuzhiyun 	sdio_release_host(smsdev->func);
115*4882a593Smuzhiyun 
116*4882a593Smuzhiyun 	return ret;
117*4882a593Smuzhiyun }
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun /*******************************************************************/
120*4882a593Smuzhiyun /* SDIO callbacks                                                  */
121*4882a593Smuzhiyun /*******************************************************************/
122*4882a593Smuzhiyun 
smssdio_interrupt(struct sdio_func * func)123*4882a593Smuzhiyun static void smssdio_interrupt(struct sdio_func *func)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun 	int ret;
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun 	struct smssdio_device *smsdev;
128*4882a593Smuzhiyun 	struct smscore_buffer_t *cb;
129*4882a593Smuzhiyun 	struct sms_msg_hdr *hdr;
130*4882a593Smuzhiyun 	size_t size;
131*4882a593Smuzhiyun 
132*4882a593Smuzhiyun 	smsdev = sdio_get_drvdata(func);
133*4882a593Smuzhiyun 
134*4882a593Smuzhiyun 	/*
135*4882a593Smuzhiyun 	 * The interrupt register has no defined meaning. It is just
136*4882a593Smuzhiyun 	 * a way of turning of the level triggered interrupt.
137*4882a593Smuzhiyun 	 */
138*4882a593Smuzhiyun 	(void)sdio_readb(func, SMSSDIO_INT, &ret);
139*4882a593Smuzhiyun 	if (ret) {
140*4882a593Smuzhiyun 		pr_err("Unable to read interrupt register!\n");
141*4882a593Smuzhiyun 		return;
142*4882a593Smuzhiyun 	}
143*4882a593Smuzhiyun 
144*4882a593Smuzhiyun 	if (smsdev->split_cb == NULL) {
145*4882a593Smuzhiyun 		cb = smscore_getbuffer(smsdev->coredev);
146*4882a593Smuzhiyun 		if (!cb) {
147*4882a593Smuzhiyun 			pr_err("Unable to allocate data buffer!\n");
148*4882a593Smuzhiyun 			return;
149*4882a593Smuzhiyun 		}
150*4882a593Smuzhiyun 
151*4882a593Smuzhiyun 		ret = sdio_memcpy_fromio(smsdev->func,
152*4882a593Smuzhiyun 					 cb->p,
153*4882a593Smuzhiyun 					 SMSSDIO_DATA,
154*4882a593Smuzhiyun 					 SMSSDIO_BLOCK_SIZE);
155*4882a593Smuzhiyun 		if (ret) {
156*4882a593Smuzhiyun 			pr_err("Error %d reading initial block!\n", ret);
157*4882a593Smuzhiyun 			return;
158*4882a593Smuzhiyun 		}
159*4882a593Smuzhiyun 
160*4882a593Smuzhiyun 		hdr = cb->p;
161*4882a593Smuzhiyun 
162*4882a593Smuzhiyun 		if (hdr->msg_flags & MSG_HDR_FLAG_SPLIT_MSG) {
163*4882a593Smuzhiyun 			smsdev->split_cb = cb;
164*4882a593Smuzhiyun 			return;
165*4882a593Smuzhiyun 		}
166*4882a593Smuzhiyun 
167*4882a593Smuzhiyun 		if (hdr->msg_length > smsdev->func->cur_blksize)
168*4882a593Smuzhiyun 			size = hdr->msg_length - smsdev->func->cur_blksize;
169*4882a593Smuzhiyun 		else
170*4882a593Smuzhiyun 			size = 0;
171*4882a593Smuzhiyun 	} else {
172*4882a593Smuzhiyun 		cb = smsdev->split_cb;
173*4882a593Smuzhiyun 		hdr = cb->p;
174*4882a593Smuzhiyun 
175*4882a593Smuzhiyun 		size = hdr->msg_length - sizeof(struct sms_msg_hdr);
176*4882a593Smuzhiyun 
177*4882a593Smuzhiyun 		smsdev->split_cb = NULL;
178*4882a593Smuzhiyun 	}
179*4882a593Smuzhiyun 
180*4882a593Smuzhiyun 	if (size) {
181*4882a593Smuzhiyun 		void *buffer;
182*4882a593Smuzhiyun 
183*4882a593Smuzhiyun 		buffer = cb->p + (hdr->msg_length - size);
184*4882a593Smuzhiyun 		size = ALIGN(size, SMSSDIO_BLOCK_SIZE);
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun 		BUG_ON(smsdev->func->cur_blksize != SMSSDIO_BLOCK_SIZE);
187*4882a593Smuzhiyun 
188*4882a593Smuzhiyun 		/*
189*4882a593Smuzhiyun 		 * First attempt to transfer all of it in one go...
190*4882a593Smuzhiyun 		 */
191*4882a593Smuzhiyun 		ret = sdio_memcpy_fromio(smsdev->func,
192*4882a593Smuzhiyun 					 buffer,
193*4882a593Smuzhiyun 					 SMSSDIO_DATA,
194*4882a593Smuzhiyun 					 size);
195*4882a593Smuzhiyun 		if (ret && ret != -EINVAL) {
196*4882a593Smuzhiyun 			smscore_putbuffer(smsdev->coredev, cb);
197*4882a593Smuzhiyun 			pr_err("Error %d reading data from card!\n", ret);
198*4882a593Smuzhiyun 			return;
199*4882a593Smuzhiyun 		}
200*4882a593Smuzhiyun 
201*4882a593Smuzhiyun 		/*
202*4882a593Smuzhiyun 		 * ..then fall back to one block at a time if that is
203*4882a593Smuzhiyun 		 * not possible...
204*4882a593Smuzhiyun 		 *
205*4882a593Smuzhiyun 		 * (we have to do this manually because of the
206*4882a593Smuzhiyun 		 * problem with the "increase address" bit)
207*4882a593Smuzhiyun 		 */
208*4882a593Smuzhiyun 		if (ret == -EINVAL) {
209*4882a593Smuzhiyun 			while (size) {
210*4882a593Smuzhiyun 				ret = sdio_memcpy_fromio(smsdev->func,
211*4882a593Smuzhiyun 						  buffer, SMSSDIO_DATA,
212*4882a593Smuzhiyun 						  smsdev->func->cur_blksize);
213*4882a593Smuzhiyun 				if (ret) {
214*4882a593Smuzhiyun 					smscore_putbuffer(smsdev->coredev, cb);
215*4882a593Smuzhiyun 					pr_err("Error %d reading data from card!\n",
216*4882a593Smuzhiyun 					       ret);
217*4882a593Smuzhiyun 					return;
218*4882a593Smuzhiyun 				}
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun 				buffer += smsdev->func->cur_blksize;
221*4882a593Smuzhiyun 				if (size > smsdev->func->cur_blksize)
222*4882a593Smuzhiyun 					size -= smsdev->func->cur_blksize;
223*4882a593Smuzhiyun 				else
224*4882a593Smuzhiyun 					size = 0;
225*4882a593Smuzhiyun 			}
226*4882a593Smuzhiyun 		}
227*4882a593Smuzhiyun 	}
228*4882a593Smuzhiyun 
229*4882a593Smuzhiyun 	cb->size = hdr->msg_length;
230*4882a593Smuzhiyun 	cb->offset = 0;
231*4882a593Smuzhiyun 
232*4882a593Smuzhiyun 	smsendian_handle_rx_message((struct sms_msg_data *) cb->p);
233*4882a593Smuzhiyun 	smscore_onresponse(smsdev->coredev, cb);
234*4882a593Smuzhiyun }
235*4882a593Smuzhiyun 
smssdio_probe(struct sdio_func * func,const struct sdio_device_id * id)236*4882a593Smuzhiyun static int smssdio_probe(struct sdio_func *func,
237*4882a593Smuzhiyun 			 const struct sdio_device_id *id)
238*4882a593Smuzhiyun {
239*4882a593Smuzhiyun 	int ret;
240*4882a593Smuzhiyun 
241*4882a593Smuzhiyun 	int board_id;
242*4882a593Smuzhiyun 	struct smssdio_device *smsdev;
243*4882a593Smuzhiyun 	struct smsdevice_params_t params;
244*4882a593Smuzhiyun 
245*4882a593Smuzhiyun 	board_id = id->driver_data;
246*4882a593Smuzhiyun 
247*4882a593Smuzhiyun 	smsdev = kzalloc(sizeof(struct smssdio_device), GFP_KERNEL);
248*4882a593Smuzhiyun 	if (!smsdev)
249*4882a593Smuzhiyun 		return -ENOMEM;
250*4882a593Smuzhiyun 
251*4882a593Smuzhiyun 	smsdev->func = func;
252*4882a593Smuzhiyun 
253*4882a593Smuzhiyun 	memset(&params, 0, sizeof(struct smsdevice_params_t));
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun 	params.device = &func->dev;
256*4882a593Smuzhiyun 	params.buffer_size = 0x5000;	/* ?? */
257*4882a593Smuzhiyun 	params.num_buffers = 22;	/* ?? */
258*4882a593Smuzhiyun 	params.context = smsdev;
259*4882a593Smuzhiyun 
260*4882a593Smuzhiyun 	snprintf(params.devpath, sizeof(params.devpath),
261*4882a593Smuzhiyun 		 "sdio\\%s", sdio_func_id(func));
262*4882a593Smuzhiyun 
263*4882a593Smuzhiyun 	params.sendrequest_handler = smssdio_sendrequest;
264*4882a593Smuzhiyun 
265*4882a593Smuzhiyun 	params.device_type = sms_get_board(board_id)->type;
266*4882a593Smuzhiyun 
267*4882a593Smuzhiyun 	if (params.device_type != SMS_STELLAR)
268*4882a593Smuzhiyun 		params.flags |= SMS_DEVICE_FAMILY2;
269*4882a593Smuzhiyun 	else {
270*4882a593Smuzhiyun 		/*
271*4882a593Smuzhiyun 		 * FIXME: Stellar needs special handling...
272*4882a593Smuzhiyun 		 */
273*4882a593Smuzhiyun 		ret = -ENODEV;
274*4882a593Smuzhiyun 		goto free;
275*4882a593Smuzhiyun 	}
276*4882a593Smuzhiyun 
277*4882a593Smuzhiyun 	ret = smscore_register_device(&params, &smsdev->coredev, GFP_DMA, NULL);
278*4882a593Smuzhiyun 	if (ret < 0)
279*4882a593Smuzhiyun 		goto free;
280*4882a593Smuzhiyun 
281*4882a593Smuzhiyun 	smscore_set_board_id(smsdev->coredev, board_id);
282*4882a593Smuzhiyun 
283*4882a593Smuzhiyun 	sdio_claim_host(func);
284*4882a593Smuzhiyun 
285*4882a593Smuzhiyun 	ret = sdio_enable_func(func);
286*4882a593Smuzhiyun 	if (ret)
287*4882a593Smuzhiyun 		goto release;
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun 	ret = sdio_set_block_size(func, SMSSDIO_BLOCK_SIZE);
290*4882a593Smuzhiyun 	if (ret)
291*4882a593Smuzhiyun 		goto disable;
292*4882a593Smuzhiyun 
293*4882a593Smuzhiyun 	ret = sdio_claim_irq(func, smssdio_interrupt);
294*4882a593Smuzhiyun 	if (ret)
295*4882a593Smuzhiyun 		goto disable;
296*4882a593Smuzhiyun 
297*4882a593Smuzhiyun 	sdio_set_drvdata(func, smsdev);
298*4882a593Smuzhiyun 
299*4882a593Smuzhiyun 	sdio_release_host(func);
300*4882a593Smuzhiyun 
301*4882a593Smuzhiyun 	ret = smscore_start_device(smsdev->coredev);
302*4882a593Smuzhiyun 	if (ret < 0)
303*4882a593Smuzhiyun 		goto reclaim;
304*4882a593Smuzhiyun 
305*4882a593Smuzhiyun 	return 0;
306*4882a593Smuzhiyun 
307*4882a593Smuzhiyun reclaim:
308*4882a593Smuzhiyun 	sdio_claim_host(func);
309*4882a593Smuzhiyun 	sdio_release_irq(func);
310*4882a593Smuzhiyun disable:
311*4882a593Smuzhiyun 	sdio_disable_func(func);
312*4882a593Smuzhiyun release:
313*4882a593Smuzhiyun 	sdio_release_host(func);
314*4882a593Smuzhiyun 	smscore_unregister_device(smsdev->coredev);
315*4882a593Smuzhiyun free:
316*4882a593Smuzhiyun 	kfree(smsdev);
317*4882a593Smuzhiyun 
318*4882a593Smuzhiyun 	return ret;
319*4882a593Smuzhiyun }
320*4882a593Smuzhiyun 
smssdio_remove(struct sdio_func * func)321*4882a593Smuzhiyun static void smssdio_remove(struct sdio_func *func)
322*4882a593Smuzhiyun {
323*4882a593Smuzhiyun 	struct smssdio_device *smsdev;
324*4882a593Smuzhiyun 
325*4882a593Smuzhiyun 	smsdev = sdio_get_drvdata(func);
326*4882a593Smuzhiyun 
327*4882a593Smuzhiyun 	/* FIXME: racy! */
328*4882a593Smuzhiyun 	if (smsdev->split_cb)
329*4882a593Smuzhiyun 		smscore_putbuffer(smsdev->coredev, smsdev->split_cb);
330*4882a593Smuzhiyun 
331*4882a593Smuzhiyun 	smscore_unregister_device(smsdev->coredev);
332*4882a593Smuzhiyun 
333*4882a593Smuzhiyun 	sdio_claim_host(func);
334*4882a593Smuzhiyun 	sdio_release_irq(func);
335*4882a593Smuzhiyun 	sdio_disable_func(func);
336*4882a593Smuzhiyun 	sdio_release_host(func);
337*4882a593Smuzhiyun 
338*4882a593Smuzhiyun 	kfree(smsdev);
339*4882a593Smuzhiyun }
340*4882a593Smuzhiyun 
341*4882a593Smuzhiyun static struct sdio_driver smssdio_driver = {
342*4882a593Smuzhiyun 	.name = "smssdio",
343*4882a593Smuzhiyun 	.id_table = smssdio_ids,
344*4882a593Smuzhiyun 	.probe = smssdio_probe,
345*4882a593Smuzhiyun 	.remove = smssdio_remove,
346*4882a593Smuzhiyun };
347*4882a593Smuzhiyun 
348*4882a593Smuzhiyun /*******************************************************************/
349*4882a593Smuzhiyun /* Module functions                                                */
350*4882a593Smuzhiyun /*******************************************************************/
351*4882a593Smuzhiyun 
smssdio_module_init(void)352*4882a593Smuzhiyun static int __init smssdio_module_init(void)
353*4882a593Smuzhiyun {
354*4882a593Smuzhiyun 	int ret = 0;
355*4882a593Smuzhiyun 
356*4882a593Smuzhiyun 	printk(KERN_INFO "smssdio: Siano SMS1xxx SDIO driver\n");
357*4882a593Smuzhiyun 	printk(KERN_INFO "smssdio: Copyright Pierre Ossman\n");
358*4882a593Smuzhiyun 
359*4882a593Smuzhiyun 	ret = sdio_register_driver(&smssdio_driver);
360*4882a593Smuzhiyun 
361*4882a593Smuzhiyun 	return ret;
362*4882a593Smuzhiyun }
363*4882a593Smuzhiyun 
smssdio_module_exit(void)364*4882a593Smuzhiyun static void __exit smssdio_module_exit(void)
365*4882a593Smuzhiyun {
366*4882a593Smuzhiyun 	sdio_unregister_driver(&smssdio_driver);
367*4882a593Smuzhiyun }
368*4882a593Smuzhiyun 
369*4882a593Smuzhiyun module_init(smssdio_module_init);
370*4882a593Smuzhiyun module_exit(smssdio_module_exit);
371*4882a593Smuzhiyun 
372*4882a593Smuzhiyun MODULE_DESCRIPTION("Siano SMS1xxx SDIO driver");
373*4882a593Smuzhiyun MODULE_AUTHOR("Pierre Ossman");
374*4882a593Smuzhiyun MODULE_LICENSE("GPL");
375