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