1*d9bab776SHans de Goede // SPDX-License-Identifier: GPL-2.0-or-later
2*d9bab776SHans de Goede /*
3*d9bab776SHans de Goede * Dell AIO Serial Backlight board emulator for testing
4*d9bab776SHans de Goede * the Linux dell-uart-backlight driver.
5*d9bab776SHans de Goede *
6*d9bab776SHans de Goede * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
7*d9bab776SHans de Goede */
8*d9bab776SHans de Goede #include <errno.h>
9*d9bab776SHans de Goede #include <fcntl.h>
10*d9bab776SHans de Goede #include <signal.h>
11*d9bab776SHans de Goede #include <stdio.h>
12*d9bab776SHans de Goede #include <stdlib.h>
13*d9bab776SHans de Goede #include <string.h>
14*d9bab776SHans de Goede #include <sys/ioctl.h>
15*d9bab776SHans de Goede #include <sys/stat.h>
16*d9bab776SHans de Goede #include <sys/types.h>
17*d9bab776SHans de Goede #include <sys/un.h>
18*d9bab776SHans de Goede #include <termios.h>
19*d9bab776SHans de Goede #include <unistd.h>
20*d9bab776SHans de Goede
21*d9bab776SHans de Goede int serial_fd;
22*d9bab776SHans de Goede int brightness = 50;
23*d9bab776SHans de Goede
dell_uart_checksum(unsigned char * buf,int len)24*d9bab776SHans de Goede static unsigned char dell_uart_checksum(unsigned char *buf, int len)
25*d9bab776SHans de Goede {
26*d9bab776SHans de Goede unsigned char val = 0;
27*d9bab776SHans de Goede
28*d9bab776SHans de Goede while (len-- > 0)
29*d9bab776SHans de Goede val += buf[len];
30*d9bab776SHans de Goede
31*d9bab776SHans de Goede return val ^ 0xff;
32*d9bab776SHans de Goede }
33*d9bab776SHans de Goede
34*d9bab776SHans de Goede /* read() will return -1 on SIGINT / SIGTERM causing the mainloop to cleanly exit */
signalhdlr(int signum)35*d9bab776SHans de Goede void signalhdlr(int signum)
36*d9bab776SHans de Goede {
37*d9bab776SHans de Goede }
38*d9bab776SHans de Goede
main(int argc,char * argv[])39*d9bab776SHans de Goede int main(int argc, char *argv[])
40*d9bab776SHans de Goede {
41*d9bab776SHans de Goede struct sigaction sigact = { .sa_handler = signalhdlr };
42*d9bab776SHans de Goede unsigned char buf[4], csum, response[32];
43*d9bab776SHans de Goede const char *version_str = "PHI23-V321";
44*d9bab776SHans de Goede struct termios tty, saved_tty;
45*d9bab776SHans de Goede int ret, idx, len = 0;
46*d9bab776SHans de Goede
47*d9bab776SHans de Goede if (argc != 2) {
48*d9bab776SHans de Goede fprintf(stderr, "Invalid or missing arguments\n");
49*d9bab776SHans de Goede fprintf(stderr, "Usage: %s <serial-port>\n", argv[0]);
50*d9bab776SHans de Goede return 1;
51*d9bab776SHans de Goede }
52*d9bab776SHans de Goede
53*d9bab776SHans de Goede serial_fd = open(argv[1], O_RDWR | O_NOCTTY);
54*d9bab776SHans de Goede if (serial_fd == -1) {
55*d9bab776SHans de Goede fprintf(stderr, "Error opening %s: %s\n", argv[1], strerror(errno));
56*d9bab776SHans de Goede return 1;
57*d9bab776SHans de Goede }
58*d9bab776SHans de Goede
59*d9bab776SHans de Goede ret = tcgetattr(serial_fd, &tty);
60*d9bab776SHans de Goede if (ret == -1) {
61*d9bab776SHans de Goede fprintf(stderr, "Error getting tcattr: %s\n", strerror(errno));
62*d9bab776SHans de Goede goto out_close;
63*d9bab776SHans de Goede }
64*d9bab776SHans de Goede saved_tty = tty;
65*d9bab776SHans de Goede
66*d9bab776SHans de Goede cfsetspeed(&tty, 9600);
67*d9bab776SHans de Goede cfmakeraw(&tty);
68*d9bab776SHans de Goede tty.c_cflag &= ~CSTOPB;
69*d9bab776SHans de Goede tty.c_cflag &= ~CRTSCTS;
70*d9bab776SHans de Goede tty.c_cflag |= CLOCAL | CREAD;
71*d9bab776SHans de Goede
72*d9bab776SHans de Goede ret = tcsetattr(serial_fd, TCSANOW, &tty);
73*d9bab776SHans de Goede if (ret == -1) {
74*d9bab776SHans de Goede fprintf(stderr, "Error setting tcattr: %s\n", strerror(errno));
75*d9bab776SHans de Goede goto out_restore;
76*d9bab776SHans de Goede }
77*d9bab776SHans de Goede
78*d9bab776SHans de Goede sigaction(SIGINT, &sigact, 0);
79*d9bab776SHans de Goede sigaction(SIGTERM, &sigact, 0);
80*d9bab776SHans de Goede
81*d9bab776SHans de Goede idx = 0;
82*d9bab776SHans de Goede while (read(serial_fd, &buf[idx], 1) == 1) {
83*d9bab776SHans de Goede if (idx == 0) {
84*d9bab776SHans de Goede switch (buf[0]) {
85*d9bab776SHans de Goede /* 3 MSB bits: cmd-len + 01010 SOF marker */
86*d9bab776SHans de Goede case 0x6a: len = 3; break;
87*d9bab776SHans de Goede case 0x8a: len = 4; break;
88*d9bab776SHans de Goede default:
89*d9bab776SHans de Goede fprintf(stderr, "Error unexpected first byte: 0x%02x\n", buf[0]);
90*d9bab776SHans de Goede continue; /* Try to sync up with sender */
91*d9bab776SHans de Goede }
92*d9bab776SHans de Goede }
93*d9bab776SHans de Goede
94*d9bab776SHans de Goede /* Process msg when len bytes have been received */
95*d9bab776SHans de Goede if (idx != (len - 1)) {
96*d9bab776SHans de Goede idx++;
97*d9bab776SHans de Goede continue;
98*d9bab776SHans de Goede }
99*d9bab776SHans de Goede
100*d9bab776SHans de Goede /* Reset idx for next command */
101*d9bab776SHans de Goede idx = 0;
102*d9bab776SHans de Goede
103*d9bab776SHans de Goede csum = dell_uart_checksum(buf, len - 1);
104*d9bab776SHans de Goede if (buf[len - 1] != csum) {
105*d9bab776SHans de Goede fprintf(stderr, "Error checksum mismatch got 0x%02x expected 0x%02x\n",
106*d9bab776SHans de Goede buf[len - 1], csum);
107*d9bab776SHans de Goede continue;
108*d9bab776SHans de Goede }
109*d9bab776SHans de Goede
110*d9bab776SHans de Goede switch ((buf[0] << 8) | buf[1]) {
111*d9bab776SHans de Goede case 0x6a06: /* cmd = 0x06, get version */
112*d9bab776SHans de Goede len = strlen(version_str);
113*d9bab776SHans de Goede strcpy((char *)&response[2], version_str);
114*d9bab776SHans de Goede printf("Get version, reply: %s\n", version_str);
115*d9bab776SHans de Goede break;
116*d9bab776SHans de Goede case 0x8a0b: /* cmd = 0x0b, set brightness */
117*d9bab776SHans de Goede if (buf[2] > 100) {
118*d9bab776SHans de Goede fprintf(stderr, "Error invalid brightness param: %d\n", buf[2]);
119*d9bab776SHans de Goede continue;
120*d9bab776SHans de Goede }
121*d9bab776SHans de Goede
122*d9bab776SHans de Goede len = 0;
123*d9bab776SHans de Goede brightness = buf[2];
124*d9bab776SHans de Goede printf("Set brightness %d\n", brightness);
125*d9bab776SHans de Goede break;
126*d9bab776SHans de Goede case 0x6a0c: /* cmd = 0x0c, get brightness */
127*d9bab776SHans de Goede len = 1;
128*d9bab776SHans de Goede response[2] = brightness;
129*d9bab776SHans de Goede printf("Get brightness, reply: %d\n", brightness);
130*d9bab776SHans de Goede break;
131*d9bab776SHans de Goede case 0x8a0e: /* cmd = 0x0e, set backlight power */
132*d9bab776SHans de Goede if (buf[2] != 0 && buf[2] != 1) {
133*d9bab776SHans de Goede fprintf(stderr, "Error invalid set power param: %d\n", buf[2]);
134*d9bab776SHans de Goede continue;
135*d9bab776SHans de Goede }
136*d9bab776SHans de Goede
137*d9bab776SHans de Goede len = 0;
138*d9bab776SHans de Goede printf("Set power %d\n", buf[2]);
139*d9bab776SHans de Goede break;
140*d9bab776SHans de Goede default:
141*d9bab776SHans de Goede fprintf(stderr, "Error unknown cmd 0x%04x\n",
142*d9bab776SHans de Goede (buf[0] << 8) | buf[1]);
143*d9bab776SHans de Goede continue;
144*d9bab776SHans de Goede }
145*d9bab776SHans de Goede
146*d9bab776SHans de Goede /* Respond with <total-len> <cmd> <data...> <csum> */
147*d9bab776SHans de Goede response[0] = len + 3; /* response length in bytes */
148*d9bab776SHans de Goede response[1] = buf[1]; /* ack cmd */
149*d9bab776SHans de Goede csum = dell_uart_checksum(response, len + 2);
150*d9bab776SHans de Goede response[len + 2] = csum;
151*d9bab776SHans de Goede ret = write(serial_fd, response, response[0]);
152*d9bab776SHans de Goede if (ret != (response[0]))
153*d9bab776SHans de Goede fprintf(stderr, "Error writing %d bytes: %d\n",
154*d9bab776SHans de Goede response[0], ret);
155*d9bab776SHans de Goede }
156*d9bab776SHans de Goede
157*d9bab776SHans de Goede ret = 0;
158*d9bab776SHans de Goede out_restore:
159*d9bab776SHans de Goede tcsetattr(serial_fd, TCSANOW, &saved_tty);
160*d9bab776SHans de Goede out_close:
161*d9bab776SHans de Goede close(serial_fd);
162*d9bab776SHans de Goede return ret;
163*d9bab776SHans de Goede }
164