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 * 9*0b4efcb1STom Rix * specifically 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 40d1b928eeSYang 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 } }, 47bca828ccSSamuel Thibault { PITCH, .u.n = {"[:dv ap %d] ", 122, 50, 350, 0, 0, NULL } }, 482067fd92SSamuel Thibault { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } }, 492067fd92SSamuel Thibault { VOL, .u.n = {"[:dv g5 %d] ", 86, 60, 86, 0, 0, NULL } }, 502067fd92SSamuel Thibault { PUNCT, .u.n = {"[:pu %c] ", 0, 0, 2, 0, 0, "nsa" } }, 512067fd92SSamuel Thibault { VOICE, .u.n = {"[:n%c] ", 0, 0, 9, 0, 0, "phfdburwkv" } }, 522067fd92SSamuel Thibault { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, 532067fd92SSamuel Thibault V_LAST_VAR 542067fd92SSamuel Thibault }; 552067fd92SSamuel Thibault 562067fd92SSamuel Thibault /* 572067fd92SSamuel Thibault * These attributes will appear in /sys/accessibility/speakup/dectlk. 582067fd92SSamuel Thibault */ 592067fd92SSamuel Thibault static struct kobj_attribute caps_start_attribute = 602067fd92SSamuel Thibault __ATTR(caps_start, 0644, spk_var_show, spk_var_store); 612067fd92SSamuel Thibault static struct kobj_attribute caps_stop_attribute = 622067fd92SSamuel Thibault __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); 632067fd92SSamuel Thibault static struct kobj_attribute pitch_attribute = 642067fd92SSamuel Thibault __ATTR(pitch, 0644, spk_var_show, spk_var_store); 652067fd92SSamuel Thibault static struct kobj_attribute inflection_attribute = 662067fd92SSamuel Thibault __ATTR(inflection, 0644, spk_var_show, spk_var_store); 672067fd92SSamuel Thibault static struct kobj_attribute punct_attribute = 682067fd92SSamuel Thibault __ATTR(punct, 0644, spk_var_show, spk_var_store); 692067fd92SSamuel Thibault static struct kobj_attribute rate_attribute = 702067fd92SSamuel Thibault __ATTR(rate, 0644, spk_var_show, spk_var_store); 712067fd92SSamuel Thibault static struct kobj_attribute voice_attribute = 722067fd92SSamuel Thibault __ATTR(voice, 0644, spk_var_show, spk_var_store); 732067fd92SSamuel Thibault static struct kobj_attribute vol_attribute = 742067fd92SSamuel Thibault __ATTR(vol, 0644, spk_var_show, spk_var_store); 752067fd92SSamuel Thibault 762067fd92SSamuel Thibault static struct kobj_attribute delay_time_attribute = 772067fd92SSamuel Thibault __ATTR(delay_time, 0644, spk_var_show, spk_var_store); 782067fd92SSamuel Thibault static struct kobj_attribute direct_attribute = 792067fd92SSamuel Thibault __ATTR(direct, 0644, spk_var_show, spk_var_store); 802067fd92SSamuel Thibault static struct kobj_attribute full_time_attribute = 812067fd92SSamuel Thibault __ATTR(full_time, 0644, spk_var_show, spk_var_store); 821f7c14afSSamuel Thibault static struct kobj_attribute flush_time_attribute = 831f7c14afSSamuel Thibault __ATTR(flush_time, 0644, spk_var_show, spk_var_store); 842067fd92SSamuel Thibault static struct kobj_attribute jiffy_delta_attribute = 852067fd92SSamuel Thibault __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); 862067fd92SSamuel Thibault static struct kobj_attribute trigger_time_attribute = 872067fd92SSamuel Thibault __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); 882067fd92SSamuel Thibault 892067fd92SSamuel Thibault /* 902067fd92SSamuel Thibault * Create a group of attributes so that we can create and destroy them all 912067fd92SSamuel Thibault * at once. 922067fd92SSamuel Thibault */ 932067fd92SSamuel Thibault static struct attribute *synth_attrs[] = { 942067fd92SSamuel Thibault &caps_start_attribute.attr, 952067fd92SSamuel Thibault &caps_stop_attribute.attr, 962067fd92SSamuel Thibault &pitch_attribute.attr, 972067fd92SSamuel Thibault &inflection_attribute.attr, 982067fd92SSamuel Thibault &punct_attribute.attr, 992067fd92SSamuel Thibault &rate_attribute.attr, 1002067fd92SSamuel Thibault &voice_attribute.attr, 1012067fd92SSamuel Thibault &vol_attribute.attr, 1022067fd92SSamuel Thibault &delay_time_attribute.attr, 1032067fd92SSamuel Thibault &direct_attribute.attr, 1042067fd92SSamuel Thibault &full_time_attribute.attr, 1051f7c14afSSamuel Thibault &flush_time_attribute.attr, 1062067fd92SSamuel Thibault &jiffy_delta_attribute.attr, 1072067fd92SSamuel Thibault &trigger_time_attribute.attr, 1082067fd92SSamuel Thibault NULL, /* need to NULL terminate the list of attributes */ 1092067fd92SSamuel Thibault }; 1102067fd92SSamuel Thibault 1112067fd92SSamuel Thibault static int ap_defaults[] = {122, 89, 155, 110, 208, 240, 200, 106, 306}; 1122067fd92SSamuel Thibault static int g5_defaults[] = {86, 81, 86, 84, 81, 80, 83, 83, 73}; 1132067fd92SSamuel Thibault 1142067fd92SSamuel Thibault static struct spk_synth synth_dectlk = { 1152067fd92SSamuel Thibault .name = "dectlk", 1162067fd92SSamuel Thibault .version = DRV_VERSION, 1172067fd92SSamuel Thibault .long_name = "Dectalk Express", 1182067fd92SSamuel Thibault .init = "[:error sp :name paul :rate 180 :tsr off] ", 1192067fd92SSamuel Thibault .procspeech = PROCSPEECH, 1202067fd92SSamuel Thibault .clear = SYNTH_CLEAR, 1212067fd92SSamuel Thibault .delay = 500, 1222067fd92SSamuel Thibault .trigger = 50, 1232067fd92SSamuel Thibault .jiffies = 50, 1242067fd92SSamuel Thibault .full = 40000, 1251f7c14afSSamuel Thibault .flush_time = 4000, 1262067fd92SSamuel Thibault .dev_name = SYNTH_DEFAULT_DEV, 1272067fd92SSamuel Thibault .startup = SYNTH_START, 1282067fd92SSamuel Thibault .checkval = SYNTH_CHECK, 1292067fd92SSamuel Thibault .vars = vars, 1302067fd92SSamuel Thibault .default_pitch = ap_defaults, 1312067fd92SSamuel Thibault .default_vol = g5_defaults, 1322067fd92SSamuel Thibault .io_ops = &spk_ttyio_ops, 1332067fd92SSamuel Thibault .probe = spk_ttyio_synth_probe, 1342067fd92SSamuel Thibault .release = spk_ttyio_release, 1352067fd92SSamuel Thibault .synth_immediate = spk_ttyio_synth_immediate, 1362067fd92SSamuel Thibault .catch_up = do_catch_up, 1372067fd92SSamuel Thibault .flush = synth_flush, 1382067fd92SSamuel Thibault .is_alive = spk_synth_is_alive_restart, 1392067fd92SSamuel Thibault .synth_adjust = NULL, 1402067fd92SSamuel Thibault .read_buff_add = read_buff_add, 1412067fd92SSamuel Thibault .get_index = get_index, 1422067fd92SSamuel Thibault .indexing = { 1432067fd92SSamuel Thibault .command = "[:in re %d ] ", 1442067fd92SSamuel Thibault .lowindex = 1, 1452067fd92SSamuel Thibault .highindex = 8, 1462067fd92SSamuel Thibault .currindex = 1, 1472067fd92SSamuel Thibault }, 1482067fd92SSamuel Thibault .attributes = { 1492067fd92SSamuel Thibault .attrs = synth_attrs, 1502067fd92SSamuel Thibault .name = "dectlk", 1512067fd92SSamuel Thibault }, 1522067fd92SSamuel Thibault }; 1532067fd92SSamuel Thibault 1542067fd92SSamuel Thibault static int is_indnum(u_char *ch) 1552067fd92SSamuel Thibault { 1562067fd92SSamuel Thibault if ((*ch >= '0') && (*ch <= '9')) { 1572067fd92SSamuel Thibault *ch = *ch - '0'; 1582067fd92SSamuel Thibault return 1; 1592067fd92SSamuel Thibault } 1602067fd92SSamuel Thibault return 0; 1612067fd92SSamuel Thibault } 1622067fd92SSamuel Thibault 1632067fd92SSamuel Thibault static u_char lastind; 1642067fd92SSamuel Thibault 1652067fd92SSamuel Thibault static unsigned char get_index(struct spk_synth *synth) 1662067fd92SSamuel Thibault { 1672067fd92SSamuel Thibault u_char rv; 1682067fd92SSamuel Thibault 1692067fd92SSamuel Thibault rv = lastind; 1702067fd92SSamuel Thibault lastind = 0; 1712067fd92SSamuel Thibault return rv; 1722067fd92SSamuel Thibault } 1732067fd92SSamuel Thibault 1742067fd92SSamuel Thibault static void read_buff_add(u_char c) 1752067fd92SSamuel Thibault { 1762067fd92SSamuel Thibault static int ind = -1; 1772067fd92SSamuel Thibault 1782067fd92SSamuel Thibault if (c == 0x01) { 1792067fd92SSamuel Thibault unsigned long flags; 1802067fd92SSamuel Thibault 1812067fd92SSamuel Thibault spin_lock_irqsave(&flush_lock, flags); 1822067fd92SSamuel Thibault is_flushing = 0; 1832067fd92SSamuel Thibault wake_up_interruptible(&flush); 1842067fd92SSamuel Thibault spin_unlock_irqrestore(&flush_lock, flags); 1852067fd92SSamuel Thibault } else if (c == 0x13) { 1862067fd92SSamuel Thibault xoff = 1; 1872067fd92SSamuel Thibault } else if (c == 0x11) { 1882067fd92SSamuel Thibault xoff = 0; 1892067fd92SSamuel Thibault } else if (is_indnum(&c)) { 1902067fd92SSamuel Thibault if (ind == -1) 1912067fd92SSamuel Thibault ind = c; 1922067fd92SSamuel Thibault else 1932067fd92SSamuel Thibault ind = ind * 10 + c; 1942067fd92SSamuel Thibault } else if ((c > 31) && (c < 127)) { 1952067fd92SSamuel Thibault if (ind != -1) 1962067fd92SSamuel Thibault lastind = (u_char)ind; 1972067fd92SSamuel Thibault ind = -1; 1982067fd92SSamuel Thibault } 1992067fd92SSamuel Thibault } 2002067fd92SSamuel Thibault 2012067fd92SSamuel Thibault static void do_catch_up(struct spk_synth *synth) 2022067fd92SSamuel Thibault { 2032067fd92SSamuel Thibault int synth_full_val = 0; 2042067fd92SSamuel Thibault static u_char ch; 2052067fd92SSamuel Thibault static u_char last = '\0'; 2062067fd92SSamuel Thibault unsigned long flags; 2072067fd92SSamuel Thibault unsigned long jiff_max; 2081f7c14afSSamuel Thibault unsigned long timeout; 2092067fd92SSamuel Thibault DEFINE_WAIT(wait); 2102067fd92SSamuel Thibault struct var_t *jiffy_delta; 2112067fd92SSamuel Thibault struct var_t *delay_time; 2121f7c14afSSamuel Thibault struct var_t *flush_time; 2132067fd92SSamuel Thibault int jiffy_delta_val; 2142067fd92SSamuel Thibault int delay_time_val; 2151f7c14afSSamuel Thibault int timeout_val; 2162067fd92SSamuel Thibault 2172067fd92SSamuel Thibault jiffy_delta = spk_get_var(JIFFY); 2182067fd92SSamuel Thibault delay_time = spk_get_var(DELAY); 2191f7c14afSSamuel Thibault flush_time = spk_get_var(FLUSH); 2202067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 2212067fd92SSamuel Thibault jiffy_delta_val = jiffy_delta->u.n.value; 2221f7c14afSSamuel Thibault timeout_val = flush_time->u.n.value; 2232067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2241f7c14afSSamuel Thibault timeout = msecs_to_jiffies(timeout_val); 2252067fd92SSamuel Thibault jiff_max = jiffies + jiffy_delta_val; 2262067fd92SSamuel Thibault 2272067fd92SSamuel Thibault while (!kthread_should_stop()) { 2282067fd92SSamuel Thibault /* if no ctl-a in 4, send data anyway */ 2292067fd92SSamuel Thibault spin_lock_irqsave(&flush_lock, flags); 2302067fd92SSamuel Thibault while (is_flushing && timeout) { 2312067fd92SSamuel Thibault prepare_to_wait(&flush, &wait, TASK_INTERRUPTIBLE); 2322067fd92SSamuel Thibault spin_unlock_irqrestore(&flush_lock, flags); 2332067fd92SSamuel Thibault timeout = schedule_timeout(timeout); 2342067fd92SSamuel Thibault spin_lock_irqsave(&flush_lock, flags); 2352067fd92SSamuel Thibault } 2362067fd92SSamuel Thibault finish_wait(&flush, &wait); 2372067fd92SSamuel Thibault is_flushing = 0; 2382067fd92SSamuel Thibault spin_unlock_irqrestore(&flush_lock, flags); 2392067fd92SSamuel Thibault 2402067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 2412067fd92SSamuel Thibault if (speakup_info.flushing) { 2422067fd92SSamuel Thibault speakup_info.flushing = 0; 2432067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2442067fd92SSamuel Thibault synth->flush(synth); 2452067fd92SSamuel Thibault continue; 2462067fd92SSamuel Thibault } 2472067fd92SSamuel Thibault synth_buffer_skip_nonlatin1(); 2482067fd92SSamuel Thibault if (synth_buffer_empty()) { 2492067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2502067fd92SSamuel Thibault break; 2512067fd92SSamuel Thibault } 2522067fd92SSamuel Thibault ch = synth_buffer_peek(); 2532067fd92SSamuel Thibault set_current_state(TASK_INTERRUPTIBLE); 2542067fd92SSamuel Thibault delay_time_val = delay_time->u.n.value; 2552067fd92SSamuel Thibault synth_full_val = synth_full(); 2562067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2572067fd92SSamuel Thibault if (ch == '\n') 2582067fd92SSamuel Thibault ch = 0x0D; 2592067fd92SSamuel Thibault if (synth_full_val || !synth->io_ops->synth_out(synth, ch)) { 2602067fd92SSamuel Thibault schedule_timeout(msecs_to_jiffies(delay_time_val)); 2612067fd92SSamuel Thibault continue; 2622067fd92SSamuel Thibault } 2632067fd92SSamuel Thibault set_current_state(TASK_RUNNING); 2642067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 2652067fd92SSamuel Thibault synth_buffer_getc(); 2662067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2672067fd92SSamuel Thibault if (ch == '[') { 2682067fd92SSamuel Thibault in_escape = 1; 2692067fd92SSamuel Thibault } else if (ch == ']') { 2702067fd92SSamuel Thibault in_escape = 0; 2712067fd92SSamuel Thibault } else if (ch <= SPACE) { 2722067fd92SSamuel Thibault if (!in_escape && strchr(",.!?;:", last)) 2732067fd92SSamuel Thibault synth->io_ops->synth_out(synth, PROCSPEECH); 2742067fd92SSamuel Thibault if (time_after_eq(jiffies, jiff_max)) { 2752067fd92SSamuel Thibault if (!in_escape) 2762067fd92SSamuel Thibault synth->io_ops->synth_out(synth, 2772067fd92SSamuel Thibault PROCSPEECH); 2782067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, 2792067fd92SSamuel Thibault flags); 2802067fd92SSamuel Thibault jiffy_delta_val = jiffy_delta->u.n.value; 2812067fd92SSamuel Thibault delay_time_val = delay_time->u.n.value; 2822067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, 2832067fd92SSamuel Thibault flags); 2842067fd92SSamuel Thibault schedule_timeout(msecs_to_jiffies 2852067fd92SSamuel Thibault (delay_time_val)); 2862067fd92SSamuel Thibault jiff_max = jiffies + jiffy_delta_val; 2872067fd92SSamuel Thibault } 2882067fd92SSamuel Thibault } 2892067fd92SSamuel Thibault last = ch; 2902067fd92SSamuel Thibault } 2912067fd92SSamuel Thibault if (!in_escape) 2922067fd92SSamuel Thibault synth->io_ops->synth_out(synth, PROCSPEECH); 2932067fd92SSamuel Thibault } 2942067fd92SSamuel Thibault 2952067fd92SSamuel Thibault static void synth_flush(struct spk_synth *synth) 2962067fd92SSamuel Thibault { 2972067fd92SSamuel Thibault if (in_escape) 2982067fd92SSamuel Thibault /* if in command output ']' so we don't get an error */ 2992067fd92SSamuel Thibault synth->io_ops->synth_out(synth, ']'); 3002067fd92SSamuel Thibault in_escape = 0; 3012067fd92SSamuel Thibault is_flushing = 1; 3021941ab1dSSamuel Thibault synth->io_ops->flush_buffer(synth); 3032067fd92SSamuel Thibault synth->io_ops->synth_out(synth, SYNTH_CLEAR); 3042067fd92SSamuel Thibault } 3052067fd92SSamuel Thibault 3062067fd92SSamuel Thibault module_param_named(ser, synth_dectlk.ser, int, 0444); 3072067fd92SSamuel Thibault module_param_named(dev, synth_dectlk.dev_name, charp, 0444); 3082067fd92SSamuel Thibault module_param_named(start, synth_dectlk.startup, short, 0444); 3092067fd92SSamuel Thibault 3102067fd92SSamuel Thibault MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based)."); 3112067fd92SSamuel Thibault MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer."); 3122067fd92SSamuel Thibault MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded."); 3132067fd92SSamuel Thibault 3142067fd92SSamuel Thibault module_spk_synth(synth_dectlk); 3152067fd92SSamuel Thibault 3162067fd92SSamuel Thibault MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>"); 3172067fd92SSamuel Thibault MODULE_AUTHOR("David Borowski"); 3182067fd92SSamuel Thibault MODULE_DESCRIPTION("Speakup support for DECtalk Express synthesizers"); 3192067fd92SSamuel Thibault MODULE_LICENSE("GPL"); 3202067fd92SSamuel Thibault MODULE_VERSION(DRV_VERSION); 3212067fd92SSamuel Thibault 322