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/mutex.h>
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 DEFINE_MUTEX(dtlk_mutex);
77static void dtlk_timer_tick(unsigned long data);
78
79static int dtlk_major;
80static int dtlk_port_lpc;
81static int dtlk_port_tts;
82static int dtlk_busy;
83static int dtlk_has_indexing;
84static unsigned int dtlk_portlist[] =
85{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
86static wait_queue_head_t dtlk_process_list;
87static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick, 0, 0);
88
89/* prototypes for file_operations struct */
90static ssize_t dtlk_read(struct file *, char __user *,
91			 size_t nbytes, loff_t * ppos);
92static ssize_t dtlk_write(struct file *, const char __user *,
93			  size_t nbytes, loff_t * ppos);
94static unsigned int dtlk_poll(struct file *, poll_table *);
95static int dtlk_open(struct inode *, struct file *);
96static int dtlk_release(struct inode *, struct file *);
97static long dtlk_ioctl(struct file *file,
98		       unsigned int cmd, unsigned long arg);
99
100static const struct file_operations dtlk_fops =
101{
102	.owner		= THIS_MODULE,
103	.read		= dtlk_read,
104	.write		= dtlk_write,
105	.poll		= dtlk_poll,
106	.unlocked_ioctl	= dtlk_ioctl,
107	.open		= dtlk_open,
108	.release	= dtlk_release,
109	.llseek		= no_llseek,
110};
111
112/* local prototypes */
113static int dtlk_dev_probe(void);
114static struct dtlk_settings *dtlk_interrogate(void);
115static int dtlk_readable(void);
116static char dtlk_read_lpc(void);
117static char dtlk_read_tts(void);
118static int dtlk_writeable(void);
119static char dtlk_write_bytes(const char *buf, int n);
120static char dtlk_write_tts(char);
121/*
122   static void dtlk_handle_error(char, char, unsigned int);
123 */
124
125static ssize_t dtlk_read(struct file *file, char __user *buf,
126			 size_t count, loff_t * ppos)
127{
128	unsigned int minor = iminor(file->f_path.dentry->d_inode);
129	char ch;
130	int i = 0, retries;
131
132	TRACE_TEXT("(dtlk_read");
133	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
134
135	if (minor != DTLK_MINOR || !dtlk_has_indexing)
136		return -EINVAL;
137
138	for (retries = 0; retries < loops_per_jiffy; retries++) {
139		while (i < count && dtlk_readable()) {
140			ch = dtlk_read_lpc();
141			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
142			if (put_user(ch, buf++))
143				return -EFAULT;
144			i++;
145		}
146		if (i)
147			return i;
148		if (file->f_flags & O_NONBLOCK)
149			break;
150		msleep_interruptible(100);
151	}
152	if (retries == loops_per_jiffy)
153		printk(KERN_ERR "dtlk_read times out\n");
154	TRACE_RET;
155	return -EAGAIN;
156}
157
158static ssize_t dtlk_write(struct file *file, const char __user *buf,
159			  size_t count, loff_t * ppos)
160{
161	int i = 0, retries = 0, ch;
162
163	TRACE_TEXT("(dtlk_write");
164#ifdef TRACING
165	printk(" \"");
166	{
167		int i, ch;
168		for (i = 0; i < count; i++) {
169			if (get_user(ch, buf + i))
170				return -EFAULT;
171			if (' ' <= ch && ch <= '~')
172				printk("%c", ch);
173			else
174				printk("\\%03o", ch);
175		}
176		printk("\"");
177	}
178#endif
179
180	if (iminor(file->f_path.dentry->d_inode) != DTLK_MINOR)
181		return -EINVAL;
182
183	while (1) {
184		while (i < count && !get_user(ch, buf) &&
185		       (ch == DTLK_CLEAR || dtlk_writeable())) {
186			dtlk_write_tts(ch);
187			buf++;
188			i++;
189			if (i % 5 == 0)
190				/* We yield our time until scheduled
191				   again.  This reduces the transfer
192				   rate to 500 bytes/sec, but that's
193				   still enough to keep up with the
194				   speech synthesizer. */
195				msleep_interruptible(1);
196			else {
197				/* the RDY bit goes zero 2-3 usec
198				   after writing, and goes 1 again
199				   180-190 usec later.  Here, we wait
200				   up to 250 usec for the RDY bit to
201				   go nonzero. */
202				for (retries = 0;
203				     retries < loops_per_jiffy / (4000/HZ);
204				     retries++)
205					if (inb_p(dtlk_port_tts) &
206					    TTS_WRITABLE)
207						break;
208			}
209			retries = 0;
210		}
211		if (i == count)
212			return i;
213		if (file->f_flags & O_NONBLOCK)
214			break;
215
216		msleep_interruptible(1);
217
218		if (++retries > 10 * HZ) { /* wait no more than 10 sec
219					      from last write */
220			printk("dtlk: write timeout.  "
221			       "inb_p(dtlk_port_tts) = 0x%02x\n",
222			       inb_p(dtlk_port_tts));
223			TRACE_RET;
224			return -EBUSY;
225		}
226	}
227	TRACE_RET;
228	return -EAGAIN;
229}
230
231static unsigned int dtlk_poll(struct file *file, poll_table * wait)
232{
233	int mask = 0;
234	unsigned long expires;
235
236	TRACE_TEXT(" dtlk_poll");
237	/*
238	   static long int j;
239	   printk(".");
240	   printk("<%ld>", jiffies-j);
241	   j=jiffies;
242	 */
243	poll_wait(file, &dtlk_process_list, wait);
244
245	if (dtlk_has_indexing && dtlk_readable()) {
246	        del_timer(&dtlk_timer);
247		mask = POLLIN | POLLRDNORM;
248	}
249	if (dtlk_writeable()) {
250	        del_timer(&dtlk_timer);
251		mask |= POLLOUT | POLLWRNORM;
252	}
253	/* there are no exception conditions */
254
255	/* There won't be any interrupts, so we set a timer instead. */
256	expires = jiffies + 3*HZ / 100;
257	mod_timer(&dtlk_timer, expires);
258
259	return mask;
260}
261
262static void dtlk_timer_tick(unsigned long data)
263{
264	TRACE_TEXT(" dtlk_timer_tick");
265	wake_up_interruptible(&dtlk_process_list);
266}
267
268static long dtlk_ioctl(struct file *file,
269		       unsigned int cmd,
270		       unsigned long arg)
271{
272	char __user *argp = (char __user *)arg;
273	struct dtlk_settings *sp;
274	char portval;
275	TRACE_TEXT(" dtlk_ioctl");
276
277	switch (cmd) {
278
279	case DTLK_INTERROGATE:
280		mutex_lock(&dtlk_mutex);
281		sp = dtlk_interrogate();
282		mutex_unlock(&dtlk_mutex);
283		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
284			return -EINVAL;
285		return 0;
286
287	case DTLK_STATUS:
288		portval = inb_p(dtlk_port_tts);
289		return put_user(portval, argp);
290
291	default:
292		return -EINVAL;
293	}
294}
295
296/* Note that nobody ever sets dtlk_busy... */
297static int dtlk_open(struct inode *inode, struct file *file)
298{
299	TRACE_TEXT("(dtlk_open");
300
301	nonseekable_open(inode, file);
302	switch (iminor(inode)) {
303	case DTLK_MINOR:
304		if (dtlk_busy)
305			return -EBUSY;
306		return nonseekable_open(inode, file);
307
308	default:
309		return -ENXIO;
310	}
311}
312
313static 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
331static 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
355static 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
368module_init(dtlk_init);
369module_exit(dtlk_cleanup);
370
371/* ------------------------------------------------------------------------ */
372
373static 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
381static 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
390static 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					\
438for (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					\
468for (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 */
511static 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
566static 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
596static 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 */
621static 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
632static 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
663MODULE_LICENSE("GPL");
664