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