speakup_dtlk.c (10945B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * originally written by: Kirk Reiser <kirk@braille.uwo.ca> 4 * this version considerably modified by David Borowski, david575@rogers.com 5 * 6 * Copyright (C) 1998-99 Kirk Reiser. 7 * Copyright (C) 2003 David Borowski. 8 * 9 * specifically written as a driver for the speakup screenreview 10 * package it's not a general device driver. 11 * This driver is for the RC Systems DoubleTalk PC internal synthesizer. 12 */ 13#include <linux/jiffies.h> 14#include <linux/sched.h> 15#include <linux/timer.h> 16#include <linux/kthread.h> 17 18#include "spk_priv.h" 19#include "serialio.h" 20#include "speakup_dtlk.h" /* local header file for DoubleTalk values */ 21#include "speakup.h" 22 23#define DRV_VERSION "2.10" 24#define PROCSPEECH 0x00 25 26static int synth_probe(struct spk_synth *synth); 27static void dtlk_release(struct spk_synth *synth); 28static const char *synth_immediate(struct spk_synth *synth, const char *buf); 29static void do_catch_up(struct spk_synth *synth); 30static void synth_flush(struct spk_synth *synth); 31 32static int synth_lpc; 33static int port_forced; 34static unsigned int synth_portlist[] = { 35 0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0 36}; 37 38static u_char synth_status; 39 40static struct var_t vars[] = { 41 { CAPS_START, .u.s = {"\x01+35p" } }, 42 { CAPS_STOP, .u.s = {"\x01-35p" } }, 43 { RATE, .u.n = {"\x01%ds", 8, 0, 9, 0, 0, NULL } }, 44 { PITCH, .u.n = {"\x01%dp", 50, 0, 99, 0, 0, NULL } }, 45 { VOL, .u.n = {"\x01%dv", 5, 0, 9, 0, 0, NULL } }, 46 { TONE, .u.n = {"\x01%dx", 1, 0, 2, 0, 0, NULL } }, 47 { PUNCT, .u.n = {"\x01%db", 7, 0, 15, 0, 0, NULL } }, 48 { VOICE, .u.n = {"\x01%do", 0, 0, 7, 0, 0, NULL } }, 49 { FREQUENCY, .u.n = {"\x01%df", 5, 0, 9, 0, 0, NULL } }, 50 { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, 51 V_LAST_VAR 52}; 53 54/* 55 * These attributes will appear in /sys/accessibility/speakup/dtlk. 56 */ 57static struct kobj_attribute caps_start_attribute = 58 __ATTR(caps_start, 0644, spk_var_show, spk_var_store); 59static struct kobj_attribute caps_stop_attribute = 60 __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); 61static struct kobj_attribute freq_attribute = 62 __ATTR(freq, 0644, spk_var_show, spk_var_store); 63static struct kobj_attribute pitch_attribute = 64 __ATTR(pitch, 0644, spk_var_show, spk_var_store); 65static struct kobj_attribute punct_attribute = 66 __ATTR(punct, 0644, spk_var_show, spk_var_store); 67static struct kobj_attribute rate_attribute = 68 __ATTR(rate, 0644, spk_var_show, spk_var_store); 69static struct kobj_attribute tone_attribute = 70 __ATTR(tone, 0644, spk_var_show, spk_var_store); 71static struct kobj_attribute voice_attribute = 72 __ATTR(voice, 0644, spk_var_show, spk_var_store); 73static struct kobj_attribute vol_attribute = 74 __ATTR(vol, 0644, spk_var_show, spk_var_store); 75 76static struct kobj_attribute delay_time_attribute = 77 __ATTR(delay_time, 0644, spk_var_show, spk_var_store); 78static struct kobj_attribute direct_attribute = 79 __ATTR(direct, 0644, spk_var_show, spk_var_store); 80static struct kobj_attribute full_time_attribute = 81 __ATTR(full_time, 0644, spk_var_show, spk_var_store); 82static struct kobj_attribute jiffy_delta_attribute = 83 __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); 84static struct kobj_attribute trigger_time_attribute = 85 __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); 86 87/* 88 * Create a group of attributes so that we can create and destroy them all 89 * at once. 90 */ 91static struct attribute *synth_attrs[] = { 92 &caps_start_attribute.attr, 93 &caps_stop_attribute.attr, 94 &freq_attribute.attr, 95 &pitch_attribute.attr, 96 &punct_attribute.attr, 97 &rate_attribute.attr, 98 &tone_attribute.attr, 99 &voice_attribute.attr, 100 &vol_attribute.attr, 101 &delay_time_attribute.attr, 102 &direct_attribute.attr, 103 &full_time_attribute.attr, 104 &jiffy_delta_attribute.attr, 105 &trigger_time_attribute.attr, 106 NULL, /* need to NULL terminate the list of attributes */ 107}; 108 109static struct spk_synth synth_dtlk = { 110 .name = "dtlk", 111 .version = DRV_VERSION, 112 .long_name = "DoubleTalk PC", 113 .init = "\x01@\x01\x31y", 114 .procspeech = PROCSPEECH, 115 .clear = SYNTH_CLEAR, 116 .delay = 500, 117 .trigger = 30, 118 .jiffies = 50, 119 .full = 1000, 120 .startup = SYNTH_START, 121 .checkval = SYNTH_CHECK, 122 .vars = vars, 123 .io_ops = &spk_serial_io_ops, 124 .probe = synth_probe, 125 .release = dtlk_release, 126 .synth_immediate = synth_immediate, 127 .catch_up = do_catch_up, 128 .flush = synth_flush, 129 .is_alive = spk_synth_is_alive_nop, 130 .synth_adjust = NULL, 131 .read_buff_add = NULL, 132 .get_index = spk_synth_get_index, 133 .indexing = { 134 .command = "\x01%di", 135 .lowindex = 1, 136 .highindex = 5, 137 .currindex = 1, 138 }, 139 .attributes = { 140 .attrs = synth_attrs, 141 .name = "dtlk", 142 }, 143}; 144 145static inline bool synth_readable(void) 146{ 147 synth_status = inb_p(speakup_info.port_tts + UART_RX); 148 return (synth_status & TTS_READABLE) != 0; 149} 150 151static inline bool synth_writable(void) 152{ 153 synth_status = inb_p(speakup_info.port_tts + UART_RX); 154 return (synth_status & TTS_WRITABLE) != 0; 155} 156 157static inline bool synth_full(void) 158{ 159 synth_status = inb_p(speakup_info.port_tts + UART_RX); 160 return (synth_status & TTS_ALMOST_FULL) != 0; 161} 162 163static void spk_out(const char ch) 164{ 165 int timeout = SPK_XMITR_TIMEOUT; 166 167 while (!synth_writable()) { 168 if (!--timeout) 169 break; 170 udelay(1); 171 } 172 outb_p(ch, speakup_info.port_tts); 173 timeout = SPK_XMITR_TIMEOUT; 174 while (synth_writable()) { 175 if (!--timeout) 176 break; 177 udelay(1); 178 } 179} 180 181static void do_catch_up(struct spk_synth *synth) 182{ 183 u_char ch; 184 unsigned long flags; 185 unsigned long jiff_max; 186 struct var_t *jiffy_delta; 187 struct var_t *delay_time; 188 int jiffy_delta_val; 189 int delay_time_val; 190 191 jiffy_delta = spk_get_var(JIFFY); 192 delay_time = spk_get_var(DELAY); 193 spin_lock_irqsave(&speakup_info.spinlock, flags); 194 jiffy_delta_val = jiffy_delta->u.n.value; 195 spin_unlock_irqrestore(&speakup_info.spinlock, flags); 196 jiff_max = jiffies + jiffy_delta_val; 197 while (!kthread_should_stop()) { 198 spin_lock_irqsave(&speakup_info.spinlock, flags); 199 if (speakup_info.flushing) { 200 speakup_info.flushing = 0; 201 spin_unlock_irqrestore(&speakup_info.spinlock, flags); 202 synth->flush(synth); 203 continue; 204 } 205 synth_buffer_skip_nonlatin1(); 206 if (synth_buffer_empty()) { 207 spin_unlock_irqrestore(&speakup_info.spinlock, flags); 208 break; 209 } 210 set_current_state(TASK_INTERRUPTIBLE); 211 delay_time_val = delay_time->u.n.value; 212 spin_unlock_irqrestore(&speakup_info.spinlock, flags); 213 if (synth_full()) { 214 schedule_timeout(msecs_to_jiffies(delay_time_val)); 215 continue; 216 } 217 set_current_state(TASK_RUNNING); 218 spin_lock_irqsave(&speakup_info.spinlock, flags); 219 ch = synth_buffer_getc(); 220 spin_unlock_irqrestore(&speakup_info.spinlock, flags); 221 if (ch == '\n') 222 ch = PROCSPEECH; 223 spk_out(ch); 224 if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) { 225 spk_out(PROCSPEECH); 226 spin_lock_irqsave(&speakup_info.spinlock, flags); 227 delay_time_val = delay_time->u.n.value; 228 jiffy_delta_val = jiffy_delta->u.n.value; 229 spin_unlock_irqrestore(&speakup_info.spinlock, flags); 230 schedule_timeout(msecs_to_jiffies(delay_time_val)); 231 jiff_max = jiffies + jiffy_delta_val; 232 } 233 } 234 spk_out(PROCSPEECH); 235} 236 237static const char *synth_immediate(struct spk_synth *synth, const char *buf) 238{ 239 u_char ch; 240 241 while ((ch = (u_char)*buf)) { 242 if (synth_full()) 243 return buf; 244 if (ch == '\n') 245 ch = PROCSPEECH; 246 spk_out(ch); 247 buf++; 248 } 249 return NULL; 250} 251 252static void synth_flush(struct spk_synth *synth) 253{ 254 outb_p(SYNTH_CLEAR, speakup_info.port_tts); 255 while (synth_writable()) 256 cpu_relax(); 257} 258 259static char synth_read_tts(void) 260{ 261 u_char ch; 262 263 while (!synth_readable()) 264 cpu_relax(); 265 ch = synth_status & 0x7f; 266 outb_p(ch, speakup_info.port_tts); 267 while (synth_readable()) 268 cpu_relax(); 269 return (char)ch; 270} 271 272/* interrogate the DoubleTalk PC and return its settings */ 273static struct synth_settings *synth_interrogate(struct spk_synth *synth) 274{ 275 u_char *t; 276 static char buf[sizeof(struct synth_settings) + 1]; 277 int total, i; 278 static struct synth_settings status; 279 280 synth_immediate(synth, "\x18\x01?"); 281 for (total = 0, i = 0; i < 50; i++) { 282 buf[total] = synth_read_tts(); 283 if (total > 2 && buf[total] == 0x7f) 284 break; 285 if (total < sizeof(struct synth_settings)) 286 total++; 287 } 288 t = buf; 289 /* serial number is little endian */ 290 status.serial_number = t[0] + t[1] * 256; 291 t += 2; 292 for (i = 0; *t != '\r'; t++) { 293 status.rom_version[i] = *t; 294 if (i < sizeof(status.rom_version) - 1) 295 i++; 296 } 297 status.rom_version[i] = 0; 298 t++; 299 status.mode = *t++; 300 status.punc_level = *t++; 301 status.formant_freq = *t++; 302 status.pitch = *t++; 303 status.speed = *t++; 304 status.volume = *t++; 305 status.tone = *t++; 306 status.expression = *t++; 307 status.ext_dict_loaded = *t++; 308 status.ext_dict_status = *t++; 309 status.free_ram = *t++; 310 status.articulation = *t++; 311 status.reverb = *t++; 312 status.eob = *t++; 313 return &status; 314} 315 316static int synth_probe(struct spk_synth *synth) 317{ 318 unsigned int port_val = 0; 319 int i; 320 struct synth_settings *sp; 321 322 pr_info("Probing for DoubleTalk.\n"); 323 if (port_forced) { 324 speakup_info.port_tts = port_forced; 325 pr_info("probe forced to %x by kernel command line\n", 326 speakup_info.port_tts); 327 if ((port_forced & 0xf) != 0xf) 328 pr_info("warning: port base should probably end with f\n"); 329 if (synth_request_region(speakup_info.port_tts - 1, 330 SYNTH_IO_EXTENT)) { 331 pr_warn("sorry, port already reserved\n"); 332 return -EBUSY; 333 } 334 port_val = inw(speakup_info.port_tts - 1); 335 synth_lpc = speakup_info.port_tts - 1; 336 } else { 337 for (i = 0; synth_portlist[i]; i++) { 338 if (synth_request_region(synth_portlist[i], 339 SYNTH_IO_EXTENT)) 340 continue; 341 port_val = inw(synth_portlist[i]) & 0xfbff; 342 if (port_val == 0x107f) { 343 synth_lpc = synth_portlist[i]; 344 speakup_info.port_tts = synth_lpc + 1; 345 break; 346 } 347 synth_release_region(synth_portlist[i], 348 SYNTH_IO_EXTENT); 349 } 350 } 351 port_val &= 0xfbff; 352 if (port_val != 0x107f) { 353 pr_info("DoubleTalk PC: not found\n"); 354 if (synth_lpc) 355 synth_release_region(synth_lpc, SYNTH_IO_EXTENT); 356 return -ENODEV; 357 } 358 while (inw_p(synth_lpc) != 0x147f) 359 cpu_relax(); /* wait until it's ready */ 360 sp = synth_interrogate(synth); 361 pr_info("%s: %03x-%03x, ROM ver %s, s/n %u, driver: %s\n", 362 synth->long_name, synth_lpc, synth_lpc + SYNTH_IO_EXTENT - 1, 363 sp->rom_version, sp->serial_number, synth->version); 364 synth->alive = 1; 365 return 0; 366} 367 368static void dtlk_release(struct spk_synth *synth) 369{ 370 spk_stop_serial_interrupt(); 371 if (speakup_info.port_tts) 372 synth_release_region(speakup_info.port_tts - 1, 373 SYNTH_IO_EXTENT); 374 speakup_info.port_tts = 0; 375} 376 377module_param_hw_named(port, port_forced, int, ioport, 0444); 378module_param_named(start, synth_dtlk.startup, short, 0444); 379 380MODULE_PARM_DESC(port, "Set the port for the synthesizer (override probing)."); 381MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded."); 382 383module_spk_synth(synth_dtlk); 384 385MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>"); 386MODULE_AUTHOR("David Borowski"); 387MODULE_DESCRIPTION("Speakup support for DoubleTalk PC synthesizers"); 388MODULE_LICENSE("GPL"); 389MODULE_VERSION(DRV_VERSION); 390