[PATCH] USB: add support for SuperH USBF
Yoshihiro Shimoda <[email protected]> Thu, 10 Jan 2008 21:07:57 +0900
| Newsgroups | gmane.linux.usb.devel |
|---|---|
| Message-ID | <[email protected]> |
add support for SuperH USBF(USB peripheral controller). supported CPUs are: - SH7720 - SH7721 - SH7763 Signed-off-by: Yoshihiro Shimoda <[email protected]> --- 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 <[email protected]> + * + * 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 <[email protected]> + * + * 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 _______________________________________________ [email protected] To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel