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