📄 net2280.c
字号:
/*
* Driver for the PLX NET2280 USB device controller.
* Specs and errata are available from <http://www.plxtech.com>.
*
* PLX Technology Inc. (formerly NetChip Technology) supported the
* development of this driver.
*
*
* CODE STATUS HIGHLIGHTS
*
* This driver should work well with most "gadget" drivers, including
* the File Storage, Serial, and Ethernet/RNDIS gadget drivers
* as well as Gadget Zero and Gadgetfs.
*
* DMA is enabled by default. Drivers using transfer queues might use
* DMA chaining to remove IRQ latencies between transfers. (Except when
* short OUT transfers happen.) Drivers can use the req->no_interrupt
* hint to completely eliminate some IRQs, if a later IRQ is guaranteed
* and DMA chaining is enabled.
*
* Note that almost all the errata workarounds here are only needed for
* rev1 chips. Rev1a silicon (0110) fixes almost all of them.
*/
/*
* Copyright (C) 2003 David Brownell
* Copyright (C) 2003-2005 PLX Technology, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#undef DEBUG /* messages on error and most fault paths */
#undef VERBOSE /* extra debug messages (success too) */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/usb_ch9.h>
#include <linux/usb_gadget.h>
#include <asm/byteorder.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/unaligned.h>
#define DRIVER_DESC "PLX NET2280 USB Peripheral Controller"
#define DRIVER_VERSION "2005 Feb 03"
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
#define EP_DONTUSE 13 /* nonzero */
#define USE_RDK_LEDS /* GPIO pins control three LEDs */
static const char driver_name [] = "net2280";
static const char driver_desc [] = DRIVER_DESC;
static const char ep0name [] = "ep0";
static const char *ep_name [] = {
ep0name,
"ep-a", "ep-b", "ep-c", "ep-d",
"ep-e", "ep-f",
};
/* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO)
* use_dma_chaining -- dma descriptor queueing gives even more irq reduction
*
* The net2280 DMA engines are not tightly integrated with their FIFOs;
* not all cases are (yet) handled well in this driver or the silicon.
* Some gadget drivers work better with the dma support here than others.
* These two parameters let you use PIO or more aggressive DMA.
*/
static int use_dma = 1;
static int use_dma_chaining = 0;
/* "modprobe net2280 use_dma=n" etc */
module_param (use_dma, bool, S_IRUGO);
module_param (use_dma_chaining, bool, S_IRUGO);
/* mode 0 == ep-{a,b,c,d} 1K fifo each
* mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable
* mode 2 == ep-a 2K fifo, ep-{b,c} 1K each, ep-d unavailable
*/
static ushort fifo_mode = 0;
/* "modprobe net2280 fifo_mode=1" etc */
module_param (fifo_mode, ushort, 0644);
/* enable_suspend -- When enabled, the driver will respond to
* USB suspend requests by powering down the NET2280. Otherwise,
* USB suspend requests will be ignored. This is acceptible for
* self-powered devices, and helps avoid some quirks.
*/
static int enable_suspend = 0;
/* "modprobe net2280 enable_suspend=1" etc */
module_param (enable_suspend, bool, S_IRUGO);
#define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out")
#if defined(CONFIG_USB_GADGET_DEBUG_FILES) || defined (DEBUG)
static char *type_string (u8 bmAttributes)
{
switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) {
case USB_ENDPOINT_XFER_BULK: return "bulk";
case USB_ENDPOINT_XFER_ISOC: return "iso";
case USB_ENDPOINT_XFER_INT: return "intr";
};
return "control";
}
#endif
#include "net2280.h"
#define valid_bit __constant_cpu_to_le32 (1 << VALID_BIT)
#define dma_done_ie __constant_cpu_to_le32 (1 << DMA_DONE_INTERRUPT_ENABLE)
/*-------------------------------------------------------------------------*/
static int
net2280_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
{
struct net2280 *dev;
struct net2280_ep *ep;
u32 max, tmp;
unsigned long flags;
ep = container_of (_ep, struct net2280_ep, ep);
if (!_ep || !desc || ep->desc || _ep->name == ep0name
|| desc->bDescriptorType != USB_DT_ENDPOINT)
return -EINVAL;
dev = ep->dev;
if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN;
/* erratum 0119 workaround ties up an endpoint number */
if ((desc->bEndpointAddress & 0x0f) == EP_DONTUSE)
return -EDOM;
/* sanity check ep-e/ep-f since their fifos are small */
max = le16_to_cpu (desc->wMaxPacketSize) & 0x1fff;
if (ep->num > 4 && max > 64)
return -ERANGE;
spin_lock_irqsave (&dev->lock, flags);
_ep->maxpacket = max & 0x7ff;
ep->desc = desc;
/* ep_reset() has already been called */
ep->stopped = 0;
ep->out_overflow = 0;
/* set speed-dependent max packet; may kick in high bandwidth */
set_idx_reg (dev->regs, REG_EP_MAXPKT (dev, ep->num), max);
/* FIFO lines can't go to different packets. PIO is ok, so
* use it instead of troublesome (non-bulk) multi-packet DMA.
*/
if (ep->dma && (max % 4) != 0 && use_dma_chaining) {
DEBUG (ep->dev, "%s, no dma for maxpacket %d\n",
ep->ep.name, ep->ep.maxpacket);
ep->dma = NULL;
}
/* set type, direction, address; reset fifo counters */
writel ((1 << FIFO_FLUSH), &ep->regs->ep_stat);
tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK);
if (tmp == USB_ENDPOINT_XFER_INT) {
/* erratum 0105 workaround prevents hs NYET */
if (dev->chiprev == 0100
&& dev->gadget.speed == USB_SPEED_HIGH
&& !(desc->bEndpointAddress & USB_DIR_IN))
writel ((1 << CLEAR_NAK_OUT_PACKETS_MODE),
&ep->regs->ep_rsp);
} else if (tmp == USB_ENDPOINT_XFER_BULK) {
/* catch some particularly blatant driver bugs */
if ((dev->gadget.speed == USB_SPEED_HIGH
&& max != 512)
|| (dev->gadget.speed == USB_SPEED_FULL
&& max > 64)) {
spin_unlock_irqrestore (&dev->lock, flags);
return -ERANGE;
}
}
ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC) ? 1 : 0;
tmp <<= ENDPOINT_TYPE;
tmp |= desc->bEndpointAddress;
tmp |= (4 << ENDPOINT_BYTE_COUNT); /* default full fifo lines */
tmp |= 1 << ENDPOINT_ENABLE;
wmb ();
/* for OUT transfers, block the rx fifo until a read is posted */
ep->is_in = (tmp & USB_DIR_IN) != 0;
if (!ep->is_in)
writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp);
writel (tmp, &ep->regs->ep_cfg);
/* enable irqs */
if (!ep->dma) { /* pio, per-packet */
tmp = (1 << ep->num) | readl (&dev->regs->pciirqenb0);
writel (tmp, &dev->regs->pciirqenb0);
tmp = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE)
| (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE)
| readl (&ep->regs->ep_irqenb);
writel (tmp, &ep->regs->ep_irqenb);
} else { /* dma, per-request */
tmp = (1 << (8 + ep->num)); /* completion */
tmp |= readl (&dev->regs->pciirqenb1);
writel (tmp, &dev->regs->pciirqenb1);
/* for short OUT transfers, dma completions can't
* advance the queue; do it pio-style, by hand.
* NOTE erratum 0112 workaround #2
*/
if ((desc->bEndpointAddress & USB_DIR_IN) == 0) {
tmp = (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE);
writel (tmp, &ep->regs->ep_irqenb);
tmp = (1 << ep->num) | readl (&dev->regs->pciirqenb0);
writel (tmp, &dev->regs->pciirqenb0);
}
}
tmp = desc->bEndpointAddress;
DEBUG (dev, "enabled %s (ep%d%s-%s) %s max %04x\n",
_ep->name, tmp & 0x0f, DIR_STRING (tmp),
type_string (desc->bmAttributes),
ep->dma ? "dma" : "pio", max);
/* pci writes may still be posted */
spin_unlock_irqrestore (&dev->lock, flags);
return 0;
}
static int handshake (u32 __iomem *ptr, u32 mask, u32 done, int usec)
{
u32 result;
do {
result = readl (ptr);
if (result == ~(u32)0) /* "device unplugged" */
return -ENODEV;
result &= mask;
if (result == done)
return 0;
udelay (1);
usec--;
} while (usec > 0);
return -ETIMEDOUT;
}
static struct usb_ep_ops net2280_ep_ops;
static void ep_reset (struct net2280_regs __iomem *regs, struct net2280_ep *ep)
{
u32 tmp;
ep->desc = NULL;
INIT_LIST_HEAD (&ep->queue);
ep->ep.maxpacket = ~0;
ep->ep.ops = &net2280_ep_ops;
/* disable the dma, irqs, endpoint... */
if (ep->dma) {
writel (0, &ep->dma->dmactl);
writel ( (1 << DMA_SCATTER_GATHER_DONE_INTERRUPT)
| (1 << DMA_TRANSACTION_DONE_INTERRUPT)
| (1 << DMA_ABORT)
, &ep->dma->dmastat);
tmp = readl (®s->pciirqenb0);
tmp &= ~(1 << ep->num);
writel (tmp, ®s->pciirqenb0);
} else {
tmp = readl (®s->pciirqenb1);
tmp &= ~(1 << (8 + ep->num)); /* completion */
writel (tmp, ®s->pciirqenb1);
}
writel (0, &ep->regs->ep_irqenb);
/* init to our chosen defaults, notably so that we NAK OUT
* packets until the driver queues a read (+note erratum 0112)
*/
tmp = (1 << SET_NAK_OUT_PACKETS_MODE)
| (1 << SET_NAK_OUT_PACKETS)
| (1 << CLEAR_EP_HIDE_STATUS_PHASE)
| (1 << CLEAR_INTERRUPT_MODE);
if (ep->num != 0) {
tmp |= (1 << CLEAR_ENDPOINT_TOGGLE)
| (1 << CLEAR_ENDPOINT_HALT);
}
writel (tmp, &ep->regs->ep_rsp);
/* scrub most status bits, and flush any fifo state */
writel ( (1 << TIMEOUT)
| (1 << USB_STALL_SENT)
| (1 << USB_IN_NAK_SENT)
| (1 << USB_IN_ACK_RCVD)
| (1 << USB_OUT_PING_NAK_SENT)
| (1 << USB_OUT_ACK_SENT)
| (1 << FIFO_OVERFLOW)
| (1 << FIFO_UNDERFLOW)
| (1 << FIFO_FLUSH)
| (1 << SHORT_PACKET_OUT_DONE_INTERRUPT)
| (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)
| (1 << DATA_PACKET_RECEIVED_INTERRUPT)
| (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)
| (1 << DATA_OUT_PING_TOKEN_INTERRUPT)
| (1 << DATA_IN_TOKEN_INTERRUPT)
, &ep->regs->ep_stat);
/* fifo size is handled separately */
}
static void nuke (struct net2280_ep *);
static int net2280_disable (struct usb_ep *_ep)
{
struct net2280_ep *ep;
unsigned long flags;
ep = container_of (_ep, struct net2280_ep, ep);
if (!_ep || !ep->desc || _ep->name == ep0name)
return -EINVAL;
spin_lock_irqsave (&ep->dev->lock, flags);
nuke (ep);
ep_reset (ep->dev->regs, ep);
VDEBUG (ep->dev, "disabled %s %s\n",
ep->dma ? "dma" : "pio", _ep->name);
/* synch memory views with the device */
(void) readl (&ep->regs->ep_cfg);
if (use_dma && !ep->dma && ep->num >= 1 && ep->num <= 4)
ep->dma = &ep->dev->dma [ep->num - 1];
spin_unlock_irqrestore (&ep->dev->lock, flags);
return 0;
}
/*-------------------------------------------------------------------------*/
static struct usb_request *
net2280_alloc_request (struct usb_ep *_ep, unsigned gfp_flags)
{
struct net2280_ep *ep;
struct net2280_request *req;
if (!_ep)
return NULL;
ep = container_of (_ep, struct net2280_ep, ep);
req = kmalloc (sizeof *req, gfp_flags);
if (!req)
return NULL;
memset (req, 0, sizeof *req);
req->req.dma = DMA_ADDR_INVALID;
INIT_LIST_HEAD (&req->queue);
/* this dma descriptor may be swapped with the previous dummy */
if (ep->dma) {
struct net2280_dma *td;
td = pci_pool_alloc (ep->dev->requests, gfp_flags,
&req->td_dma);
if (!td) {
kfree (req);
return NULL;
}
td->dmacount = 0; /* not VALID */
td->dmaaddr = __constant_cpu_to_le32 (DMA_ADDR_INVALID);
td->dmadesc = td->dmaaddr;
req->td = td;
}
return &req->req;
}
static void
net2280_free_request (struct usb_ep *_ep, struct usb_request *_req)
{
struct net2280_ep *ep;
struct net2280_request *req;
ep = container_of (_ep, struct net2280_ep, ep);
if (!_ep || !_req)
return;
req = container_of (_req, struct net2280_request, req);
WARN_ON (!list_empty (&req->queue));
if (req->td)
pci_pool_free (ep->dev->requests, req->td, req->td_dma);
kfree (req);
}
/*-------------------------------------------------------------------------*/
#undef USE_KMALLOC
/* many common platforms have dma-coherent caches, which means that it's
* safe to use kmalloc() memory for all i/o buffers without using any
* cache flushing calls. (unless you're trying to share cache lines
* between dma and non-dma activities, which is a slow idea in any case.)
*
* other platforms need more care, with 2.5 having a moderately general
* solution (which falls down for allocations smaller than one page)
* that improves significantly on the 2.4 PCI allocators by removing
* the restriction that memory never be freed in_interrupt().
*/
#if defined(CONFIG_X86)
#define USE_KMALLOC
#elif defined(CONFIG_PPC) && !defined(CONFIG_NOT_COHERENT_CACHE)
#define USE_KMALLOC
#elif defined(CONFIG_MIPS) && !defined(CONFIG_DMA_NONCOHERENT)
#define USE_KMALLOC
/* FIXME there are other cases, including an x86-64 one ... */
#endif
/* allocating buffers this way eliminates dma mapping overhead, which
* on some platforms will mean eliminating a per-io buffer copy. with
* some kinds of system caches, further tweaks may still be needed.
*/
static void *
net2280_alloc_buffer (
struct usb_ep *_ep,
unsigned bytes,
dma_addr_t *dma,
unsigned gfp_flags
)
{
void *retval;
struct net2280_ep *ep;
ep = container_of (_ep, struct net2280_ep, ep);
if (!_ep)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -