xref: /linux/drivers/char/dtlk.c (revision cb787f4ac0c2e439ea8d7e6387b925f74576bdf8)
109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21da177e4SLinus Torvalds /*                                              -*- linux-c -*-
31da177e4SLinus Torvalds  * dtlk.c - DoubleTalk PC driver for Linux
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  * Original author: Chris Pallotta <chris@allmedia.com>
61da177e4SLinus Torvalds  * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
71da177e4SLinus Torvalds  *
81da177e4SLinus Torvalds  * 2000-03-18 Jim Van Zandt: Fix polling.
91da177e4SLinus Torvalds  *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
101da177e4SLinus Torvalds  *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
111da177e4SLinus Torvalds  *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
121da177e4SLinus Torvalds  *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
131da177e4SLinus Torvalds  */
141da177e4SLinus Torvalds 
151da177e4SLinus Torvalds /* This driver is for the DoubleTalk PC, a speech synthesizer
161da177e4SLinus Torvalds    manufactured by RC Systems (http://www.rcsys.com/).  It was written
171da177e4SLinus Torvalds    based on documentation in their User's Manual file and Developer's
181da177e4SLinus Torvalds    Tools disk.
191da177e4SLinus Torvalds 
201da177e4SLinus Torvalds    The DoubleTalk PC contains four voice synthesizers: text-to-speech
211da177e4SLinus Torvalds    (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
221da177e4SLinus Torvalds    also has a tone generator.  Output data for LPC are written to the
231da177e4SLinus Torvalds    LPC port, and output data for the other modes are written to the
241da177e4SLinus Torvalds    TTS port.
251da177e4SLinus Torvalds 
261da177e4SLinus Torvalds    Two kinds of data can be read from the DoubleTalk: status
271da177e4SLinus Torvalds    information (in response to the "\001?" interrogation command) is
281da177e4SLinus Torvalds    read from the TTS port, and index markers (which mark the progress
291da177e4SLinus Torvalds    of the speech) are read from the LPC port.  Not all models of the
301da177e4SLinus Torvalds    DoubleTalk PC implement index markers.  Both the TTS and LPC ports
311da177e4SLinus Torvalds    can also display status flags.
321da177e4SLinus Torvalds 
331da177e4SLinus Torvalds    The DoubleTalk PC generates no interrupts.
341da177e4SLinus Torvalds 
351da177e4SLinus Torvalds    These characteristics are mapped into the Unix stream I/O model as
361da177e4SLinus Torvalds    follows:
371da177e4SLinus Torvalds 
381da177e4SLinus Torvalds    "write" sends bytes to the TTS port.  It is the responsibility of
391da177e4SLinus Torvalds    the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
401da177e4SLinus Torvalds    This driver was written for use with the text-to-speech
411da177e4SLinus Torvalds    synthesizer.  If LPC output is needed some day, other minor device
421da177e4SLinus Torvalds    numbers can be used to select among output modes.
431da177e4SLinus Torvalds 
441da177e4SLinus Torvalds    "read" gets index markers from the LPC port.  If the device does
451da177e4SLinus Torvalds    not implement index markers, the read will fail with error EINVAL.
461da177e4SLinus Torvalds 
471da177e4SLinus Torvalds    Status information is available using the DTLK_INTERROGATE ioctl.
481da177e4SLinus Torvalds 
491da177e4SLinus Torvalds  */
501da177e4SLinus Torvalds 
511da177e4SLinus Torvalds #include <linux/module.h>
521da177e4SLinus Torvalds 
531da177e4SLinus Torvalds #define KERNEL
541da177e4SLinus Torvalds #include <linux/types.h>
551da177e4SLinus Torvalds #include <linux/fs.h>
56e49332bdSJesper Juhl #include <linux/mm.h>
571da177e4SLinus Torvalds #include <linux/errno.h>	/* for -EBUSY */
581da177e4SLinus Torvalds #include <linux/ioport.h>	/* for request_region */
591da177e4SLinus Torvalds #include <linux/delay.h>	/* for loops_per_jiffy */
60a99bbaf5SAlexey Dobriyan #include <linux/sched.h>
61613655faSArnd Bergmann #include <linux/mutex.h>
621da177e4SLinus Torvalds #include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */
637c0f6ba6SLinus Torvalds #include <linux/uaccess.h>	/* for get_user, etc. */
641da177e4SLinus Torvalds #include <linux/wait.h>		/* for wait_queue */
651da177e4SLinus Torvalds #include <linux/init.h>		/* for __init, module_{init,exit} */
66a9a08845SLinus Torvalds #include <linux/poll.h>		/* for EPOLLIN, etc. */
671da177e4SLinus Torvalds #include <linux/dtlk.h>		/* local header file for DoubleTalk values */
681da177e4SLinus Torvalds 
691da177e4SLinus Torvalds #ifdef TRACING
701da177e4SLinus Torvalds #define TRACE_TEXT(str) printk(str);
711da177e4SLinus Torvalds #define TRACE_RET printk(")")
721da177e4SLinus Torvalds #else				/* !TRACING */
731da177e4SLinus Torvalds #define TRACE_TEXT(str) ((void) 0)
741da177e4SLinus Torvalds #define TRACE_RET ((void) 0)
751da177e4SLinus Torvalds #endif				/* TRACING */
761da177e4SLinus Torvalds 
77613655faSArnd Bergmann static DEFINE_MUTEX(dtlk_mutex);
7824ed960aSKees Cook static void dtlk_timer_tick(struct timer_list *unused);
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds static int dtlk_major;
811da177e4SLinus Torvalds static int dtlk_port_lpc;
821da177e4SLinus Torvalds static int dtlk_port_tts;
831da177e4SLinus Torvalds static int dtlk_busy;
841da177e4SLinus Torvalds static int dtlk_has_indexing;
851da177e4SLinus Torvalds static unsigned int dtlk_portlist[] =
861da177e4SLinus Torvalds {0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
871da177e4SLinus Torvalds static wait_queue_head_t dtlk_process_list;
881d27e3e2SKees Cook static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick);
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds /* prototypes for file_operations struct */
911da177e4SLinus Torvalds static ssize_t dtlk_read(struct file *, char __user *,
921da177e4SLinus Torvalds 			 size_t nbytes, loff_t * ppos);
931da177e4SLinus Torvalds static ssize_t dtlk_write(struct file *, const char __user *,
941da177e4SLinus Torvalds 			  size_t nbytes, loff_t * ppos);
95afc9a42bSAl Viro static __poll_t dtlk_poll(struct file *, poll_table *);
961da177e4SLinus Torvalds static int dtlk_open(struct inode *, struct file *);
971da177e4SLinus Torvalds static int dtlk_release(struct inode *, struct file *);
9855929332SArnd Bergmann static long dtlk_ioctl(struct file *file,
991da177e4SLinus Torvalds 		       unsigned int cmd, unsigned long arg);
1001da177e4SLinus Torvalds 
10162322d25SArjan van de Ven static const struct file_operations dtlk_fops =
1021da177e4SLinus Torvalds {
1031da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
1041da177e4SLinus Torvalds 	.read		= dtlk_read,
1051da177e4SLinus Torvalds 	.write		= dtlk_write,
1061da177e4SLinus Torvalds 	.poll		= dtlk_poll,
10755929332SArnd Bergmann 	.unlocked_ioctl	= dtlk_ioctl,
1081da177e4SLinus Torvalds 	.open		= dtlk_open,
1091da177e4SLinus Torvalds 	.release	= dtlk_release,
1101da177e4SLinus Torvalds };
1111da177e4SLinus Torvalds 
1121da177e4SLinus Torvalds /* local prototypes */
1131da177e4SLinus Torvalds static int dtlk_dev_probe(void);
1141da177e4SLinus Torvalds static struct dtlk_settings *dtlk_interrogate(void);
1151da177e4SLinus Torvalds static int dtlk_readable(void);
1161da177e4SLinus Torvalds static char dtlk_read_lpc(void);
1171da177e4SLinus Torvalds static char dtlk_read_tts(void);
1181da177e4SLinus Torvalds static int dtlk_writeable(void);
1191da177e4SLinus Torvalds static char dtlk_write_bytes(const char *buf, int n);
1201da177e4SLinus Torvalds static char dtlk_write_tts(char);
1211da177e4SLinus Torvalds /*
1221da177e4SLinus Torvalds    static void dtlk_handle_error(char, char, unsigned int);
1231da177e4SLinus Torvalds  */
1241da177e4SLinus Torvalds 
1251da177e4SLinus Torvalds static ssize_t dtlk_read(struct file *file, char __user *buf,
1261da177e4SLinus Torvalds 			 size_t count, loff_t * ppos)
1271da177e4SLinus Torvalds {
128496ad9aaSAl Viro 	unsigned int minor = iminor(file_inode(file));
1291da177e4SLinus Torvalds 	char ch;
1301da177e4SLinus Torvalds 	int i = 0, retries;
1311da177e4SLinus Torvalds 
1321da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_read");
1331da177e4SLinus Torvalds 	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
1341da177e4SLinus Torvalds 
1351da177e4SLinus Torvalds 	if (minor != DTLK_MINOR || !dtlk_has_indexing)
1361da177e4SLinus Torvalds 		return -EINVAL;
1371da177e4SLinus Torvalds 
1381da177e4SLinus Torvalds 	for (retries = 0; retries < loops_per_jiffy; retries++) {
1391da177e4SLinus Torvalds 		while (i < count && dtlk_readable()) {
1401da177e4SLinus Torvalds 			ch = dtlk_read_lpc();
1411da177e4SLinus Torvalds 			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
1421da177e4SLinus Torvalds 			if (put_user(ch, buf++))
1431da177e4SLinus Torvalds 				return -EFAULT;
1441da177e4SLinus Torvalds 			i++;
1451da177e4SLinus Torvalds 		}
1461da177e4SLinus Torvalds 		if (i)
1471da177e4SLinus Torvalds 			return i;
1481da177e4SLinus Torvalds 		if (file->f_flags & O_NONBLOCK)
1491da177e4SLinus Torvalds 			break;
1501da177e4SLinus Torvalds 		msleep_interruptible(100);
1511da177e4SLinus Torvalds 	}
1521da177e4SLinus Torvalds 	if (retries == loops_per_jiffy)
1531da177e4SLinus Torvalds 		printk(KERN_ERR "dtlk_read times out\n");
1541da177e4SLinus Torvalds 	TRACE_RET;
1551da177e4SLinus Torvalds 	return -EAGAIN;
1561da177e4SLinus Torvalds }
1571da177e4SLinus Torvalds 
1581da177e4SLinus Torvalds static ssize_t dtlk_write(struct file *file, const char __user *buf,
1591da177e4SLinus Torvalds 			  size_t count, loff_t * ppos)
1601da177e4SLinus Torvalds {
1611da177e4SLinus Torvalds 	int i = 0, retries = 0, ch;
1621da177e4SLinus Torvalds 
1631da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_write");
1641da177e4SLinus Torvalds #ifdef TRACING
1651da177e4SLinus Torvalds 	printk(" \"");
1661da177e4SLinus Torvalds 	{
1671da177e4SLinus Torvalds 		int i, ch;
1681da177e4SLinus Torvalds 		for (i = 0; i < count; i++) {
1691da177e4SLinus Torvalds 			if (get_user(ch, buf + i))
1701da177e4SLinus Torvalds 				return -EFAULT;
1711da177e4SLinus Torvalds 			if (' ' <= ch && ch <= '~')
1721da177e4SLinus Torvalds 				printk("%c", ch);
1731da177e4SLinus Torvalds 			else
1741da177e4SLinus Torvalds 				printk("\\%03o", ch);
1751da177e4SLinus Torvalds 		}
1761da177e4SLinus Torvalds 		printk("\"");
1771da177e4SLinus Torvalds 	}
1781da177e4SLinus Torvalds #endif
1791da177e4SLinus Torvalds 
180496ad9aaSAl Viro 	if (iminor(file_inode(file)) != DTLK_MINOR)
1811da177e4SLinus Torvalds 		return -EINVAL;
1821da177e4SLinus Torvalds 
1831da177e4SLinus Torvalds 	while (1) {
1841da177e4SLinus Torvalds 		while (i < count && !get_user(ch, buf) &&
1851da177e4SLinus Torvalds 		       (ch == DTLK_CLEAR || dtlk_writeable())) {
1861da177e4SLinus Torvalds 			dtlk_write_tts(ch);
1871da177e4SLinus Torvalds 			buf++;
1881da177e4SLinus Torvalds 			i++;
1891da177e4SLinus Torvalds 			if (i % 5 == 0)
1901da177e4SLinus Torvalds 				/* We yield our time until scheduled
1911da177e4SLinus Torvalds 				   again.  This reduces the transfer
1921da177e4SLinus Torvalds 				   rate to 500 bytes/sec, but that's
1931da177e4SLinus Torvalds 				   still enough to keep up with the
1941da177e4SLinus Torvalds 				   speech synthesizer. */
1951da177e4SLinus Torvalds 				msleep_interruptible(1);
1961da177e4SLinus Torvalds 			else {
1971da177e4SLinus Torvalds 				/* the RDY bit goes zero 2-3 usec
1981da177e4SLinus Torvalds 				   after writing, and goes 1 again
1991da177e4SLinus Torvalds 				   180-190 usec later.  Here, we wait
2001da177e4SLinus Torvalds 				   up to 250 usec for the RDY bit to
2011da177e4SLinus Torvalds 				   go nonzero. */
2021da177e4SLinus Torvalds 				for (retries = 0;
2031da177e4SLinus Torvalds 				     retries < loops_per_jiffy / (4000/HZ);
2041da177e4SLinus Torvalds 				     retries++)
2051da177e4SLinus Torvalds 					if (inb_p(dtlk_port_tts) &
2061da177e4SLinus Torvalds 					    TTS_WRITABLE)
2071da177e4SLinus Torvalds 						break;
2081da177e4SLinus Torvalds 			}
2091da177e4SLinus Torvalds 			retries = 0;
2101da177e4SLinus Torvalds 		}
2111da177e4SLinus Torvalds 		if (i == count)
2121da177e4SLinus Torvalds 			return i;
2131da177e4SLinus Torvalds 		if (file->f_flags & O_NONBLOCK)
2141da177e4SLinus Torvalds 			break;
2151da177e4SLinus Torvalds 
2161da177e4SLinus Torvalds 		msleep_interruptible(1);
2171da177e4SLinus Torvalds 
2181da177e4SLinus Torvalds 		if (++retries > 10 * HZ) { /* wait no more than 10 sec
2191da177e4SLinus Torvalds 					      from last write */
2201da177e4SLinus Torvalds 			printk("dtlk: write timeout.  "
2211da177e4SLinus Torvalds 			       "inb_p(dtlk_port_tts) = 0x%02x\n",
2221da177e4SLinus Torvalds 			       inb_p(dtlk_port_tts));
2231da177e4SLinus Torvalds 			TRACE_RET;
2241da177e4SLinus Torvalds 			return -EBUSY;
2251da177e4SLinus Torvalds 		}
2261da177e4SLinus Torvalds 	}
2271da177e4SLinus Torvalds 	TRACE_RET;
2281da177e4SLinus Torvalds 	return -EAGAIN;
2291da177e4SLinus Torvalds }
2301da177e4SLinus Torvalds 
231afc9a42bSAl Viro static __poll_t dtlk_poll(struct file *file, poll_table * wait)
2321da177e4SLinus Torvalds {
233afc9a42bSAl Viro 	__poll_t mask = 0;
2341da177e4SLinus Torvalds 	unsigned long expires;
2351da177e4SLinus Torvalds 
2361da177e4SLinus Torvalds 	TRACE_TEXT(" dtlk_poll");
2371da177e4SLinus Torvalds 	/*
2381da177e4SLinus Torvalds 	   static long int j;
2391da177e4SLinus Torvalds 	   printk(".");
2401da177e4SLinus Torvalds 	   printk("<%ld>", jiffies-j);
2411da177e4SLinus Torvalds 	   j=jiffies;
2421da177e4SLinus Torvalds 	 */
2431da177e4SLinus Torvalds 	poll_wait(file, &dtlk_process_list, wait);
2441da177e4SLinus Torvalds 
2451da177e4SLinus Torvalds 	if (dtlk_has_indexing && dtlk_readable()) {
2461da177e4SLinus Torvalds 	        del_timer(&dtlk_timer);
247a9a08845SLinus Torvalds 		mask = EPOLLIN | EPOLLRDNORM;
2481da177e4SLinus Torvalds 	}
2491da177e4SLinus Torvalds 	if (dtlk_writeable()) {
2501da177e4SLinus Torvalds 	        del_timer(&dtlk_timer);
251a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;
2521da177e4SLinus Torvalds 	}
2531da177e4SLinus Torvalds 	/* there are no exception conditions */
2541da177e4SLinus Torvalds 
2551da177e4SLinus Torvalds 	/* There won't be any interrupts, so we set a timer instead. */
2561da177e4SLinus Torvalds 	expires = jiffies + 3*HZ / 100;
2571da177e4SLinus Torvalds 	mod_timer(&dtlk_timer, expires);
2581da177e4SLinus Torvalds 
2591da177e4SLinus Torvalds 	return mask;
2601da177e4SLinus Torvalds }
2611da177e4SLinus Torvalds 
26224ed960aSKees Cook static void dtlk_timer_tick(struct timer_list *unused)
2631da177e4SLinus Torvalds {
2641da177e4SLinus Torvalds 	TRACE_TEXT(" dtlk_timer_tick");
2651da177e4SLinus Torvalds 	wake_up_interruptible(&dtlk_process_list);
2661da177e4SLinus Torvalds }
2671da177e4SLinus Torvalds 
26855929332SArnd Bergmann static long dtlk_ioctl(struct file *file,
2691da177e4SLinus Torvalds 		       unsigned int cmd,
2701da177e4SLinus Torvalds 		       unsigned long arg)
2711da177e4SLinus Torvalds {
2721da177e4SLinus Torvalds 	char __user *argp = (char __user *)arg;
2731da177e4SLinus Torvalds 	struct dtlk_settings *sp;
2741da177e4SLinus Torvalds 	char portval;
2751da177e4SLinus Torvalds 	TRACE_TEXT(" dtlk_ioctl");
2761da177e4SLinus Torvalds 
2771da177e4SLinus Torvalds 	switch (cmd) {
2781da177e4SLinus Torvalds 
2791da177e4SLinus Torvalds 	case DTLK_INTERROGATE:
280613655faSArnd Bergmann 		mutex_lock(&dtlk_mutex);
2811da177e4SLinus Torvalds 		sp = dtlk_interrogate();
282613655faSArnd Bergmann 		mutex_unlock(&dtlk_mutex);
2831da177e4SLinus Torvalds 		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
2841da177e4SLinus Torvalds 			return -EINVAL;
2851da177e4SLinus Torvalds 		return 0;
2861da177e4SLinus Torvalds 
2871da177e4SLinus Torvalds 	case DTLK_STATUS:
2881da177e4SLinus Torvalds 		portval = inb_p(dtlk_port_tts);
2891da177e4SLinus Torvalds 		return put_user(portval, argp);
2901da177e4SLinus Torvalds 
2911da177e4SLinus Torvalds 	default:
2921da177e4SLinus Torvalds 		return -EINVAL;
2931da177e4SLinus Torvalds 	}
2941da177e4SLinus Torvalds }
2951da177e4SLinus Torvalds 
296f2b9857eSJonathan Corbet /* Note that nobody ever sets dtlk_busy... */
2971da177e4SLinus Torvalds static int dtlk_open(struct inode *inode, struct file *file)
2981da177e4SLinus Torvalds {
2991da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_open");
3001da177e4SLinus Torvalds 
3011da177e4SLinus Torvalds 	switch (iminor(inode)) {
3021da177e4SLinus Torvalds 	case DTLK_MINOR:
3031da177e4SLinus Torvalds 		if (dtlk_busy)
3041da177e4SLinus Torvalds 			return -EBUSY;
305c5bf68feSKirill Smelkov 		return stream_open(inode, file);
3061da177e4SLinus Torvalds 
3071da177e4SLinus Torvalds 	default:
3081da177e4SLinus Torvalds 		return -ENXIO;
3091da177e4SLinus Torvalds 	}
3101da177e4SLinus Torvalds }
3111da177e4SLinus Torvalds 
3121da177e4SLinus Torvalds static int dtlk_release(struct inode *inode, struct file *file)
3131da177e4SLinus Torvalds {
3141da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_release");
3151da177e4SLinus Torvalds 
3161da177e4SLinus Torvalds 	switch (iminor(inode)) {
3171da177e4SLinus Torvalds 	case DTLK_MINOR:
3181da177e4SLinus Torvalds 		break;
3191da177e4SLinus Torvalds 
3201da177e4SLinus Torvalds 	default:
3211da177e4SLinus Torvalds 		break;
3221da177e4SLinus Torvalds 	}
3231da177e4SLinus Torvalds 	TRACE_RET;
3241da177e4SLinus Torvalds 
32540565f19SJiri Slaby 	del_timer_sync(&dtlk_timer);
3261da177e4SLinus Torvalds 
3271da177e4SLinus Torvalds 	return 0;
3281da177e4SLinus Torvalds }
3291da177e4SLinus Torvalds 
3301da177e4SLinus Torvalds static int __init dtlk_init(void)
3311da177e4SLinus Torvalds {
332b2bbe383SAkinobu Mita 	int err;
333b2bbe383SAkinobu Mita 
3341da177e4SLinus Torvalds 	dtlk_port_lpc = 0;
3351da177e4SLinus Torvalds 	dtlk_port_tts = 0;
3361da177e4SLinus Torvalds 	dtlk_busy = 0;
3371da177e4SLinus Torvalds 	dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
338b2bbe383SAkinobu Mita 	if (dtlk_major < 0) {
3391da177e4SLinus Torvalds 		printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
340b2bbe383SAkinobu Mita 		return dtlk_major;
3411da177e4SLinus Torvalds 	}
342b2bbe383SAkinobu Mita 	err = dtlk_dev_probe();
343b2bbe383SAkinobu Mita 	if (err) {
344b2bbe383SAkinobu Mita 		unregister_chrdev(dtlk_major, "dtlk");
345b2bbe383SAkinobu Mita 		return err;
346b2bbe383SAkinobu Mita 	}
3471da177e4SLinus Torvalds 	printk(", MAJOR %d\n", dtlk_major);
3481da177e4SLinus Torvalds 
3491da177e4SLinus Torvalds 	init_waitqueue_head(&dtlk_process_list);
3501da177e4SLinus Torvalds 
3511da177e4SLinus Torvalds 	return 0;
3521da177e4SLinus Torvalds }
3531da177e4SLinus Torvalds 
3541da177e4SLinus Torvalds static void __exit dtlk_cleanup (void)
3551da177e4SLinus Torvalds {
3561da177e4SLinus Torvalds 	dtlk_write_bytes("goodbye", 8);
3571da177e4SLinus Torvalds 	msleep_interruptible(500);		/* nap 0.50 sec but
3581da177e4SLinus Torvalds 						   could be awakened
3591da177e4SLinus Torvalds 						   earlier by
3601da177e4SLinus Torvalds 						   signals... */
3611da177e4SLinus Torvalds 
3621da177e4SLinus Torvalds 	dtlk_write_tts(DTLK_CLEAR);
3631da177e4SLinus Torvalds 	unregister_chrdev(dtlk_major, "dtlk");
3641da177e4SLinus Torvalds 	release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
3651da177e4SLinus Torvalds }
3661da177e4SLinus Torvalds 
3671da177e4SLinus Torvalds module_init(dtlk_init);
3681da177e4SLinus Torvalds module_exit(dtlk_cleanup);
3691da177e4SLinus Torvalds 
3701da177e4SLinus Torvalds /* ------------------------------------------------------------------------ */
3711da177e4SLinus Torvalds 
3721da177e4SLinus Torvalds static int dtlk_readable(void)
3731da177e4SLinus Torvalds {
3741da177e4SLinus Torvalds #ifdef TRACING
3751da177e4SLinus Torvalds 	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
3761da177e4SLinus Torvalds #endif
3771da177e4SLinus Torvalds 	return inb_p(dtlk_port_lpc) != 0x7f;
3781da177e4SLinus Torvalds }
3791da177e4SLinus Torvalds 
3801da177e4SLinus Torvalds static int dtlk_writeable(void)
3811da177e4SLinus Torvalds {
3821da177e4SLinus Torvalds 	/* TRACE_TEXT(" dtlk_writeable"); */
3831da177e4SLinus Torvalds #ifdef TRACINGMORE
3841da177e4SLinus Torvalds 	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
3851da177e4SLinus Torvalds #endif
3861da177e4SLinus Torvalds 	return inb_p(dtlk_port_tts) & TTS_WRITABLE;
3871da177e4SLinus Torvalds }
3881da177e4SLinus Torvalds 
3891da177e4SLinus Torvalds static int __init dtlk_dev_probe(void)
3901da177e4SLinus Torvalds {
3911da177e4SLinus Torvalds 	unsigned int testval = 0;
3921da177e4SLinus Torvalds 	int i = 0;
3931da177e4SLinus Torvalds 	struct dtlk_settings *sp;
3941da177e4SLinus Torvalds 
3951da177e4SLinus Torvalds 	if (dtlk_port_lpc | dtlk_port_tts)
3961da177e4SLinus Torvalds 		return -EBUSY;
3971da177e4SLinus Torvalds 
3981da177e4SLinus Torvalds 	for (i = 0; dtlk_portlist[i]; i++) {
3991da177e4SLinus Torvalds #if 0
4001da177e4SLinus Torvalds 		printk("DoubleTalk PC - Port %03x = %04x\n",
4011da177e4SLinus Torvalds 		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
4021da177e4SLinus Torvalds #endif
4031da177e4SLinus Torvalds 
4041da177e4SLinus Torvalds 		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT,
4051da177e4SLinus Torvalds 			       "dtlk"))
4061da177e4SLinus Torvalds 			continue;
4071da177e4SLinus Torvalds 		testval = inw_p(dtlk_portlist[i]);
4081da177e4SLinus Torvalds 		if ((testval &= 0xfbff) == 0x107f) {
4091da177e4SLinus Torvalds 			dtlk_port_lpc = dtlk_portlist[i];
4101da177e4SLinus Torvalds 			dtlk_port_tts = dtlk_port_lpc + 1;
4111da177e4SLinus Torvalds 
4121da177e4SLinus Torvalds 			sp = dtlk_interrogate();
4131da177e4SLinus Torvalds 			printk("DoubleTalk PC at %03x-%03x, "
4141da177e4SLinus Torvalds 			       "ROM version %s, serial number %u",
4151da177e4SLinus Torvalds 			       dtlk_portlist[i], dtlk_portlist[i] +
4161da177e4SLinus Torvalds 			       DTLK_IO_EXTENT - 1,
4171da177e4SLinus Torvalds 			       sp->rom_version, sp->serial_number);
4181da177e4SLinus Torvalds 
4191da177e4SLinus Torvalds                         /* put LPC port into known state, so
4201da177e4SLinus Torvalds 			   dtlk_readable() gives valid result */
4211da177e4SLinus Torvalds 			outb_p(0xff, dtlk_port_lpc);
4221da177e4SLinus Torvalds 
4231da177e4SLinus Torvalds                         /* INIT string and index marker */
4241da177e4SLinus Torvalds 			dtlk_write_bytes("\036\1@\0\0012I\r", 8);
4251da177e4SLinus Torvalds 			/* posting an index takes 18 msec.  Here, we
4261da177e4SLinus Torvalds 			   wait up to 100 msec to see whether it
4271da177e4SLinus Torvalds 			   appears. */
4281da177e4SLinus Torvalds 			msleep_interruptible(100);
4291da177e4SLinus Torvalds 			dtlk_has_indexing = dtlk_readable();
4301da177e4SLinus Torvalds #ifdef TRACING
4311da177e4SLinus Torvalds 			printk(", indexing %d\n", dtlk_has_indexing);
4321da177e4SLinus Torvalds #endif
4331da177e4SLinus Torvalds #ifdef INSCOPE
4341da177e4SLinus Torvalds 			{
4351da177e4SLinus Torvalds /* This macro records ten samples read from the LPC port, for later display */
4361da177e4SLinus Torvalds #define LOOK					\
4371da177e4SLinus Torvalds for (i = 0; i < 10; i++)			\
4381da177e4SLinus Torvalds   {						\
4391da177e4SLinus Torvalds     buffer[b++] = inb_p(dtlk_port_lpc);		\
4401da177e4SLinus Torvalds     __delay(loops_per_jiffy/(1000000/HZ));             \
4411da177e4SLinus Torvalds   }
4421da177e4SLinus Torvalds 				char buffer[1000];
4431da177e4SLinus Torvalds 				int b = 0, i, j;
4441da177e4SLinus Torvalds 
4451da177e4SLinus Torvalds 				LOOK
4461da177e4SLinus Torvalds 				outb_p(0xff, dtlk_port_lpc);
4471da177e4SLinus Torvalds 				buffer[b++] = 0;
4481da177e4SLinus Torvalds 				LOOK
4491da177e4SLinus Torvalds 				dtlk_write_bytes("\0012I\r", 4);
4501da177e4SLinus Torvalds 				buffer[b++] = 0;
4511da177e4SLinus Torvalds 				__delay(50 * loops_per_jiffy / (1000/HZ));
4521da177e4SLinus Torvalds 				outb_p(0xff, dtlk_port_lpc);
4531da177e4SLinus Torvalds 				buffer[b++] = 0;
4541da177e4SLinus Torvalds 				LOOK
4551da177e4SLinus Torvalds 
4561da177e4SLinus Torvalds 				printk("\n");
4571da177e4SLinus Torvalds 				for (j = 0; j < b; j++)
4581da177e4SLinus Torvalds 					printk(" %02x", buffer[j]);
4591da177e4SLinus Torvalds 				printk("\n");
4601da177e4SLinus Torvalds 			}
4611da177e4SLinus Torvalds #endif				/* INSCOPE */
4621da177e4SLinus Torvalds 
4631da177e4SLinus Torvalds #ifdef OUTSCOPE
4641da177e4SLinus Torvalds 			{
4651da177e4SLinus Torvalds /* This macro records ten samples read from the TTS port, for later display */
4661da177e4SLinus Torvalds #define LOOK					\
4671da177e4SLinus Torvalds for (i = 0; i < 10; i++)			\
4681da177e4SLinus Torvalds   {						\
4691da177e4SLinus Torvalds     buffer[b++] = inb_p(dtlk_port_tts);		\
4701da177e4SLinus Torvalds     __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
4711da177e4SLinus Torvalds   }
4721da177e4SLinus Torvalds 				char buffer[1000];
4731da177e4SLinus Torvalds 				int b = 0, i, j;
4741da177e4SLinus Torvalds 
4751da177e4SLinus Torvalds 				mdelay(10);	/* 10 ms */
4761da177e4SLinus Torvalds 				LOOK
4771da177e4SLinus Torvalds 				outb_p(0x03, dtlk_port_tts);
4781da177e4SLinus Torvalds 				buffer[b++] = 0;
4791da177e4SLinus Torvalds 				LOOK
4801da177e4SLinus Torvalds 				LOOK
4811da177e4SLinus Torvalds 
4821da177e4SLinus Torvalds 				printk("\n");
4831da177e4SLinus Torvalds 				for (j = 0; j < b; j++)
4841da177e4SLinus Torvalds 					printk(" %02x", buffer[j]);
4851da177e4SLinus Torvalds 				printk("\n");
4861da177e4SLinus Torvalds 			}
4871da177e4SLinus Torvalds #endif				/* OUTSCOPE */
4881da177e4SLinus Torvalds 
4891da177e4SLinus Torvalds 			dtlk_write_bytes("Double Talk found", 18);
4901da177e4SLinus Torvalds 
4911da177e4SLinus Torvalds 			return 0;
4921da177e4SLinus Torvalds 		}
4931da177e4SLinus Torvalds 		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
4941da177e4SLinus Torvalds 	}
4951da177e4SLinus Torvalds 
49649b6e2adSDave Jones 	printk(KERN_INFO "DoubleTalk PC - not found\n");
4971da177e4SLinus Torvalds 	return -ENODEV;
4981da177e4SLinus Torvalds }
4991da177e4SLinus Torvalds 
5001da177e4SLinus Torvalds /*
5011da177e4SLinus Torvalds    static void dtlk_handle_error(char op, char rc, unsigned int minor)
5021da177e4SLinus Torvalds    {
5031da177e4SLinus Torvalds    printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n",
5041da177e4SLinus Torvalds    minor, op, rc);
5051da177e4SLinus Torvalds    return;
5061da177e4SLinus Torvalds    }
5071da177e4SLinus Torvalds  */
5081da177e4SLinus Torvalds 
5091da177e4SLinus Torvalds /* interrogate the DoubleTalk PC and return its settings */
5101da177e4SLinus Torvalds static struct dtlk_settings *dtlk_interrogate(void)
5111da177e4SLinus Torvalds {
5121da177e4SLinus Torvalds 	unsigned char *t;
5131da177e4SLinus Torvalds 	static char buf[sizeof(struct dtlk_settings) + 1];
5141da177e4SLinus Torvalds 	int total, i;
5151da177e4SLinus Torvalds 	static struct dtlk_settings status;
5161da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_interrogate");
5171da177e4SLinus Torvalds 	dtlk_write_bytes("\030\001?", 3);
5181da177e4SLinus Torvalds 	for (total = 0, i = 0; i < 50; i++) {
5191da177e4SLinus Torvalds 		buf[total] = dtlk_read_tts();
5201da177e4SLinus Torvalds 		if (total > 2 && buf[total] == 0x7f)
5211da177e4SLinus Torvalds 			break;
5221da177e4SLinus Torvalds 		if (total < sizeof(struct dtlk_settings))
5231da177e4SLinus Torvalds 			total++;
5241da177e4SLinus Torvalds 	}
5251da177e4SLinus Torvalds 	/*
5261da177e4SLinus Torvalds 	   if (i==50) printk("interrogate() read overrun\n");
5271da177e4SLinus Torvalds 	   for (i=0; i<sizeof(buf); i++)
5281da177e4SLinus Torvalds 	   printk(" %02x", buf[i]);
5291da177e4SLinus Torvalds 	   printk("\n");
5301da177e4SLinus Torvalds 	 */
5311da177e4SLinus Torvalds 	t = buf;
5321da177e4SLinus Torvalds 	status.serial_number = t[0] + t[1] * 256; /* serial number is
5331da177e4SLinus Torvalds 						     little endian */
5341da177e4SLinus Torvalds 	t += 2;
5351da177e4SLinus Torvalds 
5361da177e4SLinus Torvalds 	i = 0;
5371da177e4SLinus Torvalds 	while (*t != '\r') {
5381da177e4SLinus Torvalds 		status.rom_version[i] = *t;
5391da177e4SLinus Torvalds 		if (i < sizeof(status.rom_version) - 1)
5401da177e4SLinus Torvalds 			i++;
5411da177e4SLinus Torvalds 		t++;
5421da177e4SLinus Torvalds 	}
5431da177e4SLinus Torvalds 	status.rom_version[i] = 0;
5441da177e4SLinus Torvalds 	t++;
5451da177e4SLinus Torvalds 
5461da177e4SLinus Torvalds 	status.mode = *t++;
5471da177e4SLinus Torvalds 	status.punc_level = *t++;
5481da177e4SLinus Torvalds 	status.formant_freq = *t++;
5491da177e4SLinus Torvalds 	status.pitch = *t++;
5501da177e4SLinus Torvalds 	status.speed = *t++;
5511da177e4SLinus Torvalds 	status.volume = *t++;
5521da177e4SLinus Torvalds 	status.tone = *t++;
5531da177e4SLinus Torvalds 	status.expression = *t++;
5541da177e4SLinus Torvalds 	status.ext_dict_loaded = *t++;
5551da177e4SLinus Torvalds 	status.ext_dict_status = *t++;
5561da177e4SLinus Torvalds 	status.free_ram = *t++;
5571da177e4SLinus Torvalds 	status.articulation = *t++;
5581da177e4SLinus Torvalds 	status.reverb = *t++;
5591da177e4SLinus Torvalds 	status.eob = *t++;
5601da177e4SLinus Torvalds 	status.has_indexing = dtlk_has_indexing;
5611da177e4SLinus Torvalds 	TRACE_RET;
5621da177e4SLinus Torvalds 	return &status;
5631da177e4SLinus Torvalds }
5641da177e4SLinus Torvalds 
5651da177e4SLinus Torvalds static char dtlk_read_tts(void)
5661da177e4SLinus Torvalds {
5671da177e4SLinus Torvalds 	int portval, retries = 0;
5681da177e4SLinus Torvalds 	char ch;
5691da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_read_tts");
5701da177e4SLinus Torvalds 
5711da177e4SLinus Torvalds 	/* verify DT is ready, read char, wait for ACK */
5721da177e4SLinus Torvalds 	do {
5731da177e4SLinus Torvalds 		portval = inb_p(dtlk_port_tts);
5741da177e4SLinus Torvalds 	} while ((portval & TTS_READABLE) == 0 &&
5751da177e4SLinus Torvalds 		 retries++ < DTLK_MAX_RETRIES);
5764390b9e0SRoel Kluin 	if (retries > DTLK_MAX_RETRIES)
5771da177e4SLinus Torvalds 		printk(KERN_ERR "dtlk_read_tts() timeout\n");
5781da177e4SLinus Torvalds 
5791da177e4SLinus Torvalds 	ch = inb_p(dtlk_port_tts);	/* input from TTS port */
5801da177e4SLinus Torvalds 	ch &= 0x7f;
5811da177e4SLinus Torvalds 	outb_p(ch, dtlk_port_tts);
5821da177e4SLinus Torvalds 
5831da177e4SLinus Torvalds 	retries = 0;
5841da177e4SLinus Torvalds 	do {
5851da177e4SLinus Torvalds 		portval = inb_p(dtlk_port_tts);
5861da177e4SLinus Torvalds 	} while ((portval & TTS_READABLE) != 0 &&
5871da177e4SLinus Torvalds 		 retries++ < DTLK_MAX_RETRIES);
5884390b9e0SRoel Kluin 	if (retries > DTLK_MAX_RETRIES)
5891da177e4SLinus Torvalds 		printk(KERN_ERR "dtlk_read_tts() timeout\n");
5901da177e4SLinus Torvalds 
5911da177e4SLinus Torvalds 	TRACE_RET;
5921da177e4SLinus Torvalds 	return ch;
5931da177e4SLinus Torvalds }
5941da177e4SLinus Torvalds 
5951da177e4SLinus Torvalds static char dtlk_read_lpc(void)
5961da177e4SLinus Torvalds {
5971da177e4SLinus Torvalds 	int retries = 0;
5981da177e4SLinus Torvalds 	char ch;
5991da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_read_lpc");
6001da177e4SLinus Torvalds 
6011da177e4SLinus Torvalds 	/* no need to test -- this is only called when the port is readable */
6021da177e4SLinus Torvalds 
6031da177e4SLinus Torvalds 	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */
6041da177e4SLinus Torvalds 
6051da177e4SLinus Torvalds 	outb_p(0xff, dtlk_port_lpc);
6061da177e4SLinus Torvalds 
6071da177e4SLinus Torvalds 	/* acknowledging a read takes 3-4
6081da177e4SLinus Torvalds 	   usec.  Here, we wait up to 20 usec
6091da177e4SLinus Torvalds 	   for the acknowledgement */
6101da177e4SLinus Torvalds 	retries = (loops_per_jiffy * 20) / (1000000/HZ);
6111da177e4SLinus Torvalds 	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
6121da177e4SLinus Torvalds 	if (retries == 0)
6131da177e4SLinus Torvalds 		printk(KERN_ERR "dtlk_read_lpc() timeout\n");
6141da177e4SLinus Torvalds 
6151da177e4SLinus Torvalds 	TRACE_RET;
6161da177e4SLinus Torvalds 	return ch;
6171da177e4SLinus Torvalds }
6181da177e4SLinus Torvalds 
6191da177e4SLinus Torvalds /* write n bytes to tts port */
6201da177e4SLinus Torvalds static char dtlk_write_bytes(const char *buf, int n)
6211da177e4SLinus Torvalds {
6221da177e4SLinus Torvalds 	char val = 0;
6231da177e4SLinus Torvalds 	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
6241da177e4SLinus Torvalds 	TRACE_TEXT("(dtlk_write_bytes");
6251da177e4SLinus Torvalds 	while (n-- > 0)
6261da177e4SLinus Torvalds 		val = dtlk_write_tts(*buf++);
6271da177e4SLinus Torvalds 	TRACE_RET;
6281da177e4SLinus Torvalds 	return val;
6291da177e4SLinus Torvalds }
6301da177e4SLinus Torvalds 
6311da177e4SLinus Torvalds static char dtlk_write_tts(char ch)
6321da177e4SLinus Torvalds {
6331da177e4SLinus Torvalds 	int retries = 0;
6341da177e4SLinus Torvalds #ifdef TRACINGMORE
6351da177e4SLinus Torvalds 	printk("  dtlk_write_tts(");
6361da177e4SLinus Torvalds 	if (' ' <= ch && ch <= '~')
6371da177e4SLinus Torvalds 		printk("'%c'", ch);
6381da177e4SLinus Torvalds 	else
6391da177e4SLinus Torvalds 		printk("0x%02x", ch);
6401da177e4SLinus Torvalds #endif
6411da177e4SLinus Torvalds 	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */
6421da177e4SLinus Torvalds 		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
6431da177e4SLinus Torvalds 		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */
6441da177e4SLinus Torvalds 			;
6454390b9e0SRoel Kluin 	if (retries > DTLK_MAX_RETRIES)
6461da177e4SLinus Torvalds 		printk(KERN_ERR "dtlk_write_tts() timeout\n");
6471da177e4SLinus Torvalds 
6481da177e4SLinus Torvalds 	outb_p(ch, dtlk_port_tts);	/* output to TTS port */
6491da177e4SLinus Torvalds 	/* the RDY bit goes zero 2-3 usec after writing, and goes
6501da177e4SLinus Torvalds 	   1 again 180-190 usec later.  Here, we wait up to 10
6511da177e4SLinus Torvalds 	   usec for the RDY bit to go zero. */
6521da177e4SLinus Torvalds 	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
6531da177e4SLinus Torvalds 		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
6541da177e4SLinus Torvalds 			break;
6551da177e4SLinus Torvalds 
6561da177e4SLinus Torvalds #ifdef TRACINGMORE
6571da177e4SLinus Torvalds 	printk(")\n");
6581da177e4SLinus Torvalds #endif
6591da177e4SLinus Torvalds 	return 0;
6601da177e4SLinus Torvalds }
6611da177e4SLinus Torvalds 
662*538a00a9SJeff Johnson MODULE_DESCRIPTION("RC Systems DoubleTalk PC speech card driver");
6631da177e4SLinus Torvalds MODULE_LICENSE("GPL");
664