/* * linux/drivers/usb/gadget/s3c2410_udc.c * Samsung on-chip full speed USB device controllers * * Copyright (C) 2004 Herbert Pötzl * * 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; either version 2 of the License, or * (at your option) any later version. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* remove this ASAP!!! */ // #include "../core/hub.h" #include #include #include #include #include #include #include #include #include #include #include #include "s3c2410_udc.h" #define dprintk(x...) printk("USB: " x) #define DRIVER_DESC "S3C2410 USB Device Controller Gadget" #define DRIVER_VERSION "14 Mar 2004" static const char gadget_name [] = "s3c2410_udc"; /*-------------------------------------------------------------------------*/ struct s3c2410_udc; struct s3c2410_ep { struct list_head queue; unsigned long last_io; /* jiffies timestamp */ struct usb_gadget *gadget; struct s3c2410_udc *dev; const struct usb_endpoint_descriptor *desc; struct usb_ep ep; unsigned short fifo_size; u8 bEndpointAddress; u8 bmAttributes; unsigned halted : 1; unsigned already_seen : 1; unsigned setup_stage : 1; }; struct s3c2410_request { struct list_head queue; /* ep's requests */ struct usb_request req; }; /*-------------------------------------------------------------------------*/ /* * Every device has ep0 for control requests, plus up to 30 more endpoints. * * Gadget drivers are responsible for not setting up conflicting endpoint * configurations, illegal or unsupported packet lengths, and so on. */ static const char ep0name [] = "ep0"; static const char *const ep_name[] = { ep0name, /* everyone has ep0 */ /* s3c2410 four bidirectional bulk endpoints */ "ep1", "ep2", "ep3", "ep4", }; #define S3C2410_ENDPOINTS ARRAY_SIZE(ep_name) #define FIFO_SIZE 64 struct s3c2410_udc { spinlock_t lock; struct s3c2410_ep ep[S3C2410_ENDPOINTS]; int address; struct usb_gadget gadget; struct usb_gadget_driver *driver; struct s3c2410_request fifo_req; u8 fifo_buf[FIFO_SIZE]; u16 devstatus; u32 port_status; int ep0state; unsigned got_irq : 1; unsigned req_std : 1; unsigned req_config : 1; unsigned req_pending : 1; }; static struct s3c2410_udc *the_controller; /*-------------------------------------------------------------------------*/ /* * Gadget Helpers */ static inline struct s3c2410_udc *ep_to_udc (struct s3c2410_ep *ep) { return container_of (ep->gadget, struct s3c2410_udc, gadget); } static inline struct s3c2410_udc *gadget_dev_to_udc (struct device *dev) { return container_of (dev, struct s3c2410_udc, gadget.dev); } /* called with spinlock held */ static void nuke (struct s3c2410_udc *udc, struct s3c2410_ep *ep) { while (!list_empty (&ep->queue)) { struct s3c2410_request *req; req = list_entry (ep->queue.next, struct s3c2410_request, queue); list_del_init (&req->queue); req->req.status = -ESHUTDOWN; spin_unlock (&udc->lock); req->req.complete (&ep->ep, &req->req); spin_lock (&udc->lock); } } static void fifo_complete (struct usb_ep *ep, struct usb_request *req) { dprintk( "fifo_complete: %d\n", req->status); } /*-------------------------------------------------------------------------*/ /* * Gadget Operations */ static int s3c2410_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) { struct s3c2410_udc *udc; struct s3c2410_ep *ep; unsigned max; int retval; printk("s3c2410_enable():\n"); ep = container_of (_ep, struct s3c2410_ep, ep); /* if (!the_controller->driver || !is_enabled ()) return -ESHUTDOWN; */ max = desc->wMaxPacketSize & 0x3ff; /* drivers must not request bad settings, since lower levels * (hardware or its drivers) may not check. some endpoints * can't do iso, many have maxpacket limitations, etc. */ udc = container_of (ep->gadget, struct s3c2410_udc, gadget); retval = -EINVAL; switch (desc->bmAttributes & 0x03) { case USB_ENDPOINT_XFER_BULK: switch (udc->gadget.speed) { case USB_SPEED_HIGH: if (max == 512) break; /* conserve return statements */ default: switch (max) { case 8: case 16: case 32: case 64: /* we'll fake any legal size */ break; default: case USB_SPEED_LOW: goto done; } } break; case USB_ENDPOINT_XFER_INT: switch (udc->gadget.speed) { case USB_SPEED_HIGH: if (max <= 1024) break; /* save a return statement */ case USB_SPEED_FULL: if (max <= 64) break; /* save a return statement */ default: if (max <= 8) break; goto done; } break; case USB_ENDPOINT_XFER_ISOC: /* real hardware might not handle all packet sizes */ switch (udc->gadget.speed) { case USB_SPEED_HIGH: if (max <= 1024) break; /* save a return statement */ case USB_SPEED_FULL: if (max <= 1023) break; /* save a return statement */ default: goto done; } break; default: /* few chips support control except on ep0 */ goto done; } _ep->maxpacket = max; ep->desc = desc; dprintk( "enabled %s (ep%d%s-%s) maxpacket %d\n", _ep->name, desc->bEndpointAddress & 0x0f, (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", ({ char *val; switch (desc->bmAttributes & 0x03) { case USB_ENDPOINT_XFER_BULK: val = "bulk"; break; case USB_ENDPOINT_XFER_ISOC: val = "iso"; break; case USB_ENDPOINT_XFER_INT: val = "intr"; break; default: val = "ctrl"; break; }; val; }), max); /* at this point real hardware should be NAKing transfers * to that endpoint, until a buffer is queued to it. */ retval = 0; done: return retval; } static int s3c2410_disable (struct usb_ep *_ep) { struct s3c2410_ep *ep; struct s3c2410_udc *udc; unsigned long flags; int retval; printk("s3c2410_disable()\n"); ep = container_of (_ep, struct s3c2410_ep, ep); if (!_ep || !ep->desc || _ep->name == ep0name) return -EINVAL; udc = ep_to_udc (ep); spin_lock_irqsave (&udc->lock, flags); ep->desc = 0; retval = 0; nuke (udc, ep); spin_unlock_irqrestore (&udc->lock, flags); dprintk( "disabled %s\n", _ep->name); return retval; } static struct usb_request * s3c2410_alloc_request (struct usb_ep *_ep, int mem_flags) { struct s3c2410_ep *ep; struct s3c2410_request *req; printk("s3c2410_alloc_request(ep=%p,flags=%d)\n", _ep, mem_flags); ep = container_of (_ep, struct s3c2410_ep, ep); if (!_ep) return 0; req = kmalloc (sizeof *req, mem_flags); if (!req) return 0; memset (req, 0, sizeof *req); INIT_LIST_HEAD (&req->queue); return &req->req; } static void s3c2410_free_request (struct usb_ep *_ep, struct usb_request *_req) { struct s3c2410_ep *ep; struct s3c2410_request *req; printk("s3c2410_free_request(ep=%p,req=%p)\n", _ep, _req); ep = container_of (_ep, struct s3c2410_ep, ep); if (!ep || !_req || (!ep->desc && _ep->name != ep0name)) return; req = container_of (_req, struct s3c2410_request, req); WARN_ON (!list_empty (&req->queue)); kfree (req); } static void * s3c2410_alloc_buffer ( struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, int mem_flags) { char *retval; printk("s3c2410_alloc_buffer()\n"); if (!the_controller->driver) return 0; retval = kmalloc (bytes, mem_flags); *dma = (dma_addr_t) retval; return retval; } static void s3c2410_free_buffer ( struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes) { printk("s3c2410_free_buffer()\n"); if (bytes) kfree (buf); } static int s3c2410_queue (struct usb_ep *_ep, struct usb_request *_req, int mem_flags) { struct s3c2410_ep *ep; struct s3c2410_request *req; struct s3c2410_udc *udc; unsigned long flags; printk("s3c2410_queue(ep=%p,req=%p,mem=%d)\n", _ep, _req, mem_flags); req = container_of (_req, struct s3c2410_request, req); if (!_req || !list_empty (&req->queue) || !_req->complete) return -EINVAL; ep = container_of (_ep, struct s3c2410_ep, ep); if (!_ep || (!ep->desc && _ep->name != ep0name)) return -EINVAL; /* if (!the_controller->driver || !is_enabled ()) return -ESHUTDOWN; */ udc = container_of (ep->gadget, struct s3c2410_udc, gadget); dprintk( "ep %p queue req %p to %s, len %d buf %p\n", ep, _req, _ep->name, _req->length, _req->buf); _req->status = -EINPROGRESS; _req->actual = 0; spin_lock_irqsave (&udc->lock, flags); /* implement an emulated single-request FIFO */ if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && list_empty (&udc->fifo_req.queue) && list_empty (&ep->queue) && _req->length <= FIFO_SIZE) { req = &udc->fifo_req; req->req = *_req; req->req.buf = udc->fifo_buf; memcpy (udc->fifo_buf, _req->buf, _req->length); req->req.context = udc; req->req.complete = fifo_complete; spin_unlock (&udc->lock); _req->actual = _req->length; _req->status = 0; _req->complete (_ep, _req); spin_lock (&udc->lock); } list_add_tail (&req->queue, &ep->queue); spin_unlock_irqrestore (&udc->lock, flags); /* real hardware would likely enable transfers here, in case * it'd been left NAKing. */ return 0; } static int s3c2410_dequeue (struct usb_ep *_ep, struct usb_request *_req) { struct s3c2410_ep *ep; struct s3c2410_udc *udc; int retval = -EINVAL; unsigned long flags; struct s3c2410_request *req = 0; printk("s3c2410_dequeue(ep=%p,req=%p)\n", _ep, _req); if (!the_controller->driver) return -ESHUTDOWN; if (!_ep || !_req) return retval; ep = container_of (_ep, struct s3c2410_ep, ep); udc = container_of (ep->gadget, struct s3c2410_udc, gadget); spin_lock_irqsave (&udc->lock, flags); list_for_each_entry (req, &ep->queue, queue) { if (&req->req == _req) { list_del_init (&req->queue); _req->status = -ECONNRESET; retval = 0; break; } } spin_unlock_irqrestore (&udc->lock, flags); if (retval == 0) { dprintk( "dequeued req %p from %s, len %d buf %p\n", req, _ep->name, _req->length, _req->buf); _req->complete (_ep, _req); } return retval; } static int s3c2410_set_halt (struct usb_ep *_ep, int value) { struct s3c2410_ep *ep; printk("s3c2410_set_halt(ep=%p)\n", _ep); if (!_ep) return -EINVAL; if (!the_controller->driver) return -ESHUTDOWN; ep = container_of (_ep, struct s3c2410_ep, ep); if (!value) ep->halted = 0; else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && !list_empty (&ep->queue)) return -EAGAIN; else ep->halted = 1; /* FIXME clear emulated data toggle too */ return 0; } static const struct usb_ep_ops s3c2410_ep_ops = { .enable = s3c2410_enable, .disable = s3c2410_disable, .alloc_request = s3c2410_alloc_request, .free_request = s3c2410_free_request, .alloc_buffer = s3c2410_alloc_buffer, .free_buffer = s3c2410_free_buffer, /* map, unmap, ... eventually hook the "generic" dma calls */ .queue = s3c2410_queue, .dequeue = s3c2410_dequeue, .set_halt = s3c2410_set_halt, }; /*-------------------------------------------------------------------------*/ /* there are both host and device side versions of this call ... */ static int s3c2410_g_get_frame (struct usb_gadget *_gadget) { struct timeval tv; printk("s3c2410_g_get_frame()\n"); do_gettimeofday (&tv); return tv.tv_usec / 1000; } static int s3c2410_wakeup (struct usb_gadget *_gadget) { struct s3c2410_udc *udc; printk("s3c2410_wakeup()\n"); udc = container_of (_gadget, struct s3c2410_udc, gadget); // udc->port_status |= (1 << USB_PORT_FEAT_C_SUSPEND); return 0; } static int s3c2410_set_selfpowered (struct usb_gadget *_gadget, int value) { struct s3c2410_udc *udc; printk("s3c2410_set_selfpowered()\n"); udc = container_of (_gadget, struct s3c2410_udc, gadget); if (value) udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); else udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); return 0; } static void nop_release (struct device *dev) { dprintk("%s %s\n", __FUNCTION__, dev->bus_id); } static const struct usb_gadget_ops s3c2410_ops = { .get_frame = s3c2410_g_get_frame, .wakeup = s3c2410_wakeup, .set_selfpowered = s3c2410_set_selfpowered, }; /*-------------------------------------------------------------------------*/ /* "function" sysfs attribute */ static ssize_t show_function (struct device *_dev, char *buf) { struct s3c2410_udc *udc = the_controller; printk("s3c2410_show_function(dev=%p)\n", _dev); if (!udc->driver->function || strlen (udc->driver->function) > PAGE_SIZE) return 0; return snprintf (buf, PAGE_SIZE, "%s\n", udc->driver->function); } DEVICE_ATTR (function, S_IRUGO, show_function, NULL); /*-------------------------------------------------------------------------*/ /* * Driver registration/unregistration. * * This is basically hardware-specific; there's usually only one real USB * device (not host) controller since that's how USB devices are intended * to work. So most implementations of these api calls will rely on the * fact that only one driver will ever bind to the hardware. But curious * hardware can be built with discrete components, so the gadget API doesn't * require that assumption. */ /* caller must hold lock */ static void stop_activity (struct s3c2410_udc *udc, struct usb_gadget_driver *driver) { struct s3c2410_ep *ep; printk("stop_activity()\n"); /* prevent any more requests */ udc->address = 0; /* The timer is left running so that outstanding URBs can fail */ /* nuke any pending requests first, so driver i/o is quiesced */ list_for_each_entry (ep, &udc->gadget.ep_list, ep.ep_list) nuke (udc, ep); /* driver now does any non-usb quiescing necessary */ if (driver) { spin_unlock (&udc->lock); driver->disconnect (&udc->gadget); spin_lock (&udc->lock); } } int usb_gadget_register_driver (struct usb_gadget_driver *driver) { struct s3c2410_udc *udc = the_controller; int retval, i; printk("usb_gadget_register_driver() '%s'\n", driver->driver.name); if (!udc) return -EINVAL; if (udc->driver) return -EBUSY; if (!driver->bind || !driver->unbind || !driver->setup || driver->speed == USB_SPEED_UNKNOWN) return -EINVAL; udc->gadget.name = gadget_name; udc->gadget.ops = &s3c2410_ops; udc->gadget.is_dualspeed = 1; udc->gadget.dev.release = nop_release; udc->devstatus = 0; INIT_LIST_HEAD (&udc->gadget.ep_list); for (i = 0; i < S3C2410_ENDPOINTS; i++) { struct s3c2410_ep *ep = &udc->ep[i]; if (!ep_name[i]) break; ep->ep.name = ep_name[i]; ep->ep.ops = &s3c2410_ep_ops; list_add_tail (&ep->ep.ep_list, &udc->gadget.ep_list); ep->halted = ep->already_seen = ep->setup_stage = 0; ep->ep.maxpacket = ~0; ep->last_io = jiffies; ep->gadget = &udc->gadget; ep->dev = udc; ep->desc = 0; INIT_LIST_HEAD (&ep->queue); } udc->gadget.ep0 = &udc->ep[0].ep; udc->ep[0].ep.maxpacket = 16; list_del_init (&udc->ep[0].ep.ep_list); INIT_LIST_HEAD(&udc->fifo_req.queue); udc->driver = driver; udc->gadget.dev.driver = &driver->driver; dprintk( "binding gadget driver '%s'\n", driver->driver.name); if ((retval = driver->bind (&udc->gadget)) != 0) { udc->driver = 0; udc->gadget.dev.driver = 0; return retval; } driver->driver.bus = 0; udc->driver = driver; udc->gadget.dev.driver = &driver->driver; /* driver_register (&driver->driver); device_bind_driver (&udc->gadget.dev); */ retval = driver->bind(&udc->gadget); if (retval) { dprintk("bind to driver %s --> error %d\n", driver->driver.name, retval); udc->driver = 0; udc->gadget.dev.driver = 0; return retval; } #if 0 /* khubd will enumerate this in a while */ udc->port_status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION); #endif return 0; } EXPORT_SYMBOL (usb_gadget_register_driver); int usb_gadget_unregister_driver (struct usb_gadget_driver *driver) { struct s3c2410_udc *udc = the_controller; unsigned long flags; if (!udc) return -ENODEV; if (!driver || driver != udc->driver) return -EINVAL; printk("usb_gadget_register_driver() '%s'\n", driver->driver.name); spin_lock_irqsave (&udc->lock, flags); stop_activity (udc, driver); /* udc->port_status &= ~USB_PORT_STAT_CONNECTION; udc->port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); */ spin_unlock_irqrestore (&udc->lock, flags); driver->unbind (&udc->gadget); udc->driver = 0; device_release_driver (&udc->gadget.dev); driver_unregister (&driver->driver); return 0; } EXPORT_SYMBOL (usb_gadget_unregister_driver); #undef is_enabled int s3c2410_set_fifo_mode (struct usb_gadget *gadget, int mode) { return -ENOSYS; } EXPORT_SYMBOL (s3c2410_set_fifo_mode); /*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ #define EP0_FIFO_SIZE 16 #define BULK_FIFO_SIZE 64 static struct s3c2410_udc memory; static inline void clear_ep_state (struct s3c2410_udc *dev) { unsigned i; /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint * fifos, and pending transactions mustn't be continued in any case. */ for (i = 1; i < 5; i++) nuke(dev, &dev->ep[i]); } enum ep0_state { EP0_IDLE, EP0_IN_DATA_PHASE, EP0_OUT_DATA_PHASE, EP0_END_XFER, EP0_STALL, }; static inline void ep0_idle (struct s3c2410_udc *dev) { dev->ep0state = EP0_IDLE; } static inline int windex_to_ep_num( __u16 w ) { return (int) ( w & 0x000F); } static inline void udc_change_index(int index, int *backup) { if (backup) *backup = __raw_readl(S3C2410_UDC_INDEX_REG); if ((index >= 0) && (index <= 5)) __raw_writel(index, S3C2410_UDC_INDEX_REG); } static inline int fifo_count_out(void) { int tmp; tmp = __raw_readl(S3C2410_UDC_OUT_FIFO_CNT2_REG) << 8; tmp |= __raw_readl(S3C2410_UDC_OUT_FIFO_CNT1_REG); return tmp & 0xffff; } static inline void out_pkt_ack(int last) { __raw_writel(S3C2410_UDC_EP0_CSR_SOPKTRDY | (last)?S3C2410_UDC_EP0_CSR_DE:0, S3C2410_UDC_EP0_CSR_REG); } /* * read_fifo_crq() * Read 1-8 bytes out of EP0 FIFO and put in request. * Called to do the initial read of setup requests * from the host. Return number of bytes read. * * Like write fifo above, this driver uses multiple * reads checked agains the count register with an * overall timeout. * */ static int read_fifo_crq(struct usb_ctrlrequest *crq) { int bytes_read = 0; int fifo_count = 0; int i; // , ep; unsigned char *pOut = (unsigned char*)crq; udc_change_index(0, NULL); fifo_count = fifo_count_out(); BUG_ON( fifo_count > 8 ); dprintk("read_fifo_crq(): fifo_count=%d\n", fifo_count ); while( fifo_count-- ) { i = 0; do { *pOut = (unsigned char) __raw_readl(S3C2410_UDC_EP0_FIFO_REG); udelay( 10 ); i++; } while((fifo_count_out() != fifo_count) && (i < 10)); if ( i == 10 ) { printk("read_fifo(): read failure\n"); // usbd_info.stats.ep0_fifo_read_failures++; } pOut++; bytes_read++; } dprintk("read_fifo_crq(): len=%d %02x:%02x {%x,%x,%x}\n", bytes_read, crq->bRequest, crq->bRequestType, crq->wValue, crq->wIndex, crq->wLength); // usbd_info.stats.ep0_bytes_read++; return bytes_read; } static void handle_ep0(struct s3c2410_udc *dev) { u32 ep0csr; struct s3c2410_ep *ep = &dev->ep [0]; struct s3c2410_request *req; struct usb_ctrlrequest crq; if (list_empty(&ep->queue)) req = 0; else req = list_entry(ep->queue.next, struct s3c2410_request, queue); S3C2410_UDC_SETIX(EP0); ep0csr = __raw_readl(S3C2410_UDC_IN_CSR1_REG); /* clear stall status */ if (ep0csr & S3C2410_UDC_EP0_CSR_SENTSTL) { nuke(dev, ep); clear_ep0_sst; /* del_timer(&dev->timer);*/ ep0_idle(dev); } if (ep0csr & S3C2410_UDC_EP0_CSR_SE && dev->ep0state != EP0_IDLE) { clear_ep0_se; /* del_timer(&dev->timer);*/ ep0_idle(dev); } switch (dev->ep0state) { case EP0_IDLE: /* start control request? */ if (ep0csr & S3C2410_UDC_EP0_CSR_OPKRDY) { int len, ret, tmp; len = read_fifo_crq(&crq); if (len != sizeof(crq)) { dprintk("setup begin: fifo READ ERROR" " wanted %d bytes got %d. Stalling out...\n", sizeof(crq), len); set_ep0_ss; return; } /* nuke (ep, -EPROTO);*/ /* cope with automagic for some standard requests. */ dev->req_std = (crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD; dev->req_config = 0; dev->req_pending = 1; switch (crq.bRequest) { /* hardware restricts gadget drivers here! */ case USB_REQ_SET_CONFIGURATION: dprintk("USB_REQ_SET_CONFIGURATION ... \n"); if (crq.bRequestType == USB_RECIP_DEVICE) { /* reflect hardware's automagic * up to the gadget driver. */ config_change: dev->req_config = 1; clear_ep_state(dev); /* if !has_cfr, there's no synch * else use AREN (later) not SA|OPR * USIR0_IR0 acts edge sensitive */ } break; /* ... and here, even more ... */ case USB_REQ_SET_INTERFACE: dprintk("USB_REQ_SET_INTERFACE ... \n"); if (crq.bRequestType == USB_RECIP_INTERFACE) { /* udc hardware is broken by design: * - altsetting may only be zero; * - hw resets all interfaces' eps; * - ep reset doesn't include halt(?). */ goto config_change; } break; /* hardware was supposed to hide this */ case USB_REQ_SET_ADDRESS: dprintk("USB_REQ_SET_ADDRESS ... \n"); tmp = crq.wValue & 0x7F; __raw_writel((tmp | 0x80), S3C2410_UDC_FUNC_ADDR_REG); // usbd_info.address = address; // usbctl_next_state_on_event( kEvAddress ); out_pkt_ack(1); if (crq.bRequestType == USB_RECIP_DEVICE) { // ep0start(dev, 0, "address"); return; } break; } if (crq.bRequestType & USB_DIR_IN) dev->ep0state = EP0_IN_DATA_PHASE; else dev->ep0state = EP0_OUT_DATA_PHASE; ret = dev->driver->setup(&dev->gadget, &crq); if (ret < 0) { /* hardware automagic preventing STALL... */ if (dev->req_config) { /* hardware sometimes neglects to tell * tell us about config change events, * so later ones may fail... */ dprintk("config change %02x fail %d?\n", crq.bRequest, ret); return; /* TODO experiment: if has_cfr, * hardware didn't ACK; maybe we * could actually STALL! */ } // stall: /* the watchdog timer helps deal with cases * where udc seems to clear FST wrongly, and * then NAKs instead of STALLing. */ // ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall"); // start_watchdog(dev); dev->ep0state = EP0_STALL; /* deferred i/o == no response yet */ } else if (dev->req_pending) { dprintk("dev->req_pending... what now?\n"); #if 0 if (likely(dev->ep0state == EP0_IN_DATA_PHASE || dev->req_std || crq.wLength)) ep0start(dev, 0, "defer"); else ep0start(dev, UDCCS0_IPR, "defer/IPR"); #endif } /* expect at least one data or status stage irq */ return; } break; case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ dprintk("EP0_IN_DATA_PHASE ... what now?\n"); #if 0 if (udccs0 & UDCCS0_OPR) { UDCCS0 = UDCCS0_OPR|UDCCS0_FTF; if (req) done(ep, req, 0); ep0_idle(dev); } else /* irq was IPR clearing */ { if (req) { /* this IN packet might finish the request */ (void) write_ep0_fifo(ep, req); } /* else IN token before response was written */ } #endif break; case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ dprintk("EP0_OUT_DATA_PHASE ... what now?\n"); #if 0 if (udccs0 & UDCCS0_OPR) { if (req) { /* this OUT packet might finish the request */ if (read_ep0_fifo(ep, req)) done(ep, req, 0); /* else more OUT packets expected */ } /* else OUT token before read was issued */ } else /* irq was IPR clearing */ { if (req) done(ep, req, 0); ep0_idle(dev); } #endif break; case EP0_END_XFER: dprintk("EP0_END_XFER ... what now?\n"); #if 0 if (req) done(ep, req, 0); /* ack control-IN status (maybe in-zlp was skipped) * also appears after some config change events. */ if (udccs0 & UDCCS0_OPR) UDCCS0 = UDCCS0_OPR; ep0_idle(dev); #endif break; case EP0_STALL: set_ep0_ss; break; } // USIR0 = USIR0_IR0; } #if 0 static void handle_ep(struct s3c2410_ep *ep) { struct s3c2410_request *req; int is_in = ep->bEndpointAddress & USB_DIR_IN; int completed; u32 udccs, tmp; do { completed = 0; if (likely (!list_empty(&ep->queue))) req = list_entry(ep->queue.next, struct s3c2410_request, queue); else req = 0; // TODO check FST handling udccs = *ep->reg_udccs; if (unlikely(is_in)) { /* irq from TPC, SST, or (ISO) TUR */ tmp = UDCCS_BI_TUR; if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) tmp |= UDCCS_BI_SST; tmp &= udccs; if (likely (tmp)) *ep->reg_udccs = tmp; if (req && likely ((udccs & UDCCS_BI_TFS) != 0)) completed = write_fifo(ep, req); } else { /* irq from RPC (or for ISO, ROF) */ if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) tmp = UDCCS_BO_SST | UDCCS_BO_DME; else tmp = UDCCS_IO_ROF | UDCCS_IO_DME; tmp &= udccs; if (likely(tmp)) *ep->reg_udccs = tmp; /* fifos can hold packets, ready for reading... */ if (likely(req)) { completed = read_fifo(ep, req); } else pio_irq_disable (ep->bEndpointAddress); } ep->pio_irqs++; } while (completed); } #endif /* * s3c2410_udc_irq - interrupt handler * * avoid delays in ep0 processing. the control handshaking isn't always * under software control, and delays could cause usb protocol errors. */ static irqreturn_t s3c2410_udc_irq(int irq, void *_dev, struct pt_regs *r) { struct s3c2410_udc *dev = _dev; // static int sb_debug_cnt = 1; int handled; dprintk("irq: %d ...\n", irq); do { // int saveIdx = __raw_readl(S3C2410_UDC_INDEX_REG); int usb_status = __raw_readl(S3C2410_UDC_USB_INT_REG); int usbd_status = __raw_readl(S3C2410_UDC_EP_INT_REG); handled = 0; dprintk("check usbs=0x%02x, usbds=0x%02x\n", usb_status, usbd_status); /* ReSeT Interrupt Request - USB reset */ if (usb_status & S3C2410_UDC_USBINT_RESET) { dprintk("USB reset\n"); // if( usbctl_next_state_on_event(kEvReset) != kError ) /* clear interrupt */ __raw_writel(S3C2410_UDC_USBINT_RESET, S3C2410_UDC_USB_INT_REG); dev->gadget.speed = USB_SPEED_FULL; // memset(&dev->stats, 0, sizeof dev->stats); /* driver and endpoints are still reset */ // enable_disconnect_irq(); handled = 1; } /* RESume Interrupt Request */ if (usb_status & S3C2410_UDC_USBINT_RESUME) { dprintk("USB resume\n"); /* clear interrupt */ __raw_writel(S3C2410_UDC_USBINT_RESUME, S3C2410_UDC_USB_INT_REG); if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver && dev->driver->resume /* && is_usb_connected() */) dev->driver->resume(&dev->gadget); handled = 1; } /* SUSpend Interrupt Request */ if (usb_status & S3C2410_UDC_USBINT_SUSPEND) { dprintk("USB suspend\n"); /* clear interrupt */ __raw_writel(S3C2410_UDC_USBINT_SUSPEND, S3C2410_UDC_USB_INT_REG); // if (!is_usb_connected()) stop_activity(dev, dev->driver); /* else */ if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver && dev->driver->suspend) dev->driver->suspend(&dev->gadget); ep0_idle (dev); handled = 1; } /* EP interrupts */ if (usbd_status) { int i; /* control traffic */ if (usbd_status & S3C2410_UDC_INT_EP0) { // dev->ep[0].pio_irqs++; dprintk("USB ep0 irq\n"); handle_ep0(dev); // ep0_int_hndlr(); /* Clear the interrupt bit by setting it to 1 */ __raw_writel(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_REG); handled = 1; } /* endpoint data transfers */ for (i = 1; i < 5; i++) { u32 tmp = 1 << i; if (usbd_status & tmp) { dprintk("USB ep%d irq\n", i); // handle_ep(&dev->ep[i]); /* Clear the interrupt bit by setting it to 1 */ __raw_writel(tmp, S3C2410_UDC_EP_INT_REG); handled = 1; } } } // UD_INDEX= saveIdx; /* restore idx */ } while (handled); dprintk("irq: %d done.\n\n", irq); return IRQ_HANDLED; } /* * probe - binds to the platform device */ static int __init s3c2410_udc_probe(struct device *_dev) { struct s3c2410_udc *udc = &memory; int retval; // , out_dma = 0; dprintk("s3c2410_udc_probe\n"); /* other non-static parts of init */ // udc->dev = _dev; // udc->mach = _dev->platform_data; /* init_timer(&dev->timer); dev->timer.function = udc_watchdog; dev->timer.data = (unsigned long) dev; */ device_initialize(&udc->gadget.dev); udc->gadget.dev.parent = _dev; udc->gadget.dev.dma_mask = _dev->dma_mask; the_controller = udc; dev_set_drvdata(_dev, udc); // udc_disable(udc); // udc_reinit(udc); /* irq setup after old hardware state is cleaned up */ retval = request_irq(IRQ_USBD, s3c2410_udc_irq, SA_INTERRUPT, gadget_name, udc); if (retval != 0) { printk(KERN_ERR "%s: can't get irq %i, err %d\n", gadget_name, IRQ_USBD, retval); return -EBUSY; } dprintk("%s: got irq %i\n", gadget_name, IRQ_USBD); udc->got_irq = 1; // create_proc_files(); return 0; } static int __exit s3c2410_udc_remove(struct device *_dev) { struct s3c2410_udc *udc = _dev->driver_data; dprintk("s3c2410_udc_remove\n"); // udc_disable(udc); // remove_proc_files(); usb_gadget_unregister_driver(udc->driver); if (udc->got_irq) { free_irq(IRQ_USBD, udc); udc->got_irq = 0; } dev_set_drvdata(_dev, 0); the_controller = 0; return 0; } /*-------------------------------------------------------------------------*/ static struct device_driver udc_driver = { .name = "s3c2410-udc", .bus = &platform_bus_type, .probe = s3c2410_udc_probe, .remove = __exit_p(s3c2410_udc_remove), // FIXME power management support // .suspend = ... disable UDC // .resume = ... re-enable UDC }; static int __init udc_init(void) { int tmp; dprintk("%s: version %s\n", gadget_name, DRIVER_VERSION); tmp = __raw_readl(S3C2410_CLKCON); tmp &= ~S3C2410_CLKCON_USBD; __raw_writel(tmp, S3C2410_CLKCON); tmp = __raw_readl(S3C2410_MISCCR); tmp &= ~S3C2410_MISCCR_USBHOST; __raw_writel(tmp, S3C2410_MISCCR); /* UPLLCON */ tmp = (0x78 << S3C2410_PLLCON_MDIVSHIFT) | (0x02 << S3C2410_PLLCON_PDIVSHIFT) | (0x03 << S3C2410_PLLCON_SDIVSHIFT); __raw_writel(tmp, S3C2410_UPLLCON); tmp = __raw_readl(S3C2410_CLKCON); tmp |= S3C2410_CLKCON_USBD; __raw_writel(tmp, S3C2410_CLKCON); mdelay(10); return driver_register(&udc_driver); } module_init(udc_init); static void __exit udc_exit(void) { driver_unregister(&udc_driver); } module_exit(udc_exit); MODULE_DESCRIPTION (DRIVER_DESC); MODULE_AUTHOR ("Herbert Pötzl"); MODULE_LICENSE ("GPL");