xref: /linux/drivers/accessibility/speakup/serialio.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
12067fd92SSamuel Thibault // SPDX-License-Identifier: GPL-2.0
22067fd92SSamuel Thibault #include <linux/interrupt.h>
32067fd92SSamuel Thibault #include <linux/ioport.h>
42067fd92SSamuel Thibault 
52067fd92SSamuel Thibault #include "spk_types.h"
62067fd92SSamuel Thibault #include "speakup.h"
72067fd92SSamuel Thibault #include "spk_priv.h"
82067fd92SSamuel Thibault #include "serialio.h"
92067fd92SSamuel Thibault 
102067fd92SSamuel Thibault #include <linux/serial_core.h>
112067fd92SSamuel Thibault /* WARNING:  Do not change this to <linux/serial.h> without testing that
122067fd92SSamuel Thibault  * SERIAL_PORT_DFNS does get defined to the appropriate value.
132067fd92SSamuel Thibault  */
142067fd92SSamuel Thibault #include <asm/serial.h>
152067fd92SSamuel Thibault 
162067fd92SSamuel Thibault #ifndef SERIAL_PORT_DFNS
172067fd92SSamuel Thibault #define SERIAL_PORT_DFNS
182067fd92SSamuel Thibault #endif
192067fd92SSamuel Thibault 
202067fd92SSamuel Thibault static void start_serial_interrupt(int irq);
212067fd92SSamuel Thibault 
222067fd92SSamuel Thibault static const struct old_serial_port rs_table[] = {
232067fd92SSamuel Thibault 	SERIAL_PORT_DFNS
242067fd92SSamuel Thibault };
252067fd92SSamuel Thibault 
262067fd92SSamuel Thibault static const struct old_serial_port *serstate;
272067fd92SSamuel Thibault static int timeouts;
282067fd92SSamuel Thibault 
292067fd92SSamuel Thibault static int spk_serial_out(struct spk_synth *in_synth, const char ch);
30*1941ab1dSSamuel Thibault static void spk_serial_send_xchar(struct spk_synth *in_synth, char ch);
31*1941ab1dSSamuel Thibault static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear);
32*1941ab1dSSamuel Thibault static unsigned char spk_serial_in(struct spk_synth *in_synth);
33*1941ab1dSSamuel Thibault static unsigned char spk_serial_in_nowait(struct spk_synth *in_synth);
34*1941ab1dSSamuel Thibault static void spk_serial_flush_buffer(struct spk_synth *in_synth);
352b86d9b8SSamuel Thibault static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth);
362067fd92SSamuel Thibault 
372067fd92SSamuel Thibault struct spk_io_ops spk_serial_io_ops = {
382067fd92SSamuel Thibault 	.synth_out = spk_serial_out,
392067fd92SSamuel Thibault 	.send_xchar = spk_serial_send_xchar,
402067fd92SSamuel Thibault 	.tiocmset = spk_serial_tiocmset,
412067fd92SSamuel Thibault 	.synth_in = spk_serial_in,
422067fd92SSamuel Thibault 	.synth_in_nowait = spk_serial_in_nowait,
432067fd92SSamuel Thibault 	.flush_buffer = spk_serial_flush_buffer,
442b86d9b8SSamuel Thibault 	.wait_for_xmitr = spk_serial_wait_for_xmitr,
452067fd92SSamuel Thibault };
462067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_serial_io_ops);
472067fd92SSamuel Thibault 
spk_serial_init(int index)482067fd92SSamuel Thibault const struct old_serial_port *spk_serial_init(int index)
492067fd92SSamuel Thibault {
502067fd92SSamuel Thibault 	int baud = 9600, quot = 0;
512067fd92SSamuel Thibault 	unsigned int cval = 0;
522067fd92SSamuel Thibault 	int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8;
532067fd92SSamuel Thibault 	const struct old_serial_port *ser;
542067fd92SSamuel Thibault 	int err;
552067fd92SSamuel Thibault 
562067fd92SSamuel Thibault 	if (index >= ARRAY_SIZE(rs_table)) {
572067fd92SSamuel Thibault 		pr_info("no port info for ttyS%d\n", index);
582067fd92SSamuel Thibault 		return NULL;
592067fd92SSamuel Thibault 	}
602067fd92SSamuel Thibault 	ser = rs_table + index;
612067fd92SSamuel Thibault 
622067fd92SSamuel Thibault 	/*	Divisor, byte size and parity */
632067fd92SSamuel Thibault 	quot = ser->baud_base / baud;
642067fd92SSamuel Thibault 	cval = cflag & (CSIZE | CSTOPB);
652067fd92SSamuel Thibault #if defined(__powerpc__) || defined(__alpha__)
662067fd92SSamuel Thibault 	cval >>= 8;
672067fd92SSamuel Thibault #else /* !__powerpc__ && !__alpha__ */
682067fd92SSamuel Thibault 	cval >>= 4;
692067fd92SSamuel Thibault #endif /* !__powerpc__ && !__alpha__ */
702067fd92SSamuel Thibault 	if (cflag & PARENB)
712067fd92SSamuel Thibault 		cval |= UART_LCR_PARITY;
722067fd92SSamuel Thibault 	if (!(cflag & PARODD))
732067fd92SSamuel Thibault 		cval |= UART_LCR_EPAR;
742067fd92SSamuel Thibault 	if (synth_request_region(ser->port, 8)) {
752067fd92SSamuel Thibault 		/* try to take it back. */
762067fd92SSamuel Thibault 		pr_info("Ports not available, trying to steal them\n");
772067fd92SSamuel Thibault 		__release_region(&ioport_resource, ser->port, 8);
782067fd92SSamuel Thibault 		err = synth_request_region(ser->port, 8);
792067fd92SSamuel Thibault 		if (err) {
802067fd92SSamuel Thibault 			pr_warn("Unable to allocate port at %x, errno %i",
812067fd92SSamuel Thibault 				ser->port, err);
822067fd92SSamuel Thibault 			return NULL;
832067fd92SSamuel Thibault 		}
842067fd92SSamuel Thibault 	}
852067fd92SSamuel Thibault 
862067fd92SSamuel Thibault 	/*	Disable UART interrupts, set DTR and RTS high
872067fd92SSamuel Thibault 	 *	and set speed.
882067fd92SSamuel Thibault 	 */
892067fd92SSamuel Thibault 	outb(cval | UART_LCR_DLAB, ser->port + UART_LCR);	/* set DLAB */
902067fd92SSamuel Thibault 	outb(quot & 0xff, ser->port + UART_DLL);	/* LS of divisor */
912067fd92SSamuel Thibault 	outb(quot >> 8, ser->port + UART_DLM);		/* MS of divisor */
922067fd92SSamuel Thibault 	outb(cval, ser->port + UART_LCR);		/* reset DLAB */
932067fd92SSamuel Thibault 
942067fd92SSamuel Thibault 	/* Turn off Interrupts */
952067fd92SSamuel Thibault 	outb(0, ser->port + UART_IER);
962067fd92SSamuel Thibault 	outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR);
972067fd92SSamuel Thibault 
982067fd92SSamuel Thibault 	/* If we read 0xff from the LSR, there is no UART here. */
992067fd92SSamuel Thibault 	if (inb(ser->port + UART_LSR) == 0xff) {
1002067fd92SSamuel Thibault 		synth_release_region(ser->port, 8);
1012067fd92SSamuel Thibault 		serstate = NULL;
1022067fd92SSamuel Thibault 		return NULL;
1032067fd92SSamuel Thibault 	}
1042067fd92SSamuel Thibault 
1052067fd92SSamuel Thibault 	mdelay(1);
1062067fd92SSamuel Thibault 	speakup_info.port_tts = ser->port;
1072067fd92SSamuel Thibault 	serstate = ser;
1082067fd92SSamuel Thibault 
1092067fd92SSamuel Thibault 	start_serial_interrupt(ser->irq);
1102067fd92SSamuel Thibault 
1112067fd92SSamuel Thibault 	return ser;
1122067fd92SSamuel Thibault }
1132067fd92SSamuel Thibault 
synth_readbuf_handler(int irq,void * dev_id)1142067fd92SSamuel Thibault static irqreturn_t synth_readbuf_handler(int irq, void *dev_id)
1152067fd92SSamuel Thibault {
1162067fd92SSamuel Thibault 	unsigned long flags;
1172067fd92SSamuel Thibault 	int c;
1182067fd92SSamuel Thibault 
1192067fd92SSamuel Thibault 	spin_lock_irqsave(&speakup_info.spinlock, flags);
1202067fd92SSamuel Thibault 	while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) {
1212067fd92SSamuel Thibault 		c = inb_p(speakup_info.port_tts + UART_RX);
1222067fd92SSamuel Thibault 		synth->read_buff_add((u_char)c);
1232067fd92SSamuel Thibault 	}
1242067fd92SSamuel Thibault 	spin_unlock_irqrestore(&speakup_info.spinlock, flags);
1252067fd92SSamuel Thibault 	return IRQ_HANDLED;
1262067fd92SSamuel Thibault }
1272067fd92SSamuel Thibault 
start_serial_interrupt(int irq)1282067fd92SSamuel Thibault static void start_serial_interrupt(int irq)
1292067fd92SSamuel Thibault {
1302067fd92SSamuel Thibault 	int rv;
1312067fd92SSamuel Thibault 
1322067fd92SSamuel Thibault 	if (!synth->read_buff_add)
1332067fd92SSamuel Thibault 		return;
1342067fd92SSamuel Thibault 
1352067fd92SSamuel Thibault 	rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED,
1362067fd92SSamuel Thibault 			 "serial", (void *)synth_readbuf_handler);
1372067fd92SSamuel Thibault 
1382067fd92SSamuel Thibault 	if (rv)
1392067fd92SSamuel Thibault 		pr_err("Unable to request Speakup serial I R Q\n");
1402067fd92SSamuel Thibault 	/* Set MCR */
1412067fd92SSamuel Thibault 	outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2,
1422067fd92SSamuel Thibault 	     speakup_info.port_tts + UART_MCR);
1432067fd92SSamuel Thibault 	/* Turn on Interrupts */
1442067fd92SSamuel Thibault 	outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI,
1452067fd92SSamuel Thibault 	     speakup_info.port_tts + UART_IER);
1462067fd92SSamuel Thibault 	inb(speakup_info.port_tts + UART_LSR);
1472067fd92SSamuel Thibault 	inb(speakup_info.port_tts + UART_RX);
1482067fd92SSamuel Thibault 	inb(speakup_info.port_tts + UART_IIR);
1492067fd92SSamuel Thibault 	inb(speakup_info.port_tts + UART_MSR);
1502067fd92SSamuel Thibault 	outb(1, speakup_info.port_tts + UART_FCR);	/* Turn FIFO On */
1512067fd92SSamuel Thibault }
1522067fd92SSamuel Thibault 
spk_serial_send_xchar(struct spk_synth * synth,char ch)153*1941ab1dSSamuel Thibault static void spk_serial_send_xchar(struct spk_synth *synth, char ch)
1542067fd92SSamuel Thibault {
1552067fd92SSamuel Thibault 	int timeout = SPK_XMITR_TIMEOUT;
1562067fd92SSamuel Thibault 
1572067fd92SSamuel Thibault 	while (spk_serial_tx_busy()) {
1582067fd92SSamuel Thibault 		if (!--timeout)
1592067fd92SSamuel Thibault 			break;
1602067fd92SSamuel Thibault 		udelay(1);
1612067fd92SSamuel Thibault 	}
1622067fd92SSamuel Thibault 	outb(ch, speakup_info.port_tts);
1632067fd92SSamuel Thibault }
1642067fd92SSamuel Thibault 
spk_serial_tiocmset(struct spk_synth * in_synth,unsigned int set,unsigned int clear)165*1941ab1dSSamuel Thibault static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear)
1662067fd92SSamuel Thibault {
1672067fd92SSamuel Thibault 	int old = inb(speakup_info.port_tts + UART_MCR);
1682067fd92SSamuel Thibault 
1692067fd92SSamuel Thibault 	outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR);
1702067fd92SSamuel Thibault }
1712067fd92SSamuel Thibault 
spk_serial_synth_probe(struct spk_synth * synth)1722067fd92SSamuel Thibault int spk_serial_synth_probe(struct spk_synth *synth)
1732067fd92SSamuel Thibault {
1742067fd92SSamuel Thibault 	const struct old_serial_port *ser;
1752067fd92SSamuel Thibault 	int failed = 0;
1762067fd92SSamuel Thibault 
1772067fd92SSamuel Thibault 	if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {
1782067fd92SSamuel Thibault 		ser = spk_serial_init(synth->ser);
1792067fd92SSamuel Thibault 		if (!ser) {
1802067fd92SSamuel Thibault 			failed = -1;
1812067fd92SSamuel Thibault 		} else {
1822067fd92SSamuel Thibault 			outb_p(0, ser->port);
1832067fd92SSamuel Thibault 			mdelay(1);
1842067fd92SSamuel Thibault 			outb_p('\r', ser->port);
1852067fd92SSamuel Thibault 		}
1862067fd92SSamuel Thibault 	} else {
1872067fd92SSamuel Thibault 		failed = -1;
1882067fd92SSamuel Thibault 		pr_warn("ttyS%i is an invalid port\n", synth->ser);
1892067fd92SSamuel Thibault 	}
1902067fd92SSamuel Thibault 	if (failed) {
1912067fd92SSamuel Thibault 		pr_info("%s: not found\n", synth->long_name);
1922067fd92SSamuel Thibault 		return -ENODEV;
1932067fd92SSamuel Thibault 	}
1942067fd92SSamuel Thibault 	pr_info("%s: ttyS%i, Driver Version %s\n",
1952067fd92SSamuel Thibault 		synth->long_name, synth->ser, synth->version);
1962067fd92SSamuel Thibault 	synth->alive = 1;
1972067fd92SSamuel Thibault 	return 0;
1982067fd92SSamuel Thibault }
1992067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_serial_synth_probe);
2002067fd92SSamuel Thibault 
spk_stop_serial_interrupt(void)2012067fd92SSamuel Thibault void spk_stop_serial_interrupt(void)
2022067fd92SSamuel Thibault {
2032067fd92SSamuel Thibault 	if (speakup_info.port_tts == 0)
2042067fd92SSamuel Thibault 		return;
2052067fd92SSamuel Thibault 
2062067fd92SSamuel Thibault 	if (!synth->read_buff_add)
2072067fd92SSamuel Thibault 		return;
2082067fd92SSamuel Thibault 
2092067fd92SSamuel Thibault 	/* Turn off interrupts */
2102067fd92SSamuel Thibault 	outb(0, speakup_info.port_tts + UART_IER);
2112067fd92SSamuel Thibault 	/* Free IRQ */
2122067fd92SSamuel Thibault 	free_irq(serstate->irq, (void *)synth_readbuf_handler);
2132067fd92SSamuel Thibault }
2142067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt);
2152067fd92SSamuel Thibault 
spk_serial_wait_for_xmitr(struct spk_synth * in_synth)2162b86d9b8SSamuel Thibault static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth)
2172067fd92SSamuel Thibault {
2182067fd92SSamuel Thibault 	int tmout = SPK_XMITR_TIMEOUT;
2192067fd92SSamuel Thibault 
2202067fd92SSamuel Thibault 	if ((in_synth->alive) && (timeouts >= NUM_DISABLE_TIMEOUTS)) {
2212067fd92SSamuel Thibault 		pr_warn("%s: too many timeouts, deactivating speakup\n",
2222067fd92SSamuel Thibault 			in_synth->long_name);
2232067fd92SSamuel Thibault 		in_synth->alive = 0;
2242067fd92SSamuel Thibault 		/* No synth any more, so nobody will restart TTYs, and we thus
2252067fd92SSamuel Thibault 		 * need to do it ourselves.  Now that there is no synth we can
2262067fd92SSamuel Thibault 		 * let application flood anyway
2272067fd92SSamuel Thibault 		 */
2282067fd92SSamuel Thibault 		speakup_start_ttys();
2292067fd92SSamuel Thibault 		timeouts = 0;
2302067fd92SSamuel Thibault 		return 0;
2312067fd92SSamuel Thibault 	}
2322067fd92SSamuel Thibault 	while (spk_serial_tx_busy()) {
2332067fd92SSamuel Thibault 		if (--tmout == 0) {
2342067fd92SSamuel Thibault 			pr_warn("%s: timed out (tx busy)\n",
2352067fd92SSamuel Thibault 				in_synth->long_name);
2362067fd92SSamuel Thibault 			timeouts++;
2372067fd92SSamuel Thibault 			return 0;
2382067fd92SSamuel Thibault 		}
2392067fd92SSamuel Thibault 		udelay(1);
2402067fd92SSamuel Thibault 	}
2412067fd92SSamuel Thibault 	tmout = SPK_CTS_TIMEOUT;
2422067fd92SSamuel Thibault 	while (!((inb_p(speakup_info.port_tts + UART_MSR)) & UART_MSR_CTS)) {
2432067fd92SSamuel Thibault 		/* CTS */
2442067fd92SSamuel Thibault 		if (--tmout == 0) {
2452067fd92SSamuel Thibault 			timeouts++;
2462067fd92SSamuel Thibault 			return 0;
2472067fd92SSamuel Thibault 		}
2482067fd92SSamuel Thibault 		udelay(1);
2492067fd92SSamuel Thibault 	}
2502067fd92SSamuel Thibault 	timeouts = 0;
2512067fd92SSamuel Thibault 	return 1;
2522067fd92SSamuel Thibault }
2532067fd92SSamuel Thibault 
spk_serial_in(struct spk_synth * in_synth)254*1941ab1dSSamuel Thibault static unsigned char spk_serial_in(struct spk_synth *in_synth)
2552067fd92SSamuel Thibault {
2562067fd92SSamuel Thibault 	int tmout = SPK_SERIAL_TIMEOUT;
2572067fd92SSamuel Thibault 
2582067fd92SSamuel Thibault 	while (!(inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR)) {
2592067fd92SSamuel Thibault 		if (--tmout == 0) {
2602067fd92SSamuel Thibault 			pr_warn("time out while waiting for input.\n");
2612067fd92SSamuel Thibault 			return 0xff;
2622067fd92SSamuel Thibault 		}
2632067fd92SSamuel Thibault 		udelay(1);
2642067fd92SSamuel Thibault 	}
2652067fd92SSamuel Thibault 	return inb_p(speakup_info.port_tts + UART_RX);
2662067fd92SSamuel Thibault }
2672067fd92SSamuel Thibault 
spk_serial_in_nowait(struct spk_synth * in_synth)268*1941ab1dSSamuel Thibault static unsigned char spk_serial_in_nowait(struct spk_synth *in_synth)
2692067fd92SSamuel Thibault {
2702067fd92SSamuel Thibault 	unsigned char lsr;
2712067fd92SSamuel Thibault 
2722067fd92SSamuel Thibault 	lsr = inb_p(speakup_info.port_tts + UART_LSR);
2732067fd92SSamuel Thibault 	if (!(lsr & UART_LSR_DR))
2742067fd92SSamuel Thibault 		return 0;
2752067fd92SSamuel Thibault 	return inb_p(speakup_info.port_tts + UART_RX);
2762067fd92SSamuel Thibault }
2772067fd92SSamuel Thibault 
spk_serial_flush_buffer(struct spk_synth * in_synth)278*1941ab1dSSamuel Thibault static void spk_serial_flush_buffer(struct spk_synth *in_synth)
2792067fd92SSamuel Thibault {
2802067fd92SSamuel Thibault 	/* TODO: flush the UART 16550 buffer */
2812067fd92SSamuel Thibault }
2822067fd92SSamuel Thibault 
spk_serial_out(struct spk_synth * in_synth,const char ch)2832067fd92SSamuel Thibault static int spk_serial_out(struct spk_synth *in_synth, const char ch)
2842067fd92SSamuel Thibault {
2852b86d9b8SSamuel Thibault 	if (in_synth->alive && spk_serial_wait_for_xmitr(in_synth)) {
2862067fd92SSamuel Thibault 		outb_p(ch, speakup_info.port_tts);
2872067fd92SSamuel Thibault 		return 1;
2882067fd92SSamuel Thibault 	}
2892067fd92SSamuel Thibault 	return 0;
2902067fd92SSamuel Thibault }
2912067fd92SSamuel Thibault 
spk_serial_synth_immediate(struct spk_synth * synth,const char * buff)2922067fd92SSamuel Thibault const char *spk_serial_synth_immediate(struct spk_synth *synth,
2932067fd92SSamuel Thibault 				       const char *buff)
2942067fd92SSamuel Thibault {
2952067fd92SSamuel Thibault 	u_char ch;
2962067fd92SSamuel Thibault 
2972067fd92SSamuel Thibault 	while ((ch = *buff)) {
2982067fd92SSamuel Thibault 		if (ch == '\n')
2992067fd92SSamuel Thibault 			ch = synth->procspeech;
3002b86d9b8SSamuel Thibault 		if (spk_serial_wait_for_xmitr(synth))
3012067fd92SSamuel Thibault 			outb(ch, speakup_info.port_tts);
3022067fd92SSamuel Thibault 		else
3032067fd92SSamuel Thibault 			return buff;
3042067fd92SSamuel Thibault 		buff++;
3052067fd92SSamuel Thibault 	}
3062067fd92SSamuel Thibault 	return NULL;
3072067fd92SSamuel Thibault }
3082067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_serial_synth_immediate);
3092067fd92SSamuel Thibault 
spk_serial_release(struct spk_synth * synth)310*1941ab1dSSamuel Thibault void spk_serial_release(struct spk_synth *synth)
3112067fd92SSamuel Thibault {
3122067fd92SSamuel Thibault 	spk_stop_serial_interrupt();
3132067fd92SSamuel Thibault 	if (speakup_info.port_tts == 0)
3142067fd92SSamuel Thibault 		return;
3152067fd92SSamuel Thibault 	synth_release_region(speakup_info.port_tts, 8);
3162067fd92SSamuel Thibault 	speakup_info.port_tts = 0;
3172067fd92SSamuel Thibault }
3182067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_serial_release);
319