Subject: lcd driver
To: None <port-cobalt@netbsd.org>
From: Ted Unangst <tedu@stanford.edu>
List: port-cobalt
Date: 11/23/2001 02:45:25
Hello folks.  Fueled by turkey, I present the latest NetBSD
driver. :)

Rebuild your kernel, mknod lcd, and away you go.  I've also got a little
tester to play with too.  See next mail.

diff -u cobalt.orig/include/lcd.h cobalt/include/lcd.h
--- cobalt.orig/include/lcd.h	Fri Nov 23 01:25:35 2001
+++ cobalt/include/lcd.h	Thu Nov 22 23:23:29 2001
@@ -0,0 +1,41 @@
+/*
+ * BSD Driver for Cobalt LCD
+ * Copyright 2001 Ted Unangst
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED WITHOUT WARRANTY.  USE IT AT YOUR OWN RISK.
+ */
+
+#ifdef _KERNEL
+int lcdopen(dev_t dev, int flags, int fmt, struct proc *p);
+int lcdclose(dev_t dev, int flags, int fmt, struct proc *p);
+int lcdread(dev_t dev, struct uio *uio, int flags);
+int lcdwrite(dev_t dev, struct uio *uio, int flags);
+int lcdioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p);
+#endif
+
+#define LCD_OFF		0
+#define LCD_ON		1
+#define LCD_CLEAR	2
+#define LCD_CURSOROFF	3
+#define LCD_CURSORON	4
+#define LCD_BLINKOFF	5
+#define LCD_CURSORLEFT	6
+#define LCD_CURSORRIGHT	7
+#define LCD_LEFT	8
+#define LCD_RIGHT	9
+#define LCD_HOME	10
+#define LCD_READ	11
+#define LCD_WRITE	12
+#define LCD_RESET	14
+#define LCD_CURSORSET	15
+#define LCD_CURSORGET	16
+
diff -u cobalt.orig/conf/GENERIC cobalt/conf/GENERIC
--- cobalt.orig/conf/GENERIC	Thu Nov 22 21:00:24 2001
+++ cobalt/conf/GENERIC	Thu Nov 22 02:10:02 2001
@@ -111,6 +111,8 @@
 pchb* 		at pci? dev ? function ?
 pcib* 		at pci? dev ? function ?

+lcd0		at mainbus0
+
 # PCI serial/parallel interfaces
 #puc* 		at pci? dev ? function ? 	# PCI "universal" comm. cards
 #com* 		at puc? port ?
diff -u cobalt.orig/conf/files.cobalt cobalt/conf/files.cobalt
--- cobalt.orig/conf/files.cobalt	Tue Jan 16 16:07:23 2001
+++ cobalt/conf/files.cobalt	Wed Nov 21 21:50:31 2001
@@ -19,9 +19,9 @@
 #attach zsc at mainbus with zs_mainbus
 #file arch/cobalt/dev/zs_mainbus.c	zsc_mainbus needs-flag

-#device panel
-#attach panel at mainbus
-#file arch/cobalt/dev/panel.c		panel
+device lcd
+attach lcd at mainbus
+file arch/cobalt/dev/lcd.c		lcd

 device gt: pcibus
 attach gt at mainbus
diff -u cobalt.orig/dev/lcd.c cobalt/dev/lcd.c
--- cobalt.orig/dev/lcd.c	Fri Nov 23 01:25:42 2001
+++ cobalt/dev/lcd.c	Thu Nov 22 23:23:12 2001
@@ -0,0 +1,272 @@
+/*
+ * BSD Driver for Cobalt LCD
+ * Copyright 2001 Ted Unangst
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED WITHOUT WARRANTY.  USE IT AT YOUR OWN RISK.
+ */
+
+/*
+ * Driver notes:
+ * - Doesn't really attach to the system, but there's no need.
+ * - Provides an ioctl to control everything about the LCD, and
+ *   also read and write file functions for the text.
+ * - Don't include NUL bytes in the string, as they don't display correctly.
+ * - /dev/lcd can only be opened once at a time.
+ * - The buttons aren't supported yet.  They need a userland daemon anyway.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/proc.h>
+#include <sys/conf.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/vnode.h>
+#include <sys/signalvar.h>
+#include <sys/ioctl.h>
+#include <sys/kernel.h>
+
+#include <dev/pci/pcivar.h>
+#include "pci.h"
+
+#include <machine/bus.h>
+#include <machine/lcd.h>
+
+int	lcd_match(struct device * parent, struct cfdata * match, void *aux);
+void	lcd_attach(struct device * parent, struct device * self, void *aux);
+void	lcd_read_buffer(void);
+void	lcd_write_buffer(void);
+
+struct cfattach lcd_ca = {
+	sizeof(struct device), lcd_match, lcd_attach
+};
+
+struct lcd_softc {
+	struct device   sc_dev;
+};
+
+/* Internal state variables */
+int	lcd_present = 0;
+int	lcd_position;
+char	lcd_buffer[80];
+int	lcd_open = 0;
+
+/*
+ * This is complete fakery.  I don't know where it's
+ * attached, but we can ping it directly.  If it's there,
+ * we mark it present, but have to tell the system it's not.
+ */
+int
+lcd_match(struct device * parent, struct cfdata * match, void *aux)
+{
+	if ((((*(volatile u_int32_t *) 0xBF000010) >> 24) & 0xff) == 0x00) {
+		printf("No LCD!\n");
+	} else {
+		printf("lcd0 at mainbus0\n");
+		lcd_present = 1;
+	}
+
+	return 0;
+}
+
+void
+lcd_attach(struct device * parent, struct device * self, void *aux)
+{
+	/* nothing */
+}
+
+/*
+ * Only allow one opening at a time to protect lcd_position.
+ */
+int
+lcdopen(dev_t dev, int flags, int fmt, struct proc * p)
+{
+	if (!lcd_present)
+		return ENXIO;
+	if (lcd_open)
+		return EAGAIN;
+	lcd_open = 1;
+	lcd_position = 0;
+	return (0);
+}
+
+int
+lcdclose(dev_t dev, int flags, int fmt, struct proc * p)
+{
+	lcd_open = 0;
+	return (0);
+}
+
+/* Macros for interfacing with the LCD */
+#define LCD_INST	0xBF000000
+#define LCD_DATA	0xBF000010
+#define LED_ADDR	0xBC000000
+#define LCD_FIRSTROW	0x00000080
+#define LCD_SECONDROW	0x000000C0
+
+#define lcd_write(x)	(*(volatile u_int32_t *) LCD_DATA) = ((x) << 24)
+#define lcd_inst(x)	(*(volatile u_int32_t *) LCD_INST) = ((x) << 24)
+#define lcd_read()	((*(volatile u_int32_t *) LCD_DATA) >> 24)
+#define led_set(x)	(*(volatile unsigned char *) LED_ADDR) = ((char)x)
+
+int	lcd_cmds[] = {	0x08, 0x0F, 0x01, 0x0C, 0x0F, 0x0E, 0x10,
+			0x14, 0x18, 0x1C, 0x02 };
+
+/*
+ * Controls to set various LCD states.
+ */
+int
+lcdioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc * p)
+{
+	switch (cmd) {
+	case LCD_OFF:
+	case LCD_ON:
+	case LCD_CLEAR:
+	case LCD_CURSOROFF:
+	case LCD_CURSORON:
+	case LCD_BLINKOFF:
+	case LCD_CURSORLEFT:
+	case LCD_CURSORRIGHT:
+	case LCD_LEFT:
+	case LCD_RIGHT:
+	case LCD_HOME:
+		lcd_inst(lcd_cmds[cmd]);
+		break;
+	case LCD_READ:
+		lcd_read_buffer();
+		memcpy(addr, lcd_buffer, 80);
+		break;
+	case LCD_WRITE:
+		memcpy(lcd_buffer, addr, 80);
+		lcd_write_buffer();
+		break;
+	case LCD_RESET:
+		lcd_inst(0x3F);
+		delay(200);
+		lcd_inst(0x3F);
+		delay(200);
+		lcd_inst(0x3F);
+		delay(200);
+		lcd_inst(0x3F);
+		delay(200);
+		lcd_inst(0x01);
+		delay(200);
+		lcd_inst(0x06);
+		break;
+	case LCD_CURSORSET:
+		lcd_inst(*(unsigned char *)addr | LCD_FIRSTROW);
+		break;
+	case LCD_CURSORGET:
+		*(unsigned char *)addr =
+			((*(volatile u_int32_t *) LCD_INST) >> 24) | 0x07F;
+		break;
+	default:
+		return EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * We read into the buffer on the first call, otherwise
+ * use our saved lcd_position and copy back from the buffer.
+ */
+int
+lcdread(dev_t dev, struct uio * uio, int flags)
+{
+	u_int32_t rv, len;
+
+	len = uio->uio_resid;
+	if (len == 0 || lcd_position == 80)
+		return 0;
+
+	if (lcd_position == 0)
+		lcd_read_buffer();
+
+	if (len + lcd_position > 80)
+		len = 80 - lcd_position;
+
+	rv = uiomove(lcd_buffer + lcd_position, len, uio);
+	lcd_position += len;
+
+	return rv;
+}
+
+/*
+ * We first always read in the current contents, then overwrite
+ * the buffer with new data.  Then the whole buffer is sent back
+ * to the LCD.
+ */
+int
+lcdwrite(dev_t dev, struct uio * uio, int flags)
+{
+	u_int32_t len, rv;
+
+	len = uio->uio_resid;
+	if (len == 0 || lcd_position == 80)
+		return 0;
+
+	lcd_read_buffer();
+
+	if (len + lcd_position > 80)
+		len = 80 - lcd_position;
+
+	rv = uiomove(lcd_buffer + lcd_position, len, uio);
+	lcd_position += len;
+
+	lcd_write_buffer();
+
+	return rv;
+}
+
+/*
+ * Helper functions transfer between the LCD and the buffer
+ */
+void
+lcd_read_buffer(void)
+{
+	u_int32_t i, address;
+
+	for (i = 0, address = LCD_FIRSTROW; i < 40; i++, address++) {
+		delay(200);
+		lcd_inst(address);
+		delay(200);
+		lcd_buffer[i] = lcd_read();
+	}
+	lcd_buffer[39] = '\n';
+	for (address = LCD_SECONDROW; i < 80; i++, address++) {
+		delay(200);
+		lcd_inst(address);
+		delay(200);
+		lcd_buffer[i] = lcd_read();
+	}
+	lcd_buffer[79] = '\0';
+}
+
+void
+lcd_write_buffer(void)
+{
+	int i;
+
+	lcd_inst(0x80);
+	for (i = 0; i < 40; i++) {
+		delay(200);
+		lcd_write(lcd_buffer[i]);
+	}
+	delay(200);
+	lcd_inst(0xC0);
+	for (; i < 80; i++) {
+		delay(200);
+		lcd_write(lcd_buffer[i]);
+	}
+}
diff -u cobalt.orig/cobalt/conf.c cobalt/cobalt/conf.c
--- cobalt.orig/cobalt/conf.c	Wed Mar 21 14:25:54 2001
+++ cobalt/cobalt/conf.c	Wed Nov 21 23:47:02 2001
@@ -72,6 +72,8 @@
 #include "ses.h"
 cdev_decl(ses);
 #include "ld.h"
+#include <machine/lcd.h>
+cdev_decl(lcd);

 #include "i4b.h"
 #include "i4bctl.h"
@@ -150,6 +152,7 @@
 	cdev_i4brbch_init(NI4BRBCH, i4brbch),	/* 30: i4b raw b-channel access */
 	cdev_i4btrc_init(NI4BTRC, i4btrc),	/* 31: i4b trace device */
 	cdev_i4btel_init(NI4BTEL, i4btel),	/* 32: i4b phone device */
+	cdev_lcd_init(1, lcd),		/* 33: lcd driver */

 };
 int	nchrdev = sizeof(cdevsw) / sizeof(cdevsw[0]);


--
"Bitch set me up."
      - M. Barry, Mayor of Washington, DC