Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*                                              -*- linux-c -*-
0003  * dtlk.c - DoubleTalk PC driver for Linux
0004  *
0005  * Original author: Chris Pallotta <chris@allmedia.com>
0006  * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
0007  * 
0008  * 2000-03-18 Jim Van Zandt: Fix polling.
0009  *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
0010  *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
0011  *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
0012  *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
0013  */
0014 
0015 /* This driver is for the DoubleTalk PC, a speech synthesizer
0016    manufactured by RC Systems (http://www.rcsys.com/).  It was written
0017    based on documentation in their User's Manual file and Developer's
0018    Tools disk.
0019 
0020    The DoubleTalk PC contains four voice synthesizers: text-to-speech
0021    (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
0022    also has a tone generator.  Output data for LPC are written to the
0023    LPC port, and output data for the other modes are written to the
0024    TTS port.
0025 
0026    Two kinds of data can be read from the DoubleTalk: status
0027    information (in response to the "\001?" interrogation command) is
0028    read from the TTS port, and index markers (which mark the progress
0029    of the speech) are read from the LPC port.  Not all models of the
0030    DoubleTalk PC implement index markers.  Both the TTS and LPC ports
0031    can also display status flags.
0032 
0033    The DoubleTalk PC generates no interrupts.
0034 
0035    These characteristics are mapped into the Unix stream I/O model as
0036    follows:
0037 
0038    "write" sends bytes to the TTS port.  It is the responsibility of
0039    the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
0040    This driver was written for use with the text-to-speech
0041    synthesizer.  If LPC output is needed some day, other minor device
0042    numbers can be used to select among output modes.
0043 
0044    "read" gets index markers from the LPC port.  If the device does
0045    not implement index markers, the read will fail with error EINVAL.
0046 
0047    Status information is available using the DTLK_INTERROGATE ioctl.
0048 
0049  */
0050 
0051 #include <linux/module.h>
0052 
0053 #define KERNEL
0054 #include <linux/types.h>
0055 #include <linux/fs.h>
0056 #include <linux/mm.h>
0057 #include <linux/errno.h>    /* for -EBUSY */
0058 #include <linux/ioport.h>   /* for request_region */
0059 #include <linux/delay.h>    /* for loops_per_jiffy */
0060 #include <linux/sched.h>
0061 #include <linux/mutex.h>
0062 #include <asm/io.h>     /* for inb_p, outb_p, inb, outb, etc. */
0063 #include <linux/uaccess.h>  /* for get_user, etc. */
0064 #include <linux/wait.h>     /* for wait_queue */
0065 #include <linux/init.h>     /* for __init, module_{init,exit} */
0066 #include <linux/poll.h>     /* for EPOLLIN, etc. */
0067 #include <linux/dtlk.h>     /* local header file for DoubleTalk values */
0068 
0069 #ifdef TRACING
0070 #define TRACE_TEXT(str) printk(str);
0071 #define TRACE_RET printk(")")
0072 #else               /* !TRACING */
0073 #define TRACE_TEXT(str) ((void) 0)
0074 #define TRACE_RET ((void) 0)
0075 #endif              /* TRACING */
0076 
0077 static DEFINE_MUTEX(dtlk_mutex);
0078 static void dtlk_timer_tick(struct timer_list *unused);
0079 
0080 static int dtlk_major;
0081 static int dtlk_port_lpc;
0082 static int dtlk_port_tts;
0083 static int dtlk_busy;
0084 static int dtlk_has_indexing;
0085 static unsigned int dtlk_portlist[] =
0086 {0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
0087 static wait_queue_head_t dtlk_process_list;
0088 static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick);
0089 
0090 /* prototypes for file_operations struct */
0091 static ssize_t dtlk_read(struct file *, char __user *,
0092              size_t nbytes, loff_t * ppos);
0093 static ssize_t dtlk_write(struct file *, const char __user *,
0094               size_t nbytes, loff_t * ppos);
0095 static __poll_t dtlk_poll(struct file *, poll_table *);
0096 static int dtlk_open(struct inode *, struct file *);
0097 static int dtlk_release(struct inode *, struct file *);
0098 static long dtlk_ioctl(struct file *file,
0099                unsigned int cmd, unsigned long arg);
0100 
0101 static const struct file_operations dtlk_fops =
0102 {
0103     .owner      = THIS_MODULE,
0104     .read       = dtlk_read,
0105     .write      = dtlk_write,
0106     .poll       = dtlk_poll,
0107     .unlocked_ioctl = dtlk_ioctl,
0108     .open       = dtlk_open,
0109     .release    = dtlk_release,
0110     .llseek     = no_llseek,
0111 };
0112 
0113 /* local prototypes */
0114 static int dtlk_dev_probe(void);
0115 static struct dtlk_settings *dtlk_interrogate(void);
0116 static int dtlk_readable(void);
0117 static char dtlk_read_lpc(void);
0118 static char dtlk_read_tts(void);
0119 static int dtlk_writeable(void);
0120 static char dtlk_write_bytes(const char *buf, int n);
0121 static char dtlk_write_tts(char);
0122 /*
0123    static void dtlk_handle_error(char, char, unsigned int);
0124  */
0125 
0126 static ssize_t dtlk_read(struct file *file, char __user *buf,
0127              size_t count, loff_t * ppos)
0128 {
0129     unsigned int minor = iminor(file_inode(file));
0130     char ch;
0131     int i = 0, retries;
0132 
0133     TRACE_TEXT("(dtlk_read");
0134     /*  printk("DoubleTalk PC - dtlk_read()\n"); */
0135 
0136     if (minor != DTLK_MINOR || !dtlk_has_indexing)
0137         return -EINVAL;
0138 
0139     for (retries = 0; retries < loops_per_jiffy; retries++) {
0140         while (i < count && dtlk_readable()) {
0141             ch = dtlk_read_lpc();
0142             /*        printk("dtlk_read() reads 0x%02x\n", ch); */
0143             if (put_user(ch, buf++))
0144                 return -EFAULT;
0145             i++;
0146         }
0147         if (i)
0148             return i;
0149         if (file->f_flags & O_NONBLOCK)
0150             break;
0151         msleep_interruptible(100);
0152     }
0153     if (retries == loops_per_jiffy)
0154         printk(KERN_ERR "dtlk_read times out\n");
0155     TRACE_RET;
0156     return -EAGAIN;
0157 }
0158 
0159 static ssize_t dtlk_write(struct file *file, const char __user *buf,
0160               size_t count, loff_t * ppos)
0161 {
0162     int i = 0, retries = 0, ch;
0163 
0164     TRACE_TEXT("(dtlk_write");
0165 #ifdef TRACING
0166     printk(" \"");
0167     {
0168         int i, ch;
0169         for (i = 0; i < count; i++) {
0170             if (get_user(ch, buf + i))
0171                 return -EFAULT;
0172             if (' ' <= ch && ch <= '~')
0173                 printk("%c", ch);
0174             else
0175                 printk("\\%03o", ch);
0176         }
0177         printk("\"");
0178     }
0179 #endif
0180 
0181     if (iminor(file_inode(file)) != DTLK_MINOR)
0182         return -EINVAL;
0183 
0184     while (1) {
0185         while (i < count && !get_user(ch, buf) &&
0186                (ch == DTLK_CLEAR || dtlk_writeable())) {
0187             dtlk_write_tts(ch);
0188             buf++;
0189             i++;
0190             if (i % 5 == 0)
0191                 /* We yield our time until scheduled
0192                    again.  This reduces the transfer
0193                    rate to 500 bytes/sec, but that's
0194                    still enough to keep up with the
0195                    speech synthesizer. */
0196                 msleep_interruptible(1);
0197             else {
0198                 /* the RDY bit goes zero 2-3 usec
0199                    after writing, and goes 1 again
0200                    180-190 usec later.  Here, we wait
0201                    up to 250 usec for the RDY bit to
0202                    go nonzero. */
0203                 for (retries = 0;
0204                      retries < loops_per_jiffy / (4000/HZ);
0205                      retries++)
0206                     if (inb_p(dtlk_port_tts) &
0207                         TTS_WRITABLE)
0208                         break;
0209             }
0210             retries = 0;
0211         }
0212         if (i == count)
0213             return i;
0214         if (file->f_flags & O_NONBLOCK)
0215             break;
0216 
0217         msleep_interruptible(1);
0218 
0219         if (++retries > 10 * HZ) { /* wait no more than 10 sec
0220                           from last write */
0221             printk("dtlk: write timeout.  "
0222                    "inb_p(dtlk_port_tts) = 0x%02x\n",
0223                    inb_p(dtlk_port_tts));
0224             TRACE_RET;
0225             return -EBUSY;
0226         }
0227     }
0228     TRACE_RET;
0229     return -EAGAIN;
0230 }
0231 
0232 static __poll_t dtlk_poll(struct file *file, poll_table * wait)
0233 {
0234     __poll_t mask = 0;
0235     unsigned long expires;
0236 
0237     TRACE_TEXT(" dtlk_poll");
0238     /*
0239        static long int j;
0240        printk(".");
0241        printk("<%ld>", jiffies-j);
0242        j=jiffies;
0243      */
0244     poll_wait(file, &dtlk_process_list, wait);
0245 
0246     if (dtlk_has_indexing && dtlk_readable()) {
0247             del_timer(&dtlk_timer);
0248         mask = EPOLLIN | EPOLLRDNORM;
0249     }
0250     if (dtlk_writeable()) {
0251             del_timer(&dtlk_timer);
0252         mask |= EPOLLOUT | EPOLLWRNORM;
0253     }
0254     /* there are no exception conditions */
0255 
0256     /* There won't be any interrupts, so we set a timer instead. */
0257     expires = jiffies + 3*HZ / 100;
0258     mod_timer(&dtlk_timer, expires);
0259 
0260     return mask;
0261 }
0262 
0263 static void dtlk_timer_tick(struct timer_list *unused)
0264 {
0265     TRACE_TEXT(" dtlk_timer_tick");
0266     wake_up_interruptible(&dtlk_process_list);
0267 }
0268 
0269 static long dtlk_ioctl(struct file *file,
0270                unsigned int cmd,
0271                unsigned long arg)
0272 {
0273     char __user *argp = (char __user *)arg;
0274     struct dtlk_settings *sp;
0275     char portval;
0276     TRACE_TEXT(" dtlk_ioctl");
0277 
0278     switch (cmd) {
0279 
0280     case DTLK_INTERROGATE:
0281         mutex_lock(&dtlk_mutex);
0282         sp = dtlk_interrogate();
0283         mutex_unlock(&dtlk_mutex);
0284         if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
0285             return -EINVAL;
0286         return 0;
0287 
0288     case DTLK_STATUS:
0289         portval = inb_p(dtlk_port_tts);
0290         return put_user(portval, argp);
0291 
0292     default:
0293         return -EINVAL;
0294     }
0295 }
0296 
0297 /* Note that nobody ever sets dtlk_busy... */
0298 static int dtlk_open(struct inode *inode, struct file *file)
0299 {
0300     TRACE_TEXT("(dtlk_open");
0301 
0302     switch (iminor(inode)) {
0303     case DTLK_MINOR:
0304         if (dtlk_busy)
0305             return -EBUSY;
0306         return stream_open(inode, file);
0307 
0308     default:
0309         return -ENXIO;
0310     }
0311 }
0312 
0313 static int dtlk_release(struct inode *inode, struct file *file)
0314 {
0315     TRACE_TEXT("(dtlk_release");
0316 
0317     switch (iminor(inode)) {
0318     case DTLK_MINOR:
0319         break;
0320 
0321     default:
0322         break;
0323     }
0324     TRACE_RET;
0325     
0326     del_timer_sync(&dtlk_timer);
0327 
0328     return 0;
0329 }
0330 
0331 static int __init dtlk_init(void)
0332 {
0333     int err;
0334 
0335     dtlk_port_lpc = 0;
0336     dtlk_port_tts = 0;
0337     dtlk_busy = 0;
0338     dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
0339     if (dtlk_major < 0) {
0340         printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
0341         return dtlk_major;
0342     }
0343     err = dtlk_dev_probe();
0344     if (err) {
0345         unregister_chrdev(dtlk_major, "dtlk");
0346         return err;
0347     }
0348     printk(", MAJOR %d\n", dtlk_major);
0349 
0350     init_waitqueue_head(&dtlk_process_list);
0351 
0352     return 0;
0353 }
0354 
0355 static void __exit dtlk_cleanup (void)
0356 {
0357     dtlk_write_bytes("goodbye", 8);
0358     msleep_interruptible(500);      /* nap 0.50 sec but
0359                            could be awakened
0360                            earlier by
0361                            signals... */
0362 
0363     dtlk_write_tts(DTLK_CLEAR);
0364     unregister_chrdev(dtlk_major, "dtlk");
0365     release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
0366 }
0367 
0368 module_init(dtlk_init);
0369 module_exit(dtlk_cleanup);
0370 
0371 /* ------------------------------------------------------------------------ */
0372 
0373 static int dtlk_readable(void)
0374 {
0375 #ifdef TRACING
0376     printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
0377 #endif
0378     return inb_p(dtlk_port_lpc) != 0x7f;
0379 }
0380 
0381 static int dtlk_writeable(void)
0382 {
0383     /* TRACE_TEXT(" dtlk_writeable"); */
0384 #ifdef TRACINGMORE
0385     printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
0386 #endif
0387     return inb_p(dtlk_port_tts) & TTS_WRITABLE;
0388 }
0389 
0390 static int __init dtlk_dev_probe(void)
0391 {
0392     unsigned int testval = 0;
0393     int i = 0;
0394     struct dtlk_settings *sp;
0395 
0396     if (dtlk_port_lpc | dtlk_port_tts)
0397         return -EBUSY;
0398 
0399     for (i = 0; dtlk_portlist[i]; i++) {
0400 #if 0
0401         printk("DoubleTalk PC - Port %03x = %04x\n",
0402                dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
0403 #endif
0404 
0405         if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT, 
0406                    "dtlk"))
0407             continue;
0408         testval = inw_p(dtlk_portlist[i]);
0409         if ((testval &= 0xfbff) == 0x107f) {
0410             dtlk_port_lpc = dtlk_portlist[i];
0411             dtlk_port_tts = dtlk_port_lpc + 1;
0412 
0413             sp = dtlk_interrogate();
0414             printk("DoubleTalk PC at %03x-%03x, "
0415                    "ROM version %s, serial number %u",
0416                    dtlk_portlist[i], dtlk_portlist[i] +
0417                    DTLK_IO_EXTENT - 1,
0418                    sp->rom_version, sp->serial_number);
0419 
0420                         /* put LPC port into known state, so
0421                dtlk_readable() gives valid result */
0422             outb_p(0xff, dtlk_port_lpc); 
0423 
0424                         /* INIT string and index marker */
0425             dtlk_write_bytes("\036\1@\0\0012I\r", 8);
0426             /* posting an index takes 18 msec.  Here, we
0427                wait up to 100 msec to see whether it
0428                appears. */
0429             msleep_interruptible(100);
0430             dtlk_has_indexing = dtlk_readable();
0431 #ifdef TRACING
0432             printk(", indexing %d\n", dtlk_has_indexing);
0433 #endif
0434 #ifdef INSCOPE
0435             {
0436 /* This macro records ten samples read from the LPC port, for later display */
0437 #define LOOK                    \
0438 for (i = 0; i < 10; i++)            \
0439   {                     \
0440     buffer[b++] = inb_p(dtlk_port_lpc);     \
0441     __delay(loops_per_jiffy/(1000000/HZ));             \
0442   }
0443                 char buffer[1000];
0444                 int b = 0, i, j;
0445 
0446                 LOOK
0447                 outb_p(0xff, dtlk_port_lpc);
0448                 buffer[b++] = 0;
0449                 LOOK
0450                 dtlk_write_bytes("\0012I\r", 4);
0451                 buffer[b++] = 0;
0452                 __delay(50 * loops_per_jiffy / (1000/HZ));
0453                 outb_p(0xff, dtlk_port_lpc);
0454                 buffer[b++] = 0;
0455                 LOOK
0456 
0457                 printk("\n");
0458                 for (j = 0; j < b; j++)
0459                     printk(" %02x", buffer[j]);
0460                 printk("\n");
0461             }
0462 #endif              /* INSCOPE */
0463 
0464 #ifdef OUTSCOPE
0465             {
0466 /* This macro records ten samples read from the TTS port, for later display */
0467 #define LOOK                    \
0468 for (i = 0; i < 10; i++)            \
0469   {                     \
0470     buffer[b++] = inb_p(dtlk_port_tts);     \
0471     __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
0472   }
0473                 char buffer[1000];
0474                 int b = 0, i, j;
0475 
0476                 mdelay(10); /* 10 ms */
0477                 LOOK
0478                 outb_p(0x03, dtlk_port_tts);
0479                 buffer[b++] = 0;
0480                 LOOK
0481                 LOOK
0482 
0483                 printk("\n");
0484                 for (j = 0; j < b; j++)
0485                     printk(" %02x", buffer[j]);
0486                 printk("\n");
0487             }
0488 #endif              /* OUTSCOPE */
0489 
0490             dtlk_write_bytes("Double Talk found", 18);
0491 
0492             return 0;
0493         }
0494         release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
0495     }
0496 
0497     printk(KERN_INFO "DoubleTalk PC - not found\n");
0498     return -ENODEV;
0499 }
0500 
0501 /*
0502    static void dtlk_handle_error(char op, char rc, unsigned int minor)
0503    {
0504    printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n", 
0505    minor, op, rc);
0506    return;
0507    }
0508  */
0509 
0510 /* interrogate the DoubleTalk PC and return its settings */
0511 static struct dtlk_settings *dtlk_interrogate(void)
0512 {
0513     unsigned char *t;
0514     static char buf[sizeof(struct dtlk_settings) + 1];
0515     int total, i;
0516     static struct dtlk_settings status;
0517     TRACE_TEXT("(dtlk_interrogate");
0518     dtlk_write_bytes("\030\001?", 3);
0519     for (total = 0, i = 0; i < 50; i++) {
0520         buf[total] = dtlk_read_tts();
0521         if (total > 2 && buf[total] == 0x7f)
0522             break;
0523         if (total < sizeof(struct dtlk_settings))
0524             total++;
0525     }
0526     /*
0527        if (i==50) printk("interrogate() read overrun\n");
0528        for (i=0; i<sizeof(buf); i++)
0529        printk(" %02x", buf[i]);
0530        printk("\n");
0531      */
0532     t = buf;
0533     status.serial_number = t[0] + t[1] * 256; /* serial number is
0534                              little endian */
0535     t += 2;
0536 
0537     i = 0;
0538     while (*t != '\r') {
0539         status.rom_version[i] = *t;
0540         if (i < sizeof(status.rom_version) - 1)
0541             i++;
0542         t++;
0543     }
0544     status.rom_version[i] = 0;
0545     t++;
0546 
0547     status.mode = *t++;
0548     status.punc_level = *t++;
0549     status.formant_freq = *t++;
0550     status.pitch = *t++;
0551     status.speed = *t++;
0552     status.volume = *t++;
0553     status.tone = *t++;
0554     status.expression = *t++;
0555     status.ext_dict_loaded = *t++;
0556     status.ext_dict_status = *t++;
0557     status.free_ram = *t++;
0558     status.articulation = *t++;
0559     status.reverb = *t++;
0560     status.eob = *t++;
0561     status.has_indexing = dtlk_has_indexing;
0562     TRACE_RET;
0563     return &status;
0564 }
0565 
0566 static char dtlk_read_tts(void)
0567 {
0568     int portval, retries = 0;
0569     char ch;
0570     TRACE_TEXT("(dtlk_read_tts");
0571 
0572     /* verify DT is ready, read char, wait for ACK */
0573     do {
0574         portval = inb_p(dtlk_port_tts);
0575     } while ((portval & TTS_READABLE) == 0 &&
0576          retries++ < DTLK_MAX_RETRIES);
0577     if (retries > DTLK_MAX_RETRIES)
0578         printk(KERN_ERR "dtlk_read_tts() timeout\n");
0579 
0580     ch = inb_p(dtlk_port_tts);  /* input from TTS port */
0581     ch &= 0x7f;
0582     outb_p(ch, dtlk_port_tts);
0583 
0584     retries = 0;
0585     do {
0586         portval = inb_p(dtlk_port_tts);
0587     } while ((portval & TTS_READABLE) != 0 &&
0588          retries++ < DTLK_MAX_RETRIES);
0589     if (retries > DTLK_MAX_RETRIES)
0590         printk(KERN_ERR "dtlk_read_tts() timeout\n");
0591 
0592     TRACE_RET;
0593     return ch;
0594 }
0595 
0596 static char dtlk_read_lpc(void)
0597 {
0598     int retries = 0;
0599     char ch;
0600     TRACE_TEXT("(dtlk_read_lpc");
0601 
0602     /* no need to test -- this is only called when the port is readable */
0603 
0604     ch = inb_p(dtlk_port_lpc);  /* input from LPC port */
0605 
0606     outb_p(0xff, dtlk_port_lpc);
0607 
0608     /* acknowledging a read takes 3-4
0609        usec.  Here, we wait up to 20 usec
0610        for the acknowledgement */
0611     retries = (loops_per_jiffy * 20) / (1000000/HZ);
0612     while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
0613     if (retries == 0)
0614         printk(KERN_ERR "dtlk_read_lpc() timeout\n");
0615 
0616     TRACE_RET;
0617     return ch;
0618 }
0619 
0620 /* write n bytes to tts port */
0621 static char dtlk_write_bytes(const char *buf, int n)
0622 {
0623     char val = 0;
0624     /*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
0625     TRACE_TEXT("(dtlk_write_bytes");
0626     while (n-- > 0)
0627         val = dtlk_write_tts(*buf++);
0628     TRACE_RET;
0629     return val;
0630 }
0631 
0632 static char dtlk_write_tts(char ch)
0633 {
0634     int retries = 0;
0635 #ifdef TRACINGMORE
0636     printk("  dtlk_write_tts(");
0637     if (' ' <= ch && ch <= '~')
0638         printk("'%c'", ch);
0639     else
0640         printk("0x%02x", ch);
0641 #endif
0642     if (ch != DTLK_CLEAR)   /* no flow control for CLEAR command */
0643         while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
0644                retries++ < DTLK_MAX_RETRIES)    /* DT ready? */
0645             ;
0646     if (retries > DTLK_MAX_RETRIES)
0647         printk(KERN_ERR "dtlk_write_tts() timeout\n");
0648 
0649     outb_p(ch, dtlk_port_tts);  /* output to TTS port */
0650     /* the RDY bit goes zero 2-3 usec after writing, and goes
0651        1 again 180-190 usec later.  Here, we wait up to 10
0652        usec for the RDY bit to go zero. */
0653     for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
0654         if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
0655             break;
0656 
0657 #ifdef TRACINGMORE
0658     printk(")\n");
0659 #endif
0660     return 0;
0661 }
0662 
0663 MODULE_LICENSE("GPL");