From: Marcel <korg...@home.nl> Atmel USBA UDC cleanup
Atmel USBA UDC cleanup more cleanup of Atmel USBA UDC Some more cleaning of Atmel USBA UDC further cleaning of Atmel USBA UDC Signed-off-by: Marcel <korg...@home.nl> --- drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/atmel_usba_udc.c | 1438 +++++++++++++++++++++++++++++++++++ include/usb/atmel_usba_udc.h | 398 ++++++++++ 3 files changed, 1837 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/atmel_usba_udc.c create mode 100644 include/usb/atmel_usba_udc.h diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 0846233..024844d 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -29,6 +29,7 @@ LIB := $(obj)libusb_gadget.o ifdef CONFIG_USB_ETHER COBJS-y += ether.o epautoconf.o config.o usbstring.o COBJS-$(CONFIG_USB_GADGET_AT91) += at91_udc.o +COBJS-$(CONFIG_USB_GADGET_ATMEL_USBA) += atmel_usba_udc.o else # Devices not related to the new gadget layer depend on CONFIG_USB_DEVICE ifdef CONFIG_USB_DEVICE diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c new file mode 100644 index 0000000..6d02de6 --- /dev/null +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -0,0 +1,1438 @@ +/* + * Driver for the Atmel USBA high speed USB device controller + * + * Copyright (C) 2011 Marcel Janssen, Admesy B.V. + * Copyright (C) 2005-2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <common.h> +#include <malloc.h> +#include <asm/errno.h> +#include <linux/list.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/mtd/compat.h> +#include <linux/porting-compat.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/arch/clk.h> +#include <asm/arch/gpio.h> +#include <asm/arch/at91_pmc.h> +#include <asm/arch/io.h> + +#include <usb/atmel_usba_udc.h> + +#define __iomem + +#define memcpy_toio(a, b, c) memcpy((a), (b), (c)) +#define memcpy_fromio(a, b, c) memcpy((a), (b), (c)) +#define REQ_COUNT 8 +#define MAX_REQ (REQ_COUNT-1) + +static struct usba_udc the_udc; +#define true 1 +#define false 0 +static const char driver_name[] = "atmel_usba_udc"; +static const char ep0name[] = "ep0"; + + +static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) +{ + unsigned int transaction_len; + + transaction_len = req->req.length - req->req.actual; + req->last_transaction = 1; + if (transaction_len > ep->ep.maxpacket) { + transaction_len = ep->ep.maxpacket; + req->last_transaction = 0; + } else if (transaction_len == ep->ep.maxpacket && req->req.zero) + req->last_transaction = 0; + + debug("%s: submit_transaction, req %p (length %d)%s\n", + ep->ep.name, req, transaction_len, + req->last_transaction ? ", done" : ""); + + memcpy_toio(ep->fifo, req->req.buf + req->req.actual, transaction_len); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + req->req.actual += transaction_len; +} + +static void submit_request(struct usba_ep *ep, struct usba_request *req) +{ + + req->req.actual = 0; + req->submitted = 1; + + debug("%s : submit_request: req %p (length %d)\n", + ep->ep.name, req, req->req.length); + next_fifo_transaction(ep, req); + if (req->last_transaction) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } else { + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + } +} + +static void submit_next_request(struct usba_ep *ep) +{ + struct usba_request *req; + + if (list_empty(&ep->queue)) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY | USBA_RX_BK_RDY); + return; + } + req = list_entry(ep->queue.next, struct usba_request, queue); + if (!req->submitted) + submit_request(ep, req); + debug("%s : submit_next_request: req %p (length %d)\n", + ep->ep.name, req, req->req.length); +} + +static void send_status(struct usba_udc *udc, struct usba_ep *ep) +{ + debug("Send USBA status\n"); + ep->state = STATUS_STAGE_IN; + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); +} + +static void receive_data(struct usba_ep *ep) +{ + struct usba_udc *udc = ep->udc; + struct usba_request *req; + unsigned long status; + unsigned int bytecount, nr_busy; + int is_complete = 0; + + status = usba_ep_readl(ep, STA); + nr_busy = USBA_BFEXT(BUSY_BANKS, status); + + debug("receive data: nr_busy=%u\n", nr_busy); + + while (nr_busy > 0) { + if (list_empty(&ep->queue)) { + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + break; + } + req = list_entry(ep->queue.next, + struct usba_request, queue); + + bytecount = USBA_BFEXT(BYTE_COUNT, status); + + if (status & (1 << 31)) + is_complete = 1; + if (req->req.actual + bytecount >= req->req.length) { + is_complete = 1; + bytecount = req->req.length - req->req.actual; + } + + memcpy_fromio(req->req.buf + req->req.actual, + ep->fifo, bytecount); + req->req.actual += bytecount; + + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + + if (is_complete) { + debug("%s: request done\n", ep->ep.name); + req->req.status = 0; + list_del_init(&req->queue); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + req->req.complete(&ep->ep, &req->req); + debug("request->complete done\n"); + } + + status = usba_ep_readl(ep, STA); + nr_busy = USBA_BFEXT(BUSY_BANKS, status); + debug("nr_busy = %d\n", nr_busy); + if (is_complete && ep_is_control(ep)) { + send_status(udc, ep); + break; + } + debug("rcv : bytecount=%d actual=%d\n", req->req.actual); + } +} + +static void +request_complete(struct usba_ep *ep, struct usba_request *req, int status) +{ + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + + debug("%s: req %p complete: status %d, actual %u\n", + ep->ep.name, req, req->req.status, req->req.actual); + + req->req.complete(&ep->ep, &req->req); + debug("request complete finished\n"); +} + +static int +usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct usba_ep *ep; + struct usba_udc *udc; + unsigned long ept_cfg, maxpacket; + unsigned int nr_trans; + + ep = container_of(_ep, struct usba_ep, ep); + udc = ep->udc; + + debug("Endpoint enable ep %d\n", ep->index); + maxpacket = le16_to_cpu(desc->wMaxPacketSize) & 0x7ff; + + if (((desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) != ep->index) + || ep->index == 0 + || desc->bDescriptorType != USB_DT_ENDPOINT + || maxpacket == 0 + || maxpacket > ep->fifo_size) { + debug("ep_enable: Invalid argument"); + return -EINVAL; + } + + ep->is_isoc = 0; + ep->is_in = 0; + + if (maxpacket <= 8) + ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8); + else + /* LSB is bit 1, not 0 */ + ept_cfg = USBA_BF(EPT_SIZE, fls(maxpacket - 1) - 3); + + debug("%s: EPT_SIZE = %lu (maxpacket = %lu)\n", + ep->ep.name, ept_cfg, maxpacket); + + if ((desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + ep->is_in = 1; + ept_cfg |= USBA_EPT_DIR_IN; + debug("%s: is IN endpoint\n", ep->ep.name); + } + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + debug("%s: is CTRL endpoint\n", ep->ep.name); + break; + case USB_ENDPOINT_XFER_ISOC: + debug("%s: is ISOC endpoint\n", ep->ep.name); + if (!ep->can_isoc) + return -EINVAL; + + + /* + * Bits 11:12 specify number of _additional_ + * transactions per microframe. + */ + nr_trans = ((le16_to_cpu(desc->wMaxPacketSize) >> 11) & 3) + 1; + if (nr_trans > 3) + return -EINVAL; + + ep->is_isoc = 1; + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_ISO); + + /* + * Do triple-buffering on high-bandwidth iso endpoints. + */ + if (nr_trans > 1 && ep->nr_banks == 3) + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_TRIPLE); + else + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE); + ept_cfg |= USBA_BF(NB_TRANS, nr_trans); + break; + case USB_ENDPOINT_XFER_BULK: + debug("%s: is BULK endpoint\n", ep->ep.name); + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + break; + case USB_ENDPOINT_XFER_INT: + debug("%s: is INT endpoint\n", ep->ep.name); + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_INT); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + break; + } + + if (ep->desc) { + debug("ep%d already enabled\n", ep->index); + return -EBUSY; + } + + ep->desc = desc; + ep->ep.maxpacket = maxpacket; + + usba_ep_writel(ep, CFG, ept_cfg); + debug("ep%d CFG = 0x%lx\n", ep->index, ept_cfg); + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + + debug("ep%d not using DMA\n", ep->index); + usba_writel(udc, INT_ENB, (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1 << ep->index))); + + debug("EPT_CFG%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, CFG)); + debug("INT_ENB after init: %#08lx\n", + (unsigned long)usba_readl(udc, INT_ENB)); + debug("INT_STA after init: %#08lx\n", + (unsigned long)usba_readl(udc, INT_STA)); + debug("EPT_CTL_ENB%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, CTL_ENB)); + debug("EPT_CTL_DIS%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, CTL_DIS)); + debug("EPT_CTL%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, CTL)); + debug("EPT_STA%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, STA)); + return 0; +} + +static int usba_ep_disable(struct usb_ep *_ep) +{ + struct usba_ep *ep; + struct usba_udc *udc; + + ep = container_of(_ep, struct usba_ep, ep); + udc = ep->udc; + + if (ep == &ep->udc->ep[0]) + return -EINVAL; + + debug("Endpoint disable ep %d\n", ep->index); + + ep->desc = NULL; + + usba_ep_writel(ep, CTL_DIS, USBA_EPT_ENABLE); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) + & ~USBA_BF(EPT_INT, 1 << ep->index)); + + return 0; +} + +#define REQ_COUNT 8 +#define MAX_REQ (REQ_COUNT-1) +static struct usba_request req_pool[REQ_COUNT]; + +static void init_requests(void) +{ + int i; + + for (i = 0; i < MAX_REQ; i++) + req_pool[i].in_use = 0; +} + +static void free_request(struct usba_request *ptr) +{ + if (ptr < &req_pool[0] && ptr > &req_pool[REQ_COUNT]) { + debug("%s: ptr 0x%p does not specify valid req\n", \ + __func__, ptr); + if (!(ptr->in_use)) + debug("%s: ptr not marked as \"in_use\"\n", __func__); + + hang(); + } + ptr->in_use = 0; +} + +static struct usba_request *alloc_request(void) +{ + int i; + struct usba_request *ptr = NULL; + + for (i = 0; i < MAX_REQ; i++) { + if (!req_pool[i].in_use) { + ptr = &req_pool[i]; + req_pool[i].in_use = 1; + DBG("alloc_req: request[%d]\n", i); + break; + } + } + if (!ptr) + DBG("panic: no more free req buffers\n"); + return ptr; +} + +static struct usb_request * +usba_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct usba_request *req; + + req = alloc_request(); + + if (!req) { + debug("ALLOC REQUEST FAILLED : %s\n", _ep->name); + return NULL; + } + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void usba_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usba_request *req; + + debug("FREE request for %s\n", _ep->name); + req = container_of(_req, struct usba_request, req); + + free_request(req); +} + +static int +usba_ep_queue(struct usb_ep *_ep, + struct usb_request *_req, + gfp_t gfp_flags) +{ + struct usba_request *req = to_usba_req(_req); + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + int ret; + + debug("%s: queue req %p, len %u\n", + ep->ep.name, req, _req->length); + + if (!_req || !_req->complete + || !_req->buf || !list_empty(&req->queue)) { + + debug("invalid request\n"); + if (!_req) + debug("NO REQUEST\n"); + if (!_req->complete) + debug("NO REQUEST COMPLETE\n"); + if (!_req->buf) + debug("NO REQUEST BUF\n"); + if (!list_empty(&req->queue)) + debug("NO LIST EMPTY\n"); + return -EINVAL; + } + + if (!_ep || (!ep->desc && ep->ep.name != ep0name)) { + DBG("invalid ep\n"); + return -EINVAL; + } + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN || !ep->desc) + return -ESHUTDOWN; + + req->submitted = 0; + req->last_transaction = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* May have received a reset since last time we checked */ + ret = -ESHUTDOWN; + + if (ep->desc) { + list_add_tail(&req->queue, &ep->queue); + + if ((!ep_is_control(ep) && ep->is_in) || + (ep_is_control(ep) && (ep->state == DATA_STAGE_IN || + ep->state == STATUS_STAGE_IN))) { + + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + debug("WE TRANSMITTED DATA ep= %s\n", ep->ep.name); + } else { + debug("WE RECEIVED OUT DATA ep= %s\n", ep->ep.name); + usba_ep_writel(ep, CTL_ENB, USBA_RX_BK_RDY); + /*FIXME may be needed, not sure yet */ + /*usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY);*/ + } + ret = 0; + } + return ret; +} + +static int usba_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usba_ep *ep = container_of(_ep, struct usba_ep, ep); + struct usba_request *req = container_of(_req, struct usba_request, req); + + debug("ep_dequeue: %s, req %p\n", + ep->ep.name, req); + + if (!_ep || ep->ep.name == ep0name) { + DBG("invalid dequeue request\n"); + if (!_ep) + debug("NO ENDPOINT\n"); + if (ep->ep.name == ep0name) + debug("Cannot dequeue on ep0\n"); + return -EINVAL; + } + /* + * Errors should stop the queue from advancing until the + * completion function returns. + */ + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) + return -EINVAL; + request_complete(ep, req, -ECONNRESET); + + /* Process the next request if any */ + submit_next_request(ep); + + return 0; +} + +static int usba_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct usba_ep *ep = container_of(_ep, struct usba_ep, ep); + int ret = 0; + + debug("Endpoint halt\n"); + if (!ep->desc) { + debug("Attempted to halt uninitialized ep %s\n", + ep->ep.name); + return -ENODEV; + } + if (ep->is_isoc) { + debug("Attempted to halt isochronous ep %s\n", + ep->ep.name); + return -ENOTTY; + } + + /* + * We can't halt IN endpoints while there are still data to be + * transferred + */ + if (!list_empty(&ep->queue) + || ((value && ep->is_in && (usba_ep_readl(ep, STA) + & USBA_BF(BUSY_BANKS, -1L))))) { + ret = -EAGAIN; + } else { + if (value) + usba_ep_writel(ep, SET_STA, USBA_FORCE_STALL); + else + usba_ep_writel(ep, CLR_STA, + USBA_FORCE_STALL | USBA_TOGGLE_CLR); + usba_ep_readl(ep, STA); + } + + return ret; +} + +static const struct usb_ep_ops usba_ep_ops = { + .enable = usba_ep_enable, + .disable = usba_ep_disable, + .alloc_request = usba_ep_alloc_request, + .free_request = usba_ep_free_request, + .queue = usba_ep_queue, + .dequeue = usba_ep_dequeue, + .set_halt = usba_ep_set_halt, +}; + +static int usba_udc_get_frame(struct usb_gadget *gadget) +{ + struct usba_udc *udc = to_usba_udc(gadget); + + return USBA_BFEXT(FRAME_NUMBER, usba_readl(udc, FNUM)); +} + +static int usba_udc_wakeup(struct usb_gadget *gadget) +{ + struct usba_udc *udc = to_usba_udc(gadget); + u32 ctrl; + int ret = -EINVAL; + debug("UDC wakeup\n"); + + if (udc->devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) { + ctrl = usba_readl(udc, CTRL); + usba_writel(udc, CTRL, ctrl | USBA_REMOTE_WAKE_UP); + ret = 0; + } + + return ret; +} + +static int +usba_udc_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) +{ + struct usba_udc *udc = to_usba_udc(gadget); + debug("UDC set selfpowered\n"); + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static const struct usb_gadget_ops usba_udc_ops = { + .get_frame = usba_udc_get_frame, + .wakeup = usba_udc_wakeup, + .set_selfpowered = usba_udc_set_selfpowered, +}; + +static struct usb_endpoint_descriptor usba_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = __constant_cpu_to_le16(64), + /* FIXME: I have no idea what to put here */ + .bInterval = 1, +}; + +static struct usba_udc the_udc = { + .regs = (unsigned *) AT91SAM9G45_BASE_UDPHS, + .fifo = (unsigned *) AT91SAM9G45_UDPHS_FIFO, + .gadget = { + .ops = &usba_udc_ops, + .ep0 = &the_udc.ep[0].ep, + .is_dualspeed = 1, + .name = driver_name, + }, + + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &usba_ep_ops, + .maxpacket = 64, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 0, + .fifo_size = 64, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, + .ep[1] = { + .ep = { + .name = "ep1", + .ops = &usba_ep_ops, + .maxpacket = 1024, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 1, + .fifo_size = 1024, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, + .ep[2] = { + .ep = { + .name = "ep2", + .ops = &usba_ep_ops, + .maxpacket = 1024, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 2, + .fifo_size = 1024, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, + .ep[3] = { + .ep = { + .name = "ep3", + .ops = &usba_ep_ops, + .maxpacket = 1024, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 3, + .fifo_size = 1024, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, + .ep[4] = { + .ep = { + .name = "ep4", + .ops = &usba_ep_ops, + .maxpacket = 1024, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 4, + .fifo_size = 1024, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, + .ep[5] = { + .ep = { + .name = "ep5", + .ops = &usba_ep_ops, + .maxpacket = 1024, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 5, + .fifo_size = 1024, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, + .ep[6] = { + .ep = { + .name = "ep6", + .ops = &usba_ep_ops, + .maxpacket = 1024, + }, + .udc = &the_udc, + .is_pingpong = 0, + .int_mask = 1 << 6, + .fifo_size = 1024, + .nr_banks = 1, + .can_dma = 0, + .can_isoc = 0, + }, +}; + +static void reset_all_endpoints(struct usba_udc *udc) +{ + + struct usba_ep *ep; + struct usba_request *req, *tmp_req; + debug("USBA : reset all endpoints\n"); + usba_writel(udc, EPT_RST, ~0UL); + + ep = container_of(udc->gadget.ep0, struct usba_ep, ep); + list_for_each_entry_safe(req, tmp_req, &ep->queue, queue) { + list_del_init(&req->queue); + request_complete(ep, req, -ECONNRESET); + } + + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) + usba_ep_disable(&ep->ep); + } +} + +static struct usba_ep *get_ep_by_addr(struct usba_udc *udc, u16 wIndex) +{ + struct usba_ep *ep; + debug("get EP by Address\n"); + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return container_of(udc->gadget.ep0, struct usba_ep, ep); + + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + + if (!ep->desc) + continue; + bEndpointAddress = ep->desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + if ((bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) + == (wIndex & USB_ENDPOINT_NUMBER_MASK)) + return ep; + } + return NULL; +} + +/* Called with interrupts disabled and udc->lock held */ +static inline void set_protocol_stall(struct usba_udc *udc, struct usba_ep *ep) +{ + debug("Set protocol stall\n"); + usba_ep_writel(ep, SET_STA, USBA_FORCE_STALL); + ep->state = WAIT_FOR_SETUP; +} + +static inline int is_stalled(struct usba_udc *udc, struct usba_ep *ep) +{ + debug("IS STALLED\n"); + if (usba_ep_readl(ep, STA) & USBA_FORCE_STALL) + return 1; + return 0; +} + +static inline void set_address(struct usba_udc *udc, unsigned int addr) +{ + u32 regval; + debug("USBA : set address %d\n", addr); + regval = usba_readl(udc, CTRL); + regval = USBA_BFINS(DEV_ADDR, addr, regval); + usba_writel(udc, CTRL, regval); +} + +/* Avoid overly long expressions */ +static inline char feature_is_dev_remote_wakeup(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == __constant_cpu_to_le16(USB_DEVICE_REMOTE_WAKEUP)) + return true; + return false; +} + +static inline char feature_is_ep_halt(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == __constant_cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + return false; +} + +static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep, + struct usb_ctrlrequest *crq) +{ + int retval = 0; + + debug("USBA : EP0 setup\n"); + switch (crq->bRequest) { + case USB_REQ_GET_STATUS: { + u16 status; + debug("USBA : USB_REQ_GET_STATUS\n"); + if (crq->bRequestType == (USB_DIR_IN | USB_RECIP_DEVICE)) { + status = cpu_to_le16(udc->devstatus); + } else if (crq->bRequestType + == (USB_DIR_IN | USB_RECIP_INTERFACE)) { + status = cpu_to_le16(0); + } else if (crq->bRequestType + == (USB_DIR_IN | USB_RECIP_ENDPOINT)) { + struct usba_ep *target; + + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + status = 0; + if (is_stalled(udc, target)) + status |= cpu_to_le16(1); + } else { + debug("Delegate : USB_REQ_GET_STATUS\n"); + goto delegate; + } + /* Write directly to the FIFO. No queueing is done. */ + if (crq->wLength != cpu_to_le16(sizeof(status))) + goto stall; + ep->state = DATA_STAGE_IN; + debug("Writing to FIFO\n"); + __raw_writew(status, ep->fifo); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + break; + } + + case USB_REQ_CLEAR_FEATURE: { + debug("USBA : USB_REQ_CLEAR_FEATURE\n"); + if (crq->bRequestType == USB_RECIP_DEVICE) { + if (feature_is_dev_remote_wakeup(crq)) + udc->devstatus + &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); + else + /* Can't CLEAR_FEATURE TEST_MODE */ + goto stall; + } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { + struct usba_ep *target; + + if (crq->wLength != cpu_to_le16(0) + || !feature_is_ep_halt(crq)) + goto stall; + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + usba_ep_writel(target, CLR_STA, USBA_FORCE_STALL); + if (target->index != 0) + usba_ep_writel(target, CLR_STA, + USBA_TOGGLE_CLR); + } else { + goto delegate; + } + + send_status(udc, ep); + break; + } + + case USB_REQ_SET_FEATURE: { + debug("USBA : USB_REQ_SET_FEATURE\n"); + if (crq->bRequestType == USB_RECIP_DEVICE) { + if (feature_is_dev_remote_wakeup(crq)) + udc->devstatus |= 1 << USB_DEVICE_REMOTE_WAKEUP; + else + goto stall; + } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { + struct usba_ep *target; + + if (crq->wLength != cpu_to_le16(0) + || !feature_is_ep_halt(crq)) + goto stall; + + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + usba_ep_writel(target, SET_STA, USBA_FORCE_STALL); + } else + goto delegate; + + send_status(udc, ep); + break; + } + + case USB_REQ_SET_ADDRESS: + debug("USBA : USB_REQ_SET_ADDRESS\n"); + if (crq->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + goto delegate; + + set_address(udc, le16_to_cpu(crq->wValue)); + send_status(udc, ep); + ep->state = STATUS_STAGE_ADDR; + break; + + default: +delegate: + debug("deligate SETUP Type = %4x %4x %4x\n", + crq->bRequest, + crq->wValue, + crq->wIndex); + + retval = udc->driver->setup(&udc->gadget, crq); + } + + return retval; + +stall: + debug("udc: %s: Invalid setup request: %02x.%02x v%04x i%04x l%d, " + "halting endpoint...\n", + ep->ep.name, crq->bRequestType, crq->bRequest, + le16_to_cpu(crq->wValue), le16_to_cpu(crq->wIndex), + le16_to_cpu(crq->wLength)); + set_protocol_stall(udc, ep); + return -1; +} + +static void usba_control_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 epstatus; + u32 epctrl; + +restart: + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + + debug("%s [%d]: s/%08x c/%08x\n", + ep->ep.name, ep->state, epstatus, epctrl); + + req = NULL; + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct usba_request, queue); + + if ((epctrl & USBA_TX_PK_RDY) && !(epstatus & USBA_TX_PK_RDY)) { + debug("usba_control_irq : USBA_TX_PK_RDY\n"); + if (req->submitted) + next_fifo_transaction(ep, req); + else + submit_request(ep, req); + + if (req->last_transaction) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } + goto restart; + } + if ((epstatus & epctrl) & USBA_TX_COMPLETE) { + debug("usba_control_irq : USBA_TX_COMPLETE\n"); + usba_ep_writel(ep, CLR_STA, USBA_TX_COMPLETE); + + switch (ep->state) { + case DATA_STAGE_IN: + debug("USBA : DATA Stage in\n"); + usba_ep_writel(ep, CTL_ENB, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = STATUS_STAGE_OUT; + break; + case STATUS_STAGE_ADDR: + debug("USBA : Status stage addr\n"); + /* Activate our new address */ + usba_writel(udc, CTRL, (usba_readl(udc, CTRL) + | USBA_FADDR_EN)); + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + break; + case STATUS_STAGE_IN: + debug("USBA : Status stage in\n"); + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, 0); + submit_next_request(ep); + } + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + break; + case STATUS_STAGE_TEST: + debug("USBA : test mode not supported\n"); + ep->state = WAIT_FOR_SETUP; + break; + default: + debug("udc: %s: TXRDY: Invalid endpoint state %d, " + "halting endpoint...\n", + ep->ep.name, ep->state); + set_protocol_stall(udc, ep); + break; + } + + goto restart; + } + if ((epstatus & epctrl) & USBA_RX_BK_RDY) { + debug("USBA : USBA_RX_BK_RDY\n"); + switch (ep->state) { + case STATUS_STAGE_OUT: + debug("%s : USBA :STATUS_STAGE_OUT\n", ep->ep.name); + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + + if (req) { + list_del_init(&req->queue); + debug("%s :USBA:request complete\n", + ep->ep.name); + request_complete(ep, req, 0); + } + ep->state = WAIT_FOR_SETUP; + break; + + case DATA_STAGE_OUT: + debug("%s : DATA STAGE OUT\n", ep->ep.name); + receive_data(ep); + break; + + default: + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + debug("udc: %s: RXRDY: Invalid endpoint state %d, " + "halting endpoint...\n", + ep->ep.name, ep->state); + set_protocol_stall(udc, ep); + break; + } + + goto restart; + } + if (epstatus & USBA_RX_SETUP) { + debug("USBA : USBA_RX_SETUP\n"); + union { + struct usb_ctrlrequest crq; + unsigned long data[2]; + } crq; + unsigned int pkt_len; + int ret; + + if (ep->state != WAIT_FOR_SETUP) { + /* + * Didn't expect a SETUP packet at this + * point. Clean up any pending requests (which + * may be successful). + */ + int status = -EPROTO; + + /* + * RXRDY and TXCOMP are dropped when SETUP + * packets arrive. Just pretend we received + * the status packet. + */ + if (ep->state == STATUS_STAGE_OUT + || ep->state == STATUS_STAGE_IN) { + debug("DROPPING DATA %s\n", ep->ep.name); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + status = 0; + } + + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, status); + } + } + + pkt_len = USBA_BFEXT(BYTE_COUNT, usba_ep_readl(ep, STA)); + debug("Packet length: %u\n", pkt_len); + if (pkt_len != sizeof(crq)) { + debug("udc: Invalid packet length %u " + "(expected %zu)\n", pkt_len, sizeof(crq)); + set_protocol_stall(udc, ep); + return; + } + + debug("Copying ctrl request from 0x%p:\n", ep->fifo); + memcpy_fromio(crq.data, ep->fifo, sizeof(crq)); + + /* Free up one bank in the FIFO so that we can + * generate or receive a reply right away. */ + usba_ep_writel(ep, CLR_STA, USBA_RX_SETUP); + + if (crq.crq.bRequestType & USB_DIR_IN) { + /* + * The USB 2.0 spec states that "if wLength is + * zero, there is no data transfer phase." + * However, testusb #14 seems to actually + * expect a data phase even if wLength = 0... + */ + ep->state = DATA_STAGE_IN; + } else { + if (crq.crq.wLength != cpu_to_le16(0)) + ep->state = DATA_STAGE_OUT; + else + ep->state = STATUS_STAGE_IN; + } + + ret = -1; + if (ep->index == 0) + ret = handle_ep0_setup(udc, ep, &crq.crq); + else { + debug("driver setup %d %d\n", + crq.crq.wIndex, crq.crq.wValue); + ret = udc->driver->setup(&udc->gadget, &crq.crq); + } + + if (ret < 0) { + /* Let the host know that we failed */ + set_protocol_stall(udc, ep); + } + } +} + +static void usba_ep_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 epstatus; + u32 epctrl; + + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + + while ((epctrl & USBA_TX_PK_RDY) && !(epstatus & USBA_TX_PK_RDY)) { + debug("%s: TX PK ready\n", ep->ep.name); + + if (list_empty(&ep->queue)) { + debug("ep_irq: queue empty\n"); + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + return; + } + + req = list_entry(ep->queue.next, struct usba_request, queue); + + if (req->submitted) { + debug("TX next trans %s\n", ep->ep.name); + next_fifo_transaction(ep, req); + } else { + debug("TX submit %s\n", ep->ep.name); + submit_request(ep, req); + } + if (req->last_transaction) { + debug("last transaction %s\n", ep->ep.name); + list_del_init(&req->queue); + submit_next_request(ep); + request_complete(ep, req, 0); + } + + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + } + debug("%s status = %lx\n", ep->ep.name, epstatus); + if ((epstatus & epctrl) & USBA_RX_BK_RDY) { + debug("%s RX data ready\n", ep->ep.name); + receive_data(ep); + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + } +} + +int usb_gadget_handle_interrupts(void) +{ + struct usba_udc *udc = &the_udc; + u32 status; + u32 ep_status; + + status = usba_readl(udc, INT_STA); + debug("irq, status=%#08x\n", status); + + if (status & USBA_DET_SUSPEND) { + debug("USBA : USBA_DET_SUSPEND\n"); + usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver && udc->driver->suspend) { + udc->driver->suspend(&udc->gadget); + } + } + + if (status & USBA_WAKE_UP) { + debug("USBA : USBA_WAKE_UP\n"); + usba_writel(udc, INT_CLR, USBA_WAKE_UP); + } + + if (status & USBA_END_OF_RESUME) { + debug("USBA : USBA_END_OF_RESUME\n"); + usba_writel(udc, INT_CLR, USBA_END_OF_RESUME); + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver && udc->driver->resume) { + udc->driver->resume(&udc->gadget); + } + } + + ep_status = USBA_BFEXT(EPT_INT, status); + if (ep_status) { + int i; + + for (i = 0; i < USBA_NR_ENDPOINTS; i++) + if (ep_status & (1 << i)) { + if (ep_is_control(&udc->ep[i])) { + debug("EP %d = control\n", i); + usba_control_irq(udc, &udc->ep[i]); + } else { + debug("EP = %d\n", i); + usba_ep_irq(udc, &udc->ep[i]); + } + } + } + + if (status & USBA_END_OF_RESET) { + struct usba_ep *ep0; + debug("USBA : USBA_END_OF_RESET\n"); + usba_writel(udc, INT_CLR, USBA_END_OF_RESET); + reset_all_endpoints(udc); + + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver->disconnect) { + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver->disconnect(&udc->gadget); + } + + if (status & USBA_HIGH_SPEED) { + debug("USBA : High speed\n"); + udc->gadget.speed = USB_SPEED_HIGH; + } else { + debug("USBA : Full speed\n"); + udc->gadget.speed = USB_SPEED_FULL; + } + + ep0 = &udc->ep[0]; + ep0->desc = &usba_ep0_desc; + ep0->state = WAIT_FOR_SETUP; + usba_ep_writel(ep0, CFG, + (USBA_BF(EPT_SIZE, EP0_EPT_SIZE) + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL) + | USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE))); + usba_ep_writel(ep0, CTL_ENB, + USBA_EPT_ENABLE | USBA_RX_SETUP); + usba_writel(udc, INT_ENB, + (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1) + | USBA_DET_SUSPEND + | USBA_END_OF_RESUME)); + + /* + * Unclear why we hit this irregularly, e.g. in usbtest, + * but it's clearly harmless... + */ + if (!(usba_ep_readl(ep0, CFG) & USBA_EPT_MAPPED)) { + ERR("ODD: EP0 configuration is invalid!\n"); + debug("ERROR : ep0 config invalid\n"); + } + debug("USBA : USBA_END_OF_RESET - finished\n"); + } + return IRQ_HANDLED; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usba_udc *udc = &the_udc; + + if (!driver) + return -ENODEV; + if (driver != udc->driver || !driver->unbind) + return -EINVAL; + + udc->gadget.speed = USB_SPEED_UNKNOWN; + reset_all_endpoints(udc); + + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + driver->unbind(&udc->gadget); + udc->driver = NULL; + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct usba_udc *udc = &the_udc; + int retval = -1; + + debug("USBA : registering driver\n"); + + if (udc->driver) { + debug("UDC already has a gadget driver\n"); + return -EBUSY; + } + + udc->driver = driver; + + if (udc->driver) + retval = driver->bind(&udc->gadget); + + if (retval) { + debug("driver->bind() returned %d\n", retval); + udc->driver = NULL; + return retval; + } + + debug("Enabling controller\n"); + usba_writel(udc, CTRL, USBA_ENABLE_MASK); + usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + + debug("USBA register finished\n"); + return 0; +} + +/* reinit == restore inital software state */ +static void udc_reinit(struct usba_udc *udc) +{ + u32 i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + for (i = 0; i < USBA_NR_ENDPOINTS; i++) { + struct usba_ep *ep = &udc->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->desc = NULL; + ep->stopped = 0; + ep->fifo_bank = 0; + ep->ep.maxpacket = ep->maxpacket; + ep->ep_regs = (void __iomem *) udc->regs + USBA_EPT_BASE(i); + INIT_LIST_HEAD(&ep->queue); + } +} + +int usba_udc_probe(struct platform_data *pdata) +{ + struct usba_udc *udc; + int i; + /* needed to disable DMA */ + at91_pmc_t *pmc = (at91_pmc_t *)AT91_PMC_BASE; + u32 regvalue; + + udc = &the_udc; + udc->regs = (unsigned *) AT91SAM9G45_BASE_UDPHS; + udc->fifo = (unsigned *) AT91SAM9G45_UDPHS_FIFO; + + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + + debug("USBA : Probing USB controller\n"); + writel(readl(&pmc->pcer) | (1 << AT91SAM9G45_ID_UDPHS), &pmc->pcer); + writel(readl(&pmc->uckr) | + AT91_PMC_UPLLEN | AT91_PMC_BIASEN, &pmc->uckr); + + debug("USBA : Enable controller\n"); + usba_writel(udc, CTRL, USBA_ENABLE_MASK); + usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + + init_requests(); + udc_reinit(udc); + + the_udc.gadget.ep0 = &udc->ep[0].ep; + + udc->ep[0].ep_regs = udc->regs + USBA_EPT_BASE(0); + udc->ep[0].dma_regs = udc->regs + USBA_DMA_BASE(0); + udc->ep[0].fifo = udc->fifo + USBA_FIFO_BASE(0); + udc->ep[0].ep.ops = &usba_ep_ops; + udc->ep[0].ep.name = the_udc.ep[0].ep.name; + udc->ep[0].ep.maxpacket = 64; + udc->ep[0].udc = &the_udc; + udc->ep[0].fifo_size = 64; + udc->ep[0].nr_banks = the_udc.ep[0].nr_banks; + udc->ep[0].index = 0; + udc->ep[0].is_pingpong = 0; + udc->ep[0].can_isoc = 0; + udc->ep[0].can_dma = 0; + + for (i = 1; i < USBA_NR_ENDPOINTS; i++) { + struct usba_ep *ep = &udc->ep[i]; + + ep->ep_regs = udc->regs + USBA_EPT_BASE(i); + ep->dma_regs = udc->regs + USBA_DMA_BASE(i); + ep->fifo = udc->fifo + USBA_FIFO_BASE(i); + ep->ep.ops = &usba_ep_ops; + ep->ep.name = the_udc.ep[i].ep.name; + ep->ep.maxpacket = 1024; + ep->udc = &the_udc; + ep->fifo_size = 1024; + ep->index = i; + if ((i == 1) || (i == 2)) { + ep->nr_banks = 1; + ep->is_pingpong = 0; + } else { + ep->nr_banks = 1; + ep->is_pingpong = 0; + } + ep->can_isoc = 0; + ep->can_dma = 0; + } + + /* This code disables DMA : see g45 spec */ + regvalue = usba_readl(udc, CTRL); + usba_writel(udc, CTRL, (regvalue & ~AT91C_UDPHS_EN_UDPHS)); + usba_writel(udc, CTRL, (regvalue | AT91C_UDPHS_EN_UDPHS)); + + regvalue = usba_readl(udc, CTRL); + debug("UDPHS_CTRL after reset 0x%lx\n", regvalue); + for (i = 1; i <= (((AT91SAM9G45_BASE_UDPHS + UDPHS_IPFEATURES) & + AT91C_UDPHS_DMA_CHANNEL_NBR) >> 4); i++) { + debug("UDPHS ep %d disable DMA\n", i); + /* DMA stop channel command */ + __raw_writel(0x00, + AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMACONTROL); + /* Disable endpoint */ + regvalue = readl(AT91SAM9G45_BASE_UDPHS + + 0x100 + (0x20*i) + UDPHS_EPTCTLDIS); + __raw_writel((regvalue | 0XFFFFFFFF), + AT91SAM9G45_BASE_UDPHS + + 0x100 + (0x20*i) + UDPHS_EPTCTLDIS); + /* Reset endpoint config */ + __raw_writel(0x00, + AT91SAM9G45_BASE_UDPHS + + 0x100 + (0x20*i) + UDPHS_EPTCFG); + /* Reset DMA channel (Buff count and Control field) */ + __raw_writel(0x02, + AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMACONTROL); + /* DMA stop channel command */ + __raw_writel(0x00, + AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMACONTROL); + /* Clear DMA channel status (read register to clear it) */ + regvalue = readl(AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMASTATUS); + __raw_writel(regvalue, + AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMASTATUS); + + regvalue = readl(AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMACONTROL); + regvalue = readl(AT91SAM9G45_BASE_UDPHS + + 0x100 + (0x20*i) + UDPHS_EPTCTLDIS); + regvalue = readl(AT91SAM9G45_BASE_UDPHS + + 0x100 + (0x20*i) + UDPHS_EPTCFG); + regvalue = readl(AT91SAM9G45_BASE_UDPHS + + (UDPHS_DMA + 0x10*i) + UDPHS_DMASTATUS); + regvalue = readl(AT91SAM9G45_BASE_UDPHS + 0xF0); + } + debug("USBA : Probing finished\n"); + return 0; +} + +MODULE_DESCRIPTION("Atmel usba udc driver"); +MODULE_AUTHOR("Marcel Janssen"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel_usba_udc"); + diff --git a/include/usb/atmel_usba_udc.h b/include/usb/atmel_usba_udc.h new file mode 100644 index 0000000..b165003 --- /dev/null +++ b/include/usb/atmel_usba_udc.h @@ -0,0 +1,398 @@ +/* + * Driver for the Atmel USBA high speed USB device controller + * + * Copyright (C) 2005-2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __LINUX_USB_GADGET_USBA_UDC_H__ +#define __LINUX_USB_GADGET_USBA_UDC_H__ + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/list.h> + +#define __iomem + +/* USB register offsets */ +#define USBA_CTRL 0x0000 +#define USBA_FNUM 0x0004 +#define USBA_INT_ENB 0x0010 +#define USBA_INT_STA 0x0014 +#define USBA_INT_CLR 0x0018 +#define USBA_EPT_RST 0x001c +#define USBA_TST 0x00e0 + +/* USB endpoint register offsets */ +#define USBA_EPT_CFG 0x0000 +#define USBA_EPT_CTL_ENB 0x0004 +#define USBA_EPT_CTL_DIS 0x0008 +#define USBA_EPT_CTL 0x000c +#define USBA_EPT_SET_STA 0x0014 +#define USBA_EPT_CLR_STA 0x0018 +#define USBA_EPT_STA 0x001c + +/* USB DMA register offsets */ +#define USBA_DMA_NXT_DSC 0x0000 +#define USBA_DMA_ADDRESS 0x0004 +#define USBA_DMA_CONTROL 0x0008 +#define USBA_DMA_STATUS 0x000c + +/* Bitfields in CTRL */ +#define USBA_DEV_ADDR_OFFSET 0 +#define USBA_DEV_ADDR_SIZE 7 +#define USBA_FADDR_EN (1 << 7) +#define USBA_EN_USBA (1 << 8) +#define USBA_DETACH (1 << 9) +#define USBA_REMOTE_WAKE_UP (1 << 10) +#define USBA_PULLD_DIS (1 << 11) + +#if defined(CONFIG_AVR32) +#define USBA_ENABLE_MASK USBA_EN_USBA +#define USBA_DISABLE_MASK 0 +#elif defined(CONFIG_ARCH_AT91) +#define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS) +#define USBA_DISABLE_MASK USBA_DETACH +#endif /* CONFIG_ARCH_AT91 */ + +/* Bitfields in FNUM */ +#define USBA_MICRO_FRAME_NUM_OFFSET 0 +#define USBA_MICRO_FRAME_NUM_SIZE 3 +#define USBA_FRAME_NUMBER_OFFSET 3 +#define USBA_FRAME_NUMBER_SIZE 11 +#define USBA_FRAME_NUM_ERROR (1 << 31) + +/* Bitfields in INT_ENB/INT_STA/INT_CLR */ +#define USBA_HIGH_SPEED (1 << 0) +#define USBA_DET_SUSPEND (1 << 1) +#define USBA_MICRO_SOF (1 << 2) +#define USBA_SOF (1 << 3) +#define USBA_END_OF_RESET (1 << 4) +#define USBA_WAKE_UP (1 << 5) +#define USBA_END_OF_RESUME (1 << 6) +#define USBA_UPSTREAM_RESUME (1 << 7) +#define USBA_EPT_INT_OFFSET 8 +#define USBA_EPT_INT_SIZE 16 +#define USBA_DMA_INT_OFFSET 24 +#define USBA_DMA_INT_SIZE 8 + +/* Bitfields in EPT_RST */ +#define USBA_RST_OFFSET 0 +#define USBA_RST_SIZE 16 + +/* Bitfields in USBA_TST */ +#define USBA_SPEED_CFG_OFFSET 0 +#define USBA_SPEED_CFG_SIZE 2 +#define USBA_TST_J_MODE (1 << 2) +#define USBA_TST_K_MODE (1 << 3) +#define USBA_TST_PKT_MODE (1 << 4) +#define USBA_OPMODE2 (1 << 5) + +/* Bitfields in EPT_CFG */ +#define USBA_EPT_SIZE_OFFSET 0 +#define USBA_EPT_SIZE_SIZE 3 +#define USBA_EPT_DIR_IN (1 << 3) +#define USBA_EPT_TYPE_OFFSET 4 +#define USBA_EPT_TYPE_SIZE 2 +#define USBA_BK_NUMBER_OFFSET 6 +#define USBA_BK_NUMBER_SIZE 2 +#define USBA_NB_TRANS_OFFSET 8 +#define USBA_NB_TRANS_SIZE 2 +#define USBA_EPT_MAPPED (1 << 31) + +/* Bitfields in EPT_CTL/EPT_CTL_ENB/EPT_CTL_DIS */ +#define USBA_EPT_ENABLE (1 << 0) +#define USBA_AUTO_VALID (1 << 1) +#define USBA_INTDIS_DMA (1 << 3) +#define USBA_NYET_DIS (1 << 4) +#define USBA_DATAX_RX (1 << 6) +#define USBA_MDATA_RX (1 << 7) +/* Bits 8-15 and 31 enable interrupts for respective bits in EPT_STA */ +#define USBA_BUSY_BANK_IE (1 << 18) + +/* Bitfields in EPT_SET_STA/EPT_CLR_STA/EPT_STA */ +#define USBA_FORCE_STALL (1 << 5) +#define USBA_TOGGLE_CLR (1 << 6) +#define USBA_TOGGLE_SEQ_OFFSET 6 +#define USBA_TOGGLE_SEQ_SIZE 2 +#define USBA_ERR_OVFLW (1 << 8) +#define USBA_RX_BK_RDY (1 << 9) +#define USBA_KILL_BANK (1 << 9) +#define USBA_TX_COMPLETE (1 << 10) +#define USBA_TX_PK_RDY (1 << 11) +#define USBA_ISO_ERR_TRANS (1 << 11) +#define USBA_RX_SETUP (1 << 12) +#define USBA_ISO_ERR_FLOW (1 << 12) +#define USBA_STALL_SENT (1 << 13) +#define USBA_ISO_ERR_CRC (1 << 13) +#define USBA_ISO_ERR_NBTRANS (1 << 13) +#define USBA_NAK_IN (1 << 14) +#define USBA_ISO_ERR_FLUSH (1 << 14) +#define USBA_NAK_OUT (1 << 15) +#define USBA_CURRENT_BANK_OFFSET 16 +#define USBA_CURRENT_BANK_SIZE 2 +#define USBA_BUSY_BANKS_OFFSET 18 +#define USBA_BUSY_BANKS_SIZE 2 +#define USBA_BYTE_COUNT_OFFSET 20 +#define USBA_BYTE_COUNT_SIZE 11 +#define USBA_SHORT_PACKET (1 << 31) + +/* Bitfields in DMA_CONTROL */ +#define USBA_DMA_CH_EN (1 << 0) +#define USBA_DMA_LINK (1 << 1) +#define USBA_DMA_END_TR_EN (1 << 2) +#define USBA_DMA_END_BUF_EN (1 << 3) +#define USBA_DMA_END_TR_IE (1 << 4) +#define USBA_DMA_END_BUF_IE (1 << 5) +#define USBA_DMA_DESC_LOAD_IE (1 << 6) +#define USBA_DMA_BURST_LOCK (1 << 7) +#define USBA_DMA_BUF_LEN_OFFSET 16 +#define USBA_DMA_BUF_LEN_SIZE 16 + +/* Bitfields in DMA_STATUS */ +#define USBA_DMA_CH_ACTIVE (1 << 1) +#define USBA_DMA_END_TR_ST (1 << 4) +#define USBA_DMA_END_BUF_ST (1 << 5) +#define USBA_DMA_DESC_LOAD_ST (1 << 6) + +/* Constants for SPEED_CFG */ +#define USBA_SPEED_CFG_NORMAL 0 +#define USBA_SPEED_CFG_FORCE_HIGH 2 +#define USBA_SPEED_CFG_FORCE_FULL 3 + +/* Constants for EPT_SIZE */ +#define USBA_EPT_SIZE_8 0 +#define USBA_EPT_SIZE_16 1 +#define USBA_EPT_SIZE_32 2 +#define USBA_EPT_SIZE_64 3 +#define USBA_EPT_SIZE_128 4 +#define USBA_EPT_SIZE_256 5 +#define USBA_EPT_SIZE_512 6 +#define USBA_EPT_SIZE_1024 7 + +/* Constants for EPT_TYPE */ +#define USBA_EPT_TYPE_CONTROL 0 +#define USBA_EPT_TYPE_ISO 1 +#define USBA_EPT_TYPE_BULK 2 +#define USBA_EPT_TYPE_INT 3 + +/* Constants for BK_NUMBER */ +#define USBA_BK_NUMBER_ZERO 0 +#define USBA_BK_NUMBER_ONE 1 +#define USBA_BK_NUMBER_DOUBLE 2 +#define USBA_BK_NUMBER_TRIPLE 3 + +/* Bit manipulation macros */ +#define USBA_BF(name, value) \ + (((value) & ((1 << USBA_##name##_SIZE) - 1)) \ + << USBA_##name##_OFFSET) +#define USBA_BFEXT(name, value) \ + (((value) >> USBA_##name##_OFFSET) \ + & ((1 << USBA_##name##_SIZE) - 1)) +#define USBA_BFINS(name, value, old) \ + (((old) & ~(((1 << USBA_##name##_SIZE) - 1) \ + << USBA_##name##_OFFSET)) \ + | USBA_BF(name, value)) + +/* Register access macros */ +#define usba_readl(udc, reg) \ + __raw_readl((udc)->regs + USBA_##reg) +#define usba_writel(udc, reg, value) \ + __raw_writel((value), (udc)->regs + USBA_##reg) +#define usba_ep_readl(ep, reg) \ + __raw_readl((ep)->ep_regs + USBA_EPT_##reg) +#define usba_ep_writel(ep, reg, value) \ + __raw_writel((value), (ep)->ep_regs + USBA_EPT_##reg) +#define usba_dma_readl(ep, reg) \ + __raw_readl((ep)->dma_regs + USBA_DMA_##reg) +#define usba_dma_writel(ep, reg, value) \ + __raw_writel((value), (ep)->dma_regs + USBA_DMA_##reg) + +/* Calculate base address for a given endpoint or DMA controller */ +#define USBA_EPT_BASE(x) (0x100 + (x) * 0x20) +#define USBA_DMA_BASE(x) (0x300 + (x) * 0x10) +#define USBA_FIFO_BASE(x) ((x) << 16) + +/* Synth parameters */ +#define USBA_NR_ENDPOINTS 7 + +#define EP0_FIFO_SIZE 64 +#define EP0_EPT_SIZE USBA_EPT_SIZE_64 +#define EP0_NR_BANKS 1 + +/* + * REVISIT: Try to eliminate this value. Can we rely on req->mapped to + * provide this information? + */ +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#define FIFO_IOMEM_ID 0 +#define CTRL_IOMEM_ID 1 + +#define DBG_ERR 0x0001 /* report all error returns */ +#define DBG_HW 0x0002 /* debug hardware initialization */ +#define DBG_GADGET 0x0004 /* calls to/from gadget driver */ +#define DBG_INT 0x0008 /* interrupts */ +#define DBG_BUS 0x0010 /* report changes in bus state */ +#define DBG_QUEUE 0x0020 /* debug request queue processing */ +#define DBG_FIFO 0x0040 /* debug FIFO contents */ +#define DBG_DMA 0x0080 /* debug DMA handling */ +#define DBG_REQ 0x0100 /* print out queued request length */ +#define DBG_ALL 0xffff +#define DBG_NONE 0x0000 + +#define DEBUG_LEVEL (DBG_ERR) + +enum usba_ctrl_state { + WAIT_FOR_SETUP, + DATA_STAGE_IN, + DATA_STAGE_OUT, + STATUS_STAGE_IN, + STATUS_STAGE_OUT, + STATUS_STAGE_ADDR, + STATUS_STAGE_TEST, +}; + +struct usba_ep { + int state; + struct usb_ep ep; + struct list_head queue; + struct usba_udc *udc; + void __iomem *fifo; + void __iomem *dma_regs; + void __iomem *ep_regs; + + unsigned maxpacket:16; + u8 int_mask; + unsigned is_pingpong:1; + u8 index; + u16 fifo_size; + u8 nr_banks; + unsigned stopped:1; + unsigned int can_dma:1; + unsigned int can_isoc:1; + unsigned is_in:1; + unsigned is_isoc:1; + unsigned fifo_bank:1; + + const struct usb_endpoint_descriptor *desc; +}; + +struct usba_request { + struct usb_request req; + struct list_head queue; + unsigned int last_transaction:1; + unsigned int submitted:1; + unsigned in_use; +}; + +/* USB Device */ +struct usba_udc_data { + u8 vbus_pin; /* high == host powering us */ + u8 pullup_pin; /* high == D+ pulled up */ +}; + +struct platform_data { + struct usba_udc_data board; + unsigned udc_clk; +}; + +/* + * driver is non-SMP, and just blocks IRQs whenever it needs + * access protection for chip registers or driver state + */ + +struct usba_udc { + void __iomem *regs; + void __iomem *fifo; + struct usb_gadget gadget; + struct usba_ep ep[USBA_NR_ENDPOINTS]; + struct usb_gadget_driver *driver; + struct usba_udc_data board; + unsigned vbus:1; + int irq; + int vbus_pin; + struct clk *pclk; + struct clk *hclk; + u16 devstatus; + int vbus_prev; +}; + +static inline struct usba_ep *to_usba_ep(struct usb_ep *ep) +{ + return container_of(ep, struct usba_ep, ep); +} + +static inline struct usba_request *to_usba_req(struct usb_request *req) +{ + return container_of(req, struct usba_request, req); +} + +static inline struct usba_udc *to_usba_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct usba_udc, gadget); +} + +int usba_udc_probe(struct platform_data *pdata); + +#define ep_is_control(ep) ((ep)->index == 0) +#define ep_is_idle(ep) ((ep)->state == EP_STATE_IDLE) + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +#define DBG (stuff...) printf("udc: " stuff) +#else +#define DBG (stuff...) do {} while (0) +#endif + +#ifdef VERBOSE +#define VDBG(x...) printf(x) +#else +#define VDBG(stuff...) do {} while (0) +#endif + +#ifdef PACKET_TRACE +# define PACKET VDBG +#else +# define PACKET(stuff...) do {} while (0) +#endif + +#define ERR(stuff...) printf("ERR udc: " stuff) +#define WARN(stuff...) printf("WARNING udc: " stuff) +#define INFO(stuff...) printf("INFO udc: " stuff) + +/* following defines come form At91Bootstrap. + * They're needed for disabling DMA for the USB device controller + */ +#define UDPHS_CTRL (0) +#define AT91C_UDPHS_EN_UDPHS (0x1 << 8) +#define UDPHS_IPFEATURES (248) +#define AT91C_UDPHS_DMA_CHANNEL_NBR (0x7 << 4) +#define UDPHS_DMACONTROL (8) +#define UDPHS_EPT (256) +#define AT91C_BASE_UDPHS_EPT_0 (0xFFF78100) +#define UDPHS_EPTCTLDIS (8) +#define UDPHS_EPTCFG (0) +#define UDPHS_EPTCTL (12) +#define UDPHS_DMA (768) +#define AT91C_BASE_UDPHS_DMA_1 (0xFFF78310) +#define AT91C_BASE_UDPHS_DMA_1 (0xFFF78310) +#define AT91C_BASE_UDPHS_DMA_2 (0xFFF78320) +#define AT91C_BASE_UDPHS_DMA_3 (0xFFF78330) +#define AT91C_BASE_UDPHS_DMA_4 (0xFFF78340) +#define AT91C_BASE_UDPHS_DMA_5 (0xFFF78350) +#define AT91C_BASE_UDPHS_DMA_6 (0xFFF78360) +#define UDPHS_DMASTATUS (12) +#define AT91C_BASE_UDPHS_EPTFIFO (0x00600000) +#define AT91C_UDPHS_EPTFIFO_READEPT3 (0x00630000) +#define AT91C_UDPHS_EPTFIFO_READEPT5 (0x00650000) +#define AT91C_UDPHS_EPTFIFO_READEPT1 (0x00610000) +#define AT91C_UDPHS_EPTFIFO_READEPT0 (0x00600000) +#define AT91C_UDPHS_EPTFIFO_READEPT6 (0x00660000) +#define AT91C_UDPHS_EPTFIFO_READEPT2 (0x00620000) +#define AT91C_UDPHS_EPTFIFO_READEPT4 (0x00640000) + +#endif /* __LINUX_USB_GADGET_USBA_UDC_H */ -- 1.7.3.4 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot