18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
38c2ecf20Sopenharmony_ci#include <linux/ioport.h>
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include "spk_types.h"
68c2ecf20Sopenharmony_ci#include "speakup.h"
78c2ecf20Sopenharmony_ci#include "spk_priv.h"
88c2ecf20Sopenharmony_ci#include "serialio.h"
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/serial_core.h>
118c2ecf20Sopenharmony_ci/* WARNING:  Do not change this to <linux/serial.h> without testing that
128c2ecf20Sopenharmony_ci * SERIAL_PORT_DFNS does get defined to the appropriate value.
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci#include <asm/serial.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#ifndef SERIAL_PORT_DFNS
178c2ecf20Sopenharmony_ci#define SERIAL_PORT_DFNS
188c2ecf20Sopenharmony_ci#endif
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic void start_serial_interrupt(int irq);
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic const struct old_serial_port rs_table[] = {
238c2ecf20Sopenharmony_ci	SERIAL_PORT_DFNS
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic const struct old_serial_port *serstate;
278c2ecf20Sopenharmony_cistatic int timeouts;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic int spk_serial_out(struct spk_synth *in_synth, const char ch);
308c2ecf20Sopenharmony_cistatic void spk_serial_send_xchar(char ch);
318c2ecf20Sopenharmony_cistatic void spk_serial_tiocmset(unsigned int set, unsigned int clear);
328c2ecf20Sopenharmony_cistatic unsigned char spk_serial_in(void);
338c2ecf20Sopenharmony_cistatic unsigned char spk_serial_in_nowait(void);
348c2ecf20Sopenharmony_cistatic void spk_serial_flush_buffer(void);
358c2ecf20Sopenharmony_cistatic int spk_serial_wait_for_xmitr(struct spk_synth *in_synth);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistruct spk_io_ops spk_serial_io_ops = {
388c2ecf20Sopenharmony_ci	.synth_out = spk_serial_out,
398c2ecf20Sopenharmony_ci	.send_xchar = spk_serial_send_xchar,
408c2ecf20Sopenharmony_ci	.tiocmset = spk_serial_tiocmset,
418c2ecf20Sopenharmony_ci	.synth_in = spk_serial_in,
428c2ecf20Sopenharmony_ci	.synth_in_nowait = spk_serial_in_nowait,
438c2ecf20Sopenharmony_ci	.flush_buffer = spk_serial_flush_buffer,
448c2ecf20Sopenharmony_ci	.wait_for_xmitr = spk_serial_wait_for_xmitr,
458c2ecf20Sopenharmony_ci};
468c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_serial_io_ops);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ciconst struct old_serial_port *spk_serial_init(int index)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	int baud = 9600, quot = 0;
518c2ecf20Sopenharmony_ci	unsigned int cval = 0;
528c2ecf20Sopenharmony_ci	int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8;
538c2ecf20Sopenharmony_ci	const struct old_serial_port *ser;
548c2ecf20Sopenharmony_ci	int err;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (index >= ARRAY_SIZE(rs_table)) {
578c2ecf20Sopenharmony_ci		pr_info("no port info for ttyS%d\n", index);
588c2ecf20Sopenharmony_ci		return NULL;
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci	ser = rs_table + index;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	/*	Divisor, bytesize and parity */
638c2ecf20Sopenharmony_ci	quot = ser->baud_base / baud;
648c2ecf20Sopenharmony_ci	cval = cflag & (CSIZE | CSTOPB);
658c2ecf20Sopenharmony_ci#if defined(__powerpc__) || defined(__alpha__)
668c2ecf20Sopenharmony_ci	cval >>= 8;
678c2ecf20Sopenharmony_ci#else /* !__powerpc__ && !__alpha__ */
688c2ecf20Sopenharmony_ci	cval >>= 4;
698c2ecf20Sopenharmony_ci#endif /* !__powerpc__ && !__alpha__ */
708c2ecf20Sopenharmony_ci	if (cflag & PARENB)
718c2ecf20Sopenharmony_ci		cval |= UART_LCR_PARITY;
728c2ecf20Sopenharmony_ci	if (!(cflag & PARODD))
738c2ecf20Sopenharmony_ci		cval |= UART_LCR_EPAR;
748c2ecf20Sopenharmony_ci	if (synth_request_region(ser->port, 8)) {
758c2ecf20Sopenharmony_ci		/* try to take it back. */
768c2ecf20Sopenharmony_ci		pr_info("Ports not available, trying to steal them\n");
778c2ecf20Sopenharmony_ci		__release_region(&ioport_resource, ser->port, 8);
788c2ecf20Sopenharmony_ci		err = synth_request_region(ser->port, 8);
798c2ecf20Sopenharmony_ci		if (err) {
808c2ecf20Sopenharmony_ci			pr_warn("Unable to allocate port at %x, errno %i",
818c2ecf20Sopenharmony_ci				ser->port, err);
828c2ecf20Sopenharmony_ci			return NULL;
838c2ecf20Sopenharmony_ci		}
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	/*	Disable UART interrupts, set DTR and RTS high
878c2ecf20Sopenharmony_ci	 *	and set speed.
888c2ecf20Sopenharmony_ci	 */
898c2ecf20Sopenharmony_ci	outb(cval | UART_LCR_DLAB, ser->port + UART_LCR);	/* set DLAB */
908c2ecf20Sopenharmony_ci	outb(quot & 0xff, ser->port + UART_DLL);	/* LS of divisor */
918c2ecf20Sopenharmony_ci	outb(quot >> 8, ser->port + UART_DLM);		/* MS of divisor */
928c2ecf20Sopenharmony_ci	outb(cval, ser->port + UART_LCR);		/* reset DLAB */
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	/* Turn off Interrupts */
958c2ecf20Sopenharmony_ci	outb(0, ser->port + UART_IER);
968c2ecf20Sopenharmony_ci	outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	/* If we read 0xff from the LSR, there is no UART here. */
998c2ecf20Sopenharmony_ci	if (inb(ser->port + UART_LSR) == 0xff) {
1008c2ecf20Sopenharmony_ci		synth_release_region(ser->port, 8);
1018c2ecf20Sopenharmony_ci		serstate = NULL;
1028c2ecf20Sopenharmony_ci		return NULL;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	mdelay(1);
1068c2ecf20Sopenharmony_ci	speakup_info.port_tts = ser->port;
1078c2ecf20Sopenharmony_ci	serstate = ser;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	start_serial_interrupt(ser->irq);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	return ser;
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic irqreturn_t synth_readbuf_handler(int irq, void *dev_id)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	unsigned long flags;
1178c2ecf20Sopenharmony_ci	int c;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	spin_lock_irqsave(&speakup_info.spinlock, flags);
1208c2ecf20Sopenharmony_ci	while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) {
1218c2ecf20Sopenharmony_ci		c = inb_p(speakup_info.port_tts + UART_RX);
1228c2ecf20Sopenharmony_ci		synth->read_buff_add((u_char)c);
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&speakup_info.spinlock, flags);
1258c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic void start_serial_interrupt(int irq)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	int rv;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	if (!synth->read_buff_add)
1338c2ecf20Sopenharmony_ci		return;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED,
1368c2ecf20Sopenharmony_ci			 "serial", (void *)synth_readbuf_handler);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	if (rv)
1398c2ecf20Sopenharmony_ci		pr_err("Unable to request Speakup serial I R Q\n");
1408c2ecf20Sopenharmony_ci	/* Set MCR */
1418c2ecf20Sopenharmony_ci	outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2,
1428c2ecf20Sopenharmony_ci	     speakup_info.port_tts + UART_MCR);
1438c2ecf20Sopenharmony_ci	/* Turn on Interrupts */
1448c2ecf20Sopenharmony_ci	outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI,
1458c2ecf20Sopenharmony_ci	     speakup_info.port_tts + UART_IER);
1468c2ecf20Sopenharmony_ci	inb(speakup_info.port_tts + UART_LSR);
1478c2ecf20Sopenharmony_ci	inb(speakup_info.port_tts + UART_RX);
1488c2ecf20Sopenharmony_ci	inb(speakup_info.port_tts + UART_IIR);
1498c2ecf20Sopenharmony_ci	inb(speakup_info.port_tts + UART_MSR);
1508c2ecf20Sopenharmony_ci	outb(1, speakup_info.port_tts + UART_FCR);	/* Turn FIFO On */
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_cistatic void spk_serial_send_xchar(char ch)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	int timeout = SPK_XMITR_TIMEOUT;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	while (spk_serial_tx_busy()) {
1588c2ecf20Sopenharmony_ci		if (!--timeout)
1598c2ecf20Sopenharmony_ci			break;
1608c2ecf20Sopenharmony_ci		udelay(1);
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci	outb(ch, speakup_info.port_tts);
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic void spk_serial_tiocmset(unsigned int set, unsigned int clear)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	int old = inb(speakup_info.port_tts + UART_MCR);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR);
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ciint spk_serial_synth_probe(struct spk_synth *synth)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	const struct old_serial_port *ser;
1758c2ecf20Sopenharmony_ci	int failed = 0;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {
1788c2ecf20Sopenharmony_ci		ser = spk_serial_init(synth->ser);
1798c2ecf20Sopenharmony_ci		if (!ser) {
1808c2ecf20Sopenharmony_ci			failed = -1;
1818c2ecf20Sopenharmony_ci		} else {
1828c2ecf20Sopenharmony_ci			outb_p(0, ser->port);
1838c2ecf20Sopenharmony_ci			mdelay(1);
1848c2ecf20Sopenharmony_ci			outb_p('\r', ser->port);
1858c2ecf20Sopenharmony_ci		}
1868c2ecf20Sopenharmony_ci	} else {
1878c2ecf20Sopenharmony_ci		failed = -1;
1888c2ecf20Sopenharmony_ci		pr_warn("ttyS%i is an invalid port\n", synth->ser);
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci	if (failed) {
1918c2ecf20Sopenharmony_ci		pr_info("%s: not found\n", synth->long_name);
1928c2ecf20Sopenharmony_ci		return -ENODEV;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci	pr_info("%s: ttyS%i, Driver Version %s\n",
1958c2ecf20Sopenharmony_ci		synth->long_name, synth->ser, synth->version);
1968c2ecf20Sopenharmony_ci	synth->alive = 1;
1978c2ecf20Sopenharmony_ci	return 0;
1988c2ecf20Sopenharmony_ci}
1998c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_serial_synth_probe);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_civoid spk_stop_serial_interrupt(void)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	if (speakup_info.port_tts == 0)
2048c2ecf20Sopenharmony_ci		return;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	if (!synth->read_buff_add)
2078c2ecf20Sopenharmony_ci		return;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	/* Turn off interrupts */
2108c2ecf20Sopenharmony_ci	outb(0, speakup_info.port_tts + UART_IER);
2118c2ecf20Sopenharmony_ci	/* Free IRQ */
2128c2ecf20Sopenharmony_ci	free_irq(serstate->irq, (void *)synth_readbuf_handler);
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_stop_serial_interrupt);
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_cistatic int spk_serial_wait_for_xmitr(struct spk_synth *in_synth)
2178c2ecf20Sopenharmony_ci{
2188c2ecf20Sopenharmony_ci	int tmout = SPK_XMITR_TIMEOUT;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	if ((in_synth->alive) && (timeouts >= NUM_DISABLE_TIMEOUTS)) {
2218c2ecf20Sopenharmony_ci		pr_warn("%s: too many timeouts, deactivating speakup\n",
2228c2ecf20Sopenharmony_ci			in_synth->long_name);
2238c2ecf20Sopenharmony_ci		in_synth->alive = 0;
2248c2ecf20Sopenharmony_ci		/* No synth any more, so nobody will restart TTYs, and we thus
2258c2ecf20Sopenharmony_ci		 * need to do it ourselves.  Now that there is no synth we can
2268c2ecf20Sopenharmony_ci		 * let application flood anyway
2278c2ecf20Sopenharmony_ci		 */
2288c2ecf20Sopenharmony_ci		speakup_start_ttys();
2298c2ecf20Sopenharmony_ci		timeouts = 0;
2308c2ecf20Sopenharmony_ci		return 0;
2318c2ecf20Sopenharmony_ci	}
2328c2ecf20Sopenharmony_ci	while (spk_serial_tx_busy()) {
2338c2ecf20Sopenharmony_ci		if (--tmout == 0) {
2348c2ecf20Sopenharmony_ci			pr_warn("%s: timed out (tx busy)\n",
2358c2ecf20Sopenharmony_ci				in_synth->long_name);
2368c2ecf20Sopenharmony_ci			timeouts++;
2378c2ecf20Sopenharmony_ci			return 0;
2388c2ecf20Sopenharmony_ci		}
2398c2ecf20Sopenharmony_ci		udelay(1);
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci	tmout = SPK_CTS_TIMEOUT;
2428c2ecf20Sopenharmony_ci	while (!((inb_p(speakup_info.port_tts + UART_MSR)) & UART_MSR_CTS)) {
2438c2ecf20Sopenharmony_ci		/* CTS */
2448c2ecf20Sopenharmony_ci		if (--tmout == 0) {
2458c2ecf20Sopenharmony_ci			timeouts++;
2468c2ecf20Sopenharmony_ci			return 0;
2478c2ecf20Sopenharmony_ci		}
2488c2ecf20Sopenharmony_ci		udelay(1);
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci	timeouts = 0;
2518c2ecf20Sopenharmony_ci	return 1;
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic unsigned char spk_serial_in(void)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	int tmout = SPK_SERIAL_TIMEOUT;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	while (!(inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR)) {
2598c2ecf20Sopenharmony_ci		if (--tmout == 0) {
2608c2ecf20Sopenharmony_ci			pr_warn("time out while waiting for input.\n");
2618c2ecf20Sopenharmony_ci			return 0xff;
2628c2ecf20Sopenharmony_ci		}
2638c2ecf20Sopenharmony_ci		udelay(1);
2648c2ecf20Sopenharmony_ci	}
2658c2ecf20Sopenharmony_ci	return inb_p(speakup_info.port_tts + UART_RX);
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic unsigned char spk_serial_in_nowait(void)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	unsigned char lsr;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	lsr = inb_p(speakup_info.port_tts + UART_LSR);
2738c2ecf20Sopenharmony_ci	if (!(lsr & UART_LSR_DR))
2748c2ecf20Sopenharmony_ci		return 0;
2758c2ecf20Sopenharmony_ci	return inb_p(speakup_info.port_tts + UART_RX);
2768c2ecf20Sopenharmony_ci}
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_cistatic void spk_serial_flush_buffer(void)
2798c2ecf20Sopenharmony_ci{
2808c2ecf20Sopenharmony_ci	/* TODO: flush the UART 16550 buffer */
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_cistatic int spk_serial_out(struct spk_synth *in_synth, const char ch)
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	if (in_synth->alive && spk_serial_wait_for_xmitr(in_synth)) {
2868c2ecf20Sopenharmony_ci		outb_p(ch, speakup_info.port_tts);
2878c2ecf20Sopenharmony_ci		return 1;
2888c2ecf20Sopenharmony_ci	}
2898c2ecf20Sopenharmony_ci	return 0;
2908c2ecf20Sopenharmony_ci}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ciconst char *spk_serial_synth_immediate(struct spk_synth *synth,
2938c2ecf20Sopenharmony_ci				       const char *buff)
2948c2ecf20Sopenharmony_ci{
2958c2ecf20Sopenharmony_ci	u_char ch;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	while ((ch = *buff)) {
2988c2ecf20Sopenharmony_ci		if (ch == '\n')
2998c2ecf20Sopenharmony_ci			ch = synth->procspeech;
3008c2ecf20Sopenharmony_ci		if (spk_serial_wait_for_xmitr(synth))
3018c2ecf20Sopenharmony_ci			outb(ch, speakup_info.port_tts);
3028c2ecf20Sopenharmony_ci		else
3038c2ecf20Sopenharmony_ci			return buff;
3048c2ecf20Sopenharmony_ci		buff++;
3058c2ecf20Sopenharmony_ci	}
3068c2ecf20Sopenharmony_ci	return NULL;
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_serial_synth_immediate);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_civoid spk_serial_release(void)
3118c2ecf20Sopenharmony_ci{
3128c2ecf20Sopenharmony_ci	spk_stop_serial_interrupt();
3138c2ecf20Sopenharmony_ci	if (speakup_info.port_tts == 0)
3148c2ecf20Sopenharmony_ci		return;
3158c2ecf20Sopenharmony_ci	synth_release_region(speakup_info.port_tts, 8);
3168c2ecf20Sopenharmony_ci	speakup_info.port_tts = 0;
3178c2ecf20Sopenharmony_ci}
3188c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_serial_release);
319