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