162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*                                              -*- linux-c -*-
362306a36Sopenharmony_ci * dtlk.c - DoubleTalk PC driver for Linux
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Original author: Chris Pallotta <chris@allmedia.com>
662306a36Sopenharmony_ci * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * 2000-03-18 Jim Van Zandt: Fix polling.
962306a36Sopenharmony_ci *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
1062306a36Sopenharmony_ci *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
1162306a36Sopenharmony_ci *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
1262306a36Sopenharmony_ci *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/* This driver is for the DoubleTalk PC, a speech synthesizer
1662306a36Sopenharmony_ci   manufactured by RC Systems (http://www.rcsys.com/).  It was written
1762306a36Sopenharmony_ci   based on documentation in their User's Manual file and Developer's
1862306a36Sopenharmony_ci   Tools disk.
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci   The DoubleTalk PC contains four voice synthesizers: text-to-speech
2162306a36Sopenharmony_ci   (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
2262306a36Sopenharmony_ci   also has a tone generator.  Output data for LPC are written to the
2362306a36Sopenharmony_ci   LPC port, and output data for the other modes are written to the
2462306a36Sopenharmony_ci   TTS port.
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci   Two kinds of data can be read from the DoubleTalk: status
2762306a36Sopenharmony_ci   information (in response to the "\001?" interrogation command) is
2862306a36Sopenharmony_ci   read from the TTS port, and index markers (which mark the progress
2962306a36Sopenharmony_ci   of the speech) are read from the LPC port.  Not all models of the
3062306a36Sopenharmony_ci   DoubleTalk PC implement index markers.  Both the TTS and LPC ports
3162306a36Sopenharmony_ci   can also display status flags.
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci   The DoubleTalk PC generates no interrupts.
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci   These characteristics are mapped into the Unix stream I/O model as
3662306a36Sopenharmony_ci   follows:
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci   "write" sends bytes to the TTS port.  It is the responsibility of
3962306a36Sopenharmony_ci   the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
4062306a36Sopenharmony_ci   This driver was written for use with the text-to-speech
4162306a36Sopenharmony_ci   synthesizer.  If LPC output is needed some day, other minor device
4262306a36Sopenharmony_ci   numbers can be used to select among output modes.
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci   "read" gets index markers from the LPC port.  If the device does
4562306a36Sopenharmony_ci   not implement index markers, the read will fail with error EINVAL.
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci   Status information is available using the DTLK_INTERROGATE ioctl.
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#include <linux/module.h>
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci#define KERNEL
5462306a36Sopenharmony_ci#include <linux/types.h>
5562306a36Sopenharmony_ci#include <linux/fs.h>
5662306a36Sopenharmony_ci#include <linux/mm.h>
5762306a36Sopenharmony_ci#include <linux/errno.h>	/* for -EBUSY */
5862306a36Sopenharmony_ci#include <linux/ioport.h>	/* for request_region */
5962306a36Sopenharmony_ci#include <linux/delay.h>	/* for loops_per_jiffy */
6062306a36Sopenharmony_ci#include <linux/sched.h>
6162306a36Sopenharmony_ci#include <linux/mutex.h>
6262306a36Sopenharmony_ci#include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */
6362306a36Sopenharmony_ci#include <linux/uaccess.h>	/* for get_user, etc. */
6462306a36Sopenharmony_ci#include <linux/wait.h>		/* for wait_queue */
6562306a36Sopenharmony_ci#include <linux/init.h>		/* for __init, module_{init,exit} */
6662306a36Sopenharmony_ci#include <linux/poll.h>		/* for EPOLLIN, etc. */
6762306a36Sopenharmony_ci#include <linux/dtlk.h>		/* local header file for DoubleTalk values */
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci#ifdef TRACING
7062306a36Sopenharmony_ci#define TRACE_TEXT(str) printk(str);
7162306a36Sopenharmony_ci#define TRACE_RET printk(")")
7262306a36Sopenharmony_ci#else				/* !TRACING */
7362306a36Sopenharmony_ci#define TRACE_TEXT(str) ((void) 0)
7462306a36Sopenharmony_ci#define TRACE_RET ((void) 0)
7562306a36Sopenharmony_ci#endif				/* TRACING */
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic DEFINE_MUTEX(dtlk_mutex);
7862306a36Sopenharmony_cistatic void dtlk_timer_tick(struct timer_list *unused);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic int dtlk_major;
8162306a36Sopenharmony_cistatic int dtlk_port_lpc;
8262306a36Sopenharmony_cistatic int dtlk_port_tts;
8362306a36Sopenharmony_cistatic int dtlk_busy;
8462306a36Sopenharmony_cistatic int dtlk_has_indexing;
8562306a36Sopenharmony_cistatic unsigned int dtlk_portlist[] =
8662306a36Sopenharmony_ci{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
8762306a36Sopenharmony_cistatic wait_queue_head_t dtlk_process_list;
8862306a36Sopenharmony_cistatic DEFINE_TIMER(dtlk_timer, dtlk_timer_tick);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci/* prototypes for file_operations struct */
9162306a36Sopenharmony_cistatic ssize_t dtlk_read(struct file *, char __user *,
9262306a36Sopenharmony_ci			 size_t nbytes, loff_t * ppos);
9362306a36Sopenharmony_cistatic ssize_t dtlk_write(struct file *, const char __user *,
9462306a36Sopenharmony_ci			  size_t nbytes, loff_t * ppos);
9562306a36Sopenharmony_cistatic __poll_t dtlk_poll(struct file *, poll_table *);
9662306a36Sopenharmony_cistatic int dtlk_open(struct inode *, struct file *);
9762306a36Sopenharmony_cistatic int dtlk_release(struct inode *, struct file *);
9862306a36Sopenharmony_cistatic long dtlk_ioctl(struct file *file,
9962306a36Sopenharmony_ci		       unsigned int cmd, unsigned long arg);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic const struct file_operations dtlk_fops =
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	.owner		= THIS_MODULE,
10462306a36Sopenharmony_ci	.read		= dtlk_read,
10562306a36Sopenharmony_ci	.write		= dtlk_write,
10662306a36Sopenharmony_ci	.poll		= dtlk_poll,
10762306a36Sopenharmony_ci	.unlocked_ioctl	= dtlk_ioctl,
10862306a36Sopenharmony_ci	.open		= dtlk_open,
10962306a36Sopenharmony_ci	.release	= dtlk_release,
11062306a36Sopenharmony_ci	.llseek		= no_llseek,
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci/* local prototypes */
11462306a36Sopenharmony_cistatic int dtlk_dev_probe(void);
11562306a36Sopenharmony_cistatic struct dtlk_settings *dtlk_interrogate(void);
11662306a36Sopenharmony_cistatic int dtlk_readable(void);
11762306a36Sopenharmony_cistatic char dtlk_read_lpc(void);
11862306a36Sopenharmony_cistatic char dtlk_read_tts(void);
11962306a36Sopenharmony_cistatic int dtlk_writeable(void);
12062306a36Sopenharmony_cistatic char dtlk_write_bytes(const char *buf, int n);
12162306a36Sopenharmony_cistatic char dtlk_write_tts(char);
12262306a36Sopenharmony_ci/*
12362306a36Sopenharmony_ci   static void dtlk_handle_error(char, char, unsigned int);
12462306a36Sopenharmony_ci */
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic ssize_t dtlk_read(struct file *file, char __user *buf,
12762306a36Sopenharmony_ci			 size_t count, loff_t * ppos)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	unsigned int minor = iminor(file_inode(file));
13062306a36Sopenharmony_ci	char ch;
13162306a36Sopenharmony_ci	int i = 0, retries;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_read");
13462306a36Sopenharmony_ci	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (minor != DTLK_MINOR || !dtlk_has_indexing)
13762306a36Sopenharmony_ci		return -EINVAL;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	for (retries = 0; retries < loops_per_jiffy; retries++) {
14062306a36Sopenharmony_ci		while (i < count && dtlk_readable()) {
14162306a36Sopenharmony_ci			ch = dtlk_read_lpc();
14262306a36Sopenharmony_ci			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
14362306a36Sopenharmony_ci			if (put_user(ch, buf++))
14462306a36Sopenharmony_ci				return -EFAULT;
14562306a36Sopenharmony_ci			i++;
14662306a36Sopenharmony_ci		}
14762306a36Sopenharmony_ci		if (i)
14862306a36Sopenharmony_ci			return i;
14962306a36Sopenharmony_ci		if (file->f_flags & O_NONBLOCK)
15062306a36Sopenharmony_ci			break;
15162306a36Sopenharmony_ci		msleep_interruptible(100);
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci	if (retries == loops_per_jiffy)
15462306a36Sopenharmony_ci		printk(KERN_ERR "dtlk_read times out\n");
15562306a36Sopenharmony_ci	TRACE_RET;
15662306a36Sopenharmony_ci	return -EAGAIN;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic ssize_t dtlk_write(struct file *file, const char __user *buf,
16062306a36Sopenharmony_ci			  size_t count, loff_t * ppos)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	int i = 0, retries = 0, ch;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_write");
16562306a36Sopenharmony_ci#ifdef TRACING
16662306a36Sopenharmony_ci	printk(" \"");
16762306a36Sopenharmony_ci	{
16862306a36Sopenharmony_ci		int i, ch;
16962306a36Sopenharmony_ci		for (i = 0; i < count; i++) {
17062306a36Sopenharmony_ci			if (get_user(ch, buf + i))
17162306a36Sopenharmony_ci				return -EFAULT;
17262306a36Sopenharmony_ci			if (' ' <= ch && ch <= '~')
17362306a36Sopenharmony_ci				printk("%c", ch);
17462306a36Sopenharmony_ci			else
17562306a36Sopenharmony_ci				printk("\\%03o", ch);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci		printk("\"");
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci#endif
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (iminor(file_inode(file)) != DTLK_MINOR)
18262306a36Sopenharmony_ci		return -EINVAL;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	while (1) {
18562306a36Sopenharmony_ci		while (i < count && !get_user(ch, buf) &&
18662306a36Sopenharmony_ci		       (ch == DTLK_CLEAR || dtlk_writeable())) {
18762306a36Sopenharmony_ci			dtlk_write_tts(ch);
18862306a36Sopenharmony_ci			buf++;
18962306a36Sopenharmony_ci			i++;
19062306a36Sopenharmony_ci			if (i % 5 == 0)
19162306a36Sopenharmony_ci				/* We yield our time until scheduled
19262306a36Sopenharmony_ci				   again.  This reduces the transfer
19362306a36Sopenharmony_ci				   rate to 500 bytes/sec, but that's
19462306a36Sopenharmony_ci				   still enough to keep up with the
19562306a36Sopenharmony_ci				   speech synthesizer. */
19662306a36Sopenharmony_ci				msleep_interruptible(1);
19762306a36Sopenharmony_ci			else {
19862306a36Sopenharmony_ci				/* the RDY bit goes zero 2-3 usec
19962306a36Sopenharmony_ci				   after writing, and goes 1 again
20062306a36Sopenharmony_ci				   180-190 usec later.  Here, we wait
20162306a36Sopenharmony_ci				   up to 250 usec for the RDY bit to
20262306a36Sopenharmony_ci				   go nonzero. */
20362306a36Sopenharmony_ci				for (retries = 0;
20462306a36Sopenharmony_ci				     retries < loops_per_jiffy / (4000/HZ);
20562306a36Sopenharmony_ci				     retries++)
20662306a36Sopenharmony_ci					if (inb_p(dtlk_port_tts) &
20762306a36Sopenharmony_ci					    TTS_WRITABLE)
20862306a36Sopenharmony_ci						break;
20962306a36Sopenharmony_ci			}
21062306a36Sopenharmony_ci			retries = 0;
21162306a36Sopenharmony_ci		}
21262306a36Sopenharmony_ci		if (i == count)
21362306a36Sopenharmony_ci			return i;
21462306a36Sopenharmony_ci		if (file->f_flags & O_NONBLOCK)
21562306a36Sopenharmony_ci			break;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci		msleep_interruptible(1);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		if (++retries > 10 * HZ) { /* wait no more than 10 sec
22062306a36Sopenharmony_ci					      from last write */
22162306a36Sopenharmony_ci			printk("dtlk: write timeout.  "
22262306a36Sopenharmony_ci			       "inb_p(dtlk_port_tts) = 0x%02x\n",
22362306a36Sopenharmony_ci			       inb_p(dtlk_port_tts));
22462306a36Sopenharmony_ci			TRACE_RET;
22562306a36Sopenharmony_ci			return -EBUSY;
22662306a36Sopenharmony_ci		}
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci	TRACE_RET;
22962306a36Sopenharmony_ci	return -EAGAIN;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic __poll_t dtlk_poll(struct file *file, poll_table * wait)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	__poll_t mask = 0;
23562306a36Sopenharmony_ci	unsigned long expires;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	TRACE_TEXT(" dtlk_poll");
23862306a36Sopenharmony_ci	/*
23962306a36Sopenharmony_ci	   static long int j;
24062306a36Sopenharmony_ci	   printk(".");
24162306a36Sopenharmony_ci	   printk("<%ld>", jiffies-j);
24262306a36Sopenharmony_ci	   j=jiffies;
24362306a36Sopenharmony_ci	 */
24462306a36Sopenharmony_ci	poll_wait(file, &dtlk_process_list, wait);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (dtlk_has_indexing && dtlk_readable()) {
24762306a36Sopenharmony_ci	        del_timer(&dtlk_timer);
24862306a36Sopenharmony_ci		mask = EPOLLIN | EPOLLRDNORM;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci	if (dtlk_writeable()) {
25162306a36Sopenharmony_ci	        del_timer(&dtlk_timer);
25262306a36Sopenharmony_ci		mask |= EPOLLOUT | EPOLLWRNORM;
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci	/* there are no exception conditions */
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	/* There won't be any interrupts, so we set a timer instead. */
25762306a36Sopenharmony_ci	expires = jiffies + 3*HZ / 100;
25862306a36Sopenharmony_ci	mod_timer(&dtlk_timer, expires);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return mask;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic void dtlk_timer_tick(struct timer_list *unused)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	TRACE_TEXT(" dtlk_timer_tick");
26662306a36Sopenharmony_ci	wake_up_interruptible(&dtlk_process_list);
26762306a36Sopenharmony_ci}
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_cistatic long dtlk_ioctl(struct file *file,
27062306a36Sopenharmony_ci		       unsigned int cmd,
27162306a36Sopenharmony_ci		       unsigned long arg)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	char __user *argp = (char __user *)arg;
27462306a36Sopenharmony_ci	struct dtlk_settings *sp;
27562306a36Sopenharmony_ci	char portval;
27662306a36Sopenharmony_ci	TRACE_TEXT(" dtlk_ioctl");
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	switch (cmd) {
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	case DTLK_INTERROGATE:
28162306a36Sopenharmony_ci		mutex_lock(&dtlk_mutex);
28262306a36Sopenharmony_ci		sp = dtlk_interrogate();
28362306a36Sopenharmony_ci		mutex_unlock(&dtlk_mutex);
28462306a36Sopenharmony_ci		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
28562306a36Sopenharmony_ci			return -EINVAL;
28662306a36Sopenharmony_ci		return 0;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	case DTLK_STATUS:
28962306a36Sopenharmony_ci		portval = inb_p(dtlk_port_tts);
29062306a36Sopenharmony_ci		return put_user(portval, argp);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	default:
29362306a36Sopenharmony_ci		return -EINVAL;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci/* Note that nobody ever sets dtlk_busy... */
29862306a36Sopenharmony_cistatic int dtlk_open(struct inode *inode, struct file *file)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_open");
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	switch (iminor(inode)) {
30362306a36Sopenharmony_ci	case DTLK_MINOR:
30462306a36Sopenharmony_ci		if (dtlk_busy)
30562306a36Sopenharmony_ci			return -EBUSY;
30662306a36Sopenharmony_ci		return stream_open(inode, file);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	default:
30962306a36Sopenharmony_ci		return -ENXIO;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic int dtlk_release(struct inode *inode, struct file *file)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_release");
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	switch (iminor(inode)) {
31862306a36Sopenharmony_ci	case DTLK_MINOR:
31962306a36Sopenharmony_ci		break;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	default:
32262306a36Sopenharmony_ci		break;
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci	TRACE_RET;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	del_timer_sync(&dtlk_timer);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	return 0;
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_cistatic int __init dtlk_init(void)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	int err;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	dtlk_port_lpc = 0;
33662306a36Sopenharmony_ci	dtlk_port_tts = 0;
33762306a36Sopenharmony_ci	dtlk_busy = 0;
33862306a36Sopenharmony_ci	dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
33962306a36Sopenharmony_ci	if (dtlk_major < 0) {
34062306a36Sopenharmony_ci		printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
34162306a36Sopenharmony_ci		return dtlk_major;
34262306a36Sopenharmony_ci	}
34362306a36Sopenharmony_ci	err = dtlk_dev_probe();
34462306a36Sopenharmony_ci	if (err) {
34562306a36Sopenharmony_ci		unregister_chrdev(dtlk_major, "dtlk");
34662306a36Sopenharmony_ci		return err;
34762306a36Sopenharmony_ci	}
34862306a36Sopenharmony_ci	printk(", MAJOR %d\n", dtlk_major);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	init_waitqueue_head(&dtlk_process_list);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	return 0;
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic void __exit dtlk_cleanup (void)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	dtlk_write_bytes("goodbye", 8);
35862306a36Sopenharmony_ci	msleep_interruptible(500);		/* nap 0.50 sec but
35962306a36Sopenharmony_ci						   could be awakened
36062306a36Sopenharmony_ci						   earlier by
36162306a36Sopenharmony_ci						   signals... */
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	dtlk_write_tts(DTLK_CLEAR);
36462306a36Sopenharmony_ci	unregister_chrdev(dtlk_major, "dtlk");
36562306a36Sopenharmony_ci	release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cimodule_init(dtlk_init);
36962306a36Sopenharmony_cimodule_exit(dtlk_cleanup);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci/* ------------------------------------------------------------------------ */
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic int dtlk_readable(void)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci#ifdef TRACING
37662306a36Sopenharmony_ci	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
37762306a36Sopenharmony_ci#endif
37862306a36Sopenharmony_ci	return inb_p(dtlk_port_lpc) != 0x7f;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int dtlk_writeable(void)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	/* TRACE_TEXT(" dtlk_writeable"); */
38462306a36Sopenharmony_ci#ifdef TRACINGMORE
38562306a36Sopenharmony_ci	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
38662306a36Sopenharmony_ci#endif
38762306a36Sopenharmony_ci	return inb_p(dtlk_port_tts) & TTS_WRITABLE;
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_cistatic int __init dtlk_dev_probe(void)
39162306a36Sopenharmony_ci{
39262306a36Sopenharmony_ci	unsigned int testval = 0;
39362306a36Sopenharmony_ci	int i = 0;
39462306a36Sopenharmony_ci	struct dtlk_settings *sp;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	if (dtlk_port_lpc | dtlk_port_tts)
39762306a36Sopenharmony_ci		return -EBUSY;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	for (i = 0; dtlk_portlist[i]; i++) {
40062306a36Sopenharmony_ci#if 0
40162306a36Sopenharmony_ci		printk("DoubleTalk PC - Port %03x = %04x\n",
40262306a36Sopenharmony_ci		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
40362306a36Sopenharmony_ci#endif
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT,
40662306a36Sopenharmony_ci			       "dtlk"))
40762306a36Sopenharmony_ci			continue;
40862306a36Sopenharmony_ci		testval = inw_p(dtlk_portlist[i]);
40962306a36Sopenharmony_ci		if ((testval &= 0xfbff) == 0x107f) {
41062306a36Sopenharmony_ci			dtlk_port_lpc = dtlk_portlist[i];
41162306a36Sopenharmony_ci			dtlk_port_tts = dtlk_port_lpc + 1;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci			sp = dtlk_interrogate();
41462306a36Sopenharmony_ci			printk("DoubleTalk PC at %03x-%03x, "
41562306a36Sopenharmony_ci			       "ROM version %s, serial number %u",
41662306a36Sopenharmony_ci			       dtlk_portlist[i], dtlk_portlist[i] +
41762306a36Sopenharmony_ci			       DTLK_IO_EXTENT - 1,
41862306a36Sopenharmony_ci			       sp->rom_version, sp->serial_number);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci                        /* put LPC port into known state, so
42162306a36Sopenharmony_ci			   dtlk_readable() gives valid result */
42262306a36Sopenharmony_ci			outb_p(0xff, dtlk_port_lpc);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci                        /* INIT string and index marker */
42562306a36Sopenharmony_ci			dtlk_write_bytes("\036\1@\0\0012I\r", 8);
42662306a36Sopenharmony_ci			/* posting an index takes 18 msec.  Here, we
42762306a36Sopenharmony_ci			   wait up to 100 msec to see whether it
42862306a36Sopenharmony_ci			   appears. */
42962306a36Sopenharmony_ci			msleep_interruptible(100);
43062306a36Sopenharmony_ci			dtlk_has_indexing = dtlk_readable();
43162306a36Sopenharmony_ci#ifdef TRACING
43262306a36Sopenharmony_ci			printk(", indexing %d\n", dtlk_has_indexing);
43362306a36Sopenharmony_ci#endif
43462306a36Sopenharmony_ci#ifdef INSCOPE
43562306a36Sopenharmony_ci			{
43662306a36Sopenharmony_ci/* This macro records ten samples read from the LPC port, for later display */
43762306a36Sopenharmony_ci#define LOOK					\
43862306a36Sopenharmony_cifor (i = 0; i < 10; i++)			\
43962306a36Sopenharmony_ci  {						\
44062306a36Sopenharmony_ci    buffer[b++] = inb_p(dtlk_port_lpc);		\
44162306a36Sopenharmony_ci    __delay(loops_per_jiffy/(1000000/HZ));             \
44262306a36Sopenharmony_ci  }
44362306a36Sopenharmony_ci				char buffer[1000];
44462306a36Sopenharmony_ci				int b = 0, i, j;
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci				LOOK
44762306a36Sopenharmony_ci				outb_p(0xff, dtlk_port_lpc);
44862306a36Sopenharmony_ci				buffer[b++] = 0;
44962306a36Sopenharmony_ci				LOOK
45062306a36Sopenharmony_ci				dtlk_write_bytes("\0012I\r", 4);
45162306a36Sopenharmony_ci				buffer[b++] = 0;
45262306a36Sopenharmony_ci				__delay(50 * loops_per_jiffy / (1000/HZ));
45362306a36Sopenharmony_ci				outb_p(0xff, dtlk_port_lpc);
45462306a36Sopenharmony_ci				buffer[b++] = 0;
45562306a36Sopenharmony_ci				LOOK
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci				printk("\n");
45862306a36Sopenharmony_ci				for (j = 0; j < b; j++)
45962306a36Sopenharmony_ci					printk(" %02x", buffer[j]);
46062306a36Sopenharmony_ci				printk("\n");
46162306a36Sopenharmony_ci			}
46262306a36Sopenharmony_ci#endif				/* INSCOPE */
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci#ifdef OUTSCOPE
46562306a36Sopenharmony_ci			{
46662306a36Sopenharmony_ci/* This macro records ten samples read from the TTS port, for later display */
46762306a36Sopenharmony_ci#define LOOK					\
46862306a36Sopenharmony_cifor (i = 0; i < 10; i++)			\
46962306a36Sopenharmony_ci  {						\
47062306a36Sopenharmony_ci    buffer[b++] = inb_p(dtlk_port_tts);		\
47162306a36Sopenharmony_ci    __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
47262306a36Sopenharmony_ci  }
47362306a36Sopenharmony_ci				char buffer[1000];
47462306a36Sopenharmony_ci				int b = 0, i, j;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci				mdelay(10);	/* 10 ms */
47762306a36Sopenharmony_ci				LOOK
47862306a36Sopenharmony_ci				outb_p(0x03, dtlk_port_tts);
47962306a36Sopenharmony_ci				buffer[b++] = 0;
48062306a36Sopenharmony_ci				LOOK
48162306a36Sopenharmony_ci				LOOK
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci				printk("\n");
48462306a36Sopenharmony_ci				for (j = 0; j < b; j++)
48562306a36Sopenharmony_ci					printk(" %02x", buffer[j]);
48662306a36Sopenharmony_ci				printk("\n");
48762306a36Sopenharmony_ci			}
48862306a36Sopenharmony_ci#endif				/* OUTSCOPE */
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci			dtlk_write_bytes("Double Talk found", 18);
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci			return 0;
49362306a36Sopenharmony_ci		}
49462306a36Sopenharmony_ci		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
49562306a36Sopenharmony_ci	}
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	printk(KERN_INFO "DoubleTalk PC - not found\n");
49862306a36Sopenharmony_ci	return -ENODEV;
49962306a36Sopenharmony_ci}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci/*
50262306a36Sopenharmony_ci   static void dtlk_handle_error(char op, char rc, unsigned int minor)
50362306a36Sopenharmony_ci   {
50462306a36Sopenharmony_ci   printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n",
50562306a36Sopenharmony_ci   minor, op, rc);
50662306a36Sopenharmony_ci   return;
50762306a36Sopenharmony_ci   }
50862306a36Sopenharmony_ci */
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci/* interrogate the DoubleTalk PC and return its settings */
51162306a36Sopenharmony_cistatic struct dtlk_settings *dtlk_interrogate(void)
51262306a36Sopenharmony_ci{
51362306a36Sopenharmony_ci	unsigned char *t;
51462306a36Sopenharmony_ci	static char buf[sizeof(struct dtlk_settings) + 1];
51562306a36Sopenharmony_ci	int total, i;
51662306a36Sopenharmony_ci	static struct dtlk_settings status;
51762306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_interrogate");
51862306a36Sopenharmony_ci	dtlk_write_bytes("\030\001?", 3);
51962306a36Sopenharmony_ci	for (total = 0, i = 0; i < 50; i++) {
52062306a36Sopenharmony_ci		buf[total] = dtlk_read_tts();
52162306a36Sopenharmony_ci		if (total > 2 && buf[total] == 0x7f)
52262306a36Sopenharmony_ci			break;
52362306a36Sopenharmony_ci		if (total < sizeof(struct dtlk_settings))
52462306a36Sopenharmony_ci			total++;
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci	/*
52762306a36Sopenharmony_ci	   if (i==50) printk("interrogate() read overrun\n");
52862306a36Sopenharmony_ci	   for (i=0; i<sizeof(buf); i++)
52962306a36Sopenharmony_ci	   printk(" %02x", buf[i]);
53062306a36Sopenharmony_ci	   printk("\n");
53162306a36Sopenharmony_ci	 */
53262306a36Sopenharmony_ci	t = buf;
53362306a36Sopenharmony_ci	status.serial_number = t[0] + t[1] * 256; /* serial number is
53462306a36Sopenharmony_ci						     little endian */
53562306a36Sopenharmony_ci	t += 2;
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	i = 0;
53862306a36Sopenharmony_ci	while (*t != '\r') {
53962306a36Sopenharmony_ci		status.rom_version[i] = *t;
54062306a36Sopenharmony_ci		if (i < sizeof(status.rom_version) - 1)
54162306a36Sopenharmony_ci			i++;
54262306a36Sopenharmony_ci		t++;
54362306a36Sopenharmony_ci	}
54462306a36Sopenharmony_ci	status.rom_version[i] = 0;
54562306a36Sopenharmony_ci	t++;
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	status.mode = *t++;
54862306a36Sopenharmony_ci	status.punc_level = *t++;
54962306a36Sopenharmony_ci	status.formant_freq = *t++;
55062306a36Sopenharmony_ci	status.pitch = *t++;
55162306a36Sopenharmony_ci	status.speed = *t++;
55262306a36Sopenharmony_ci	status.volume = *t++;
55362306a36Sopenharmony_ci	status.tone = *t++;
55462306a36Sopenharmony_ci	status.expression = *t++;
55562306a36Sopenharmony_ci	status.ext_dict_loaded = *t++;
55662306a36Sopenharmony_ci	status.ext_dict_status = *t++;
55762306a36Sopenharmony_ci	status.free_ram = *t++;
55862306a36Sopenharmony_ci	status.articulation = *t++;
55962306a36Sopenharmony_ci	status.reverb = *t++;
56062306a36Sopenharmony_ci	status.eob = *t++;
56162306a36Sopenharmony_ci	status.has_indexing = dtlk_has_indexing;
56262306a36Sopenharmony_ci	TRACE_RET;
56362306a36Sopenharmony_ci	return &status;
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_cistatic char dtlk_read_tts(void)
56762306a36Sopenharmony_ci{
56862306a36Sopenharmony_ci	int portval, retries = 0;
56962306a36Sopenharmony_ci	char ch;
57062306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_read_tts");
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	/* verify DT is ready, read char, wait for ACK */
57362306a36Sopenharmony_ci	do {
57462306a36Sopenharmony_ci		portval = inb_p(dtlk_port_tts);
57562306a36Sopenharmony_ci	} while ((portval & TTS_READABLE) == 0 &&
57662306a36Sopenharmony_ci		 retries++ < DTLK_MAX_RETRIES);
57762306a36Sopenharmony_ci	if (retries > DTLK_MAX_RETRIES)
57862306a36Sopenharmony_ci		printk(KERN_ERR "dtlk_read_tts() timeout\n");
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	ch = inb_p(dtlk_port_tts);	/* input from TTS port */
58162306a36Sopenharmony_ci	ch &= 0x7f;
58262306a36Sopenharmony_ci	outb_p(ch, dtlk_port_tts);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	retries = 0;
58562306a36Sopenharmony_ci	do {
58662306a36Sopenharmony_ci		portval = inb_p(dtlk_port_tts);
58762306a36Sopenharmony_ci	} while ((portval & TTS_READABLE) != 0 &&
58862306a36Sopenharmony_ci		 retries++ < DTLK_MAX_RETRIES);
58962306a36Sopenharmony_ci	if (retries > DTLK_MAX_RETRIES)
59062306a36Sopenharmony_ci		printk(KERN_ERR "dtlk_read_tts() timeout\n");
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	TRACE_RET;
59362306a36Sopenharmony_ci	return ch;
59462306a36Sopenharmony_ci}
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_cistatic char dtlk_read_lpc(void)
59762306a36Sopenharmony_ci{
59862306a36Sopenharmony_ci	int retries = 0;
59962306a36Sopenharmony_ci	char ch;
60062306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_read_lpc");
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	/* no need to test -- this is only called when the port is readable */
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	outb_p(0xff, dtlk_port_lpc);
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	/* acknowledging a read takes 3-4
60962306a36Sopenharmony_ci	   usec.  Here, we wait up to 20 usec
61062306a36Sopenharmony_ci	   for the acknowledgement */
61162306a36Sopenharmony_ci	retries = (loops_per_jiffy * 20) / (1000000/HZ);
61262306a36Sopenharmony_ci	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
61362306a36Sopenharmony_ci	if (retries == 0)
61462306a36Sopenharmony_ci		printk(KERN_ERR "dtlk_read_lpc() timeout\n");
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	TRACE_RET;
61762306a36Sopenharmony_ci	return ch;
61862306a36Sopenharmony_ci}
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci/* write n bytes to tts port */
62162306a36Sopenharmony_cistatic char dtlk_write_bytes(const char *buf, int n)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	char val = 0;
62462306a36Sopenharmony_ci	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
62562306a36Sopenharmony_ci	TRACE_TEXT("(dtlk_write_bytes");
62662306a36Sopenharmony_ci	while (n-- > 0)
62762306a36Sopenharmony_ci		val = dtlk_write_tts(*buf++);
62862306a36Sopenharmony_ci	TRACE_RET;
62962306a36Sopenharmony_ci	return val;
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic char dtlk_write_tts(char ch)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	int retries = 0;
63562306a36Sopenharmony_ci#ifdef TRACINGMORE
63662306a36Sopenharmony_ci	printk("  dtlk_write_tts(");
63762306a36Sopenharmony_ci	if (' ' <= ch && ch <= '~')
63862306a36Sopenharmony_ci		printk("'%c'", ch);
63962306a36Sopenharmony_ci	else
64062306a36Sopenharmony_ci		printk("0x%02x", ch);
64162306a36Sopenharmony_ci#endif
64262306a36Sopenharmony_ci	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */
64362306a36Sopenharmony_ci		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
64462306a36Sopenharmony_ci		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */
64562306a36Sopenharmony_ci			;
64662306a36Sopenharmony_ci	if (retries > DTLK_MAX_RETRIES)
64762306a36Sopenharmony_ci		printk(KERN_ERR "dtlk_write_tts() timeout\n");
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	outb_p(ch, dtlk_port_tts);	/* output to TTS port */
65062306a36Sopenharmony_ci	/* the RDY bit goes zero 2-3 usec after writing, and goes
65162306a36Sopenharmony_ci	   1 again 180-190 usec later.  Here, we wait up to 10
65262306a36Sopenharmony_ci	   usec for the RDY bit to go zero. */
65362306a36Sopenharmony_ci	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
65462306a36Sopenharmony_ci		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
65562306a36Sopenharmony_ci			break;
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci#ifdef TRACINGMORE
65862306a36Sopenharmony_ci	printk(")\n");
65962306a36Sopenharmony_ci#endif
66062306a36Sopenharmony_ci	return 0;
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
664