1 // SPDX-License-Identifier: GPL-2.0-only
2 /*                                              -*- linux-c -*-
3  * dtlk.c - DoubleTalk PC driver for Linux
4  *
5  * Original author: Chris Pallotta <chris@allmedia.com>
6  * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
7  *
8  * 2000-03-18 Jim Van Zandt: Fix polling.
9  *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
10  *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
11  *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
12  *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
13  */
14 
15 /* This driver is for the DoubleTalk PC, a speech synthesizer
16    manufactured by RC Systems (http://www.rcsys.com/).  It was written
17    based on documentation in their User's Manual file and Developer's
18    Tools disk.
19 
20    The DoubleTalk PC contains four voice synthesizers: text-to-speech
21    (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
22    also has a tone generator.  Output data for LPC are written to the
23    LPC port, and output data for the other modes are written to the
24    TTS port.
25 
26    Two kinds of data can be read from the DoubleTalk: status
27    information (in response to the "\001?" interrogation command) is
28    read from the TTS port, and index markers (which mark the progress
29    of the speech) are read from the LPC port.  Not all models of the
30    DoubleTalk PC implement index markers.  Both the TTS and LPC ports
31    can also display status flags.
32 
33    The DoubleTalk PC generates no interrupts.
34 
35    These characteristics are mapped into the Unix stream I/O model as
36    follows:
37 
38    "write" sends bytes to the TTS port.  It is the responsibility of
39    the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
40    This driver was written for use with the text-to-speech
41    synthesizer.  If LPC output is needed some day, other minor device
42    numbers can be used to select among output modes.
43 
44    "read" gets index markers from the LPC port.  If the device does
45    not implement index markers, the read will fail with error EINVAL.
46 
47    Status information is available using the DTLK_INTERROGATE ioctl.
48 
49  */
50 
51 #include <linux/module.h>
52 
53 #define KERNEL
54 #include <linux/types.h>
55 #include <linux/fs.h>
56 #include <linux/mm.h>
57 #include <linux/errno.h>	/* for -EBUSY */
58 #include <linux/ioport.h>	/* for request_region */
59 #include <linux/delay.h>	/* for loops_per_jiffy */
60 #include <linux/sched.h>
61 #include <linux/mutex.h>
62 #include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */
63 #include <linux/uaccess.h>	/* for get_user, etc. */
64 #include <linux/wait.h>		/* for wait_queue */
65 #include <linux/init.h>		/* for __init, module_{init,exit} */
66 #include <linux/poll.h>		/* for EPOLLIN, etc. */
67 #include <linux/dtlk.h>		/* local header file for DoubleTalk values */
68 
69 #ifdef TRACING
70 #define TRACE_TEXT(str) printk(str);
71 #define TRACE_RET printk(")")
72 #else				/* !TRACING */
73 #define TRACE_TEXT(str) ((void) 0)
74 #define TRACE_RET ((void) 0)
75 #endif				/* TRACING */
76 
77 static DEFINE_MUTEX(dtlk_mutex);
78 static void dtlk_timer_tick(struct timer_list *unused);
79 
80 static int dtlk_major;
81 static int dtlk_port_lpc;
82 static int dtlk_port_tts;
83 static int dtlk_busy;
84 static int dtlk_has_indexing;
85 static unsigned int dtlk_portlist[] =
86 {0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
87 static wait_queue_head_t dtlk_process_list;
88 static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick);
89 
90 /* prototypes for file_operations struct */
91 static ssize_t dtlk_read(struct file *, char __user *,
92 			 size_t nbytes, loff_t * ppos);
93 static ssize_t dtlk_write(struct file *, const char __user *,
94 			  size_t nbytes, loff_t * ppos);
95 static __poll_t dtlk_poll(struct file *, poll_table *);
96 static int dtlk_open(struct inode *, struct file *);
97 static int dtlk_release(struct inode *, struct file *);
98 static long dtlk_ioctl(struct file *file,
99 		       unsigned int cmd, unsigned long arg);
100 
101 static const struct file_operations dtlk_fops =
102 {
103 	.owner		= THIS_MODULE,
104 	.read		= dtlk_read,
105 	.write		= dtlk_write,
106 	.poll		= dtlk_poll,
107 	.unlocked_ioctl	= dtlk_ioctl,
108 	.open		= dtlk_open,
109 	.release	= dtlk_release,
110 	.llseek		= no_llseek,
111 };
112 
113 /* local prototypes */
114 static int dtlk_dev_probe(void);
115 static struct dtlk_settings *dtlk_interrogate(void);
116 static int dtlk_readable(void);
117 static char dtlk_read_lpc(void);
118 static char dtlk_read_tts(void);
119 static int dtlk_writeable(void);
120 static char dtlk_write_bytes(const char *buf, int n);
121 static char dtlk_write_tts(char);
122 /*
123    static void dtlk_handle_error(char, char, unsigned int);
124  */
125 
dtlk_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)126 static ssize_t dtlk_read(struct file *file, char __user *buf,
127 			 size_t count, loff_t * ppos)
128 {
129 	unsigned int minor = iminor(file_inode(file));
130 	char ch;
131 	int i = 0, retries;
132 
133 	TRACE_TEXT("(dtlk_read");
134 	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
135 
136 	if (minor != DTLK_MINOR || !dtlk_has_indexing)
137 		return -EINVAL;
138 
139 	for (retries = 0; retries < loops_per_jiffy; retries++) {
140 		while (i < count && dtlk_readable()) {
141 			ch = dtlk_read_lpc();
142 			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
143 			if (put_user(ch, buf++))
144 				return -EFAULT;
145 			i++;
146 		}
147 		if (i)
148 			return i;
149 		if (file->f_flags & O_NONBLOCK)
150 			break;
151 		msleep_interruptible(100);
152 	}
153 	if (retries == loops_per_jiffy)
154 		printk(KERN_ERR "dtlk_read times out\n");
155 	TRACE_RET;
156 	return -EAGAIN;
157 }
158 
dtlk_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)159 static ssize_t dtlk_write(struct file *file, const char __user *buf,
160 			  size_t count, loff_t * ppos)
161 {
162 	int i = 0, retries = 0, ch;
163 
164 	TRACE_TEXT("(dtlk_write");
165 #ifdef TRACING
166 	printk(" \"");
167 	{
168 		int i, ch;
169 		for (i = 0; i < count; i++) {
170 			if (get_user(ch, buf + i))
171 				return -EFAULT;
172 			if (' ' <= ch && ch <= '~')
173 				printk("%c", ch);
174 			else
175 				printk("\\%03o", ch);
176 		}
177 		printk("\"");
178 	}
179 #endif
180 
181 	if (iminor(file_inode(file)) != DTLK_MINOR)
182 		return -EINVAL;
183 
184 	while (1) {
185 		while (i < count && !get_user(ch, buf) &&
186 		       (ch == DTLK_CLEAR || dtlk_writeable())) {
187 			dtlk_write_tts(ch);
188 			buf++;
189 			i++;
190 			if (i % 5 == 0)
191 				/* We yield our time until scheduled
192 				   again.  This reduces the transfer
193 				   rate to 500 bytes/sec, but that's
194 				   still enough to keep up with the
195 				   speech synthesizer. */
196 				msleep_interruptible(1);
197 			else {
198 				/* the RDY bit goes zero 2-3 usec
199 				   after writing, and goes 1 again
200 				   180-190 usec later.  Here, we wait
201 				   up to 250 usec for the RDY bit to
202 				   go nonzero. */
203 				for (retries = 0;
204 				     retries < loops_per_jiffy / (4000/HZ);
205 				     retries++)
206 					if (inb_p(dtlk_port_tts) &
207 					    TTS_WRITABLE)
208 						break;
209 			}
210 			retries = 0;
211 		}
212 		if (i == count)
213 			return i;
214 		if (file->f_flags & O_NONBLOCK)
215 			break;
216 
217 		msleep_interruptible(1);
218 
219 		if (++retries > 10 * HZ) { /* wait no more than 10 sec
220 					      from last write */
221 			printk("dtlk: write timeout.  "
222 			       "inb_p(dtlk_port_tts) = 0x%02x\n",
223 			       inb_p(dtlk_port_tts));
224 			TRACE_RET;
225 			return -EBUSY;
226 		}
227 	}
228 	TRACE_RET;
229 	return -EAGAIN;
230 }
231 
dtlk_poll(struct file * file,poll_table * wait)232 static __poll_t dtlk_poll(struct file *file, poll_table * wait)
233 {
234 	__poll_t mask = 0;
235 	unsigned long expires;
236 
237 	TRACE_TEXT(" dtlk_poll");
238 	/*
239 	   static long int j;
240 	   printk(".");
241 	   printk("<%ld>", jiffies-j);
242 	   j=jiffies;
243 	 */
244 	poll_wait(file, &dtlk_process_list, wait);
245 
246 	if (dtlk_has_indexing && dtlk_readable()) {
247 	        del_timer(&dtlk_timer);
248 		mask = EPOLLIN | EPOLLRDNORM;
249 	}
250 	if (dtlk_writeable()) {
251 	        del_timer(&dtlk_timer);
252 		mask |= EPOLLOUT | EPOLLWRNORM;
253 	}
254 	/* there are no exception conditions */
255 
256 	/* There won't be any interrupts, so we set a timer instead. */
257 	expires = jiffies + 3*HZ / 100;
258 	mod_timer(&dtlk_timer, expires);
259 
260 	return mask;
261 }
262 
dtlk_timer_tick(struct timer_list * unused)263 static void dtlk_timer_tick(struct timer_list *unused)
264 {
265 	TRACE_TEXT(" dtlk_timer_tick");
266 	wake_up_interruptible(&dtlk_process_list);
267 }
268 
dtlk_ioctl(struct file * file,unsigned int cmd,unsigned long arg)269 static long dtlk_ioctl(struct file *file,
270 		       unsigned int cmd,
271 		       unsigned long arg)
272 {
273 	char __user *argp = (char __user *)arg;
274 	struct dtlk_settings *sp;
275 	char portval;
276 	TRACE_TEXT(" dtlk_ioctl");
277 
278 	switch (cmd) {
279 
280 	case DTLK_INTERROGATE:
281 		mutex_lock(&dtlk_mutex);
282 		sp = dtlk_interrogate();
283 		mutex_unlock(&dtlk_mutex);
284 		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
285 			return -EINVAL;
286 		return 0;
287 
288 	case DTLK_STATUS:
289 		portval = inb_p(dtlk_port_tts);
290 		return put_user(portval, argp);
291 
292 	default:
293 		return -EINVAL;
294 	}
295 }
296 
297 /* Note that nobody ever sets dtlk_busy... */
dtlk_open(struct inode * inode,struct file * file)298 static int dtlk_open(struct inode *inode, struct file *file)
299 {
300 	TRACE_TEXT("(dtlk_open");
301 
302 	switch (iminor(inode)) {
303 	case DTLK_MINOR:
304 		if (dtlk_busy)
305 			return -EBUSY;
306 		return stream_open(inode, file);
307 
308 	default:
309 		return -ENXIO;
310 	}
311 }
312 
dtlk_release(struct inode * inode,struct file * file)313 static int dtlk_release(struct inode *inode, struct file *file)
314 {
315 	TRACE_TEXT("(dtlk_release");
316 
317 	switch (iminor(inode)) {
318 	case DTLK_MINOR:
319 		break;
320 
321 	default:
322 		break;
323 	}
324 	TRACE_RET;
325 
326 	del_timer_sync(&dtlk_timer);
327 
328 	return 0;
329 }
330 
dtlk_init(void)331 static int __init dtlk_init(void)
332 {
333 	int err;
334 
335 	dtlk_port_lpc = 0;
336 	dtlk_port_tts = 0;
337 	dtlk_busy = 0;
338 	dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
339 	if (dtlk_major < 0) {
340 		printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
341 		return dtlk_major;
342 	}
343 	err = dtlk_dev_probe();
344 	if (err) {
345 		unregister_chrdev(dtlk_major, "dtlk");
346 		return err;
347 	}
348 	printk(", MAJOR %d\n", dtlk_major);
349 
350 	init_waitqueue_head(&dtlk_process_list);
351 
352 	return 0;
353 }
354 
dtlk_cleanup(void)355 static void __exit dtlk_cleanup (void)
356 {
357 	dtlk_write_bytes("goodbye", 8);
358 	msleep_interruptible(500);		/* nap 0.50 sec but
359 						   could be awakened
360 						   earlier by
361 						   signals... */
362 
363 	dtlk_write_tts(DTLK_CLEAR);
364 	unregister_chrdev(dtlk_major, "dtlk");
365 	release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
366 }
367 
368 module_init(dtlk_init);
369 module_exit(dtlk_cleanup);
370 
371 /* ------------------------------------------------------------------------ */
372 
dtlk_readable(void)373 static int dtlk_readable(void)
374 {
375 #ifdef TRACING
376 	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
377 #endif
378 	return inb_p(dtlk_port_lpc) != 0x7f;
379 }
380 
dtlk_writeable(void)381 static int dtlk_writeable(void)
382 {
383 	/* TRACE_TEXT(" dtlk_writeable"); */
384 #ifdef TRACINGMORE
385 	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
386 #endif
387 	return inb_p(dtlk_port_tts) & TTS_WRITABLE;
388 }
389 
dtlk_dev_probe(void)390 static int __init dtlk_dev_probe(void)
391 {
392 	unsigned int testval = 0;
393 	int i = 0;
394 	struct dtlk_settings *sp;
395 
396 	if (dtlk_port_lpc | dtlk_port_tts)
397 		return -EBUSY;
398 
399 	for (i = 0; dtlk_portlist[i]; i++) {
400 #if 0
401 		printk("DoubleTalk PC - Port %03x = %04x\n",
402 		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
403 #endif
404 
405 		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT,
406 			       "dtlk"))
407 			continue;
408 		testval = inw_p(dtlk_portlist[i]);
409 		if ((testval &= 0xfbff) == 0x107f) {
410 			dtlk_port_lpc = dtlk_portlist[i];
411 			dtlk_port_tts = dtlk_port_lpc + 1;
412 
413 			sp = dtlk_interrogate();
414 			printk("DoubleTalk PC at %03x-%03x, "
415 			       "ROM version %s, serial number %u",
416 			       dtlk_portlist[i], dtlk_portlist[i] +
417 			       DTLK_IO_EXTENT - 1,
418 			       sp->rom_version, sp->serial_number);
419 
420                         /* put LPC port into known state, so
421 			   dtlk_readable() gives valid result */
422 			outb_p(0xff, dtlk_port_lpc);
423 
424                         /* INIT string and index marker */
425 			dtlk_write_bytes("\036\1@\0\0012I\r", 8);
426 			/* posting an index takes 18 msec.  Here, we
427 			   wait up to 100 msec to see whether it
428 			   appears. */
429 			msleep_interruptible(100);
430 			dtlk_has_indexing = dtlk_readable();
431 #ifdef TRACING
432 			printk(", indexing %d\n", dtlk_has_indexing);
433 #endif
434 #ifdef INSCOPE
435 			{
436 /* This macro records ten samples read from the LPC port, for later display */
437 #define LOOK					\
438 for (i = 0; i < 10; i++)			\
439   {						\
440     buffer[b++] = inb_p(dtlk_port_lpc);		\
441     __delay(loops_per_jiffy/(1000000/HZ));             \
442   }
443 				char buffer[1000];
444 				int b = 0, i, j;
445 
446 				LOOK
447 				outb_p(0xff, dtlk_port_lpc);
448 				buffer[b++] = 0;
449 				LOOK
450 				dtlk_write_bytes("\0012I\r", 4);
451 				buffer[b++] = 0;
452 				__delay(50 * loops_per_jiffy / (1000/HZ));
453 				outb_p(0xff, dtlk_port_lpc);
454 				buffer[b++] = 0;
455 				LOOK
456 
457 				printk("\n");
458 				for (j = 0; j < b; j++)
459 					printk(" %02x", buffer[j]);
460 				printk("\n");
461 			}
462 #endif				/* INSCOPE */
463 
464 #ifdef OUTSCOPE
465 			{
466 /* This macro records ten samples read from the TTS port, for later display */
467 #define LOOK					\
468 for (i = 0; i < 10; i++)			\
469   {						\
470     buffer[b++] = inb_p(dtlk_port_tts);		\
471     __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
472   }
473 				char buffer[1000];
474 				int b = 0, i, j;
475 
476 				mdelay(10);	/* 10 ms */
477 				LOOK
478 				outb_p(0x03, dtlk_port_tts);
479 				buffer[b++] = 0;
480 				LOOK
481 				LOOK
482 
483 				printk("\n");
484 				for (j = 0; j < b; j++)
485 					printk(" %02x", buffer[j]);
486 				printk("\n");
487 			}
488 #endif				/* OUTSCOPE */
489 
490 			dtlk_write_bytes("Double Talk found", 18);
491 
492 			return 0;
493 		}
494 		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
495 	}
496 
497 	printk(KERN_INFO "DoubleTalk PC - not found\n");
498 	return -ENODEV;
499 }
500 
501 /*
502    static void dtlk_handle_error(char op, char rc, unsigned int minor)
503    {
504    printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n",
505    minor, op, rc);
506    return;
507    }
508  */
509 
510 /* interrogate the DoubleTalk PC and return its settings */
dtlk_interrogate(void)511 static struct dtlk_settings *dtlk_interrogate(void)
512 {
513 	unsigned char *t;
514 	static char buf[sizeof(struct dtlk_settings) + 1];
515 	int total, i;
516 	static struct dtlk_settings status;
517 	TRACE_TEXT("(dtlk_interrogate");
518 	dtlk_write_bytes("\030\001?", 3);
519 	for (total = 0, i = 0; i < 50; i++) {
520 		buf[total] = dtlk_read_tts();
521 		if (total > 2 && buf[total] == 0x7f)
522 			break;
523 		if (total < sizeof(struct dtlk_settings))
524 			total++;
525 	}
526 	/*
527 	   if (i==50) printk("interrogate() read overrun\n");
528 	   for (i=0; i<sizeof(buf); i++)
529 	   printk(" %02x", buf[i]);
530 	   printk("\n");
531 	 */
532 	t = buf;
533 	status.serial_number = t[0] + t[1] * 256; /* serial number is
534 						     little endian */
535 	t += 2;
536 
537 	i = 0;
538 	while (*t != '\r') {
539 		status.rom_version[i] = *t;
540 		if (i < sizeof(status.rom_version) - 1)
541 			i++;
542 		t++;
543 	}
544 	status.rom_version[i] = 0;
545 	t++;
546 
547 	status.mode = *t++;
548 	status.punc_level = *t++;
549 	status.formant_freq = *t++;
550 	status.pitch = *t++;
551 	status.speed = *t++;
552 	status.volume = *t++;
553 	status.tone = *t++;
554 	status.expression = *t++;
555 	status.ext_dict_loaded = *t++;
556 	status.ext_dict_status = *t++;
557 	status.free_ram = *t++;
558 	status.articulation = *t++;
559 	status.reverb = *t++;
560 	status.eob = *t++;
561 	status.has_indexing = dtlk_has_indexing;
562 	TRACE_RET;
563 	return &status;
564 }
565 
dtlk_read_tts(void)566 static char dtlk_read_tts(void)
567 {
568 	int portval, retries = 0;
569 	char ch;
570 	TRACE_TEXT("(dtlk_read_tts");
571 
572 	/* verify DT is ready, read char, wait for ACK */
573 	do {
574 		portval = inb_p(dtlk_port_tts);
575 	} while ((portval & TTS_READABLE) == 0 &&
576 		 retries++ < DTLK_MAX_RETRIES);
577 	if (retries > DTLK_MAX_RETRIES)
578 		printk(KERN_ERR "dtlk_read_tts() timeout\n");
579 
580 	ch = inb_p(dtlk_port_tts);	/* input from TTS port */
581 	ch &= 0x7f;
582 	outb_p(ch, dtlk_port_tts);
583 
584 	retries = 0;
585 	do {
586 		portval = inb_p(dtlk_port_tts);
587 	} while ((portval & TTS_READABLE) != 0 &&
588 		 retries++ < DTLK_MAX_RETRIES);
589 	if (retries > DTLK_MAX_RETRIES)
590 		printk(KERN_ERR "dtlk_read_tts() timeout\n");
591 
592 	TRACE_RET;
593 	return ch;
594 }
595 
dtlk_read_lpc(void)596 static char dtlk_read_lpc(void)
597 {
598 	int retries = 0;
599 	char ch;
600 	TRACE_TEXT("(dtlk_read_lpc");
601 
602 	/* no need to test -- this is only called when the port is readable */
603 
604 	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */
605 
606 	outb_p(0xff, dtlk_port_lpc);
607 
608 	/* acknowledging a read takes 3-4
609 	   usec.  Here, we wait up to 20 usec
610 	   for the acknowledgement */
611 	retries = (loops_per_jiffy * 20) / (1000000/HZ);
612 	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
613 	if (retries == 0)
614 		printk(KERN_ERR "dtlk_read_lpc() timeout\n");
615 
616 	TRACE_RET;
617 	return ch;
618 }
619 
620 /* write n bytes to tts port */
dtlk_write_bytes(const char * buf,int n)621 static char dtlk_write_bytes(const char *buf, int n)
622 {
623 	char val = 0;
624 	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
625 	TRACE_TEXT("(dtlk_write_bytes");
626 	while (n-- > 0)
627 		val = dtlk_write_tts(*buf++);
628 	TRACE_RET;
629 	return val;
630 }
631 
dtlk_write_tts(char ch)632 static char dtlk_write_tts(char ch)
633 {
634 	int retries = 0;
635 #ifdef TRACINGMORE
636 	printk("  dtlk_write_tts(");
637 	if (' ' <= ch && ch <= '~')
638 		printk("'%c'", ch);
639 	else
640 		printk("0x%02x", ch);
641 #endif
642 	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */
643 		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
644 		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */
645 			;
646 	if (retries > DTLK_MAX_RETRIES)
647 		printk(KERN_ERR "dtlk_write_tts() timeout\n");
648 
649 	outb_p(ch, dtlk_port_tts);	/* output to TTS port */
650 	/* the RDY bit goes zero 2-3 usec after writing, and goes
651 	   1 again 180-190 usec later.  Here, we wait up to 10
652 	   usec for the RDY bit to go zero. */
653 	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
654 		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
655 			break;
656 
657 #ifdef TRACINGMORE
658 	printk(")\n");
659 #endif
660 	return 0;
661 }
662 
663 MODULE_LICENSE("GPL");
664