Root/drivers/char/dtlk.c

1/* -*- linux-c -*-
2 * dtlk.c - DoubleTalk PC driver for Linux
3 *
4 * Original author: Chris Pallotta <chris@allmedia.com>
5 * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
6 *
7 * 2000-03-18 Jim Van Zandt: Fix polling.
8 * Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
9 * function. Don't restart timer in dtlk_timer_tick. Restart timer
10 * in dtlk_poll after every poll. dtlk_poll returns mask (duh).
11 * Eliminate unused function dtlk_write_byte. Misc. code cleanups.
12 */
13
14/* This driver is for the DoubleTalk PC, a speech synthesizer
15   manufactured by RC Systems (http://www.rcsys.com/). It was written
16   based on documentation in their User's Manual file and Developer's
17   Tools disk.
18
19   The DoubleTalk PC contains four voice synthesizers: text-to-speech
20   (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD. It
21   also has a tone generator. Output data for LPC are written to the
22   LPC port, and output data for the other modes are written to the
23   TTS port.
24
25   Two kinds of data can be read from the DoubleTalk: status
26   information (in response to the "\001?" interrogation command) is
27   read from the TTS port, and index markers (which mark the progress
28   of the speech) are read from the LPC port. Not all models of the
29   DoubleTalk PC implement index markers. Both the TTS and LPC ports
30   can also display status flags.
31
32   The DoubleTalk PC generates no interrupts.
33
34   These characteristics are mapped into the Unix stream I/O model as
35   follows:
36
37   "write" sends bytes to the TTS port. It is the responsibility of
38   the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
39   This driver was written for use with the text-to-speech
40   synthesizer. If LPC output is needed some day, other minor device
41   numbers can be used to select among output modes.
42
43   "read" gets index markers from the LPC port. If the device does
44   not implement index markers, the read will fail with error EINVAL.
45
46   Status information is available using the DTLK_INTERROGATE ioctl.
47
48 */
49
50#include <linux/module.h>
51
52#define KERNEL
53#include <linux/types.h>
54#include <linux/fs.h>
55#include <linux/mm.h>
56#include <linux/errno.h> /* for -EBUSY */
57#include <linux/ioport.h> /* for request_region */
58#include <linux/delay.h> /* for loops_per_jiffy */
59#include <linux/sched.h>
60#include <linux/mutex.h>
61#include <asm/io.h> /* for inb_p, outb_p, inb, outb, etc. */
62#include <asm/uaccess.h> /* for get_user, etc. */
63#include <linux/wait.h> /* for wait_queue */
64#include <linux/init.h> /* for __init, module_{init,exit} */
65#include <linux/poll.h> /* for POLLIN, etc. */
66#include <linux/dtlk.h> /* local header file for DoubleTalk values */
67
68#ifdef TRACING
69#define TRACE_TEXT(str) printk(str);
70#define TRACE_RET printk(")")
71#else /* !TRACING */
72#define TRACE_TEXT(str) ((void) 0)
73#define TRACE_RET ((void) 0)
74#endif /* TRACING */
75
76static DEFINE_MUTEX(dtlk_mutex);
77static void dtlk_timer_tick(unsigned long data);
78
79static int dtlk_major;
80static int dtlk_port_lpc;
81static int dtlk_port_tts;
82static int dtlk_busy;
83static int dtlk_has_indexing;
84static unsigned int dtlk_portlist[] =
85{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
86static wait_queue_head_t dtlk_process_list;
87static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick, 0, 0);
88
89/* prototypes for file_operations struct */
90static ssize_t dtlk_read(struct file *, char __user *,
91             size_t nbytes, loff_t * ppos);
92static ssize_t dtlk_write(struct file *, const char __user *,
93              size_t nbytes, loff_t * ppos);
94static unsigned int dtlk_poll(struct file *, poll_table *);
95static int dtlk_open(struct inode *, struct file *);
96static int dtlk_release(struct inode *, struct file *);
97static long dtlk_ioctl(struct file *file,
98               unsigned int cmd, unsigned long arg);
99
100static const struct file_operations dtlk_fops =
101{
102    .owner = THIS_MODULE,
103    .read = dtlk_read,
104    .write = dtlk_write,
105    .poll = dtlk_poll,
106    .unlocked_ioctl = dtlk_ioctl,
107    .open = dtlk_open,
108    .release = dtlk_release,
109    .llseek = no_llseek,
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 TRACING
165    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            else
174                printk("\\%03o", ch);
175        }
176        printk("\"");
177    }
178#endif
179
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 scheduled
191                   again. This reduces the transfer
192                   rate to 500 bytes/sec, but that's
193                   still enough to keep up with the
194                   speech synthesizer. */
195                msleep_interruptible(1);
196            else {
197                /* the RDY bit goes zero 2-3 usec
198                   after writing, and goes 1 again
199                   180-190 usec later. Here, we wait
200                   up to 250 usec for the RDY bit to
201                   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 sec
219                          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 unsigned int dtlk_poll(struct file *file, poll_table * wait)
232{
233    int 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 = POLLIN | POLLRDNORM;
248    }
249    if (dtlk_writeable()) {
250            del_timer(&dtlk_timer);
251        mask |= POLLOUT | POLLWRNORM;
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(unsigned long data)
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    nonseekable_open(inode, file);
302    switch (iminor(inode)) {
303    case DTLK_MINOR:
304        if (dtlk_busy)
305            return -EBUSY;
306        return nonseekable_open(inode, file);
307
308    default:
309        return -ENXIO;
310    }
311}
312
313static int dtlk_release(struct inode *inode, struct file *file)
314{
315    TRACE_TEXT("(dtlk_release");
316
317    switch (iminor(inode)) {
318    case DTLK_MINOR:
319        break;
320
321    default:
322        break;
323    }
324    TRACE_RET;
325    
326    del_timer_sync(&dtlk_timer);
327
328    return 0;
329}
330
331static int __init dtlk_init(void)
332{
333    int err;
334
335    dtlk_port_lpc = 0;
336    dtlk_port_tts = 0;
337    dtlk_busy = 0;
338    dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
339    if (dtlk_major < 0) {
340        printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
341        return dtlk_major;
342    }
343    err = dtlk_dev_probe();
344    if (err) {
345        unregister_chrdev(dtlk_major, "dtlk");
346        return err;
347    }
348    printk(", MAJOR %d\n", dtlk_major);
349
350    init_waitqueue_head(&dtlk_process_list);
351
352    return 0;
353}
354
355static void __exit dtlk_cleanup (void)
356{
357    dtlk_write_bytes("goodbye", 8);
358    msleep_interruptible(500); /* nap 0.50 sec but
359                           could be awakened
360                           earlier by
361                           signals... */
362
363    dtlk_write_tts(DTLK_CLEAR);
364    unregister_chrdev(dtlk_major, "dtlk");
365    release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
366}
367
368module_init(dtlk_init);
369module_exit(dtlk_cleanup);
370
371/* ------------------------------------------------------------------------ */
372
373static int dtlk_readable(void)
374{
375#ifdef TRACING
376    printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
377#endif
378    return inb_p(dtlk_port_lpc) != 0x7f;
379}
380
381static int dtlk_writeable(void)
382{
383    /* TRACE_TEXT(" dtlk_writeable"); */
384#ifdef TRACINGMORE
385    printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
386#endif
387    return inb_p(dtlk_port_tts) & TTS_WRITABLE;
388}
389
390static int __init dtlk_dev_probe(void)
391{
392    unsigned int testval = 0;
393    int i = 0;
394    struct dtlk_settings *sp;
395
396    if (dtlk_port_lpc | dtlk_port_tts)
397        return -EBUSY;
398
399    for (i = 0; dtlk_portlist[i]; i++) {
400#if 0
401        printk("DoubleTalk PC - Port %03x = %04x\n",
402               dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
403#endif
404
405        if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT,
406                   "dtlk"))
407            continue;
408        testval = inw_p(dtlk_portlist[i]);
409        if ((testval &= 0xfbff) == 0x107f) {
410            dtlk_port_lpc = dtlk_portlist[i];
411            dtlk_port_tts = dtlk_port_lpc + 1;
412
413            sp = dtlk_interrogate();
414            printk("DoubleTalk PC at %03x-%03x, "
415                   "ROM version %s, serial number %u",
416                   dtlk_portlist[i], dtlk_portlist[i] +
417                   DTLK_IO_EXTENT - 1,
418                   sp->rom_version, sp->serial_number);
419
420                        /* put LPC port into known state, so
421               dtlk_readable() gives valid result */
422            outb_p(0xff, dtlk_port_lpc);
423
424                        /* INIT string and index marker */
425            dtlk_write_bytes("\036\1@\0\0012I\r", 8);
426            /* posting an index takes 18 msec. Here, we
427               wait up to 100 msec to see whether it
428               appears. */
429            msleep_interruptible(100);
430            dtlk_has_indexing = dtlk_readable();
431#ifdef TRACING
432            printk(", indexing %d\n", dtlk_has_indexing);
433#endif
434#ifdef INSCOPE
435            {
436/* This macro records ten samples read from the LPC port, for later display */
437#define LOOK \
438for (i = 0; i < 10; i++) \
439  { \
440    buffer[b++] = inb_p(dtlk_port_lpc); \
441    __delay(loops_per_jiffy/(1000000/HZ)); \
442  }
443                char buffer[1000];
444                int b = 0, i, j;
445
446                LOOK
447                outb_p(0xff, dtlk_port_lpc);
448                buffer[b++] = 0;
449                LOOK
450                dtlk_write_bytes("\0012I\r", 4);
451                buffer[b++] = 0;
452                __delay(50 * loops_per_jiffy / (1000/HZ));
453                outb_p(0xff, dtlk_port_lpc);
454                buffer[b++] = 0;
455                LOOK
456
457                printk("\n");
458                for (j = 0; j < b; j++)
459                    printk(" %02x", buffer[j]);
460                printk("\n");
461            }
462#endif /* INSCOPE */
463
464#ifdef OUTSCOPE
465            {
466/* This macro records ten samples read from the TTS port, for later display */
467#define LOOK \
468for (i = 0; i < 10; i++) \
469  { \
470    buffer[b++] = inb_p(dtlk_port_tts); \
471    __delay(loops_per_jiffy/(1000000/HZ)); /* 1 us */ \
472  }
473                char buffer[1000];
474                int b = 0, i, j;
475
476                mdelay(10); /* 10 ms */
477                LOOK
478                outb_p(0x03, dtlk_port_tts);
479                buffer[b++] = 0;
480                LOOK
481                LOOK
482
483                printk("\n");
484                for (j = 0; j < b; j++)
485                    printk(" %02x", buffer[j]);
486                printk("\n");
487            }
488#endif /* OUTSCOPE */
489
490            dtlk_write_bytes("Double Talk found", 18);
491
492            return 0;
493        }
494        release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
495    }
496
497    printk(KERN_INFO "DoubleTalk PC - not found\n");
498    return -ENODEV;
499}
500
501/*
502   static void dtlk_handle_error(char op, char rc, unsigned int minor)
503   {
504   printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n",
505   minor, op, rc);
506   return;
507   }
508 */
509
510/* interrogate the DoubleTalk PC and return its settings */
511static struct dtlk_settings *dtlk_interrogate(void)
512{
513    unsigned char *t;
514    static char buf[sizeof(struct dtlk_settings) + 1];
515    int total, i;
516    static struct dtlk_settings status;
517    TRACE_TEXT("(dtlk_interrogate");
518    dtlk_write_bytes("\030\001?", 3);
519    for (total = 0, i = 0; i < 50; i++) {
520        buf[total] = dtlk_read_tts();
521        if (total > 2 && buf[total] == 0x7f)
522            break;
523        if (total < sizeof(struct dtlk_settings))
524            total++;
525    }
526    /*
527       if (i==50) printk("interrogate() read overrun\n");
528       for (i=0; i<sizeof(buf); i++)
529       printk(" %02x", buf[i]);
530       printk("\n");
531     */
532    t = buf;
533    status.serial_number = t[0] + t[1] * 256; /* serial number is
534                             little endian */
535    t += 2;
536
537    i = 0;
538    while (*t != '\r') {
539        status.rom_version[i] = *t;
540        if (i < sizeof(status.rom_version) - 1)
541            i++;
542        t++;
543    }
544    status.rom_version[i] = 0;
545    t++;
546
547    status.mode = *t++;
548    status.punc_level = *t++;
549    status.formant_freq = *t++;
550    status.pitch = *t++;
551    status.speed = *t++;
552    status.volume = *t++;
553    status.tone = *t++;
554    status.expression = *t++;
555    status.ext_dict_loaded = *t++;
556    status.ext_dict_status = *t++;
557    status.free_ram = *t++;
558    status.articulation = *t++;
559    status.reverb = *t++;
560    status.eob = *t++;
561    status.has_indexing = dtlk_has_indexing;
562    TRACE_RET;
563    return &status;
564}
565
566static char dtlk_read_tts(void)
567{
568    int portval, retries = 0;
569    char ch;
570    TRACE_TEXT("(dtlk_read_tts");
571
572    /* verify DT is ready, read char, wait for ACK */
573    do {
574        portval = inb_p(dtlk_port_tts);
575    } while ((portval & TTS_READABLE) == 0 &&
576         retries++ < DTLK_MAX_RETRIES);
577    if (retries > DTLK_MAX_RETRIES)
578        printk(KERN_ERR "dtlk_read_tts() timeout\n");
579
580    ch = inb_p(dtlk_port_tts); /* input from TTS port */
581    ch &= 0x7f;
582    outb_p(ch, dtlk_port_tts);
583
584    retries = 0;
585    do {
586        portval = inb_p(dtlk_port_tts);
587    } while ((portval & TTS_READABLE) != 0 &&
588         retries++ < DTLK_MAX_RETRIES);
589    if (retries > DTLK_MAX_RETRIES)
590        printk(KERN_ERR "dtlk_read_tts() timeout\n");
591
592    TRACE_RET;
593    return ch;
594}
595
596static char dtlk_read_lpc(void)
597{
598    int retries = 0;
599    char ch;
600    TRACE_TEXT("(dtlk_read_lpc");
601
602    /* no need to test -- this is only called when the port is readable */
603
604    ch = inb_p(dtlk_port_lpc); /* input from LPC port */
605
606    outb_p(0xff, dtlk_port_lpc);
607
608    /* acknowledging a read takes 3-4
609       usec. Here, we wait up to 20 usec
610       for the acknowledgement */
611    retries = (loops_per_jiffy * 20) / (1000000/HZ);
612    while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
613    if (retries == 0)
614        printk(KERN_ERR "dtlk_read_lpc() timeout\n");
615
616    TRACE_RET;
617    return ch;
618}
619
620/* write n bytes to tts port */
621static char dtlk_write_bytes(const char *buf, int n)
622{
623    char val = 0;
624    /* printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
625    TRACE_TEXT("(dtlk_write_bytes");
626    while (n-- > 0)
627        val = dtlk_write_tts(*buf++);
628    TRACE_RET;
629    return val;
630}
631
632static char dtlk_write_tts(char ch)
633{
634    int retries = 0;
635#ifdef TRACINGMORE
636    printk(" dtlk_write_tts(");
637    if (' ' <= ch && ch <= '~')
638        printk("'%c'", ch);
639    else
640        printk("0x%02x", ch);
641#endif
642    if (ch != DTLK_CLEAR) /* no flow control for CLEAR command */
643        while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
644               retries++ < DTLK_MAX_RETRIES) /* DT ready? */
645            ;
646    if (retries > DTLK_MAX_RETRIES)
647        printk(KERN_ERR "dtlk_write_tts() timeout\n");
648
649    outb_p(ch, dtlk_port_tts); /* output to TTS port */
650    /* the RDY bit goes zero 2-3 usec after writing, and goes
651       1 again 180-190 usec later. Here, we wait up to 10
652       usec for the RDY bit to go zero. */
653    for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
654        if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
655            break;
656
657#ifdef TRACINGMORE
658    printk(")\n");
659#endif
660    return 0;
661}
662
663MODULE_LICENSE("GPL");
664

Archive Download this file



interactive