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