18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * originally written by: Kirk Reiser <kirk@braille.uwo.ca> 48c2ecf20Sopenharmony_ci * this version considerably modified by David Borowski, david575@rogers.com 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright (C) 1998-99 Kirk Reiser. 78c2ecf20Sopenharmony_ci * Copyright (C) 2003 David Borowski. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * specificly written as a driver for the speakup screenreview 108c2ecf20Sopenharmony_ci * s not a general device driver. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci#include <linux/unistd.h> 138c2ecf20Sopenharmony_ci#include <linux/proc_fs.h> 148c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 158c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 168c2ecf20Sopenharmony_ci#include <linux/sched.h> 178c2ecf20Sopenharmony_ci#include <linux/timer.h> 188c2ecf20Sopenharmony_ci#include <linux/kthread.h> 198c2ecf20Sopenharmony_ci#include "speakup.h" 208c2ecf20Sopenharmony_ci#include "spk_priv.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define DRV_VERSION "2.20" 238c2ecf20Sopenharmony_ci#define SYNTH_CLEAR 0x03 248c2ecf20Sopenharmony_ci#define PROCSPEECH 0x0b 258c2ecf20Sopenharmony_cistatic int xoff; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic inline int synth_full(void) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci return xoff; 308c2ecf20Sopenharmony_ci} 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic void do_catch_up(struct spk_synth *synth); 338c2ecf20Sopenharmony_cistatic void synth_flush(struct spk_synth *synth); 348c2ecf20Sopenharmony_cistatic void read_buff_add(u_char c); 358c2ecf20Sopenharmony_cistatic unsigned char get_index(struct spk_synth *synth); 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic int in_escape; 388c2ecf20Sopenharmony_cistatic int is_flushing; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(flush_lock); 418c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(flush); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic struct var_t vars[] = { 448c2ecf20Sopenharmony_ci { CAPS_START, .u.s = {"[:dv ap 160] " } }, 458c2ecf20Sopenharmony_ci { CAPS_STOP, .u.s = {"[:dv ap 100 ] " } }, 468c2ecf20Sopenharmony_ci { RATE, .u.n = {"[:ra %d] ", 180, 75, 650, 0, 0, NULL } }, 478c2ecf20Sopenharmony_ci { PITCH, .u.n = {"[:dv ap %d] ", 122, 50, 350, 0, 0, NULL } }, 488c2ecf20Sopenharmony_ci { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } }, 498c2ecf20Sopenharmony_ci { VOL, .u.n = {"[:dv g5 %d] ", 86, 60, 86, 0, 0, NULL } }, 508c2ecf20Sopenharmony_ci { PUNCT, .u.n = {"[:pu %c] ", 0, 0, 2, 0, 0, "nsa" } }, 518c2ecf20Sopenharmony_ci { VOICE, .u.n = {"[:n%c] ", 0, 0, 9, 0, 0, "phfdburwkv" } }, 528c2ecf20Sopenharmony_ci { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, 538c2ecf20Sopenharmony_ci V_LAST_VAR 548c2ecf20Sopenharmony_ci}; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/* 578c2ecf20Sopenharmony_ci * These attributes will appear in /sys/accessibility/speakup/dectlk. 588c2ecf20Sopenharmony_ci */ 598c2ecf20Sopenharmony_cistatic struct kobj_attribute caps_start_attribute = 608c2ecf20Sopenharmony_ci __ATTR(caps_start, 0644, spk_var_show, spk_var_store); 618c2ecf20Sopenharmony_cistatic struct kobj_attribute caps_stop_attribute = 628c2ecf20Sopenharmony_ci __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); 638c2ecf20Sopenharmony_cistatic struct kobj_attribute pitch_attribute = 648c2ecf20Sopenharmony_ci __ATTR(pitch, 0644, spk_var_show, spk_var_store); 658c2ecf20Sopenharmony_cistatic struct kobj_attribute inflection_attribute = 668c2ecf20Sopenharmony_ci __ATTR(inflection, 0644, spk_var_show, spk_var_store); 678c2ecf20Sopenharmony_cistatic struct kobj_attribute punct_attribute = 688c2ecf20Sopenharmony_ci __ATTR(punct, 0644, spk_var_show, spk_var_store); 698c2ecf20Sopenharmony_cistatic struct kobj_attribute rate_attribute = 708c2ecf20Sopenharmony_ci __ATTR(rate, 0644, spk_var_show, spk_var_store); 718c2ecf20Sopenharmony_cistatic struct kobj_attribute voice_attribute = 728c2ecf20Sopenharmony_ci __ATTR(voice, 0644, spk_var_show, spk_var_store); 738c2ecf20Sopenharmony_cistatic struct kobj_attribute vol_attribute = 748c2ecf20Sopenharmony_ci __ATTR(vol, 0644, spk_var_show, spk_var_store); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic struct kobj_attribute delay_time_attribute = 778c2ecf20Sopenharmony_ci __ATTR(delay_time, 0644, spk_var_show, spk_var_store); 788c2ecf20Sopenharmony_cistatic struct kobj_attribute direct_attribute = 798c2ecf20Sopenharmony_ci __ATTR(direct, 0644, spk_var_show, spk_var_store); 808c2ecf20Sopenharmony_cistatic struct kobj_attribute full_time_attribute = 818c2ecf20Sopenharmony_ci __ATTR(full_time, 0644, spk_var_show, spk_var_store); 828c2ecf20Sopenharmony_cistatic struct kobj_attribute jiffy_delta_attribute = 838c2ecf20Sopenharmony_ci __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); 848c2ecf20Sopenharmony_cistatic struct kobj_attribute trigger_time_attribute = 858c2ecf20Sopenharmony_ci __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci/* 888c2ecf20Sopenharmony_ci * Create a group of attributes so that we can create and destroy them all 898c2ecf20Sopenharmony_ci * at once. 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_cistatic struct attribute *synth_attrs[] = { 928c2ecf20Sopenharmony_ci &caps_start_attribute.attr, 938c2ecf20Sopenharmony_ci &caps_stop_attribute.attr, 948c2ecf20Sopenharmony_ci &pitch_attribute.attr, 958c2ecf20Sopenharmony_ci &inflection_attribute.attr, 968c2ecf20Sopenharmony_ci &punct_attribute.attr, 978c2ecf20Sopenharmony_ci &rate_attribute.attr, 988c2ecf20Sopenharmony_ci &voice_attribute.attr, 998c2ecf20Sopenharmony_ci &vol_attribute.attr, 1008c2ecf20Sopenharmony_ci &delay_time_attribute.attr, 1018c2ecf20Sopenharmony_ci &direct_attribute.attr, 1028c2ecf20Sopenharmony_ci &full_time_attribute.attr, 1038c2ecf20Sopenharmony_ci &jiffy_delta_attribute.attr, 1048c2ecf20Sopenharmony_ci &trigger_time_attribute.attr, 1058c2ecf20Sopenharmony_ci NULL, /* need to NULL terminate the list of attributes */ 1068c2ecf20Sopenharmony_ci}; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic int ap_defaults[] = {122, 89, 155, 110, 208, 240, 200, 106, 306}; 1098c2ecf20Sopenharmony_cistatic int g5_defaults[] = {86, 81, 86, 84, 81, 80, 83, 83, 73}; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic struct spk_synth synth_dectlk = { 1128c2ecf20Sopenharmony_ci .name = "dectlk", 1138c2ecf20Sopenharmony_ci .version = DRV_VERSION, 1148c2ecf20Sopenharmony_ci .long_name = "Dectalk Express", 1158c2ecf20Sopenharmony_ci .init = "[:error sp :name paul :rate 180 :tsr off] ", 1168c2ecf20Sopenharmony_ci .procspeech = PROCSPEECH, 1178c2ecf20Sopenharmony_ci .clear = SYNTH_CLEAR, 1188c2ecf20Sopenharmony_ci .delay = 500, 1198c2ecf20Sopenharmony_ci .trigger = 50, 1208c2ecf20Sopenharmony_ci .jiffies = 50, 1218c2ecf20Sopenharmony_ci .full = 40000, 1228c2ecf20Sopenharmony_ci .dev_name = SYNTH_DEFAULT_DEV, 1238c2ecf20Sopenharmony_ci .startup = SYNTH_START, 1248c2ecf20Sopenharmony_ci .checkval = SYNTH_CHECK, 1258c2ecf20Sopenharmony_ci .vars = vars, 1268c2ecf20Sopenharmony_ci .default_pitch = ap_defaults, 1278c2ecf20Sopenharmony_ci .default_vol = g5_defaults, 1288c2ecf20Sopenharmony_ci .io_ops = &spk_ttyio_ops, 1298c2ecf20Sopenharmony_ci .probe = spk_ttyio_synth_probe, 1308c2ecf20Sopenharmony_ci .release = spk_ttyio_release, 1318c2ecf20Sopenharmony_ci .synth_immediate = spk_ttyio_synth_immediate, 1328c2ecf20Sopenharmony_ci .catch_up = do_catch_up, 1338c2ecf20Sopenharmony_ci .flush = synth_flush, 1348c2ecf20Sopenharmony_ci .is_alive = spk_synth_is_alive_restart, 1358c2ecf20Sopenharmony_ci .synth_adjust = NULL, 1368c2ecf20Sopenharmony_ci .read_buff_add = read_buff_add, 1378c2ecf20Sopenharmony_ci .get_index = get_index, 1388c2ecf20Sopenharmony_ci .indexing = { 1398c2ecf20Sopenharmony_ci .command = "[:in re %d ] ", 1408c2ecf20Sopenharmony_ci .lowindex = 1, 1418c2ecf20Sopenharmony_ci .highindex = 8, 1428c2ecf20Sopenharmony_ci .currindex = 1, 1438c2ecf20Sopenharmony_ci }, 1448c2ecf20Sopenharmony_ci .attributes = { 1458c2ecf20Sopenharmony_ci .attrs = synth_attrs, 1468c2ecf20Sopenharmony_ci .name = "dectlk", 1478c2ecf20Sopenharmony_ci }, 1488c2ecf20Sopenharmony_ci}; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic int is_indnum(u_char *ch) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci if ((*ch >= '0') && (*ch <= '9')) { 1538c2ecf20Sopenharmony_ci *ch = *ch - '0'; 1548c2ecf20Sopenharmony_ci return 1; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic u_char lastind; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic unsigned char get_index(struct spk_synth *synth) 1628c2ecf20Sopenharmony_ci{ 1638c2ecf20Sopenharmony_ci u_char rv; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci rv = lastind; 1668c2ecf20Sopenharmony_ci lastind = 0; 1678c2ecf20Sopenharmony_ci return rv; 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic void read_buff_add(u_char c) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci static int ind = -1; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (c == 0x01) { 1758c2ecf20Sopenharmony_ci unsigned long flags; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci spin_lock_irqsave(&flush_lock, flags); 1788c2ecf20Sopenharmony_ci is_flushing = 0; 1798c2ecf20Sopenharmony_ci wake_up_interruptible(&flush); 1808c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&flush_lock, flags); 1818c2ecf20Sopenharmony_ci } else if (c == 0x13) { 1828c2ecf20Sopenharmony_ci xoff = 1; 1838c2ecf20Sopenharmony_ci } else if (c == 0x11) { 1848c2ecf20Sopenharmony_ci xoff = 0; 1858c2ecf20Sopenharmony_ci } else if (is_indnum(&c)) { 1868c2ecf20Sopenharmony_ci if (ind == -1) 1878c2ecf20Sopenharmony_ci ind = c; 1888c2ecf20Sopenharmony_ci else 1898c2ecf20Sopenharmony_ci ind = ind * 10 + c; 1908c2ecf20Sopenharmony_ci } else if ((c > 31) && (c < 127)) { 1918c2ecf20Sopenharmony_ci if (ind != -1) 1928c2ecf20Sopenharmony_ci lastind = (u_char)ind; 1938c2ecf20Sopenharmony_ci ind = -1; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic void do_catch_up(struct spk_synth *synth) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci int synth_full_val = 0; 2008c2ecf20Sopenharmony_ci static u_char ch; 2018c2ecf20Sopenharmony_ci static u_char last = '\0'; 2028c2ecf20Sopenharmony_ci unsigned long flags; 2038c2ecf20Sopenharmony_ci unsigned long jiff_max; 2048c2ecf20Sopenharmony_ci unsigned long timeout = msecs_to_jiffies(4000); 2058c2ecf20Sopenharmony_ci DEFINE_WAIT(wait); 2068c2ecf20Sopenharmony_ci struct var_t *jiffy_delta; 2078c2ecf20Sopenharmony_ci struct var_t *delay_time; 2088c2ecf20Sopenharmony_ci int jiffy_delta_val; 2098c2ecf20Sopenharmony_ci int delay_time_val; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci jiffy_delta = spk_get_var(JIFFY); 2128c2ecf20Sopenharmony_ci delay_time = spk_get_var(DELAY); 2138c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2148c2ecf20Sopenharmony_ci jiffy_delta_val = jiffy_delta->u.n.value; 2158c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2168c2ecf20Sopenharmony_ci jiff_max = jiffies + jiffy_delta_val; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci while (!kthread_should_stop()) { 2198c2ecf20Sopenharmony_ci /* if no ctl-a in 4, send data anyway */ 2208c2ecf20Sopenharmony_ci spin_lock_irqsave(&flush_lock, flags); 2218c2ecf20Sopenharmony_ci while (is_flushing && timeout) { 2228c2ecf20Sopenharmony_ci prepare_to_wait(&flush, &wait, TASK_INTERRUPTIBLE); 2238c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&flush_lock, flags); 2248c2ecf20Sopenharmony_ci timeout = schedule_timeout(timeout); 2258c2ecf20Sopenharmony_ci spin_lock_irqsave(&flush_lock, flags); 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci finish_wait(&flush, &wait); 2288c2ecf20Sopenharmony_ci is_flushing = 0; 2298c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&flush_lock, flags); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2328c2ecf20Sopenharmony_ci if (speakup_info.flushing) { 2338c2ecf20Sopenharmony_ci speakup_info.flushing = 0; 2348c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2358c2ecf20Sopenharmony_ci synth->flush(synth); 2368c2ecf20Sopenharmony_ci continue; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci synth_buffer_skip_nonlatin1(); 2398c2ecf20Sopenharmony_ci if (synth_buffer_empty()) { 2408c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2418c2ecf20Sopenharmony_ci break; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci ch = synth_buffer_peek(); 2448c2ecf20Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 2458c2ecf20Sopenharmony_ci delay_time_val = delay_time->u.n.value; 2468c2ecf20Sopenharmony_ci synth_full_val = synth_full(); 2478c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2488c2ecf20Sopenharmony_ci if (ch == '\n') 2498c2ecf20Sopenharmony_ci ch = 0x0D; 2508c2ecf20Sopenharmony_ci if (synth_full_val || !synth->io_ops->synth_out(synth, ch)) { 2518c2ecf20Sopenharmony_ci schedule_timeout(msecs_to_jiffies(delay_time_val)); 2528c2ecf20Sopenharmony_ci continue; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci set_current_state(TASK_RUNNING); 2558c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2568c2ecf20Sopenharmony_ci synth_buffer_getc(); 2578c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2588c2ecf20Sopenharmony_ci if (ch == '[') { 2598c2ecf20Sopenharmony_ci in_escape = 1; 2608c2ecf20Sopenharmony_ci } else if (ch == ']') { 2618c2ecf20Sopenharmony_ci in_escape = 0; 2628c2ecf20Sopenharmony_ci } else if (ch <= SPACE) { 2638c2ecf20Sopenharmony_ci if (!in_escape && strchr(",.!?;:", last)) 2648c2ecf20Sopenharmony_ci synth->io_ops->synth_out(synth, PROCSPEECH); 2658c2ecf20Sopenharmony_ci if (time_after_eq(jiffies, jiff_max)) { 2668c2ecf20Sopenharmony_ci if (!in_escape) 2678c2ecf20Sopenharmony_ci synth->io_ops->synth_out(synth, 2688c2ecf20Sopenharmony_ci PROCSPEECH); 2698c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, 2708c2ecf20Sopenharmony_ci flags); 2718c2ecf20Sopenharmony_ci jiffy_delta_val = jiffy_delta->u.n.value; 2728c2ecf20Sopenharmony_ci delay_time_val = delay_time->u.n.value; 2738c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, 2748c2ecf20Sopenharmony_ci flags); 2758c2ecf20Sopenharmony_ci schedule_timeout(msecs_to_jiffies 2768c2ecf20Sopenharmony_ci (delay_time_val)); 2778c2ecf20Sopenharmony_ci jiff_max = jiffies + jiffy_delta_val; 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci last = ch; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci if (!in_escape) 2838c2ecf20Sopenharmony_ci synth->io_ops->synth_out(synth, PROCSPEECH); 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic void synth_flush(struct spk_synth *synth) 2878c2ecf20Sopenharmony_ci{ 2888c2ecf20Sopenharmony_ci if (in_escape) 2898c2ecf20Sopenharmony_ci /* if in command output ']' so we don't get an error */ 2908c2ecf20Sopenharmony_ci synth->io_ops->synth_out(synth, ']'); 2918c2ecf20Sopenharmony_ci in_escape = 0; 2928c2ecf20Sopenharmony_ci is_flushing = 1; 2938c2ecf20Sopenharmony_ci synth->io_ops->flush_buffer(); 2948c2ecf20Sopenharmony_ci synth->io_ops->synth_out(synth, SYNTH_CLEAR); 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cimodule_param_named(ser, synth_dectlk.ser, int, 0444); 2988c2ecf20Sopenharmony_cimodule_param_named(dev, synth_dectlk.dev_name, charp, 0444); 2998c2ecf20Sopenharmony_cimodule_param_named(start, synth_dectlk.startup, short, 0444); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ciMODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based)."); 3028c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer."); 3038c2ecf20Sopenharmony_ciMODULE_PARM_DESC(start, "Start the synthesizer once it is loaded."); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_cimodule_spk_synth(synth_dectlk); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ciMODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>"); 3088c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Borowski"); 3098c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Speakup support for DECtalk Express synthesizers"); 3108c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3118c2ecf20Sopenharmony_ciMODULE_VERSION(DRV_VERSION); 3128c2ecf20Sopenharmony_ci 313