?? dwc_otg_hcd_intr.c
字號:
/** * Gets the actual length of a transfer after the transfer halts. _halt_status * holds the reason for the halt. * * For IN transfers where _halt_status is DWC_OTG_HC_XFER_COMPLETE, * *_short_read is set to 1 upon return if less than the requested * number of bytes were transferred. Otherwise, *_short_read is set to 0 upon * return. _short_read may also be NULL on entry, in which case it remains * unchanged. */static uint32_t get_actual_xfer_length(dwc_hc_t * _hc, dwc_otg_hc_regs_t * _hc_regs, dwc_otg_qtd_t * _qtd, dwc_otg_halt_status_e _halt_status, int *_short_read){ hctsiz_data_t hctsiz; uint32_t length; if (_short_read != NULL) { *_short_read = 0; } hctsiz.d32 = dwc_read_reg32(&_hc_regs->hctsiz); if (_halt_status == DWC_OTG_HC_XFER_COMPLETE) { if (_hc->ep_is_in) { length = _hc->xfer_len - hctsiz.b.xfersize; if (_short_read != NULL) { *_short_read = (hctsiz.b.xfersize != 0); } } else if (_hc->qh->do_split) { length = _qtd->ssplit_out_xfer_count; } else { length = _hc->xfer_len; } } else { /* * Must use the hctsiz.pktcnt field to determine how much data * has been transferred. This field reflects the number of * packets that have been transferred via the USB. This is * always an integral number of packets if the transfer was * halted before its normal completion. (Can't use the * hctsiz.xfersize field because that reflects the number of * bytes transferred via the AHB, not the USB). */ length = (_hc->start_pkt_count - hctsiz.b.pktcnt) * _hc->max_packet; } return length;}/** * Updates the state of the URB after a Transfer Complete interrupt on the * host channel. Updates the actual_length field of the URB based on the * number of bytes transferred via the host channel. Sets the URB status * if the data transfer is finished. * * @return 1 if the data transfer specified by the URB is completely finished, * 0 otherwise. */static int update_urb_state_xfer_comp(dwc_hc_t * _hc, dwc_otg_hc_regs_t * _hc_regs, struct urb *_urb, dwc_otg_qtd_t * _qtd){ int xfer_done = 0; int short_read = 0; _urb->actual_length += get_actual_xfer_length(_hc, _hc_regs, _qtd, DWC_OTG_HC_XFER_COMPLETE, &short_read);#if 1 //orig if (short_read || (_urb->actual_length == _urb->transfer_buffer_length)) { xfer_done = 1; if (short_read && (_urb->transfer_flags & URB_SHORT_NOT_OK)) { _urb->status = -EREMOTEIO; } else { _urb->status = 0; } }#else if (short_read) { xfer_done = 1; if (_urb->transfer_flags & URB_SHORT_NOT_OK) { _urb->status = -EREMOTEIO; } else { _urb->status = 0; } } if (_urb->actual_length == _urb->transfer_buffer_length) { xfer_done = 1; }#endif { hctsiz_data_t hctsiz; hctsiz.d32 = dwc_read_reg32(&_hc_regs->hctsiz); dbg_otg("DWC_otg: %s: %s, channel %d\n", __FUNCTION__, (_hc->ep_is_in ? "IN" : "OUT"), _hc->hc_num); dbg_otg(" hc->xfer_len %d\n", _hc->xfer_len); dbg_otg(" hctsiz.xfersize %d\n", hctsiz.b.xfersize); dbg_otg(" urb->transfer_buffer_length %d\n", _urb->transfer_buffer_length); dbg_otg(" urb->actual_length %d\n", _urb->actual_length); dbg_otg(" short_read %d, xfer_done %d\n", short_read, xfer_done); } return xfer_done;}/* * Save the starting data toggle for the next transfer. The data toggle is * saved in the QH for non-control transfers and it's saved in the QTD for * control transfers. */static void save_data_toggle(dwc_hc_t * _hc, dwc_otg_hc_regs_t * _hc_regs, dwc_otg_qtd_t * _qtd){ hctsiz_data_t hctsiz; hctsiz.d32 = dwc_read_reg32(&_hc_regs->hctsiz); if (_hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) { dwc_otg_qh_t *qh = _hc->qh; if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { qh->data_toggle = DWC_OTG_HC_PID_DATA0; } else { qh->data_toggle = DWC_OTG_HC_PID_DATA1; } } else { if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { _qtd->data_toggle = DWC_OTG_HC_PID_DATA0; } else { _qtd->data_toggle = DWC_OTG_HC_PID_DATA1; } }}/** * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic * QHs, removes the QH from the active non-periodic schedule. If any QTDs are * still linked to the QH, the QH is added to the end of the inactive * non-periodic schedule. For periodic QHs, removes the QH from the periodic * schedule if no more QTDs are linked to the QH. */static void deactivate_qh(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh, int free_qtd){ int continue_split = 0; dwc_otg_qtd_t *qtd; DWC_DEBUGPL(DBG_HCDV, " %s(%p,%p,%d)\n", __func__, _hcd, _qh, free_qtd); qtd = list_entry(_qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); if (qtd->complete_split) { continue_split = 1; } else if ((qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID) || (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END)) { continue_split = 1; } if (free_qtd) { dwc_otg_hcd_qtd_remove_and_free(qtd); continue_split = 0; } _qh->channel = NULL; _qh->qtd_in_process = NULL; dwc_otg_hcd_qh_deactivate(_hcd, _qh, continue_split);}/** * Updates the state of an Isochronous URB when the transfer is stopped for * any reason. The fields of the current entry in the frame descriptor array * are set based on the transfer state and the input _halt_status. Completes * the Isochronous URB if all the URB frames have been completed. * * @return DWC_OTG_HC_XFER_COMPLETE if there are more frames remaining to be * transferred in the URB. Otherwise return DWC_OTG_HC_XFER_URB_COMPLETE. */static dwc_otg_halt_status_eupdate_isoc_urb_state(dwc_otg_hcd_t * _hcd, dwc_hc_t * _hc, dwc_otg_hc_regs_t * _hc_regs, dwc_otg_qtd_t * _qtd, dwc_otg_halt_status_e _halt_status){ struct urb *urb = _qtd->urb; dwc_otg_halt_status_e ret_val = _halt_status; struct usb_iso_packet_descriptor *frame_desc; frame_desc = &urb->iso_frame_desc[_qtd->isoc_frame_index]; switch (_halt_status) { case DWC_OTG_HC_XFER_COMPLETE: frame_desc->status = 0; frame_desc->actual_length = get_actual_xfer_length(_hc, _hc_regs, _qtd, _halt_status, NULL); break; case DWC_OTG_HC_XFER_FRAME_OVERRUN: urb->error_count++; if (_hc->ep_is_in) { frame_desc->status = -ENOSR; } else { frame_desc->status = -ECOMM; } frame_desc->actual_length = 0; break; case DWC_OTG_HC_XFER_BABBLE_ERR: urb->error_count++; frame_desc->status = -EOVERFLOW; /* Don't need to update actual_length in this case. */ break; case DWC_OTG_HC_XFER_XACT_ERR: urb->error_count++; frame_desc->status = -EPROTO; frame_desc->actual_length = get_actual_xfer_length(_hc, _hc_regs, _qtd, _halt_status, NULL); default: DWC_ERROR("%s: Unhandled _halt_status (%d)\n", __func__, _halt_status); BUG(); break; } if (++_qtd->isoc_frame_index == urb->number_of_packets) { /* * urb->status is not used for isoc transfers. * The individual frame_desc statuses are used instead. */ dwc_otg_hcd_complete_urb(_hcd, urb, 0); ret_val = DWC_OTG_HC_XFER_URB_COMPLETE; } else { ret_val = DWC_OTG_HC_XFER_COMPLETE; } return ret_val;}/** * Releases a host channel for use by other transfers. Attempts to select and * queue more transactions since at least one host channel is available. * * @param _hcd The HCD state structure. * @param _hc The host channel to release. * @param _qtd The QTD associated with the host channel. This QTD may be freed * if the transfer is complete or an error has occurred. * @param _halt_status Reason the channel is being released. This status * determines the actions taken by this function. */static void release_channel(dwc_otg_hcd_t * _hcd, dwc_hc_t * _hc, dwc_otg_qtd_t * _qtd, dwc_otg_halt_status_e _halt_status){ dwc_otg_transaction_type_e tr_type; int free_qtd; DWC_DEBUGPL(DBG_HCDV, " %s: channel %d, halt_status %d\n", __func__, _hc->hc_num, _halt_status); switch (_halt_status) { case DWC_OTG_HC_XFER_URB_COMPLETE: free_qtd = 1; break; case DWC_OTG_HC_XFER_AHB_ERR: case DWC_OTG_HC_XFER_STALL: case DWC_OTG_HC_XFER_BABBLE_ERR: free_qtd = 1; break; case DWC_OTG_HC_XFER_XACT_ERR: if (_qtd->error_count >= 3) { DWC_DEBUGPL(DBG_HCDV, " Complete URB with transaction error\n"); free_qtd = 1; _qtd->urb->status = -EPROTO; dwc_otg_hcd_complete_urb(_hcd, _qtd->urb, -EPROTO); } else { free_qtd = 0; } break; case DWC_OTG_HC_XFER_URB_DEQUEUE: /* * The QTD has already been removed and the QH has been * deactivated. Don't want to do anything except release the * host channel and try to queue more transfers. */ goto cleanup; case DWC_OTG_HC_XFER_NO_HALT_STATUS: DWC_ERROR("%s: No halt_status, channel %d\n", __func__, _hc->hc_num); free_qtd = 0; break; default: free_qtd = 0; break; } deactivate_qh(_hcd, _hc->qh, free_qtd); cleanup: /* * Release the host channel for use by other transfers. The cleanup * function clears the channel interrupt enables and conditions, so * there's no need to clear the Channel Halted interrupt separately. */ dwc_otg_hc_cleanup(_hcd->core_if, _hc); list_add_tail(&_hc->hc_list_entry, &_hcd->free_hc_list); switch (_hc->ep_type) { case DWC_OTG_EP_TYPE_CONTROL: case DWC_OTG_EP_TYPE_BULK: _hcd->non_periodic_channels--; break; default: /* * Don't release reservations for periodic channels here. * That's done when a periodic transfer is descheduled (i.e. * when the QH is removed from the periodic schedule). */ break; } /* Try to queue more transfers now that there's a free channel. */ tr_type = dwc_otg_hcd_select_transactions(_hcd); if (tr_type != DWC_OTG_TRANSACTION_NONE) { dwc_otg_hcd_queue_transactions(_hcd, tr_type); }}/** * Halts a host channel. If the channel cannot be halted immediately because * the request queue is full, this function ensures that the FIFO empty * interrupt for the appropriate queue is enabled so that the halt request can * be queued when there is space in the request queue. * * This function may also be called in DMA mode. In that case, the channel is * simply released since the core always halts the channel automatically in * DMA mode. */static void halt_channel(dwc_otg_hcd_t * _hcd, dwc_hc_t * _hc, dwc_otg_qtd_t * _qtd, dwc_otg_halt_status_e _halt_status){ if (_hcd->core_if->dma_enable) { release_channel(_hcd, _hc, _qtd, _halt_status); return; } /* Slave mode processing... */ dwc_otg_hc_halt(_hcd->core_if, _hc, _halt_status); if (_hc->halt_on_queue) { gintmsk_data_t gintmsk = {.d32 = 0 }; dwc_otg_core_global_regs_t *global_regs; global_regs = _hcd->core_if->core_global_regs; if (_hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || _hc->ep_type == DWC_OTG_EP_TYPE_BULK) { /* * Make sure the Non-periodic Tx FIFO empty interrupt * is enabled so that the non-periodic schedule will * be processed. */ gintmsk.b.nptxfempty = 1; dwc_modify_reg32(&global_regs->gintmsk, 0, gintmsk.d32); } else { /* * Move the QH from the periodic queued schedule to * the periodic assigned schedule. This allows the * halt to be queued when the periodic schedule is * processed. */ list_move(&_hc->qh->qh_list_entry, &_hcd->periodic_sched_assigned); /* * Make sure the Periodic Tx FIFO Empty interrupt is * enabled so that the periodic schedule will be * processed. */ gintmsk.b.ptxfempty = 1; dwc_modify_reg32(&global_regs->gintmsk, 0, gintmsk.d32); } }}/** * Performs common cleanup for non-periodic transfers after a Transfer * Complete interrupt. This function should be called after any endpoint type * specific handling is finished to release the host channel. */static void complete_non_periodic_xfer(dwc_otg_hcd_t * _hcd, dwc_hc_t * _hc, dwc_otg_hc_regs_t * _hc_regs, dwc_otg_qtd_t * _qtd, dwc_otg_halt_status_e _halt_status){ hcint_data_t hcint; _qtd->error_count = 0; hcint.d32 = dwc_read_reg32(&_hc_regs->hcint); if (hcint.b.nyet) { /* * Got a NYET on the last transaction of the transfer. This * means that the endpoint should be in the PING state at the * beginning of the next transfer. */ _hc->qh->ping_state = 1; clear_hc_int(_hc_regs, nyet); } /* * Always halt and release the host channel to make it available for * more transfers. There may still be more phases for a control * transfer or more data packets for a bulk transfer at this point, * but the host channel is still halted. A channel will be reassigned * to the transfer when the non-periodic schedule is processed after * the channel is released. This allows transactions to be queued * properly via dwc_otg_hcd_queue_transactions, which also enables the * Tx FIFO Empty interrupt if necessary. */ if (_hc->ep_is_in) { /* * IN transfers in Slave mode require an explicit disable to * halt the channel. (In DMA mode, this call simply releases * the channel.) */ halt_channel(_hcd, _hc, _qtd, _halt_status); } else { /* * The channel is automatically disabled by the core for OUT * transfers in Slave mode. */ release_channel(_hcd, _hc, _qtd, _halt_status); }}/** * Performs common cleanup for periodic transfers after a Transfer Complete * interrupt. This function should be called after any endpoint type specific * handling is finished to release the host channel. */static void complete_periodic_xfer(dwc_otg_hcd_t * _hcd, dwc_hc_t * _hc, dwc_otg_hc_regs_t * _hc_regs, dwc_otg_qtd_t * _qtd, dwc_otg_halt_status_e _halt_status){ hctsiz_data_t hctsiz; _qtd->error_count = 0;
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -