blob: ba060d0ceca2f69d3b0dc1cd5f65919fdd2ec204 [file] [log] [blame]
William Hubbsc6e3fd22010-10-07 13:20:02 -05001#include <linux/interrupt.h>
2#include <linux/ioport.h>
3
4#include "spk_types.h"
5#include "speakup.h"
6#include "spk_priv.h"
7#include "serialio.h"
8
Samuel Thibault327b8822016-01-15 00:47:41 +01009#include <linux/serial_core.h>
10/* WARNING: Do not change this to <linux/serial.h> without testing that
Janani Ravichandrane6ceec822016-02-13 22:05:01 -050011 * SERIAL_PORT_DFNS does get defined to the appropriate value.
12 */
Samuel Thibault327b8822016-01-15 00:47:41 +010013#include <asm/serial.h>
14
Chen Gang5e6dc542013-10-31 15:27:37 +080015#ifndef SERIAL_PORT_DFNS
16#define SERIAL_PORT_DFNS
17#endif
18
William Hubbsc6e3fd22010-10-07 13:20:02 -050019static void start_serial_interrupt(int irq);
20
Jiri Slaby3ee00172012-03-05 14:52:11 +010021static const struct old_serial_port rs_table[] = {
William Hubbsc6e3fd22010-10-07 13:20:02 -050022 SERIAL_PORT_DFNS
23};
Arushi Singhaldefaa9a2017-03-14 10:46:42 +053024
Jiri Slaby3ee00172012-03-05 14:52:11 +010025static const struct old_serial_port *serstate;
William Hubbsc6e3fd22010-10-07 13:20:02 -050026static int timeouts;
27
Okash Khawaja1e441592017-03-14 13:41:53 +000028static int spk_serial_out(struct spk_synth *in_synth, const char ch);
Okash Khawajabe223d52017-04-22 09:00:28 +010029static void spk_serial_send_xchar(char ch);
30static void spk_serial_tiocmset(unsigned int set, unsigned int clear);
31
Okash Khawaja1e441592017-03-14 13:41:53 +000032struct spk_io_ops spk_serial_io_ops = {
33 .synth_out = spk_serial_out,
Okash Khawajabe223d52017-04-22 09:00:28 +010034 .send_xchar = spk_serial_send_xchar,
35 .tiocmset = spk_serial_tiocmset,
Okash Khawaja1e441592017-03-14 13:41:53 +000036};
37EXPORT_SYMBOL_GPL(spk_serial_io_ops);
38
Jiri Slaby3ee00172012-03-05 14:52:11 +010039const struct old_serial_port *spk_serial_init(int index)
William Hubbsc6e3fd22010-10-07 13:20:02 -050040{
41 int baud = 9600, quot = 0;
42 unsigned int cval = 0;
43 int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8;
Samuel Thibault327b8822016-01-15 00:47:41 +010044 const struct old_serial_port *ser;
William Hubbsc6e3fd22010-10-07 13:20:02 -050045 int err;
46
Samuel Thibault327b8822016-01-15 00:47:41 +010047 if (index >= ARRAY_SIZE(rs_table)) {
48 pr_info("no port info for ttyS%d\n", index);
49 return NULL;
50 }
51 ser = rs_table + index;
52
William Hubbsc6e3fd22010-10-07 13:20:02 -050053 /* Divisor, bytesize and parity */
54 quot = ser->baud_base / baud;
55 cval = cflag & (CSIZE | CSTOPB);
56#if defined(__powerpc__) || defined(__alpha__)
57 cval >>= 8;
58#else /* !__powerpc__ && !__alpha__ */
59 cval >>= 4;
60#endif /* !__powerpc__ && !__alpha__ */
61 if (cflag & PARENB)
62 cval |= UART_LCR_PARITY;
63 if (!(cflag & PARODD))
64 cval |= UART_LCR_EPAR;
65 if (synth_request_region(ser->port, 8)) {
66 /* try to take it back. */
Keerthimai Janarthanan3a046c12014-03-18 14:10:13 +053067 pr_info("Ports not available, trying to steal them\n");
William Hubbsc6e3fd22010-10-07 13:20:02 -050068 __release_region(&ioport_resource, ser->port, 8);
69 err = synth_request_region(ser->port, 8);
70 if (err) {
Jiri Slaby3ee00172012-03-05 14:52:11 +010071 pr_warn("Unable to allocate port at %x, errno %i",
William Hubbsbaf9ac92010-10-15 22:13:36 -050072 ser->port, err);
William Hubbsc6e3fd22010-10-07 13:20:02 -050073 return NULL;
74 }
75 }
76
77 /* Disable UART interrupts, set DTR and RTS high
Aleksei Fedotov13d825e2015-08-14 22:34:37 +030078 * and set speed.
79 */
William Hubbsc6e3fd22010-10-07 13:20:02 -050080 outb(cval | UART_LCR_DLAB, ser->port + UART_LCR); /* set DLAB */
81 outb(quot & 0xff, ser->port + UART_DLL); /* LS of divisor */
82 outb(quot >> 8, ser->port + UART_DLM); /* MS of divisor */
83 outb(cval, ser->port + UART_LCR); /* reset DLAB */
84
85 /* Turn off Interrupts */
86 outb(0, ser->port + UART_IER);
87 outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR);
88
89 /* If we read 0xff from the LSR, there is no UART here. */
90 if (inb(ser->port + UART_LSR) == 0xff) {
91 synth_release_region(ser->port, 8);
92 serstate = NULL;
93 return NULL;
94 }
95
96 mdelay(1);
97 speakup_info.port_tts = ser->port;
98 serstate = ser;
99
100 start_serial_interrupt(ser->irq);
101
102 return ser;
103}
104
105static irqreturn_t synth_readbuf_handler(int irq, void *dev_id)
106{
107 unsigned long flags;
William Hubbsc6e3fd22010-10-07 13:20:02 -0500108 int c;
Domagoj Trsan8e69a812014-09-09 20:04:34 +0200109
William Hubbs9fb31b12013-05-13 00:02:58 -0500110 spin_lock_irqsave(&speakup_info.spinlock, flags);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500111 while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) {
Varsha Raoe3bab5e2017-02-22 23:16:40 +0530112 c = inb_p(speakup_info.port_tts + UART_RX);
Shiva Kerdeld290eff2016-11-06 15:09:18 +0100113 synth->read_buff_add((u_char)c);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500114 }
William Hubbs9fb31b12013-05-13 00:02:58 -0500115 spin_unlock_irqrestore(&speakup_info.spinlock, flags);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500116 return IRQ_HANDLED;
117}
118
119static void start_serial_interrupt(int irq)
120{
121 int rv;
122
Shraddha Barke114885e2015-09-11 11:32:27 +0530123 if (!synth->read_buff_add)
William Hubbsc6e3fd22010-10-07 13:20:02 -0500124 return;
125
126 rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED,
Shiva Kerdeld290eff2016-11-06 15:09:18 +0100127 "serial", (void *)synth_readbuf_handler);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500128
129 if (rv)
Keerthimai Janarthanan3a046c12014-03-18 14:10:13 +0530130 pr_err("Unable to request Speakup serial I R Q\n");
William Hubbsc6e3fd22010-10-07 13:20:02 -0500131 /* Set MCR */
132 outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2,
Arushi Singhal65bf4ea12017-03-21 17:12:34 +0530133 speakup_info.port_tts + UART_MCR);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500134 /* Turn on Interrupts */
135 outb(UART_IER_MSI|UART_IER_RLSI|UART_IER_RDI,
136 speakup_info.port_tts + UART_IER);
Varsha Raoe3bab5e2017-02-22 23:16:40 +0530137 inb(speakup_info.port_tts + UART_LSR);
138 inb(speakup_info.port_tts + UART_RX);
139 inb(speakup_info.port_tts + UART_IIR);
140 inb(speakup_info.port_tts + UART_MSR);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500141 outb(1, speakup_info.port_tts + UART_FCR); /* Turn FIFO On */
142}
143
Okash Khawajabe223d52017-04-22 09:00:28 +0100144static void spk_serial_send_xchar(char ch)
145{
146 int timeout = SPK_XMITR_TIMEOUT;
147
148 while (spk_serial_tx_busy()) {
149 if (!--timeout)
150 break;
151 udelay(1);
152 }
153 outb(ch, speakup_info.port_tts);
154}
155
156static void spk_serial_tiocmset(unsigned int set, unsigned int clear)
157{
158 int old = inb(speakup_info.port_tts + UART_MCR);
159 outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR);
160}
161
Okash Khawaja98c1fda2017-03-16 08:10:17 +0000162int spk_serial_synth_probe(struct spk_synth *synth)
163{
164 const struct old_serial_port *ser;
165 int failed = 0;
166
167 if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {
168 ser = spk_serial_init(synth->ser);
169 if (!ser) {
170 failed = -1;
171 } else {
172 outb_p(0, ser->port);
173 mdelay(1);
174 outb_p('\r', ser->port);
175 }
176 } else {
177 failed = -1;
178 pr_warn("ttyS%i is an invalid port\n", synth->ser);
179 }
180 if (failed) {
181 pr_info("%s: not found\n", synth->long_name);
182 return -ENODEV;
183 }
184 pr_info("%s: ttyS%i, Driver Version %s\n",
185 synth->long_name, synth->ser, synth->version);
186 synth->alive = 1;
187 return 0;
188}
189EXPORT_SYMBOL_GPL(spk_serial_synth_probe);
190
Samuel Thibaultca2beaf2013-01-02 02:37:40 +0100191void spk_stop_serial_interrupt(void)
William Hubbsc6e3fd22010-10-07 13:20:02 -0500192{
193 if (speakup_info.port_tts == 0)
194 return;
195
Shraddha Barke114885e2015-09-11 11:32:27 +0530196 if (!synth->read_buff_add)
William Hubbsc6e3fd22010-10-07 13:20:02 -0500197 return;
198
199 /* Turn off interrupts */
Varsha Raoe3bab5e2017-02-22 23:16:40 +0530200 outb(0, speakup_info.port_tts + UART_IER);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500201 /* Free IRQ */
Shiva Kerdeld290eff2016-11-06 15:09:18 +0100202 free_irq(serstate->irq, (void *)synth_readbuf_handler);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500203}
Okash Khawajaa50ef312017-03-14 13:41:54 +0000204EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500205
Okash Khawaja9176d152017-03-14 13:41:52 +0000206int spk_wait_for_xmitr(struct spk_synth *in_synth)
William Hubbsc6e3fd22010-10-07 13:20:02 -0500207{
208 int tmout = SPK_XMITR_TIMEOUT;
Domagoj Trsan8e69a812014-09-09 20:04:34 +0200209
Okash Khawaja9176d152017-03-14 13:41:52 +0000210 if ((in_synth->alive) && (timeouts >= NUM_DISABLE_TIMEOUTS)) {
William Hubbsbaf9ac92010-10-15 22:13:36 -0500211 pr_warn("%s: too many timeouts, deactivating speakup\n",
Okash Khawaja9176d152017-03-14 13:41:52 +0000212 in_synth->long_name);
213 in_synth->alive = 0;
William Hubbsc6e3fd22010-10-07 13:20:02 -0500214 /* No synth any more, so nobody will restart TTYs, and we thus
215 * need to do it ourselves. Now that there is no synth we can
Aleksei Fedotov13d825e2015-08-14 22:34:37 +0300216 * let application flood anyway
217 */
William Hubbsc6e3fd22010-10-07 13:20:02 -0500218 speakup_start_ttys();
219 timeouts = 0;
220 return 0;
221 }
222 while (spk_serial_tx_busy()) {
223 if (--tmout == 0) {
Okash Khawaja9176d152017-03-14 13:41:52 +0000224 pr_warn("%s: timed out (tx busy)\n", in_synth->long_name);
William Hubbsc6e3fd22010-10-07 13:20:02 -0500225 timeouts++;
226 return 0;
227 }
228 udelay(1);
229 }
230 tmout = SPK_CTS_TIMEOUT;
231 while (!((inb_p(speakup_info.port_tts + UART_MSR)) & UART_MSR_CTS)) {
232 /* CTS */
233 if (--tmout == 0) {
William Hubbsc6e3fd22010-10-07 13:20:02 -0500234 timeouts++;
235 return 0;
236 }
237 udelay(1);
238 }
239 timeouts = 0;
240 return 1;
241}
242
243unsigned char spk_serial_in(void)
244{
245 int tmout = SPK_SERIAL_TIMEOUT;
246
247 while (!(inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR)) {
248 if (--tmout == 0) {
249 pr_warn("time out while waiting for input.\n");
250 return 0xff;
251 }
252 udelay(1);
253 }
254 return inb_p(speakup_info.port_tts + UART_RX);
255}
256EXPORT_SYMBOL_GPL(spk_serial_in);
257
258unsigned char spk_serial_in_nowait(void)
259{
260 unsigned char lsr;
261
262 lsr = inb_p(speakup_info.port_tts + UART_LSR);
263 if (!(lsr & UART_LSR_DR))
264 return 0;
265 return inb_p(speakup_info.port_tts + UART_RX);
266}
267EXPORT_SYMBOL_GPL(spk_serial_in_nowait);
268
Gustavo A. R. Silvaa15505e2017-03-27 01:37:29 -0500269static int spk_serial_out(struct spk_synth *in_synth, const char ch)
William Hubbsc6e3fd22010-10-07 13:20:02 -0500270{
Okash Khawaja9176d152017-03-14 13:41:52 +0000271 if (in_synth->alive && spk_wait_for_xmitr(in_synth)) {
William Hubbsc6e3fd22010-10-07 13:20:02 -0500272 outb_p(ch, speakup_info.port_tts);
273 return 1;
274 }
275 return 0;
276}
William Hubbsc6e3fd22010-10-07 13:20:02 -0500277
Okash Khawaja98c1fda2017-03-16 08:10:17 +0000278const char *spk_serial_synth_immediate(struct spk_synth *synth, const char *buff)
279{
280 u_char ch;
281
282 while ((ch = *buff)) {
283 if (ch == '\n')
284 ch = synth->procspeech;
285 if (spk_wait_for_xmitr(synth))
286 outb(ch, speakup_info.port_tts);
287 else
288 return buff;
289 buff++;
290 }
291 return NULL;
292}
293EXPORT_SYMBOL_GPL(spk_serial_synth_immediate);
294
William Hubbsc6e3fd22010-10-07 13:20:02 -0500295void spk_serial_release(void)
296{
Okash Khawajaa50ef312017-03-14 13:41:54 +0000297 spk_stop_serial_interrupt();
William Hubbsc6e3fd22010-10-07 13:20:02 -0500298 if (speakup_info.port_tts == 0)
299 return;
300 synth_release_region(speakup_info.port_tts, 8);
301 speakup_info.port_tts = 0;
302}
303EXPORT_SYMBOL_GPL(spk_serial_release);