Add the glcd2usb connection type to the glcd driver.
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
/** \file server/drivers/glcd-glcd2usb.c
|
||||
* Driver for the glcd2usb device by Till Harbaum, a graphic lcd display
|
||||
* interface based on AVR USB driver. Based on his driver for lcd4linux.
|
||||
*
|
||||
* \note This connection type uses the screen size as reported by the glcd2usb
|
||||
* device. Any size configured in LCDd.conf will be ignored.
|
||||
* \note The glcd2usb with KS108 firmware uses a vertival (aka paged) frame
|
||||
* buffer layout. Other layouts are not supported by this driver.
|
||||
*
|
||||
* \todo Keypad input
|
||||
*/
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2007 Till Harbaum
|
||||
* 2011 Markus Dolze <bsdfan@nurfuerspam.de>
|
||||
*
|
||||
* This file is released under the GNU General Public License. Refer to the
|
||||
* COPYING file distributed with this package.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <usb.h>
|
||||
|
||||
#include "lcd.h"
|
||||
#include "report.h"
|
||||
#include "glcd-low.h"
|
||||
#include "glcd-glcd2usb.h"
|
||||
|
||||
/* USB Vendor and Product ID used for glcd2usb device */
|
||||
#define GLCD2USB_VID 0x1c40
|
||||
#define GLCD2USB_PID 0x0525
|
||||
|
||||
/* Some useful sortcuts */
|
||||
#define PAGES (p->framebuf.px_height / 8)
|
||||
#define PAGED_SIZE (p->framebuf.px_width * PAGES)
|
||||
|
||||
/** Data local to the glcd2usb connection type */
|
||||
typedef struct glcd_glcd2usb_data {
|
||||
usb_dev_handle *device;
|
||||
unsigned char *paged_buffer; /**< frame buffer in paged format */
|
||||
unsigned char *dirty_buffer; /**< buffer indicating changed bytes */
|
||||
union {
|
||||
unsigned char bytes[132];
|
||||
/** when reading this buffer can be accessed as structure */
|
||||
display_info_t display_info;
|
||||
} tx_buffer; /**< buffer to accumulate bytes to be sent */
|
||||
} CT_glcd2usb_data;
|
||||
|
||||
|
||||
/*-------------------------- USB HID IMPLEMENTATION -----------------------*/
|
||||
|
||||
/*
|
||||
* Note: The glcd2usb device implements a HID device. My first try to use
|
||||
* libhid failed, because glcd2usb uses report descriptors that totally confuse
|
||||
* libhid's parser. Therefor we need to roll our own. The code below has been
|
||||
* copied from the lcd4linux implementation of this driver.
|
||||
*/
|
||||
|
||||
/* Error codes */
|
||||
#define USB_ERROR_NONE 0
|
||||
#define USB_ERROR_ACCESS 1
|
||||
#define USB_ERROR_NOTFOUND 2
|
||||
#define USB_ERROR_BUSY 16
|
||||
#define USB_ERROR_IO 5
|
||||
|
||||
/* HID report types */
|
||||
#define USB_HID_REPORT_TYPE_INPUT 1
|
||||
#define USB_HID_REPORT_TYPE_OUTPUT 2
|
||||
#define USB_HID_REPORT_TYPE_FEATURE 3
|
||||
|
||||
/* USB request types */
|
||||
#define USBRQ_HID_GET_REPORT 0x01
|
||||
#define USBRQ_HID_SET_REPORT 0x09
|
||||
|
||||
/**
|
||||
* Sends a 'set report' request to the default control end point of glcd2usb
|
||||
* device. At most 128 bytes may be sent (plus 4 additional control bytes).
|
||||
*
|
||||
* \param device USB device handle of the device to talk to
|
||||
* \param reportType A HID report type
|
||||
* \param buffer Pointer to data to write
|
||||
* \param len Amount of data that shall be written
|
||||
* \return 0 on success, USB_ERROR_IO otherwise
|
||||
*/
|
||||
static int
|
||||
usbSetReport(usb_dev_handle * device, int reportType, unsigned char *buffer, int len)
|
||||
{
|
||||
int bytesSent;
|
||||
|
||||
/*
|
||||
* the write command needs some tweaking regarding allowed report
|
||||
* lengths
|
||||
*/
|
||||
if (buffer[0] == GLCD2USB_RID_WRITE) {
|
||||
const int allowed_lengths[] = {4 + 4, 8 + 4, 16 + 4, 32 + 4, 64 + 4, 128 + 4};
|
||||
int i = 0;
|
||||
|
||||
if (len > 128 + 4)
|
||||
fprintf(stderr, "%d bytes usb report is too long \n", len);
|
||||
|
||||
while (allowed_lengths[i] != (128 + 4) && allowed_lengths[i] < len)
|
||||
i++;
|
||||
|
||||
len = allowed_lengths[i];
|
||||
buffer[0] = GLCD2USB_RID_WRITE + i;
|
||||
}
|
||||
|
||||
bytesSent = usb_control_msg(device, USB_TYPE_CLASS | USB_RECIP_INTERFACE |
|
||||
USB_ENDPOINT_OUT, USBRQ_HID_SET_REPORT,
|
||||
reportType << 8 | buffer[0], 0, (char *)buffer, len, 1000);
|
||||
|
||||
if (bytesSent != len) {
|
||||
if (bytesSent < 0)
|
||||
fprintf(stderr, "Error sending message: %s", usb_strerror());
|
||||
return USB_ERROR_IO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a 'get report' request to the default control end point of glcd2usb
|
||||
* device. The amout of data read is stored in parameter len.
|
||||
*
|
||||
* \param device USB device handle of the device to talk to
|
||||
* \param reportType A HID report type (input / output / feature)
|
||||
* \param reportNumber The ReportID to be read
|
||||
* \param buffer Pointer to buffer to read the data into. The buffer MUST
|
||||
* be allocated beforehand and be at least of 'len' size.
|
||||
* \param len Pointer to an integer with the number of bytes to read
|
||||
* at most. Upon return, this hold the number of bytes
|
||||
* actually read or -1 if an error occured.
|
||||
* \return 0 on success, USB_ERROR_IO otherwise
|
||||
*/
|
||||
static int
|
||||
usbGetReport(usb_dev_handle * device, int reportType, int reportNumber, unsigned char *buffer, int *len)
|
||||
{
|
||||
*len = usb_control_msg(device, USB_TYPE_CLASS | USB_RECIP_INTERFACE |
|
||||
USB_ENDPOINT_IN, USBRQ_HID_GET_REPORT,
|
||||
reportType << 8 | reportNumber, 0, (char *)buffer, *len, 1000);
|
||||
|
||||
if (*len < 0) {
|
||||
fprintf(stderr, "Error sending message: %s", usb_strerror());
|
||||
return USB_ERROR_IO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an error code into a user readable string.
|
||||
* \param errCode The error code
|
||||
* \return Pointer to error message
|
||||
*/
|
||||
static char *
|
||||
usbErrorMessage(int errCode)
|
||||
{
|
||||
static char buffer[80];
|
||||
|
||||
switch (errCode) {
|
||||
case USB_ERROR_ACCESS:
|
||||
return "Access to device denied";
|
||||
case USB_ERROR_NOTFOUND:
|
||||
return "The specified device was not found";
|
||||
case USB_ERROR_BUSY:
|
||||
return "The device is used by another application";
|
||||
case USB_ERROR_IO:
|
||||
return "Communication error with device";
|
||||
default:
|
||||
sprintf(buffer, "Unknown USB error %d", errCode);
|
||||
return buffer;
|
||||
}
|
||||
/* NOTREACHED */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*------------------- Connection type functions below ---------------------*/
|
||||
|
||||
/**
|
||||
* API: Close device descriptors and free resources
|
||||
*/
|
||||
void
|
||||
glcd2usb_close(PrivateData *p)
|
||||
{
|
||||
if (p->ct_data != NULL) {
|
||||
CT_glcd2usb_data *ctd = (CT_glcd2usb_data *) p->ct_data;
|
||||
|
||||
if (ctd->device != 0)
|
||||
usb_close(ctd->device);
|
||||
if (ctd->paged_buffer != NULL)
|
||||
free(ctd->paged_buffer);
|
||||
if (ctd->dirty_buffer != NULL)
|
||||
free(ctd->dirty_buffer);
|
||||
free(ctd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* API: Set the backlight brightness
|
||||
*/
|
||||
void
|
||||
glcd2usb_backlight(PrivateData *p, int state)
|
||||
{
|
||||
int err = 0;
|
||||
CT_glcd2usb_data *ctd = (CT_glcd2usb_data *) p->ct_data;
|
||||
|
||||
ctd->tx_buffer.bytes[0] = GLCD2USB_RID_SET_BL;
|
||||
ctd->tx_buffer.bytes[1] = (state == BACKLIGHT_ON) ? p->brightness : p->offbrightness;
|
||||
|
||||
if ((err = usbSetReport(ctd->device, USB_HID_REPORT_TYPE_FEATURE, ctd->tx_buffer.bytes, 2)) != 0) {
|
||||
p->glcd_functions->drv_report(RPT_ERR, "Error freeing display: %s\n", usbErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* API: Transfer an image to the glcd2usb device. This function and its update
|
||||
* algorithm are copied from the LCD4Linux driver as it does its job well.
|
||||
*/
|
||||
void
|
||||
glcd2usb_blit(PrivateData *p)
|
||||
{
|
||||
CT_glcd2usb_data *ctd = (CT_glcd2usb_data *) p->ct_data;
|
||||
int c, r;
|
||||
int i, j;
|
||||
int err;
|
||||
|
||||
p->glcd_functions->drv_debug(RPT_DEBUG, "glcd2usb_blit: starting");
|
||||
|
||||
/* Reset the dirty buffer */
|
||||
memset(ctd->dirty_buffer, 0x00, PAGED_SIZE);
|
||||
|
||||
/*
|
||||
* Step 1: Convert the linear frame buffer format of the glcd driver
|
||||
* into the paged format of the glcd2usb device. Compare newly
|
||||
* written data with what was stored previously and set the dirty
|
||||
* buffer.
|
||||
*/
|
||||
for (r = 0; r < p->framebuf.px_height; r++) {
|
||||
int position, bit;
|
||||
unsigned char tmp;
|
||||
for (c = 0; c < p->framebuf.px_width; c++) {
|
||||
position = (r / 8) * p->framebuf.px_width + c;
|
||||
bit = r % 8;
|
||||
|
||||
tmp = ctd->paged_buffer[position];
|
||||
|
||||
if (fb_get_pixel(&(p->framebuf), c, r) == FB_BLACK)
|
||||
ctd->paged_buffer[position] |= 1 << bit;
|
||||
else
|
||||
ctd->paged_buffer[position] &= ~(1 << bit);
|
||||
|
||||
if (ctd->paged_buffer[position] != tmp)
|
||||
ctd->dirty_buffer[position] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 2: Short gaps of unchanged bytes in fact increase the
|
||||
* communication overhead. So we eliminate them here.
|
||||
*/
|
||||
for (j = -1, i = 0; i < PAGED_SIZE; i++) {
|
||||
if (ctd->dirty_buffer[i] && j >= 0 && i - j <= 4) {
|
||||
/* found a clean gap <= 4 bytes: mark it dirty */
|
||||
for (r = j; r < i; r++)
|
||||
ctd->dirty_buffer[r] = 1;
|
||||
}
|
||||
|
||||
/* if this is dirty, drop the saved position */
|
||||
if (ctd->dirty_buffer[i])
|
||||
j = -1;
|
||||
|
||||
/* save position of this clean entry if no position saved yet */
|
||||
if (!ctd->dirty_buffer[i] && j < 0)
|
||||
j = i;
|
||||
}
|
||||
|
||||
/* Step 3: Send the changes. */
|
||||
ctd->tx_buffer.bytes[0] = 0;
|
||||
for (i = 0; i < PAGED_SIZE; i++) {
|
||||
if (ctd->dirty_buffer[i]) {
|
||||
/* Start a new packet */
|
||||
if (!ctd->tx_buffer.bytes[0]) {
|
||||
ctd->tx_buffer.bytes[0] = GLCD2USB_RID_WRITE;
|
||||
ctd->tx_buffer.bytes[1] = i % 256;
|
||||
ctd->tx_buffer.bytes[2] = i / 256;
|
||||
ctd->tx_buffer.bytes[3] = 0; /* length */
|
||||
}
|
||||
/* Append the dirty byte and increase size */
|
||||
ctd->tx_buffer.bytes[4 + ctd->tx_buffer.bytes[3]++] = ctd->paged_buffer[i];
|
||||
}
|
||||
|
||||
/*
|
||||
* Send the data if we hit a clean byte, are at the end of
|
||||
* the frame or reached the maximum payload for a write
|
||||
* request.
|
||||
*/
|
||||
if (!ctd->dirty_buffer[i] || i == PAGED_SIZE - 1 || ctd->tx_buffer.bytes[3] == 128) {
|
||||
/* Only write if there IS something to be written */
|
||||
if (ctd->tx_buffer.bytes[0] == GLCD2USB_RID_WRITE && ctd->tx_buffer.bytes[3] > 0) {
|
||||
err = usbSetReport(ctd->device, USB_HID_REPORT_TYPE_FEATURE,
|
||||
ctd->tx_buffer.bytes, ctd->tx_buffer.bytes[3] + 4);
|
||||
if (err)
|
||||
p->glcd_functions->drv_report(RPT_ERR, "glcd2usb_blit: error in transfer");
|
||||
/* Start a new packet the next time */
|
||||
ctd->tx_buffer.bytes[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* API: Initialize glcd2usb connection type.
|
||||
*/
|
||||
int
|
||||
glcd2usb_init(Driver *drvthis)
|
||||
{
|
||||
PrivateData *p = (PrivateData *)drvthis->private_data;
|
||||
CT_glcd2usb_data *ctd;
|
||||
|
||||
static int didUsbInit = 0;
|
||||
struct usb_bus *bus;
|
||||
struct usb_device *dev;
|
||||
usb_dev_handle *handle = NULL;
|
||||
int err = 0;
|
||||
int rval, retries = 3;
|
||||
int len;
|
||||
|
||||
/* Set up connection type low-level functions */
|
||||
p->glcd_functions->blit = glcd2usb_blit;
|
||||
p->glcd_functions->close = glcd2usb_close;
|
||||
p->glcd_functions->set_backlight = glcd2usb_backlight;
|
||||
|
||||
/* Allocate memory structures */
|
||||
ctd = (CT_glcd2usb_data *) calloc(1, sizeof(CT_glcd2usb_data));
|
||||
if (ctd == NULL) {
|
||||
report(RPT_ERR, "%s/glcd2usb: error allocating connection data", drvthis->name);
|
||||
return -1;
|
||||
}
|
||||
p->ct_data = ctd;
|
||||
|
||||
ctd->paged_buffer = malloc(PAGED_SIZE);
|
||||
if (ctd->paged_buffer == NULL) {
|
||||
report(RPT_ERR, "%s/glcd2usb: cannot allocate memory", drvthis->name);
|
||||
goto err_out;
|
||||
}
|
||||
memset(ctd->paged_buffer, 0x00, PAGED_SIZE);
|
||||
|
||||
ctd->dirty_buffer = malloc(PAGED_SIZE);
|
||||
if (ctd->dirty_buffer == NULL) {
|
||||
report(RPT_ERR, "%s/glcd2usb: cannot allocate memory", drvthis->name);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to find and open a device. Only the first device found will be
|
||||
* recognized.
|
||||
*/
|
||||
if (!didUsbInit) {
|
||||
usb_init();
|
||||
didUsbInit = 1;
|
||||
}
|
||||
|
||||
usb_find_busses();
|
||||
usb_find_devices();
|
||||
|
||||
for (bus = usb_get_busses(); bus != NULL; bus = bus->next) {
|
||||
for (dev = bus->devices; dev != NULL; dev = dev->next) {
|
||||
|
||||
if (dev->descriptor.idVendor == GLCD2USB_VID
|
||||
&& dev->descriptor.idProduct == GLCD2USB_PID) {
|
||||
|
||||
handle = usb_open(dev);
|
||||
if (!handle) {
|
||||
report(RPT_WARNING, "%s/glcd2usb: cannot open USB device: %s",
|
||||
drvthis->name, usb_strerror());
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
goto found_dev;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found_dev:
|
||||
if (handle) {
|
||||
debug(RPT_DEBUG, "%s/glcd2usb: opening device succeeded", drvthis->name);
|
||||
}
|
||||
else {
|
||||
report(RPT_ERR, "%s/glcd2usb: no GLCD2USB device found", drvthis->name);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (usb_set_configuration(handle, 1))
|
||||
report(RPT_WARNING, "%s/glcd2usb: could not set configuration: %s",
|
||||
drvthis->name, usb_strerror());
|
||||
|
||||
/*
|
||||
* now try to claim the interface and detach the kernel HID driver on
|
||||
* linux and other operating systems which support the call.
|
||||
*/
|
||||
while ((rval = usb_claim_interface(handle, 0)) != 0 && retries-- > 0) {
|
||||
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
|
||||
if (usb_detach_kernel_driver_np(handle, 0) < 0) {
|
||||
report(RPT_WARNING, "%s/glcd2usb: could not detach kernel HID driver: %s",
|
||||
drvthis->name, usb_strerror());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (rval != 0)
|
||||
report(RPT_WARNING, "%s/glcd2usb: could not claim interface", drvthis->name);
|
||||
|
||||
/*
|
||||
* Continue anyway, even if we could not claim the interface. Control
|
||||
* transfers should still work.
|
||||
*/
|
||||
ctd->device = handle;
|
||||
|
||||
/* Query device */
|
||||
memset(&(ctd->tx_buffer), 0, sizeof(ctd->tx_buffer));
|
||||
len = sizeof(display_info_t);
|
||||
|
||||
if ((err = usbGetReport(ctd->device, USB_HID_REPORT_TYPE_FEATURE, GLCD2USB_RID_GET_INFO, ctd->tx_buffer.bytes, &len)) != 0) {
|
||||
report(RPT_ERR, "%s/glcd2usb: query display parameters: %s",
|
||||
drvthis->name, usbErrorMessage(err));
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (len < (int)sizeof(ctd->tx_buffer.display_info)) {
|
||||
report(RPT_ERR, "%s/glcd2usb: incomplete display info report (%d instead of %d)",
|
||||
drvthis->name, len, (int)sizeof(ctd->tx_buffer.display_info));
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (!(ctd->tx_buffer.display_info.flags & FLAG_VERTICAL_UNITS)) {
|
||||
report(RPT_ERR, "%s/glcd2usb: unsupported display layout", drvthis->name);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (ctd->tx_buffer.display_info.width > GLCD_MAX_WIDTH
|
||||
|| ctd->tx_buffer.display_info.width <= 0
|
||||
|| ctd->tx_buffer.display_info.height > GLCD_MAX_HEIGHT
|
||||
|| ctd->tx_buffer.display_info.height <= 0) {
|
||||
report(RPT_ERR, "%s/glcd2usb: display size out of range: %dx%d",
|
||||
drvthis->name, ctd->tx_buffer.display_info.width,
|
||||
ctd->tx_buffer.display_info.height);
|
||||
goto err_out;
|
||||
}
|
||||
else {
|
||||
p->framebuf.px_width = ctd->tx_buffer.display_info.width;
|
||||
p->framebuf.px_height = ctd->tx_buffer.display_info.height;
|
||||
report(RPT_INFO, "%s/glcd2usb: using display size %dx%d", drvthis->name,
|
||||
ctd->tx_buffer.display_info.width, ctd->tx_buffer.display_info.height);
|
||||
}
|
||||
|
||||
/* Allocate the display (turn off the 'screensaver') */
|
||||
ctd->tx_buffer.bytes[0] = GLCD2USB_RID_SET_ALLOC;
|
||||
ctd->tx_buffer.bytes[1] = 1;
|
||||
if ((err = usbSetReport(ctd->device, USB_HID_REPORT_TYPE_FEATURE, ctd->tx_buffer.bytes, 2)) != 0) {
|
||||
report(RPT_ERR, "%s/glcd2usb: Error allocating display: %s",
|
||||
drvthis->name, usbErrorMessage(err));
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
glcd2usb_close(p);
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user