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