| /* |
| * BRIEF MODULE DESCRIPTION |
| * Au1000 USB Device-Side (device layer) |
| * |
| * Copyright 2001-2002 MontaVista Software Inc. |
| * Author: MontaVista Software, Inc. |
| * stevel@mvista.com or source@mvista.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; either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * 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., |
| * 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| #include <linux/kernel.h> |
| #include <linux/ioport.h> |
| #include <linux/sched.h> |
| #include <linux/signal.h> |
| #include <linux/errno.h> |
| #include <linux/poll.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/fcntl.h> |
| #include <linux/module.h> |
| #include <linux/spinlock.h> |
| #include <linux/list.h> |
| #include <linux/smp_lock.h> |
| #define DEBUG |
| #include <linux/usb.h> |
| |
| #include <asm/io.h> |
| #include <asm/uaccess.h> |
| #include <asm/irq.h> |
| #include <asm/mipsregs.h> |
| #include <asm/au1000.h> |
| #include <asm/au1000_dma.h> |
| #include <asm/au1000_usbdev.h> |
| |
| #ifdef DEBUG |
| #undef VDEBUG |
| #ifdef VDEBUG |
| #define vdbg(fmt, arg...) printk(KERN_DEBUG __FILE__ ": " fmt "\n" , ## arg) |
| #else |
| #define vdbg(fmt, arg...) do {} while (0) |
| #endif |
| #else |
| #define vdbg(fmt, arg...) do {} while (0) |
| #endif |
| |
| #define ALLOC_FLAGS (in_interrupt () ? GFP_ATOMIC : GFP_KERNEL) |
| |
| #define EP_FIFO_DEPTH 8 |
| |
| typedef enum { |
| SETUP_STAGE = 0, |
| DATA_STAGE, |
| STATUS_STAGE |
| } ep0_stage_t; |
| |
| typedef struct { |
| int read_fifo; |
| int write_fifo; |
| int ctrl_stat; |
| int read_fifo_status; |
| int write_fifo_status; |
| } endpoint_reg_t; |
| |
| typedef struct { |
| usbdev_pkt_t *head; |
| usbdev_pkt_t *tail; |
| int count; |
| } pkt_list_t; |
| |
| typedef struct { |
| int active; |
| struct usb_endpoint_descriptor *desc; |
| endpoint_reg_t *reg; |
| /* Only one of these are used, unless this is the control ep */ |
| pkt_list_t inlist; |
| pkt_list_t outlist; |
| unsigned int indma, outdma; /* DMA channel numbers for IN, OUT */ |
| /* following are extracted from endpoint descriptor for easy access */ |
| int max_pkt_size; |
| int type; |
| int direction; |
| /* WE assign endpoint addresses! */ |
| int address; |
| spinlock_t lock; |
| } endpoint_t; |
| |
| |
| static struct usb_dev { |
| endpoint_t ep[6]; |
| ep0_stage_t ep0_stage; |
| |
| struct usb_device_descriptor * dev_desc; |
| struct usb_interface_descriptor* if_desc; |
| struct usb_config_descriptor * conf_desc; |
| u8 * full_conf_desc; |
| struct usb_string_descriptor * str_desc[6]; |
| |
| /* callback to function layer */ |
| void (*func_cb)(usbdev_cb_type_t type, unsigned long arg, |
| void *cb_data); |
| void* cb_data; |
| |
| usbdev_state_t state; // device state |
| int suspended; // suspended flag |
| int address; // device address |
| int interface; |
| int num_ep; |
| u8 alternate_setting; |
| u8 configuration; // configuration value |
| int remote_wakeup_en; |
| } usbdev; |
| |
| |
| static endpoint_reg_t ep_reg[] = { |
| // FIFO's 0 and 1 are EP0 default control |
| {USBD_EP0RD, USBD_EP0WR, USBD_EP0CS, USBD_EP0RDSTAT, USBD_EP0WRSTAT }, |
| {0}, |
| // FIFO 2 is EP2, IN |
| { -1, USBD_EP2WR, USBD_EP2CS, -1, USBD_EP2WRSTAT }, |
| // FIFO 3 is EP3, IN |
| { -1, USBD_EP3WR, USBD_EP3CS, -1, USBD_EP3WRSTAT }, |
| // FIFO 4 is EP4, OUT |
| {USBD_EP4RD, -1, USBD_EP4CS, USBD_EP4RDSTAT, -1 }, |
| // FIFO 5 is EP5, OUT |
| {USBD_EP5RD, -1, USBD_EP5CS, USBD_EP5RDSTAT, -1 } |
| }; |
| |
| static struct { |
| unsigned int id; |
| const char *str; |
| } ep_dma_id[] = { |
| { DMA_ID_USBDEV_EP0_TX, "USBDev EP0 IN" }, |
| { DMA_ID_USBDEV_EP0_RX, "USBDev EP0 OUT" }, |
| { DMA_ID_USBDEV_EP2_TX, "USBDev EP2 IN" }, |
| { DMA_ID_USBDEV_EP3_TX, "USBDev EP3 IN" }, |
| { DMA_ID_USBDEV_EP4_RX, "USBDev EP4 OUT" }, |
| { DMA_ID_USBDEV_EP5_RX, "USBDev EP5 OUT" } |
| }; |
| |
| #define DIR_OUT 0 |
| #define DIR_IN (1<<3) |
| |
| #define CONTROL_EP USB_ENDPOINT_XFER_CONTROL |
| #define BULK_EP USB_ENDPOINT_XFER_BULK |
| |
| static inline endpoint_t * |
| epaddr_to_ep(struct usb_dev* dev, int ep_addr) |
| { |
| if (ep_addr >= 0 && ep_addr < 2) |
| return &dev->ep[0]; |
| if (ep_addr < 6) |
| return &dev->ep[ep_addr]; |
| return NULL; |
| } |
| |
| static const char* std_req_name[] = { |
| "GET_STATUS", |
| "CLEAR_FEATURE", |
| "RESERVED", |
| "SET_FEATURE", |
| "RESERVED", |
| "SET_ADDRESS", |
| "GET_DESCRIPTOR", |
| "SET_DESCRIPTOR", |
| "GET_CONFIGURATION", |
| "SET_CONFIGURATION", |
| "GET_INTERFACE", |
| "SET_INTERFACE", |
| "SYNCH_FRAME" |
| }; |
| |
| static inline const char* |
| get_std_req_name(int req) |
| { |
| return (req >= 0 && req <= 12) ? std_req_name[req] : "UNKNOWN"; |
| } |
| |
| #if 0 |
| static void |
| dump_setup(struct usb_ctrlrequest* s) |
| { |
| dbg("%s: requesttype=%d", __FUNCTION__, s->requesttype); |
| dbg("%s: request=%d %s", __FUNCTION__, s->request, |
| get_std_req_name(s->request)); |
| dbg("%s: value=0x%04x", __FUNCTION__, s->wValue); |
| dbg("%s: index=%d", __FUNCTION__, s->index); |
| dbg("%s: length=%d", __FUNCTION__, s->length); |
| } |
| #endif |
| |
| static inline usbdev_pkt_t * |
| alloc_packet(endpoint_t * ep, int data_size, void* data) |
| { |
| usbdev_pkt_t* pkt = kmalloc(sizeof(usbdev_pkt_t) + data_size, |
| ALLOC_FLAGS); |
| if (!pkt) |
| return NULL; |
| pkt->ep_addr = ep->address; |
| pkt->size = data_size; |
| pkt->status = 0; |
| pkt->next = NULL; |
| if (data) |
| memcpy(pkt->payload, data, data_size); |
| |
| return pkt; |
| } |
| |
| |
| /* |
| * Link a packet to the tail of the enpoint's packet list. |
| * EP spinlock must be held when calling. |
| */ |
| static void |
| link_tail(endpoint_t * ep, pkt_list_t * list, usbdev_pkt_t * pkt) |
| { |
| if (!list->tail) { |
| list->head = list->tail = pkt; |
| list->count = 1; |
| } else { |
| list->tail->next = pkt; |
| list->tail = pkt; |
| list->count++; |
| } |
| } |
| |
| /* |
| * Unlink and return a packet from the head of the given packet |
| * list. It is the responsibility of the caller to free the packet. |
| * EP spinlock must be held when calling. |
| */ |
| static usbdev_pkt_t * |
| unlink_head(pkt_list_t * list) |
| { |
| usbdev_pkt_t *pkt; |
| |
| pkt = list->head; |
| if (!pkt || !list->count) { |
| return NULL; |
| } |
| |
| list->head = pkt->next; |
| if (!list->head) { |
| list->head = list->tail = NULL; |
| list->count = 0; |
| } else |
| list->count--; |
| |
| return pkt; |
| } |
| |
| /* |
| * Create and attach a new packet to the tail of the enpoint's |
| * packet list. EP spinlock must be held when calling. |
| */ |
| static usbdev_pkt_t * |
| add_packet(endpoint_t * ep, pkt_list_t * list, int size) |
| { |
| usbdev_pkt_t *pkt = alloc_packet(ep, size, NULL); |
| if (!pkt) |
| return NULL; |
| |
| link_tail(ep, list, pkt); |
| return pkt; |
| } |
| |
| |
| /* |
| * Unlink and free a packet from the head of the enpoint's |
| * packet list. EP spinlock must be held when calling. |
| */ |
| static inline void |
| free_packet(pkt_list_t * list) |
| { |
| kfree(unlink_head(list)); |
| } |
| |
| /* EP spinlock must be held when calling. */ |
| static inline void |
| flush_pkt_list(pkt_list_t * list) |
| { |
| while (list->count) |
| free_packet(list); |
| } |
| |
| /* EP spinlock must be held when calling */ |
| static inline void |
| flush_write_fifo(endpoint_t * ep) |
| { |
| if (ep->reg->write_fifo_status >= 0) { |
| au_writel(USBDEV_FSTAT_FLUSH | USBDEV_FSTAT_UF | |
| USBDEV_FSTAT_OF, |
| ep->reg->write_fifo_status); |
| //udelay(100); |
| //au_writel(USBDEV_FSTAT_UF | USBDEV_FSTAT_OF, |
| // ep->reg->write_fifo_status); |
| } |
| } |
| |
| /* EP spinlock must be held when calling */ |
| static inline void |
| flush_read_fifo(endpoint_t * ep) |
| { |
| if (ep->reg->read_fifo_status >= 0) { |
| au_writel(USBDEV_FSTAT_FLUSH | USBDEV_FSTAT_UF | |
| USBDEV_FSTAT_OF, |
| ep->reg->read_fifo_status); |
| //udelay(100); |
| //au_writel(USBDEV_FSTAT_UF | USBDEV_FSTAT_OF, |
| // ep->reg->read_fifo_status); |
| } |
| } |
| |
| |
| /* EP spinlock must be held when calling. */ |
| static void |
| endpoint_flush(endpoint_t * ep) |
| { |
| // First, flush all packets |
| flush_pkt_list(&ep->inlist); |
| flush_pkt_list(&ep->outlist); |
| |
| // Now flush the endpoint's h/w FIFO(s) |
| flush_write_fifo(ep); |
| flush_read_fifo(ep); |
| } |
| |
| /* EP spinlock must be held when calling. */ |
| static void |
| endpoint_stall(endpoint_t * ep) |
| { |
| u32 cs; |
| |
| warn(__FUNCTION__); |
| |
| cs = au_readl(ep->reg->ctrl_stat) | USBDEV_CS_STALL; |
| au_writel(cs, ep->reg->ctrl_stat); |
| } |
| |
| /* EP spinlock must be held when calling. */ |
| static void |
| endpoint_unstall(endpoint_t * ep) |
| { |
| u32 cs; |
| |
| warn(__FUNCTION__); |
| |
| cs = au_readl(ep->reg->ctrl_stat) & ~USBDEV_CS_STALL; |
| au_writel(cs, ep->reg->ctrl_stat); |
| } |
| |
| static void |
| endpoint_reset_datatoggle(endpoint_t * ep) |
| { |
| // FIXME: is this possible? |
| } |
| |
| |
| /* EP spinlock must be held when calling. */ |
| static int |
| endpoint_fifo_read(endpoint_t * ep) |
| { |
| int read_count = 0; |
| u8 *bufptr; |
| usbdev_pkt_t *pkt = ep->outlist.tail; |
| |
| if (!pkt) |
| return -EINVAL; |
| |
| bufptr = &pkt->payload[pkt->size]; |
| while (au_readl(ep->reg->read_fifo_status) & USBDEV_FSTAT_FCNT_MASK) { |
| *bufptr++ = au_readl(ep->reg->read_fifo) & 0xff; |
| read_count++; |
| pkt->size++; |
| } |
| |
| return read_count; |
| } |
| |
| #if 0 |
| /* EP spinlock must be held when calling. */ |
| static int |
| endpoint_fifo_write(endpoint_t * ep, int index) |
| { |
| int write_count = 0; |
| u8 *bufptr; |
| usbdev_pkt_t *pkt = ep->inlist.head; |
| |
| if (!pkt) |
| return -EINVAL; |
| |
| bufptr = &pkt->payload[index]; |
| while ((au_readl(ep->reg->write_fifo_status) & |
| USBDEV_FSTAT_FCNT_MASK) < EP_FIFO_DEPTH) { |
| if (bufptr < pkt->payload + pkt->size) { |
| au_writel(*bufptr++, ep->reg->write_fifo); |
| write_count++; |
| } else { |
| break; |
| } |
| } |
| |
| return write_count; |
| } |
| #endif |
| |
| /* |
| * This routine is called to restart transmission of a packet. |
| * The endpoint's TSIZE must be set to the new packet's size, |
| * and DMA to the write FIFO needs to be restarted. |
| * EP spinlock must be held when calling. |
| */ |
| static void |
| kickstart_send_packet(endpoint_t * ep) |
| { |
| u32 cs; |
| usbdev_pkt_t *pkt = ep->inlist.head; |
| |
| vdbg("%s: ep%d, pkt=%p", __FUNCTION__, ep->address, pkt); |
| |
| if (!pkt) { |
| err("%s: head=NULL! list->count=%d", __FUNCTION__, |
| ep->inlist.count); |
| return; |
| } |
| |
| dma_cache_wback_inv((unsigned long)pkt->payload, pkt->size); |
| |
| /* |
| * make sure FIFO is empty |
| */ |
| flush_write_fifo(ep); |
| |
| cs = au_readl(ep->reg->ctrl_stat) & USBDEV_CS_STALL; |
| cs |= (pkt->size << USBDEV_CS_TSIZE_BIT); |
| au_writel(cs, ep->reg->ctrl_stat); |
| |
| if (get_dma_active_buffer(ep->indma) == 1) { |
| set_dma_count1(ep->indma, pkt->size); |
| set_dma_addr1(ep->indma, virt_to_phys(pkt->payload)); |
| enable_dma_buffer1(ep->indma); // reenable |
| } else { |
| set_dma_count0(ep->indma, pkt->size); |
| set_dma_addr0(ep->indma, virt_to_phys(pkt->payload)); |
| enable_dma_buffer0(ep->indma); // reenable |
| } |
| if (dma_halted(ep->indma)) |
| start_dma(ep->indma); |
| } |
| |
| |
| /* |
| * This routine is called when a packet in the inlist has been |
| * completed. Frees the completed packet and starts sending the |
| * next. EP spinlock must be held when calling. |
| */ |
| static usbdev_pkt_t * |
| send_packet_complete(endpoint_t * ep) |
| { |
| usbdev_pkt_t *pkt = unlink_head(&ep->inlist); |
| |
| if (pkt) { |
| pkt->status = |
| (au_readl(ep->reg->ctrl_stat) & USBDEV_CS_NAK) ? |
| PKT_STATUS_NAK : PKT_STATUS_ACK; |
| |
| vdbg("%s: ep%d, %s pkt=%p, list count=%d", __FUNCTION__, |
| ep->address, (pkt->status & PKT_STATUS_NAK) ? |
| "NAK" : "ACK", pkt, ep->inlist.count); |
| } |
| |
| /* |
| * The write fifo should already be drained if things are |
| * working right, but flush it anyway just in case. |
| */ |
| flush_write_fifo(ep); |
| |
| // begin transmitting next packet in the inlist |
| if (ep->inlist.count) { |
| kickstart_send_packet(ep); |
| } |
| |
| return pkt; |
| } |
| |
| /* |
| * Add a new packet to the tail of the given ep's packet |
| * inlist. The transmit complete interrupt frees packets from |
| * the head of this list. EP spinlock must be held when calling. |
| */ |
| static int |
| send_packet(struct usb_dev* dev, usbdev_pkt_t *pkt, int async) |
| { |
| pkt_list_t *list; |
| endpoint_t* ep; |
| |
| if (!pkt || !(ep = epaddr_to_ep(dev, pkt->ep_addr))) |
| return -EINVAL; |
| |
| if (!pkt->size) |
| return 0; |
| |
| list = &ep->inlist; |
| |
| if (!async && list->count) { |
| halt_dma(ep->indma); |
| flush_pkt_list(list); |
| } |
| |
| link_tail(ep, list, pkt); |
| |
| vdbg("%s: ep%d, pkt=%p, size=%d, list count=%d", __FUNCTION__, |
| ep->address, pkt, pkt->size, list->count); |
| |
| if (list->count == 1) { |
| /* |
| * if the packet count is one, it means the list was empty, |
| * and no more data will go out this ep until we kick-start |
| * it again. |
| */ |
| kickstart_send_packet(ep); |
| } |
| |
| return pkt->size; |
| } |
| |
| /* |
| * This routine is called to restart reception of a packet. |
| * EP spinlock must be held when calling. |
| */ |
| static void |
| kickstart_receive_packet(endpoint_t * ep) |
| { |
| usbdev_pkt_t *pkt; |
| |
| // get and link a new packet for next reception |
| if (!(pkt = add_packet(ep, &ep->outlist, ep->max_pkt_size))) { |
| err("%s: could not alloc new packet", __FUNCTION__); |
| return; |
| } |
| |
| if (get_dma_active_buffer(ep->outdma) == 1) { |
| clear_dma_done1(ep->outdma); |
| set_dma_count1(ep->outdma, ep->max_pkt_size); |
| set_dma_count0(ep->outdma, 0); |
| set_dma_addr1(ep->outdma, virt_to_phys(pkt->payload)); |
| enable_dma_buffer1(ep->outdma); // reenable |
| } else { |
| clear_dma_done0(ep->outdma); |
| set_dma_count0(ep->outdma, ep->max_pkt_size); |
| set_dma_count1(ep->outdma, 0); |
| set_dma_addr0(ep->outdma, virt_to_phys(pkt->payload)); |
| enable_dma_buffer0(ep->outdma); // reenable |
| } |
| if (dma_halted(ep->outdma)) |
| start_dma(ep->outdma); |
| } |
| |
| |
| /* |
| * This routine is called when a packet in the outlist has been |
| * completed (received) and we need to prepare for a new packet |
| * to be received. Halts DMA and computes the packet size from the |
| * remaining DMA counter. Then prepares a new packet for reception |
| * and restarts DMA. FIXME: what if another packet comes in |
| * on top of the completed packet? Counter would be wrong. |
| * EP spinlock must be held when calling. |
| */ |
| static usbdev_pkt_t * |
| receive_packet_complete(endpoint_t * ep) |
| { |
| usbdev_pkt_t *pkt = ep->outlist.tail; |
| u32 cs; |
| |
| halt_dma(ep->outdma); |
| |
| cs = au_readl(ep->reg->ctrl_stat); |
| |
| if (!pkt) |
| return NULL; |
| |
| pkt->size = ep->max_pkt_size - get_dma_residue(ep->outdma); |
| if (pkt->size) |
| dma_cache_inv((unsigned long)pkt->payload, pkt->size); |
| /* |
| * need to pull out any remaining bytes in the FIFO. |
| */ |
| endpoint_fifo_read(ep); |
| /* |
| * should be drained now, but flush anyway just in case. |
| */ |
| flush_read_fifo(ep); |
| |
| pkt->status = (cs & USBDEV_CS_NAK) ? PKT_STATUS_NAK : PKT_STATUS_ACK; |
| if (ep->address == 0 && (cs & USBDEV_CS_SU)) |
| pkt->status |= PKT_STATUS_SU; |
| |
| vdbg("%s: ep%d, %s pkt=%p, size=%d", __FUNCTION__, |
| ep->address, (pkt->status & PKT_STATUS_NAK) ? |
| "NAK" : "ACK", pkt, pkt->size); |
| |
| kickstart_receive_packet(ep); |
| |
| return pkt; |
| } |
| |
| |
| /* |
| **************************************************************************** |
| * Here starts the standard device request handlers. They are |
| * all called by do_setup() via a table of function pointers. |
| **************************************************************************** |
| */ |
| |
| static ep0_stage_t |
| do_get_status(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| switch (setup->bRequestType) { |
| case 0x80: // Device |
| // FIXME: send device status |
| break; |
| case 0x81: // Interface |
| // FIXME: send interface status |
| break; |
| case 0x82: // End Point |
| // FIXME: send endpoint status |
| break; |
| default: |
| // Invalid Command |
| endpoint_stall(&dev->ep[0]); // Stall End Point 0 |
| break; |
| } |
| |
| return STATUS_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_clear_feature(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| switch (setup->bRequestType) { |
| case 0x00: // Device |
| if ((le16_to_cpu(setup->wValue) & 0xff) == 1) |
| dev->remote_wakeup_en = 0; |
| else |
| endpoint_stall(&dev->ep[0]); |
| break; |
| case 0x02: // End Point |
| if ((le16_to_cpu(setup->wValue) & 0xff) == 0) { |
| endpoint_t *ep = |
| epaddr_to_ep(dev, |
| le16_to_cpu(setup->wIndex) & 0xff); |
| |
| endpoint_unstall(ep); |
| endpoint_reset_datatoggle(ep); |
| } else |
| endpoint_stall(&dev->ep[0]); |
| break; |
| } |
| |
| return SETUP_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_reserved(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| // Invalid request, stall End Point 0 |
| endpoint_stall(&dev->ep[0]); |
| return SETUP_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_set_feature(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| switch (setup->bRequestType) { |
| case 0x00: // Device |
| if ((le16_to_cpu(setup->wValue) & 0xff) == 1) |
| dev->remote_wakeup_en = 1; |
| else |
| endpoint_stall(&dev->ep[0]); |
| break; |
| case 0x02: // End Point |
| if ((le16_to_cpu(setup->wValue) & 0xff) == 0) { |
| endpoint_t *ep = |
| epaddr_to_ep(dev, |
| le16_to_cpu(setup->wIndex) & 0xff); |
| |
| endpoint_stall(ep); |
| } else |
| endpoint_stall(&dev->ep[0]); |
| break; |
| } |
| |
| return SETUP_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_set_address(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| int new_state = dev->state; |
| int new_addr = le16_to_cpu(setup->wValue); |
| |
| dbg("%s: our address=%d", __FUNCTION__, new_addr); |
| |
| if (new_addr > 127) { |
| // usb spec doesn't tell us what to do, so just go to |
| // default state |
| new_state = DEFAULT; |
| dev->address = 0; |
| } else if (dev->address != new_addr) { |
| dev->address = new_addr; |
| new_state = ADDRESS; |
| } |
| |
| if (dev->state != new_state) { |
| dev->state = new_state; |
| /* inform function layer of usbdev state change */ |
| dev->func_cb(CB_NEW_STATE, dev->state, dev->cb_data); |
| } |
| |
| return SETUP_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_get_descriptor(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| int strnum, desc_len = le16_to_cpu(setup->wLength); |
| |
| switch (le16_to_cpu(setup->wValue) >> 8) { |
| case USB_DT_DEVICE: |
| // send device descriptor! |
| desc_len = desc_len > dev->dev_desc->bLength ? |
| dev->dev_desc->bLength : desc_len; |
| dbg("sending device desc, size=%d", desc_len); |
| send_packet(dev, alloc_packet(&dev->ep[0], desc_len, |
| dev->dev_desc), 0); |
| break; |
| case USB_DT_CONFIG: |
| // If the config descr index in low-byte of |
| // setup->wValue is valid, send config descr, |
| // otherwise stall ep0. |
| if ((le16_to_cpu(setup->wValue) & 0xff) == 0) { |
| // send config descriptor! |
| if (desc_len <= USB_DT_CONFIG_SIZE) { |
| dbg("sending partial config desc, size=%d", |
| desc_len); |
| send_packet(dev, |
| alloc_packet(&dev->ep[0], |
| desc_len, |
| dev->conf_desc), |
| 0); |
| } else { |
| int len = le16_to_cpu(dev->conf_desc->wTotalLength); |
| dbg("sending whole config desc," |
| " size=%d, our size=%d", desc_len, len); |
| desc_len = desc_len > len ? len : desc_len; |
| send_packet(dev, |
| alloc_packet(&dev->ep[0], |
| desc_len, |
| dev->full_conf_desc), |
| 0); |
| } |
| } else |
| endpoint_stall(&dev->ep[0]); |
| break; |
| case USB_DT_STRING: |
| // If the string descr index in low-byte of setup->wValue |
| // is valid, send string descr, otherwise stall ep0. |
| strnum = le16_to_cpu(setup->wValue) & 0xff; |
| if (strnum >= 0 && strnum < 6) { |
| struct usb_string_descriptor *desc = |
| dev->str_desc[strnum]; |
| desc_len = desc_len > desc->bLength ? |
| desc->bLength : desc_len; |
| dbg("sending string desc %d", strnum); |
| send_packet(dev, |
| alloc_packet(&dev->ep[0], desc_len, |
| desc), 0); |
| } else |
| endpoint_stall(&dev->ep[0]); |
| break; |
| default: |
| // Invalid request |
| err("invalid get desc=%d, stalled", |
| le16_to_cpu(setup->wValue) >> 8); |
| endpoint_stall(&dev->ep[0]); // Stall endpoint 0 |
| break; |
| } |
| |
| return STATUS_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_set_descriptor(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| // TODO: implement |
| // there will be an OUT data stage (the descriptor to set) |
| return DATA_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_get_configuration(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| // send dev->configuration |
| dbg("sending config"); |
| send_packet(dev, alloc_packet(&dev->ep[0], 1, &dev->configuration), |
| 0); |
| return STATUS_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_set_configuration(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| // set active config to low-byte of setup->wValue |
| dev->configuration = le16_to_cpu(setup->wValue) & 0xff; |
| dbg("set config, config=%d", dev->configuration); |
| if (!dev->configuration && dev->state > DEFAULT) { |
| dev->state = ADDRESS; |
| /* inform function layer of usbdev state change */ |
| dev->func_cb(CB_NEW_STATE, dev->state, dev->cb_data); |
| } else if (dev->configuration == 1) { |
| dev->state = CONFIGURED; |
| /* inform function layer of usbdev state change */ |
| dev->func_cb(CB_NEW_STATE, dev->state, dev->cb_data); |
| } else { |
| // FIXME: "respond with request error" - how? |
| } |
| |
| return SETUP_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_get_interface(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| // interface must be zero. |
| if ((le16_to_cpu(setup->wIndex) & 0xff) || dev->state == ADDRESS) { |
| // FIXME: respond with "request error". how? |
| } else if (dev->state == CONFIGURED) { |
| // send dev->alternate_setting |
| dbg("sending alt setting"); |
| send_packet(dev, alloc_packet(&dev->ep[0], 1, |
| &dev->alternate_setting), 0); |
| } |
| |
| return STATUS_STAGE; |
| |
| } |
| |
| static ep0_stage_t |
| do_set_interface(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| if (dev->state == ADDRESS) { |
| // FIXME: respond with "request error". how? |
| } else if (dev->state == CONFIGURED) { |
| dev->interface = le16_to_cpu(setup->wIndex) & 0xff; |
| dev->alternate_setting = |
| le16_to_cpu(setup->wValue) & 0xff; |
| // interface and alternate_setting must be zero |
| if (dev->interface || dev->alternate_setting) { |
| // FIXME: respond with "request error". how? |
| } |
| } |
| |
| return SETUP_STAGE; |
| } |
| |
| static ep0_stage_t |
| do_synch_frame(struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| // TODO |
| return SETUP_STAGE; |
| } |
| |
| typedef ep0_stage_t (*req_method_t)(struct usb_dev* dev, |
| struct usb_ctrlrequest* setup); |
| |
| |
| /* Table of the standard device request handlers */ |
| static const req_method_t req_method[] = { |
| do_get_status, |
| do_clear_feature, |
| do_reserved, |
| do_set_feature, |
| do_reserved, |
| do_set_address, |
| do_get_descriptor, |
| do_set_descriptor, |
| do_get_configuration, |
| do_set_configuration, |
| do_get_interface, |
| do_set_interface, |
| do_synch_frame |
| }; |
| |
| |
| // SETUP packet request dispatcher |
| static void |
| do_setup (struct usb_dev* dev, struct usb_ctrlrequest* setup) |
| { |
| req_method_t m; |
| |
| dbg("%s: req %d %s", __FUNCTION__, setup->bRequestType, |
| get_std_req_name(setup->bRequestType)); |
| |
| if ((setup->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD || |
| (setup->bRequestType & USB_RECIP_MASK) != USB_RECIP_DEVICE) { |
| err("%s: invalid requesttype 0x%02x", __FUNCTION__, |
| setup->bRequestType); |
| return; |
| } |
| |
| if ((setup->bRequestType & 0x80) == USB_DIR_OUT && setup->wLength) |
| dbg("%s: OUT phase! length=%d", __FUNCTION__, setup->wLength); |
| |
| if (setup->bRequestType < sizeof(req_method)/sizeof(req_method_t)) |
| m = req_method[setup->bRequestType]; |
| else |
| m = do_reserved; |
| |
| dev->ep0_stage = (*m)(dev, setup); |
| } |
| |
| /* |
| * A SETUP, DATA0, or DATA1 packet has been received |
| * on the default control endpoint's fifo. |
| */ |
| static void |
| process_ep0_receive (struct usb_dev* dev) |
| { |
| endpoint_t *ep0 = &dev->ep[0]; |
| usbdev_pkt_t *pkt; |
| |
| spin_lock(&ep0->lock); |
| |
| // complete packet and prepare a new packet |
| pkt = receive_packet_complete(ep0); |
| if (!pkt) { |
| // FIXME: should put a warn/err here. |
| spin_unlock(&ep0->lock); |
| return; |
| } |
| |
| // unlink immediately from endpoint. |
| unlink_head(&ep0->outlist); |
| |
| // override current stage if h/w says it's a setup packet |
| if (pkt->status & PKT_STATUS_SU) |
| dev->ep0_stage = SETUP_STAGE; |
| |
| switch (dev->ep0_stage) { |
| case SETUP_STAGE: |
| vdbg("SU bit is %s in setup stage", |
| (pkt->status & PKT_STATUS_SU) ? "set" : "not set"); |
| |
| if (pkt->size == sizeof(struct usb_ctrlrequest)) { |
| #ifdef VDEBUG |
| if (pkt->status & PKT_STATUS_ACK) |
| vdbg("received SETUP"); |
| else |
| vdbg("received NAK SETUP"); |
| #endif |
| do_setup(dev, (struct usb_ctrlrequest*)pkt->payload); |
| } else |
| err("%s: wrong size SETUP received", __FUNCTION__); |
| break; |
| case DATA_STAGE: |
| /* |
| * this setup has an OUT data stage. Of the standard |
| * device requests, only set_descriptor has this stage, |
| * so this packet is that descriptor. TODO: drop it for |
| * now, set_descriptor not implemented. |
| * |
| * Need to place a byte in the write FIFO here, to prepare |
| * to send a zero-length DATA ack packet to the host in the |
| * STATUS stage. |
| */ |
| au_writel(0, ep0->reg->write_fifo); |
| dbg("received OUT stage DATAx on EP0, size=%d", pkt->size); |
| dev->ep0_stage = SETUP_STAGE; |
| break; |
| case STATUS_STAGE: |
| // this setup had an IN data stage, and host is ACK'ing |
| // the packet we sent during that stage. |
| if (pkt->size != 0) |
| warn("received non-zero ACK on EP0??"); |
| #ifdef VDEBUG |
| else |
| vdbg("received ACK on EP0"); |
| #endif |
| dev->ep0_stage = SETUP_STAGE; |
| break; |
| } |
| |
| spin_unlock(&ep0->lock); |
| // we're done processing the packet, free it |
| kfree(pkt); |
| } |
| |
| |
| /* |
| * A DATA0/1 packet has been received on one of the OUT endpoints (4 or 5) |
| */ |
| static void |
| process_ep_receive (struct usb_dev* dev, endpoint_t *ep) |
| { |
| usbdev_pkt_t *pkt; |
| |
| spin_lock(&ep->lock); |
| pkt = receive_packet_complete(ep); |
| spin_unlock(&ep->lock); |
| |
| dev->func_cb(CB_PKT_COMPLETE, (unsigned long)pkt, dev->cb_data); |
| } |
| |
| |
| |
| /* This ISR handles the receive complete and suspend events */ |
| static void |
| req_sus_intr (int irq, void *dev_id, struct pt_regs *regs) |
| { |
| struct usb_dev *dev = (struct usb_dev *) dev_id; |
| u32 status; |
| |
| status = au_readl(USBD_INTSTAT); |
| au_writel(status, USBD_INTSTAT); // ack'em |
| |
| if (status & (1<<0)) |
| process_ep0_receive(dev); |
| if (status & (1<<4)) |
| process_ep_receive(dev, &dev->ep[4]); |
| if (status & (1<<5)) |
| process_ep_receive(dev, &dev->ep[5]); |
| } |
| |
| |
| /* This ISR handles the DMA done events on EP0 */ |
| static void |
| dma_done_ep0_intr(int irq, void *dev_id, struct pt_regs *regs) |
| { |
| struct usb_dev *dev = (struct usb_dev *) dev_id; |
| usbdev_pkt_t* pkt; |
| endpoint_t *ep0 = &dev->ep[0]; |
| u32 cs0, buff_done; |
| |
| spin_lock(&ep0->lock); |
| cs0 = au_readl(ep0->reg->ctrl_stat); |
| |
| // first check packet transmit done |
| if ((buff_done = get_dma_buffer_done(ep0->indma)) != 0) { |
| // transmitted a DATAx packet during DATA stage |
| // on control endpoint 0 |
| // clear DMA done bit |
| if (buff_done & DMA_D0) |
| clear_dma_done0(ep0->indma); |
| if (buff_done & DMA_D1) |
| clear_dma_done1(ep0->indma); |
| |
| pkt = send_packet_complete(ep0); |
| if (pkt) |
| kfree(pkt); |
| } |
| |
| /* |
| * Now check packet receive done. Shouldn't get these, |
| * the receive packet complete intr should happen |
| * before the DMA done intr occurs. |
| */ |
| if ((buff_done = get_dma_buffer_done(ep0->outdma)) != 0) { |
| // clear DMA done bit |
| if (buff_done & DMA_D0) |
| clear_dma_done0(ep0->outdma); |
| if (buff_done & DMA_D1) |
| clear_dma_done1(ep0->outdma); |
| |
| //process_ep0_receive(dev); |
| } |
| |
| spin_unlock(&ep0->lock); |
| } |
| |
| /* This ISR handles the DMA done events on endpoints 2,3,4,5 */ |
| static void |
| dma_done_ep_intr(int irq, void *dev_id, struct pt_regs *regs) |
| { |
| struct usb_dev *dev = (struct usb_dev *) dev_id; |
| int i; |
| |
| for (i = 2; i < 6; i++) { |
| u32 buff_done; |
| usbdev_pkt_t* pkt; |
| endpoint_t *ep = &dev->ep[i]; |
| |
| if (!ep->active) continue; |
| |
| spin_lock(&ep->lock); |
| |
| if (ep->direction == USB_DIR_IN) { |
| buff_done = get_dma_buffer_done(ep->indma); |
| if (buff_done != 0) { |
| // transmitted a DATAx pkt on the IN ep |
| // clear DMA done bit |
| if (buff_done & DMA_D0) |
| clear_dma_done0(ep->indma); |
| if (buff_done & DMA_D1) |
| clear_dma_done1(ep->indma); |
| |
| pkt = send_packet_complete(ep); |
| |
| spin_unlock(&ep->lock); |
| dev->func_cb(CB_PKT_COMPLETE, |
| (unsigned long)pkt, |
| dev->cb_data); |
| spin_lock(&ep->lock); |
| } |
| } else { |
| /* |
| * Check packet receive done (OUT ep). Shouldn't get |
| * these, the rx packet complete intr should happen |
| * before the DMA done intr occurs. |
| */ |
| buff_done = get_dma_buffer_done(ep->outdma); |
| if (buff_done != 0) { |
| // received a DATAx pkt on the OUT ep |
| // clear DMA done bit |
| if (buff_done & DMA_D0) |
| clear_dma_done0(ep->outdma); |
| if (buff_done & DMA_D1) |
| clear_dma_done1(ep->outdma); |
| |
| //process_ep_receive(dev, ep); |
| } |
| } |
| |
| spin_unlock(&ep->lock); |
| } |
| } |
| |
| |
| /*************************************************************************** |
| * Here begins the external interface functions |
| *************************************************************************** |
| */ |
| |
| /* |
| * allocate a new packet |
| */ |
| int |
| usbdev_alloc_packet(int ep_addr, int data_size, usbdev_pkt_t** pkt) |
| { |
| endpoint_t * ep = epaddr_to_ep(&usbdev, ep_addr); |
| usbdev_pkt_t* lpkt = NULL; |
| |
| if (!ep || !ep->active || ep->address < 2) |
| return -ENODEV; |
| if (data_size > ep->max_pkt_size) |
| return -EINVAL; |
| |
| lpkt = *pkt = alloc_packet(ep, data_size, NULL); |
| if (!lpkt) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| |
| /* |
| * packet send |
| */ |
| int |
| usbdev_send_packet(int ep_addr, usbdev_pkt_t * pkt) |
| { |
| unsigned long flags; |
| int count; |
| endpoint_t * ep; |
| |
| if (!pkt || !(ep = epaddr_to_ep(&usbdev, pkt->ep_addr)) || |
| !ep->active || ep->address < 2) |
| return -ENODEV; |
| if (ep->direction != USB_DIR_IN) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&ep->lock, flags); |
| count = send_packet(&usbdev, pkt, 1); |
| spin_unlock_irqrestore(&ep->lock, flags); |
| |
| return count; |
| } |
| |
| /* |
| * packet receive |
| */ |
| int |
| usbdev_receive_packet(int ep_addr, usbdev_pkt_t** pkt) |
| { |
| unsigned long flags; |
| usbdev_pkt_t* lpkt = NULL; |
| endpoint_t *ep = epaddr_to_ep(&usbdev, ep_addr); |
| |
| if (!ep || !ep->active || ep->address < 2) |
| return -ENODEV; |
| if (ep->direction != USB_DIR_OUT) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&ep->lock, flags); |
| if (ep->outlist.count > 1) |
| lpkt = unlink_head(&ep->outlist); |
| spin_unlock_irqrestore(&ep->lock, flags); |
| |
| if (!lpkt) { |
| /* no packet available */ |
| *pkt = NULL; |
| return -ENODATA; |
| } |
| |
| *pkt = lpkt; |
| |
| return lpkt->size; |
| } |
| |
| |
| /* |
| * return total queued byte count on the endpoint. |
| */ |
| int |
| usbdev_get_byte_count(int ep_addr) |
| { |
| unsigned long flags; |
| pkt_list_t *list; |
| usbdev_pkt_t *scan; |
| int count = 0; |
| endpoint_t * ep = epaddr_to_ep(&usbdev, ep_addr); |
| |
| if (!ep || !ep->active || ep->address < 2) |
| return -ENODEV; |
| |
| if (ep->direction == USB_DIR_IN) { |
| list = &ep->inlist; |
| |
| spin_lock_irqsave(&ep->lock, flags); |
| for (scan = list->head; scan; scan = scan->next) |
| count += scan->size; |
| spin_unlock_irqrestore(&ep->lock, flags); |
| } else { |
| list = &ep->outlist; |
| |
| spin_lock_irqsave(&ep->lock, flags); |
| if (list->count > 1) { |
| for (scan = list->head; scan != list->tail; |
| scan = scan->next) |
| count += scan->size; |
| } |
| spin_unlock_irqrestore(&ep->lock, flags); |
| } |
| |
| return count; |
| } |
| |
| |
| void |
| usbdev_exit(void) |
| { |
| endpoint_t *ep; |
| int i; |
| |
| au_writel(0, USBD_INTEN); // disable usb dev ints |
| au_writel(0, USBD_ENABLE); // disable usb dev |
| |
| free_irq(AU1000_USB_DEV_REQ_INT, &usbdev); |
| free_irq(AU1000_USB_DEV_SUS_INT, &usbdev); |
| |
| // free all control endpoint resources |
| ep = &usbdev.ep[0]; |
| free_au1000_dma(ep->indma); |
| free_au1000_dma(ep->outdma); |
| endpoint_flush(ep); |
| |
| // free ep resources |
| for (i = 2; i < 6; i++) { |
| ep = &usbdev.ep[i]; |
| if (!ep->active) continue; |
| |
| if (ep->direction == USB_DIR_IN) { |
| free_au1000_dma(ep->indma); |
| } else { |
| free_au1000_dma(ep->outdma); |
| } |
| endpoint_flush(ep); |
| } |
| |
| if (usbdev.full_conf_desc) |
| kfree(usbdev.full_conf_desc); |
| } |
| |
| int |
| usbdev_init(struct usb_device_descriptor* dev_desc, |
| struct usb_config_descriptor* config_desc, |
| struct usb_interface_descriptor* if_desc, |
| struct usb_endpoint_descriptor* ep_desc, |
| struct usb_string_descriptor* str_desc[], |
| void (*cb)(usbdev_cb_type_t, unsigned long, void *), |
| void* cb_data) |
| { |
| endpoint_t *ep0; |
| int i, ret=0; |
| u8* fcd; |
| |
| if (dev_desc->bNumConfigurations > 1 || |
| config_desc->bNumInterfaces > 1 || |
| if_desc->bNumEndpoints > 4) { |
| err("Only one config, one i/f, and no more " |
| "than 4 ep's allowed"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (!cb) { |
| err("Function-layer callback required"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (dev_desc->bMaxPacketSize0 != USBDEV_EP0_MAX_PACKET_SIZE) { |
| warn("EP0 Max Packet size must be %d", |
| USBDEV_EP0_MAX_PACKET_SIZE); |
| dev_desc->bMaxPacketSize0 = USBDEV_EP0_MAX_PACKET_SIZE; |
| } |
| |
| memset(&usbdev, 0, sizeof(struct usb_dev)); |
| |
| usbdev.state = DEFAULT; |
| usbdev.dev_desc = dev_desc; |
| usbdev.if_desc = if_desc; |
| usbdev.conf_desc = config_desc; |
| for (i=0; i<6; i++) |
| usbdev.str_desc[i] = str_desc[i]; |
| usbdev.func_cb = cb; |
| usbdev.cb_data = cb_data; |
| |
| /* Initialize default control endpoint */ |
| ep0 = &usbdev.ep[0]; |
| ep0->active = 1; |
| ep0->type = CONTROL_EP; |
| ep0->max_pkt_size = USBDEV_EP0_MAX_PACKET_SIZE; |
| spin_lock_init(&ep0->lock); |
| ep0->desc = NULL; // ep0 has no descriptor |
| ep0->address = 0; |
| ep0->direction = 0; |
| ep0->reg = &ep_reg[0]; |
| |
| /* Initialize the other requested endpoints */ |
| for (i = 0; i < if_desc->bNumEndpoints; i++) { |
| struct usb_endpoint_descriptor* epd = &ep_desc[i]; |
| endpoint_t *ep; |
| |
| if ((epd->bEndpointAddress & 0x80) == USB_DIR_IN) { |
| ep = &usbdev.ep[2]; |
| ep->address = 2; |
| if (ep->active) { |
| ep = &usbdev.ep[3]; |
| ep->address = 3; |
| if (ep->active) { |
| err("too many IN ep's requested"); |
| ret = -ENODEV; |
| goto out; |
| } |
| } |
| } else { |
| ep = &usbdev.ep[4]; |
| ep->address = 4; |
| if (ep->active) { |
| ep = &usbdev.ep[5]; |
| ep->address = 5; |
| if (ep->active) { |
| err("too many OUT ep's requested"); |
| ret = -ENODEV; |
| goto out; |
| } |
| } |
| } |
| |
| ep->active = 1; |
| epd->bEndpointAddress &= ~0x0f; |
| epd->bEndpointAddress |= (u8)ep->address; |
| ep->direction = epd->bEndpointAddress & 0x80; |
| ep->type = epd->bmAttributes & 0x03; |
| ep->max_pkt_size = le16_to_cpu(epd->wMaxPacketSize); |
| spin_lock_init(&ep->lock); |
| ep->desc = epd; |
| ep->reg = &ep_reg[ep->address]; |
| } |
| |
| /* |
| * initialize the full config descriptor |
| */ |
| usbdev.full_conf_desc = fcd = kmalloc(le16_to_cpu(config_desc->wTotalLength), |
| ALLOC_FLAGS); |
| if (!fcd) { |
| err("failed to alloc full config descriptor"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| memcpy(fcd, config_desc, USB_DT_CONFIG_SIZE); |
| fcd += USB_DT_CONFIG_SIZE; |
| memcpy(fcd, if_desc, USB_DT_INTERFACE_SIZE); |
| fcd += USB_DT_INTERFACE_SIZE; |
| for (i = 0; i < if_desc->bNumEndpoints; i++) { |
| memcpy(fcd, &ep_desc[i], USB_DT_ENDPOINT_SIZE); |
| fcd += USB_DT_ENDPOINT_SIZE; |
| } |
| |
| /* Now we're ready to enable the controller */ |
| au_writel(0x0002, USBD_ENABLE); |
| udelay(100); |
| au_writel(0x0003, USBD_ENABLE); |
| udelay(100); |
| |
| /* build and send config table based on ep descriptors */ |
| for (i = 0; i < 6; i++) { |
| endpoint_t *ep; |
| if (i == 1) |
| continue; // skip dummy ep |
| ep = &usbdev.ep[i]; |
| if (ep->active) { |
| au_writel((ep->address << 4) | 0x04, USBD_CONFIG); |
| au_writel(((ep->max_pkt_size & 0x380) >> 7) | |
| (ep->direction >> 4) | (ep->type << 4), |
| USBD_CONFIG); |
| au_writel((ep->max_pkt_size & 0x7f) << 1, USBD_CONFIG); |
| au_writel(0x00, USBD_CONFIG); |
| au_writel(ep->address, USBD_CONFIG); |
| } else { |
| u8 dir = (i==2 || i==3) ? DIR_IN : DIR_OUT; |
| au_writel((i << 4) | 0x04, USBD_CONFIG); |
| au_writel(((16 & 0x380) >> 7) | dir | |
| (BULK_EP << 4), USBD_CONFIG); |
| au_writel((16 & 0x7f) << 1, USBD_CONFIG); |
| au_writel(0x00, USBD_CONFIG); |
| au_writel(i, USBD_CONFIG); |
| } |
| } |
| |
| /* |
| * Enable Receive FIFO Complete interrupts only. Transmit |
| * complete is being handled by the DMA done interrupts. |
| */ |
| au_writel(0x31, USBD_INTEN); |
| |
| /* |
| * Controller is now enabled, request DMA and IRQ |
| * resources. |
| */ |
| |
| /* request the USB device transfer complete interrupt */ |
| if (request_irq(AU1000_USB_DEV_REQ_INT, req_sus_intr, SA_INTERRUPT, |
| "USBdev req", &usbdev)) { |
| err("Can't get device request intr"); |
| ret = -ENXIO; |
| goto out; |
| } |
| /* request the USB device suspend interrupt */ |
| if (request_irq(AU1000_USB_DEV_SUS_INT, req_sus_intr, SA_INTERRUPT, |
| "USBdev sus", &usbdev)) { |
| err("Can't get device suspend intr"); |
| ret = -ENXIO; |
| goto out; |
| } |
| |
| /* Request EP0 DMA and IRQ */ |
| if ((ep0->indma = request_au1000_dma(ep_dma_id[0].id, |
| ep_dma_id[0].str, |
| dma_done_ep0_intr, |
| SA_INTERRUPT, |
| &usbdev)) < 0) { |
| err("Can't get %s DMA", ep_dma_id[0].str); |
| ret = -ENXIO; |
| goto out; |
| } |
| if ((ep0->outdma = request_au1000_dma(ep_dma_id[1].id, |
| ep_dma_id[1].str, |
| NULL, 0, NULL)) < 0) { |
| err("Can't get %s DMA", ep_dma_id[1].str); |
| ret = -ENXIO; |
| goto out; |
| } |
| |
| // Flush the ep0 buffers and FIFOs |
| endpoint_flush(ep0); |
| // start packet reception on ep0 |
| kickstart_receive_packet(ep0); |
| |
| /* Request DMA and IRQ for the other endpoints */ |
| for (i = 2; i < 6; i++) { |
| endpoint_t *ep = &usbdev.ep[i]; |
| if (!ep->active) |
| continue; |
| |
| // Flush the endpoint buffers and FIFOs |
| endpoint_flush(ep); |
| |
| if (ep->direction == USB_DIR_IN) { |
| ep->indma = |
| request_au1000_dma(ep_dma_id[ep->address].id, |
| ep_dma_id[ep->address].str, |
| dma_done_ep_intr, |
| SA_INTERRUPT, |
| &usbdev); |
| if (ep->indma < 0) { |
| err("Can't get %s DMA", |
| ep_dma_id[ep->address].str); |
| ret = -ENXIO; |
| goto out; |
| } |
| } else { |
| ep->outdma = |
| request_au1000_dma(ep_dma_id[ep->address].id, |
| ep_dma_id[ep->address].str, |
| NULL, 0, NULL); |
| if (ep->outdma < 0) { |
| err("Can't get %s DMA", |
| ep_dma_id[ep->address].str); |
| ret = -ENXIO; |
| goto out; |
| } |
| |
| // start packet reception on OUT endpoint |
| kickstart_receive_packet(ep); |
| } |
| } |
| |
| out: |
| if (ret) |
| usbdev_exit(); |
| return ret; |
| } |
| |
| EXPORT_SYMBOL(usbdev_init); |
| EXPORT_SYMBOL(usbdev_exit); |
| EXPORT_SYMBOL(usbdev_alloc_packet); |
| EXPORT_SYMBOL(usbdev_receive_packet); |
| EXPORT_SYMBOL(usbdev_send_packet); |
| EXPORT_SYMBOL(usbdev_get_byte_count); |