linux/drivers/char/dtlk.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*                                              -*- linux-c -*-
   3 * dtlk.c - DoubleTalk PC driver for Linux
   4 *
   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_timer
  10 *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
  11 *  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 synthesizer
  16   manufactured by RC Systems (http://www.rcsys.com/).  It was written
  17   based on documentation in their User's Manual file and Developer's
  18   Tools disk.
  19
  20   The DoubleTalk PC contains four voice synthesizers: text-to-speech
  21   (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
  22   also has a tone generator.  Output data for LPC are written to the
  23   LPC port, and output data for the other modes are written to the
  24   TTS port.
  25
  26   Two kinds of data can be read from the DoubleTalk: status
  27   information (in response to the "\001?" interrogation command) is
  28   read from the TTS port, and index markers (which mark the progress
  29   of the speech) are read from the LPC port.  Not all models of the
  30   DoubleTalk PC implement index markers.  Both the TTS and LPC ports
  31   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 as
  36   follows:
  37
  38   "write" sends bytes to the TTS port.  It is the responsibility of
  39   the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
  40   This driver was written for use with the text-to-speech
  41   synthesizer.  If LPC output is needed some day, other minor device
  42   numbers can be used to select among output modes.
  43
  44   "read" gets index markers from the LPC port.  If the device does
  45   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 KERNEL
  54#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 TRACING
  70#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        .llseek         = no_llseek,
 111};
 112
 113/* local prototypes */
 114static int dtlk_dev_probe(void);
 115static struct dtlk_settings *dtlk_interrogate(void);
 116static int dtlk_readable(void);
 117static char dtlk_read_lpc(void);
 118static char dtlk_read_tts(void);
 119static int dtlk_writeable(void);
 120static char dtlk_write_bytes(const char *buf, int n);
 121static char dtlk_write_tts(char);
 122/*
 123   static void dtlk_handle_error(char, char, unsigned int);
 124 */
 125
 126static ssize_t dtlk_read(struct file *file, char __user *buf,
 127                         size_t count, loff_t * ppos)
 128{
 129        unsigned int minor = iminor(file_inode(file));
 130        char ch;
 131        int i = 0, retries;
 132
 133        TRACE_TEXT("(dtlk_read");
 134        /*  printk("DoubleTalk PC - dtlk_read()\n"); */
 135
 136        if (minor != DTLK_MINOR || !dtlk_has_indexing)
 137                return -EINVAL;
 138
 139        for (retries = 0; retries < loops_per_jiffy; retries++) {
 140                while (i < count && dtlk_readable()) {
 141                        ch = dtlk_read_lpc();
 142                        /*        printk("dtlk_read() reads 0x%02x\n", ch); */
 143                        if (put_user(ch, buf++))
 144                                return -EFAULT;
 145                        i++;
 146                }
 147                if (i)
 148                        return i;
 149                if (file->f_flags & O_NONBLOCK)
 150                        break;
 151                msleep_interruptible(100);
 152        }
 153        if (retries == loops_per_jiffy)
 154                printk(KERN_ERR "dtlk_read times out\n");
 155        TRACE_RET;
 156        return -EAGAIN;
 157}
 158
 159static ssize_t dtlk_write(struct file *file, const char __user *buf,
 160                          size_t count, loff_t * ppos)
 161{
 162        int i = 0, retries = 0, ch;
 163
 164        TRACE_TEXT("(dtlk_write");
 165#ifdef TRACING
 166        printk(" \"");
 167        {
 168                int i, ch;
 169                for (i = 0; i < count; i++) {
 170                        if (get_user(ch, buf + i))
 171                                return -EFAULT;
 172                        if (' ' <= ch && ch <= '~')
 173                                printk("%c", ch);
 174                        else
 175                                printk("\\%03o", ch);
 176                }
 177                printk("\"");
 178        }
 179#endif
 180
 181        if (iminor(file_inode(file)) != DTLK_MINOR)
 182                return -EINVAL;
 183
 184        while (1) {
 185                while (i < count && !get_user(ch, buf) &&
 186                       (ch == DTLK_CLEAR || dtlk_writeable())) {
 187                        dtlk_write_tts(ch);
 188                        buf++;
 189                        i++;
 190                        if (i % 5 == 0)
 191                                /* We yield our time until scheduled
 192                                   again.  This reduces the transfer
 193                                   rate to 500 bytes/sec, but that's
 194                                   still enough to keep up with the
 195                                   speech synthesizer. */
 196                                msleep_interruptible(1);
 197                        else {
 198                                /* the RDY bit goes zero 2-3 usec
 199                                   after writing, and goes 1 again
 200                                   180-190 usec later.  Here, we wait
 201                                   up to 250 usec for the RDY bit to
 202                                   go nonzero. */
 203                                for (retries = 0;
 204                                     retries < loops_per_jiffy / (4000/HZ);
 205                                     retries++)
 206                                        if (inb_p(dtlk_port_tts) &
 207                                            TTS_WRITABLE)
 208                                                break;
 209                        }
 210                        retries = 0;
 211                }
 212                if (i == count)
 213                        return i;
 214                if (file->f_flags & O_NONBLOCK)
 215                        break;
 216
 217                msleep_interruptible(1);
 218
 219                if (++retries > 10 * HZ) { /* wait no more than 10 sec
 220                                              from last write */
 221                        printk("dtlk: write timeout.  "
 222                               "inb_p(dtlk_port_tts) = 0x%02x\n",
 223                               inb_p(dtlk_port_tts));
 224                        TRACE_RET;
 225                        return -EBUSY;
 226                }
 227        }
 228        TRACE_RET;
 229        return -EAGAIN;
 230}
 231
 232static __poll_t dtlk_poll(struct file *file, poll_table * wait)
 233{
 234        __poll_t mask = 0;
 235        unsigned long expires;
 236
 237        TRACE_TEXT(" dtlk_poll");
 238        /*
 239           static long int j;
 240           printk(".");
 241           printk("<%ld>", jiffies-j);
 242           j=jiffies;
 243         */
 244        poll_wait(file, &dtlk_process_list, wait);
 245
 246        if (dtlk_has_indexing && dtlk_readable()) {
 247                del_timer(&dtlk_timer);
 248                mask = EPOLLIN | EPOLLRDNORM;
 249        }
 250        if (dtlk_writeable()) {
 251                del_timer(&dtlk_timer);
 252                mask |= EPOLLOUT | EPOLLWRNORM;
 253        }
 254        /* there are no exception conditions */
 255
 256        /* There won't be any interrupts, so we set a timer instead. */
 257        expires = jiffies + 3*HZ / 100;
 258        mod_timer(&dtlk_timer, expires);
 259
 260        return mask;
 261}
 262
 263static void dtlk_timer_tick(struct timer_list *unused)
 264{
 265        TRACE_TEXT(" dtlk_timer_tick");
 266        wake_up_interruptible(&dtlk_process_list);
 267}
 268
 269static long dtlk_ioctl(struct file *file,
 270                       unsigned int cmd,
 271                       unsigned long arg)
 272{
 273        char __user *argp = (char __user *)arg;
 274        struct dtlk_settings *sp;
 275        char portval;
 276        TRACE_TEXT(" dtlk_ioctl");
 277
 278        switch (cmd) {
 279
 280        case DTLK_INTERROGATE:
 281                mutex_lock(&dtlk_mutex);
 282                sp = dtlk_interrogate();
 283                mutex_unlock(&dtlk_mutex);
 284                if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
 285                        return -EINVAL;
 286                return 0;
 287
 288        case DTLK_STATUS:
 289                portval = inb_p(dtlk_port_tts);
 290                return put_user(portval, argp);
 291
 292        default:
 293                return -EINVAL;
 294        }
 295}
 296
 297/* Note that nobody ever sets dtlk_busy... */
 298static int dtlk_open(struct inode *inode, struct file *file)
 299{
 300        TRACE_TEXT("(dtlk_open");
 301
 302        switch (iminor(inode)) {
 303        case DTLK_MINOR:
 304                if (dtlk_busy)
 305                        return -EBUSY;
 306                return stream_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