/* $Id$
 * $URL$
 *
 * driver for USBHUB
 *
 * Copyright (C) 2006 Ernst Bachmann <e.bachmann@xebec.de>
 * Copyright (C) 2004,2006 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * Based on the USBLCD driver Copyright (C) 2003 Michael Reinelt <michael@reinelt.co.at>
 *
 * 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_USBHUB
 *
 */

#include "config.h"

#ifdef HAVE_USB_H
#include <usb.h>
#else
#error The USB-HUB driver only makes sense with USB support
#endif

#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "udelay.h"
#include "drv.h"
#include "drv_generic_gpio.h"



#define HUB_CONTROL_PORT 0x23
#define HUB_SET_FEATURE 3
#define HUB_SET_INDICATOR 22

static char Name[] = "USBHUB";

/* TODO: Better not specify defaults here, 
 * instead look for the first suitable HUB arround if
 * no Vendor/Product specified in config.
 */

static unsigned int hubVendor = 0x0409;
static unsigned int hubProduct = 0x0058;

static usb_dev_handle *hub = NULL;

typedef struct _usb_hub_descriptor {
    u_int8_t bLength;
    u_int8_t bDescriptorType;
    u_int8_t nNbrPorts;
    u_int8_t wHubCharacteristicLow;
    u_int8_t wHubCharacteristicHigh;
    u_int8_t bPwrOn2PwrGood;
    u_int8_t bHubContrCurrent;
    u_int8_t deviceRemovable;
    u_int8_t PortPwrCtrlMask[8];
} usb_hub_descriptor;

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


static int drv_UH_open(void)
{
    struct usb_bus *busses, *bus;
    struct usb_device *dev;

    hub = NULL;

    info("%s: scanning for an USB HUB (0x%04x:0x%04x)...", Name, hubVendor, hubProduct);

    usb_init();
    usb_find_busses();
    usb_find_devices();
    busses = usb_get_busses();

    for (bus = busses; bus; bus = bus->next) {
	for (dev = bus->devices; dev; dev = dev->next) {
	    if ((dev->descriptor.idVendor == hubVendor) && (dev->descriptor.idProduct == hubProduct)) {

		unsigned int v = dev->descriptor.bcdDevice;

		info("%s: found USBHUB V%1d%1d.%1d%1d on bus %s device %s", Name,
		     (v & 0xF000) >> 12, (v & 0xF00) >> 8, (v & 0xF0) >> 4, (v & 0xF), bus->dirname, dev->filename);

		if (dev->descriptor.bDeviceClass != USB_CLASS_HUB) {
		    error("%s: the specified device claims to be no HUB", Name);
		    return -1;
		}

		hub = usb_open(dev);
		if (!hub) {
		    error("%s: usb_open() failed!", Name);
		    return -1;
		}
		return 0;
	    }
	}
    }
    error("%s: could not find a USB HUB", Name);
    return -1;
}


static int drv_UH_close(void)
{
    debug("closing USB handle");

    usb_close(hub);

    return 0;
}


/*
 * Set the Indicator status on port "num+1" to val.
 * according to the USB Specification, the following values would be allowed:
 *
 *   0 : Automatic color (display link state etc)
 *   1 : Amber
 *   2 : Green
 *   3 : Off
 *   4..255: Reserved
 *
 */

static int drv_UH_set(const int num, const int val)
{
    int ret;

    if (!hub)
	return -1;

    if (val < 0 || val > 3) {
	info("%s: value %d out of range (0..3)", Name, val);
	return -1;
    }

    if ((ret = usb_control_msg(hub,
			       HUB_CONTROL_PORT,
			       HUB_SET_FEATURE, HUB_SET_INDICATOR, (val << 8) | (num + 1), NULL, 0, 1000)) != 0) {
	info("%s: usb_control_msg failed with %d", Name, ret);
	return -1;
    }

    return 0;
}


static int drv_UH_start(const char *section, const __attribute__ ((unused))
			int quiet)
{
    char *buf;

    usb_hub_descriptor hub_desc;
    int ret;


    buf = cfg_get(section, "Vendor", NULL);
    if (buf) {
	if (!*buf) {
	    error("%s: Strange Vendor Specification", Name);
	    return -1;
	}
	if (sscanf(buf, "0x%x", &hubVendor) != 1) {
	    error("%s: Strange Vendor Specification: [%s]", Name, buf);
	    return -1;
	}
    }

    buf = cfg_get(section, "Product", NULL);
    if (buf) {
	if (!*buf) {
	    error("%s: Strange Product Specification", Name);
	    return -1;
	}
	if (sscanf(buf, "0x%x", &hubProduct) != 1) {
	    error("%s: Strange Product Specification: [%s]", Name, buf);
	    return -1;
	}
    }

    if (drv_UH_open() < 0) {
	return -1;
    }


    if ((ret = usb_control_msg(hub,
			       USB_ENDPOINT_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
			       USB_REQ_GET_DESCRIPTOR, USB_DT_HUB << 8, 0, (char *) &hub_desc, sizeof(hub_desc),
			       1000)) <= 8) {
	error("%s: hub_get_descriptor failed with %d", Name, ret);
	drv_UH_close();
	return -1;
    }
    GPOS = hub_desc.nNbrPorts;
    debug("%s: HUB claims to have %d ports. Configuring them as GPOs", Name, GPOS);
    if (!(hub_desc.wHubCharacteristicLow & 0x80)) {
	error("%s: HUB claims to have no Indicator LEDs (Characteristics 0x%04x). Bailing out.", Name,
	      (hub_desc.wHubCharacteristicHigh << 8) | hub_desc.wHubCharacteristicLow);
	/* The HUB Tells us that there are no LEDs to control. Breaking? Maybe don't trust it and continue anyways? */
	drv_UH_close();
	return -1;

    }

    return 0;
}


/****************************************/
/***            plugins               ***/
/****************************************/

/* none at the moment... */


/****************************************/
/***        widget callbacks          ***/
/****************************************/


/* using drv_generic_text_draw(W) */
/* using drv_generic_text_icon_draw(W) */
/* using drv_generic_text_bar_draw(W) */


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


/* list models */
int drv_UH_list(void)
{
    printf("USBHUB");
    return 0;
}


/* initialize driver & display */
int drv_UH_init(const char *section, const int quiet)
{
    int ret;
    int i;

    info("%s: %s", Name, "$Rev$");



    /* start display */
    if ((ret = drv_UH_start(section, quiet)) != 0)
	return ret;


    /* real worker functions */
    drv_generic_gpio_real_set = drv_UH_set;


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

    /* register gpio widget, done already by generic_gpio */

    /* register plugins */
    /* none at the moment... */

    /* greeting */
    if (!quiet) {
	/* Light all LEDS green for a greeting */
	for (i = 0; i < GPOS; ++i) {
	    drv_UH_set(i, 2);
	}
	sleep(1);
	for (i = 0; i < GPOS; ++i) {
	    drv_UH_set(i, 3);	/* OFF */
	}
    }


    return 0;
}


/* close driver & display */
int drv_UH_quit(const int quiet)
{
    int i;
    debug("%s: shutting down.", Name);

    /* say goodbye... */
    if (!quiet) {
	/* Light all LEDS amber for a goodbye */
	for (i = 0; i < GPOS; ++i) {
	    drv_UH_set(i, 1);
	}
	sleep(1);

    }

    drv_generic_gpio_quit();

    drv_UH_close();

    info("%s: shutdown complete.", Name);
    return 0;
}


DRIVER drv_USBHUB = {
    .name = Name,
    .list = drv_UH_list,
    .init = drv_UH_init,
    .quit = drv_UH_quit,
};