uart.c revision 1.1 1 1.1 brad /* $NetBSD: uart.c,v 1.1 2021/12/07 17:39:55 brad Exp $ */
2 1.1 brad
3 1.1 brad /*
4 1.1 brad * Copyright (c) 2021 Brad Spencer <brad (at) anduin.eldar.org>
5 1.1 brad *
6 1.1 brad * Permission to use, copy, modify, and distribute this software for any
7 1.1 brad * purpose with or without fee is hereby granted, provided that the above
8 1.1 brad * copyright notice and this permission notice appear in all copies.
9 1.1 brad *
10 1.1 brad * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 1.1 brad * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 1.1 brad * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 1.1 brad * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 1.1 brad * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 1.1 brad * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 1.1 brad * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 1.1 brad */
18 1.1 brad
19 1.1 brad #ifdef __RCSID
20 1.1 brad __RCSID("$NetBSD: uart.c,v 1.1 2021/12/07 17:39:55 brad Exp $");
21 1.1 brad #endif
22 1.1 brad
23 1.1 brad /* Functions that know how to talk to a SCMD using the uart tty
24 1.1 brad * mode or via SPI userland, which ends up being mostly the same.
25 1.1 brad *
26 1.1 brad * Some of this is the same stuff that the kernel scmd(4) driver
27 1.1 brad * ends up doing.
28 1.1 brad */
29 1.1 brad
30 1.1 brad #include <inttypes.h>
31 1.1 brad #include <stdbool.h>
32 1.1 brad #include <stdio.h>
33 1.1 brad #include <stdlib.h>
34 1.1 brad #include <unistd.h>
35 1.1 brad #include <err.h>
36 1.1 brad #include <fcntl.h>
37 1.1 brad #include <string.h>
38 1.1 brad #include <limits.h>
39 1.1 brad #include <termios.h>
40 1.1 brad #include <errno.h>
41 1.1 brad #include <sys/ioctl.h>
42 1.1 brad #include <sys/time.h>
43 1.1 brad #include <dev/spi/spi_io.h>
44 1.1 brad
45 1.1 brad #include <dev/ic/scmdreg.h>
46 1.1 brad
47 1.1 brad #include "scmdctl.h"
48 1.1 brad #include "responses.h"
49 1.1 brad
50 1.1 brad #define EXTERN
51 1.1 brad #include "uart.h"
52 1.1 brad
53 1.1 brad
54 1.1 brad static int uart_subtype = -1;
55 1.1 brad static int uart_spi_slave_addr = -1;
56 1.1 brad
57 1.1 brad /* The uart tty mode of the SCMD device is useful for human or
58 1.1 brad * machine use. However you can't really know what state it is in
59 1.1 brad * so send some junk and look for '>' character indicating a new
60 1.1 brad * command can be entered. Usually this won't be needed, but
61 1.1 brad * you can never know when it is.
62 1.1 brad */
63 1.1 brad int
64 1.1 brad uart_clear(int fd, bool debug)
65 1.1 brad {
66 1.1 brad const char jcmd[4] = "qq\r\n";
67 1.1 brad char input;
68 1.1 brad int i;
69 1.1 brad
70 1.1 brad if (uart_subtype == UART_IS_PURE_UART) {
71 1.1 brad i = write(fd,jcmd,4);
72 1.1 brad if (i == 4) {
73 1.1 brad i = read(fd,&input,1);
74 1.1 brad while (input != '>') {
75 1.1 brad if (debug)
76 1.1 brad fprintf(stderr,"uart_clear: %c\n",input);
77 1.1 brad i = read(fd,&input,1);
78 1.1 brad }
79 1.1 brad } else {
80 1.1 brad return EINVAL;
81 1.1 brad }
82 1.1 brad }
83 1.1 brad
84 1.1 brad return 0;
85 1.1 brad }
86 1.1 brad
87 1.1 brad /* The SCMD device will echo back the characters in uart tty mode.
88 1.1 brad * Eat them here.
89 1.1 brad */
90 1.1 brad static int
91 1.1 brad pure_uart_send_cmd(int fd, const char *s, char *ibuf, int len)
92 1.1 brad {
93 1.1 brad int i;
94 1.1 brad
95 1.1 brad i = write(fd,s,len);
96 1.1 brad if (i == len) {
97 1.1 brad i = read(fd,ibuf,len);
98 1.1 brad return 0;
99 1.1 brad } else {
100 1.1 brad return EINVAL;
101 1.1 brad }
102 1.1 brad }
103 1.1 brad
104 1.1 brad /* In pure uart tty mode, the command is sent as text and we are
105 1.1 brad * looking for '>'. There is not a lot that can go wrong, but
106 1.1 brad * noise on the line is one of them, and that really is not detected here.
107 1.1 brad * This is probably the least reliable method of speaking to a SCMD
108 1.1 brad * device.
109 1.1 brad */
110 1.1 brad static int
111 1.1 brad uart_get_response(int fd, bool debug, char *obuf, int len)
112 1.1 brad {
113 1.1 brad int n,i;
114 1.1 brad char c;
115 1.1 brad
116 1.1 brad memset(obuf,0,len);
117 1.1 brad n = 0;
118 1.1 brad i = read(fd,&c,1);
119 1.1 brad if (i == -1)
120 1.1 brad return EINVAL;
121 1.1 brad while (c != '>' && c != '\r' && n < len) {
122 1.1 brad obuf[n] = c;
123 1.1 brad if (debug)
124 1.1 brad fprintf(stderr,"uart_get_response: looking for EOL or NL: %d %d -%c-\n",i,n,c);
125 1.1 brad n++;
126 1.1 brad i = read(fd,&c,1);
127 1.1 brad }
128 1.1 brad
129 1.1 brad if (c != '>') {
130 1.1 brad i = read(fd,&c,1);
131 1.1 brad if (i == -1)
132 1.1 brad return EINVAL;
133 1.1 brad while (c != '>') {
134 1.1 brad if (debug)
135 1.1 brad fprintf(stderr,"uart_get_reponse: draining: %d -%c-\n",i,c);
136 1.1 brad i = read(fd,&c,1);
137 1.1 brad if (i == -1)
138 1.1 brad return EINVAL;
139 1.1 brad }
140 1.1 brad }
141 1.1 brad
142 1.1 brad return 0;
143 1.1 brad }
144 1.1 brad
145 1.1 brad /* This handles the two uart cases. Either pure tty uart or SPI
146 1.1 brad * userland. The first uses text commands and the second is binary,
147 1.1 brad * but has the strange read situation that scmd(4) has.
148 1.1 brad */
149 1.1 brad static int
150 1.1 brad uart_phy_read_register(int fd, bool debug, uint8_t reg, uint8_t *buf)
151 1.1 brad {
152 1.1 brad int err;
153 1.1 brad char cmdbuf[9];
154 1.1 brad char qbuf[10];
155 1.1 brad struct timespec ts;
156 1.1 brad struct spi_ioctl_transfer spi_t;
157 1.1 brad uint8_t b;
158 1.1 brad
159 1.1 brad if (SCMD_IS_HOLE(reg)) {
160 1.1 brad *buf = SCMD_HOLE_VALUE;
161 1.1 brad return 0;
162 1.1 brad }
163 1.1 brad
164 1.1 brad switch (uart_subtype) {
165 1.1 brad case UART_IS_PURE_UART:
166 1.1 brad sprintf(cmdbuf, "R%02X\r\n", reg);
167 1.1 brad err = pure_uart_send_cmd(fd, cmdbuf, qbuf, 5);
168 1.1 brad if (! err) {
169 1.1 brad err = uart_get_response(fd, debug, qbuf, 5);
170 1.1 brad *buf = (uint8_t)strtol(qbuf,NULL,16);
171 1.1 brad }
172 1.1 brad break;
173 1.1 brad case UART_IS_SPI_USERLAND:
174 1.1 brad spi_t.sit_addr = uart_spi_slave_addr;
175 1.1 brad reg = reg | 0x80;
176 1.1 brad spi_t.sit_send = ®
177 1.1 brad spi_t.sit_sendlen = 1;
178 1.1 brad spi_t.sit_recv = NULL;
179 1.1 brad spi_t.sit_recvlen = 0;
180 1.1 brad
181 1.1 brad err = ioctl(fd,SPI_IOCTL_TRANSFER,&spi_t);
182 1.1 brad if (debug)
183 1.1 brad fprintf(stderr,"uart_phy_read_register: IOCTL UL SPI send err: %d ; reg: %02x ; xreg: %02x\n",
184 1.1 brad err,reg,reg & 0x7f);
185 1.1 brad
186 1.1 brad if (err == -1)
187 1.1 brad return errno;
188 1.1 brad
189 1.1 brad ts.tv_sec = 0;
190 1.1 brad ts.tv_nsec = 50;
191 1.1 brad nanosleep(&ts,NULL);
192 1.1 brad
193 1.1 brad spi_t.sit_addr = uart_spi_slave_addr;
194 1.1 brad spi_t.sit_send = NULL;
195 1.1 brad spi_t.sit_sendlen = 0;
196 1.1 brad b = SCMD_HOLE_VALUE;
197 1.1 brad spi_t.sit_recv = &b;
198 1.1 brad spi_t.sit_recvlen = 1;
199 1.1 brad
200 1.1 brad err = ioctl(fd,SPI_IOCTL_TRANSFER,&spi_t);
201 1.1 brad if (debug)
202 1.1 brad fprintf(stderr,"uart_phy_read_register: IOCTL UL SPI receive 1 err: %d ; b: %02x\n",
203 1.1 brad err,b);
204 1.1 brad
205 1.1 brad if (err == -1)
206 1.1 brad return errno;
207 1.1 brad
208 1.1 brad ts.tv_sec = 0;
209 1.1 brad ts.tv_nsec = 50;
210 1.1 brad nanosleep(&ts,NULL);
211 1.1 brad
212 1.1 brad *buf = (uint8_t)b;
213 1.1 brad
214 1.1 brad /* Bogus read that is needed */
215 1.1 brad spi_t.sit_addr = uart_spi_slave_addr;
216 1.1 brad spi_t.sit_send = NULL;
217 1.1 brad spi_t.sit_sendlen = 0;
218 1.1 brad b = SCMD_HOLE_VALUE;
219 1.1 brad spi_t.sit_recv = &b;
220 1.1 brad spi_t.sit_recvlen = 1;
221 1.1 brad
222 1.1 brad err = ioctl(fd,SPI_IOCTL_TRANSFER,&spi_t);
223 1.1 brad if (debug)
224 1.1 brad fprintf(stderr,"uart_phy_read_register: IOCTL UL SPI receive 2 err: %d ; b: %02x\n",
225 1.1 brad err,b);
226 1.1 brad
227 1.1 brad if (err == -1)
228 1.1 brad return errno;
229 1.1 brad
230 1.1 brad ts.tv_sec = 0;
231 1.1 brad ts.tv_nsec = 50;
232 1.1 brad nanosleep(&ts,NULL);
233 1.1 brad
234 1.1 brad break;
235 1.1 brad default:
236 1.1 brad return EINVAL;
237 1.1 brad break;
238 1.1 brad }
239 1.1 brad
240 1.1 brad return err;
241 1.1 brad }
242 1.1 brad
243 1.1 brad /* Like read, this handles the two uart cases. */
244 1.1 brad static int
245 1.1 brad uart_phy_write_register(int fd, bool debug, uint8_t reg, uint8_t buf)
246 1.1 brad {
247 1.1 brad int err;
248 1.1 brad char cmdbuf[9];
249 1.1 brad char qbuf[10];
250 1.1 brad struct timespec ts;
251 1.1 brad struct spi_ioctl_transfer spi_t;
252 1.1 brad
253 1.1 brad if (SCMD_IS_HOLE(reg)) {
254 1.1 brad return 0;
255 1.1 brad }
256 1.1 brad
257 1.1 brad switch (uart_subtype) {
258 1.1 brad case UART_IS_PURE_UART:
259 1.1 brad sprintf(cmdbuf, "W%02X%02X\r\n", reg, buf);
260 1.1 brad err = pure_uart_send_cmd(fd, cmdbuf, qbuf, 7);
261 1.1 brad if (! err) {
262 1.1 brad err = uart_get_response(fd, debug, qbuf, 10);
263 1.1 brad }
264 1.1 brad break;
265 1.1 brad case UART_IS_SPI_USERLAND:
266 1.1 brad spi_t.sit_addr = uart_spi_slave_addr;
267 1.1 brad reg = reg & 0x7f;
268 1.1 brad spi_t.sit_send = ®
269 1.1 brad spi_t.sit_sendlen = 1;
270 1.1 brad spi_t.sit_recv = NULL;
271 1.1 brad spi_t.sit_recvlen = 0;
272 1.1 brad
273 1.1 brad err = ioctl(fd,SPI_IOCTL_TRANSFER,&spi_t);
274 1.1 brad if (debug)
275 1.1 brad fprintf(stderr,"uart_phy_write_register: IOCTL UL SPI write send 1 err: %d ; reg: %02x ; xreg: %02x\n",
276 1.1 brad err,reg,reg & 0x7f);
277 1.1 brad
278 1.1 brad if (err == -1)
279 1.1 brad return errno;
280 1.1 brad
281 1.1 brad spi_t.sit_addr = uart_spi_slave_addr;
282 1.1 brad spi_t.sit_send = &buf;
283 1.1 brad spi_t.sit_sendlen = 1;
284 1.1 brad spi_t.sit_recv = NULL;
285 1.1 brad spi_t.sit_recvlen = 0;
286 1.1 brad
287 1.1 brad err = ioctl(fd,SPI_IOCTL_TRANSFER,&spi_t);
288 1.1 brad if (debug)
289 1.1 brad fprintf(stderr,"uart_phy_write_register: IOCTL UL SPI write send 2 err: %d ; buf: %02x\n",
290 1.1 brad err,buf);
291 1.1 brad
292 1.1 brad if (err == -1)
293 1.1 brad return errno;
294 1.1 brad
295 1.1 brad ts.tv_sec = 0;
296 1.1 brad ts.tv_nsec = 50;
297 1.1 brad nanosleep(&ts,NULL);
298 1.1 brad
299 1.1 brad break;
300 1.1 brad default:
301 1.1 brad return EINVAL;
302 1.1 brad break;
303 1.1 brad }
304 1.1 brad
305 1.1 brad return err;
306 1.1 brad }
307 1.1 brad
308 1.1 brad static int
309 1.1 brad uart_local_read_register(int fd, bool debug, uint8_t reg, uint8_t reg_end, uint8_t *r)
310 1.1 brad {
311 1.1 brad uint8_t b;
312 1.1 brad int err = 0;
313 1.1 brad
314 1.1 brad for(int q = reg, g = 0; q <= reg_end; q++, g++) {
315 1.1 brad err = uart_phy_read_register(fd, debug, q, &b);
316 1.1 brad if (!err)
317 1.1 brad r[g] = b;
318 1.1 brad }
319 1.1 brad
320 1.1 brad return err;
321 1.1 brad }
322 1.1 brad
323 1.1 brad /* When speaking to a SCMD device in any uart mode the view port for
324 1.1 brad * chained slave modules has to be handled in userland. This is simular
325 1.1 brad * to what the scmd(4) kernel driver ends up doing, but is much slower.
326 1.1 brad */
327 1.1 brad static int
328 1.1 brad uart_set_view_port(int fd, bool debug, int a_module, uint8_t vpi2creg)
329 1.1 brad {
330 1.1 brad int err;
331 1.1 brad uint8_t vpi2caddr = (SCMD_REMOTE_ADDR_LOW + a_module) - 1;
332 1.1 brad
333 1.1 brad if (debug)
334 1.1 brad fprintf(stderr, "uart_set_view_port: View port addr: %02x ; View port register: %02x\n",
335 1.1 brad vpi2caddr, vpi2creg);
336 1.1 brad
337 1.1 brad err = uart_phy_write_register(fd, debug, SCMD_REG_REM_ADDR, vpi2caddr);
338 1.1 brad if (! err)
339 1.1 brad err = uart_phy_write_register(fd, debug, SCMD_REG_REM_OFFSET, vpi2creg);
340 1.1 brad
341 1.1 brad return err;
342 1.1 brad }
343 1.1 brad
344 1.1 brad static int
345 1.1 brad uart_remote_read_register(int fd, bool debug, int a_module, uint8_t reg, uint8_t reg_end, uint8_t *r)
346 1.1 brad {
347 1.1 brad int err;
348 1.1 brad int c;
349 1.1 brad uint8_t b;
350 1.1 brad
351 1.1 brad for(int q = reg, g = 0; q <= reg_end; q++, g++) {
352 1.1 brad err = uart_set_view_port(fd, debug, a_module, q);
353 1.1 brad if (err)
354 1.1 brad break;
355 1.1 brad
356 1.1 brad b = 0xff; /* you can write anything here.. it doesn't matter */
357 1.1 brad err = uart_phy_write_register(fd, debug, SCMD_REG_REM_READ, b);
358 1.1 brad if (err)
359 1.1 brad break;
360 1.1 brad
361 1.1 brad /* So ... there is no way to really know that the data is ready and
362 1.1 brad * there is no way to know if there was an error in the master module reading
363 1.1 brad * the data from the slave module. The data sheet says wait 5ms.. so we will
364 1.1 brad * wait a bit and see if the register cleared, but don't wait forever... I
365 1.1 brad * can't see how it would not be possible to read junk at times.
366 1.1 brad */
367 1.1 brad c = 0;
368 1.1 brad do {
369 1.1 brad sleep(1);
370 1.1 brad err = uart_phy_read_register(fd, debug, SCMD_REG_REM_READ, &b);
371 1.1 brad c++;
372 1.1 brad } while ((c < 10) && (b != 0x00) && (!err));
373 1.1 brad
374 1.1 brad /* We can only hope that whatever was read from the slave module is there */
375 1.1 brad if (err)
376 1.1 brad break;
377 1.1 brad err = uart_phy_read_register(fd, debug, SCMD_REG_REM_DATA_RD, &b);
378 1.1 brad if (err)
379 1.1 brad break;
380 1.1 brad r[g] = b;
381 1.1 brad }
382 1.1 brad
383 1.1 brad return err;
384 1.1 brad }
385 1.1 brad
386 1.1 brad void
387 1.1 brad uart_set_subtype(int subt, int spi_s_addr)
388 1.1 brad {
389 1.1 brad uart_subtype = subt;
390 1.1 brad uart_spi_slave_addr = spi_s_addr;
391 1.1 brad
392 1.1 brad return;
393 1.1 brad }
394 1.1 brad
395 1.1 brad /* Unlike scmd(4) local reads and remote module reads are done very
396 1.1 brad * differently.
397 1.1 brad */
398 1.1 brad int
399 1.1 brad uart_read_register(int fd, bool debug, int a_module, uint8_t reg, uint8_t reg_end, uint8_t *r)
400 1.1 brad {
401 1.1 brad int err;
402 1.1 brad
403 1.1 brad if (reg > SCMD_LAST_REG ||
404 1.1 brad reg_end > SCMD_LAST_REG)
405 1.1 brad return EINVAL;
406 1.1 brad
407 1.1 brad if (reg_end < reg)
408 1.1 brad return EINVAL;
409 1.1 brad
410 1.1 brad err = uart_clear(fd, debug);
411 1.1 brad if (! err) {
412 1.1 brad if (a_module == 0) {
413 1.1 brad err = uart_local_read_register(fd, debug, reg, reg_end, r);
414 1.1 brad } else {
415 1.1 brad err = uart_remote_read_register(fd, debug, a_module, reg, reg_end, r);
416 1.1 brad }
417 1.1 brad }
418 1.1 brad
419 1.1 brad return err;
420 1.1 brad }
421 1.1 brad
422 1.1 brad static int
423 1.1 brad uart_remote_write_register(int fd, bool debug, int a_module, uint8_t reg, uint8_t reg_v)
424 1.1 brad {
425 1.1 brad int err;
426 1.1 brad int c;
427 1.1 brad uint8_t b;
428 1.1 brad
429 1.1 brad err = uart_set_view_port(fd, debug, a_module, reg);
430 1.1 brad if (! err) {
431 1.1 brad /* We just sort of send this write off and wait to see if the register
432 1.1 brad * clears. There really isn't any indication that the data made it to the
433 1.1 brad * slave modules.
434 1.1 brad */
435 1.1 brad err = uart_phy_write_register(fd, debug, SCMD_REG_REM_DATA_WR, reg_v);
436 1.1 brad if (! err) {
437 1.1 brad b = 0xff; /* you can write anything here.. it doesn't matter */
438 1.1 brad err = uart_phy_write_register(fd, debug, SCMD_REG_REM_WRITE, b);
439 1.1 brad if (! err) {
440 1.1 brad c = 0;
441 1.1 brad do {
442 1.1 brad sleep(1);
443 1.1 brad err = uart_phy_read_register(fd, debug, SCMD_REG_REM_WRITE, &b);
444 1.1 brad c++;
445 1.1 brad } while ((c < 10) && (b != 0x00) && (!err));
446 1.1 brad }
447 1.1 brad }
448 1.1 brad }
449 1.1 brad
450 1.1 brad return err;
451 1.1 brad }
452 1.1 brad
453 1.1 brad /* Like reads, writes are done very differently between scmd(4) and
454 1.1 brad * the uart modes.
455 1.1 brad */
456 1.1 brad int
457 1.1 brad uart_write_register(int fd, bool debug, int a_module, uint8_t reg, uint8_t reg_v)
458 1.1 brad {
459 1.1 brad int err;
460 1.1 brad
461 1.1 brad if (reg > SCMD_LAST_REG)
462 1.1 brad return EINVAL;
463 1.1 brad
464 1.1 brad err = uart_clear(fd, debug);
465 1.1 brad if (! err) {
466 1.1 brad if (a_module == 0) {
467 1.1 brad err = uart_phy_write_register(fd, debug, reg, reg_v);
468 1.1 brad } else {
469 1.1 brad err = uart_remote_write_register(fd, debug, a_module, reg, reg_v);
470 1.1 brad }
471 1.1 brad }
472 1.1 brad
473 1.1 brad return err;
474 1.1 brad }
475 1.1 brad
476 1.1 brad /* This is a special ability to do a single SPI receive that has the
477 1.1 brad * hope of resyncing the device should it get out of sync in SPI mode.
478 1.1 brad * This will work for either SPI userland mode or scmd(4) when attached
479 1.1 brad * to the SPI bus as you can still write to /dev/spiN then too.
480 1.1 brad */
481 1.1 brad int
482 1.1 brad uart_ul_spi_read_one(int fd, bool debug)
483 1.1 brad {
484 1.1 brad int err = 0;
485 1.1 brad struct timespec ts;
486 1.1 brad struct spi_ioctl_transfer spi_t;
487 1.1 brad uint8_t b;
488 1.1 brad
489 1.1 brad if (uart_subtype == UART_IS_SPI_USERLAND) {
490 1.1 brad spi_t.sit_addr = uart_spi_slave_addr;
491 1.1 brad spi_t.sit_send = NULL;
492 1.1 brad spi_t.sit_sendlen = 0;
493 1.1 brad b = SCMD_HOLE_VALUE;
494 1.1 brad spi_t.sit_recv = &b;
495 1.1 brad spi_t.sit_recvlen = 1;
496 1.1 brad
497 1.1 brad err = ioctl(fd,SPI_IOCTL_TRANSFER,&spi_t);
498 1.1 brad if (debug)
499 1.1 brad fprintf(stderr,"uart_ul_spi_read_one: IOCTL UL SPI receive 1 err: %d ; b: %02x\n",
500 1.1 brad err,b);
501 1.1 brad
502 1.1 brad if (err == -1)
503 1.1 brad return errno;
504 1.1 brad
505 1.1 brad ts.tv_sec = 0;
506 1.1 brad ts.tv_nsec = 50;
507 1.1 brad nanosleep(&ts,NULL);
508 1.1 brad }
509 1.1 brad
510 1.1 brad return err;
511 1.1 brad }
512