18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * written by David Borowski 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2003 David Borowski. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * specificly written as a driver for the speakup screenreview 88c2ecf20Sopenharmony_ci * package it's not a general device driver. 98c2ecf20Sopenharmony_ci * This driver is for the Keynote Gold internal synthesizer. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 128c2ecf20Sopenharmony_ci#include <linux/sched.h> 138c2ecf20Sopenharmony_ci#include <linux/timer.h> 148c2ecf20Sopenharmony_ci#include <linux/kthread.h> 158c2ecf20Sopenharmony_ci#include <linux/serial_reg.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include "spk_priv.h" 188c2ecf20Sopenharmony_ci#include "speakup.h" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define DRV_VERSION "2.10" 218c2ecf20Sopenharmony_ci#define SYNTH_IO_EXTENT 0x04 228c2ecf20Sopenharmony_ci#define SWAIT udelay(70) 238c2ecf20Sopenharmony_ci#define PROCSPEECH 0x1f 248c2ecf20Sopenharmony_ci#define SYNTH_CLEAR 0x03 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic int synth_probe(struct spk_synth *synth); 278c2ecf20Sopenharmony_cistatic void keynote_release(void); 288c2ecf20Sopenharmony_cistatic const char *synth_immediate(struct spk_synth *synth, const char *buf); 298c2ecf20Sopenharmony_cistatic void do_catch_up(struct spk_synth *synth); 308c2ecf20Sopenharmony_cistatic void synth_flush(struct spk_synth *synth); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic int synth_port; 338c2ecf20Sopenharmony_cistatic int port_forced; 348c2ecf20Sopenharmony_cistatic unsigned int synth_portlist[] = { 0x2a8, 0 }; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic struct var_t vars[] = { 378c2ecf20Sopenharmony_ci { CAPS_START, .u.s = {"[f130]" } }, 388c2ecf20Sopenharmony_ci { CAPS_STOP, .u.s = {"[f90]" } }, 398c2ecf20Sopenharmony_ci { RATE, .u.n = {"\04%c ", 8, 0, 10, 81, -8, NULL } }, 408c2ecf20Sopenharmony_ci { PITCH, .u.n = {"[f%d]", 5, 0, 9, 40, 10, NULL } }, 418c2ecf20Sopenharmony_ci { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, 428c2ecf20Sopenharmony_ci V_LAST_VAR 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* 468c2ecf20Sopenharmony_ci * These attributes will appear in /sys/accessibility/speakup/keypc. 478c2ecf20Sopenharmony_ci */ 488c2ecf20Sopenharmony_cistatic struct kobj_attribute caps_start_attribute = 498c2ecf20Sopenharmony_ci __ATTR(caps_start, 0644, spk_var_show, spk_var_store); 508c2ecf20Sopenharmony_cistatic struct kobj_attribute caps_stop_attribute = 518c2ecf20Sopenharmony_ci __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); 528c2ecf20Sopenharmony_cistatic struct kobj_attribute pitch_attribute = 538c2ecf20Sopenharmony_ci __ATTR(pitch, 0644, spk_var_show, spk_var_store); 548c2ecf20Sopenharmony_cistatic struct kobj_attribute rate_attribute = 558c2ecf20Sopenharmony_ci __ATTR(rate, 0644, spk_var_show, spk_var_store); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic struct kobj_attribute delay_time_attribute = 588c2ecf20Sopenharmony_ci __ATTR(delay_time, 0644, spk_var_show, spk_var_store); 598c2ecf20Sopenharmony_cistatic struct kobj_attribute direct_attribute = 608c2ecf20Sopenharmony_ci __ATTR(direct, 0644, spk_var_show, spk_var_store); 618c2ecf20Sopenharmony_cistatic struct kobj_attribute full_time_attribute = 628c2ecf20Sopenharmony_ci __ATTR(full_time, 0644, spk_var_show, spk_var_store); 638c2ecf20Sopenharmony_cistatic struct kobj_attribute jiffy_delta_attribute = 648c2ecf20Sopenharmony_ci __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); 658c2ecf20Sopenharmony_cistatic struct kobj_attribute trigger_time_attribute = 668c2ecf20Sopenharmony_ci __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* 698c2ecf20Sopenharmony_ci * Create a group of attributes so that we can create and destroy them all 708c2ecf20Sopenharmony_ci * at once. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_cistatic struct attribute *synth_attrs[] = { 738c2ecf20Sopenharmony_ci &caps_start_attribute.attr, 748c2ecf20Sopenharmony_ci &caps_stop_attribute.attr, 758c2ecf20Sopenharmony_ci &pitch_attribute.attr, 768c2ecf20Sopenharmony_ci &rate_attribute.attr, 778c2ecf20Sopenharmony_ci &delay_time_attribute.attr, 788c2ecf20Sopenharmony_ci &direct_attribute.attr, 798c2ecf20Sopenharmony_ci &full_time_attribute.attr, 808c2ecf20Sopenharmony_ci &jiffy_delta_attribute.attr, 818c2ecf20Sopenharmony_ci &trigger_time_attribute.attr, 828c2ecf20Sopenharmony_ci NULL, /* need to NULL terminate the list of attributes */ 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic struct spk_synth synth_keypc = { 868c2ecf20Sopenharmony_ci .name = "keypc", 878c2ecf20Sopenharmony_ci .version = DRV_VERSION, 888c2ecf20Sopenharmony_ci .long_name = "Keynote PC", 898c2ecf20Sopenharmony_ci .init = "[t][n7,1][n8,0]", 908c2ecf20Sopenharmony_ci .procspeech = PROCSPEECH, 918c2ecf20Sopenharmony_ci .clear = SYNTH_CLEAR, 928c2ecf20Sopenharmony_ci .delay = 500, 938c2ecf20Sopenharmony_ci .trigger = 50, 948c2ecf20Sopenharmony_ci .jiffies = 50, 958c2ecf20Sopenharmony_ci .full = 1000, 968c2ecf20Sopenharmony_ci .startup = SYNTH_START, 978c2ecf20Sopenharmony_ci .checkval = SYNTH_CHECK, 988c2ecf20Sopenharmony_ci .vars = vars, 998c2ecf20Sopenharmony_ci .io_ops = &spk_serial_io_ops, 1008c2ecf20Sopenharmony_ci .probe = synth_probe, 1018c2ecf20Sopenharmony_ci .release = keynote_release, 1028c2ecf20Sopenharmony_ci .synth_immediate = synth_immediate, 1038c2ecf20Sopenharmony_ci .catch_up = do_catch_up, 1048c2ecf20Sopenharmony_ci .flush = synth_flush, 1058c2ecf20Sopenharmony_ci .is_alive = spk_synth_is_alive_nop, 1068c2ecf20Sopenharmony_ci .synth_adjust = NULL, 1078c2ecf20Sopenharmony_ci .read_buff_add = NULL, 1088c2ecf20Sopenharmony_ci .get_index = NULL, 1098c2ecf20Sopenharmony_ci .indexing = { 1108c2ecf20Sopenharmony_ci .command = NULL, 1118c2ecf20Sopenharmony_ci .lowindex = 0, 1128c2ecf20Sopenharmony_ci .highindex = 0, 1138c2ecf20Sopenharmony_ci .currindex = 0, 1148c2ecf20Sopenharmony_ci }, 1158c2ecf20Sopenharmony_ci .attributes = { 1168c2ecf20Sopenharmony_ci .attrs = synth_attrs, 1178c2ecf20Sopenharmony_ci .name = "keypc", 1188c2ecf20Sopenharmony_ci }, 1198c2ecf20Sopenharmony_ci}; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic inline bool synth_writable(void) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci return (inb_p(synth_port + UART_RX) & 0x10) != 0; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic inline bool synth_full(void) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci return (inb_p(synth_port + UART_RX) & 0x80) == 0; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic char *oops(void) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci int s1, s2, s3, s4; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci s1 = inb_p(synth_port); 1368c2ecf20Sopenharmony_ci s2 = inb_p(synth_port + 1); 1378c2ecf20Sopenharmony_ci s3 = inb_p(synth_port + 2); 1388c2ecf20Sopenharmony_ci s4 = inb_p(synth_port + 3); 1398c2ecf20Sopenharmony_ci pr_warn("synth timeout %d %d %d %d\n", s1, s2, s3, s4); 1408c2ecf20Sopenharmony_ci return NULL; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic const char *synth_immediate(struct spk_synth *synth, const char *buf) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci u_char ch; 1468c2ecf20Sopenharmony_ci int timeout; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci while ((ch = *buf)) { 1498c2ecf20Sopenharmony_ci if (ch == '\n') 1508c2ecf20Sopenharmony_ci ch = PROCSPEECH; 1518c2ecf20Sopenharmony_ci if (synth_full()) 1528c2ecf20Sopenharmony_ci return buf; 1538c2ecf20Sopenharmony_ci timeout = 1000; 1548c2ecf20Sopenharmony_ci while (synth_writable()) 1558c2ecf20Sopenharmony_ci if (--timeout <= 0) 1568c2ecf20Sopenharmony_ci return oops(); 1578c2ecf20Sopenharmony_ci outb_p(ch, synth_port); 1588c2ecf20Sopenharmony_ci udelay(70); 1598c2ecf20Sopenharmony_ci buf++; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci return NULL; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic void do_catch_up(struct spk_synth *synth) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci u_char ch; 1678c2ecf20Sopenharmony_ci int timeout; 1688c2ecf20Sopenharmony_ci unsigned long flags; 1698c2ecf20Sopenharmony_ci unsigned long jiff_max; 1708c2ecf20Sopenharmony_ci struct var_t *jiffy_delta; 1718c2ecf20Sopenharmony_ci struct var_t *delay_time; 1728c2ecf20Sopenharmony_ci struct var_t *full_time; 1738c2ecf20Sopenharmony_ci int delay_time_val; 1748c2ecf20Sopenharmony_ci int full_time_val; 1758c2ecf20Sopenharmony_ci int jiffy_delta_val; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci jiffy_delta = spk_get_var(JIFFY); 1788c2ecf20Sopenharmony_ci delay_time = spk_get_var(DELAY); 1798c2ecf20Sopenharmony_ci full_time = spk_get_var(FULL); 1808c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 1818c2ecf20Sopenharmony_ci jiffy_delta_val = jiffy_delta->u.n.value; 1828c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci jiff_max = jiffies + jiffy_delta_val; 1858c2ecf20Sopenharmony_ci while (!kthread_should_stop()) { 1868c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 1878c2ecf20Sopenharmony_ci if (speakup_info.flushing) { 1888c2ecf20Sopenharmony_ci speakup_info.flushing = 0; 1898c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1908c2ecf20Sopenharmony_ci synth->flush(synth); 1918c2ecf20Sopenharmony_ci continue; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci synth_buffer_skip_nonlatin1(); 1948c2ecf20Sopenharmony_ci if (synth_buffer_empty()) { 1958c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 1968c2ecf20Sopenharmony_ci break; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 1998c2ecf20Sopenharmony_ci full_time_val = full_time->u.n.value; 2008c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2018c2ecf20Sopenharmony_ci if (synth_full()) { 2028c2ecf20Sopenharmony_ci schedule_timeout(msecs_to_jiffies(full_time_val)); 2038c2ecf20Sopenharmony_ci continue; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci set_current_state(TASK_RUNNING); 2068c2ecf20Sopenharmony_ci timeout = 1000; 2078c2ecf20Sopenharmony_ci while (synth_writable()) 2088c2ecf20Sopenharmony_ci if (--timeout <= 0) 2098c2ecf20Sopenharmony_ci break; 2108c2ecf20Sopenharmony_ci if (timeout <= 0) { 2118c2ecf20Sopenharmony_ci oops(); 2128c2ecf20Sopenharmony_ci break; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2158c2ecf20Sopenharmony_ci ch = synth_buffer_getc(); 2168c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2178c2ecf20Sopenharmony_ci if (ch == '\n') 2188c2ecf20Sopenharmony_ci ch = PROCSPEECH; 2198c2ecf20Sopenharmony_ci outb_p(ch, synth_port); 2208c2ecf20Sopenharmony_ci SWAIT; 2218c2ecf20Sopenharmony_ci if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) { 2228c2ecf20Sopenharmony_ci timeout = 1000; 2238c2ecf20Sopenharmony_ci while (synth_writable()) 2248c2ecf20Sopenharmony_ci if (--timeout <= 0) 2258c2ecf20Sopenharmony_ci break; 2268c2ecf20Sopenharmony_ci if (timeout <= 0) { 2278c2ecf20Sopenharmony_ci oops(); 2288c2ecf20Sopenharmony_ci break; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci outb_p(PROCSPEECH, synth_port); 2318c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2328c2ecf20Sopenharmony_ci jiffy_delta_val = jiffy_delta->u.n.value; 2338c2ecf20Sopenharmony_ci delay_time_val = delay_time->u.n.value; 2348c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2358c2ecf20Sopenharmony_ci schedule_timeout(msecs_to_jiffies(delay_time_val)); 2368c2ecf20Sopenharmony_ci jiff_max = jiffies + jiffy_delta_val; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci } 2398c2ecf20Sopenharmony_ci timeout = 1000; 2408c2ecf20Sopenharmony_ci while (synth_writable()) 2418c2ecf20Sopenharmony_ci if (--timeout <= 0) 2428c2ecf20Sopenharmony_ci break; 2438c2ecf20Sopenharmony_ci if (timeout <= 0) 2448c2ecf20Sopenharmony_ci oops(); 2458c2ecf20Sopenharmony_ci else 2468c2ecf20Sopenharmony_ci outb_p(PROCSPEECH, synth_port); 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic void synth_flush(struct spk_synth *synth) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci outb_p(SYNTH_CLEAR, synth_port); 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int synth_probe(struct spk_synth *synth) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci unsigned int port_val = 0; 2578c2ecf20Sopenharmony_ci int i = 0; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci pr_info("Probing for %s.\n", synth->long_name); 2608c2ecf20Sopenharmony_ci if (port_forced) { 2618c2ecf20Sopenharmony_ci synth_port = port_forced; 2628c2ecf20Sopenharmony_ci pr_info("probe forced to %x by kernel command line\n", 2638c2ecf20Sopenharmony_ci synth_port); 2648c2ecf20Sopenharmony_ci if (synth_request_region(synth_port - 1, SYNTH_IO_EXTENT)) { 2658c2ecf20Sopenharmony_ci pr_warn("sorry, port already reserved\n"); 2668c2ecf20Sopenharmony_ci return -EBUSY; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci port_val = inb(synth_port); 2698c2ecf20Sopenharmony_ci } else { 2708c2ecf20Sopenharmony_ci for (i = 0; synth_portlist[i]; i++) { 2718c2ecf20Sopenharmony_ci if (synth_request_region(synth_portlist[i], 2728c2ecf20Sopenharmony_ci SYNTH_IO_EXTENT)) { 2738c2ecf20Sopenharmony_ci pr_warn 2748c2ecf20Sopenharmony_ci ("request_region: failed with 0x%x, %d\n", 2758c2ecf20Sopenharmony_ci synth_portlist[i], SYNTH_IO_EXTENT); 2768c2ecf20Sopenharmony_ci continue; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci port_val = inb(synth_portlist[i]); 2798c2ecf20Sopenharmony_ci if (port_val == 0x80) { 2808c2ecf20Sopenharmony_ci synth_port = synth_portlist[i]; 2818c2ecf20Sopenharmony_ci break; 2828c2ecf20Sopenharmony_ci } 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci if (port_val != 0x80) { 2868c2ecf20Sopenharmony_ci pr_info("%s: not found\n", synth->long_name); 2878c2ecf20Sopenharmony_ci synth_release_region(synth_port, SYNTH_IO_EXTENT); 2888c2ecf20Sopenharmony_ci synth_port = 0; 2898c2ecf20Sopenharmony_ci return -ENODEV; 2908c2ecf20Sopenharmony_ci } 2918c2ecf20Sopenharmony_ci pr_info("%s: %03x-%03x, driver version %s,\n", synth->long_name, 2928c2ecf20Sopenharmony_ci synth_port, synth_port + SYNTH_IO_EXTENT - 1, 2938c2ecf20Sopenharmony_ci synth->version); 2948c2ecf20Sopenharmony_ci synth->alive = 1; 2958c2ecf20Sopenharmony_ci return 0; 2968c2ecf20Sopenharmony_ci} 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_cistatic void keynote_release(void) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci spk_stop_serial_interrupt(); 3018c2ecf20Sopenharmony_ci if (synth_port) 3028c2ecf20Sopenharmony_ci synth_release_region(synth_port, SYNTH_IO_EXTENT); 3038c2ecf20Sopenharmony_ci synth_port = 0; 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cimodule_param_hw_named(port, port_forced, int, ioport, 0444); 3078c2ecf20Sopenharmony_cimodule_param_named(start, synth_keypc.startup, short, 0444); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ciMODULE_PARM_DESC(port, "Set the port for the synthesizer (override probing)."); 3108c2ecf20Sopenharmony_ciMODULE_PARM_DESC(start, "Start the synthesizer once it is loaded."); 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_cimodule_spk_synth(synth_keypc); 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Borowski"); 3158c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Speakup support for Keynote Gold PC synthesizers"); 3168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3178c2ecf20Sopenharmony_ciMODULE_VERSION(DRV_VERSION); 3188c2ecf20Sopenharmony_ci 319