Path: blob/master/drivers/accessibility/speakup/serialio.c
26282 views
// SPDX-License-Identifier: GPL-2.01#include <linux/interrupt.h>2#include <linux/ioport.h>34#include "spk_types.h"5#include "speakup.h"6#include "spk_priv.h"7#include "serialio.h"89#include <linux/serial_core.h>10/* WARNING: Do not change this to <linux/serial.h> without testing that11* SERIAL_PORT_DFNS does get defined to the appropriate value.12*/13#include <asm/serial.h>1415#ifndef SERIAL_PORT_DFNS16#define SERIAL_PORT_DFNS17#endif1819static void start_serial_interrupt(int irq);2021static const struct old_serial_port rs_table[] = {22SERIAL_PORT_DFNS23};2425static const struct old_serial_port *serstate;26static int timeouts;2728static int spk_serial_out(struct spk_synth *in_synth, const char ch);29static void spk_serial_send_xchar(struct spk_synth *in_synth, char ch);30static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear);31static unsigned char spk_serial_in(struct spk_synth *in_synth);32static unsigned char spk_serial_in_nowait(struct spk_synth *in_synth);33static void spk_serial_flush_buffer(struct spk_synth *in_synth);34static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth);3536struct spk_io_ops spk_serial_io_ops = {37.synth_out = spk_serial_out,38.send_xchar = spk_serial_send_xchar,39.tiocmset = spk_serial_tiocmset,40.synth_in = spk_serial_in,41.synth_in_nowait = spk_serial_in_nowait,42.flush_buffer = spk_serial_flush_buffer,43.wait_for_xmitr = spk_serial_wait_for_xmitr,44};45EXPORT_SYMBOL_GPL(spk_serial_io_ops);4647const struct old_serial_port *spk_serial_init(int index)48{49int baud = 9600, quot = 0;50unsigned int cval = 0;51int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8;52const struct old_serial_port *ser;53int err;5455if (index >= ARRAY_SIZE(rs_table)) {56pr_info("no port info for ttyS%d\n", index);57return NULL;58}59ser = rs_table + index;6061/* Divisor, byte size and parity */62quot = ser->baud_base / baud;63cval = cflag & (CSIZE | CSTOPB);64#if defined(__powerpc__) || defined(__alpha__)65cval >>= 8;66#else /* !__powerpc__ && !__alpha__ */67cval >>= 4;68#endif /* !__powerpc__ && !__alpha__ */69if (cflag & PARENB)70cval |= UART_LCR_PARITY;71if (!(cflag & PARODD))72cval |= UART_LCR_EPAR;73if (synth_request_region(ser->port, 8)) {74/* try to take it back. */75pr_info("Ports not available, trying to steal them\n");76__release_region(&ioport_resource, ser->port, 8);77err = synth_request_region(ser->port, 8);78if (err) {79pr_warn("Unable to allocate port at %x, errno %i",80ser->port, err);81return NULL;82}83}8485/* Disable UART interrupts, set DTR and RTS high86* and set speed.87*/88outb(cval | UART_LCR_DLAB, ser->port + UART_LCR); /* set DLAB */89outb(quot & 0xff, ser->port + UART_DLL); /* LS of divisor */90outb(quot >> 8, ser->port + UART_DLM); /* MS of divisor */91outb(cval, ser->port + UART_LCR); /* reset DLAB */9293/* Turn off Interrupts */94outb(0, ser->port + UART_IER);95outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR);9697/* If we read 0xff from the LSR, there is no UART here. */98if (inb(ser->port + UART_LSR) == 0xff) {99synth_release_region(ser->port, 8);100serstate = NULL;101return NULL;102}103104mdelay(1);105speakup_info.port_tts = ser->port;106serstate = ser;107108start_serial_interrupt(ser->irq);109110return ser;111}112113static irqreturn_t synth_readbuf_handler(int irq, void *dev_id)114{115unsigned long flags;116int c;117118spin_lock_irqsave(&speakup_info.spinlock, flags);119while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) {120c = inb_p(speakup_info.port_tts + UART_RX);121synth->read_buff_add((u_char)c);122}123spin_unlock_irqrestore(&speakup_info.spinlock, flags);124return IRQ_HANDLED;125}126127static void start_serial_interrupt(int irq)128{129int rv;130131if (!synth->read_buff_add)132return;133134rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED,135"serial", (void *)synth_readbuf_handler);136137if (rv)138pr_err("Unable to request Speakup serial I R Q\n");139/* Set MCR */140outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2,141speakup_info.port_tts + UART_MCR);142/* Turn on Interrupts */143outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI,144speakup_info.port_tts + UART_IER);145inb(speakup_info.port_tts + UART_LSR);146inb(speakup_info.port_tts + UART_RX);147inb(speakup_info.port_tts + UART_IIR);148inb(speakup_info.port_tts + UART_MSR);149outb(1, speakup_info.port_tts + UART_FCR); /* Turn FIFO On */150}151152static void spk_serial_send_xchar(struct spk_synth *synth, char ch)153{154int timeout = SPK_XMITR_TIMEOUT;155156while (spk_serial_tx_busy()) {157if (!--timeout)158break;159udelay(1);160}161outb(ch, speakup_info.port_tts);162}163164static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear)165{166int old = inb(speakup_info.port_tts + UART_MCR);167168outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR);169}170171int spk_serial_synth_probe(struct spk_synth *synth)172{173const struct old_serial_port *ser;174int failed = 0;175176if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {177ser = spk_serial_init(synth->ser);178if (!ser) {179failed = -1;180} else {181outb_p(0, ser->port);182mdelay(1);183outb_p('\r', ser->port);184}185} else {186failed = -1;187pr_warn("ttyS%i is an invalid port\n", synth->ser);188}189if (failed) {190pr_info("%s: not found\n", synth->long_name);191return -ENODEV;192}193pr_info("%s: ttyS%i, Driver Version %s\n",194synth->long_name, synth->ser, synth->version);195synth->alive = 1;196return 0;197}198EXPORT_SYMBOL_GPL(spk_serial_synth_probe);199200void spk_stop_serial_interrupt(void)201{202if (speakup_info.port_tts == 0)203return;204205if (!synth->read_buff_add)206return;207208/* Turn off interrupts */209outb(0, speakup_info.port_tts + UART_IER);210/* Free IRQ */211free_irq(serstate->irq, (void *)synth_readbuf_handler);212}213EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt);214215static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth)216{217int tmout = SPK_XMITR_TIMEOUT;218219if ((in_synth->alive) && (timeouts >= NUM_DISABLE_TIMEOUTS)) {220pr_warn("%s: too many timeouts, deactivating speakup\n",221in_synth->long_name);222in_synth->alive = 0;223/* No synth any more, so nobody will restart TTYs, and we thus224* need to do it ourselves. Now that there is no synth we can225* let application flood anyway226*/227speakup_start_ttys();228timeouts = 0;229return 0;230}231while (spk_serial_tx_busy()) {232if (--tmout == 0) {233pr_warn("%s: timed out (tx busy)\n",234in_synth->long_name);235timeouts++;236return 0;237}238udelay(1);239}240tmout = SPK_CTS_TIMEOUT;241while (!((inb_p(speakup_info.port_tts + UART_MSR)) & UART_MSR_CTS)) {242/* CTS */243if (--tmout == 0) {244timeouts++;245return 0;246}247udelay(1);248}249timeouts = 0;250return 1;251}252253static unsigned char spk_serial_in(struct spk_synth *in_synth)254{255int tmout = SPK_SERIAL_TIMEOUT;256257while (!(inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR)) {258if (--tmout == 0) {259pr_warn("time out while waiting for input.\n");260return 0xff;261}262udelay(1);263}264return inb_p(speakup_info.port_tts + UART_RX);265}266267static unsigned char spk_serial_in_nowait(struct spk_synth *in_synth)268{269unsigned char lsr;270271lsr = inb_p(speakup_info.port_tts + UART_LSR);272if (!(lsr & UART_LSR_DR))273return 0;274return inb_p(speakup_info.port_tts + UART_RX);275}276277static void spk_serial_flush_buffer(struct spk_synth *in_synth)278{279/* TODO: flush the UART 16550 buffer */280}281282static int spk_serial_out(struct spk_synth *in_synth, const char ch)283{284if (in_synth->alive && spk_serial_wait_for_xmitr(in_synth)) {285outb_p(ch, speakup_info.port_tts);286return 1;287}288return 0;289}290291const char *spk_serial_synth_immediate(struct spk_synth *synth,292const char *buff)293{294u_char ch;295296while ((ch = *buff)) {297if (ch == '\n')298ch = synth->procspeech;299if (spk_serial_wait_for_xmitr(synth))300outb(ch, speakup_info.port_tts);301else302return buff;303buff++;304}305return NULL;306}307EXPORT_SYMBOL_GPL(spk_serial_synth_immediate);308309void spk_serial_release(struct spk_synth *synth)310{311spk_stop_serial_interrupt();312if (speakup_info.port_tts == 0)313return;314synth_release_region(speakup_info.port_tts, 8);315speakup_info.port_tts = 0;316}317EXPORT_SYMBOL_GPL(spk_serial_release);318319320