aboutsummaryrefslogtreecommitdiffstats
path: root/drv_ula200.c
diff options
context:
space:
mode:
authormichael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f>2008-09-03 03:28:52 +0000
committermichael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f>2008-09-03 03:28:52 +0000
commite6f31f6b7949440cd735d0184c846aa2a3330f23 (patch)
tree69ec79fea0201a50a91b8fde5023a61863db524c /drv_ula200.c
parent1285008c5f75c28d6304ba984c432992c450ba3a (diff)
downloadlcd4linux-e6f31f6b7949440cd735d0184c846aa2a3330f23.tar.gz
ULA200 driver by Bernhard Walle
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@892 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
Diffstat (limited to 'drv_ula200.c')
-rw-r--r--drv_ula200.c791
1 files changed, 791 insertions, 0 deletions
diff --git a/drv_ula200.c b/drv_ula200.c
new file mode 100644
index 0000000..1e8eed5
--- /dev/null
+++ b/drv_ula200.c
@@ -0,0 +1,791 @@
+/* $Id: drv_Sample.c 840 2007-09-09 12:17:42Z michael $
+ * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/drv_Sample.c $
+ *
+ * ULA200 driver for lcd4linux
+ *
+ * Copyright (C) 2008 Bernhard Walle <bernhard.walle@gmx.de>
+ *
+ * This file is part of LCD4Linux.
+ *
+ * LCD4Linux is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * LCD4Linux is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/*
+ * Driver for the ELV ULA200 USB device. The device can control one
+ * HD44780 display up to 4x20 characters.
+ *
+ * Implemented functions:
+ * - displaying characters :-)
+ * - controlling the backlight
+ *
+ * Todo:
+ * - input buttons
+ *
+ * Configuration:
+ * - Size (XxY): size of the display (e.g. '20x4')
+ * - Backlight (0/1): initial state of the backlight
+ *
+ * Author:
+ * Bernhard Walle <bernhard.walle@gmx.de>
+ *
+ * exported fuctions:
+ * struct DRIVER drv_ula200
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ftdi.h>
+
+#include "debug.h"
+#include "cfg.h"
+#include "qprintf.h"
+#include "udelay.h"
+#include "plugin.h"
+#include "widget.h"
+#include "widget_text.h"
+#include "widget_icon.h"
+#include "widget_bar.h"
+#include "drv.h"
+
+/* text mode display? */
+#include "drv_generic_text.h"
+
+/****************************************/
+/*** Global variables ***/
+/****************************************/
+
+static char Name[] = "ULA200";
+static struct ftdi_context *Ftdi = NULL;
+
+
+/****************************************/
+/*** Constants ***/
+/****************************************/
+
+/* USB connection */
+#define ULA200_VENDOR_ID 0x0403
+#define ULA200_PRODUCT_ID 0xf06d
+
+/* connection parameters */
+#define ULA200_BAUDRATE 19200
+#define ULA200_DATABITS BITS_8
+#define ULA200_STOPBITS STOP_BIT_1
+#define ULA200_PARITY EVEN
+
+/* character constants used for the communication */
+#define ULA200_CH_STX 0x02
+#define ULA200_CH_ETX 0x03
+#define ULA200_CH_ENQ 0x05
+#define ULA200_CH_ACK 0x06
+#define ULA200_CH_NAK 0x15
+#define ULA200_CH_DC2 0x12
+#define ULA200_CH_DC3 0x13
+
+/* commands used for the communication (names are German) */
+#define ULA200_CMD_POSITION 'p' /* 'position' */
+#define ULA200_CMD_STRING 's' /* 'string' */
+#define ULA200_CMD_CLEAR 'l' /* 'loeschen' */
+#define ULA200_CMD_BACKLIGHT 'h' /* 'hintergrund' */
+#define ULA200_CMD_CHAR 'c' /* 'character' */
+
+/* raw register access */
+#define ULA200_RS_DATA 0x00 /* data */
+#define ULA200_RS_INSTR 0x01 /* instruction */
+#define ULA200_SETCHAR 0x40 /* set user-defined character */
+
+/* character sizes */
+#define ULA200_CELLWIDTH 5
+#define ULA200_CELLHEIGHT 8
+
+/* internal implementation constants */
+#define ULA200_BUFFER_LENGTH 1024
+#define ULA200_MAXLEN 512
+#define ULA200_MAX_REPEATS 20
+
+/* define TRUE and FALSE for better code readability if not already defined */
+#ifndef TRUE
+# define TRUE 1
+#endif
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+
+/****************************************/
+/*** Macros ***/
+/****************************************/
+
+#define ULA200_ERROR(msg, ...) \
+ error("%s: In %s():%d: " msg, Name, \
+ __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+#define ULA200_INFO(msg, ...) \
+ info("%s: " msg, Name, ##__VA_ARGS__)
+
+#define ULA200_DEBUG(msg, ...) \
+ debug("%s: In %s():%d: " msg, Name, \
+ __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+#define ULA200_TRACE() \
+ debug("%s: Calling %s()", Name, __FUNCTION__)
+
+
+/****************************************/
+/*** Prototypes ***/
+/****************************************/
+
+static int drv_ula200_ftdi_read_response(void);
+static int drv_ula200_ftdi_usb_read(void);
+static int drv_ula200_ftdi_write_command(const unsigned char *, int);
+static int drv_ula200_backlight(int);
+static int drv_ula200_close(void);
+
+static void plugin_backlight(RESULT *, RESULT *);
+
+/****************************************/
+/*** Internal (helper) funcs ***/
+/****************************************/
+
+/**
+ * Write a command to the display. Adds the STX and ETX header/trailer.
+ *
+ * @param[in] data the data bytes
+ * @param[in] length the number of bytes in data which are valid
+ * @return 0 on success, negative value on error
+ */
+static int drv_ula200_ftdi_write_command(const unsigned char *data, int length)
+{
+ int i, err;
+ int repeat_count = 0;
+ int pos = 0;
+ unsigned char buffer[ULA200_BUFFER_LENGTH];
+
+ /* check for the maximum length */
+ if (length > ULA200_MAXLEN) {
+ return -EINVAL;
+ }
+
+ /* fill the array */
+ buffer[pos++] = ULA200_CH_STX;
+ for (i = 0; i < length; i++) {
+ if (data[i] == ULA200_CH_STX) {
+ buffer[pos++] = ULA200_CH_ENQ;
+ buffer[pos++] = ULA200_CH_DC2;
+ } else if (data[i] == ULA200_CH_ETX) {
+ buffer[pos++] = ULA200_CH_ENQ;
+ buffer[pos++] = ULA200_CH_DC3;
+ } else if (data[i] == ULA200_CH_ENQ) {
+ buffer[pos++] = ULA200_CH_ENQ;
+ buffer[pos++] = ULA200_CH_NAK;
+ } else {
+ buffer[pos++] = data[i];
+ }
+ }
+ buffer[pos++] = ULA200_CH_ETX;
+
+ do {
+ /* ULA200_DEBUG("ftdi_write_data(%p, %d)", buffer, pos); */
+ err = ftdi_write_data(Ftdi, buffer, pos);
+ if (err < 0) {
+ ULA200_ERROR("ftdi_write_data() failed");
+ return -1;
+ }
+ }
+ while (!drv_ula200_ftdi_read_response() && (repeat_count++ < ULA200_MAX_REPEATS));
+
+ return 0;
+}
+
+/**
+ * Reads a character from USB.
+ *
+ * @return a positive value between 0 and 255 indicates the character that
+ * has been read successfully, -1 indicates an error
+ */
+static int drv_ula200_ftdi_usb_read(void)
+{
+ unsigned char buffer[1];
+ int err;
+
+ while ((err = ftdi_read_data(Ftdi, buffer, 1)) == 0);
+ return err >= 0 ? buffer[0] : -1;
+}
+
+
+/**
+ * Reads the response of the display. Currently, key input is ignored
+ * and only ACK / NACK is read.
+ *
+ * @return TRUE on success (ACK), FALSE on failure (NACK)
+ */
+static int drv_ula200_ftdi_read_response(void)
+{
+ int result = FALSE;
+ int answer_read = FALSE;
+ int ret;
+ int ch;
+
+ while (!answer_read) {
+ /* wait until STX */
+ do {
+ ret = drv_ula200_ftdi_usb_read();
+ /* ULA200_DEBUG("STX drv_ula200_ftdi_usb_read = %d", ret); */
+ } while ((ret != ULA200_CH_STX) && (ret > 0));
+
+ if (ret < 0) {
+ return FALSE;
+ }
+
+ /* read next char */
+ ch = drv_ula200_ftdi_usb_read();
+ /* ULA200_DEBUG("drv_ula200_ftdi_usb_read = %d", ch); */
+
+ switch (ch) {
+ case 't':
+ ch = drv_ula200_ftdi_usb_read();
+ /* ULA200_DEBUG("drv_ula200_ftdi_usb_read = %d", ch); */
+ /* ignore currently */
+ break;
+
+ case ULA200_CH_ACK:
+ answer_read = TRUE;
+ result = TRUE;
+ break;
+
+ case ULA200_CH_NAK:
+ answer_read = TRUE;
+ result = FALSE;
+ break;
+
+ default:
+ answer_read = TRUE;
+ ULA200_ERROR("Read invalid answer");
+ }
+
+ /* wait until ETX */
+ do {
+ ret = drv_ula200_ftdi_usb_read();
+ /* ULA200_DEBUG("ETX drv_ula200_ftdi_usb_read = %d", ret); */
+ } while ((ret != ULA200_CH_ETX) && (ret > 0));
+
+ if (ret < 0) {
+ return FALSE;
+ }
+ }
+
+ return result;
+}
+
+static int drv_ula200_ftdi_enable_raw_mode(void)
+{
+ unsigned char command[3];
+
+ command[0] = 'R';
+ command[1] = 'E';
+ command[2] = '1';
+ return drv_ula200_ftdi_write_command(command, 3);
+}
+
+
+/**
+ * Writes raw data (access the HD44780 registers directly.
+ *
+ * @param[in] flags ULA200_RS_DATA or ULA200_RS_INSTR
+ * @param[in] ch the real data
+ * @return 0 on success, a negative value on error
+ */
+static int drv_ula200_ftdi_rawdata(unsigned char flags, unsigned char ch)
+{
+ unsigned char command[3];
+ int err;
+
+ command[0] = 'R';
+ command[1] = flags == ULA200_RS_DATA ? '2' : '0';
+ command[2] = ch;
+ err = drv_ula200_ftdi_write_command(command, 3);
+ if (err < 0) {
+ ULA200_ERROR("ula200_ftdi_write_command() failed");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Sets the cursor position.
+ *
+ * @param[in] x the x coordinate of the position
+ * @param[in] y the y coordinate of the position
+ * @return 0 on success, a negative value on error
+ */
+static int drv_ula200_set_position(int x, int y)
+{
+ unsigned char command[3];
+ int err;
+
+ if (y >= 2) {
+ y -= 2;
+ x += DCOLS; /* XXX: multiply by 2? */
+ }
+
+ command[0] = ULA200_CMD_POSITION;
+ command[1] = x;
+ command[2] = y;
+ err = drv_ula200_ftdi_write_command(command, 3);
+ if (err < 0) {
+ ULA200_ERROR("ula200_ftdi_write_command() failed");
+ }
+
+ return err;
+}
+
+/**
+ * Sends the text
+ *
+ * @param[in] data the data bytes
+ * @param[in] len the number of valid bytes in @p data
+ * @return 0 on success, a negative value on error
+ */
+static int drv_ula200_send_text(const unsigned char *data, int len)
+{
+ unsigned char buffer[ULA200_BUFFER_LENGTH];
+ int err;
+
+ if (len > ULA200_MAXLEN) {
+ return -EINVAL;
+ }
+
+ buffer[0] = ULA200_CMD_STRING;
+ buffer[1] = len;
+ memcpy(buffer + 2, data, len);
+ buffer[2 + len] = 0; /* only necessary for the debug message */
+
+ /* ULA200_DEBUG("Text: =%s= (%d)", buffer+2, len); */
+
+ err = drv_ula200_ftdi_write_command(buffer, len + 2);
+ if (err < 0) {
+ ULA200_ERROR("ula200_ftdi_write_command() failed");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Sends one character.
+ *
+ * @param[in] ch the character to send
+ * @return 0 on success, a negative value on error
+ */
+static int drv_ula200_send_char(char ch)
+{
+ unsigned char buffer[2];
+ int err;
+
+ buffer[0] = ULA200_CMD_CHAR;
+ buffer[1] = ch;
+
+ err = drv_ula200_ftdi_write_command(buffer, 2);
+ if (err < 0) {
+ ULA200_ERROR("ula200_ftdi_write_command() failed");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Opens the ULA200 display. Uses libftdi to initialise the USB communication to
+ * the display.
+ *
+ @ @return a value less then zero on failure, 0 on success
+ */
+static int drv_ula200_open(void)
+{
+ int err;
+
+ /* check if the device was already open */
+ if (Ftdi != NULL) {
+ ULA200_ERROR("open called although device was already open");
+ drv_ula200_close();
+ }
+
+ /* get memory for the device descriptor */
+ Ftdi = malloc(sizeof(struct ftdi_context));
+ if (Ftdi == NULL) {
+ ULA200_ERROR("Memory allocation failed");
+ return -1;
+ }
+
+ /* open the ftdi library */
+ ftdi_init(Ftdi);
+ Ftdi->usb_write_timeout = 20;
+ Ftdi->usb_read_timeout = 20;
+
+ /* open the device */
+ err = ftdi_usb_open(Ftdi, ULA200_VENDOR_ID, ULA200_PRODUCT_ID);
+ if (err < 0) {
+ ULA200_ERROR("ftdi_usb_open() failed");
+ free(Ftdi);
+ Ftdi = NULL;
+ return -1;
+ }
+
+ /* set the baudrate */
+ err = ftdi_set_baudrate(Ftdi, ULA200_BAUDRATE);
+ if (err < 0) {
+ ULA200_ERROR("ftdi_set_baudrate() failed");
+ ftdi_usb_close(Ftdi);
+ free(Ftdi);
+ Ftdi = NULL;
+ return -1;
+ }
+ /* set communication parameters */
+ err = ftdi_set_line_property(Ftdi, ULA200_DATABITS, ULA200_STOPBITS, ULA200_PARITY);
+ if (err < 0) {
+ ULA200_ERROR("ftdi_set_line_property() failed");
+ ftdi_usb_close(Ftdi);
+ free(Ftdi);
+ Ftdi = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Closes the display.
+ *
+ * @return 0 on success, a negative value on failure
+ */
+static int drv_ula200_close(void)
+{
+ ULA200_TRACE();
+
+ ftdi_usb_purge_buffers(Ftdi);
+ ftdi_usb_close(Ftdi);
+ ftdi_deinit(Ftdi);
+
+ free(Ftdi);
+ Ftdi = NULL;
+
+ return 0;
+}
+
+/**
+ * Clears the contents of the display.
+ *
+ * @return 0 on success, a negative value on error
+ */
+static void drv_ula200_clear(void)
+{
+ unsigned const char command[] = { ULA200_CMD_CLEAR };
+ int err;
+
+ ULA200_TRACE();
+
+ err = drv_ula200_ftdi_write_command(command, 1);
+ if (err < 0) {
+ ULA200_ERROR("ula200_ftdi_write_command() failed");
+ }
+}
+
+/**
+ * Writes data to the display.
+ *
+ * @param[in] row the row where the data should be written to
+ * @param[in] col the column where the data should be written to
+ * @param[in] data the data that should actually be written
+ * @param[in] len the number of valid bytes in @p data
+ */
+static void drv_ula200_write(const int row, const int col, const char *data, int len)
+{
+ int ret;
+
+ /* do the cursor positioning here */
+ ret = drv_ula200_set_position(col, row);
+ if (ret < 0) {
+ ULA200_ERROR("drv_ula200_set_position() failed");
+ return;
+ }
+
+ /* send string to the display */
+ if (len == 1) {
+ ret = drv_ula200_send_char(data[0]);
+ } else {
+ ret = drv_ula200_send_text((unsigned char *) data, len);
+ }
+ if (ret < 0) {
+ ULA200_ERROR("drv_ula200_send_text() failed");
+ return;
+ }
+}
+
+/* text mode displays only */
+static void drv_ula200_defchar(const int ascii, const unsigned char *matrix)
+{
+ int err, i;
+
+ if (ascii >= 8) {
+ ULA200_ERROR("Invalid value in drv_ula200_defchar");
+ return;
+ }
+
+ /* Tell the HD44780 we will redefine char number 'ascii' */
+ err = drv_ula200_ftdi_rawdata(ULA200_RS_INSTR, ULA200_SETCHAR | (ascii * 8));
+ if (err < 0) {
+ ULA200_ERROR("drv_ula200_ftdi_rawdata() failed");
+ return;
+ }
+
+ /* Send the subsequent rows */
+ for (i = 0; i < YRES; i++) {
+ err = drv_ula200_ftdi_rawdata(ULA200_RS_DATA, *matrix++ & 0x1f);
+ if (err < 0) {
+ ULA200_ERROR("ula200_ftdi_rawdata() failed");
+ return;
+ }
+ }
+}
+
+/**
+ * Controls the backlight of the ULA200 display.
+ *
+ * @param[in] backlight a negative value if the backlight should be turned off,
+ * a positive value if it should be turned on
+ * @return 0 on success, any other value on failure
+ */
+static int drv_ula200_backlight(int backlight)
+{
+ unsigned char cmd[2] = { ULA200_CMD_BACKLIGHT };
+ int ret;
+
+ if (backlight <= 0) {
+ backlight = '0';
+ } else {
+ backlight = '1';
+ }
+
+ cmd[1] = backlight;
+ ret = drv_ula200_ftdi_write_command(cmd, 2);
+ if (ret < 0) {
+ ULA200_ERROR("ula200_ftdi_write_command() failed");
+ }
+
+ return backlight == '1';
+}
+
+/**
+ * Starts the display.
+ *
+ * @param[in] section the section of the configuration file
+ * @return 0 on success, a negative value on failure
+ */
+static int drv_ula200_start(const char *section)
+{
+ int rows = -1, cols = -1;
+ char *s;
+ int backlight = 0;
+ int err;
+
+ s = cfg_get(section, "Size", NULL);
+ if (s == NULL || *s == '\0') {
+ ULA200_ERROR("No '%s.Size' entry from %s", section, cfg_source());
+ return -1;
+ }
+ if (sscanf(s, "%dx%d", &cols, &rows) != 2 || rows < 1 || cols < 1) {
+ ULA200_ERROR("Bad %s.Size '%s' from %s", section, s, cfg_source());
+ free(s);
+ return -1;
+ }
+
+ DROWS = rows;
+ DCOLS = cols;
+
+ /* open communication with the display */
+ err = drv_ula200_open();
+ if (err < 0) {
+ return -1;
+ }
+
+ cfg_number(section, "Backlight", 0, 0, 1, &backlight);
+ err = drv_ula200_backlight(backlight);
+ if (err < 0) {
+ ULA200_ERROR("drv_ula200_backlight() failed");
+ return -1;
+ }
+
+ /* clear display */
+ drv_ula200_clear();
+
+ /* enable raw mode for defining own chars */
+ drv_ula200_ftdi_enable_raw_mode();
+
+ return 0;
+}
+
+/****************************************/
+/*** plugins ***/
+/****************************************/
+
+/**
+ * Backlight plugin
+ */
+static void plugin_backlight(RESULT * result, RESULT * arg1)
+{
+ double backlight;
+
+ backlight = drv_ula200_backlight(R2N(arg1));
+ SetResult(&result, R_NUMBER, &backlight);
+}
+
+/****************************************/
+/*** exported functions ***/
+/****************************************/
+
+/**
+ * list models
+ *
+ * @return 0 on success, a negative value on failure
+ */
+int drv_ula200_list(void)
+{
+ printf("generic");
+ return 0;
+}
+
+/**
+ * initialize driver & display
+ *
+ * @param[in] section the name of the section in the configuration file
+ * @param[in] quiet TRUE on quiet mode
+ * @return 0 on success, any negative error value on failure
+ */
+/* use this function for a text display */
+int drv_ula200_init(const char *section, const int quiet)
+{
+ WIDGET_CLASS wc;
+ int ret;
+
+ ULA200_INFO("%s", "$Rev: 840 $");
+
+ /* display preferences */
+ XRES = ULA200_CELLWIDTH; /* pixel width of one char */
+ YRES = ULA200_CELLHEIGHT; /* pixel height of one char */
+ CHARS = 7; /* number of user-defineable characters */
+ CHAR0 = 1; /* ASCII of first user-defineable char */
+ GOTO_COST = 4; /* number of bytes a goto command requires */
+
+ /* real worker functions */
+ drv_generic_text_real_write = drv_ula200_write;
+ drv_generic_text_real_defchar = drv_ula200_defchar;
+
+ /* start display */
+ if ((ret = drv_ula200_start(section)) != 0) {
+ return ret;
+ }
+
+ if (!quiet) {
+ char buffer[40];
+ qprintf(buffer, sizeof(buffer), "%s %dx%d", Name, DCOLS, DROWS);
+ if (drv_generic_text_greet(buffer, "ULA 200")) {
+ sleep(3);
+ drv_ula200_clear();
+ }
+ }
+
+ /* initialize generic text driver */
+ if ((ret = drv_generic_text_init(section, Name)) != 0)
+ return ret;
+
+ /* initialize generic icon driver */
+ if ((ret = drv_generic_text_icon_init()) != 0)
+ return ret;
+
+ /* initialize generic bar driver */
+ if ((ret = drv_generic_text_bar_init(0)) != 0)
+ return ret;
+
+ /* add fixed chars to the bar driver */
+ drv_generic_text_bar_add_segment(0, 0, 255, 32); /* ASCII 32 = blank */
+
+ /* register text widget */
+ wc = Widget_Text;
+ wc.draw = drv_generic_text_draw;
+ widget_register(&wc);
+
+ /* register icon widget */
+ wc = Widget_Icon;
+ wc.draw = drv_generic_text_icon_draw;
+ widget_register(&wc);
+
+ /* register bar widget */
+ wc = Widget_Bar;
+ wc.draw = drv_generic_text_bar_draw;
+ widget_register(&wc);
+
+ /* register plugins */
+ AddFunction("LCD::backlight", -1, plugin_backlight);
+
+ return 0;
+}
+
+/**
+ * close driver & display
+ *
+ * @param[in] quiet TRUE on quiet mode
+ * @return 0 on success, any negative error value on failure
+ */
+/* use this function for a text display */
+int drv_ula200_quit(int quiet)
+{
+ ULA200_INFO("shutting down.");
+
+ drv_generic_text_quit();
+
+ /* turn backlight off */
+ drv_ula200_backlight(0);
+
+ /* clear display */
+ drv_ula200_clear();
+
+ /* say goodbye... */
+ if (!quiet) {
+ drv_generic_text_greet("goodbye!", NULL);
+ }
+
+ debug("closing connection");
+ drv_ula200_close();
+
+ return 0;
+}
+
+/* use this one for a text display */
+DRIVER drv_ula200 = {
+ .name = Name,
+ .list = drv_ula200_list,
+ .init = drv_ula200_init,
+ .quit = drv_ula200_quit,
+};
+
+/* :indentSize=4:tabSize=8:noTabs=false: */