Google
  Web www.spinics.net

[PATCH] USB: add support for SuperH USBF

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]


add support for SuperH USBF(USB peripheral controller).

supported CPUs are:
 - SH7720
 - SH7721
 - SH7763

Signed-off-by: Yoshihiro Shimoda <shimoda.yoshihiro@xxxxxxxxxxx>
---
 drivers/usb/gadget/Kconfig  |   25 +
 drivers/usb/gadget/Makefile |    1
 drivers/usb/gadget/sh_udc.c |  829 ++++++++++++++++++++++++++++++++++
 drivers/usb/gadget/sh_udc.h |  365 ++++++++++++++
 4 files changed, 1220 insertions(+)

diff -uprN a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
--- a/drivers/usb/gadget/Kconfig	2008-01-08 20:46:19.000000000 +0900
+++ b/drivers/usb/gadget/Kconfig	2008-01-10 20:12:04.000000000 +0900
@@ -334,6 +334,31 @@ config USB_AT91
 	depends on USB_GADGET_AT91
 	default USB_GADGET

+config USB_GADGET_SUPERH
+	boolean "SuperH USB Function driver"
+	help
+	   Some SuperH processors have a full speed peripheral controller.
+	   It has some fixed endpoints, and endpoint zero.
+
+	   This controller has only one configuration, numbered one.
+	   Therefore this controller cannot work g_zero driver.
+
+	   This driver sets interface number and alternate setting for
+	   each endpoint at the time of initialization for this
+	   controller. Please change interface number and alternate
+	   setting if necessary. But please perform power-on reset
+	   when you want to change this setting.
+
+	   Say "y" to link the driver statically, or "m" to build a
+	   dynamically linked module called "sh_udc" and force all
+	   gadget drivers to also be dynamically linked.
+
+config USB_SUPERH
+	tristate
+	depends on USB_GADGET_SUPERH
+	default USB_GADGET
+	select USB_GADGET_SELECTED
+
 config USB_GADGET_DUMMY_HCD
 	boolean "Dummy HCD (DEVELOPMENT)"
 	depends on (USB=y || (USB=m && USB_GADGET=m)) && EXPERIMENTAL
diff -uprN a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
--- a/drivers/usb/gadget/Makefile	2008-01-08 20:46:19.000000000 +0900
+++ b/drivers/usb/gadget/Makefile	2008-01-10 20:12:05.000000000 +0900
@@ -17,6 +17,7 @@ obj-$(CONFIG_USB_AT91)		+= at91_udc.o
 obj-$(CONFIG_USB_ATMEL_USBA)	+= atmel_usba_udc.o
 obj-$(CONFIG_USB_FSL_USB2)	+= fsl_usb2_udc.o
 obj-$(CONFIG_USB_M66592)	+= m66592-udc.o
+obj-$(CONFIG_USB_SUPERH)	+= sh_udc.o

 #
 # USB gadget drivers
diff -uprN a/drivers/usb/gadget/sh_udc.c b/drivers/usb/gadget/sh_udc.c
--- a/drivers/usb/gadget/sh_udc.c	1970-01-01 09:00:00.000000000 +0900
+++ b/drivers/usb/gadget/sh_udc.c	2008-01-10 20:12:04.000000000 +0900
@@ -0,0 +1,829 @@
+/*
+ * SuperH USB Function (USB gadget)
+ *
+ * Copyright (C) 2008 Renesas Solutions Corp.
+ *
+ * Author : Yoshihiro Shimoda <shimoda.yoshihiro@xxxxxxxxxxx>
+ *
+ * This program 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; version 2 of the License.
+ *
+ * This program 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#include "sh_udc.h"
+
+MODULE_DESCRIPTION("SuperH USBF gadget driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yoshihiro Shimoda");
+
+#define DRIVER_VERSION	"10 Jan 2008"
+
+static const char udc_name[] = "sh_udc";
+
+static const char *sh_udc_ep_name[] = {
+	"ep0",
+	"ep1out-bulk", "ep2in-bulk", "ep3in-int",
+	"ep4out-iso", "ep5in-iso"
+};
+
+/*
+ * make_EPIR00(ep_addr, ep_config, ep_interface)
+ * make_EPIR01(ep_alt, ep_type, ep_dir)
+ * make_EPIR02(ep_maxpacket)
+ * make_EPIR04(ep_addr)
+ */
+static const unsigned char ep_info[] = {
+	/* control */
+	make_EPIR00(0, 0, 0),
+	make_EPIR01(0, USBF_TYPE_CONTROL, 0),
+	make_EPIR02(USBF_EP0_SIZE), make_EPIR03(), make_EPIR04(0),
+
+	/* ep1 (bulk out) */
+	make_EPIR00(1, 1, 0),
+	make_EPIR01(0, USBF_TYPE_BULK, USBF_DIR_OUT),
+	make_EPIR02(USBF_BULK_SIZE), make_EPIR03(), make_EPIR04(1),
+
+	/* ep2 (bulk in) */
+	make_EPIR00(2, 1, 0),
+	make_EPIR01(0, USBF_TYPE_BULK, USBF_DIR_IN),
+	make_EPIR02(USBF_BULK_SIZE), make_EPIR03(), make_EPIR04(2),
+
+	/* ep3 (interrupt in) */
+	make_EPIR00(3, 1, 0),
+	make_EPIR01(0, USBF_TYPE_INTERRUPT, USBF_DIR_IN),
+	make_EPIR02(USBF_INT_SIZE), make_EPIR03(), make_EPIR04(3),
+
+	/* ep4 (isochronous out) */
+	make_EPIR00(4, 1, 0),
+	make_EPIR01(0, USBF_TYPE_ISO, USBF_DIR_OUT),
+	make_EPIR02(USBF_ISO_SIZE), make_EPIR03(), make_EPIR04(4),
+
+	/* ep5 (isochronous in)	*/
+	make_EPIR00(5, 1, 0),
+	make_EPIR01(0, USBF_TYPE_ISO, USBF_DIR_IN),
+	make_EPIR02(USBF_ISO_SIZE), make_EPIR03(), make_EPIR04(5),
+
+	0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0
+};
+
+static void disable_controller(struct sh_udc *sh_udc);
+static int sh_udc_queue(struct usb_ep *_ep, struct usb_request *_req,
+			gfp_t gfp_flags);
+static void write_packet(struct sh_udc *sh_udc, int ep_addr);
+
+static void transfer_complete(struct sh_udc_ep *ep,
+			      struct sh_udc_request *req,
+			      int status);
+/*-------------------------------------------------------------------------*/
+static int sh_udc_get_fifosize(int ep_addr)
+{
+	if (ep_addr == 0)
+		return USBF_EP0_SIZE;
+	else if (is_usb_type_interrupt(ep_addr))
+		return USBF_INT_SIZE;
+	else
+		return USBF_BULK_SIZE;
+}
+
+static void sh_udc_usb_connect(struct sh_udc *sh_udc)
+{
+	sh_udc->gadget.speed = USB_SPEED_FULL;
+	sh_udc_write(sh_udc, USBF_BRST | USBF_SETUPTS, USBF_IER0);
+	sh_udc_write(sh_udc, USBF_SETC, USBF_IER2);
+}
+
+static void sh_udc_usb_disconnect(struct sh_udc *sh_udc)
+{
+	sh_udc->gadget.speed = USB_SPEED_UNKNOWN;
+	spin_unlock(&sh_udc->lock);
+	sh_udc->driver->disconnect(&sh_udc->gadget);
+	spin_lock(&sh_udc->lock);
+
+	sh_udc_write(sh_udc, 0, USBF_IER0);
+	sh_udc_write(sh_udc, USBF_VBUS, USBF_IER1);
+	sh_udc_write(sh_udc, 0, USBF_IER2);
+
+	INIT_LIST_HEAD(&sh_udc->ep[0].queue);
+}
+
+static int start_ep_to_ep_addr(struct sh_udc_ep *ep)
+{
+	int ep_addr;
+
+	for (ep_addr = 0; ep_addr < USBF_NUM_ENDPOINT; ep_addr++) {
+		if (ep->ep.name == sh_udc_ep_name[ep_addr])
+			return ep_addr;
+	}
+
+	printk(KERN_ERR "error ep to ep_addr\n");
+	return 0;
+}
+
+static void start_packet_write(struct sh_udc_ep *ep, struct sh_udc_request *req)
+{
+	struct sh_udc *sh_udc = ep->sh_udc;
+	int ep_addr;
+
+	ep_addr = start_ep_to_ep_addr(ep);
+	if (sh_udc_get_data_status(sh_udc, ep_addr))
+		sh_udc_irq_enable(sh_udc, ep_addr, 1);
+	else
+		write_packet(sh_udc, ep_addr);
+}
+
+static void start_packet_read(struct sh_udc_ep *ep, struct sh_udc_request *req)
+{
+	struct sh_udc *sh_udc = ep->sh_udc;
+	int ep_addr;
+
+	ep_addr = start_ep_to_ep_addr(ep);
+	sh_udc_irq_enable(sh_udc, ep_addr, 0);
+}
+
+static void start_packet(struct sh_udc_ep *ep, struct sh_udc_request *req)
+{
+	if (ep->desc->bEndpointAddress & USB_DIR_IN)
+		start_packet_write(ep, req);
+	else
+		start_packet_read(ep, req);
+}
+
+static void start_ep0(struct sh_udc_ep *ep, struct sh_udc_request *req)
+{
+	if (req->req.length == 0) {
+		sh_udc_set_trigger(ep->sh_udc, 0, 1);	/* send zero-packet */
+		transfer_complete(ep, req, 0);
+	} else {
+		sh_udc_bclr(ep->sh_udc, USBF_EP0iTS, USBF_IFR0);
+		write_packet(ep->sh_udc, 0);
+	}
+}
+
+static void init_controller_once(struct sh_udc *sh_udc)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ep_info); i++)
+		sh_udc_write(sh_udc, ep_info[i], USBF_EPIR);
+}
+
+static void init_controller(struct sh_udc *sh_udc)
+{
+	sh_udc_write(sh_udc, 0x00, USBF_ISR0);
+	sh_udc_write(sh_udc, 0x00, USBF_ISR1);
+	sh_udc_write(sh_udc, 0x04, USBF_ISR2);
+	sh_udc_write(sh_udc, 0x00, USBF_ISR3);
+	sh_udc_write(sh_udc, 0x00, USBF_ISR4);
+
+	sh_udc_write(sh_udc, USBF_VBUS, USBF_IER1);
+	sh_udc_write(sh_udc, USBF_PULLUPE, USBF_DMA);
+}
+
+static void disable_controller(struct sh_udc *sh_udc)
+{
+	sh_udc_write(sh_udc, 0, USBF_DMA);	/* D+ pullup off */
+}
+
+/*-------------------------------------------------------------------------*/
+static void transfer_complete(struct sh_udc_ep *ep,
+			      struct sh_udc_request *req,
+			      int status)
+{
+	int restart = 0;
+
+	list_del_init(&req->queue);
+	if (ep->sh_udc->gadget.speed == USB_SPEED_UNKNOWN)
+		req->req.status = -ESHUTDOWN;
+	else
+		req->req.status = status;
+
+	if (!list_empty(&ep->queue))
+		restart = 1;
+
+	if (likely(req->req.complete))
+		req->req.complete(&ep->ep, &req->req);
+
+	if (restart) {
+		req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+		if (ep->desc)
+			start_packet(ep, req);
+	}
+}
+
+static void sh_udc_busreset(struct sh_udc *sh_udc)
+{
+	int i;
+	struct sh_udc_ep *ep;
+	struct sh_udc_request *req;
+
+	for (i = 0; i < USBF_NUM_ENDPOINT; i++) {
+		ep = &sh_udc->ep[i];
+
+		if (list_empty(&ep->queue))
+			continue;
+
+		req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+		transfer_complete(ep, req, -EPIPE);
+	}
+
+	sh_udc_write(sh_udc, USBF_EP3CLR | USBF_EP1CLR | USBF_EP2CLR |
+			USBF_EP0oCLR | USBF_EP0iCLR, USBF_FCLR0);
+}
+
+/* if return value is true, call class driver's setup() */
+static void setup_packet(struct sh_udc *sh_udc)
+{
+	struct usb_ctrlrequest ctrl;
+	unsigned char *p = (unsigned char *)&ctrl;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		*p++ = sh_udc_read(sh_udc, USBF_EPDR0s);
+
+	sh_udc_write(sh_udc, USBF_EP0oCLR | USBF_EP0iCLR, USBF_FCLR0);
+	sh_udc_write(sh_udc, USBF_EP0sRDFN, USBF_TRG0);
+
+	if (sh_udc->driver->setup(&sh_udc->gadget, &ctrl) < 0)
+		sh_udc_set_stall(sh_udc, 0, 1);
+}
+
+static void detect_set_config(struct sh_udc *sh_udc)
+{
+	struct usb_ctrlrequest ctrl;
+
+	ctrl.bRequestType = 0x00;
+	ctrl.bRequest = USB_REQ_SET_CONFIGURATION;
+	ctrl.wValue = sh_udc_config_value(sh_udc_read(sh_udc, USBF_CVR));
+	ctrl.wIndex = sh_udc_interface_value(sh_udc_read(sh_udc, USBF_CVR));
+	ctrl.wLength = 0;
+
+	if (sh_udc->driver->setup(&sh_udc->gadget, &ctrl) < 0)
+		sh_udc_set_stall(sh_udc, 0, 1);
+}
+
+static void write_packet(struct sh_udc *sh_udc, int ep_addr)
+{
+	struct sh_udc_ep *ep;
+	struct sh_udc_request *req;
+	void *buf;
+	int size, fifosize;
+
+	ep = &sh_udc->ep[ep_addr];
+	req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+
+	buf = req->req.buf + req->req.actual;
+	fifosize = sh_udc_get_fifosize(ep_addr);
+	size = min(fifosize, (int)(req->req.length - req->req.actual));
+
+	if (req->req.buf) {
+		if (unlikely(ep_addr == 0))
+			ep->fifoaddr = USBF_EPDR0i;
+		if (size > 0)
+			sh_udc_write_fifo(sh_udc, ep->fifoaddr, buf, size);
+		sh_udc_set_trigger(sh_udc, ep_addr, 1);
+	}
+
+	req->req.actual += size;
+
+	if ((!req->req.zero && (req->req.actual == req->req.length)) ||
+	    (size % fifosize) || (size == 0)) {
+		/* finish */
+		sh_udc_irq_disable(sh_udc, ep_addr, 1);
+		transfer_complete(ep, req, 0);
+	} else
+		sh_udc_irq_enable(sh_udc, ep_addr, 1);
+}
+
+static void read_packet(struct sh_udc *sh_udc, int ep_addr)
+{
+	struct sh_udc_ep *ep;
+	struct sh_udc_request *req;
+	void *buf;
+	int size, receive_size, fifosize;
+	int finish = 0;
+
+	ep = &sh_udc->ep[ep_addr];
+	req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+
+	buf = req->req.buf + req->req.actual;
+	receive_size = sh_udc_read(sh_udc, ep->receive_size_addr);
+	size = min(receive_size, (int)(req->req.length - req->req.actual));
+	fifosize = sh_udc_get_fifosize(ep_addr);
+
+	req->req.actual += size;
+
+	if ((!req->req.zero && (req->req.actual == req->req.length)) ||
+	    (size % fifosize) || (size == 0)) {
+		/* finish */
+		sh_udc_irq_disable(sh_udc, ep_addr, 0);
+		finish = 1;
+	} else
+		sh_udc_irq_enable(sh_udc, ep_addr, 0);
+
+	if (req->req.buf) {
+		if (unlikely(ep_addr == 0))
+			ep->fifoaddr = USBF_EPDR0o;
+		sh_udc_read_fifo(sh_udc, ep->fifoaddr, buf, size);
+		sh_udc_set_trigger(sh_udc, ep_addr, 0);
+	}
+
+	if (finish)
+		transfer_complete(ep, req, 0);
+}
+
+static irqreturn_t sh_udc_irq(int irq, void *_sh_udc)
+{
+	struct sh_udc *sh_udc = _sh_udc;
+	unsigned char flg0, flg1, flg2, flg3, flg4;
+
+	flg0 = sh_udc_read(sh_udc, USBF_IFR0) & sh_udc_read(sh_udc, USBF_IER0);
+	flg1 = sh_udc_read(sh_udc, USBF_IFR1) & sh_udc_read(sh_udc, USBF_IER1);
+	flg2 = sh_udc_read(sh_udc, USBF_IFR2) & sh_udc_read(sh_udc, USBF_IER2);
+	flg3 = sh_udc_read(sh_udc, USBF_IFR3) & sh_udc_read(sh_udc, USBF_IER3);
+	flg4 = sh_udc_read(sh_udc, USBF_IFR4) & sh_udc_read(sh_udc, USBF_IER4);
+
+	if (flg0 & USBF_BRST) {
+		sh_udc_bclr(sh_udc, USBF_BRST, USBF_IFR0);
+		sh_udc_busreset(sh_udc);
+	}
+	if (flg0 & USBF_SETUPTS) {
+		sh_udc_bclr(sh_udc, USBF_SETUPTS, USBF_IFR0);
+		setup_packet(sh_udc);
+	}
+	if (flg1 & USBF_VBUS) {
+		sh_udc_bclr(sh_udc, USBF_VBUS, USBF_IFR1);
+
+		/* start vbus sampling */
+		sh_udc->old_vbus = sh_udc_read(sh_udc, USBF_VBUSMN) &
+						USBF_VBUSMN;
+			sh_udc->scount = USBF_MAX_SAMPLING;
+			mod_timer(&sh_udc->timer,
+				  jiffies + msecs_to_jiffies(50));
+	}
+	if (flg0 & USBF_EP1FULL) {
+		sh_udc_bclr(sh_udc, USBF_EP1FULL, USBF_IFR0);
+		read_packet(sh_udc, 1);
+	}
+	if (flg0 & USBF_EP2EMPTY)
+		write_packet(sh_udc, 2);
+	if (flg0 & USBF_EP0oTS) {
+		sh_udc_bclr(sh_udc, USBF_EP0oTS, USBF_IFR0);
+		read_packet(sh_udc, 0);
+	}
+	if (flg0 & USBF_EP0iTS) {
+		sh_udc_bclr(sh_udc, USBF_EP0iTS, USBF_IFR0);
+		write_packet(sh_udc, 0);
+	}
+	if (flg2 & USBF_SETC) {
+		sh_udc_bclr(sh_udc, USBF_SETC, USBF_IFR2);
+		detect_set_config(sh_udc);
+	}
+	if (flg1 & USBF_EP3TS) {
+		sh_udc_bclr(sh_udc, USBF_EP3TS, USBF_IFR1);
+		write_packet(sh_udc, 3);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void sh_udc_timer(unsigned long _sh_udc)
+{
+	struct sh_udc *sh_udc = (struct sh_udc *)_sh_udc;
+	unsigned long flags;
+	unsigned char tmp;
+
+	spin_lock_irqsave(&sh_udc->lock, flags);
+
+	if (sh_udc->scount > 0) {
+		tmp = sh_udc_read(sh_udc, USBF_IFR1) & USBF_VBUSMN;
+		if (tmp == sh_udc->old_vbus) {
+			sh_udc->scount--;
+			if (sh_udc->scount == 0) {
+				if (tmp == USBF_VBUSMN)
+					sh_udc_usb_connect(sh_udc);
+				else
+					sh_udc_usb_disconnect(sh_udc);
+			} else {
+				mod_timer(&sh_udc->timer,
+					  jiffies + msecs_to_jiffies(50));
+			}
+		} else {
+			sh_udc->scount = USBF_MAX_SAMPLING;
+			sh_udc->old_vbus = tmp;
+			mod_timer(&sh_udc->timer,
+				  jiffies + msecs_to_jiffies(50));
+		}
+	}
+
+	spin_unlock_irqrestore(&sh_udc->lock, flags);
+}
+
+/*-------------------------------------------------------------------------*/
+static int sh_udc_enable(struct usb_ep *_ep,
+			 const struct usb_endpoint_descriptor *desc)
+{
+	struct sh_udc_ep *ep;
+
+	ep = container_of(_ep, struct sh_udc_ep, ep);
+	ep->desc = desc;
+	INIT_LIST_HEAD(&ep->queue);
+	return 0;
+}
+
+static int sh_udc_disable(struct usb_ep *_ep)
+{
+	struct sh_udc_ep *ep;
+
+	ep = container_of(_ep, struct sh_udc_ep, ep);
+	BUG_ON(!ep);
+
+	return 0;
+}
+
+static struct usb_request *sh_udc_alloc_request(struct usb_ep *_ep,
+						gfp_t gfp_flags)
+{
+	struct sh_udc_request *req;
+
+	req = kzalloc(sizeof(struct sh_udc_request), gfp_flags);
+	if (!req)
+		return NULL;
+
+	INIT_LIST_HEAD(&req->queue);
+
+	return &req->req;
+}
+
+static void sh_udc_free_request(struct usb_ep *_ep, struct usb_request *_req)
+{
+	struct sh_udc_request *req;
+
+	req = container_of(_req, struct sh_udc_request, req);
+	kfree(req);
+}
+
+static int sh_udc_queue(struct usb_ep *_ep, struct usb_request *_req,
+			gfp_t gfp_flags)
+{
+	struct sh_udc_ep *ep;
+	struct sh_udc_request *req;
+	unsigned long flags;
+	int request = 0;
+
+	ep = container_of(_ep, struct sh_udc_ep, ep);
+	req = container_of(_req, struct sh_udc_request, req);
+
+	if (ep->sh_udc->gadget.speed == USB_SPEED_UNKNOWN)
+		return -ESHUTDOWN;
+
+	spin_lock_irqsave(&ep->sh_udc->lock, flags);
+
+	if (list_empty(&ep->queue))
+		request = 1;
+
+	list_add_tail(&req->queue, &ep->queue);
+	req->req.actual = 0;
+	req->req.status = -EINPROGRESS;
+
+	if (ep->desc == 0)	/* control */
+		start_ep0(ep, req);
+	else {
+		if (request && !ep->busy)
+			start_packet(ep, req);
+	}
+
+	spin_unlock_irqrestore(&ep->sh_udc->lock, flags);
+
+	return 0;
+}
+
+static int sh_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+	struct sh_udc_ep *ep;
+	struct sh_udc_request *req;
+	unsigned long flags;
+
+	ep = container_of(_ep, struct sh_udc_ep, ep);
+	req = container_of(_req, struct sh_udc_request, req);
+
+	spin_lock_irqsave(&ep->sh_udc->lock, flags);
+	if (!list_empty(&ep->queue))
+		transfer_complete(ep, req, -ECONNRESET);
+	spin_unlock_irqrestore(&ep->sh_udc->lock, flags);
+
+	return 0;
+}
+
+static int sh_udc_set_halt(struct usb_ep *_ep, int value)
+{
+	struct sh_udc_ep *ep;
+	struct sh_udc_request *req = NULL;
+	unsigned long flags;
+	int ret = 0;
+
+	ep = container_of(_ep, struct sh_udc_ep, ep);
+	if (!list_empty(&ep->queue))
+		req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+
+	spin_lock_irqsave(&ep->sh_udc->lock, flags);
+	if (!list_empty(&ep->queue)) {
+		ret = -EAGAIN;
+		goto out;
+	}
+	if (value) {
+		ep->busy = 1;
+		sh_udc_set_stall(ep->sh_udc, start_ep_to_ep_addr(ep), 1);
+	} else {
+		ep->busy = 0;
+		sh_udc_set_stall(ep->sh_udc, start_ep_to_ep_addr(ep), 0);
+	}
+
+out:
+	spin_unlock_irqrestore(&ep->sh_udc->lock, flags);
+	return ret;
+}
+
+static int sh_udc_fifo_status(struct usb_ep *_ep)
+{
+	return -EOPNOTSUPP;
+}
+
+static void sh_udc_fifo_flush(struct usb_ep *_ep)
+{
+	struct sh_udc_ep *ep;
+	unsigned long flags;
+
+	ep = container_of(_ep, struct sh_udc_ep, ep);
+	spin_lock_irqsave(&ep->sh_udc->lock, flags);
+	if (list_empty(&ep->queue) && !ep->busy)
+		sh_udc_fifo_clear(ep->sh_udc, start_ep_to_ep_addr(ep));
+	spin_unlock_irqrestore(&ep->sh_udc->lock, flags);
+}
+
+static struct usb_ep_ops sh_udc_ep_ops = {
+	.enable		= sh_udc_enable,
+	.disable	= sh_udc_disable,
+
+	.alloc_request	= sh_udc_alloc_request,
+	.free_request	= sh_udc_free_request,
+
+	.queue		= sh_udc_queue,
+	.dequeue	= sh_udc_dequeue,
+
+	.set_halt	= sh_udc_set_halt,
+	.fifo_status	= sh_udc_fifo_status,
+	.fifo_flush	= sh_udc_fifo_flush,
+};
+
+/*-------------------------------------------------------------------------*/
+static struct sh_udc *the_controller;
+
+int usb_gadget_register_driver(struct usb_gadget_driver *driver)
+{
+	struct sh_udc *sh_udc = the_controller;
+	int retval;
+
+	if (!driver ||
+	    !driver->bind ||
+	    !driver->unbind ||
+	    !driver->setup)
+		return -EINVAL;
+	if (!sh_udc)
+		return -ENODEV;
+	if (sh_udc->driver)
+		return -EBUSY;
+
+	/* hook up the driver */
+	driver->driver.bus = NULL;
+	sh_udc->driver = driver;
+	sh_udc->gadget.dev.driver = &driver->driver;
+
+	retval = device_add(&sh_udc->gadget.dev);
+	if (retval) {
+		printk(KERN_ERR "device_add error (%d)\n", retval);
+		goto error;
+	}
+
+	retval = driver->bind(&sh_udc->gadget);
+	if (retval) {
+		printk(KERN_ERR "bind to driver error (%d)\n", retval);
+		device_del(&sh_udc->gadget.dev);
+		goto error;
+	}
+
+	init_controller(sh_udc);
+	if (sh_udc_read(sh_udc, USBF_IFR1) & USBF_VBUSMN) {
+		/* start vbus sampling */
+		sh_udc->old_vbus = USBF_VBUSMN;
+		sh_udc->scount = USBF_MAX_SAMPLING;
+		mod_timer(&sh_udc->timer,
+			  jiffies + msecs_to_jiffies(50));
+	}
+
+	return 0;
+
+error:
+	sh_udc->driver = NULL;
+	sh_udc->gadget.dev.driver = NULL;
+
+	return retval;
+}
+EXPORT_SYMBOL(usb_gadget_register_driver);
+
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+	struct sh_udc *sh_udc = the_controller;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sh_udc->lock, flags);
+	if (sh_udc->gadget.speed != USB_SPEED_UNKNOWN)
+		sh_udc_usb_disconnect(sh_udc);
+	spin_unlock_irqrestore(&sh_udc->lock, flags);
+
+	driver->unbind(&sh_udc->gadget);
+
+	disable_controller(sh_udc);
+
+	device_del(&sh_udc->gadget.dev);
+	sh_udc->driver = NULL;
+	return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+/*-------------------------------------------------------------------------*/
+static int sh_udc_get_frame(struct usb_gadget *_gadget)
+{
+	/* this controller is not supported at isochronous */
+	return 0;
+}
+
+static struct usb_gadget_ops sh_udc_gadget_ops = {
+	.get_frame		= sh_udc_get_frame,
+};
+
+static int sh_udc_remove(struct platform_device *pdev)
+{
+	struct sh_udc		*sh_udc = dev_get_drvdata(&pdev->dev);
+
+	del_timer_sync(&sh_udc->timer);
+	iounmap(sh_udc->reg);
+	free_irq(platform_get_irq(pdev, 0), sh_udc);
+	kfree(sh_udc);
+	return 0;
+}
+
+#define resource_len(r) (((r)->end - (r)->start) + 1)
+static int sh_udc_probe(struct platform_device *pdev)
+{
+	struct resource *res = NULL;
+	int irq = -1;
+	void __iomem *reg = NULL;
+	struct sh_udc *sh_udc = NULL;
+	int ret = 0;
+	int i;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -ENODEV;
+		printk(KERN_ERR "platform_get_resource_byname error.\n");
+		goto clean_up;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = -ENODEV;
+		printk(KERN_ERR "platform_get_irq error.\n");
+		goto clean_up;
+	}
+
+	reg = ioremap(res->start, resource_len(res));
+	if (reg == NULL) {
+		ret = -ENOMEM;
+		printk(KERN_ERR "ioremap error.\n");
+		goto clean_up;
+	}
+
+	/* initialize ucd */
+	sh_udc = kzalloc(sizeof(struct sh_udc), GFP_KERNEL);
+	if (sh_udc == NULL) {
+		printk(KERN_ERR "kzalloc error\n");
+		goto clean_up;
+	}
+
+	spin_lock_init(&sh_udc->lock);
+	dev_set_drvdata(&pdev->dev, sh_udc);
+
+	sh_udc->gadget.ops = &sh_udc_gadget_ops;
+	device_initialize(&sh_udc->gadget.dev);
+	strcpy(sh_udc->gadget.dev.bus_id, "gadget");
+	sh_udc->gadget.is_dualspeed = 1;
+	sh_udc->gadget.dev.parent = &pdev->dev;
+	sh_udc->gadget.dev.dma_mask = pdev->dev.dma_mask;
+	sh_udc->gadget.dev.release = pdev->dev.release;
+	sh_udc->gadget.name = udc_name;
+
+	init_timer(&sh_udc->timer);
+	sh_udc->timer.function = sh_udc_timer;
+	sh_udc->timer.data = (unsigned long)sh_udc;
+	sh_udc->reg = reg;
+
+	ret = request_irq(irq, sh_udc_irq, IRQF_DISABLED | IRQF_SHARED,
+			  udc_name, sh_udc);
+	if (ret < 0) {
+		printk(KERN_ERR "request_irq error (%d)\n", ret);
+		goto clean_up;
+	}
+
+	INIT_LIST_HEAD(&sh_udc->gadget.ep_list);
+	sh_udc->gadget.ep0 = &sh_udc->ep[0].ep;
+	INIT_LIST_HEAD(&sh_udc->gadget.ep0->ep_list);
+	for (i = 0; i < USBF_NUM_ENDPOINT; i++) {
+		struct sh_udc_ep *ep = &sh_udc->ep[i];
+
+		if (i != 0) {
+			INIT_LIST_HEAD(&sh_udc->ep[i].ep.ep_list);
+			list_add_tail(&sh_udc->ep[i].ep.ep_list,
+				      &sh_udc->gadget.ep_list);
+		}
+		ep->sh_udc = sh_udc;
+		INIT_LIST_HEAD(&ep->queue);
+		ep->ep.name = sh_udc_ep_name[i];
+		ep->ep.ops = &sh_udc_ep_ops;
+		ep->ep.maxpacket = sh_udc_get_fifosize(i);
+	}
+	sh_udc->ep[1].receive_size_addr = USBF_EPSZ1;
+	sh_udc->ep[4].receive_size_addr = USBF_EPSZ4;
+	sh_udc->ep[0].fifoaddr = USBF_EPDR0i;
+	sh_udc->ep[1].fifoaddr = USBF_EPDR1;
+	sh_udc->ep[2].fifoaddr = USBF_EPDR2;
+	sh_udc->ep[3].fifoaddr = USBF_EPDR3;
+	sh_udc->ep[4].fifoaddr = USBF_EPDR4;
+	sh_udc->ep[5].fifoaddr = USBF_EPDR5;
+
+	the_controller = sh_udc;
+	init_controller_once(sh_udc);
+
+	printk(KERN_INFO "driver %s, %s\n", udc_name, DRIVER_VERSION);
+	return 0;
+
+clean_up:
+	kfree(sh_udc);
+	if (reg)
+		iounmap(reg);
+
+	return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+static struct platform_driver sh_udc_driver = {
+	.probe =	sh_udc_probe,
+	.remove =	sh_udc_remove,
+	.driver		= {
+		.name =	(char *) udc_name,
+	},
+};
+
+static int __init sh_udc_udc_init(void)
+{
+	return platform_driver_register(&sh_udc_driver);
+}
+
+static void __exit sh_udc_udc_cleanup(void)
+{
+	platform_driver_unregister(&sh_udc_driver);
+}
+
+module_init(sh_udc_udc_init);
+module_exit(sh_udc_udc_cleanup);
+
diff -uprN a/drivers/usb/gadget/sh_udc.h b/drivers/usb/gadget/sh_udc.h
--- a/drivers/usb/gadget/sh_udc.h	1970-01-01 09:00:00.000000000 +0900
+++ b/drivers/usb/gadget/sh_udc.h	2008-01-10 20:12:05.000000000 +0900
@@ -0,0 +1,365 @@
+/*
+ * SuperH USB Function (USB gadget)
+ *
+ * Copyright (C) 2008 Renesas Solutions Corp.
+ *
+ * Author : Yoshihiro Shimoda <shimoda.yoshihiro@xxxxxxxxxxx>
+ *
+ * This program 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; version 2 of the License.
+ *
+ * This program 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __SH_UDC_H__
+#define __SH_UDC_H__
+
+#define USBF_IFR0	0x00
+#define USBF_IFR1	0x01
+#define USBF_IFR2	0x02
+#define USBF_IFR3	0x03
+#define USBF_IER0	0x04
+#define USBF_IER1	0x05
+#define USBF_IER2	0x06
+#define USBF_IER3	0x07
+#define USBF_ISR0	0x08
+#define USBF_ISR1	0x09
+#define USBF_ISR2	0x0A
+#define USBF_ISR3	0x0B
+#define USBF_EPDR0i	0x0C
+#define USBF_EPDR0o	0x0D
+#define USBF_EPDR0s	0x0E
+#define USBF_EPDR1	0x10
+#define USBF_EPDR2	0x14
+#define USBF_EPDR3	0x18
+#define USBF_EPDR4	0x1C
+#define USBF_EPDR5	0x20
+#define USBF_EPSZ0o	0x24
+#define USBF_EPSZ1	0x25
+#define USBF_EPSZ4	0x26
+#define USBF_DASTS0	0x27
+#define USBF_FCLR0	0x28
+#define USBF_FCLR1	0x29
+#define USBF_EPSTL0	0x2A
+#define USBF_EPSTL1	0x2B
+#define USBF_TRG0	0x2C
+#define USBF_DMA	0x2D
+#define USBF_CVR	0x2E
+#define USBF_CTRL0	0x2F
+#define USBF_TSRH	0x30
+#define USBF_TSRL	0x31
+#define USBF_EPIR	0x32
+#define USBF_IFR4	0x34
+#define USBF_IER4	0x35
+#define USBF_ISR4	0x36
+#define USBF_CTRL1	0x37
+#define USBF_TMRH	0x38
+#define USBF_TMRL	0x39
+#define USBF_STOH	0x3A
+#define USBF_STOL	0x3B
+
+/* IxR0 */
+#define USBF_BRST		0x80
+#define USBF_EP1FULL		0x40
+#define USBF_EP2TR		0x20
+#define USBF_EP2EMPTY		0x10
+#define USBF_SETUPTS		0x08
+#define USBF_EP0oTS		0x04
+#define USBF_EP0iTR		0x02
+#define USBF_EP0iTS		0x01
+
+/* IxR1 */
+#define USBF_VBUSMN		0x08
+#define USBF_EP3TR		0x04
+#define USBF_EP3TS		0x02
+#define USBF_VBUS		0x01
+
+/* IxR2 */
+#define USBF_SURSS		0x20
+#define USBF_SURSF		0x10
+#define USBF_CFDN		0x08
+#define USBF_SETC		0x02
+#define USBF_SETI		0x01
+
+/* IxR3 */
+#define USBF_EP5TR		0x08
+#define USBF_EP5TS		0x04
+#define USBF_EP4TF		0x02
+#define USBF_EP4TS		0x01
+
+/* IxR4 */
+#define USBF_TMOUT		0x01
+
+/* TRG0 */
+#define USBF_EP3PKTE		0x40
+#define USBF_EP1RDFN		0x20
+#define USBF_EP2PKTE		0x10
+#define USBF_EP0sRDFN		0x04
+#define USBF_EP0oRDFN		0x02
+#define USBF_EP0iPKTE		0x01
+
+/* DASTS0 */
+#define USBF_EP3DE		0x20
+#define USBF_EP2DE		0x10
+#define USBF_EP0iDE		0x01
+
+/* FCLR0 */
+#define USBF_EP3CLR		0x40
+#define USBF_EP1CLR		0x20
+#define USBF_EP2CLR		0x10
+#define USBF_EP0oCLR		0x02
+#define USBF_EP0iCLR		0x01
+
+/* FCLR1 */
+#define USBF_EP5CCLR		0x10
+#define USBF_EP5CLR		0x02
+#define USBF_EP4CLR		0x01
+
+/* DMA */
+#define USBF_PULLUPE		0x04
+#define USBF_EP2DMAE		0x02
+#define USBF_EP1DMAE		0x01
+
+/* EPSTL0 */
+#define USBF_EP3STL		0x08
+#define USBF_EP2STL		0x04
+#define USBF_EP1STL		0x02
+#define USBF_EP0STL		0x01
+
+/* EPSTL1 */
+#define USBF_EP5STL		0x02
+#define USBF_EP4STL		0x01
+
+/* CVR */
+#define USBF_CNFV		0xC0
+#define USBF_INTV		0x30
+#define USBF_ALTV		0x07
+
+/* CTLR */
+#define USBF_RWUPS		0x10
+#define USBF_RSME		0x08
+#define USBF_PWWD		0x04
+#define USBF_ASCE		0x02
+
+/* macro */
+#define USBF_TYPE_CONTROL	0
+#define USBF_TYPE_ISO		1
+#define USBF_TYPE_BULK		2
+#define USBF_TYPE_INTERRUPT	3
+
+#define USBF_DIR_OUT		0
+#define USBF_DIR_IN		1
+
+#define USBF_MAX_SAMPLING	5
+#define USBF_NUM_ENDPOINT	6
+#define USBF_EP0_SIZE		8
+#define USBF_BULK_SIZE		64
+#define USBF_ISO_SIZE		64
+#define USBF_INT_SIZE		8
+
+#define make_EPIR00(ep_addr, ep_config, ep_interface)	\
+	(((ep_addr & 0x0F) << 4) | ((ep_config & 0x03) << 2) |	\
+	 ((ep_interface & 0x03)))
+#define make_EPIR01(ep_alt, ep_type, ep_dir)	\
+	(((ep_alt & 0x03) << 6) | ((ep_type & 0x03) << 4) |	\
+	 ((ep_dir & 0x01) << 3))
+#define make_EPIR02(ep_maxpacket)	((ep_maxpacket & 0xFF) << 1)
+#define make_EPIR03()			0
+#define make_EPIR04(ep_addr)		(ep_addr & 0x0F)
+
+#define	sh_udc_config_value(cvr)	((cvr & USBF_CNFV) >> 6)
+#define	sh_udc_interface_value(cvr)	((cvr & USBF_INTV) >> 4)
+
+#define is_usb_type_interrupt(ep_addr)	((ep_addr == 3 || ep_addr == 6))
+
+struct sh_udc_request {
+	struct usb_request	req;
+	struct list_head	queue;
+};
+
+struct sh_udc_ep {
+	struct usb_ep		ep;
+	struct sh_udc		*sh_udc;
+
+	struct list_head	queue;
+	unsigned 		busy:1;
+	const struct usb_endpoint_descriptor	*desc;
+
+	unsigned long fifoaddr;
+	unsigned long receive_size_addr;
+};
+
+struct sh_udc {
+	spinlock_t			lock;
+	void __iomem			*reg;
+	struct usb_gadget		gadget;
+	struct usb_gadget_driver	*driver;
+	struct sh_udc_ep		ep[USBF_NUM_ENDPOINT];
+	struct timer_list		timer;
+	u16				old_vbus;
+	int				scount;
+};
+
+#define gadget_to_sh_udc(_gadget) container_of(_gadget, struct sh_udc, gadget)
+#define sh_udc_to_gadget(sh_udc) (&sh_udc->gadget)
+
+/*-------------------------------------------------------------------------*/
+static inline unsigned long get_offset(unsigned long offset)
+{
+#if defined(CONFIG_CPU_SUBTYPE_SH7763)
+	return (offset << 2) + 1;
+#else
+	return offset;
+#endif
+}
+
+static inline u16 sh_udc_read(struct sh_udc *sh_udc, unsigned long offset)
+{
+	return inb((unsigned long)sh_udc->reg + get_offset(offset));
+}
+
+static inline void sh_udc_read_fifo(struct sh_udc *sh_udc,
+				    unsigned long offset,
+				    void *buf, unsigned long len)
+{
+	insb((unsigned long)sh_udc->reg + get_offset(offset), buf, len);
+}
+
+static inline void sh_udc_write(struct sh_udc *sh_udc, u16 val,
+				unsigned long offset)
+{
+	outb(val, (unsigned long)sh_udc->reg + get_offset(offset));
+}
+
+static inline void sh_udc_write_fifo(struct sh_udc *sh_udc,
+				     unsigned long offset,
+				     void *buf, unsigned long len)
+{
+	outsb((unsigned long)sh_udc->reg + get_offset(offset), buf, len);
+}
+
+static inline void sh_udc_bset(struct sh_udc *sh_udc,
+				unsigned char val,
+				unsigned long offset)
+{
+	sh_udc_write(sh_udc, sh_udc_read(sh_udc, offset) | val, offset);
+}
+
+static inline void sh_udc_bclr(struct sh_udc *sh_udc,
+				unsigned char val,
+				unsigned long offset)
+{
+	sh_udc_write(sh_udc, sh_udc_read(sh_udc, offset) & ~val, offset);
+}
+
+/*--------------------- CPU independence ---------------------*/
+static inline void sh_udc_set_stall(struct sh_udc *sh_udc, int ep_addr,
+					int stall)
+{
+	unsigned char bit;
+	unsigned long offset;
+
+	if (ep_addr <= 3) {
+		const unsigned char stl0_ep2bit[] =
+		{ USBF_EP0STL, USBF_EP1STL, USBF_EP2STL, USBF_EP3STL };
+		bit = stl0_ep2bit[ep_addr];
+		offset = USBF_EPSTL0;
+	} else {
+		const unsigned char stl1_ep2bit[] =
+			{ USBF_EP4STL, USBF_EP5STL };
+		bit = stl1_ep2bit[ep_addr];
+		offset = USBF_EPSTL1;
+	}
+
+	if (stall)
+		sh_udc_bset(sh_udc, bit, offset);
+	else
+		sh_udc_bclr(sh_udc, bit, offset);
+}
+
+static inline void sh_udc_fifo_clear(struct sh_udc *sh_udc, int ep_addr)
+{
+	if (ep_addr == 0)
+		return;
+	else if (ep_addr <= 3) {
+		const unsigned char fclr0_ep2bit[] =
+			{ USBF_EP1CLR, USBF_EP2CLR, USBF_EP3CLR };
+		sh_udc_write(sh_udc, fclr0_ep2bit[ep_addr - 1], USBF_FCLR0);
+	} else {
+		const unsigned char fclr1_ep2bit[] =
+			{ USBF_EP4STL, USBF_EP5STL };
+		sh_udc_write(sh_udc, fclr1_ep2bit[ep_addr - 3], USBF_FCLR1);
+	}
+}
+
+static inline void sh_udc_set_trigger(struct sh_udc *sh_udc, int ep_addr,
+					int dir_in)
+{
+	if (ep_addr == 0) {
+		if (dir_in)
+			sh_udc_write(sh_udc, USBF_EP0iPKTE, USBF_TRG0);
+		else
+			sh_udc_write(sh_udc, USBF_EP0oRDFN, USBF_TRG0);
+	} else if (ep_addr <= 3) {
+		const unsigned char trg0_ep2bit[] =
+			{ USBF_EP1RDFN, USBF_EP2PKTE, USBF_EP3PKTE };
+		sh_udc_write(sh_udc, trg0_ep2bit[ep_addr - 1], USBF_TRG0);
+	}
+}
+
+static inline int sh_udc_get_data_status(struct sh_udc *sh_udc, int ep_addr)
+{
+	unsigned char val = 0;
+
+	if (ep_addr <= 3) {
+		const unsigned char d0_ep2bit[] =
+			{ USBF_DASTS0, 0, USBF_EP2DE, USBF_EP3DE };
+		val = sh_udc_read(sh_udc, USBF_DASTS0) & d0_ep2bit[ep_addr];
+	}
+
+	return (val) ? 1 : 0;
+}
+
+static inline void sh_udc_irq_enable(struct sh_udc *sh_udc, int ep_addr,
+					int dir_in)
+{
+	if (ep_addr == 0) {
+		if (dir_in)
+			sh_udc_bset(sh_udc, USBF_EP0iTS, USBF_IER0);
+		else
+			sh_udc_bset(sh_udc, USBF_EP0oTS, USBF_IER0);
+	} else if (ep_addr <= 2) {
+		const unsigned char ier0_ep2bit[] =
+			{ USBF_EP1FULL, USBF_EP2EMPTY };
+		sh_udc_bset(sh_udc, ier0_ep2bit[ep_addr - 1], USBF_IER0);
+	} else if (ep_addr == 3)
+		sh_udc_bset(sh_udc, USBF_EP3TS, USBF_IER1);
+}
+
+static inline void sh_udc_irq_disable(struct sh_udc *sh_udc, int ep_addr,
+					int dir_in)
+{
+	if (ep_addr == 0) {
+		if (dir_in)
+			sh_udc_bclr(sh_udc, USBF_EP0iTS, USBF_IER0);
+		else
+			sh_udc_bclr(sh_udc, USBF_EP0oTS, USBF_IER0);
+	} else if (ep_addr <= 2) {
+		const unsigned char ier0_ep2bit[] =
+			{ USBF_EP1FULL, USBF_EP2EMPTY };
+		sh_udc_bclr(sh_udc, ier0_ep2bit[ep_addr - 1], USBF_IER0);
+	} else if (ep_addr == 3)
+		sh_udc_bclr(sh_udc, USBF_EP3TS, USBF_IER1);
+}
+
+#endif	/* ifndef __SH_UDC_H__ */
+


-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://ad.doubleclick.net/clk;164216239;13503038;w?http://sf.net/marketplace
_______________________________________________
linux-usb-devel@xxxxxxxxxxxxxxxxxxxxx
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

[Home]     [Video for Linux]     [Photo]     [Yosemite Forum]     [Yosemite Photos]    [Video Projectors]     [PDAs]     [Hacking TiVo]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Devices]     [Big List of Linux Books]     [Free Dating]

  Powered by Linux