📄 atmel_usba_udc.c
字号:
/* * 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. */#include <linux/clk.h>#include <linux/module.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/io.h>#include <linux/device.h>#include <linux/dma-mapping.h>#include <linux/list.h>#include <linux/platform_device.h>#include <linux/usb/ch9.h>#include <linux/usb/gadget.h>#include <linux/delay.h>#include <asm/gpio.h>#include <asm/arch/board.h>#include "atmel_usba_udc.h"static struct usba_udc the_udc;#ifdef CONFIG_USB_GADGET_DEBUG_FS#include <linux/debugfs.h>#include <linux/uaccess.h>static int queue_dbg_open(struct inode *inode, struct file *file){ struct usba_ep *ep = inode->i_private; struct usba_request *req, *req_copy; struct list_head *queue_data; queue_data = kmalloc(sizeof(*queue_data), GFP_KERNEL); if (!queue_data) return -ENOMEM; INIT_LIST_HEAD(queue_data); spin_lock_irq(&ep->udc->lock); list_for_each_entry(req, &ep->queue, queue) { req_copy = kmalloc(sizeof(*req_copy), GFP_ATOMIC); if (!req_copy) goto fail; memcpy(req_copy, req, sizeof(*req_copy)); list_add_tail(&req_copy->queue, queue_data); } spin_unlock_irq(&ep->udc->lock); file->private_data = queue_data; return 0;fail: spin_unlock_irq(&ep->udc->lock); list_for_each_entry_safe(req, req_copy, queue_data, queue) { list_del(&req->queue); kfree(req); } kfree(queue_data); return -ENOMEM;}/* * bbbbbbbb llllllll IZS sssss nnnn FDL\n\0 * * b: buffer address * l: buffer length * I/i: interrupt/no interrupt * Z/z: zero/no zero * S/s: short ok/short not ok * s: status * n: nr_packets * F/f: submitted/not submitted to FIFO * D/d: using/not using DMA * L/l: last transaction/not last transaction */static ssize_t queue_dbg_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos){ struct list_head *queue = file->private_data; struct usba_request *req, *tmp_req; size_t len, remaining, actual = 0; char tmpbuf[38]; if (!access_ok(VERIFY_WRITE, buf, nbytes)) return -EFAULT; mutex_lock(&file->f_dentry->d_inode->i_mutex); list_for_each_entry_safe(req, tmp_req, queue, queue) { len = snprintf(tmpbuf, sizeof(tmpbuf), "%8p %08x %c%c%c %5d %c%c%c\n", req->req.buf, req->req.length, req->req.no_interrupt ? 'i' : 'I', req->req.zero ? 'Z' : 'z', req->req.short_not_ok ? 's' : 'S', req->req.status, req->submitted ? 'F' : 'f', req->using_dma ? 'D' : 'd', req->last_transaction ? 'L' : 'l'); len = min(len, sizeof(tmpbuf)); if (len > nbytes) break; list_del(&req->queue); kfree(req); remaining = __copy_to_user(buf, tmpbuf, len); actual += len - remaining; if (remaining) break; nbytes -= len; buf += len; } mutex_unlock(&file->f_dentry->d_inode->i_mutex); return actual;}static int queue_dbg_release(struct inode *inode, struct file *file){ struct list_head *queue_data = file->private_data; struct usba_request *req, *tmp_req; list_for_each_entry_safe(req, tmp_req, queue_data, queue) { list_del(&req->queue); kfree(req); } kfree(queue_data); return 0;}static int regs_dbg_open(struct inode *inode, struct file *file){ struct usba_udc *udc; unsigned int i; u32 *data; int ret = -ENOMEM; mutex_lock(&inode->i_mutex); udc = inode->i_private; data = kmalloc(inode->i_size, GFP_KERNEL); if (!data) goto out; spin_lock_irq(&udc->lock); for (i = 0; i < inode->i_size / 4; i++) data[i] = __raw_readl(udc->regs + i * 4); spin_unlock_irq(&udc->lock); file->private_data = data; ret = 0;out: mutex_unlock(&inode->i_mutex); return ret;}static ssize_t regs_dbg_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos){ struct inode *inode = file->f_dentry->d_inode; int ret; mutex_lock(&inode->i_mutex); ret = simple_read_from_buffer(buf, nbytes, ppos, file->private_data, file->f_dentry->d_inode->i_size); mutex_unlock(&inode->i_mutex); return ret;}static int regs_dbg_release(struct inode *inode, struct file *file){ kfree(file->private_data); return 0;}const struct file_operations queue_dbg_fops = { .owner = THIS_MODULE, .open = queue_dbg_open, .llseek = no_llseek, .read = queue_dbg_read, .release = queue_dbg_release,};const struct file_operations regs_dbg_fops = { .owner = THIS_MODULE, .open = regs_dbg_open, .llseek = generic_file_llseek, .read = regs_dbg_read, .release = regs_dbg_release,};static void usba_ep_init_debugfs(struct usba_udc *udc, struct usba_ep *ep){ struct dentry *ep_root; ep_root = debugfs_create_dir(ep->ep.name, udc->debugfs_root); if (!ep_root) goto err_root; ep->debugfs_dir = ep_root; ep->debugfs_queue = debugfs_create_file("queue", 0400, ep_root, ep, &queue_dbg_fops); if (!ep->debugfs_queue) goto err_queue; if (ep->can_dma) { ep->debugfs_dma_status = debugfs_create_u32("dma_status", 0400, ep_root, &ep->last_dma_status); if (!ep->debugfs_dma_status) goto err_dma_status; } if (ep_is_control(ep)) { ep->debugfs_state = debugfs_create_u32("state", 0400, ep_root, &ep->state); if (!ep->debugfs_state) goto err_state; } return;err_state: if (ep->can_dma) debugfs_remove(ep->debugfs_dma_status);err_dma_status: debugfs_remove(ep->debugfs_queue);err_queue: debugfs_remove(ep_root);err_root: dev_err(&ep->udc->pdev->dev, "failed to create debugfs directory for %s\n", ep->ep.name);}static void usba_ep_cleanup_debugfs(struct usba_ep *ep){ debugfs_remove(ep->debugfs_queue); debugfs_remove(ep->debugfs_dma_status); debugfs_remove(ep->debugfs_state); debugfs_remove(ep->debugfs_dir); ep->debugfs_dma_status = NULL; ep->debugfs_dir = NULL;}static void usba_init_debugfs(struct usba_udc *udc){ struct dentry *root, *regs; struct resource *regs_resource; root = debugfs_create_dir(udc->gadget.name, NULL); if (IS_ERR(root) || !root) goto err_root; udc->debugfs_root = root; regs = debugfs_create_file("regs", 0400, root, udc, ®s_dbg_fops); if (!regs) goto err_regs; regs_resource = platform_get_resource(udc->pdev, IORESOURCE_MEM, CTRL_IOMEM_ID); regs->d_inode->i_size = regs_resource->end - regs_resource->start + 1; udc->debugfs_regs = regs; usba_ep_init_debugfs(udc, to_usba_ep(udc->gadget.ep0)); return;err_regs: debugfs_remove(root);err_root: udc->debugfs_root = NULL; dev_err(&udc->pdev->dev, "debugfs is not available\n");}static void usba_cleanup_debugfs(struct usba_udc *udc){ usba_ep_cleanup_debugfs(to_usba_ep(udc->gadget.ep0)); debugfs_remove(udc->debugfs_regs); debugfs_remove(udc->debugfs_root); udc->debugfs_regs = NULL; udc->debugfs_root = NULL;}#elsestatic inline void usba_ep_init_debugfs(struct usba_udc *udc, struct usba_ep *ep){}static inline void usba_ep_cleanup_debugfs(struct usba_ep *ep){}static inline void usba_init_debugfs(struct usba_udc *udc){}static inline void usba_cleanup_debugfs(struct usba_udc *udc){}#endifstatic int vbus_is_present(struct usba_udc *udc){ if (udc->vbus_pin != -1) return gpio_get_value(udc->vbus_pin); /* No Vbus detection: Assume always present */ return 1;}static void copy_to_fifo(void __iomem *fifo, const void *buf, int len){ unsigned long tmp; DBG(DBG_FIFO, "copy to FIFO (len %d):\n", len); for (; len > 0; len -= 4, buf += 4, fifo += 4) { tmp = *(unsigned long *)buf; if (len >= 4) { DBG(DBG_FIFO, " -> %08lx\n", tmp); __raw_writel(tmp, fifo); } else { do { DBG(DBG_FIFO, " -> %02lx\n", tmp >> 24); __raw_writeb(tmp >> 24, fifo); fifo++; tmp <<= 8; } while (--len); break; } }}static void copy_from_fifo(void *buf, void __iomem *fifo, int len){ union { unsigned long *w; unsigned char *b; } p; unsigned long tmp; DBG(DBG_FIFO, "copy from FIFO (len %d):\n", len); for (p.w = buf; len > 0; len -= 4, p.w++, fifo += 4) { if (len >= 4) { tmp = __raw_readl(fifo); *p.w = tmp; DBG(DBG_FIFO, " -> %08lx\n", tmp); } else { do { tmp = __raw_readb(fifo); *p.b = tmp; DBG(DBG_FIFO, " -> %02lx\n", tmp); fifo++, p.b++; } while (--len); } }}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; DBG(DBG_QUEUE, "%s: submit_transaction, req %p (length %d)%s\n", ep->ep.name, req, transaction_len, req->last_transaction ? ", done" : ""); copy_to_fifo(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){ DBG(DBG_QUEUE, "%s: submit_request: req %p (length %d)\n", ep->ep.name, req, req->req.length); req->req.actual = 0; req->submitted = 1; if (req->using_dma) { if (req->req.length == 0) { usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); return; } if (req->req.zero) usba_ep_writel(ep, CTL_ENB, USBA_SHORT_PACKET); else usba_ep_writel(ep, CTL_DIS, USBA_SHORT_PACKET); usba_dma_writel(ep, ADDRESS, req->req.dma); usba_dma_writel(ep, CONTROL, req->ctrl); } else { 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);}static void send_status(struct usba_udc *udc, struct usba_ep *ep){ 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); DBG(DBG_QUEUE, "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; } copy_from_fifo(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) { DBG(DBG_QUEUE, "%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); spin_unlock(&udc->lock); req->req.complete(&ep->ep, &req->req); spin_lock(&udc->lock); } status = usba_ep_readl(ep, STA); nr_busy = USBA_BFEXT(BUSY_BANKS, status); if (is_complete && ep_is_control(ep)) { send_status(udc, ep); break; } }}static voidrequest_complete(struct usba_ep *ep, struct usba_request *req, int status){ struct usba_udc *udc = ep->udc; WARN_ON(!list_empty(&req->queue)); if (req->req.status == -EINPROGRESS) req->req.status = status; if (req->mapped) { dma_unmap_single( &udc->pdev->dev, req->req.dma, req->req.length, ep->is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); req->req.dma = DMA_ADDR_INVALID; req->mapped = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -