xref: /linux/drivers/char/mwave/mwavedd.c (revision 3b4df2320ef66a8762f5a283fdf9c0e09e6ca6b2)
1 /*
2 *
3 * mwavedd.c -- mwave device driver
4 *
5 *
6 * Written By: Mike Sullivan IBM Corporation
7 *
8 * Copyright (C) 1999 IBM Corporation
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * NO WARRANTY
21 * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
22 * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
23 * LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
24 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
25 * solely responsible for determining the appropriateness of using and
26 * distributing the Program and assumes all risks associated with its
27 * exercise of rights under this Agreement, including but not limited to
28 * the risks and costs of program errors, damage to or loss of data,
29 * programs or equipment, and unavailability or interruption of operations.
30 *
31 * DISCLAIMER OF LIABILITY
32 * NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
33 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
35 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
36 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
37 * USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
38 * HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
39 *
40 * You should have received a copy of the GNU General Public License
41 * along with this program; if not, write to the Free Software
42 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
43 *
44 *
45 * 10/23/2000 - Alpha Release
46 *	First release to the public
47 */
48 
49 #include <linux/module.h>
50 #include <linux/kernel.h>
51 #include <linux/fs.h>
52 #include <linux/init.h>
53 #include <linux/major.h>
54 #include <linux/miscdevice.h>
55 #include <linux/device.h>
56 #include <linux/serial.h>
57 #include <linux/sched.h>
58 #include <linux/spinlock.h>
59 #include <linux/mutex.h>
60 #include <linux/delay.h>
61 #include <linux/serial_8250.h>
62 #include <linux/nospec.h>
63 #include "smapi.h"
64 #include "mwavedd.h"
65 #include "3780i.h"
66 #include "tp3780i.h"
67 
68 MODULE_DESCRIPTION("3780i Advanced Communications Processor (Mwave) driver");
69 MODULE_AUTHOR("Mike Sullivan and Paul Schroeder");
70 MODULE_LICENSE("GPL");
71 
72 /*
73 * These parameters support the setting of MWave resources. Note that no
74 * checks are made against other devices (ie. superio) for conflicts.
75 * We'll depend on users using the tpctl utility to do that for now
76 */
77 static DEFINE_MUTEX(mwave_mutex);
78 int mwave_debug = 0;
79 int mwave_3780i_irq = 0;
80 int mwave_3780i_io = 0;
81 int mwave_uart_irq = 0;
82 int mwave_uart_io = 0;
83 module_param(mwave_debug, int, 0);
84 module_param_hw(mwave_3780i_irq, int, irq, 0);
85 module_param_hw(mwave_3780i_io, int, ioport, 0);
86 module_param_hw(mwave_uart_irq, int, irq, 0);
87 module_param_hw(mwave_uart_io, int, ioport, 0);
88 
89 MWAVE_DEVICE_DATA mwave_s_mdd;
90 
91 static long mwave_ioctl(struct file *file, unsigned int iocmd,
92 							unsigned long ioarg)
93 {
94 	unsigned int retval = 0;
95 	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
96 	void __user *arg = (void __user *)ioarg;
97 
98 	PRINTK_4(TRACE_MWAVE,
99 		"mwavedd::mwave_ioctl, entry file %p cmd %x arg %x\n",
100 		file, iocmd, (int) ioarg);
101 
102 	switch (iocmd) {
103 
104 		case IOCTL_MW_RESET:
105 			PRINTK_1(TRACE_MWAVE,
106 				"mwavedd::mwave_ioctl, IOCTL_MW_RESET"
107 				" calling tp3780I_ResetDSP\n");
108 			mutex_lock(&mwave_mutex);
109 			retval = tp3780I_ResetDSP(&pDrvData->rBDData);
110 			mutex_unlock(&mwave_mutex);
111 			PRINTK_2(TRACE_MWAVE,
112 				"mwavedd::mwave_ioctl, IOCTL_MW_RESET"
113 				" retval %x from tp3780I_ResetDSP\n",
114 				retval);
115 			break;
116 
117 		case IOCTL_MW_RUN:
118 			PRINTK_1(TRACE_MWAVE,
119 				"mwavedd::mwave_ioctl, IOCTL_MW_RUN"
120 				" calling tp3780I_StartDSP\n");
121 			mutex_lock(&mwave_mutex);
122 			retval = tp3780I_StartDSP(&pDrvData->rBDData);
123 			mutex_unlock(&mwave_mutex);
124 			PRINTK_2(TRACE_MWAVE,
125 				"mwavedd::mwave_ioctl, IOCTL_MW_RUN"
126 				" retval %x from tp3780I_StartDSP\n",
127 				retval);
128 			break;
129 
130 		case IOCTL_MW_DSP_ABILITIES: {
131 			MW_ABILITIES rAbilities;
132 
133 			PRINTK_1(TRACE_MWAVE,
134 				"mwavedd::mwave_ioctl,"
135 				" IOCTL_MW_DSP_ABILITIES calling"
136 				" tp3780I_QueryAbilities\n");
137 			mutex_lock(&mwave_mutex);
138 			retval = tp3780I_QueryAbilities(&pDrvData->rBDData,
139 					&rAbilities);
140 			mutex_unlock(&mwave_mutex);
141 			PRINTK_2(TRACE_MWAVE,
142 				"mwavedd::mwave_ioctl, IOCTL_MW_DSP_ABILITIES"
143 				" retval %x from tp3780I_QueryAbilities\n",
144 				retval);
145 			if (retval == 0) {
146 				if( copy_to_user(arg, &rAbilities,
147 							sizeof(MW_ABILITIES)) )
148 					return -EFAULT;
149 			}
150 			PRINTK_2(TRACE_MWAVE,
151 				"mwavedd::mwave_ioctl, IOCTL_MW_DSP_ABILITIES"
152 				" exit retval %x\n",
153 				retval);
154 		}
155 			break;
156 
157 		case IOCTL_MW_READ_DATA:
158 		case IOCTL_MW_READCLEAR_DATA: {
159 			MW_READWRITE rReadData;
160 			unsigned short __user *pusBuffer = NULL;
161 
162 			if( copy_from_user(&rReadData, arg,
163 						sizeof(MW_READWRITE)) )
164 				return -EFAULT;
165 			pusBuffer = (unsigned short __user *) (rReadData.pBuf);
166 
167 			PRINTK_4(TRACE_MWAVE,
168 				"mwavedd::mwave_ioctl IOCTL_MW_READ_DATA,"
169 				" size %lx, ioarg %lx pusBuffer %p\n",
170 				rReadData.ulDataLength, ioarg, pusBuffer);
171 			mutex_lock(&mwave_mutex);
172 			retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
173 					iocmd,
174 					pusBuffer,
175 					rReadData.ulDataLength,
176 					rReadData.usDspAddress);
177 			mutex_unlock(&mwave_mutex);
178 		}
179 			break;
180 
181 		case IOCTL_MW_READ_INST: {
182 			MW_READWRITE rReadData;
183 			unsigned short __user *pusBuffer = NULL;
184 
185 			if( copy_from_user(&rReadData, arg,
186 						sizeof(MW_READWRITE)) )
187 				return -EFAULT;
188 			pusBuffer = (unsigned short __user *) (rReadData.pBuf);
189 
190 			PRINTK_4(TRACE_MWAVE,
191 				"mwavedd::mwave_ioctl IOCTL_MW_READ_INST,"
192 				" size %lx, ioarg %lx pusBuffer %p\n",
193 				rReadData.ulDataLength / 2, ioarg,
194 				pusBuffer);
195 			mutex_lock(&mwave_mutex);
196 			retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
197 				iocmd, pusBuffer,
198 				rReadData.ulDataLength / 2,
199 				rReadData.usDspAddress);
200 			mutex_unlock(&mwave_mutex);
201 		}
202 			break;
203 
204 		case IOCTL_MW_WRITE_DATA: {
205 			MW_READWRITE rWriteData;
206 			unsigned short __user *pusBuffer = NULL;
207 
208 			if( copy_from_user(&rWriteData, arg,
209 						sizeof(MW_READWRITE)) )
210 				return -EFAULT;
211 			pusBuffer = (unsigned short __user *) (rWriteData.pBuf);
212 
213 			PRINTK_4(TRACE_MWAVE,
214 				"mwavedd::mwave_ioctl IOCTL_MW_WRITE_DATA,"
215 				" size %lx, ioarg %lx pusBuffer %p\n",
216 				rWriteData.ulDataLength, ioarg,
217 				pusBuffer);
218 			mutex_lock(&mwave_mutex);
219 			retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
220 					iocmd, pusBuffer,
221 					rWriteData.ulDataLength,
222 					rWriteData.usDspAddress);
223 			mutex_unlock(&mwave_mutex);
224 		}
225 			break;
226 
227 		case IOCTL_MW_WRITE_INST: {
228 			MW_READWRITE rWriteData;
229 			unsigned short __user *pusBuffer = NULL;
230 
231 			if( copy_from_user(&rWriteData, arg,
232 						sizeof(MW_READWRITE)) )
233 				return -EFAULT;
234 			pusBuffer = (unsigned short __user *)(rWriteData.pBuf);
235 
236 			PRINTK_4(TRACE_MWAVE,
237 				"mwavedd::mwave_ioctl IOCTL_MW_WRITE_INST,"
238 				" size %lx, ioarg %lx pusBuffer %p\n",
239 				rWriteData.ulDataLength, ioarg,
240 				pusBuffer);
241 			mutex_lock(&mwave_mutex);
242 			retval = tp3780I_ReadWriteDspIStore(&pDrvData->rBDData,
243 					iocmd, pusBuffer,
244 					rWriteData.ulDataLength,
245 					rWriteData.usDspAddress);
246 			mutex_unlock(&mwave_mutex);
247 		}
248 			break;
249 
250 		case IOCTL_MW_REGISTER_IPC: {
251 			unsigned int ipcnum = (unsigned int) ioarg;
252 
253 			if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) {
254 				PRINTK_ERROR(KERN_ERR_MWAVE
255 						"mwavedd::mwave_ioctl:"
256 						" IOCTL_MW_REGISTER_IPC:"
257 						" Error: Invalid ipcnum %x\n",
258 						ipcnum);
259 				return -EINVAL;
260 			}
261 			ipcnum = array_index_nospec(ipcnum,
262 						    ARRAY_SIZE(pDrvData->IPCs));
263 			PRINTK_3(TRACE_MWAVE,
264 				"mwavedd::mwave_ioctl IOCTL_MW_REGISTER_IPC"
265 				" ipcnum %x entry usIntCount %x\n",
266 				ipcnum,
267 				pDrvData->IPCs[ipcnum].usIntCount);
268 
269 			mutex_lock(&mwave_mutex);
270 			pDrvData->IPCs[ipcnum].bIsHere = false;
271 			pDrvData->IPCs[ipcnum].bIsEnabled = true;
272 			mutex_unlock(&mwave_mutex);
273 
274 			PRINTK_2(TRACE_MWAVE,
275 				"mwavedd::mwave_ioctl IOCTL_MW_REGISTER_IPC"
276 				" ipcnum %x exit\n",
277 				ipcnum);
278 		}
279 			break;
280 
281 		case IOCTL_MW_GET_IPC: {
282 			unsigned int ipcnum = (unsigned int) ioarg;
283 
284 			if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) {
285 				PRINTK_ERROR(KERN_ERR_MWAVE
286 						"mwavedd::mwave_ioctl:"
287 						" IOCTL_MW_GET_IPC: Error:"
288 						" Invalid ipcnum %x\n", ipcnum);
289 				return -EINVAL;
290 			}
291 			ipcnum = array_index_nospec(ipcnum,
292 						    ARRAY_SIZE(pDrvData->IPCs));
293 			PRINTK_3(TRACE_MWAVE,
294 				"mwavedd::mwave_ioctl IOCTL_MW_GET_IPC"
295 				" ipcnum %x, usIntCount %x\n",
296 				ipcnum,
297 				pDrvData->IPCs[ipcnum].usIntCount);
298 
299 			mutex_lock(&mwave_mutex);
300 			if (pDrvData->IPCs[ipcnum].bIsEnabled == true) {
301 				DECLARE_WAITQUEUE(wait, current);
302 
303 				PRINTK_2(TRACE_MWAVE,
304 					"mwavedd::mwave_ioctl, thread for"
305 					" ipc %x going to sleep\n",
306 					ipcnum);
307 				add_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait);
308 				pDrvData->IPCs[ipcnum].bIsHere = true;
309 				set_current_state(TASK_INTERRUPTIBLE);
310 				/* check whether an event was signalled by */
311 				/* the interrupt handler while we were gone */
312 				if (pDrvData->IPCs[ipcnum].usIntCount == 1) {	/* first int has occurred (race condition) */
313 					pDrvData->IPCs[ipcnum].usIntCount = 2;	/* first int has been handled */
314 					PRINTK_2(TRACE_MWAVE,
315 						"mwavedd::mwave_ioctl"
316 						" IOCTL_MW_GET_IPC ipcnum %x"
317 						" handling first int\n",
318 						ipcnum);
319 				} else {	/* either 1st int has not yet occurred, or we have already handled the first int */
320 					schedule();
321 					if (pDrvData->IPCs[ipcnum].usIntCount == 1) {
322 						pDrvData->IPCs[ipcnum].usIntCount = 2;
323 					}
324 					PRINTK_2(TRACE_MWAVE,
325 						"mwavedd::mwave_ioctl"
326 						" IOCTL_MW_GET_IPC ipcnum %x"
327 						" woke up and returning to"
328 						" application\n",
329 						ipcnum);
330 				}
331 				pDrvData->IPCs[ipcnum].bIsHere = false;
332 				remove_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait);
333 				set_current_state(TASK_RUNNING);
334 				PRINTK_2(TRACE_MWAVE,
335 					"mwavedd::mwave_ioctl IOCTL_MW_GET_IPC,"
336 					" returning thread for ipc %x"
337 					" processing\n",
338 					ipcnum);
339 			}
340 			mutex_unlock(&mwave_mutex);
341 		}
342 			break;
343 
344 		case IOCTL_MW_UNREGISTER_IPC: {
345 			unsigned int ipcnum = (unsigned int) ioarg;
346 
347 			PRINTK_2(TRACE_MWAVE,
348 				"mwavedd::mwave_ioctl IOCTL_MW_UNREGISTER_IPC"
349 				" ipcnum %x\n",
350 				ipcnum);
351 			if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) {
352 				PRINTK_ERROR(KERN_ERR_MWAVE
353 						"mwavedd::mwave_ioctl:"
354 						" IOCTL_MW_UNREGISTER_IPC:"
355 						" Error: Invalid ipcnum %x\n",
356 						ipcnum);
357 				return -EINVAL;
358 			}
359 			ipcnum = array_index_nospec(ipcnum,
360 						    ARRAY_SIZE(pDrvData->IPCs));
361 			mutex_lock(&mwave_mutex);
362 			if (pDrvData->IPCs[ipcnum].bIsEnabled == true) {
363 				pDrvData->IPCs[ipcnum].bIsEnabled = false;
364 				if (pDrvData->IPCs[ipcnum].bIsHere == true) {
365 					wake_up_interruptible(&pDrvData->IPCs[ipcnum].ipc_wait_queue);
366 				}
367 			}
368 			mutex_unlock(&mwave_mutex);
369 		}
370 			break;
371 
372 		default:
373 			return -ENOTTY;
374 	} /* switch */
375 
376 	PRINTK_2(TRACE_MWAVE, "mwavedd::mwave_ioctl, exit retval %x\n", retval);
377 
378 	return retval;
379 }
380 
381 static int register_serial_portandirq(unsigned int port, int irq)
382 {
383 	struct uart_8250_port uart;
384 
385 	switch ( port ) {
386 		case 0x3f8:
387 		case 0x2f8:
388 		case 0x3e8:
389 		case 0x2e8:
390 			/* OK */
391 			break;
392 		default:
393 			PRINTK_ERROR(KERN_ERR_MWAVE
394 					"mwavedd::register_serial_portandirq:"
395 					" Error: Illegal port %x\n", port );
396 			return -1;
397 	} /* switch */
398 	/* port is okay */
399 
400 	switch ( irq ) {
401 		case 3:
402 		case 4:
403 		case 5:
404 		case 7:
405 			/* OK */
406 			break;
407 		default:
408 			PRINTK_ERROR(KERN_ERR_MWAVE
409 					"mwavedd::register_serial_portandirq:"
410 					" Error: Illegal irq %x\n", irq );
411 			return -1;
412 	} /* switch */
413 	/* irq is okay */
414 
415 	memset(&uart, 0, sizeof(uart));
416 
417 	uart.port.uartclk =  1843200;
418 	uart.port.iobase = port;
419 	uart.port.irq = irq;
420 	uart.port.iotype = UPIO_PORT;
421 	uart.port.flags =  UPF_SHARE_IRQ;
422 	return serial8250_register_8250_port(&uart);
423 }
424 
425 static const struct file_operations mwave_fops = {
426 	.owner		= THIS_MODULE,
427 	.unlocked_ioctl	= mwave_ioctl,
428 	.llseek		= default_llseek,
429 };
430 
431 static struct miscdevice mwave_misc_dev = { MWAVE_MINOR, "mwave", &mwave_fops };
432 
433 /*
434 * mwave_init is called on module load
435 *
436 * mwave_exit is called on module unload
437 * mwave_exit is also used to clean up after an aborted mwave_init
438 */
439 static void mwave_exit(void)
440 {
441 	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
442 
443 	PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_exit entry\n");
444 
445 	if ( pDrvData->sLine >= 0 ) {
446 		serial8250_unregister_port(pDrvData->sLine);
447 	}
448 	if (pDrvData->bMwaveDevRegistered) {
449 		misc_deregister(&mwave_misc_dev);
450 	}
451 	if (pDrvData->bDSPEnabled) {
452 		tp3780I_DisableDSP(&pDrvData->rBDData);
453 	}
454 	if (pDrvData->bResourcesClaimed) {
455 		tp3780I_ReleaseResources(&pDrvData->rBDData);
456 	}
457 	if (pDrvData->bBDInitialized) {
458 		tp3780I_Cleanup(&pDrvData->rBDData);
459 	}
460 
461 	PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_exit exit\n");
462 }
463 
464 module_exit(mwave_exit);
465 
466 static int __init mwave_init(void)
467 {
468 	int i;
469 	int retval = 0;
470 	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
471 
472 	PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_init entry\n");
473 
474 	memset(&mwave_s_mdd, 0, sizeof(MWAVE_DEVICE_DATA));
475 
476 	pDrvData->bBDInitialized = false;
477 	pDrvData->bResourcesClaimed = false;
478 	pDrvData->bDSPEnabled = false;
479 	pDrvData->bDSPReset = false;
480 	pDrvData->bMwaveDevRegistered = false;
481 	pDrvData->sLine = -1;
482 
483 	for (i = 0; i < ARRAY_SIZE(pDrvData->IPCs); i++) {
484 		pDrvData->IPCs[i].bIsEnabled = false;
485 		pDrvData->IPCs[i].bIsHere = false;
486 		pDrvData->IPCs[i].usIntCount = 0;	/* no ints received yet */
487 		init_waitqueue_head(&pDrvData->IPCs[i].ipc_wait_queue);
488 	}
489 
490 	retval = tp3780I_InitializeBoardData(&pDrvData->rBDData);
491 	PRINTK_2(TRACE_MWAVE,
492 		"mwavedd::mwave_init, return from tp3780I_InitializeBoardData"
493 		" retval %x\n",
494 		retval);
495 	if (retval) {
496 		PRINTK_ERROR(KERN_ERR_MWAVE
497 				"mwavedd::mwave_init: Error:"
498 				" Failed to initialize board data\n");
499 		goto cleanup_error;
500 	}
501 	pDrvData->bBDInitialized = true;
502 
503 	retval = tp3780I_CalcResources(&pDrvData->rBDData);
504 	PRINTK_2(TRACE_MWAVE,
505 		"mwavedd::mwave_init, return from tp3780I_CalcResources"
506 		" retval %x\n",
507 		retval);
508 	if (retval) {
509 		PRINTK_ERROR(KERN_ERR_MWAVE
510 				"mwavedd:mwave_init: Error:"
511 				" Failed to calculate resources\n");
512 		goto cleanup_error;
513 	}
514 
515 	retval = tp3780I_ClaimResources(&pDrvData->rBDData);
516 	PRINTK_2(TRACE_MWAVE,
517 		"mwavedd::mwave_init, return from tp3780I_ClaimResources"
518 		" retval %x\n",
519 		retval);
520 	if (retval) {
521 		PRINTK_ERROR(KERN_ERR_MWAVE
522 				"mwavedd:mwave_init: Error:"
523 				" Failed to claim resources\n");
524 		goto cleanup_error;
525 	}
526 	pDrvData->bResourcesClaimed = true;
527 
528 	retval = tp3780I_EnableDSP(&pDrvData->rBDData);
529 	PRINTK_2(TRACE_MWAVE,
530 		"mwavedd::mwave_init, return from tp3780I_EnableDSP"
531 		" retval %x\n",
532 		retval);
533 	if (retval) {
534 		PRINTK_ERROR(KERN_ERR_MWAVE
535 				"mwavedd:mwave_init: Error:"
536 				" Failed to enable DSP\n");
537 		goto cleanup_error;
538 	}
539 	pDrvData->bDSPEnabled = true;
540 
541 	if (misc_register(&mwave_misc_dev) < 0) {
542 		PRINTK_ERROR(KERN_ERR_MWAVE
543 				"mwavedd:mwave_init: Error:"
544 				" Failed to register misc device\n");
545 		goto cleanup_error;
546 	}
547 	pDrvData->bMwaveDevRegistered = true;
548 
549 	pDrvData->sLine = register_serial_portandirq(
550 		pDrvData->rBDData.rDspSettings.usUartBaseIO,
551 		pDrvData->rBDData.rDspSettings.usUartIrq
552 	);
553 	if (pDrvData->sLine < 0) {
554 		PRINTK_ERROR(KERN_ERR_MWAVE
555 				"mwavedd:mwave_init: Error:"
556 				" Failed to register serial driver\n");
557 		goto cleanup_error;
558 	}
559 	/* uart is registered */
560 
561 	/* SUCCESS! */
562 	return 0;
563 
564 cleanup_error:
565 	PRINTK_ERROR(KERN_ERR_MWAVE
566 			"mwavedd::mwave_init: Error:"
567 			" Failed to initialize\n");
568 	mwave_exit(); /* clean up */
569 
570 	return -EIO;
571 }
572 
573 module_init(mwave_init);
574 
575