/* * c67x00-sched.c: Cypress C67X00 USB Host Controller Driver - TD scheduling * * Copyright (C) 2006-2008 Barco N.V. * Derived from the Cypress cy7c67200/300 ezusb linux driver and * based on multiple host controller drivers inside the linux kernel. * * 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., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. */ #include #include #include "c67x00.h" #include "c67x00-hcd.h" /* * These are the stages for a control urb, they are kept * in both urb->interval and td->privdata. */ #define SETUP_STAGE 0 #define DATA_STAGE 1 #define STATUS_STAGE 2 /* -------------------------------------------------------------------------- */ /** * struct c67x00_ep_data: Host endpoint data structure */ struct c67x00_ep_data { struct list_head queue; struct list_head node; struct usb_host_endpoint *hep; struct usb_device *dev; u16 next_frame; /* For int/isoc transactions */ }; /** * struct c67x00_td * * Hardware parts are little endiannes, SW in CPU endianess. */ struct c67x00_td { /* HW specific part */ __le16 ly_base_addr; /* Bytes 0-1 */ __le16 port_length; /* Bytes 2-3 */ u8 pid_ep; /* Byte 4 */ u8 dev_addr; /* Byte 5 */ u8 ctrl_reg; /* Byte 6 */ u8 status; /* Byte 7 */ u8 retry_cnt; /* Byte 8 */ #define TT_OFFSET 2 #define TT_CONTROL 0 #define TT_ISOCHRONOUS 1 #define TT_BULK 2 #define TT_INTERRUPT 3 u8 residue; /* Byte 9 */ __le16 next_td_addr; /* Bytes 10-11 */ /* SW part */ struct list_head td_list; u16 td_addr; void *data; struct urb *urb; unsigned long privdata; /* These are needed for handling the toggle bits: * an urb can be dequeued while a td is in progress * after checking the td, the toggle bit might need to * be fixed */ struct c67x00_ep_data *ep_data; unsigned int pipe; }; struct c67x00_urb_priv { struct list_head hep_node; struct urb *urb; int port; int cnt; /* packet number for isoc */ int status; struct c67x00_ep_data *ep_data; }; #define td_udev(td) ((td)->ep_data->dev) #define CY_TD_SIZE 12 #define TD_PIDEP_OFFSET 0x04 #define TD_PIDEPMASK_PID 0xF0 #define TD_PIDEPMASK_EP 0x0F #define TD_PORTLENMASK_DL 0x02FF #define TD_PORTLENMASK_PN 0xC000 #define TD_STATUS_OFFSET 0x07 #define TD_STATUSMASK_ACK 0x01 #define TD_STATUSMASK_ERR 0x02 #define TD_STATUSMASK_TMOUT 0x04 #define TD_STATUSMASK_SEQ 0x08 #define TD_STATUSMASK_SETUP 0x10 #define TD_STATUSMASK_OVF 0x20 #define TD_STATUSMASK_NAK 0x40 #define TD_STATUSMASK_STALL 0x80 #define TD_ERROR_MASK (TD_STATUSMASK_ERR | TD_STATUSMASK_TMOUT | \ TD_STATUSMASK_STALL) #define TD_RETRYCNT_OFFSET 0x08 #define TD_RETRYCNTMASK_ACT_FLG 0x10 #define TD_RETRYCNTMASK_TX_TYPE 0x0C #define TD_RETRYCNTMASK_RTY_CNT 0x03 #define TD_RESIDUE_OVERFLOW 0x80 #define TD_PID_IN 0x90 /* Residue: signed 8bits, neg -> OVERFLOW, pos -> UNDERFLOW */ #define td_residue(td) ((__s8)(td->residue)) #define td_ly_base_addr(td) (__le16_to_cpu((td)->ly_base_addr)) #define td_port_length(td) (__le16_to_cpu((td)->port_length)) #define td_next_td_addr(td) (__le16_to_cpu((td)->next_td_addr)) #define td_active(td) ((td)->retry_cnt & TD_RETRYCNTMASK_ACT_FLG) #define td_length(td) (td_port_length(td) & TD_PORTLENMASK_DL) #define td_sequence_ok(td) (!td->status || \ (!(td->status & TD_STATUSMASK_SEQ) == \ !(td->ctrl_reg & SEQ_SEL))) #define td_acked(td) (!td->status || \ (td->status & TD_STATUSMASK_ACK)) #define td_actual_bytes(td) (td_length(td) - td_residue(td)) /* -------------------------------------------------------------------------- */ #ifdef DEBUG /** * dbg_td - Dump the contents of the TD */ static void dbg_td(struct c67x00_hcd *c67x00, struct c67x00_td *td, char *msg) { struct device *dev = c67x00_hcd_dev(c67x00); dev_dbg(dev, "### %s at 0x%04x\n", msg, td->td_addr); dev_dbg(dev, "urb: 0x%p\n", td->urb); dev_dbg(dev, "endpoint: %4d\n", usb_pipeendpoint(td->pipe)); dev_dbg(dev, "pipeout: %4d\n", usb_pipeout(td->pipe)); dev_dbg(dev, "ly_base_addr: 0x%04x\n", td_ly_base_addr(td)); dev_dbg(dev, "port_length: 0x%04x\n", td_port_length(td)); dev_dbg(dev, "pid_ep: 0x%02x\n", td->pid_ep); dev_dbg(dev, "dev_addr: 0x%02x\n", td->dev_addr); dev_dbg(dev, "ctrl_reg: 0x%02x\n", td->ctrl_reg); dev_dbg(dev, "status: 0x%02x\n", td->status); dev_dbg(dev, "retry_cnt: 0x%02x\n", td->retry_cnt); dev_dbg(dev, "residue: 0x%02x\n", td->residue); dev_dbg(dev, "next_td_addr: 0x%04x\n", td_next_td_addr(td)); dev_dbg(dev, "data:"); print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, 1, td->data, td_length(td), 1); } #else /* DEBUG */ static inline void dbg_td(struct c67x00_hcd *c67x00, struct c67x00_td *td, char *msg) { } #endif /* DEBUG */ /* -------------------------------------------------------------------------- */ /* Helper functions */ static inline u16 c67x00_get_current_frame_number(struct c67x00_hcd *c67x00) { return c67x00_ll_husb_get_frame(c67x00->sie) & HOST_FRAME_MASK; } /** * frame_add * Software wraparound for framenumbers. */ static inline u16 frame_add(u16 a, u16 b) { return (a + b) & HOST_FRAME_MASK; } /** * frame_after - is frame a after frame b */ static inline int frame_after(u16 a, u16 b) { return ((HOST_FRAME_MASK + a - b) & HOST_FRAME_MASK) < (HOST_FRAME_MASK / 2); } /** * frame_after_eq - is frame a after or equal to frame b */ static inline int frame_after_eq(u16 a, u16 b) { return ((HOST_FRAME_MASK + 1 + a - b) & HOST_FRAME_MASK) < (HOST_FRAME_MASK / 2); } /* -------------------------------------------------------------------------- */ /** * c67x00_release_urb - remove link from all tds to this urb * Disconnects the urb from it's tds, so that it can be given back. * pre: urb->hcpriv != NULL */ static void c67x00_release_urb(struct c67x00_hcd *c67x00, struct urb *urb) { struct c67x00_td *td; struct c67x00_urb_priv *urbp; BUG_ON(!urb); c67x00->urb_count--; if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { c67x00->urb_iso_count--; if (c67x00->urb_iso_count == 0) c67x00->max_frame_bw = MAX_FRAME_BW_STD; } /* TODO this might be not so efficient when we've got many urbs! * Alternatives: * * only clear when needed * * keep a list of tds with each urbp */ list_for_each_entry(td, &c67x00->td_list, td_list) if (urb == td->urb) td->urb = NULL; urbp = urb->hcpriv; urb->hcpriv = NULL; list_del(&urbp->hep_node); kfree(urbp); } /* -------------------------------------------------------------------------- */ static struct c67x00_ep_data * c67x00_ep_data_alloc(struct c67x00_hcd *c67x00, struct urb *urb) { struct usb_host_endpoint *hep = urb->ep; struct c67x00_ep_data *ep_data; int type; c67x00->current_frame = c67x00_get_current_frame_number(c67x00); /* Check if endpoint already has a c67x00_ep_data struct allocated */ if (hep->hcpriv) { ep_data = hep->hcpriv; if (frame_after(c67x00->current_frame, ep_data->next_frame)) ep_data->next_frame = frame_add(c67x00->current_frame, 1); return hep->hcpriv; } /* Allocate and initialize a new c67x00 endpoint data structure */ ep_data = kzalloc(sizeof(*ep_data), GFP_ATOMIC); if (!ep_data) return NULL; INIT_LIST_HEAD(&ep_data->queue); INIT_LIST_HEAD(&ep_data->node); ep_data->hep = hep; /* hold a reference to udev as long as this endpoint lives, * this is needed to possibly fix the data toggle */ ep_data->dev = usb_get_dev(urb->dev); hep->hcpriv = ep_data; /* For ISOC and INT endpoints, start ASAP: */ ep_data->next_frame = frame_add(c67x00->current_frame, 1); /* Add the endpoint data to one of the pipe lists; must be added in order of endpoint address */ type = usb_pipetype(urb->pipe); if (list_empty(&ep_data->node)) { list_add(&ep_data->node, &c67x00->list[type]); } else { struct c67x00_ep_data *prev; list_for_each_entry(prev, &c67x00->list[type], node) { if (prev->hep->desc.bEndpointAddress > hep->desc.bEndpointAddress) { list_add(&ep_data->node, prev->node.prev); break; } } } return ep_data; } static int c67x00_ep_data_free(struct usb_host_endpoint *hep) { struct c67x00_ep_data *ep_data = hep->hcpriv; if (!ep_data) return 0; if (!list_empty(&ep_data->queue)) return -EBUSY; usb_put_dev(ep_data->dev); list_del(&ep_data->queue); list_del(&ep_data->node); kfree(ep_data); hep->hcpriv = NULL; return 0; } void c67x00_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); unsigned long flags; if (!list_empty(&ep->urb_list)) dev_warn(c67x00_hcd_dev(c67x00), "error: urb list not empty\n"); spin_lock_irqsave(&c67x00->lock, flags); /* loop waiting for all transfers in the endpoint queue to complete */ while (c67x00_ep_data_free(ep)) { /* Drop the lock so we can sleep waiting for the hardware */ spin_unlock_irqrestore(&c67x00->lock, flags); /* it could happen that we reinitialize this completion, while * somebody was waiting for that completion. The timeout and * while loop handle such cases, but this might be improved */ INIT_COMPLETION(c67x00->endpoint_disable); c67x00_sched_kick(c67x00); wait_for_completion_timeout(&c67x00->endpoint_disable, 1 * HZ); spin_lock_irqsave(&c67x00->lock, flags); } spin_unlock_irqrestore(&c67x00->lock, flags); } /* -------------------------------------------------------------------------- */ static inline int get_root_port(struct usb_device *dev) { while (dev->parent->parent) dev = dev->parent; return dev->portnum; } int c67x00_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { int ret; unsigned long flags; struct c67x00_urb_priv *urbp; struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); int port = get_root_port(urb->dev)-1; spin_lock_irqsave(&c67x00->lock, flags); /* Make sure host controller is running */ if (!HC_IS_RUNNING(hcd->state)) { ret = -ENODEV; goto err_not_linked; } ret = usb_hcd_link_urb_to_ep(hcd, urb); if (ret) goto err_not_linked; /* Allocate and initialize urb private data */ urbp = kzalloc(sizeof(*urbp), mem_flags); if (!urbp) { ret = -ENOMEM; goto err_urbp; } INIT_LIST_HEAD(&urbp->hep_node); urbp->urb = urb; urbp->port = port; urbp->ep_data = c67x00_ep_data_alloc(c67x00, urb); if (!urbp->ep_data) { ret = -ENOMEM; goto err_epdata; } /* TODO claim bandwidth with usb_claim_bandwidth? * also release it somewhere! */ urb->hcpriv = urbp; urb->actual_length = 0; /* Nothing received/transmitted yet */ switch (usb_pipetype(urb->pipe)) { case PIPE_CONTROL: urb->interval = SETUP_STAGE; break; case PIPE_INTERRUPT: break; case PIPE_BULK: break; case PIPE_ISOCHRONOUS: if (c67x00->urb_iso_count == 0) c67x00->max_frame_bw = MAX_FRAME_BW_ISO; c67x00->urb_iso_count++; /* Assume always URB_ISO_ASAP, FIXME */ if (list_empty(&urbp->ep_data->queue)) urb->start_frame = urbp->ep_data->next_frame; else { /* Go right after the last one */ struct urb *last_urb; last_urb = list_entry(urbp->ep_data->queue.prev, struct c67x00_urb_priv, hep_node)->urb; urb->start_frame = frame_add(last_urb->start_frame, last_urb->number_of_packets * last_urb->interval); } urbp->cnt = 0; break; } /* Add the URB to the endpoint queue */ list_add_tail(&urbp->hep_node, &urbp->ep_data->queue); /* If this is the only URB, kick start the controller */ if (!c67x00->urb_count++) c67x00_ll_hpi_enable_sofeop(c67x00->sie); c67x00_sched_kick(c67x00); spin_unlock_irqrestore(&c67x00->lock, flags); return 0; err_epdata: kfree(urbp); err_urbp: usb_hcd_unlink_urb_from_ep(hcd, urb); err_not_linked: spin_unlock_irqrestore(&c67x00->lock, flags); return ret; } int c67x00_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); unsigned long flags; int rc; spin_lock_irqsave(&c67x00->lock, flags); rc = usb_hcd_check_unlink_urb(hcd, urb, status); if (rc) goto done; c67x00_release_urb(c67x00, urb); usb_hcd_unlink_urb_from_ep(hcd, urb); spin_unlock(&c67x00->lock); usb_hcd_giveback_urb(hcd, urb, status); spin_lock(&c67x00->lock); spin_unlock_irqrestore(&c67x00->lock, flags); return 0; done: spin_unlock_irqrestore(&c67x00->lock, flags); return rc; } /* -------------------------------------------------------------------------- */ /* * pre: c67x00 locked, urb unlocked */ static void c67x00_giveback_urb(struct c67x00_hcd *c67x00, struct urb *urb, int status) { struct c67x00_urb_priv *urbp; if (!urb) return; urbp = urb->hcpriv; urbp->status = status; list_del_init(&urbp->hep_node); c67x00_release_urb(c67x00, urb); usb_hcd_unlink_urb_from_ep(c67x00_hcd_to_hcd(c67x00), urb); spin_unlock(&c67x00->lock); usb_hcd_giveback_urb(c67x00_hcd_to_hcd(c67x00), urb, urbp->status); spin_lock(&c67x00->lock); } /* -------------------------------------------------------------------------- */ static int c67x00_claim_frame_bw(struct c67x00_hcd *c67x00, struct urb *urb, int len, int periodic) { struct c67x00_urb_priv *urbp = urb->hcpriv; int bit_time; /* According to the C67x00 BIOS user manual, page 3-18,19, the * following calculations provide the full speed bit times for * a transaction. * * FS(in) = 112.5 + 9.36*BC + HOST_DELAY * FS(in,iso) = 90.5 + 9.36*BC + HOST_DELAY * FS(out) = 112.5 + 9.36*BC + HOST_DELAY * FS(out,iso) = 78.4 + 9.36*BC + HOST_DELAY * LS(in) = 802.4 + 75.78*BC + HOST_DELAY * LS(out) = 802.6 + 74.67*BC + HOST_DELAY * * HOST_DELAY == 106 for the c67200 and c67300. */ /* make calculations in 1/100 bit times to maintain resolution */ if (urbp->ep_data->dev->speed == USB_SPEED_LOW) { /* Low speed pipe */ if (usb_pipein(urb->pipe)) bit_time = 80240 + 7578*len; else bit_time = 80260 + 7467*len; } else { /* FS pipes */ if (usb_pipeisoc(urb->pipe)) bit_time = usb_pipein(urb->pipe) ? 9050 : 7840; else bit_time = 11250; bit_time += 936*len; } /* Scale back down to integer bit times. Use a host delay of 106. * (this is the only place it is used) */ bit_time = ((bit_time+50) / 100) + 106; if (unlikely(bit_time + c67x00->bandwidth_allocated >= c67x00->max_frame_bw)) return -EMSGSIZE; if (unlikely(c67x00->next_td_addr + CY_TD_SIZE >= c67x00->td_base_addr + SIE_TD_SIZE)) return -EMSGSIZE; if (unlikely(c67x00->next_buf_addr + len >= c67x00->buf_base_addr + SIE_TD_BUF_SIZE)) return -EMSGSIZE; if (periodic) { if (unlikely(bit_time + c67x00->periodic_bw_allocated >= MAX_PERIODIC_BW(c67x00->max_frame_bw))) return -EMSGSIZE; c67x00->periodic_bw_allocated += bit_time; } c67x00->bandwidth_allocated += bit_time; return 0; } /* -------------------------------------------------------------------------- */ /** * td_addr and buf_addr must be word aligned */ static int c67x00_create_td(struct c67x00_hcd *c67x00, struct urb *urb, void *data, int len, int pid, int toggle, unsigned long privdata) { struct c67x00_td *td; struct c67x00_urb_priv *urbp = urb->hcpriv; const __u8 active_flag = 1, retry_cnt = 1; __u8 cmd = 0; int tt = 0; if (c67x00_claim_frame_bw(c67x00, urb, len, usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe))) return -EMSGSIZE; /* Not really an error, but expected */ td = kzalloc(sizeof(*td), GFP_ATOMIC); if (!td) return -ENOMEM; td->pipe = urb->pipe; td->ep_data = urbp->ep_data; if ((td_udev(td)->speed == USB_SPEED_LOW) && !(c67x00->low_speed_ports & (1 << urbp->port))) cmd |= PREAMBLE_EN; switch (usb_pipetype(td->pipe)) { case PIPE_ISOCHRONOUS: tt = TT_ISOCHRONOUS; cmd |= ISO_EN; break; case PIPE_CONTROL: tt = TT_CONTROL; break; case PIPE_BULK: tt = TT_BULK; break; case PIPE_INTERRUPT: tt = TT_INTERRUPT; break; } if (toggle) cmd |= SEQ_SEL; cmd |= ARM_EN; /* SW part */ td->td_addr = c67x00->next_td_addr; c67x00->next_td_addr = c67x00->next_td_addr + CY_TD_SIZE; /* HW part */ td->ly_base_addr = __cpu_to_le16(c67x00->next_buf_addr); td->port_length = __cpu_to_le16((c67x00->sie->sie_num << 15) | (urbp->port << 14) | (len & 0x3FF)); td->pid_ep = ((pid & 0xF) << TD_PIDEP_OFFSET) | (usb_pipeendpoint(td->pipe) & 0xF); td->dev_addr = usb_pipedevice(td->pipe) & 0x7F; td->ctrl_reg = cmd; td->status = 0; td->retry_cnt = (tt << TT_OFFSET) | (active_flag << 4) | retry_cnt; td->residue = 0; td->next_td_addr = __cpu_to_le16(c67x00->next_td_addr); /* SW part */ td->data = data; td->urb = urb; td->privdata = privdata; c67x00->next_buf_addr += (len + 1) & ~0x01; /* properly align */ list_add_tail(&td->td_list, &c67x00->td_list); return 0; } static inline void c67x00_release_td(struct c67x00_td *td) { list_del_init(&td->td_list); kfree(td); } /* -------------------------------------------------------------------------- */ static int c67x00_add_data_urb(struct c67x00_hcd *c67x00, struct urb *urb) { int remaining; int toggle; int pid; int ret = 0; int maxps; int need_empty; toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); remaining = urb->transfer_buffer_length - urb->actual_length; maxps = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); need_empty = (urb->transfer_flags & URB_ZERO_PACKET) && usb_pipeout(urb->pipe) && !(remaining % maxps); while (remaining || need_empty) { int len; char *td_buf; len = (remaining > maxps) ? maxps : remaining; if (!len) need_empty = 0; pid = usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN; td_buf = urb->transfer_buffer + urb->transfer_buffer_length - remaining; ret = c67x00_create_td(c67x00, urb, td_buf, len, pid, toggle, DATA_STAGE); if (ret) return ret; /* td wasn't created */ toggle ^= 1; remaining -= len; if (usb_pipecontrol(urb->pipe)) break; } return 0; } /** * return 0 in case more bandwidth is available, else errorcode */ static int c67x00_add_ctrl_urb(struct c67x00_hcd *c67x00, struct urb *urb) { int ret; int pid; switch (urb->interval) { default: case SETUP_STAGE: ret = c67x00_create_td(c67x00, urb, urb->setup_packet, 8, USB_PID_SETUP, 0, SETUP_STAGE); if (ret) return ret; urb->interval = SETUP_STAGE; usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), 1); break; case DATA_STAGE: if (urb->transfer_buffer_length) { ret = c67x00_add_data_urb(c67x00, urb); if (ret) return ret; break; } /* else fallthrough */ case STATUS_STAGE: pid = !usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN; ret = c67x00_create_td(c67x00, urb, NULL, 0, pid, 1, STATUS_STAGE); if (ret) return ret; break; } return 0; } /* * return 0 in case more bandwidth is available, else errorcode */ static int c67x00_add_int_urb(struct c67x00_hcd *c67x00, struct urb *urb) { struct c67x00_urb_priv *urbp = urb->hcpriv; if (frame_after_eq(c67x00->current_frame, urbp->ep_data->next_frame)) { urbp->ep_data->next_frame = frame_add(urbp->ep_data->next_frame, urb->interval); return c67x00_add_data_urb(c67x00, urb); } return 0; } static int c67x00_add_iso_urb(struct c67x00_hcd *c67x00, struct urb *urb) { struct c67x00_urb_priv *urbp = urb->hcpriv; if (frame_after_eq(c67x00->current_frame, urbp->ep_data->next_frame)) { char *td_buf; int len, pid, ret; BUG_ON(urbp->cnt >= urb->number_of_packets); td_buf = urb->transfer_buffer + urb->iso_frame_desc[urbp->cnt].offset; len = urb->iso_frame_desc[urbp->cnt].length; pid = usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN; ret = c67x00_create_td(c67x00, urb, td_buf, len, pid, 0, urbp->cnt); if (ret) { printk(KERN_DEBUG "create failed: %d\n", ret); urb->iso_frame_desc[urbp->cnt].actual_length = 0; urb->iso_frame_desc[urbp->cnt].status = ret; if (urbp->cnt + 1 == urb->number_of_packets) c67x00_giveback_urb(c67x00, urb, 0); } urbp->ep_data->next_frame = frame_add(urbp->ep_data->next_frame, urb->interval); urbp->cnt++; } return 0; } /* -------------------------------------------------------------------------- */ static void c67x00_fill_from_list(struct c67x00_hcd *c67x00, int type, int (*add)(struct c67x00_hcd *, struct urb *)) { struct c67x00_ep_data *ep_data; struct urb *urb; /* traverse every endpoint on the list */ list_for_each_entry(ep_data, &c67x00->list[type], node) { if (!list_empty(&ep_data->queue)) { /* and add the first urb */ /* isochronous transfer rely on this */ urb = list_entry(ep_data->queue.next, struct c67x00_urb_priv, hep_node)->urb; add(c67x00, urb); } } } static void c67x00_fill_frame(struct c67x00_hcd *c67x00) { struct c67x00_td *td, *ttd; /* Check if we can proceed */ if (!list_empty(&c67x00->td_list)) { dev_warn(c67x00_hcd_dev(c67x00), "TD list not empty! This should not happen!\n"); list_for_each_entry_safe(td, ttd, &c67x00->td_list, td_list) { dbg_td(c67x00, td, "Unprocessed td"); c67x00_release_td(td); } } /* Reinitialize variables */ c67x00->bandwidth_allocated = 0; c67x00->periodic_bw_allocated = 0; c67x00->next_td_addr = c67x00->td_base_addr; c67x00->next_buf_addr = c67x00->buf_base_addr; /* Fill the list */ c67x00_fill_from_list(c67x00, PIPE_ISOCHRONOUS, c67x00_add_iso_urb); c67x00_fill_from_list(c67x00, PIPE_INTERRUPT, c67x00_add_int_urb); c67x00_fill_from_list(c67x00, PIPE_CONTROL, c67x00_add_ctrl_urb); c67x00_fill_from_list(c67x00, PIPE_BULK, c67x00_add_data_urb); } /* -------------------------------------------------------------------------- */ /** * Get TD from C67X00 */ static inline void c67x00_parse_td(struct c67x00_hcd *c67x00, struct c67x00_td *td) { c67x00_ll_read_mem_le16(c67x00->sie->dev, td->td_addr, td, CY_TD_SIZE); if (usb_pipein(td->pipe) && td_actual_bytes(td)) c67x00_ll_read_mem_le16(c67x00->sie->dev, td_ly_base_addr(td), td->data, td_actual_bytes(td)); } static int c67x00_td_to_error(struct c67x00_hcd *c67x00, struct c67x00_td *td) { if (td->status & TD_STATUSMASK_ERR) { dbg_td(c67x00, td, "ERROR_FLAG"); return -EILSEQ; } if (td->status & TD_STATUSMASK_STALL) { /* dbg_td(c67x00, td, "STALL"); */ return -EPIPE; } if (td->status & TD_STATUSMASK_TMOUT) { dbg_td(c67x00, td, "TIMEOUT"); return -ETIMEDOUT; } return 0; } static inline int c67x00_end_of_data(struct c67x00_td *td) { int maxps, need_empty, remaining; struct urb *urb = td->urb; int act_bytes; act_bytes = td_actual_bytes(td); if (unlikely(!act_bytes)) return 1; /* This was an empty packet */ maxps = usb_maxpacket(td_udev(td), td->pipe, usb_pipeout(td->pipe)); if (unlikely(act_bytes < maxps)) return 1; /* Smaller then full packet */ remaining = urb->transfer_buffer_length - urb->actual_length; need_empty = (urb->transfer_flags & URB_ZERO_PACKET) && usb_pipeout(urb->pipe) && !(remaining % maxps); if (unlikely(!remaining && !need_empty)) return 1; return 0; } /* -------------------------------------------------------------------------- */ /* Remove all td's from the list which come * after last_td and are meant for the same pipe. * This is used when a short packet has occurred */ static inline void c67x00_clear_pipe(struct c67x00_hcd *c67x00, struct c67x00_td *last_td) { struct c67x00_td *td, *tmp; td = last_td; tmp = last_td; while (td->td_list.next != &c67x00->td_list) { td = list_entry(td->td_list.next, struct c67x00_td, td_list); if (td->pipe == last_td->pipe) { c67x00_release_td(td); td = tmp; } tmp = td; } } /* -------------------------------------------------------------------------- */ static void c67x00_handle_successful_td(struct c67x00_hcd *c67x00, struct c67x00_td *td) { struct urb *urb = td->urb; if (!urb) return; urb->actual_length += td_actual_bytes(td); switch (usb_pipetype(td->pipe)) { /* isochronous tds are handled separately */ case PIPE_CONTROL: switch (td->privdata) { case SETUP_STAGE: urb->interval = urb->transfer_buffer_length ? DATA_STAGE : STATUS_STAGE; /* Don't count setup_packet with normal data: */ urb->actual_length = 0; break; case DATA_STAGE: if (c67x00_end_of_data(td)) { urb->interval = STATUS_STAGE; c67x00_clear_pipe(c67x00, td); } break; case STATUS_STAGE: urb->interval = 0; c67x00_giveback_urb(c67x00, urb, 0); break; } break; case PIPE_INTERRUPT: case PIPE_BULK: if (unlikely(c67x00_end_of_data(td))) { c67x00_clear_pipe(c67x00, td); c67x00_giveback_urb(c67x00, urb, 0); } break; } } static void c67x00_handle_isoc(struct c67x00_hcd *c67x00, struct c67x00_td *td) { struct urb *urb = td->urb; struct c67x00_urb_priv *urbp; int cnt; if (!urb) return; urbp = urb->hcpriv; cnt = td->privdata; if (td->status & TD_ERROR_MASK) urb->error_count++; urb->iso_frame_desc[cnt].actual_length = td_actual_bytes(td); urb->iso_frame_desc[cnt].status = c67x00_td_to_error(c67x00, td); if (cnt + 1 == urb->number_of_packets) /* Last packet */ c67x00_giveback_urb(c67x00, urb, 0); } /* -------------------------------------------------------------------------- */ /** * c67x00_check_td_list - handle tds which have been processed by the c67x00 * pre: current_td == 0 */ static inline void c67x00_check_td_list(struct c67x00_hcd *c67x00) { struct c67x00_td *td, *tmp; struct urb *urb; int ack_ok; int clear_endpoint; list_for_each_entry_safe(td, tmp, &c67x00->td_list, td_list) { /* get the TD */ c67x00_parse_td(c67x00, td); urb = td->urb; /* urb can be NULL! */ ack_ok = 0; clear_endpoint = 1; /* Handle isochronous transfers separately */ if (usb_pipeisoc(td->pipe)) { clear_endpoint = 0; c67x00_handle_isoc(c67x00, td); goto cont; } /* When an error occurs, all td's for that pipe go into an * inactive state. This state matches successful transfers so * we must make sure not to service them. */ if (td->status & TD_ERROR_MASK) { c67x00_giveback_urb(c67x00, urb, c67x00_td_to_error(c67x00, td)); goto cont; } if ((td->status & TD_STATUSMASK_NAK) || !td_sequence_ok(td) || !td_acked(td)) goto cont; /* Sequence ok and acked, don't need to fix toggle */ ack_ok = 1; if (unlikely(td->status & TD_STATUSMASK_OVF)) { if (td_residue(td) & TD_RESIDUE_OVERFLOW) { /* Overflow */ c67x00_giveback_urb(c67x00, urb, -EOVERFLOW); goto cont; } } clear_endpoint = 0; c67x00_handle_successful_td(c67x00, td); cont: if (clear_endpoint) c67x00_clear_pipe(c67x00, td); if (ack_ok) usb_settoggle(td_udev(td), usb_pipeendpoint(td->pipe), usb_pipeout(td->pipe), !(td->ctrl_reg & SEQ_SEL)); /* next in list could have been removed, due to clear_pipe! */ tmp = list_entry(td->td_list.next, typeof(*td), td_list); c67x00_release_td(td); } } /* -------------------------------------------------------------------------- */ static inline int c67x00_all_tds_processed(struct c67x00_hcd *c67x00) { /* If all tds are processed, we can check the previous frame (if * there was any) and start our next frame. */ return !c67x00_ll_husb_get_current_td(c67x00->sie); } /** * Send td to C67X00 */ static void c67x00_send_td(struct c67x00_hcd *c67x00, struct c67x00_td *td) { int len = td_length(td); if (len && ((td->pid_ep & TD_PIDEPMASK_PID) != TD_PID_IN)) c67x00_ll_write_mem_le16(c67x00->sie->dev, td_ly_base_addr(td), td->data, len); c67x00_ll_write_mem_le16(c67x00->sie->dev, td->td_addr, td, CY_TD_SIZE); } static void c67x00_send_frame(struct c67x00_hcd *c67x00) { struct c67x00_td *td; if (list_empty(&c67x00->td_list)) dev_warn(c67x00_hcd_dev(c67x00), "%s: td list should not be empty here!\n", __func__); list_for_each_entry(td, &c67x00->td_list, td_list) { if (td->td_list.next == &c67x00->td_list) td->next_td_addr = 0; /* Last td in list */ c67x00_send_td(c67x00, td); } c67x00_ll_husb_set_current_td(c67x00->sie, c67x00->td_base_addr); } /* -------------------------------------------------------------------------- */ /** * c67x00_do_work - Schedulers state machine */ static void c67x00_do_work(struct c67x00_hcd *c67x00) { spin_lock(&c67x00->lock); /* Make sure all tds are processed */ if (!c67x00_all_tds_processed(c67x00)) goto out; c67x00_check_td_list(c67x00); /* no td's are being processed (current == 0) * and all have been "checked" */ complete(&c67x00->endpoint_disable); if (!list_empty(&c67x00->td_list)) goto out; c67x00->current_frame = c67x00_get_current_frame_number(c67x00); if (c67x00->current_frame == c67x00->last_frame) goto out; /* Don't send tds in same frame */ c67x00->last_frame = c67x00->current_frame; /* If no urbs are scheduled, our work is done */ if (!c67x00->urb_count) { c67x00_ll_hpi_disable_sofeop(c67x00->sie); goto out; } c67x00_fill_frame(c67x00); if (!list_empty(&c67x00->td_list)) /* TD's have been added to the frame */ c67x00_send_frame(c67x00); out: spin_unlock(&c67x00->lock); } /* -------------------------------------------------------------------------- */ static void c67x00_sched_tasklet(unsigned long __c67x00) { struct c67x00_hcd *c67x00 = (struct c67x00_hcd *)__c67x00; c67x00_do_work(c67x00); } void c67x00_sched_kick(struct c67x00_hcd *c67x00) { tasklet_hi_schedule(&c67x00->tasklet); } int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00) { tasklet_init(&c67x00->tasklet, c67x00_sched_tasklet, (unsigned long)c67x00); return 0; } void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00) { tasklet_kill(&c67x00->tasklet); }