📄 fsl_usb2_udc.c
字号:
/* * Copyright (C) 2004-2007 Freescale Semicondutor, Inc. All rights reserved. * * Author: Li Yang <leoli@freescale.com> * Jiang Bo <tanya.jiang@freescale.com> * * Description: * Freescale high-speed USB SOC DR module device controller driver. * This can be found on MPC8349E/MPC8313E cpus. * The driver is previously named as mpc_udc. Based on bare board * code from Dave Liu and Shlomi Gridish. * * 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. */#undef VERBOSE#include <linux/module.h>#include <linux/kernel.h>#include <linux/ioport.h>#include <linux/types.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/timer.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/proc_fs.h>#include <linux/mm.h>#include <linux/moduleparam.h>#include <linux/device.h>#include <linux/usb/ch9.h>#include <linux/usb/gadget.h>#include <linux/usb/otg.h>#include <linux/dma-mapping.h>#include <linux/platform_device.h>#include <linux/fsl_devices.h>#include <linux/dmapool.h>#include <asm/byteorder.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/unaligned.h>#include <asm/dma.h>#include <asm/cacheflush.h>#include "fsl_usb2_udc.h"#define DRIVER_DESC "Freescale High-Speed USB SOC Device Controller driver"#define DRIVER_AUTHOR "Li Yang/Jiang Bo"#define DRIVER_VERSION "Apr 20, 2007"#define DMA_ADDR_INVALID (~(dma_addr_t)0)static const char driver_name[] = "fsl-usb2-udc";static const char driver_desc[] = DRIVER_DESC;volatile static struct usb_dr_device *dr_regs = NULL;volatile static struct usb_sys_interface *usb_sys_regs = NULL;/* it is initialized in probe() */static struct fsl_udc *udc_controller = NULL;static const struct usb_endpoint_descriptorfsl_ep0_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0, .bmAttributes = USB_ENDPOINT_XFER_CONTROL, .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD,};static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state);static int fsl_udc_resume(struct platform_device *pdev);static void fsl_ep_fifo_flush(struct usb_ep *_ep);#ifdef CONFIG_PPC32#define fsl_readl(addr) in_le32(addr)#define fsl_writel(addr, val32) out_le32(val32, addr)#else#define fsl_readl(addr) readl(addr)#define fsl_writel(addr, val32) writel(addr, val32)#endif/******************************************************************** * Internal Used Function********************************************************************//*----------------------------------------------------------------- * done() - retire a request; caller blocked irqs * @status : request status to be set, only works when * request is still in progress. *--------------------------------------------------------------*/static void done(struct fsl_ep *ep, struct fsl_req *req, int status){ struct fsl_udc *udc = NULL; unsigned char stopped = ep->stopped; struct ep_td_struct *curr_td, *next_td; int j; udc = (struct fsl_udc *)ep->udc; /* Removed the req from fsl_ep->queue */ list_del_init(&req->queue); /* req.status should be set as -EINPROGRESS in ep_queue() */ if (req->req.status == -EINPROGRESS) req->req.status = status; else status = req->req.status; /* Free dtd for the request */ next_td = req->head; for (j = 0; j < req->dtd_count; j++) { curr_td = next_td; if (j != req->dtd_count - 1) { next_td = curr_td->next_td_virt; } dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); } if (req->mapped) { dma_unmap_single(ep->udc->gadget.dev.parent, req->req.dma, req->req.length, ep_is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); req->req.dma = DMA_ADDR_INVALID; req->mapped = 0; } else dma_sync_single_for_cpu(ep->udc->gadget.dev.parent, req->req.dma, req->req.length, ep_is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); if (status && (status != -ESHUTDOWN)) VDBG("complete %s req %p stat %d len %u/%u", ep->ep.name, &req->req, status, req->req.actual, req->req.length); ep->stopped = 1; spin_unlock(&ep->udc->lock); /* complete() is from gadget layer, * eg fsg->bulk_in_complete() */ if (req->req.complete) req->req.complete(&ep->ep, &req->req); spin_lock(&ep->udc->lock); ep->stopped = stopped;}/*----------------------------------------------------------------- * nuke(): delete all requests related to this ep * called with spinlock held *--------------------------------------------------------------*/static void nuke(struct fsl_ep *ep, int status){ ep->stopped = 1; /* Flush fifo */ fsl_ep_fifo_flush(&ep->ep); /* Whether this eq has request linked */ while (!list_empty(&ep->queue)) { struct fsl_req *req = NULL; req = list_entry(ep->queue.next, struct fsl_req, queue); done(ep, req, status); }}/*------------------------------------------------------------------ Internal Hardware related function ------------------------------------------------------------------*/static int dr_controller_setup(struct fsl_udc *udc){ unsigned int tmp = 0, portctrl = 0, ctrl = 0; unsigned long timeout;#define FSL_UDC_RESET_TIMEOUT 1000 /* before here, make sure dr_regs has been initialized */ if (!udc) return -EINVAL; /* Stop and reset the usb controller */ tmp = fsl_readl(&dr_regs->usbcmd); tmp &= ~USB_CMD_RUN_STOP; fsl_writel(tmp, &dr_regs->usbcmd); tmp = fsl_readl(&dr_regs->usbcmd); tmp |= USB_CMD_CTRL_RESET; fsl_writel(tmp, &dr_regs->usbcmd); /* Wait for reset to complete */ timeout = jiffies + FSL_UDC_RESET_TIMEOUT; while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { if (time_after(jiffies, timeout)) { ERR("udc reset timeout! \n"); return -ETIMEDOUT; } cpu_relax(); } /* Set the controller as device mode */ tmp = fsl_readl(&dr_regs->usbmode); tmp |= USB_MODE_CTRL_MODE_DEVICE; /* Disable Setup Lockout */ tmp |= USB_MODE_SETUP_LOCK_OFF; fsl_writel(tmp, &dr_regs->usbmode); /* Clear the setup status */ fsl_writel(0, &dr_regs->usbsts); tmp = udc->ep_qh_dma; tmp &= USB_EP_LIST_ADDRESS_MASK; fsl_writel(tmp, &dr_regs->endpointlistaddr); VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", (int)udc->ep_qh, (int)tmp, fsl_readl(&dr_regs->endpointlistaddr)); /* Config PHY interface */ portctrl = fsl_readl(&dr_regs->portsc1); portctrl &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); switch (udc->phy_mode) { case FSL_USB2_PHY_ULPI: portctrl |= PORTSCX_PTS_ULPI; break; case FSL_USB2_PHY_UTMI_WIDE: portctrl |= PORTSCX_PTW_16BIT; /* fall through */ case FSL_USB2_PHY_UTMI: portctrl |= PORTSCX_PTS_UTMI; break; case FSL_USB2_PHY_SERIAL: portctrl |= PORTSCX_PTS_FSLS; break; default: return -EINVAL; } fsl_writel(portctrl, &dr_regs->portsc1); /* Config control enable i/o output, cpu endian register */ ctrl = __raw_readl(&usb_sys_regs->control); ctrl |= USB_CTRL_IOENB; __raw_writel(ctrl, &usb_sys_regs->control);#if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE) /* Turn on cache snooping hardware, since some PowerPC platforms * wholly rely on hardware to deal with cache coherent. */ /* Setup Snooping for all the 4GB space */ tmp = SNOOP_SIZE_2GB; /* starts from 0x0, size 2G */ __raw_writel(tmp, &usb_sys_regs->snoop1); tmp |= 0x80000000; /* starts from 0x8000000, size 2G */ __raw_writel(tmp, &usb_sys_regs->snoop2);#endif return 0;}/* Enable DR irq and set controller to run state */static void dr_controller_run(struct fsl_udc *udc){ u32 temp; /* Enable DR irq reg */ temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; fsl_writel(temp, &dr_regs->usbintr); /* Clear stopped bit */ udc->stopped = 0; /* Set the controller as device mode */ temp = fsl_readl(&dr_regs->usbmode); temp |= USB_MODE_CTRL_MODE_DEVICE; fsl_writel(temp, &dr_regs->usbmode); /* Set controller to Run */ temp = fsl_readl(&dr_regs->usbcmd); temp |= USB_CMD_RUN_STOP; fsl_writel(temp, &dr_regs->usbcmd); return;}static void dr_controller_stop(struct fsl_udc *udc){ unsigned int tmp; /* disable all INTR */ fsl_writel(0, &dr_regs->usbintr); /* Set stopped bit for isr */ udc->stopped = 1; /* disable IO output *//* usb_sys_regs->control = 0; */ /* set controller to Stop */ tmp = fsl_readl(&dr_regs->usbcmd); tmp &= ~USB_CMD_RUN_STOP; fsl_writel(tmp, &dr_regs->usbcmd); return;}void dr_ep_setup(unsigned char ep_num, unsigned char dir, unsigned char ep_type){ unsigned int tmp_epctrl = 0; tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); if (dir) { if (ep_num) tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; tmp_epctrl |= EPCTRL_TX_ENABLE; tmp_epctrl |= ((unsigned int)(ep_type) << EPCTRL_TX_EP_TYPE_SHIFT); } else { if (ep_num) tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; tmp_epctrl |= EPCTRL_RX_ENABLE; tmp_epctrl |= ((unsigned int)(ep_type) << EPCTRL_RX_EP_TYPE_SHIFT); } fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]);}static voiddr_ep_change_stall(unsigned char ep_num, unsigned char dir, int value){ u32 tmp_epctrl = 0; tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); if (value) { /* set the stall bit */ if (dir) tmp_epctrl |= EPCTRL_TX_EP_STALL; else tmp_epctrl |= EPCTRL_RX_EP_STALL; } else { /* clear the stall bit and reset data toggle */ if (dir) { tmp_epctrl &= ~EPCTRL_TX_EP_STALL; tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; } else { tmp_epctrl &= ~EPCTRL_RX_EP_STALL; tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; } } fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]);}/* Get stall status of a specific ep Return: 0: not stalled; 1:stalled */static int dr_ep_get_stall(unsigned char ep_num, unsigned char dir){ u32 epctrl; epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); if (dir) return (epctrl & EPCTRL_TX_EP_STALL) ? 1 : 0; else return (epctrl & EPCTRL_RX_EP_STALL) ? 1 : 0;}/******************************************************************** Internal Structure Build up functions********************************************************************//*------------------------------------------------------------------* struct_ep_qh_setup(): set the Endpoint Capabilites field of QH * @zlt: Zero Length Termination Select (1: disable; 0: enable) * @mult: Mult field ------------------------------------------------------------------*/static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, unsigned char dir, unsigned char ep_type, unsigned int max_pkt_len, unsigned int zlt, unsigned char mult){ struct ep_queue_head *p_QH = &udc->ep_qh[2 * ep_num + dir]; unsigned int tmp = 0; /* set the Endpoint Capabilites in QH */ switch (ep_type) { case USB_ENDPOINT_XFER_CONTROL: /* Interrupt On Setup (IOS). for control ep */ tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | EP_QUEUE_HEAD_IOS; break; case USB_ENDPOINT_XFER_ISOC: tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | (mult << EP_QUEUE_HEAD_MULT_POS); break; case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; break; default: VDBG("error ep type is %d", ep_type); return; } if (zlt) tmp |= EP_QUEUE_HEAD_ZLT_SEL; p_QH->max_pkt_length = cpu_to_le32(tmp); return;}/* Setup qh structure and ep register for ep0. */static void ep0_setup(struct fsl_udc *udc){ /* the intialization of an ep includes: fields in QH, Regs, * fsl_ep struct */ struct_ep_qh_setup(udc, 0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, 0, 0); struct_ep_qh_setup(udc, 0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, 0, 0); dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); return;}/*********************************************************************** Endpoint Management Functions***********************************************************************//*------------------------------------------------------------------------- * when configurations are set, or when interface settings change * for example the do_set_interface() in gadget layer, * the driver will enable or disable the relevant endpoints * ep0 doesn't use this routine. It is always enabled.-------------------------------------------------------------------------*/static int fsl_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc){ struct fsl_udc *udc = NULL; struct fsl_ep *ep = NULL; unsigned short max = 0; unsigned char mult = 0, zlt; int retval = -EINVAL; unsigned long flags = 0; ep = container_of(_ep, struct fsl_ep, ep); /* catch various bogus parameters */ if (!_ep || !desc || ep->desc || (desc->bDescriptorType != USB_DT_ENDPOINT)) return -EINVAL; udc = ep->udc; if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) return -ESHUTDOWN; max = le16_to_cpu(desc->wMaxPacketSize); /* Disable automatic zlp generation. Driver is reponsible to indicate * explicitly through req->req.zero. This is needed to enable multi-td * request. */ zlt = 1; /* Assume the max packet size from gadget is always correct */ switch (desc->bmAttributes & 0x03) { case USB_ENDPOINT_XFER_CONTROL: case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: /* mult = 0. Execute N Transactions as demonstrated by * the USB variable length packet protocol where N is * computed using the Maximum Packet Length (dQH) and * the Total Bytes field (dTD) */ mult = 0; break; case USB_ENDPOINT_XFER_ISOC:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -