/*
* 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/spinlock.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/usb.h>
#include <asm/qe.h>
#include <asm/fsl_gtm.h>
#include "../core/hcd.h"
#include "fhci.h"
static void recycle_frame(struct fhci_usb *usb, struct packet *pkt)
{
pkt->data = NULL;
pkt->len = 0;
pkt->status = USB_TD_OK;
pkt->info = 0;
pkt->priv_data = NULL;
cq_put(usb->ep0->empty_frame_Q, pkt);
}
/* confirm submitted packet */
void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt)
{
struct td *td;
struct packet *td_pkt;
struct ed *ed;
u32 trans_len;
bool td_done = false;
td = fhci_remove_td_from_frame(usb->actual_frame);
td_pkt = td->pkt;
trans_len = pkt->len;
td->status = pkt->status;
if (td->type == FHCI_TA_IN && td_pkt->info & PKT_DUMMY_PACKET) {
if ((td->data + td->actual_len) && trans_len)
memcpy(td->data + td->actual_len, pkt->data,
trans_len);
cq_put(usb->ep0->dummy_packets_Q, pkt->data);
}
recycle_frame(usb, pkt);
ed = td->ed;
if (ed->mode == FHCI_TF_ISO) {
if (ed->td_list.next->next != &ed->td_list) {
struct td *td_next =
list_entry(ed->td_list.next->next, struct td,
node);
td_next->start_frame = usb->actual_frame->frame_num;
}
td->actual_len = trans_len;
td_done = true;
} else if ((td->status & USB_TD_ERROR) &&
!(td->status & USB_TD_TX_ER_NAK)) {
/*
* There was an error on the transaction (but not NAK).
* If it is fatal error (data underrun, stall, bad pid or 3
* errors exceeded), mark this TD as done.
*/
if ((td->status & USB_TD_RX_DATA_UNDERUN) ||
(td->status & USB_TD_TX_ER_STALL) ||
(td->status & USB_TD_RX_ER_PID) ||
(++td->error_cnt >= 3)) {
ed->state = FHCI_ED_HALTED;
td_done = true;
if (td->status & USB_TD_RX_DATA_UNDERUN) {
fhci_dbg(usb->fhci, "td err fu\n");
td->toggle = !td->toggle;
td->actual_len += trans_len;
} else {
fhci_dbg(usb->fhci, "td err f!u\n");
}
} else {
fhci_dbg(usb->fhci, "td err !f\n");
/* it is not a fatal error -retry this transaction */
td->nak_cnt = 0;
td->error_cnt++;
td->status = USB_TD_OK;
}
} else if (td->status & USB_TD_TX_ER_NAK) {
/* there was a NAK response */
fhci_vdbg(usb->fhci, "td nack\n");
td->nak_cnt++;
td->error_cnt = 0;
td->status = USB_TD_OK;
} else {
/* there was no error on transaction */
td->error_cnt = 0;
td->nak_cnt = 0;
td->toggle = !td->toggle;
td->actual_len += trans_len;
if (td->len == td->actual_len)
td_done = true;
}
if (td_done)
fhci_move_td_from_ed_to_done_list(usb, ed);
}
/*
* Flush all transmitted packets from BDs
* This routine is called when disabling the USB port to flush all
* transmissions that are allready scheduled in the BDs
*/
void fhci_flush_all_transmissions(struct fhci_usb *usb)
{
u8 mode;
struct td *td;
mode = in_8(&usb->fhci->regs->usb_mod);
clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN);
fhci_flush_bds(usb);
while ((td = fhci_peek_td_from_frame(usb->actual_frame)) != NULL) {
struct packet *pkt = td->pkt;
pkt->status = USB_TD_TX_ER_TIMEOUT;
fhci_transaction_confirm(usb, pkt);
}
usb->actual_frame->frame_status = FRAME_END_TRANSMISSION;
/* reset the event register */
out_be16(&usb->fhci->regs->usb_event, 0xffff);
/* enable the USB controller */
out_8(&usb->fhci->regs->usb_mod, mode | USB_MODE_EN);
}
/*
* This function forms the packet and transmit the packet. This function
* will handle all endpoint type:ISO,interrupt,control and bulk
*/
static