/* $Id: drv_SamsungSPF 975 2009-01-18 11:16:20Z michael $
   * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/drv_SamsungSPF.c $
   *
   * SamsungSPF lcd4linux driver
   *
   * Copyright (C) 2012 Sascha Plazar <sascha@plazar.de>
   * Copyright (C) 2005, 2006, 2007 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
   *
   * This driver is based on playusb.c created on Aug 2, 2010 by Andre Puschmann
   * which is in turn based on code from Grace Woo: 
   *    http://web.media.mit.edu/~gracewoo/stuff/picframe
   *
   * 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.
   *
   */

/* 
   *
   * exported fuctions:
   *
   * struct DRIVER drv_SamsungSPF
   *
   */

#include "config.h"

#include <stdio.h>
#include <string.h>

#include <usb.h>
#include <jpeglib.h>

#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "timer.h"
#include "drv.h"

/* graphic display? */
#include "drv_generic_graphic.h"

// Drivername for verbose output
static char Name[] = "SamsungSPF";

struct SPFdev {
    const char type[64];
    const int vendorID;
    struct {
	const int storageMode;
	const int monitorMode;
    } productID;
    const unsigned int xRes;
    const unsigned int yRes;
};

static struct SPFdev spfDevices[] = {
    {
     .type = "SPF-75H",
     .vendorID = 0x04e8,
     .productID = {0x200e, 0x200f},
     .xRes = 800,
     .yRes = 480,
     },
    {
     .type = "SPF-85H",
     .vendorID = 0x04e8,
     .productID = {0x2012, 0x2013},
     .xRes = 800,
     .yRes = 600,
     },
    {
     .type = "SPF-107H",
     .vendorID = 0x04e8,
     .productID = {0x2027, 0x2028},
     .xRes = 1024,
     .yRes = 600,
     },
};

static int numFrames = sizeof(spfDevices) / sizeof(spfDevices[0]);

struct usb_device *myDev;
usb_dev_handle *myDevHandle;
struct SPFdev *myFrame;

typedef struct {
    unsigned char R, G, B;
} RGB;

static struct {
    RGB *buf;
    int dirty;
    int fbsize;
} image;

static struct {
    unsigned char *buf;
    unsigned long int size;
} jpegImage;


/****************************************/
/***  hardware dependant functions    ***/
/****************************************/


/* please note that in-memory compression doesn't work satisfactory */
int convert2JPG()
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    int row_stride;		/* physical row width in buffer */
    JSAMPROW row_pointer[1];	/* pointer to a single row */

    /* Initialize compression frame */
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    jpeg_mem_dest(&cinfo, &jpegImage.buf, &jpegImage.size);

    cinfo.image_width = myFrame->xRes;
    cinfo.image_height = myFrame->yRes;
    cinfo.input_components = sizeof(RGB);
    cinfo.in_color_space = JCS_RGB;

    /* call some jpeg helpers */
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 100, 1);	/*set the quality [0..100]  */
    jpeg_start_compress(&cinfo, 1);

    row_stride = cinfo.image_width;

    /* Convert line by line */
    while (cinfo.next_scanline < cinfo.image_height) {
	row_pointer[0] = (JSAMPROW) (image.buf + (cinfo.next_scanline * row_stride));
	jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }

    /* Finish compression and free internal memory */
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);

    return 0;
}


// Find specific Samsung device
static void drv_SamsungSPF_find()
{
    info("%s: Searching SPF.", Name);

    /* open USB device */
    struct usb_bus *bus;
    struct usb_device *dev;

    usb_init();
    usb_find_busses();
    usb_find_devices();

    for (bus = usb_busses; bus; bus = bus->next) {
	for (dev = bus->devices; dev; dev = dev->next) {
	    if (dev->descriptor.idVendor == myFrame->vendorID) {
		if (dev->descriptor.idProduct == myFrame->productID.storageMode) {

		    info("Samsung photoframe in Mass Storage mode found.");
		    myDev = dev;

		    return;
		} else if (dev->descriptor.idProduct == myFrame->productID.monitorMode) {

		    info("Samsung photoframe in Custom Product mode found.");
		    myDev = dev;

		    return;
		}
	    }
	}
    }

    free(bus);
    myDev = 0;
}


static int drv_SamsungSPF_open()
{
    if (!myDev) {
	error("%s: No device specified!", Name);
	return -1;
    }

    int res = -1;
    char buf[256];

    if (myDev->descriptor.idProduct == myFrame->productID.storageMode) {
	info("%s: Opening device and switching to monitor mode", Name);

	myDevHandle = usb_open(myDev);

	setuid(getuid());

	strcpy(buf, "** no string **");
	res = usb_get_string_simple(myDevHandle, myDev->descriptor.iManufacturer, buf, sizeof(buf));
	debug("usb_get_string_simple => %d, %s", res, buf);

	memset(buf, 0, 256);

	res = usb_control_msg(myDevHandle, USB_TYPE_STANDARD | USB_ENDPOINT_IN,
			      USB_REQ_GET_DESCRIPTOR, 0xfe, 0xfe, buf, 0xfe, 1000);
	/* usb_close( myDev ); */
	// Sleep some time before research
	sleep(1);
	drv_SamsungSPF_find();
    } else
	info("%s: No device in storage mode found", Name);

    if (myDev->descriptor.idProduct == myFrame->productID.storageMode) {
	error("%s: Was not able to switch to monitor mode!", Name);
	return -1;
    }

    if (myDev->descriptor.idProduct == myFrame->productID.monitorMode) {
	info("%s: Device '%s' is now in monitor mode.", Name, myFrame->type);
	myDevHandle = usb_open(myDev);
	return 0;
    }

    error("Unknown error: usb_control_msg() = %d", res);
    return -1;
}


/* dummy function that sends something to the display */
static int drv_SamsungSPF_send(char *data, unsigned int len)
{
    char usb_hdr[12] = { 0xa5, 0x5a, 0x18, 0x04, 0xff, 0xff,
	0xff, 0xff, 0x48, 0x00, 0x00, 0x00
    };
    char buffer[1] = { 0x0 };
    int usb_timeout = 1000;
    int usb_endpoint = 0x2;
    int ret;

    *(int *) (usb_hdr + 4) = len;

    debug("bytes_to_send: %d, offset: %d", len, 12);

    /* Send USB header */
    if ((ret = usb_bulk_write(myDevHandle, usb_endpoint, usb_hdr, 12, usb_timeout)) < 0) {
	error("%s: Error occurred while writing data to device.", Name);
	error("%s: usb_bulk_write returned: %d", Name, ret);
	return -1;
    }

    /* Send JPEG image */
    if ((ret = usb_bulk_write(myDevHandle, usb_endpoint, data, len, usb_timeout)) < 0) {
	error("%s: Error occurred while writing data to device.", Name);
	error("%s: usb_bulk_write returned: %d", Name, ret);
	return -1;
    }

    /* Finish transmission by sending zero */
    if ((ret = usb_bulk_write(myDevHandle, usb_endpoint, buffer, 1, usb_timeout)) < 0) {
	error("%s: Error occurred while writing data to device.", Name);
	error("%s: usb_bulk_write returned: %d", Name, ret);
	return -1;
    }

    return 0;
}


/* for graphic displays only */
static void drv_SamsungSPF_blit(const int row, const int col, const int height, const int width)
{
    int r, c;

    for (r = row; r < row + height; r++) {
	for (c = col; c < col + width; c++) {
	    RGB p1 = image.buf[r * myFrame->xRes + c];
	    RGBA p2 = drv_generic_graphic_rgb(r, c);
	    if (p1.R != p2.R || p1.G != p2.G || p1.B != p2.B) {
		image.buf[r * myFrame->xRes + c].R = p2.R;
		image.buf[r * myFrame->xRes + c].G = p2.G;
		image.buf[r * myFrame->xRes + c].B = p2.B;
		image.dirty = 1;
	    }
	}
    }
}


static void drv_SamsungSPF_timer( __attribute__ ((unused))
				 void *notused)
{
    if (image.dirty) {
	debug("FB dirty, writing jpeg...");
	convert2JPG();

	/* Sent image to display */
	if ((drv_SamsungSPF_send((char *) jpegImage.buf, jpegImage.size)) != 0) {
	    error("%s: Error occurred while sending jpeg image to device.", Name);
	}
	/* Clean dirty bit */
	image.dirty = 0;

	/* Free JPEG buffer since a new is allocated each time an image is 
	   compressed */
	if (jpegImage.size)
	    free(jpegImage.buf);
	jpegImage.size = 0;
    }
}


/* start graphic display */
static int drv_SamsungSPF_start(const char *section)
{
    int timerInterval = 1000;
    char *s;

    cfg_number(section, "update", timerInterval, 0, -1, &timerInterval);
    debug("Updating display every %dms", timerInterval);

    DROWS = myFrame->yRes;
    DCOLS = myFrame->xRes;
    info("%s: Using SPF with %dx%d pixels.", Name, DCOLS, DROWS);

    s = cfg_get(section, "Font", "6x8");
    if (s == NULL || *s == '\0') {
	error("%s: no '%s.Font' entry from %s", Name, section, cfg_source());
	return -1;
    }

    XRES = -1;
    YRES = -1;
    if (sscanf(s, "%dx%d", &XRES, &YRES) != 2 || XRES < 1 || YRES < 1) {
	error("%s: bad Font '%s' from %s", Name, s, cfg_source());
	return -1;
    }

    if (XRES < 6 || YRES < 8) {
	error("%s: bad Font '%s' from %s (must be at least 6x8)", Name, s, cfg_source());
	return -1;
    }
    free(s);

    /* Allocate framebuffer */
    image.fbsize = myFrame->xRes * myFrame->yRes * sizeof(RGB);
    image.buf = malloc(image.fbsize);
    memset(image.buf, 128, image.fbsize);
    image.dirty = 0;

    /* JPEG buffer is allocated by jpeglib */
    jpegImage.buf = 0;
    jpegImage.size = 0;

    /* regularly transmit the image */
    timer_add(drv_SamsungSPF_timer, NULL, timerInterval, 0);

    return 0;
}



/****************************************/
/***        exported functions        ***/
/****************************************/


/* list models */
int drv_SamsungSPF_list(void)
{
    int i;

    printf("SamsungSPF driver, supported models [");
    for (i = 0; i < numFrames; i++) {
	printf("%s", spfDevices[i].type);
	if (i < numFrames - 1)
	    printf(", ");
    }
    printf("]\n");

    return 0;
}


/* initialize driver & display */
/* use this function for a graphic display */
int drv_SamsungSPF_init(const char *section, const int quiet)
{
    info("%s: Initializing SPF.", Name);

    char *s;
    int i;

    myDev = 0;
    myFrame = 0;

    // Look for model entry in config
    s = cfg_get(section, "Model", NULL);
    if (s == NULL || *s != '\0') {
	s = cfg_get(section, "Model", NULL);
	if (s == NULL || *s == '\0') {

	    drv_SamsungSPF_list();
	    error("%s: no '%s.Model' entry from %s", Name, section, cfg_source());
	    return -1;
	}
    }
    // Look for specified device
    for (i = 0; i < numFrames; i++) {
	if (strcasecmp(s, spfDevices[i].type) == 0) {
	    myFrame = &spfDevices[i];
	    info("%s: Configured for model %s.", Name, spfDevices[i].type);
	    break;
	}
    }

    if (!myFrame) {
	drv_SamsungSPF_list();
	error("%s: unknown model '%s'!", Name, s);
	return -1;
    }

    free(s);

    /* try to open USB device */
    drv_SamsungSPF_find();
    if (!myDev) {
	error("%s: No Samsung '%s' found!", Name, myFrame->type);
	return -1;
    }

    /* open display and switch to monitor mode if necessary */
    if (drv_SamsungSPF_open() == -1)
	return -1;

    int ret;

    /* real worker functions */
    drv_generic_graphic_real_blit = drv_SamsungSPF_blit;

    /* start display */
    if ((ret = drv_SamsungSPF_start(section)) != 0)
	return ret;

    /* initialize generic graphic driver */
    if ((ret = drv_generic_graphic_init(section, Name)) != 0)
	return ret;

    if (!quiet) {
	char buffer[40];
	qprintf(buffer, sizeof(buffer), "%s %dx%d", Name, DCOLS, DROWS);
	if (drv_generic_graphic_greet(buffer, NULL)) {
	    sleep(3);
	    drv_generic_graphic_clear();
	}
    }

    return 0;
}


/* close driver & display */
/* use this function for a graphic display */
int drv_SamsungSPF_quit(const int quiet)
{

    info("%s: shutting down.", Name);

    /* clear display */
    drv_generic_graphic_clear();

    /* say goodbye... */
    if (!quiet) {
	drv_generic_graphic_greet("goodbye!", NULL);
    }

    drv_generic_graphic_quit();

    debug("closing connection");
    printf("%s: Closing driver...\n", Name);
    usb_close(myDevHandle);
    free(myDev);
    free(myDevHandle);

    return (0);
}


/* use this one for a graphic display */
DRIVER drv_SamsungSPF = {
    .name = Name,
    .list = drv_SamsungSPF_list,
    .init = drv_SamsungSPF_init,
    .quit = drv_SamsungSPF_quit,
};
