📄 dwc_otg_hcd.c
字号:
/* ========================================================================== * * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless * otherwise expressly agreed to in writing between Synopsys and you. * * The Software IS NOT an item of Licensed Software or Licensed Product under * any End User Software License Agreement or Agreement for Licensed Product * with Synopsys or any supplement thereto. You are permitted to use and * redistribute this Software in source and binary forms, with or without * modification, provided that redistributions of source code must retain this * notice. You may not view, use, disclose, copy or distribute this file or * any information contained herein except pursuant to this license grant from * Synopsys. If you do not agree with this notice, including the disclaimer * below, then you are not authorized to use the Software. * * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS 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. * ========================================================================== */#ifndef DWC_DEVICE_ONLY/** * @file * * This file contains the implementation of the HCD. In Linux, the HCD * implements the hc_driver API. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/device.h>#include <linux/errno.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/string.h>#include <linux/dma-mapping.h>#include <linux/version.h>#include <linux/platform_device.h>#include <asm/arch/irqs.h>#include "dwc_otg_driver.h"#include "dwc_otg_hcd.h"#include "dwc_otg_regs.h"static const char dwc_otg_hcd_name[] = "dwc_otg_hcd";static const struct hc_driver dwc_otg_hc_driver = { .description = dwc_otg_hcd_name, .product_desc = "DWC OTG Controller", .hcd_priv_size = sizeof(dwc_otg_hcd_t), .irq = dwc_otg_hcd_irq, .flags = HCD_MEMORY | HCD_USB2, //.reset = .start = dwc_otg_hcd_start, //.suspend = //.resume = .stop = dwc_otg_hcd_stop, .urb_enqueue = dwc_otg_hcd_urb_enqueue, .urb_dequeue = dwc_otg_hcd_urb_dequeue, .endpoint_disable = dwc_otg_hcd_endpoint_disable, .get_frame_number = dwc_otg_hcd_get_frame_number, .hub_status_data = dwc_otg_hcd_hub_status_data, .hub_control = dwc_otg_hcd_hub_control, //.hub_suspend = //.hub_resume =};/** * Work queue function for starting the HCD when A-Cable is connected. * The dwc_otg_hcd_start() must be called in a process context. */static void hcd_start_func(#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) void *_vp#else struct work_struct *_work#endif ){#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) struct usb_hcd *usb_hcd = (struct usb_hcd *) _vp;#else struct dwc_otg_hcd *otg_hcd = container_of(_work, struct dwc_otg_hcd, start_work); struct usb_hcd *usb_hcd = container_of((void *) otg_hcd, struct usb_hcd, hcd_priv);#endif DWC_DEBUGPL(DBG_HCDV, "%s() %p\n", __func__, usb_hcd); if (usb_hcd) { dwc_otg_hcd_start(usb_hcd); }}/** * HCD Callback function for starting the HCD when A-Cable is * connected. * * @param _p void pointer to the <code>struct usb_hcd</code> */static int32_t dwc_otg_hcd_start_cb(void *_p){ dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(_p); dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; hprt0_data_t hprt0; if (core_if->op_state == B_HOST) { /* * Reset the port. During a HNP mode switch the reset * needs to occur within 1ms and have a duration of at * least 50ms. */ hprt0.d32 = dwc_otg_read_hprt0(core_if); hprt0.b.prtrst = 1; dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); ((struct usb_hcd *) _p)->self.is_b_host = 1; } else { ((struct usb_hcd *) _p)->self.is_b_host = 0; } /* Need to start the HCD in a non-interrupt context. */#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) INIT_WORK(&dwc_otg_hcd->start_work, hcd_start_func, _p);#else INIT_WORK(&dwc_otg_hcd->start_work, hcd_start_func);#endif schedule_work(&dwc_otg_hcd->start_work); return 1;}/** * HCD Callback function for stopping the HCD. * * @param _p void pointer to the <code>struct usb_hcd</code> */static int32_t dwc_otg_hcd_stop_cb(void *_p){ struct usb_hcd *usb_hcd = (struct usb_hcd *) _p; DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, _p); dwc_otg_hcd_stop(usb_hcd); return 1;}static void del_xfer_timers(dwc_otg_hcd_t * _hcd){#ifdef DEBUG int i; int num_channels = _hcd->core_if->core_params->host_channels; for (i = 0; i < num_channels; i++) { del_timer(&_hcd->core_if->hc_xfer_timer[i]); }#endif}static void del_timers(dwc_otg_hcd_t * _hcd){ del_xfer_timers(_hcd); del_timer(&_hcd->conn_timer);}/** * Processes all the URBs in a single list of QHs. Completes them with * -ETIMEDOUT and frees the QTD. */static void kill_urbs_in_qh_list(dwc_otg_hcd_t * _hcd, struct list_head *_qh_list){ struct list_head *qh_item; dwc_otg_qh_t *qh; struct list_head *qtd_item; dwc_otg_qtd_t *qtd; list_for_each(qh_item, _qh_list) { qh = list_entry(qh_item, dwc_otg_qh_t, qh_list_entry); for (qtd_item = qh->qtd_list.next; qtd_item != &qh->qtd_list; qtd_item = qh->qtd_list.next) { qtd = list_entry(qtd_item, dwc_otg_qtd_t, qtd_list_entry); if (qtd->urb != NULL) { dwc_otg_hcd_complete_urb(_hcd, qtd->urb, -ETIMEDOUT); } dwc_otg_hcd_qtd_remove_and_free(qtd); } }}/** * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic * and periodic schedules. The QTD associated with each URB is removed from * the schedule and freed. This function may be called when a disconnect is * detected or when the HCD is being stopped. */static void kill_all_urbs(dwc_otg_hcd_t * _hcd){ kill_urbs_in_qh_list(_hcd, &_hcd->non_periodic_sched_inactive); kill_urbs_in_qh_list(_hcd, &_hcd->non_periodic_sched_active); kill_urbs_in_qh_list(_hcd, &_hcd->periodic_sched_inactive); kill_urbs_in_qh_list(_hcd, &_hcd->periodic_sched_ready); kill_urbs_in_qh_list(_hcd, &_hcd->periodic_sched_assigned); kill_urbs_in_qh_list(_hcd, &_hcd->periodic_sched_queued);}/** * HCD Callback function for disconnect of the HCD. * * @param _p void pointer to the <code>struct usb_hcd</code> */static int32_t dwc_otg_hcd_disconnect_cb(void *_p){ gintsts_data_t intr; dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(_p); //DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, _p); /* * Set status flags for the hub driver. */ dwc_otg_hcd->flags.b.port_connect_status_change = 1; dwc_otg_hcd->flags.b.port_connect_status = 0; /* * Shutdown any transfers in process by clearing the Tx FIFO Empty * interrupt mask and status bits and disabling subsequent host * channel interrupts. */ intr.d32 = 0; intr.b.nptxfempty = 1; intr.b.ptxfempty = 1; intr.b.hcintr = 1; dwc_modify_reg32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, intr.d32, 0); dwc_modify_reg32(&dwc_otg_hcd->core_if->core_global_regs->gintsts, intr.d32, 0); del_timers(dwc_otg_hcd); /* * Turn off the vbus power only if the core has transitioned to device * mode. If still in host mode, need to keep power on to detect a * reconnection. */ if (dwc_otg_is_device_mode(dwc_otg_hcd->core_if)) { if (dwc_otg_hcd->core_if->op_state != A_SUSPEND) { hprt0_data_t hprt0 = {.d32 = 0 }; DWC_PRINT("Disconnect: PortPower off\n"); hprt0.b.prtpwr = 0; dwc_write_reg32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0.d32); } dwc_otg_disable_host_interrupts(dwc_otg_hcd->core_if); } /* Respond with an error status to all URBs in the schedule. */ kill_all_urbs(dwc_otg_hcd); if (dwc_otg_is_host_mode(dwc_otg_hcd->core_if)) { /* Clean up any host channels that were in use. */ int num_channels; int i; dwc_hc_t *channel; dwc_otg_hc_regs_t *hc_regs; hcchar_data_t hcchar; num_channels = dwc_otg_hcd->core_if->core_params->host_channels; if (!dwc_otg_hcd->core_if->dma_enable) { /* Flush out any channel requests in slave mode. */ for (i = 0; i < num_channels; i++) { channel = dwc_otg_hcd->hc_ptr_array[i]; if (list_empty(&channel->hc_list_entry)) { hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[i]; hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); if (hcchar.b.chen) { hcchar.b.chen = 0; hcchar.b.chdis = 1; hcchar.b.epdir = 0; dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); } } } } for (i = 0; i < num_channels; i++) { channel = dwc_otg_hcd->hc_ptr_array[i]; if (list_empty(&channel->hc_list_entry)) { hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[i]; hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); if (hcchar.b.chen) { /* Halt the channel. */ hcchar.b.chdis = 1; dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); } dwc_otg_hc_cleanup(dwc_otg_hcd->core_if, channel); list_add_tail(&channel->hc_list_entry, &dwc_otg_hcd->free_hc_list); } } } /* A disconnect will end the session so the B-Device is no * longer a B-host. */ ((struct usb_hcd *) _p)->self.is_b_host = 0; return 1;}/** * Connection timeout function. An OTG host is required to display a * message if the device does not connect within 10 seconds. */void dwc_otg_hcd_connect_timeout(unsigned long _ptr){ DWC_DEBUGPL(DBG_HCDV, "%s(%x)\n", __func__, (int) _ptr); DWC_PRINT("Connect Timeout\n"); DWC_ERROR("Device Not Connected/Responding\n");}/** * Start the connection timer. An OTG host is required to display a * message if the device does not connect within 10 seconds. The * timer is deleted if a port connect interrupt occurs before the * timer expires. */static void dwc_otg_hcd_start_connect_timer(dwc_otg_hcd_t * _hcd){ init_timer(&_hcd->conn_timer); _hcd->conn_timer.function = dwc_otg_hcd_connect_timeout; _hcd->conn_timer.data = (unsigned long) 0; _hcd->conn_timer.expires = jiffies + (HZ * 10); add_timer(&_hcd->conn_timer);}/** * HCD Callback function for disconnect of the HCD. * * @param _p void pointer to the <code>struct usb_hcd</code> */static int32_t dwc_otg_hcd_session_start_cb(void *_p){ dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(_p); DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, _p); dwc_otg_hcd_start_connect_timer(dwc_otg_hcd); return 1;}/** * HCD Callback structure for handling mode switching. */static dwc_otg_cil_callbacks_t hcd_cil_callbacks = { .start = dwc_otg_hcd_start_cb, .stop = dwc_otg_hcd_stop_cb, .disconnect = dwc_otg_hcd_disconnect_cb, .session_start = dwc_otg_hcd_session_start_cb, .p = 0,};/** * Reset tasklet function */static void reset_tasklet_func(unsigned long data){ dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *) data; dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; hprt0_data_t hprt0; dbg_otg("USB RESET tasklet called\n"); hprt0.d32 = dwc_otg_read_hprt0(core_if); hprt0.b.prtrst = 1; dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); mdelay(60); hprt0.b.prtrst = 0; dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); dwc_otg_hcd->flags.b.port_reset_change = 1; return;}static struct tasklet_struct reset_tasklet = { .next = NULL, .state = 0, .count = ATOMIC_INIT(0), .func = reset_tasklet_func, .data = 0,};/** * Initializes the HCD. This function allocates memory for and initializes the * static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the * USB bus with the core and calls the hc_driver->start() function. It returns * a negative error on failure. */int dwc_otg_hcd_init (struct platform_device *pdev){ struct usb_hcd *hcd = NULL; dwc_otg_hcd_t *dwc_otg_hcd = NULL; dwc_otg_device_t *otg_dev = platform_get_drvdata(pdev); int num_channels; int i; dwc_hc_t *channel; int retval = 0; dbg_otg("%s\n", __FUNCTION__);#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) /* 2.6.20+ requires pdev.dma_mask to be set prior to calling usb_create_hcd() */ /* Set device flags indicating whether the HCD supports DMA. */ if (otg_dev->core_if->dma_enable) { printk("Using DMA mode\n"); pdev->dev.dma_mask = (void *) ~0; pdev->dev.coherent_dma_mask = ~0; if (otg_dev->core_if->dma_desc_enable) { dbg_otg("Device using Descriptor DMA mode\n"); } else { dbg_otg("Device using Buffer DMA mode\n"); } } else { printk("Using Slave mode\n"); pdev->dev.dma_mask = (void *) 0; pdev->dev.coherent_dma_mask = 0; }#endif /* * Allocate memory for the base HCD plus the DWC OTG HCD. * Initialize the base HCD. * 1st contact point of USB Core. by scsuh. */ hcd = usb_create_hcd(&dwc_otg_hc_driver, &pdev->dev, pdev->dev.bus_id);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -