Discussion:
[PATCH] USB: add support for SuperH USBF
Yoshihiro Shimoda
2008-01-10 12:07:57 UTC
Permalink
add support for SuperH USBF(USB peripheral controller).

supported CPUs are:
- SH7720
- SH7721
- SH7763

Signed-off-by: Yoshihiro Shimoda <***@renesas.com>
---
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 <***@renesas.com>
+ *
+ * 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 <***@renesas.com>
+ *
+ * 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-***@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
David Brownell
2008-01-27 23:20:23 UTC
Permalink
Post by Yoshihiro Shimoda
add support for SuperH USBF(USB peripheral controller).
- SH7720
- SH7721
- SH7763
I won't sign off on this until some of the obvious locking bugs
Post by Yoshihiro Shimoda
+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)
And status is zero ... don't clobber other fault codes
with ESHUTDOWN.
Post by Yoshihiro Shimoda
+ 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);
First, it's a serious bug if there's no complete(); don't bother
even checking that.

Second, that's a locking bug ... since this is at least sometimes
called with the spinlock held, it should be dropped before complete()
is called then reclaimed immediately afterwards. (Reason: it's routine
for completion callbacks to submit new requests, which grab the lock.)

Third, several callers didn't even hold that spinlock ... which is a
different class of problems. Notice the list_del_init() modifies
data structures that lock must protect. The IRQ handler doesn't
grab the spinlock; it should do so, and drop it around outcalls to
the setup() callback.

I get the impression this hasn't undergone much testing with
lockdep enabled... if SH doesn't yet support that, it should
have a manual lock audit as part of its code review. (I did
only part of one here. Basic rule: know what data the lock
protects, and make sure all access to that data is done while
holding that lock.)
Post by Yoshihiro Shimoda
+
+ if (restart) {
+ req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+ if (ep->desc)
+ start_packet(ep, req);
+ }
+}
+                       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);
You don't need the INIT_LIST_HEAD, since list_add_tail() clobbers
those fields right away.


Other random points:

- The start_ep_to_ep_addr() function would be more efficiently
done by having a byte in the EP structure; there's plenty of
padding available next to the "busy" bit.

- Most of the inline functions in the header file should be in
the C file instead. And for the IRQ logic, that's a bit much
to force as an inline ... just declare it static then let the
compiler decide if inlining is worthwhile.

- In several places you declare arrays on the stack, which are
used as constant lookup tables. Don't put those on the stack,
as it takes space and time to initialize them. Just declare
them as "static const".

- The "udc_enable" and "udc_disable" methods are misnamed; they
enable and disable endpoints, not the whole UDC. It's unwise
to do no error checking at all ... current gadget drivers don't
have bugs there, new (or custom) ones easily could. (Examples:
trying to set up ep0 as a bulk endpoint; getting direction or
size wrong on one of the fixed configuration endpoints; enabling
an endpoint that's already enabled; disabling one that's not
been enabled; no locking around ep->desc modification.)

- Actually *ALL* endpoint methods have that misnaming problem.

- If this driver isn't ready to support isochronous transfers,
then don't expose those ISO-only endpoints ...

- Don't bother with a NOP stub for fetching fifo status, it's
not needed.

That's just a brief review. I figure that once the locking bugs
are no longer so obvious, a more careful review would make sense.

- Dave


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
linux-usb-***@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
vichy
2008-01-28 09:31:13 UTC
Permalink
Dear all:
Recently I bought a 3.5g net card, HUAWEI e220, and I want to know whether
there is any driver support on Linux system.
Appreciate your help,
vichy


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
linux-usb-***@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
Alan Cox
2008-01-28 13:37:53 UTC
Permalink
On Mon, 28 Jan 2008 17:31:13 +0800
Post by vichy
Recently I bought a 3.5g net card, HUAWEI e220, and I want to know whether
there is any driver support on Linux system.
Google has pointers to the information you need to use the e220 on a
current Linux box

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
linux-usb-***@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
Greg KH
2008-01-28 18:08:49 UTC
Permalink
Post by vichy
Recently I bought a 3.5g net card, HUAWEI e220, and I want to know whether
there is any driver support on Linux system.
Yes, it should work just fine with the 2.6.24 kernel release.

thanks,

greg k-h

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
linux-usb-***@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
Yoshihiro Shimoda
2008-01-29 13:13:38 UTC
Permalink
Post by David Brownell
I won't sign off on this until some of the obvious locking bugs
Post by Yoshihiro Shimoda
+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)
And status is zero ... don't clobber other fault codes
with ESHUTDOWN.
I will modify it to check it before calling this function.
Post by David Brownell
Post by Yoshihiro Shimoda
+ 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);
First, it's a serious bug if there's no complete(); don't bother
even checking that.
This check was unnecessary...
Post by David Brownell
Second, that's a locking bug ... since this is at least sometimes
called with the spinlock held, it should be dropped before complete()
is called then reclaimed immediately afterwards. (Reason: it's routine
for completion callbacks to submit new requests, which grab the lock.)
Third, several callers didn't even hold that spinlock ... which is a
different class of problems. Notice the list_del_init() modifies
data structures that lock must protect. The IRQ handler doesn't
grab the spinlock; it should do so, and drop it around outcalls to
the setup() callback.
I get the impression this hasn't undergone much testing with
lockdep enabled... if SH doesn't yet support that, it should
have a manual lock audit as part of its code review. (I did
only part of one here. Basic rule: know what data the lock
protects, and make sure all access to that data is done while
holding that lock.)
I tried to enable lockdep. The development environment for USBF
could boot Linux :-) And it detected a problem of spinlock.
I will fix this problem.
Post by David Brownell
Post by Yoshihiro Shimoda
+
+ if (restart) {
+ req = list_entry(ep->queue.next, struct sh_udc_request, queue);
+ if (ep->desc)
+ start_packet(ep, req);
+ }
+}
+ 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);
You don't need the INIT_LIST_HEAD, since list_add_tail() clobbers
those fields right away.
I understood it.
Post by David Brownell
- The start_ep_to_ep_addr() function would be more efficiently
done by having a byte in the EP structure; there's plenty of
padding available next to the "busy" bit.
I understood it. I will try to modify this function.
Post by David Brownell
- Most of the inline functions in the header file should be in
the C file instead. And for the IRQ logic, that's a bit much
to force as an inline ... just declare it static then let the
compiler decide if inlining is worthwhile.
- In several places you declare arrays on the stack, which are
used as constant lookup tables. Don't put those on the stack,
as it takes space and time to initialize them. Just declare
them as "static const".
I understood it.
Post by David Brownell
- The "udc_enable" and "udc_disable" methods are misnamed; they
enable and disable endpoints, not the whole UDC. It's unwise
to do no error checking at all ... current gadget drivers don't
trying to set up ep0 as a bulk endpoint; getting direction or
size wrong on one of the fixed configuration endpoints; enabling
an endpoint that's already enabled; disabling one that's not
been enabled; no locking around ep->desc modification.)
- Actually *ALL* endpoint methods have that misnaming problem.
I will change the function name into "sh_udc_ep_enable".
And I will add error checking in enable method.
Post by David Brownell
- If this driver isn't ready to support isochronous transfers,
then don't expose those ISO-only endpoints ...
I will remove ISO-only endpoints.
Post by David Brownell
- Don't bother with a NOP stub for fetching fifo status, it's
not needed.
I will delete this method.
Post by David Brownell
That's just a brief review. I figure that once the locking bugs
are no longer so obvious, a more careful review would make sense.
I will try to debug the spinlock bug.

Thanks,
Yoshihiro Shimoda
Post by David Brownell
- Dave
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
linux-usb-***@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Loading...