1/*
2 * Control LCD module hung off parallel port using the
3 * ppi 'geek port' interface.
4 */
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <ctype.h>
10#include <fcntl.h>
11#include <unistd.h>
12#include <err.h>
13#include <sysexits.h>
14
15#include <dev/ppbus/ppbconf.h>
16#include <dev/ppbus/ppi.h>
17
18#define debug(lev, fmt, args...)	if (debuglevel >= lev) fprintf(stderr, fmt "\n" , ## args);
19
20static void	usage(void);
21static char	*progname;
22
23#define	DEFAULT_DEVICE	"/dev/ppi0"
24
25/* Driver functions */
26static void	hd44780_prepare(char *devname, char *options);
27static void	hd44780_finish(void);
28static void	hd44780_command(int cmd);
29static void	hd44780_putc(int c);
30
31/*
32 * Commands
33 * Note that unrecognised command escapes are passed through with
34 * the command value set to the ASCII value of the escaped character.
35 */
36#define CMD_RESET	0
37#define CMD_BKSP	1
38#define CMD_CLR		2
39#define CMD_NL		3
40#define CMD_CR		4
41#define CMD_HOME	5
42
43#define MAX_DRVOPT	10	/* maximum driver-specific options */
44
45struct lcd_driver
46{
47    char	*l_code;
48    char	*l_name;
49    char	*l_options[MAX_DRVOPT];
50    void	(* l_prepare)(char *name, char *options);
51    void	(* l_finish)(void);
52    void	(* l_command)(int cmd);
53    void	(* l_putc)(int c);
54};
55
56static struct lcd_driver lcd_drivertab[] = {
57    {
58	"hd44780",
59	"Hitachi HD44780 and compatibles",
60	{
61	    "Reset options:",
62	    "    1     1-line display (default 2)",
63	    "    B     Cursor blink enable",
64	    "    C     Cursor enable",
65	    "    F     Large font select",
66	    NULL
67	},
68	hd44780_prepare,
69	hd44780_finish,
70	hd44780_command,
71	hd44780_putc
72    },
73    {
74	NULL,
75	NULL,
76	{
77	    NULL
78	},
79	NULL,
80	NULL
81    }
82};
83
84static void	do_char(struct lcd_driver *driver, char ch);
85
86int	debuglevel = 0;
87int	vflag = 0;
88
89int
90main(int argc, char *argv[])
91{
92    extern char		*optarg;
93    extern int		optind;
94    struct lcd_driver	*driver = &lcd_drivertab[0];
95    char		*drivertype, *cp;
96    char		*devname = DEFAULT_DEVICE;
97    char		*drvopts = NULL;
98    int			ch, i;
99
100    if ((progname = strrchr(argv[0], '/'))) {
101	progname++;
102    } else {
103	progname = argv[0];
104    }
105
106    drivertype = getenv("LCD_TYPE");
107
108    while ((ch = getopt(argc, argv, "Dd:f:o:v")) != -1) {
109	switch(ch) {
110	case 'D':
111	    debuglevel++;
112	    break;
113	case 'd':
114	    drivertype = optarg;
115	    break;
116	case 'f':
117	    devname = optarg;
118	    break;
119	case 'o':
120	    drvopts = optarg;
121	    break;
122	case 'v':
123	    vflag = 1;
124	    break;
125	default:
126	    usage();
127	}
128    }
129    argc -= optind;
130    argv += optind;
131
132    /* If an LCD type was specified, look it up */
133    if (drivertype != NULL) {
134	driver = NULL;
135	for (i = 0; lcd_drivertab[i].l_code != NULL; i++) {
136	    if (!strcmp(drivertype, lcd_drivertab[i].l_code)) {
137		driver = &lcd_drivertab[i];
138		break;
139	    }
140	}
141	if (driver == NULL) {
142	    warnx("LCD driver '%s' not known", drivertype);
143	    usage();
144	}
145    }
146    debug(1, "Driver selected for %s", driver->l_name);
147    driver->l_prepare(devname, drvopts);
148    atexit(driver->l_finish);
149
150    if (argc > 0) {
151	debug(2, "reading input from %d argument%s", argc, (argc > 1) ? "s" : "");
152	for (i = 0; i < argc; i++)
153	    for (cp = argv[i]; *cp; cp++)
154		do_char(driver, *cp);
155    } else {
156	debug(2, "reading input from stdin");
157	setvbuf(stdin, NULL, _IONBF, 0);
158	while ((ch = fgetc(stdin)) != EOF)
159	    do_char(driver, (char)ch);
160    }
161    exit(EX_OK);
162}
163
164static void
165usage(void)
166{
167    int		i, j;
168
169    fprintf(stderr, "usage: %s [-v] [-d drivername] [-f device] [-o options] [args...]\n", progname);
170    fprintf(stderr, "   -D      Increase debugging\n");
171    fprintf(stderr, "   -f      Specify device, default is '%s'\n", DEFAULT_DEVICE);
172    fprintf(stderr, "   -d      Specify driver, one of:\n");
173    for (i = 0; lcd_drivertab[i].l_code != NULL; i++) {
174	fprintf(stderr, "              %-10s (%s)%s\n",
175		lcd_drivertab[i].l_code, lcd_drivertab[i].l_name, (i == 0) ? " *default*" : "");
176	if (lcd_drivertab[i].l_options[0] != NULL) {
177
178	    for (j = 0; lcd_drivertab[i].l_options[j] != NULL; j++)
179		fprintf(stderr, "                  %s\n", lcd_drivertab[i].l_options[j]);
180	}
181    }
182    fprintf(stderr, "  -o       Specify driver option string\n");
183    fprintf(stderr, "  args     Message strings.  Embedded escapes supported:\n");
184    fprintf(stderr, "                  \\b	Backspace\n");
185    fprintf(stderr, "                  \\f	Clear display, home cursor\n");
186    fprintf(stderr, "                  \\n	Newline\n");
187    fprintf(stderr, "                  \\r	Carriage return\n");
188    fprintf(stderr, "                  \\R	Reset display\n");
189    fprintf(stderr, "                  \\v	Home cursor\n");
190    fprintf(stderr, "                  \\\\	Literal \\\n");
191    fprintf(stderr, "           If args not supplied, strings are read from standard input\n");
192    exit(EX_USAGE);
193}
194
195static void
196do_char(struct lcd_driver *driver, char ch)
197{
198    static int	esc = 0;
199
200    if (esc) {
201	switch(ch) {
202	case 'b':
203	    driver->l_command(CMD_BKSP);
204	    break;
205	case 'f':
206	    driver->l_command(CMD_CLR);
207	    break;
208	case 'n':
209	    driver->l_command(CMD_NL);
210	    break;
211	case 'r':
212	    driver->l_command(CMD_CR);
213	    break;
214	case 'R':
215	    driver->l_command(CMD_RESET);
216	    break;
217	case 'v':
218	    driver->l_command(CMD_HOME);
219	    break;
220	case '\\':
221	    driver->l_putc('\\');
222	    break;
223	default:
224	    driver->l_command(ch);
225	    break;
226	}
227	esc = 0;
228    } else {
229	if (ch == '\\') {
230	    esc = 1;
231	} else {
232	    if (vflag || isprint(ch))
233		driver->l_putc(ch);
234	}
235    }
236}
237
238
239/******************************************************************************
240 * Driver for the Hitachi HD44780.  This is probably *the* most common driver
241 * to be found on one- and two-line alphanumeric LCDs.
242 *
243 * This driver assumes the following connections :
244 *
245 * Parallel Port	LCD Module
246 * --------------------------------
247 * Strobe (1)		Enable (6)
248 * Data (2-9)		Data (7-14)
249 * Select In (17)	RS (4)
250 * Auto Feed (14)	R/W (5)
251 *
252 * In addition, power must be supplied to the module, normally with
253 * a circuit similar to this:
254 *
255 * VCC (+5V) O------o-------o--------O Module pin 2
256 *                  |       | +
257 *                  /      ---
258 *                  \      --- 1uF
259 *                  /       | -
260 *                  \ <-----o--------O Module pin 3
261 *                  /
262 *                  \
263 *                  |
264 * GND       O------o----------------O Module pin 1
265 *
266 * The ground line should also be connected to the parallel port, on
267 * one of the ground pins (eg. pin 25).
268 *
269 * Note that the pinning on some LCD modules has the odd and even pins
270 * arranged as though reversed; check carefully before connecting a module
271 * as it is possible to toast the HD44780 if the power is reversed.
272 */
273
274static int	hd_fd;
275static u_int8_t	hd_cbits;
276static int	hd_lines = 2;
277static int	hd_blink = 0;
278static int 	hd_cursor = 0;
279static int	hd_font = 0;
280
281#define HD_COMMAND	SELECTIN
282#define HD_DATA		0
283#define HD_READ		0
284#define HD_WRITE	AUTOFEED
285
286#define HD_BF		0x80		/* internal busy flag */
287#define HD_ADDRMASK	0x7f		/* DDRAM address mask */
288
289#define hd_sctrl(v)	{u_int8_t _val; _val = hd_cbits | v; ioctl(hd_fd, PPISCTRL, &_val);}
290#define hd_sdata(v)	{u_int8_t _val; _val = v; ioctl(hd_fd, PPISDATA, &_val);}
291#define hd_gdata(v)	ioctl(hd_fd, PPIGDATA, &v)
292
293static void
294hd44780_output(int type, int data)
295{
296    debug(3, "%s -> 0x%02x", (type == HD_COMMAND) ? "cmd " : "data", data);
297    hd_sctrl(type | HD_WRITE | STROBE);	/* set direction, address */
298    hd_sctrl(type | HD_WRITE);		/* raise E */
299    hd_sdata((u_int8_t) data);		/* drive data */
300    hd_sctrl(type | HD_WRITE | STROBE);	/* lower E */
301}
302
303static int
304hd44780_input(int type)
305{
306    u_int8_t	val;
307
308    hd_sctrl(type | HD_READ | STROBE);	/* set direction, address */
309    hd_sctrl(type | HD_READ);		/* raise E */
310    hd_gdata(val);			/* read data */
311    hd_sctrl(type | HD_READ | STROBE);	/* lower E */
312
313    debug(3, "0x%02x -> %s", val, (type == HD_COMMAND) ? "cmd " : "data");
314    return(val);
315}
316
317static void
318hd44780_prepare(char *devname, char *options)
319{
320    char	*cp = options;
321
322    if ((hd_fd = open(devname, O_RDWR, 0)) == -1)
323	err(EX_OSFILE, "can't open '%s'", devname);
324
325    /* parse options */
326    while (cp && *cp) {
327	switch (*cp++) {
328	case '1':
329	    hd_lines = 1;
330	    break;
331	case 'B':
332	    hd_blink = 1;
333	    break;
334	case 'C':
335	    hd_cursor = 1;
336	    break;
337	case 'F':
338	    hd_font = 1;
339	    break;
340	default:
341	    errx(EX_USAGE, "hd44780: unknown option code '%c'", *(cp-1));
342	}
343    }
344
345    /* Put LCD in idle state */
346    if (ioctl(hd_fd, PPIGCTRL, &hd_cbits))		/* save other control bits */
347	err(EX_IOERR, "ioctl PPIGCTRL failed (not a ppi device?)");
348    hd_cbits &= ~(STROBE | SELECTIN | AUTOFEED);	/* set strobe, RS, R/W low */
349    debug(2, "static control bits 0x%x", hd_cbits);
350    hd_sctrl(STROBE);
351    hd_sdata(0);
352
353}
354
355static void
356hd44780_finish(void)
357{
358    close(hd_fd);
359}
360
361static void
362hd44780_command(int cmd)
363{
364    u_int8_t	val;
365
366    switch (cmd) {
367    case CMD_RESET:	/* full manual reset and reconfigure as per datasheet */
368	debug(1, "hd44780: reset to %d lines, %s font,%s%s cursor",
369	      hd_lines, hd_font ? "5x10" : "5x7", hd_cursor ? "" : " no", hd_blink ? " blinking" : "");
370	val = 0x30;
371	if (hd_lines == 2)
372	    val |= 0x08;
373	if (hd_font)
374	    val |= 0x04;
375	hd44780_output(HD_COMMAND, val);
376	usleep(10000);
377	hd44780_output(HD_COMMAND, val);
378	usleep(1000);
379	hd44780_output(HD_COMMAND, val);
380	usleep(1000);
381	val = 0x08;				/* display off */
382	hd44780_output(HD_COMMAND, val);
383	usleep(1000);
384	val |= 0x04;				/* display on */
385	if (hd_cursor)
386	    val |= 0x02;
387	if (hd_blink)
388	    val |= 0x01;
389	hd44780_output(HD_COMMAND, val);
390	usleep(1000);
391	hd44780_output(HD_COMMAND, 0x06);	/* shift cursor by increment */
392	usleep(1000);
393	/* FALLTHROUGH */
394
395    case CMD_CLR:
396	hd44780_output(HD_COMMAND, 0x01);
397	usleep(2000);
398	break;
399
400    case CMD_BKSP:
401	hd44780_output(HD_DATA, 0x10);		/* shift cursor left one */
402	break;
403
404    case CMD_NL:
405	if (hd_lines == 2)
406	    hd44780_output(HD_COMMAND, 0xc0);	/* beginning of second line */
407	break;
408
409    case CMD_CR:
410	/* XXX will not work in 4-line mode, or where readback fails */
411	val = hd44780_input(HD_COMMAND) & 0x3f;	/* mask character position, save line pos */
412	hd44780_output(HD_COMMAND, 0x80 | val);
413	break;
414
415    case CMD_HOME:
416	hd44780_output(HD_COMMAND, 0x02);
417	usleep(2000);
418	break;
419
420    default:
421	if (isprint(cmd)) {
422	    warnx("unknown command %c", cmd);
423	} else {
424	    warnx("unknown command 0x%x", cmd);
425	}
426    }
427    usleep(40);
428}
429
430static void
431hd44780_putc(int c)
432{
433    hd44780_output(HD_DATA, c);
434    usleep(40);
435}
436
437