Yoshihiro Shimoda
2008-01-10 12:07:57 UTC
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
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