12067fd92SSamuel Thibault // SPDX-License-Identifier: GPL-2.0+ 22067fd92SSamuel Thibault /* 32067fd92SSamuel Thibault * originally written by: Kirk Reiser <kirk@braille.uwo.ca> 42067fd92SSamuel Thibault * this version considerably modified by David Borowski, david575@rogers.com 52067fd92SSamuel Thibault * 62067fd92SSamuel Thibault * Copyright (C) 1998-99 Kirk Reiser. 72067fd92SSamuel Thibault * Copyright (C) 2003 David Borowski. 82067fd92SSamuel Thibault * 92067fd92SSamuel Thibault * specificly written as a driver for the speakup screenreview 102067fd92SSamuel Thibault * s not a general device driver. 112067fd92SSamuel Thibault */ 122067fd92SSamuel Thibault #include <linux/unistd.h> 132067fd92SSamuel Thibault #include <linux/proc_fs.h> 142067fd92SSamuel Thibault #include <linux/jiffies.h> 152067fd92SSamuel Thibault #include <linux/spinlock.h> 162067fd92SSamuel Thibault #include <linux/sched.h> 172067fd92SSamuel Thibault #include <linux/timer.h> 182067fd92SSamuel Thibault #include <linux/kthread.h> 192067fd92SSamuel Thibault #include "speakup.h" 202067fd92SSamuel Thibault #include "spk_priv.h" 212067fd92SSamuel Thibault 222067fd92SSamuel Thibault #define DRV_VERSION "2.20" 232067fd92SSamuel Thibault #define SYNTH_CLEAR 0x03 242067fd92SSamuel Thibault #define PROCSPEECH 0x0b 252067fd92SSamuel Thibault static int xoff; 262067fd92SSamuel Thibault 272067fd92SSamuel Thibault static inline int synth_full(void) 282067fd92SSamuel Thibault { 292067fd92SSamuel Thibault return xoff; 302067fd92SSamuel Thibault } 312067fd92SSamuel Thibault 322067fd92SSamuel Thibault static void do_catch_up(struct spk_synth *synth); 332067fd92SSamuel Thibault static void synth_flush(struct spk_synth *synth); 342067fd92SSamuel Thibault static void read_buff_add(u_char c); 352067fd92SSamuel Thibault static unsigned char get_index(struct spk_synth *synth); 362067fd92SSamuel Thibault 372067fd92SSamuel Thibault static int in_escape; 382067fd92SSamuel Thibault static int is_flushing; 392067fd92SSamuel Thibault 40*d1b928eeSYang Yingliang static DEFINE_SPINLOCK(flush_lock); 412067fd92SSamuel Thibault static DECLARE_WAIT_QUEUE_HEAD(flush); 422067fd92SSamuel Thibault 432067fd92SSamuel Thibault static struct var_t vars[] = { 442067fd92SSamuel Thibault { CAPS_START, .u.s = {"[:dv ap 160] " } }, 452067fd92SSamuel Thibault { CAPS_STOP, .u.s = {"[:dv ap 100 ] " } }, 462067fd92SSamuel Thibault { RATE, .u.n = {"[:ra %d] ", 180, 75, 650, 0, 0, NULL } }, 472067fd92SSamuel Thibault { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } }, 482067fd92SSamuel Thibault { VOL, .u.n = {"[:dv g5 %d] ", 86, 60, 86, 0, 0, NULL } }, 492067fd92SSamuel Thibault { PUNCT, .u.n = {"[:pu %c] ", 0, 0, 2, 0, 0, "nsa" } }, 502067fd92SSamuel Thibault { VOICE, .u.n = {"[:n%c] ", 0, 0, 9, 0, 0, "phfdburwkv" } }, 512067fd92SSamuel Thibault { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, 522067fd92SSamuel Thibault V_LAST_VAR 532067fd92SSamuel Thibault }; 542067fd92SSamuel Thibault 552067fd92SSamuel Thibault /* 562067fd92SSamuel Thibault * These attributes will appear in /sys/accessibility/speakup/dectlk. 572067fd92SSamuel Thibault */ 582067fd92SSamuel Thibault static struct kobj_attribute caps_start_attribute = 592067fd92SSamuel Thibault __ATTR(caps_start, 0644, spk_var_show, spk_var_store); 602067fd92SSamuel Thibault static struct kobj_attribute caps_stop_attribute = 612067fd92SSamuel Thibault __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); 622067fd92SSamuel Thibault static struct kobj_attribute pitch_attribute = 632067fd92SSamuel Thibault __ATTR(pitch, 0644, spk_var_show, spk_var_store); 642067fd92SSamuel Thibault static struct kobj_attribute inflection_attribute = 652067fd92SSamuel Thibault __ATTR(inflection, 0644, spk_var_show, spk_var_store); 662067fd92SSamuel Thibault static struct kobj_attribute punct_attribute = 672067fd92SSamuel Thibault __ATTR(punct, 0644, spk_var_show, spk_var_store); 682067fd92SSamuel Thibault static struct kobj_attribute rate_attribute = 692067fd92SSamuel Thibault __ATTR(rate, 0644, spk_var_show, spk_var_store); 702067fd92SSamuel Thibault static struct kobj_attribute voice_attribute = 712067fd92SSamuel Thibault __ATTR(voice, 0644, spk_var_show, spk_var_store); 722067fd92SSamuel Thibault static struct kobj_attribute vol_attribute = 732067fd92SSamuel Thibault __ATTR(vol, 0644, spk_var_show, spk_var_store); 742067fd92SSamuel Thibault 752067fd92SSamuel Thibault static struct kobj_attribute delay_time_attribute = 762067fd92SSamuel Thibault __ATTR(delay_time, 0644, spk_var_show, spk_var_store); 772067fd92SSamuel Thibault static struct kobj_attribute direct_attribute = 782067fd92SSamuel Thibault __ATTR(direct, 0644, spk_var_show, spk_var_store); 792067fd92SSamuel Thibault static struct kobj_attribute full_time_attribute = 802067fd92SSamuel Thibault __ATTR(full_time, 0644, spk_var_show, spk_var_store); 812067fd92SSamuel Thibault static struct kobj_attribute jiffy_delta_attribute = 822067fd92SSamuel Thibault __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); 832067fd92SSamuel Thibault static struct kobj_attribute trigger_time_attribute = 842067fd92SSamuel Thibault __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); 852067fd92SSamuel Thibault 862067fd92SSamuel Thibault /* 872067fd92SSamuel Thibault * Create a group of attributes so that we can create and destroy them all 882067fd92SSamuel Thibault * at once. 892067fd92SSamuel Thibault */ 902067fd92SSamuel Thibault static struct attribute *synth_attrs[] = { 912067fd92SSamuel Thibault &caps_start_attribute.attr, 922067fd92SSamuel Thibault &caps_stop_attribute.attr, 932067fd92SSamuel Thibault &pitch_attribute.attr, 942067fd92SSamuel Thibault &inflection_attribute.attr, 952067fd92SSamuel Thibault &punct_attribute.attr, 962067fd92SSamuel Thibault &rate_attribute.attr, 972067fd92SSamuel Thibault &voice_attribute.attr, 982067fd92SSamuel Thibault &vol_attribute.attr, 992067fd92SSamuel Thibault &delay_time_attribute.attr, 1002067fd92SSamuel Thibault &direct_attribute.attr, 1012067fd92SSamuel Thibault &full_time_attribute.attr, 1022067fd92SSamuel Thibault &jiffy_delta_attribute.attr, 1032067fd92SSamuel Thibault &trigger_time_attribute.attr, 1042067fd92SSamuel Thibault NULL, /* need to NULL terminate the list of attributes */ 1052067fd92SSamuel Thibault }; 1062067fd92SSamuel Thibault 1072067fd92SSamuel Thibault static int ap_defaults[] = {122, 89, 155, 110, 208, 240, 200, 106, 306}; 1082067fd92SSamuel Thibault static int g5_defaults[] = {86, 81, 86, 84, 81, 80, 83, 83, 73}; 1092067fd92SSamuel Thibault 1102067fd92SSamuel Thibault static struct spk_synth synth_dectlk = { 1112067fd92SSamuel Thibault .name = "dectlk", 1122067fd92SSamuel Thibault .version = DRV_VERSION, 1132067fd92SSamuel Thibault .long_name = "Dectalk Express", 1142067fd92SSamuel Thibault .init = "[:error sp :name paul :rate 180 :tsr off] ", 1152067fd92SSamuel Thibault .procspeech = PROCSPEECH, 1162067fd92SSamuel Thibault .clear = SYNTH_CLEAR, 1172067fd92SSamuel Thibault .delay = 500, 1182067fd92SSamuel Thibault .trigger = 50, 1192067fd92SSamuel Thibault .jiffies = 50, 1202067fd92SSamuel Thibault .full = 40000, 1212067fd92SSamuel Thibault .dev_name = SYNTH_DEFAULT_DEV, 1222067fd92SSamuel Thibault .startup = SYNTH_START, 1232067fd92SSamuel Thibault .checkval = SYNTH_CHECK, 1242067fd92SSamuel Thibault .vars = vars, 1252067fd92SSamuel Thibault .default_pitch = ap_defaults, 1262067fd92SSamuel Thibault .default_vol = g5_defaults, 1272067fd92SSamuel Thibault .io_ops = &spk_ttyio_ops, 1282067fd92SSamuel Thibault .probe = spk_ttyio_synth_probe, 1292067fd92SSamuel Thibault .release = spk_ttyio_release, 1302067fd92SSamuel Thibault .synth_immediate = spk_ttyio_synth_immediate, 1312067fd92SSamuel Thibault .catch_up = do_catch_up, 1322067fd92SSamuel Thibault .flush = synth_flush, 1332067fd92SSamuel Thibault .is_alive = spk_synth_is_alive_restart, 1342067fd92SSamuel Thibault .synth_adjust = NULL, 1352067fd92SSamuel Thibault .read_buff_add = read_buff_add, 1362067fd92SSamuel Thibault .get_index = get_index, 1372067fd92SSamuel Thibault .indexing = { 1382067fd92SSamuel Thibault .command = "[:in re %d ] ", 1392067fd92SSamuel Thibault .lowindex = 1, 1402067fd92SSamuel Thibault .highindex = 8, 1412067fd92SSamuel Thibault .currindex = 1, 1422067fd92SSamuel Thibault }, 1432067fd92SSamuel Thibault .attributes = { 1442067fd92SSamuel Thibault .attrs = synth_attrs, 1452067fd92SSamuel Thibault .name = "dectlk", 1462067fd92SSamuel Thibault }, 1472067fd92SSamuel Thibault }; 1482067fd92SSamuel Thibault 1492067fd92SSamuel Thibault static int is_indnum(u_char *ch) 1502067fd92SSamuel Thibault { 1512067fd92SSamuel Thibault if ((*ch >= '0') && (*ch <= '9')) { 1522067fd92SSamuel Thibault *ch = *ch - '0'; 1532067fd92SSamuel Thibault return 1; 1542067fd92SSamuel Thibault } 1552067fd92SSamuel Thibault return 0; 1562067fd92SSamuel Thibault } 1572067fd92SSamuel Thibault 1582067fd92SSamuel Thibault static u_char lastind; 1592067fd92SSamuel Thibault 1602067fd92SSamuel Thibault static unsigned char get_index(struct spk_synth *synth) 1612067fd92SSamuel Thibault { 1622067fd92SSamuel Thibault u_char rv; 1632067fd92SSamuel Thibault 1642067fd92SSamuel Thibault rv = lastind; 1652067fd92SSamuel Thibault lastind = 0; 1662067fd92SSamuel Thibault return rv; 1672067fd92SSamuel Thibault } 1682067fd92SSamuel Thibault 1692067fd92SSamuel Thibault static void read_buff_add(u_char c) 1702067fd92SSamuel Thibault { 1712067fd92SSamuel Thibault static int ind = -1; 1722067fd92SSamuel Thibault 1732067fd92SSamuel Thibault if (c == 0x01) { 1742067fd92SSamuel Thibault unsigned long flags; 1752067fd92SSamuel Thibault 1762067fd92SSamuel Thibault spin_lock_irqsave(&flush_lock, flags); 1772067fd92SSamuel Thibault is_flushing = 0; 1782067fd92SSamuel Thibault wake_up_interruptible(&flush); 1792067fd92SSamuel Thibault spin_unlock_irqrestore(&flush_lock, flags); 1802067fd92SSamuel Thibault } else if (c == 0x13) { 1812067fd92SSamuel Thibault xoff = 1; 1822067fd92SSamuel Thibault } else if (c == 0x11) { 1832067fd92SSamuel Thibault xoff = 0; 1842067fd92SSamuel Thibault } else if (is_indnum(&c)) { 1852067fd92SSamuel Thibault if (ind == -1) 1862067fd92SSamuel Thibault ind = c; 1872067fd92SSamuel Thibault else 1882067fd92SSamuel Thibault ind = ind * 10 + c; 1892067fd92SSamuel Thibault } else if ((c > 31) && (c < 127)) { 1902067fd92SSamuel Thibault if (ind != -1) 1912067fd92SSamuel Thibault lastind = (u_char)ind; 1922067fd92SSamuel Thibault ind = -1; 1932067fd92SSamuel Thibault } 1942067fd92SSamuel Thibault } 1952067fd92SSamuel Thibault 1962067fd92SSamuel Thibault static void do_catch_up(struct spk_synth *synth) 1972067fd92SSamuel Thibault { 1982067fd92SSamuel Thibault int synth_full_val = 0; 1992067fd92SSamuel Thibault static u_char ch; 2002067fd92SSamuel Thibault static u_char last = '\0'; 2012067fd92SSamuel Thibault unsigned long flags; 2022067fd92SSamuel Thibault unsigned long jiff_max; 2032067fd92SSamuel Thibault unsigned long timeout = msecs_to_jiffies(4000); 2042067fd92SSamuel Thibault DEFINE_WAIT(wait); 2052067fd92SSamuel Thibault struct var_t *jiffy_delta; 2062067fd92SSamuel Thibault struct var_t *delay_time; 2072067fd92SSamuel Thibault int jiffy_delta_val; 2082067fd92SSamuel Thibault int delay_time_val; 2092067fd92SSamuel Thibault 2102067fd92SSamuel Thibault jiffy_delta = spk_get_var(JIFFY); 2112067fd92SSamuel Thibault delay_time = spk_get_var(DELAY); 2122067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 2132067fd92SSamuel Thibault jiffy_delta_val = jiffy_delta->u.n.value; 2142067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2152067fd92SSamuel Thibault jiff_max = jiffies + jiffy_delta_val; 2162067fd92SSamuel Thibault 2172067fd92SSamuel Thibault while (!kthread_should_stop()) { 2182067fd92SSamuel Thibault /* if no ctl-a in 4, send data anyway */ 2192067fd92SSamuel Thibault spin_lock_irqsave(&flush_lock, flags); 2202067fd92SSamuel Thibault while (is_flushing && timeout) { 2212067fd92SSamuel Thibault prepare_to_wait(&flush, &wait, TASK_INTERRUPTIBLE); 2222067fd92SSamuel Thibault spin_unlock_irqrestore(&flush_lock, flags); 2232067fd92SSamuel Thibault timeout = schedule_timeout(timeout); 2242067fd92SSamuel Thibault spin_lock_irqsave(&flush_lock, flags); 2252067fd92SSamuel Thibault } 2262067fd92SSamuel Thibault finish_wait(&flush, &wait); 2272067fd92SSamuel Thibault is_flushing = 0; 2282067fd92SSamuel Thibault spin_unlock_irqrestore(&flush_lock, flags); 2292067fd92SSamuel Thibault 2302067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 2312067fd92SSamuel Thibault if (speakup_info.flushing) { 2322067fd92SSamuel Thibault speakup_info.flushing = 0; 2332067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2342067fd92SSamuel Thibault synth->flush(synth); 2352067fd92SSamuel Thibault continue; 2362067fd92SSamuel Thibault } 2372067fd92SSamuel Thibault synth_buffer_skip_nonlatin1(); 2382067fd92SSamuel Thibault if (synth_buffer_empty()) { 2392067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2402067fd92SSamuel Thibault break; 2412067fd92SSamuel Thibault } 2422067fd92SSamuel Thibault ch = synth_buffer_peek(); 2432067fd92SSamuel Thibault set_current_state(TASK_INTERRUPTIBLE); 2442067fd92SSamuel Thibault delay_time_val = delay_time->u.n.value; 2452067fd92SSamuel Thibault synth_full_val = synth_full(); 2462067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2472067fd92SSamuel Thibault if (ch == '\n') 2482067fd92SSamuel Thibault ch = 0x0D; 2492067fd92SSamuel Thibault if (synth_full_val || !synth->io_ops->synth_out(synth, ch)) { 2502067fd92SSamuel Thibault schedule_timeout(msecs_to_jiffies(delay_time_val)); 2512067fd92SSamuel Thibault continue; 2522067fd92SSamuel Thibault } 2532067fd92SSamuel Thibault set_current_state(TASK_RUNNING); 2542067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 2552067fd92SSamuel Thibault synth_buffer_getc(); 2562067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2572067fd92SSamuel Thibault if (ch == '[') { 2582067fd92SSamuel Thibault in_escape = 1; 2592067fd92SSamuel Thibault } else if (ch == ']') { 2602067fd92SSamuel Thibault in_escape = 0; 2612067fd92SSamuel Thibault } else if (ch <= SPACE) { 2622067fd92SSamuel Thibault if (!in_escape && strchr(",.!?;:", last)) 2632067fd92SSamuel Thibault synth->io_ops->synth_out(synth, PROCSPEECH); 2642067fd92SSamuel Thibault if (time_after_eq(jiffies, jiff_max)) { 2652067fd92SSamuel Thibault if (!in_escape) 2662067fd92SSamuel Thibault synth->io_ops->synth_out(synth, 2672067fd92SSamuel Thibault PROCSPEECH); 2682067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, 2692067fd92SSamuel Thibault flags); 2702067fd92SSamuel Thibault jiffy_delta_val = jiffy_delta->u.n.value; 2712067fd92SSamuel Thibault delay_time_val = delay_time->u.n.value; 2722067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, 2732067fd92SSamuel Thibault flags); 2742067fd92SSamuel Thibault schedule_timeout(msecs_to_jiffies 2752067fd92SSamuel Thibault (delay_time_val)); 2762067fd92SSamuel Thibault jiff_max = jiffies + jiffy_delta_val; 2772067fd92SSamuel Thibault } 2782067fd92SSamuel Thibault } 2792067fd92SSamuel Thibault last = ch; 2802067fd92SSamuel Thibault } 2812067fd92SSamuel Thibault if (!in_escape) 2822067fd92SSamuel Thibault synth->io_ops->synth_out(synth, PROCSPEECH); 2832067fd92SSamuel Thibault } 2842067fd92SSamuel Thibault 2852067fd92SSamuel Thibault static void synth_flush(struct spk_synth *synth) 2862067fd92SSamuel Thibault { 2872067fd92SSamuel Thibault if (in_escape) 2882067fd92SSamuel Thibault /* if in command output ']' so we don't get an error */ 2892067fd92SSamuel Thibault synth->io_ops->synth_out(synth, ']'); 2902067fd92SSamuel Thibault in_escape = 0; 2912067fd92SSamuel Thibault is_flushing = 1; 2922067fd92SSamuel Thibault synth->io_ops->flush_buffer(); 2932067fd92SSamuel Thibault synth->io_ops->synth_out(synth, SYNTH_CLEAR); 2942067fd92SSamuel Thibault } 2952067fd92SSamuel Thibault 2962067fd92SSamuel Thibault module_param_named(ser, synth_dectlk.ser, int, 0444); 2972067fd92SSamuel Thibault module_param_named(dev, synth_dectlk.dev_name, charp, 0444); 2982067fd92SSamuel Thibault module_param_named(start, synth_dectlk.startup, short, 0444); 2992067fd92SSamuel Thibault 3002067fd92SSamuel Thibault MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based)."); 3012067fd92SSamuel Thibault MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer."); 3022067fd92SSamuel Thibault MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded."); 3032067fd92SSamuel Thibault 3042067fd92SSamuel Thibault module_spk_synth(synth_dectlk); 3052067fd92SSamuel Thibault 3062067fd92SSamuel Thibault MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>"); 3072067fd92SSamuel Thibault MODULE_AUTHOR("David Borowski"); 3082067fd92SSamuel Thibault MODULE_DESCRIPTION("Speakup support for DECtalk Express synthesizers"); 3092067fd92SSamuel Thibault MODULE_LICENSE("GPL"); 3102067fd92SSamuel Thibault MODULE_VERSION(DRV_VERSION); 3112067fd92SSamuel Thibault 312