brintos

brintos / linux-shallow public Read only

0
0
Text · 16.3 KiB · 27f5f9d Raw
664 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*                                              -*- linux-c -*-3 * dtlk.c - DoubleTalk PC driver for Linux4 *5 * Original author: Chris Pallotta <chris@allmedia.com>6 * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>7 * 8 * 2000-03-18 Jim Van Zandt: Fix polling.9 *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer10 *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer11 *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).12 *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.13 */14 15/* This driver is for the DoubleTalk PC, a speech synthesizer16   manufactured by RC Systems (http://www.rcsys.com/).  It was written17   based on documentation in their User's Manual file and Developer's18   Tools disk.19 20   The DoubleTalk PC contains four voice synthesizers: text-to-speech21   (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It22   also has a tone generator.  Output data for LPC are written to the23   LPC port, and output data for the other modes are written to the24   TTS port.25 26   Two kinds of data can be read from the DoubleTalk: status27   information (in response to the "\001?" interrogation command) is28   read from the TTS port, and index markers (which mark the progress29   of the speech) are read from the LPC port.  Not all models of the30   DoubleTalk PC implement index markers.  Both the TTS and LPC ports31   can also display status flags.32 33   The DoubleTalk PC generates no interrupts.34 35   These characteristics are mapped into the Unix stream I/O model as36   follows:37 38   "write" sends bytes to the TTS port.  It is the responsibility of39   the user program to switch modes among TTS, PCM/ADPCM, and CVSD.40   This driver was written for use with the text-to-speech41   synthesizer.  If LPC output is needed some day, other minor device42   numbers can be used to select among output modes.43 44   "read" gets index markers from the LPC port.  If the device does45   not implement index markers, the read will fail with error EINVAL.46 47   Status information is available using the DTLK_INTERROGATE ioctl.48 49 */50 51#include <linux/module.h>52 53#define KERNEL54#include <linux/types.h>55#include <linux/fs.h>56#include <linux/mm.h>57#include <linux/errno.h>	/* for -EBUSY */58#include <linux/ioport.h>	/* for request_region */59#include <linux/delay.h>	/* for loops_per_jiffy */60#include <linux/sched.h>61#include <linux/mutex.h>62#include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */63#include <linux/uaccess.h>	/* for get_user, etc. */64#include <linux/wait.h>		/* for wait_queue */65#include <linux/init.h>		/* for __init, module_{init,exit} */66#include <linux/poll.h>		/* for EPOLLIN, etc. */67#include <linux/dtlk.h>		/* local header file for DoubleTalk values */68 69#ifdef TRACING70#define TRACE_TEXT(str) printk(str);71#define TRACE_RET printk(")")72#else				/* !TRACING */73#define TRACE_TEXT(str) ((void) 0)74#define TRACE_RET ((void) 0)75#endif				/* TRACING */76 77static DEFINE_MUTEX(dtlk_mutex);78static void dtlk_timer_tick(struct timer_list *unused);79 80static int dtlk_major;81static int dtlk_port_lpc;82static int dtlk_port_tts;83static int dtlk_busy;84static int dtlk_has_indexing;85static unsigned int dtlk_portlist[] =86{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};87static wait_queue_head_t dtlk_process_list;88static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick);89 90/* prototypes for file_operations struct */91static ssize_t dtlk_read(struct file *, char __user *,92			 size_t nbytes, loff_t * ppos);93static ssize_t dtlk_write(struct file *, const char __user *,94			  size_t nbytes, loff_t * ppos);95static __poll_t dtlk_poll(struct file *, poll_table *);96static int dtlk_open(struct inode *, struct file *);97static int dtlk_release(struct inode *, struct file *);98static long dtlk_ioctl(struct file *file,99		       unsigned int cmd, unsigned long arg);100 101static const struct file_operations dtlk_fops =102{103	.owner		= THIS_MODULE,104	.read		= dtlk_read,105	.write		= dtlk_write,106	.poll		= dtlk_poll,107	.unlocked_ioctl	= dtlk_ioctl,108	.open		= dtlk_open,109	.release	= dtlk_release,110};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_inode(file));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 TRACING165	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			else174				printk("\\%03o", ch);175		}176		printk("\"");177	}178#endif179 180	if (iminor(file_inode(file)) != 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 scheduled191				   again.  This reduces the transfer192				   rate to 500 bytes/sec, but that's193				   still enough to keep up with the194				   speech synthesizer. */195				msleep_interruptible(1);196			else {197				/* the RDY bit goes zero 2-3 usec198				   after writing, and goes 1 again199				   180-190 usec later.  Here, we wait200				   up to 250 usec for the RDY bit to201				   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 sec219					      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 __poll_t dtlk_poll(struct file *file, poll_table * wait)232{233	__poll_t 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 = EPOLLIN | EPOLLRDNORM;248	}249	if (dtlk_writeable()) {250	        del_timer(&dtlk_timer);251		mask |= EPOLLOUT | EPOLLWRNORM;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(struct timer_list *unused)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	switch (iminor(inode)) {302	case DTLK_MINOR:303		if (dtlk_busy)304			return -EBUSY;305		return stream_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 but358						   could be awakened359						   earlier by360						   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 TRACING375	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);376#endif377	return inb_p(dtlk_port_lpc) != 0x7f;378}379 380static int dtlk_writeable(void)381{382	/* TRACE_TEXT(" dtlk_writeable"); */383#ifdef TRACINGMORE384	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);385#endif386	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#if 0400		printk("DoubleTalk PC - Port %03x = %04x\n",401		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));402#endif403 404		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT, 405			       "dtlk"))406			continue;407		testval = inw_p(dtlk_portlist[i]);408		if ((testval &= 0xfbff) == 0x107f) {409			dtlk_port_lpc = dtlk_portlist[i];410			dtlk_port_tts = dtlk_port_lpc + 1;411 412			sp = dtlk_interrogate();413			printk("DoubleTalk PC at %03x-%03x, "414			       "ROM version %s, serial number %u",415			       dtlk_portlist[i], dtlk_portlist[i] +416			       DTLK_IO_EXTENT - 1,417			       sp->rom_version, sp->serial_number);418 419                        /* put LPC port into known state, so420			   dtlk_readable() gives valid result */421			outb_p(0xff, dtlk_port_lpc); 422 423                        /* INIT string and index marker */424			dtlk_write_bytes("\036\1@\0\0012I\r", 8);425			/* posting an index takes 18 msec.  Here, we426			   wait up to 100 msec to see whether it427			   appears. */428			msleep_interruptible(100);429			dtlk_has_indexing = dtlk_readable();430#ifdef TRACING431			printk(", indexing %d\n", dtlk_has_indexing);432#endif433#ifdef INSCOPE434			{435/* This macro records ten samples read from the LPC port, for later display */436#define LOOK					\437for (i = 0; i < 10; i++)			\438  {						\439    buffer[b++] = inb_p(dtlk_port_lpc);		\440    __delay(loops_per_jiffy/(1000000/HZ));             \441  }442				char buffer[1000];443				int b = 0, i, j;444 445				LOOK446				outb_p(0xff, dtlk_port_lpc);447				buffer[b++] = 0;448				LOOK449				dtlk_write_bytes("\0012I\r", 4);450				buffer[b++] = 0;451				__delay(50 * loops_per_jiffy / (1000/HZ));452				outb_p(0xff, dtlk_port_lpc);453				buffer[b++] = 0;454				LOOK455 456				printk("\n");457				for (j = 0; j < b; j++)458					printk(" %02x", buffer[j]);459				printk("\n");460			}461#endif				/* INSCOPE */462 463#ifdef OUTSCOPE464			{465/* This macro records ten samples read from the TTS port, for later display */466#define LOOK					\467for (i = 0; i < 10; i++)			\468  {						\469    buffer[b++] = inb_p(dtlk_port_tts);		\470    __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \471  }472				char buffer[1000];473				int b = 0, i, j;474 475				mdelay(10);	/* 10 ms */476				LOOK477				outb_p(0x03, dtlk_port_tts);478				buffer[b++] = 0;479				LOOK480				LOOK481 482				printk("\n");483				for (j = 0; j < b; j++)484					printk(" %02x", buffer[j]);485				printk("\n");486			}487#endif				/* OUTSCOPE */488 489			dtlk_write_bytes("Double Talk found", 18);490 491			return 0;492		}493		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);494	}495 496	printk(KERN_INFO "DoubleTalk PC - not found\n");497	return -ENODEV;498}499 500/*501   static void dtlk_handle_error(char op, char rc, unsigned int minor)502   {503   printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n", 504   minor, op, rc);505   return;506   }507 */508 509/* interrogate the DoubleTalk PC and return its settings */510static struct dtlk_settings *dtlk_interrogate(void)511{512	unsigned char *t;513	static char buf[sizeof(struct dtlk_settings) + 1];514	int total, i;515	static struct dtlk_settings status;516	TRACE_TEXT("(dtlk_interrogate");517	dtlk_write_bytes("\030\001?", 3);518	for (total = 0, i = 0; i < 50; i++) {519		buf[total] = dtlk_read_tts();520		if (total > 2 && buf[total] == 0x7f)521			break;522		if (total < sizeof(struct dtlk_settings))523			total++;524	}525	/*526	   if (i==50) printk("interrogate() read overrun\n");527	   for (i=0; i<sizeof(buf); i++)528	   printk(" %02x", buf[i]);529	   printk("\n");530	 */531	t = buf;532	status.serial_number = t[0] + t[1] * 256; /* serial number is533						     little endian */534	t += 2;535 536	i = 0;537	while (*t != '\r') {538		status.rom_version[i] = *t;539		if (i < sizeof(status.rom_version) - 1)540			i++;541		t++;542	}543	status.rom_version[i] = 0;544	t++;545 546	status.mode = *t++;547	status.punc_level = *t++;548	status.formant_freq = *t++;549	status.pitch = *t++;550	status.speed = *t++;551	status.volume = *t++;552	status.tone = *t++;553	status.expression = *t++;554	status.ext_dict_loaded = *t++;555	status.ext_dict_status = *t++;556	status.free_ram = *t++;557	status.articulation = *t++;558	status.reverb = *t++;559	status.eob = *t++;560	status.has_indexing = dtlk_has_indexing;561	TRACE_RET;562	return &status;563}564 565static char dtlk_read_tts(void)566{567	int portval, retries = 0;568	char ch;569	TRACE_TEXT("(dtlk_read_tts");570 571	/* verify DT is ready, read char, wait for ACK */572	do {573		portval = inb_p(dtlk_port_tts);574	} while ((portval & TTS_READABLE) == 0 &&575		 retries++ < DTLK_MAX_RETRIES);576	if (retries > DTLK_MAX_RETRIES)577		printk(KERN_ERR "dtlk_read_tts() timeout\n");578 579	ch = inb_p(dtlk_port_tts);	/* input from TTS port */580	ch &= 0x7f;581	outb_p(ch, dtlk_port_tts);582 583	retries = 0;584	do {585		portval = inb_p(dtlk_port_tts);586	} while ((portval & TTS_READABLE) != 0 &&587		 retries++ < DTLK_MAX_RETRIES);588	if (retries > DTLK_MAX_RETRIES)589		printk(KERN_ERR "dtlk_read_tts() timeout\n");590 591	TRACE_RET;592	return ch;593}594 595static char dtlk_read_lpc(void)596{597	int retries = 0;598	char ch;599	TRACE_TEXT("(dtlk_read_lpc");600 601	/* no need to test -- this is only called when the port is readable */602 603	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */604 605	outb_p(0xff, dtlk_port_lpc);606 607	/* acknowledging a read takes 3-4608	   usec.  Here, we wait up to 20 usec609	   for the acknowledgement */610	retries = (loops_per_jiffy * 20) / (1000000/HZ);611	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);612	if (retries == 0)613		printk(KERN_ERR "dtlk_read_lpc() timeout\n");614 615	TRACE_RET;616	return ch;617}618 619/* write n bytes to tts port */620static char dtlk_write_bytes(const char *buf, int n)621{622	char val = 0;623	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */624	TRACE_TEXT("(dtlk_write_bytes");625	while (n-- > 0)626		val = dtlk_write_tts(*buf++);627	TRACE_RET;628	return val;629}630 631static char dtlk_write_tts(char ch)632{633	int retries = 0;634#ifdef TRACINGMORE635	printk("  dtlk_write_tts(");636	if (' ' <= ch && ch <= '~')637		printk("'%c'", ch);638	else639		printk("0x%02x", ch);640#endif641	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */642		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&643		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */644			;645	if (retries > DTLK_MAX_RETRIES)646		printk(KERN_ERR "dtlk_write_tts() timeout\n");647 648	outb_p(ch, dtlk_port_tts);	/* output to TTS port */649	/* the RDY bit goes zero 2-3 usec after writing, and goes650	   1 again 180-190 usec later.  Here, we wait up to 10651	   usec for the RDY bit to go zero. */652	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)653		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)654			break;655 656#ifdef TRACINGMORE657	printk(")\n");658#endif659	return 0;660}661 662MODULE_DESCRIPTION("RC Systems DoubleTalk PC speech card driver");663MODULE_LICENSE("GPL");664