1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/interrupt.h> |
3 | #include <linux/ioport.h> |
4 | |
5 | #include "spk_types.h" |
6 | #include "speakup.h" |
7 | #include "spk_priv.h" |
8 | #include "serialio.h" |
9 | |
10 | #include <linux/serial_core.h> |
11 | /* WARNING: Do not change this to <linux/serial.h> without testing that |
12 | * SERIAL_PORT_DFNS does get defined to the appropriate value. |
13 | */ |
14 | #include <asm/serial.h> |
15 | |
16 | #ifndef SERIAL_PORT_DFNS |
17 | #define SERIAL_PORT_DFNS |
18 | #endif |
19 | |
20 | static void start_serial_interrupt(int irq); |
21 | |
22 | static const struct old_serial_port rs_table[] = { |
23 | SERIAL_PORT_DFNS |
24 | }; |
25 | |
26 | static const struct old_serial_port *serstate; |
27 | static int timeouts; |
28 | |
29 | static int spk_serial_out(struct spk_synth *in_synth, const char ch); |
30 | static void spk_serial_send_xchar(struct spk_synth *in_synth, char ch); |
31 | static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear); |
32 | static unsigned char spk_serial_in(struct spk_synth *in_synth); |
33 | static unsigned char spk_serial_in_nowait(struct spk_synth *in_synth); |
34 | static void spk_serial_flush_buffer(struct spk_synth *in_synth); |
35 | static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth); |
36 | |
37 | struct spk_io_ops spk_serial_io_ops = { |
38 | .synth_out = spk_serial_out, |
39 | .send_xchar = spk_serial_send_xchar, |
40 | .tiocmset = spk_serial_tiocmset, |
41 | .synth_in = spk_serial_in, |
42 | .synth_in_nowait = spk_serial_in_nowait, |
43 | .flush_buffer = spk_serial_flush_buffer, |
44 | .wait_for_xmitr = spk_serial_wait_for_xmitr, |
45 | }; |
46 | EXPORT_SYMBOL_GPL(spk_serial_io_ops); |
47 | |
48 | const struct old_serial_port *spk_serial_init(int index) |
49 | { |
50 | int baud = 9600, quot = 0; |
51 | unsigned int cval = 0; |
52 | int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8; |
53 | const struct old_serial_port *ser; |
54 | int err; |
55 | |
56 | if (index >= ARRAY_SIZE(rs_table)) { |
57 | pr_info("no port info for ttyS%d\n" , index); |
58 | return NULL; |
59 | } |
60 | ser = rs_table + index; |
61 | |
62 | /* Divisor, byte size and parity */ |
63 | quot = ser->baud_base / baud; |
64 | cval = cflag & (CSIZE | CSTOPB); |
65 | #if defined(__powerpc__) || defined(__alpha__) |
66 | cval >>= 8; |
67 | #else /* !__powerpc__ && !__alpha__ */ |
68 | cval >>= 4; |
69 | #endif /* !__powerpc__ && !__alpha__ */ |
70 | if (cflag & PARENB) |
71 | cval |= UART_LCR_PARITY; |
72 | if (!(cflag & PARODD)) |
73 | cval |= UART_LCR_EPAR; |
74 | if (synth_request_region(start: ser->port, n: 8)) { |
75 | /* try to take it back. */ |
76 | pr_info("Ports not available, trying to steal them\n" ); |
77 | __release_region(&ioport_resource, ser->port, 8); |
78 | err = synth_request_region(start: ser->port, n: 8); |
79 | if (err) { |
80 | pr_warn("Unable to allocate port at %x, errno %i" , |
81 | ser->port, err); |
82 | return NULL; |
83 | } |
84 | } |
85 | |
86 | /* Disable UART interrupts, set DTR and RTS high |
87 | * and set speed. |
88 | */ |
89 | outb(value: cval | UART_LCR_DLAB, port: ser->port + UART_LCR); /* set DLAB */ |
90 | outb(value: quot & 0xff, port: ser->port + UART_DLL); /* LS of divisor */ |
91 | outb(value: quot >> 8, port: ser->port + UART_DLM); /* MS of divisor */ |
92 | outb(value: cval, port: ser->port + UART_LCR); /* reset DLAB */ |
93 | |
94 | /* Turn off Interrupts */ |
95 | outb(value: 0, port: ser->port + UART_IER); |
96 | outb(UART_MCR_DTR | UART_MCR_RTS, port: ser->port + UART_MCR); |
97 | |
98 | /* If we read 0xff from the LSR, there is no UART here. */ |
99 | if (inb(port: ser->port + UART_LSR) == 0xff) { |
100 | synth_release_region(start: ser->port, n: 8); |
101 | serstate = NULL; |
102 | return NULL; |
103 | } |
104 | |
105 | mdelay(1); |
106 | speakup_info.port_tts = ser->port; |
107 | serstate = ser; |
108 | |
109 | start_serial_interrupt(irq: ser->irq); |
110 | |
111 | return ser; |
112 | } |
113 | |
114 | static irqreturn_t synth_readbuf_handler(int irq, void *dev_id) |
115 | { |
116 | unsigned long flags; |
117 | int c; |
118 | |
119 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
120 | while (inb_p(port: speakup_info.port_tts + UART_LSR) & UART_LSR_DR) { |
121 | c = inb_p(port: speakup_info.port_tts + UART_RX); |
122 | synth->read_buff_add((u_char)c); |
123 | } |
124 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
125 | return IRQ_HANDLED; |
126 | } |
127 | |
128 | static void start_serial_interrupt(int irq) |
129 | { |
130 | int rv; |
131 | |
132 | if (!synth->read_buff_add) |
133 | return; |
134 | |
135 | rv = request_irq(irq, handler: synth_readbuf_handler, IRQF_SHARED, |
136 | name: "serial" , dev: (void *)synth_readbuf_handler); |
137 | |
138 | if (rv) |
139 | pr_err("Unable to request Speakup serial I R Q\n" ); |
140 | /* Set MCR */ |
141 | outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2, |
142 | port: speakup_info.port_tts + UART_MCR); |
143 | /* Turn on Interrupts */ |
144 | outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI, |
145 | port: speakup_info.port_tts + UART_IER); |
146 | inb(port: speakup_info.port_tts + UART_LSR); |
147 | inb(port: speakup_info.port_tts + UART_RX); |
148 | inb(port: speakup_info.port_tts + UART_IIR); |
149 | inb(port: speakup_info.port_tts + UART_MSR); |
150 | outb(value: 1, port: speakup_info.port_tts + UART_FCR); /* Turn FIFO On */ |
151 | } |
152 | |
153 | static void spk_serial_send_xchar(struct spk_synth *synth, char ch) |
154 | { |
155 | int timeout = SPK_XMITR_TIMEOUT; |
156 | |
157 | while (spk_serial_tx_busy()) { |
158 | if (!--timeout) |
159 | break; |
160 | udelay(1); |
161 | } |
162 | outb(value: ch, port: speakup_info.port_tts); |
163 | } |
164 | |
165 | static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear) |
166 | { |
167 | int old = inb(port: speakup_info.port_tts + UART_MCR); |
168 | |
169 | outb(value: (old & ~clear) | set, port: speakup_info.port_tts + UART_MCR); |
170 | } |
171 | |
172 | int spk_serial_synth_probe(struct spk_synth *synth) |
173 | { |
174 | const struct old_serial_port *ser; |
175 | int failed = 0; |
176 | |
177 | if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) { |
178 | ser = spk_serial_init(index: synth->ser); |
179 | if (!ser) { |
180 | failed = -1; |
181 | } else { |
182 | outb_p(value: 0, port: ser->port); |
183 | mdelay(1); |
184 | outb_p(value: '\r', port: ser->port); |
185 | } |
186 | } else { |
187 | failed = -1; |
188 | pr_warn("ttyS%i is an invalid port\n" , synth->ser); |
189 | } |
190 | if (failed) { |
191 | pr_info("%s: not found\n" , synth->long_name); |
192 | return -ENODEV; |
193 | } |
194 | pr_info("%s: ttyS%i, Driver Version %s\n" , |
195 | synth->long_name, synth->ser, synth->version); |
196 | synth->alive = 1; |
197 | return 0; |
198 | } |
199 | EXPORT_SYMBOL_GPL(spk_serial_synth_probe); |
200 | |
201 | void spk_stop_serial_interrupt(void) |
202 | { |
203 | if (speakup_info.port_tts == 0) |
204 | return; |
205 | |
206 | if (!synth->read_buff_add) |
207 | return; |
208 | |
209 | /* Turn off interrupts */ |
210 | outb(value: 0, port: speakup_info.port_tts + UART_IER); |
211 | /* Free IRQ */ |
212 | free_irq(serstate->irq, (void *)synth_readbuf_handler); |
213 | } |
214 | EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt); |
215 | |
216 | static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth) |
217 | { |
218 | int tmout = SPK_XMITR_TIMEOUT; |
219 | |
220 | if ((in_synth->alive) && (timeouts >= NUM_DISABLE_TIMEOUTS)) { |
221 | pr_warn("%s: too many timeouts, deactivating speakup\n" , |
222 | in_synth->long_name); |
223 | in_synth->alive = 0; |
224 | /* No synth any more, so nobody will restart TTYs, and we thus |
225 | * need to do it ourselves. Now that there is no synth we can |
226 | * let application flood anyway |
227 | */ |
228 | speakup_start_ttys(); |
229 | timeouts = 0; |
230 | return 0; |
231 | } |
232 | while (spk_serial_tx_busy()) { |
233 | if (--tmout == 0) { |
234 | pr_warn("%s: timed out (tx busy)\n" , |
235 | in_synth->long_name); |
236 | timeouts++; |
237 | return 0; |
238 | } |
239 | udelay(1); |
240 | } |
241 | tmout = SPK_CTS_TIMEOUT; |
242 | while (!((inb_p(port: speakup_info.port_tts + UART_MSR)) & UART_MSR_CTS)) { |
243 | /* CTS */ |
244 | if (--tmout == 0) { |
245 | timeouts++; |
246 | return 0; |
247 | } |
248 | udelay(1); |
249 | } |
250 | timeouts = 0; |
251 | return 1; |
252 | } |
253 | |
254 | static unsigned char spk_serial_in(struct spk_synth *in_synth) |
255 | { |
256 | int tmout = SPK_SERIAL_TIMEOUT; |
257 | |
258 | while (!(inb_p(port: speakup_info.port_tts + UART_LSR) & UART_LSR_DR)) { |
259 | if (--tmout == 0) { |
260 | pr_warn("time out while waiting for input.\n" ); |
261 | return 0xff; |
262 | } |
263 | udelay(1); |
264 | } |
265 | return inb_p(port: speakup_info.port_tts + UART_RX); |
266 | } |
267 | |
268 | static unsigned char spk_serial_in_nowait(struct spk_synth *in_synth) |
269 | { |
270 | unsigned char lsr; |
271 | |
272 | lsr = inb_p(port: speakup_info.port_tts + UART_LSR); |
273 | if (!(lsr & UART_LSR_DR)) |
274 | return 0; |
275 | return inb_p(port: speakup_info.port_tts + UART_RX); |
276 | } |
277 | |
278 | static void spk_serial_flush_buffer(struct spk_synth *in_synth) |
279 | { |
280 | /* TODO: flush the UART 16550 buffer */ |
281 | } |
282 | |
283 | static int spk_serial_out(struct spk_synth *in_synth, const char ch) |
284 | { |
285 | if (in_synth->alive && spk_serial_wait_for_xmitr(in_synth)) { |
286 | outb_p(value: ch, port: speakup_info.port_tts); |
287 | return 1; |
288 | } |
289 | return 0; |
290 | } |
291 | |
292 | const char *spk_serial_synth_immediate(struct spk_synth *synth, |
293 | const char *buff) |
294 | { |
295 | u_char ch; |
296 | |
297 | while ((ch = *buff)) { |
298 | if (ch == '\n') |
299 | ch = synth->procspeech; |
300 | if (spk_serial_wait_for_xmitr(in_synth: synth)) |
301 | outb(value: ch, port: speakup_info.port_tts); |
302 | else |
303 | return buff; |
304 | buff++; |
305 | } |
306 | return NULL; |
307 | } |
308 | EXPORT_SYMBOL_GPL(spk_serial_synth_immediate); |
309 | |
310 | void spk_serial_release(struct spk_synth *synth) |
311 | { |
312 | spk_stop_serial_interrupt(); |
313 | if (speakup_info.port_tts == 0) |
314 | return; |
315 | synth_release_region(start: speakup_info.port_tts, n: 8); |
316 | speakup_info.port_tts = 0; |
317 | } |
318 | EXPORT_SYMBOL_GPL(spk_serial_release); |
319 | |