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