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/jiffies.h> 132067fd92SSamuel Thibault #include <linux/sched.h> 142067fd92SSamuel Thibault #include <linux/timer.h> 152067fd92SSamuel Thibault #include <linux/kthread.h> 162067fd92SSamuel Thibault 172067fd92SSamuel Thibault #include "spk_priv.h" 182067fd92SSamuel Thibault #include "speakup.h" 192067fd92SSamuel Thibault 202067fd92SSamuel Thibault #define DRV_VERSION "2.14" 212067fd92SSamuel Thibault #define SYNTH_CLEAR 0x03 222067fd92SSamuel Thibault #define PROCSPEECH 0x0b 232067fd92SSamuel Thibault 242067fd92SSamuel Thibault static volatile unsigned char last_char; 252067fd92SSamuel Thibault 262067fd92SSamuel Thibault static void read_buff_add(u_char ch) 272067fd92SSamuel Thibault { 282067fd92SSamuel Thibault last_char = ch; 292067fd92SSamuel Thibault } 302067fd92SSamuel Thibault 312067fd92SSamuel Thibault static inline bool synth_full(void) 322067fd92SSamuel Thibault { 332067fd92SSamuel Thibault return last_char == 0x13; 342067fd92SSamuel Thibault } 352067fd92SSamuel Thibault 362067fd92SSamuel Thibault static void do_catch_up(struct spk_synth *synth); 372067fd92SSamuel Thibault static void synth_flush(struct spk_synth *synth); 382067fd92SSamuel Thibault 392067fd92SSamuel Thibault static int in_escape; 402067fd92SSamuel Thibault 412067fd92SSamuel Thibault static struct var_t vars[] = { 422067fd92SSamuel Thibault { CAPS_START, .u.s = {"[:dv ap 222]" } }, 432067fd92SSamuel Thibault { CAPS_STOP, .u.s = {"[:dv ap 100]" } }, 442067fd92SSamuel Thibault { RATE, .u.n = {"[:ra %d]", 7, 0, 9, 150, 25, NULL } }, 452067fd92SSamuel Thibault { PITCH, .u.n = {"[:dv ap %d]", 100, 0, 100, 0, 0, NULL } }, 462067fd92SSamuel Thibault { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } }, 472067fd92SSamuel Thibault { VOL, .u.n = {"[:dv gv %d]", 13, 0, 16, 0, 5, NULL } }, 482067fd92SSamuel Thibault { PUNCT, .u.n = {"[:pu %c]", 0, 0, 2, 0, 0, "nsa" } }, 492067fd92SSamuel Thibault { VOICE, .u.n = {"[:n%c]", 0, 0, 9, 0, 0, "phfdburwkv" } }, 502067fd92SSamuel Thibault { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, 512067fd92SSamuel Thibault V_LAST_VAR 522067fd92SSamuel Thibault }; 532067fd92SSamuel Thibault 542067fd92SSamuel Thibault /* 552067fd92SSamuel Thibault * These attributes will appear in /sys/accessibility/speakup/decext. 562067fd92SSamuel Thibault */ 572067fd92SSamuel Thibault static struct kobj_attribute caps_start_attribute = 582067fd92SSamuel Thibault __ATTR(caps_start, 0644, spk_var_show, spk_var_store); 592067fd92SSamuel Thibault static struct kobj_attribute caps_stop_attribute = 602067fd92SSamuel Thibault __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); 612067fd92SSamuel Thibault static struct kobj_attribute pitch_attribute = 622067fd92SSamuel Thibault __ATTR(pitch, 0644, spk_var_show, spk_var_store); 632067fd92SSamuel Thibault static struct kobj_attribute inflection_attribute = 642067fd92SSamuel Thibault __ATTR(inflection, 0644, spk_var_show, spk_var_store); 652067fd92SSamuel Thibault static struct kobj_attribute punct_attribute = 662067fd92SSamuel Thibault __ATTR(punct, 0644, spk_var_show, spk_var_store); 672067fd92SSamuel Thibault static struct kobj_attribute rate_attribute = 682067fd92SSamuel Thibault __ATTR(rate, 0644, spk_var_show, spk_var_store); 692067fd92SSamuel Thibault static struct kobj_attribute voice_attribute = 702067fd92SSamuel Thibault __ATTR(voice, 0644, spk_var_show, spk_var_store); 712067fd92SSamuel Thibault static struct kobj_attribute vol_attribute = 722067fd92SSamuel Thibault __ATTR(vol, 0644, spk_var_show, spk_var_store); 732067fd92SSamuel Thibault 742067fd92SSamuel Thibault static struct kobj_attribute delay_time_attribute = 752067fd92SSamuel Thibault __ATTR(delay_time, 0644, spk_var_show, spk_var_store); 762067fd92SSamuel Thibault static struct kobj_attribute direct_attribute = 772067fd92SSamuel Thibault __ATTR(direct, 0644, spk_var_show, spk_var_store); 782067fd92SSamuel Thibault static struct kobj_attribute full_time_attribute = 792067fd92SSamuel Thibault __ATTR(full_time, 0644, spk_var_show, spk_var_store); 802067fd92SSamuel Thibault static struct kobj_attribute jiffy_delta_attribute = 812067fd92SSamuel Thibault __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); 822067fd92SSamuel Thibault static struct kobj_attribute trigger_time_attribute = 832067fd92SSamuel Thibault __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); 842067fd92SSamuel Thibault 852067fd92SSamuel Thibault /* 862067fd92SSamuel Thibault * Create a group of attributes so that we can create and destroy them all 872067fd92SSamuel Thibault * at once. 882067fd92SSamuel Thibault */ 892067fd92SSamuel Thibault static struct attribute *synth_attrs[] = { 902067fd92SSamuel Thibault &caps_start_attribute.attr, 912067fd92SSamuel Thibault &caps_stop_attribute.attr, 922067fd92SSamuel Thibault &pitch_attribute.attr, 932067fd92SSamuel Thibault &inflection_attribute.attr, 942067fd92SSamuel Thibault &punct_attribute.attr, 952067fd92SSamuel Thibault &rate_attribute.attr, 962067fd92SSamuel Thibault &voice_attribute.attr, 972067fd92SSamuel Thibault &vol_attribute.attr, 982067fd92SSamuel Thibault &delay_time_attribute.attr, 992067fd92SSamuel Thibault &direct_attribute.attr, 1002067fd92SSamuel Thibault &full_time_attribute.attr, 1012067fd92SSamuel Thibault &jiffy_delta_attribute.attr, 1022067fd92SSamuel Thibault &trigger_time_attribute.attr, 1032067fd92SSamuel Thibault NULL, /* need to NULL terminate the list of attributes */ 1042067fd92SSamuel Thibault }; 1052067fd92SSamuel Thibault 1062067fd92SSamuel Thibault static struct spk_synth synth_decext = { 1072067fd92SSamuel Thibault .name = "decext", 1082067fd92SSamuel Thibault .version = DRV_VERSION, 1092067fd92SSamuel Thibault .long_name = "Dectalk External", 1102067fd92SSamuel Thibault .init = "[:pe -380]", 1112067fd92SSamuel Thibault .procspeech = PROCSPEECH, 1122067fd92SSamuel Thibault .clear = SYNTH_CLEAR, 1132067fd92SSamuel Thibault .delay = 500, 1142067fd92SSamuel Thibault .trigger = 50, 1152067fd92SSamuel Thibault .jiffies = 50, 1162067fd92SSamuel Thibault .full = 40000, 1172067fd92SSamuel Thibault .flags = SF_DEC, 1182067fd92SSamuel Thibault .dev_name = SYNTH_DEFAULT_DEV, 1192067fd92SSamuel Thibault .startup = SYNTH_START, 1202067fd92SSamuel Thibault .checkval = SYNTH_CHECK, 1212067fd92SSamuel Thibault .vars = vars, 1222067fd92SSamuel Thibault .io_ops = &spk_ttyio_ops, 1232067fd92SSamuel Thibault .probe = spk_ttyio_synth_probe, 1242067fd92SSamuel Thibault .release = spk_ttyio_release, 1252067fd92SSamuel Thibault .synth_immediate = spk_ttyio_synth_immediate, 1262067fd92SSamuel Thibault .catch_up = do_catch_up, 1272067fd92SSamuel Thibault .flush = synth_flush, 1282067fd92SSamuel Thibault .is_alive = spk_synth_is_alive_restart, 1292067fd92SSamuel Thibault .synth_adjust = NULL, 1302067fd92SSamuel Thibault .read_buff_add = read_buff_add, 1312067fd92SSamuel Thibault .get_index = NULL, 1322067fd92SSamuel Thibault .indexing = { 1332067fd92SSamuel Thibault .command = NULL, 1342067fd92SSamuel Thibault .lowindex = 0, 1352067fd92SSamuel Thibault .highindex = 0, 1362067fd92SSamuel Thibault .currindex = 0, 1372067fd92SSamuel Thibault }, 1382067fd92SSamuel Thibault .attributes = { 1392067fd92SSamuel Thibault .attrs = synth_attrs, 1402067fd92SSamuel Thibault .name = "decext", 1412067fd92SSamuel Thibault }, 1422067fd92SSamuel Thibault }; 1432067fd92SSamuel Thibault 1442067fd92SSamuel Thibault static void do_catch_up(struct spk_synth *synth) 1452067fd92SSamuel Thibault { 1462067fd92SSamuel Thibault u_char ch; 1472067fd92SSamuel Thibault static u_char last = '\0'; 1482067fd92SSamuel Thibault unsigned long flags; 1492067fd92SSamuel Thibault unsigned long jiff_max; 1502067fd92SSamuel Thibault struct var_t *jiffy_delta; 1512067fd92SSamuel Thibault struct var_t *delay_time; 1522067fd92SSamuel Thibault int jiffy_delta_val = 0; 1532067fd92SSamuel Thibault int delay_time_val = 0; 1542067fd92SSamuel Thibault 1552067fd92SSamuel Thibault jiffy_delta = spk_get_var(JIFFY); 1562067fd92SSamuel Thibault delay_time = spk_get_var(DELAY); 1572067fd92SSamuel Thibault 1582067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 1592067fd92SSamuel Thibault jiffy_delta_val = jiffy_delta->u.n.value; 1602067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1612067fd92SSamuel Thibault jiff_max = jiffies + jiffy_delta_val; 1622067fd92SSamuel Thibault 1632067fd92SSamuel Thibault while (!kthread_should_stop()) { 1642067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 1652067fd92SSamuel Thibault if (speakup_info.flushing) { 1662067fd92SSamuel Thibault speakup_info.flushing = 0; 1672067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1682067fd92SSamuel Thibault synth->flush(synth); 1692067fd92SSamuel Thibault continue; 1702067fd92SSamuel Thibault } 1712067fd92SSamuel Thibault synth_buffer_skip_nonlatin1(); 1722067fd92SSamuel Thibault if (synth_buffer_empty()) { 1732067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1742067fd92SSamuel Thibault break; 1752067fd92SSamuel Thibault } 1762067fd92SSamuel Thibault ch = synth_buffer_peek(); 1772067fd92SSamuel Thibault set_current_state(TASK_INTERRUPTIBLE); 1782067fd92SSamuel Thibault delay_time_val = delay_time->u.n.value; 1792067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1802067fd92SSamuel Thibault if (ch == '\n') 1812067fd92SSamuel Thibault ch = 0x0D; 1822067fd92SSamuel Thibault if (synth_full() || !synth->io_ops->synth_out(synth, ch)) { 1832067fd92SSamuel Thibault schedule_timeout(msecs_to_jiffies(delay_time_val)); 1842067fd92SSamuel Thibault continue; 1852067fd92SSamuel Thibault } 1862067fd92SSamuel Thibault set_current_state(TASK_RUNNING); 1872067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 1882067fd92SSamuel Thibault synth_buffer_getc(); 1892067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1902067fd92SSamuel Thibault if (ch == '[') { 1912067fd92SSamuel Thibault in_escape = 1; 1922067fd92SSamuel Thibault } else if (ch == ']') { 1932067fd92SSamuel Thibault in_escape = 0; 1942067fd92SSamuel Thibault } else if (ch <= SPACE) { 1952067fd92SSamuel Thibault if (!in_escape && strchr(",.!?;:", last)) 1962067fd92SSamuel Thibault synth->io_ops->synth_out(synth, PROCSPEECH); 1972067fd92SSamuel Thibault if (time_after_eq(jiffies, jiff_max)) { 1982067fd92SSamuel Thibault if (!in_escape) 1992067fd92SSamuel Thibault synth->io_ops->synth_out(synth, 2002067fd92SSamuel Thibault PROCSPEECH); 2012067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, 2022067fd92SSamuel Thibault flags); 2032067fd92SSamuel Thibault jiffy_delta_val = jiffy_delta->u.n.value; 2042067fd92SSamuel Thibault delay_time_val = delay_time->u.n.value; 2052067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, 2062067fd92SSamuel Thibault flags); 2072067fd92SSamuel Thibault schedule_timeout(msecs_to_jiffies 2082067fd92SSamuel Thibault (delay_time_val)); 2092067fd92SSamuel Thibault jiff_max = jiffies + jiffy_delta_val; 2102067fd92SSamuel Thibault } 2112067fd92SSamuel Thibault } 2122067fd92SSamuel Thibault last = ch; 2132067fd92SSamuel Thibault } 2142067fd92SSamuel Thibault if (!in_escape) 2152067fd92SSamuel Thibault synth->io_ops->synth_out(synth, PROCSPEECH); 2162067fd92SSamuel Thibault } 2172067fd92SSamuel Thibault 2182067fd92SSamuel Thibault static void synth_flush(struct spk_synth *synth) 2192067fd92SSamuel Thibault { 2202067fd92SSamuel Thibault in_escape = 0; 2211941ab1dSSamuel Thibault synth->io_ops->flush_buffer(synth); 2222067fd92SSamuel Thibault synth->synth_immediate(synth, "\033P;10z\033\\"); 2232067fd92SSamuel Thibault } 2242067fd92SSamuel Thibault 2252067fd92SSamuel Thibault module_param_named(ser, synth_decext.ser, int, 0444); 2262067fd92SSamuel Thibault module_param_named(dev, synth_decext.dev_name, charp, 0444); 2272067fd92SSamuel Thibault module_param_named(start, synth_decext.startup, short, 0444); 2282067fd92SSamuel Thibault 2292067fd92SSamuel Thibault MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based)."); 2302067fd92SSamuel Thibault MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer."); 2312067fd92SSamuel Thibault MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded."); 2322067fd92SSamuel Thibault 2332067fd92SSamuel Thibault module_spk_synth(synth_decext); 2342067fd92SSamuel Thibault 2352067fd92SSamuel Thibault MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>"); 2362067fd92SSamuel Thibault MODULE_AUTHOR("David Borowski"); 2372067fd92SSamuel Thibault MODULE_DESCRIPTION("Speakup support for DECtalk External synthesizers"); 2382067fd92SSamuel Thibault MODULE_LICENSE("GPL"); 2392067fd92SSamuel Thibault MODULE_VERSION(DRV_VERSION); 2402067fd92SSamuel Thibault 241