| /* | |
| * Freescale QUICC Engine USB Host Controller Driver | |
| * | |
| * Copyright (c) Freescale Semicondutor, Inc. 2006. | |
| * Shlomi Gridish <gridish@freescale.com> | |
| * Jerry Huang <Chang-Ming.Huang@freescale.com> | |
| * Copyright (c) Logic Product Development, Inc. 2007 | |
| * Peter Barada <peterb@logicpd.com> | |
| * Copyright (c) MontaVista Software, Inc. 2008. | |
| * Anton Vorontsov <avorontsov@ru.mvista.com> | |
| * | |
| * 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. | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/types.h> | |
| #include <linux/errno.h> | |
| #include <linux/slab.h> | |
| #include <linux/list.h> | |
| #include <linux/io.h> | |
| #include <linux/usb.h> | |
| #include <linux/usb/hcd.h> | |
| #include "fhci.h" | |
| #define DUMMY_BD_BUFFER 0xdeadbeef | |
| #define DUMMY2_BD_BUFFER 0xbaadf00d | |
| /* Transaction Descriptors bits */ | |
| #define TD_R 0x8000 /* ready bit */ | |
| #define TD_W 0x2000 /* wrap bit */ | |
| #define TD_I 0x1000 /* interrupt on completion */ | |
| #define TD_L 0x0800 /* last */ | |
| #define TD_TC 0x0400 /* transmit CRC */ | |
| #define TD_CNF 0x0200 /* CNF - Must be always 1 */ | |
| #define TD_LSP 0x0100 /* Low-speed transaction */ | |
| #define TD_PID 0x00c0 /* packet id */ | |
| #define TD_RXER 0x0020 /* Rx error or not */ | |
| #define TD_NAK 0x0010 /* No ack. */ | |
| #define TD_STAL 0x0008 /* Stall received */ | |
| #define TD_TO 0x0004 /* time out */ | |
| #define TD_UN 0x0002 /* underrun */ | |
| #define TD_NO 0x0010 /* Rx Non Octet Aligned Packet */ | |
| #define TD_AB 0x0008 /* Frame Aborted */ | |
| #define TD_CR 0x0004 /* CRC Error */ | |
| #define TD_OV 0x0002 /* Overrun */ | |
| #define TD_BOV 0x0001 /* Buffer Overrun */ | |
| #define TD_ERRORS (TD_NAK | TD_STAL | TD_TO | TD_UN | \ | |
| TD_NO | TD_AB | TD_CR | TD_OV | TD_BOV) | |
| #define TD_PID_DATA0 0x0080 /* Data 0 toggle */ | |
| #define TD_PID_DATA1 0x00c0 /* Data 1 toggle */ | |
| #define TD_PID_TOGGLE 0x00c0 /* Data 0/1 toggle mask */ | |
| #define TD_TOK_SETUP 0x0000 | |
| #define TD_TOK_OUT 0x4000 | |
| #define TD_TOK_IN 0x8000 | |
| #define TD_ISO 0x1000 | |
| #define TD_ENDP 0x0780 | |
| #define TD_ADDR 0x007f | |
| #define TD_ENDP_SHIFT 7 | |
| struct usb_td { | |
| __be16 status; | |
| __be16 length; | |
| __be32 buf_ptr; | |
| __be16 extra; | |
| __be16 reserved; | |
| }; | |
| static struct usb_td __iomem *next_bd(struct usb_td __iomem *base, | |
| struct usb_td __iomem *td, | |
| u16 status) | |
| { | |
| if (status & TD_W) | |
| return base; | |
| else | |
| return ++td; | |
| } | |
| void fhci_push_dummy_bd(struct endpoint *ep) | |
| { | |
| if (!ep->already_pushed_dummy_bd) { | |
| u16 td_status = in_be16(&ep->empty_td->status); | |
| out_be32(&ep->empty_td->buf_ptr, DUMMY_BD_BUFFER); | |
| /* get the next TD in the ring */ | |
| ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); | |
| ep->already_pushed_dummy_bd = true; | |
| } | |
| } | |
| /* destroy an USB endpoint */ | |
| void fhci_ep0_free(struct fhci_usb *usb) | |
| { | |
| struct endpoint *ep; | |
| int size; | |
| ep = usb->ep0; | |
| if (ep) { | |
| if (ep->td_base) | |
| cpm_muram_free(cpm_muram_offset(ep->td_base)); | |
| if (kfifo_initialized(&ep->conf_frame_Q)) { | |
| size = cq_howmany(&ep->conf_frame_Q); | |
| for (; size; size--) { | |
| struct packet *pkt = cq_get(&ep->conf_frame_Q); | |
| kfree(pkt); | |
| } | |
| cq_delete(&ep->conf_frame_Q); | |
| } | |
| if (kfifo_initialized(&ep->empty_frame_Q)) { | |
| size = cq_howmany(&ep->empty_frame_Q); | |
| for (; size; size--) { | |
| struct packet *pkt = cq_get(&ep->empty_frame_Q); | |
| kfree(pkt); | |
| } | |
| cq_delete(&ep->empty_frame_Q); | |
| } | |
| if (kfifo_initialized(&ep->dummy_packets_Q)) { | |
| size = cq_howmany(&ep->dummy_packets_Q); | |
| for (; size; size--) { | |
| u8 *buff = cq_get(&ep->dummy_packets_Q); | |
| kfree(buff); | |
| } | |
| cq_delete(&ep->dummy_packets_Q); | |
| } | |
| kfree(ep); | |
| usb->ep0 = NULL; | |
| } | |
| } | |
| /* | |
| * create the endpoint structure | |
| * | |
| * arguments: | |
| * usb A pointer to the data structure of the USB | |
| * data_mem The data memory partition(BUS) | |
| * ring_len TD ring length | |
| */ | |
| u32 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, | |
| u32 ring_len) | |
| { | |
| struct endpoint *ep; | |
| struct usb_td __iomem *td; | |
| unsigned long ep_offset; | |
| char *err_for = "endpoint PRAM"; | |
| int ep_mem_size; | |
| u32 i; | |
| /* we need at least 3 TDs in the ring */ | |
| if (!(ring_len > 2)) { | |
| fhci_err(usb->fhci, "illegal TD ring length parameters\n"); | |
| return -EINVAL; | |
| } | |
| ep = kzalloc(sizeof(*ep), GFP_KERNEL); | |
| if (!ep) | |
| return -ENOMEM; | |
| ep_mem_size = ring_len * sizeof(*td) + sizeof(struct fhci_ep_pram); | |
| ep_offset = cpm_muram_alloc(ep_mem_size, 32); | |
| if (IS_ERR_VALUE(ep_offset)) | |
| goto err; | |
| ep->td_base = cpm_muram_addr(ep_offset); | |
| /* zero all queue pointers */ | |
| if (cq_new(&ep->conf_frame_Q, ring_len + 2) || | |
| cq_new(&ep->empty_frame_Q, ring_len + 2) || | |
| cq_new(&ep->dummy_packets_Q, ring_len + 2)) { | |
| err_for = "frame_queues"; | |
| goto err; | |
| } | |
| for (i = 0; i < (ring_len + 1); i++) { | |
| struct packet *pkt; | |
| u8 *buff; | |
| pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); | |
| if (!pkt) { | |
| err_for = "frame"; | |
| goto err; | |
| } | |
| buff = kmalloc(1028 * sizeof(*buff), GFP_KERNEL); | |
| if (!buff) { | |
| kfree(pkt); | |
| err_for = "buffer"; | |
| goto err; | |
| } | |
| cq_put(&ep->empty_frame_Q, pkt); | |
| cq_put(&ep->dummy_packets_Q, buff); | |
| } | |
| /* we put the endpoint parameter RAM right behind the TD ring */ | |
| ep->ep_pram_ptr = (void __iomem *)ep->td_base + sizeof(*td) * ring_len; | |
| ep->conf_td = ep->td_base; | |
| ep->empty_td = ep->td_base; | |
| ep->already_pushed_dummy_bd = false; | |
| /* initialize tds */ | |
| td = ep->td_base; | |
| for (i = 0; i < ring_len; i++) { | |
| out_be32(&td->buf_ptr, 0); | |
| out_be16(&td->status, 0); | |
| out_be16(&td->length, 0); | |
| out_be16(&td->extra, 0); | |
| td++; | |
| } | |
| td--; | |
| out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ | |
| out_be16(&td->length, 0); | |
| /* endpoint structure has been created */ | |
| usb->ep0 = ep; | |
| return 0; | |
| err: | |
| fhci_ep0_free(usb); | |
| kfree(ep); | |
| fhci_err(usb->fhci, "no memory for the %s\n", err_for); | |
| return -ENOMEM; | |
| } | |
| /* | |
| * initialize the endpoint register according to the given parameters | |
| * | |
| * artuments: | |
| * usb A pointer to the data strucutre of the USB | |
| * ep A pointer to the endpoint structre | |
| * data_mem The data memory partition(BUS) | |
| */ | |
| void fhci_init_ep_registers(struct fhci_usb *usb, struct endpoint *ep, | |
| enum fhci_mem_alloc data_mem) | |
| { | |
| u8 rt; | |
| /* set the endpoint registers according to the endpoint */ | |
| out_be16(&usb->fhci->regs->usb_usep[0], | |
| USB_TRANS_CTR | USB_EP_MF | USB_EP_RTE); | |
| out_be16(&usb->fhci->pram->ep_ptr[0], | |
| cpm_muram_offset(ep->ep_pram_ptr)); | |
| rt = (BUS_MODE_BO_BE | BUS_MODE_GBL); | |
| #ifdef MULTI_DATA_BUS | |
| if (data_mem == MEM_SECONDARY) | |
| rt |= BUS_MODE_DTB; | |
| #endif | |
| out_8(&ep->ep_pram_ptr->rx_func_code, rt); | |
| out_8(&ep->ep_pram_ptr->tx_func_code, rt); | |
| out_be16(&ep->ep_pram_ptr->rx_buff_len, 1028); | |
| out_be16(&ep->ep_pram_ptr->rx_base, 0); | |
| out_be16(&ep->ep_pram_ptr->tx_base, cpm_muram_offset(ep->td_base)); | |
| out_be16(&ep->ep_pram_ptr->rx_bd_ptr, 0); | |
| out_be16(&ep->ep_pram_ptr->tx_bd_ptr, cpm_muram_offset(ep->td_base)); | |
| out_be32(&ep->ep_pram_ptr->tx_state, 0); | |
| } | |
| /* | |
| * Collect the submitted frames and inform the application about them | |
| * It is also preparing the TDs for new frames. If the Tx interrupts | |
| * are disabled, the application should call that routine to get | |
| * confirmation about the submitted frames. Otherwise, the routine is | |
| * called from the interrupt service routine during the Tx interrupt. | |
| * In that case the application is informed by calling the application | |
| * specific 'fhci_transaction_confirm' routine | |
| */ | |
| static void fhci_td_transaction_confirm(struct fhci_usb *usb) | |
| { | |
| struct endpoint *ep = usb->ep0; | |
| struct packet *pkt; | |
| struct usb_td __iomem *td; | |
| u16 extra_data; | |
| u16 td_status; | |
| u16 td_length; | |
| u32 buf; | |
| /* | |
| * collect transmitted BDs from the chip. The routine clears all BDs | |
| * with R bit = 0 and the pointer to data buffer is not NULL, that is | |
| * BDs which point to the transmitted data buffer | |
| */ | |
| while (1) { | |
| td = ep->conf_td; | |
| td_status = in_be16(&td->status); | |
| td_length = in_be16(&td->length); | |
| buf = in_be32(&td->buf_ptr); | |
| extra_data = in_be16(&td->extra); | |
| /* check if the TD is empty */ | |
| if (!(!(td_status & TD_R) && ((td_status & ~TD_W) || buf))) | |
| break; | |
| /* check if it is a dummy buffer */ | |
| else if ((buf == DUMMY_BD_BUFFER) && !(td_status & ~TD_W)) | |
| break; | |
| /* mark TD as empty */ | |
| clrbits16(&td->status, ~TD_W); | |
| out_be16(&td->length, 0); | |
| out_be32(&td->buf_ptr, 0); | |
| out_be16(&td->extra, 0); | |
| /* advance the TD pointer */ | |
| ep->conf_td = next_bd(ep->td_base, ep->conf_td, td_status); | |
| /* check if it is a dummy buffer(type2) */ | |
| if ((buf == DUMMY2_BD_BUFFER) && !(td_status & ~TD_W)) | |
| continue; | |
| pkt = cq_get(&ep->conf_frame_Q); | |
| if (!pkt) | |
| fhci_err(usb->fhci, "no frame to confirm\n"); | |
| if (td_status & TD_ERRORS) { | |
| if (td_status & TD_RXER) { | |
| if (td_status & TD_CR) | |
| pkt->status = USB_TD_RX_ER_CRC; | |
| else if (td_status & TD_AB) | |
| pkt->status = USB_TD_RX_ER_BITSTUFF; | |
| else if (td_status & TD_OV) | |
| pkt->status = USB_TD_RX_ER_OVERUN; | |
| else if (td_status & TD_BOV) | |
| pkt->status = USB_TD_RX_DATA_OVERUN; | |
| else if (td_status & TD_NO) | |
| pkt->status = USB_TD_RX_ER_NONOCT; | |
| else | |
| fhci_err(usb->fhci, "illegal error " | |
| "occurred\n"); | |
| } else if (td_status & TD_NAK) | |
| pkt->status = USB_TD_TX_ER_NAK; | |
| else if (td_status & TD_TO) | |
| pkt->status = USB_TD_TX_ER_TIMEOUT; | |
| else if (td_status & TD_UN) | |
| pkt->status = USB_TD_TX_ER_UNDERUN; | |
| else if (td_status & TD_STAL) | |
| pkt->status = USB_TD_TX_ER_STALL; | |
| else | |
| fhci_err(usb->fhci, "illegal error occurred\n"); | |
| } else if ((extra_data & TD_TOK_IN) && | |
| pkt->len > td_length - CRC_SIZE) { | |
| pkt->status = USB_TD_RX_DATA_UNDERUN; | |
| } | |
| if (extra_data & TD_TOK_IN) | |
| pkt->len = td_length - CRC_SIZE; | |
| else if (pkt->info & PKT_ZLP) | |
| pkt->len = 0; | |
| else | |
| pkt->len = td_length; | |
| fhci_transaction_confirm(usb, pkt); | |
| } | |
| } | |
| /* | |
| * Submitting a data frame to a specified endpoint of a USB device | |
| * The frame is put in the driver's transmit queue for this endpoint | |
| * | |
| * Arguments: | |
| * usb A pointer to the USB structure | |
| * pkt A pointer to the user frame structure | |
| * trans_type Transaction tyep - IN,OUT or SETUP | |
| * dest_addr Device address - 0~127 | |
| * dest_ep Endpoint number of the device - 0~16 | |
| * trans_mode Pipe type - ISO,Interrupt,bulk or control | |
| * dest_speed USB speed - Low speed or FULL speed | |
| * data_toggle Data sequence toggle - 0 or 1 | |
| */ | |
| u32 fhci_host_transaction(struct fhci_usb *usb, | |
| struct packet *pkt, | |
| enum fhci_ta_type trans_type, | |
| u8 dest_addr, | |
| u8 dest_ep, | |
| enum fhci_tf_mode trans_mode, | |
| enum fhci_speed dest_speed, u8 data_toggle) | |
| { | |
| struct endpoint *ep = usb->ep0; | |
| struct usb_td __iomem *td; | |
| u16 extra_data; | |
| u16 td_status; | |
| fhci_usb_disable_interrupt(usb); | |
| /* start from the next BD that should be filled */ | |
| td = ep->empty_td; | |
| td_status = in_be16(&td->status); | |
| if (td_status & TD_R && in_be16(&td->length)) { | |
| /* if the TD is not free */ | |
| fhci_usb_enable_interrupt(usb); | |
| return -1; | |
| } | |
| /* get the next TD in the ring */ | |
| ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); | |
| fhci_usb_enable_interrupt(usb); | |
| pkt->priv_data = td; | |
| out_be32(&td->buf_ptr, virt_to_phys(pkt->data)); | |
| /* sets up transaction parameters - addr,endp,dir,and type */ | |
| extra_data = (dest_ep << TD_ENDP_SHIFT) | dest_addr; | |
| switch (trans_type) { | |
| case FHCI_TA_IN: | |
| extra_data |= TD_TOK_IN; | |
| break; | |
| case FHCI_TA_OUT: | |
| extra_data |= TD_TOK_OUT; | |
| break; | |
| case FHCI_TA_SETUP: | |
| extra_data |= TD_TOK_SETUP; | |
| break; | |
| } | |
| if (trans_mode == FHCI_TF_ISO) | |
| extra_data |= TD_ISO; | |
| out_be16(&td->extra, extra_data); | |
| /* sets up the buffer descriptor */ | |
| td_status = ((td_status & TD_W) | TD_R | TD_L | TD_I | TD_CNF); | |
| if (!(pkt->info & PKT_NO_CRC)) | |
| td_status |= TD_TC; | |
| switch (trans_type) { | |
| case FHCI_TA_IN: | |
| if (data_toggle) | |
| pkt->info |= PKT_PID_DATA1; | |
| else | |
| pkt->info |= PKT_PID_DATA0; | |
| break; | |
| default: | |
| if (data_toggle) { | |
| td_status |= TD_PID_DATA1; | |
| pkt->info |= PKT_PID_DATA1; | |
| } else { | |
| td_status |= TD_PID_DATA0; | |
| pkt->info |= PKT_PID_DATA0; | |
| } | |
| break; | |
| } | |
| if ((dest_speed == FHCI_LOW_SPEED) && | |
| (usb->port_status == FHCI_PORT_FULL)) | |
| td_status |= TD_LSP; | |
| out_be16(&td->status, td_status); | |
| /* set up buffer length */ | |
| if (trans_type == FHCI_TA_IN) | |
| out_be16(&td->length, pkt->len + CRC_SIZE); | |
| else | |
| out_be16(&td->length, pkt->len); | |
| /* put the frame to the confirmation queue */ | |
| cq_put(&ep->conf_frame_Q, pkt); | |
| if (cq_howmany(&ep->conf_frame_Q) == 1) | |
| out_8(&usb->fhci->regs->usb_uscom, USB_CMD_STR_FIFO); | |
| return 0; | |
| } | |
| /* Reset the Tx BD ring */ | |
| void fhci_flush_bds(struct fhci_usb *usb) | |
| { | |
| u16 extra_data; | |
| u16 td_status; | |
| u32 buf; | |
| struct usb_td __iomem *td; | |
| struct endpoint *ep = usb->ep0; | |
| td = ep->td_base; | |
| while (1) { | |
| td_status = in_be16(&td->status); | |
| buf = in_be32(&td->buf_ptr); | |
| extra_data = in_be16(&td->extra); | |
| /* if the TD is not empty - we'll confirm it as Timeout */ | |
| if (td_status & TD_R) | |
| out_be16(&td->status, (td_status & ~TD_R) | TD_TO); | |
| /* if this TD is dummy - let's skip this TD */ | |
| else if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) | |
| out_be32(&td->buf_ptr, DUMMY2_BD_BUFFER); | |
| /* if this is the last TD - break */ | |
| if (td_status & TD_W) | |
| break; | |
| td++; | |
| } | |
| fhci_td_transaction_confirm(usb); | |
| td = ep->td_base; | |
| do { | |
| out_be16(&td->status, 0); | |
| out_be16(&td->length, 0); | |
| out_be32(&td->buf_ptr, 0); | |
| out_be16(&td->extra, 0); | |
| td++; | |
| } while (!(in_be16(&td->status) & TD_W)); | |
| out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ | |
| out_be16(&td->length, 0); | |
| out_be32(&td->buf_ptr, 0); | |
| out_be16(&td->extra, 0); | |
| out_be16(&ep->ep_pram_ptr->tx_bd_ptr, | |
| in_be16(&ep->ep_pram_ptr->tx_base)); | |
| out_be32(&ep->ep_pram_ptr->tx_state, 0); | |
| out_be16(&ep->ep_pram_ptr->tx_cnt, 0); | |
| ep->empty_td = ep->td_base; | |
| ep->conf_td = ep->td_base; | |
| } | |
| /* | |
| * Flush all transmitted packets from TDs in the actual frame. | |
| * This routine is called when something wrong with the controller and | |
| * we want to get rid of the actual frame and start again next frame | |
| */ | |
| void fhci_flush_actual_frame(struct fhci_usb *usb) | |
| { | |
| u8 mode; | |
| u16 tb_ptr; | |
| u16 extra_data; | |
| u16 td_status; | |
| u32 buf_ptr; | |
| struct usb_td __iomem *td; | |
| struct endpoint *ep = usb->ep0; | |
| /* disable the USB controller */ | |
| mode = in_8(&usb->fhci->regs->usb_usmod); | |
| out_8(&usb->fhci->regs->usb_usmod, mode & ~USB_MODE_EN); | |
| tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); | |
| td = cpm_muram_addr(tb_ptr); | |
| td_status = in_be16(&td->status); | |
| buf_ptr = in_be32(&td->buf_ptr); | |
| extra_data = in_be16(&td->extra); | |
| do { | |
| if (td_status & TD_R) { | |
| out_be16(&td->status, (td_status & ~TD_R) | TD_TO); | |
| } else { | |
| out_be32(&td->buf_ptr, 0); | |
| ep->already_pushed_dummy_bd = false; | |
| break; | |
| } | |
| /* advance the TD pointer */ | |
| td = next_bd(ep->td_base, td, td_status); | |
| td_status = in_be16(&td->status); | |
| buf_ptr = in_be32(&td->buf_ptr); | |
| extra_data = in_be16(&td->extra); | |
| } while ((td_status & TD_R) || buf_ptr); | |
| fhci_td_transaction_confirm(usb); | |
| out_be16(&ep->ep_pram_ptr->tx_bd_ptr, | |
| in_be16(&ep->ep_pram_ptr->tx_base)); | |
| out_be32(&ep->ep_pram_ptr->tx_state, 0); | |
| out_be16(&ep->ep_pram_ptr->tx_cnt, 0); | |
| ep->empty_td = ep->td_base; | |
| ep->conf_td = ep->td_base; | |
| usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; | |
| /* reset the event register */ | |
| out_be16(&usb->fhci->regs->usb_usber, 0xffff); | |
| /* enable the USB controller */ | |
| out_8(&usb->fhci->regs->usb_usmod, mode | USB_MODE_EN); | |
| } | |
| /* handles Tx confirm and Tx error interrupt */ | |
| void fhci_tx_conf_interrupt(struct fhci_usb *usb) | |
| { | |
| fhci_td_transaction_confirm(usb); | |
| /* | |
| * Schedule another transaction to this frame only if we have | |
| * already confirmed all transaction in the frame. | |
| */ | |
| if (((fhci_get_sof_timer_count(usb) < usb->max_frame_usage) || | |
| (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION)) && | |
| (list_empty(&usb->actual_frame->tds_list))) | |
| fhci_schedule_transactions(usb); | |
| } | |
| void fhci_host_transmit_actual_frame(struct fhci_usb *usb) | |
| { | |
| u16 tb_ptr; | |
| u16 td_status; | |
| struct usb_td __iomem *td; | |
| struct endpoint *ep = usb->ep0; | |
| tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); | |
| td = cpm_muram_addr(tb_ptr); | |
| if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) { | |
| struct usb_td __iomem *old_td = td; | |
| ep->already_pushed_dummy_bd = false; | |
| td_status = in_be16(&td->status); | |
| /* gets the next TD in the ring */ | |
| td = next_bd(ep->td_base, td, td_status); | |
| tb_ptr = cpm_muram_offset(td); | |
| out_be16(&ep->ep_pram_ptr->tx_bd_ptr, tb_ptr); | |
| /* start transmit only if we have something in the TDs */ | |
| if (in_be16(&td->status) & TD_R) | |
| out_8(&usb->fhci->regs->usb_uscom, USB_CMD_STR_FIFO); | |
| if (in_be32(&ep->conf_td->buf_ptr) == DUMMY_BD_BUFFER) { | |
| out_be32(&old_td->buf_ptr, 0); | |
| ep->conf_td = next_bd(ep->td_base, ep->conf_td, | |
| td_status); | |
| } else { | |
| out_be32(&old_td->buf_ptr, DUMMY2_BD_BUFFER); | |
| } | |
| } | |
| } |