[PATCH 11/11] [RFC USB POWERPC] Freescale QUICC Engine USB Host Controller
From: Anton Vorontsov <hidden>
Date: 2008-02-03 17:11:31
Subsystem:
the rest, usb subsystem · Maintainers:
Linus Torvalds, Greg Kroah-Hartman
Signed-off-by: Anton Vorontsov <redacted> --- Hi all, This is RFC for the "FHCI" USB host controller driver. My TODO list: 1. Locking completely braindead, needs fixing. I just inserted plain spin_lock()s to outline what I'm going to do. _irq and _irqsave variants will be inserted appropriately. 2. Try to get rid of -cq part of the driver and use kfifo instead; 3. Probably fix Packet-Level interface support. It was and is broken. I didn't include fhci-bds.c file in this RFC, today it's not worth looking, and weights about 60Kb additionally; 4. Make sparse happy. Will appreciate any comments, Thanks, drivers/usb/Makefile | 1 + drivers/usb/host/Kconfig | 36 + drivers/usb/host/Makefile | 2 +- drivers/usb/host/fhci-bds.c | 1696 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/fhci-cq.c | 105 +++ drivers/usb/host/fhci-dbg.c | 133 ++++ drivers/usb/host/fhci-hcd.c | 1391 +++++++++++++++++++++++++++++++++++ drivers/usb/host/fhci-hub.c | 343 +++++++++ drivers/usb/host/fhci-mem.c | 95 +++ drivers/usb/host/fhci-q.c | 495 +++++++++++++ drivers/usb/host/fhci-tds.c | 628 ++++++++++++++++ drivers/usb/host/fhci.h | 543 ++++++++++++++ 12 files changed, 5467 insertions(+), 1 deletions(-) create mode 100644 drivers/usb/host/fhci-bds.c create mode 100644 drivers/usb/host/fhci-cq.c create mode 100644 drivers/usb/host/fhci-dbg.c create mode 100644 drivers/usb/host/fhci-hcd.c create mode 100644 drivers/usb/host/fhci-hub.c create mode 100644 drivers/usb/host/fhci-mem.c create mode 100644 drivers/usb/host/fhci-q.c create mode 100644 drivers/usb/host/fhci-tds.c create mode 100644 drivers/usb/host/fhci.h
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 516a640..e0f71af 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile@@ -16,6 +16,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += host/ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_FHCI_HCD) += host/ obj-$(CONFIG_USB_ACM) += class/ obj-$(CONFIG_USB_PRINTER) += class/
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 49a91c5..6b1d408 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig@@ -261,3 +261,39 @@ config USB_R8A66597_HCD To compile this driver as a module, choose M here: the module will be called r8a66597-hcd. +config USB_FHCI_HCD + tristate "Freescale QE USB Host Controller support" + depends on USB && GENERIC_GPIO + select QE_GTM + help + Some Freescale PowerPC processors (such as MPC8360E and + MPC8323) have a Full Speed or Low Speed QE USB Host controller. + + Say "y" to enable support for such controllers, or "m" to compile + it as a module: the module will be called fhci-hcd.ko + +config FHCI_DEBUG + bool "Freescale QE USB Host Controller debug support" + depends on USB_FHCI_HCD + select DEBUG_FS + help + Say "y" to see some FHCI debug information and statistics + throught debugfs. + +choice + prompt "FHCI interface type" + depends on USB_FHCI_HCD + default FHCI_WITH_TDS + +config FHCI_WITH_BDS + bool "Packet-Level interface (BDs)" + depends on BROKEN + help + Controller uses a packet-level interface to communicate. + +config FHCI_WITH_TDS + bool "Transaction-Level interface (TDs)" + help + Controller uses a transaction-level interface to communicate. + +endchoice
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index bb8e9d4..4282205 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile@@ -16,4 +16,4 @@ obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o - +obj-$(CONFIG_USB_FHCI_HCD) += fhci-hcd.o
diff --git a/drivers/usb/host/fhci-cq.c b/drivers/usb/host/fhci-cq.c
new file mode 100644
index 0000000..299704a
--- /dev/null
+++ b/drivers/usb/host/fhci-cq.c@@ -0,0 +1,105 @@ +/* + * 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. + */ + +/* circular queue structure */ +struct cir_q { + int max; /* size of queue */ + int max_in; /* max items in queue */ + int first; /* index of first in queue */ + int last; /* index after last in queue */ + int read; /* current reading position */ + void *table[1]; /* fake size */ +}; + +/* circular queue handle */ +static int cq_howmany(struct cir_q *cq) +{ + int l = cq->last; + int f = cq->first; + + return l >= f ? l - f : l + cq->max - f; +} + +static struct cir_q *cq_new(int size) +{ + struct cir_q *cq; + + cq = kzalloc((sizeof(*cq) + size * sizeof(void *)), GFP_KERNEL); + if (cq) { + cq->max = size; + cq->first = 0; + cq->last = 0; + cq->read = 0; + cq->max_in = 0; + } + + return cq; +} + +static void cq_delete(struct cir_q *cq) +{ + kfree(cq); +} + +static int cq_put(struct cir_q *cq, void *p) +{ + int n; + int k; + + /* see if we can freely advance the last pointer */ + n = cq->last; + k = cq_howmany(cq); + if ((k + 1) >= cq->max) + return -1; + + if (++n >= cq->max) + n = 0; + + /* add element to queue */ + cq->table[cq->last] = p; + cq->last = n; + if ((k + 1) > cq->max_in) + cq->max_in = k + 1; + + return k; +} + +static void *cq_get(struct cir_q *cq) +{ + int n; + int k; + void *p; + + n = cq->first; + /* see if the queue is not empty */ + if (n == cq->last) + return 0; + + p = cq->table[n]; + if (++n >= cq->max) + n = 0; + if (cq->read == cq->first) + cq->read = n; + cq->first = n; + + /* see if we've passed our previous maximum */ + k = cq_howmany(cq); + if (k > cq->max_in) + cq->max_in = k; + + return p; +}
diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c
new file mode 100644
index 0000000..daa79fc
--- /dev/null
+++ b/drivers/usb/host/fhci-dbg.c@@ -0,0 +1,133 @@ +/* + * 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/debugfs.h> +#include <linux/seq_file.h> + +#ifndef CONFIG_FHCI_DEBUG + +static void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) {} +static void fhci_dfs_destroy(struct fhci_hcd *fhci) {} +static void fhci_dfs_create(struct fhci_hcd *fhci) {} + +#else + +static void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) +{ + int i; + + if (usb_er == -1) { + fhci->usb_irq_stat[12]++; + return; + } + + for (i = 0; i < 12; ++i) { + if (usb_er & (1 << i)) + fhci->usb_irq_stat[i]++; + } +} + +static int fhci_dfs_regs_show(struct seq_file *s, void *v) +{ + struct fhci_hcd *fhci = s->private; + struct fhci_regs __iomem *regs = fhci->regs; + + seq_printf(s, + "mode: 0x%x\n" "addr: 0x%x\n" + "command: 0x%x\n" "ep0: 0x%x\n" + "event: 0x%x\n" "mask: 0x%x\n" + "status: 0x%x\n" "SOF timer: %d\n" + "frame number: %d\n" + "lines status: 0x%x\n", + in_8(®s->usb_mod), in_8(®s->usb_addr), + in_8(®s->usb_comm), in_be16(®s->usb_ep[0]), + in_be16(®s->usb_event), in_be16(®s->usb_mask), + in_8(®s->usb_status), in_be16(®s->usb_sof_tmr), + in_be16(®s->usb_frame_num), + fhci_ioports_check_bus_state(fhci)); + + return 0; +} + +static int fhci_dfs_irq_stat_show(struct seq_file *s, void *v) +{ + struct fhci_hcd *fhci = s->private; + int *usb_irq_stat = fhci->usb_irq_stat; + + seq_printf(s, + "RXB: %d\n" "TXB: %d\n" "BSY: %d\n" + "SOF: %d\n" "TXE0: %d\n" "TXE1: %d\n" + "TXE2: %d\n" "TXE3: %d\n" "IDLE: %d\n" + "RESET: %d\n" "SFT: %d\n" "MSF: %d\n" + "IDLE_ONLY: %d\n", + usb_irq_stat[0], usb_irq_stat[1], usb_irq_stat[2], + usb_irq_stat[3], usb_irq_stat[4], usb_irq_stat[5], + usb_irq_stat[6], usb_irq_stat[7], usb_irq_stat[8], + usb_irq_stat[9], usb_irq_stat[10], usb_irq_stat[11], + usb_irq_stat[12]); + + return 0; +} + +static int fhci_dfs_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, fhci_dfs_regs_show, inode->i_private); +} + +static int fhci_dfs_irq_stat_open(struct inode *inode, struct file *file) +{ + return single_open(file, fhci_dfs_irq_stat_show, inode->i_private); +} + +static const struct file_operations fhci_dfs_regs_fops = { + .open = fhci_dfs_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations fhci_dfs_irq_stat_fops = { + .open = fhci_dfs_irq_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void fhci_dfs_create(struct fhci_hcd *fhci) +{ + struct device *dev = fhci_to_hcd(fhci)->self.controller; + + fhci->dfs_root = debugfs_create_dir(dev->bus_id, NULL); + if (!fhci->dfs_root || IS_ERR(fhci->dfs_root)) + return; + + fhci->dfs_regs = debugfs_create_file("regs", S_IFREG | S_IRUGO, + fhci->dfs_root, fhci, &fhci_dfs_regs_fops); + + fhci->dfs_irq_stat = debugfs_create_file("irq_stat", + S_IFREG | S_IRUGO, fhci->dfs_root, fhci, + &fhci_dfs_irq_stat_fops); +} + +static void fhci_dfs_destroy(struct fhci_hcd *fhci) +{ + debugfs_remove(fhci->dfs_irq_stat); + debugfs_remove(fhci->dfs_regs); + debugfs_remove(fhci->dfs_root); +} + +#endif /* CONFIG_FHCI_DEBUG */
diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c
new file mode 100644
index 0000000..cefbce3
--- /dev/null
+++ b/drivers/usb/host/fhci-hcd.c@@ -0,0 +1,1391 @@ +/* + * 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/module.h> +#include <linux/kernel.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 <linux/of_platform.h> +#include <asm/qe.h> +#include <asm/gpio.h> +#include "../core/hcd.h" + +#include "fhci.h" +#include "fhci-hub.c" +#include "fhci-q.c" +#include "fhci-dbg.c" +#include "fhci-mem.c" +#include "fhci-cq.c" +#if defined(CONFIG_FHCI_WITH_BDS) +#include "fhci-bds.c" +#elif defined(CONFIG_FHCI_WITH_TDS) +#include "fhci-tds.c" +#endif + +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 = 0; + + cq_put(usb->ep0->empty_frame_Q, pkt); +} + +static void *get_empty_frame(struct fhci_usb *usb) +{ + return cq_get(usb->ep0->empty_frame_Q); +} + +/* confirm submitted packet */ +static void 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 = 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 (((u32) 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) { + td->toggle = !td->toggle; + td->actual_len += trans_len; + } + } else { + /* 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 */ + 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) + move_td_from_ed_to_done_list(usb, ed); +} + +void qe_usb_stop_tx(u8 ep) +{ + qe_issue_cmd(QE_USB_STOP_TX, QE_CR_SUBBLOCK_USB, ep, 0); +} + +static void qe_usb_restart_tx(u8 ep) +{ + qe_issue_cmd(QE_USB_RESTART_TX, QE_CR_SUBBLOCK_USB, ep, 0); +} + +/* Cancel transmission on the USB endpoint*/ +static void abort_transmission(struct fhci_usb *usb) +{ + /* issue stop Tx command */ + qe_usb_stop_tx(EP_ZERO); + udelay(1000); + /* flush Tx FIFOs */ + usb->fhci->regs->usb_comm = (u8) (USB_CMD_FLUSH_FIFO | EP_ZERO); + udelay(1000); + /* reset Tx BDs */ + flush_bds(usb); + /* issue restart Tx command */ + qe_usb_restart_tx(EP_ZERO); +} + +/* + * 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 flush_all_transmissions(struct fhci_usb *usb) +{ + u8 mode; + struct td *td; + + mode = usb->fhci->regs->usb_mod; + usb->fhci->regs->usb_mod = (u8) (mode & ~USB_MODE_EN); + + flush_bds(usb); + + while ((td = peek_td_from_frame(usb->actual_frame)) != NULL) { + struct packet *pkt = td->pkt; + + pkt->status = USB_TD_TX_ER_TIMEOUT; + transaction_confirm(usb, pkt); + } + + usb->actual_frame->frame_status = FRAME_END_TRANSMISSION; + + /* reset the event register */ + usb->fhci->regs->usb_event = 0xffff; + /* enable the USB controller */ + usb->fhci->regs->usb_mod = (u8) (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 int add_packet(struct fhci_usb *usb, struct ed *ed, + struct td *td) +{ + u32 fw_transaction_time, len = 0; + struct packet *pkt; + u8 *data = NULL; + + /* calcalate data address,len and toggle and then add the transaction */ + if (td->toggle == USB_TD_TOGGLE_CARRY) + td->toggle = ed->toggle_carry; + + switch (ed->mode) { + case FHCI_TF_ISO: + len = td->len; + if (td->type != FHCI_TA_IN) + data = td->data; + break; + case FHCI_TF_CTRL: + case FHCI_TF_BULK: + len = min(td->len - td->actual_len, ed->max_pkt_size); + if (!((td->type == FHCI_TA_IN) && + ((len + td->actual_len) == td->len))) + data = td->data + td->actual_len; + break; + case FHCI_TF_INTR: + len = min(td->len, ed->max_pkt_size); + if (!((td->type == FHCI_TA_IN) && + ((td->len + CRC_SIZE) >= ed->max_pkt_size))) + data = td->data; + default: + break; + } + + if (usb->port_status == FHCI_PORT_FULL) + fw_transaction_time = (((len + PROTOCOL_OVERHEAD) * 11) >> 4); + else + fw_transaction_time = ((len + PROTOCOL_OVERHEAD) * 6); + + /* check if there's enough space in this frame to submit this TD */ + if (usb->actual_frame->total_bytes + len + PROTOCOL_OVERHEAD >= + usb->max_bytes_per_frame) + return -1; + + /* check if there's enough time in this frame to submit this TD */ + if (usb->actual_frame->frame_status != FRAME_IS_PREPARED && + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION || + (fw_transaction_time + usb->sw_transaction_time >= + 1000 - get_sof_timer_count(usb)))) + return -1; + + /* update frame object fields before transmitting */ + pkt = get_empty_frame(usb); + if (!pkt) + return -1; + td->pkt = pkt; + + pkt->info = 0; + if (data == NULL) { + data = cq_get(usb->ep0->dummy_packets_Q); + BUG_ON(!data); + pkt->info = PKT_DUMMY_PACKET; + } + pkt->data = data; + pkt->len = len; + pkt->status = USB_TD_OK; + /* update TD status field before transmitting */ + td->status = USB_TD_INPROGRESS; + /* update actual frame time object with the actual transmission */ + usb->actual_frame->total_bytes += (len + PROTOCOL_OVERHEAD); + add_td_to_frame(usb->actual_frame, td); + + if (usb->port_status != FHCI_PORT_FULL && + usb->port_status != FHCI_PORT_LOW) { + pkt->status = USB_TD_TX_ER_TIMEOUT; + pkt->len = 0; + transaction_confirm(usb, pkt); + } else if (host_transaction(usb, pkt, td->type, ed->dev_addr, + ed->ep_addr, ed->mode, ed->speed, td->toggle)) { + /* remove TD from actual frame */ + list_del_init(&td->frame_lh); + td->status = USB_TD_OK; + if (pkt->info & PKT_DUMMY_PACKET) + cq_put(usb->ep0->dummy_packets_Q, pkt->data); + recycle_frame(usb, pkt); + usb->actual_frame->total_bytes -= (len + PROTOCOL_OVERHEAD); + return -1; + } + + return len; +} + +/* + * This function goes through the endpoint list and schedules the + * transactions within this list + */ +static int scan_ed_list(struct fhci_usb *usb, + struct list_head *list, enum fhci_tf_mode list_type) +{ + static const int frame_part[4] = { + [FHCI_TF_CTRL] = MAX_BYTES_PER_FRAME, + [FHCI_TF_ISO] = (MAX_BYTES_PER_FRAME * + MAX_PERIODIC_FRAME_USAGE) / 100, + [FHCI_TF_BULK] = MAX_BYTES_PER_FRAME, + [FHCI_TF_INTR] = (MAX_BYTES_PER_FRAME * + MAX_PERIODIC_FRAME_USAGE) / 100 + }; + struct list_head *ed_lh = NULL; + struct ed *ed; + struct td *td; + int ans = 1; + u32 save_transaction_time = usb->sw_transaction_time; + + list_for_each(ed_lh, list) { + ed = list_entry(ed_lh, struct ed, node); + td = ed->td_head; + + if (td != NULL && td->status != USB_TD_INPROGRESS) { + if (ed->state != FHCI_ED_OPER) { + if (ed->state == FHCI_ED_URB_DEL) { + td->status = USB_TD_OK; + move_td_from_ed_to_done_list(usb, ed); + ed->state = FHCI_ED_SKIP; + } + } + /* + * if it isn't interrupt pipe or it is not iso pipe + * and the interval time passed + */ + else if (!(list_type == FHCI_TF_INTR || + list_type == FHCI_TF_ISO) || + (((usb->actual_frame->frame_num - + td->start_frame) & 0x7ff) >= + td->interval)) { + if (add_packet(usb, ed, td) >= 0) { + /* update time stamps in the TD */ + td->start_frame = + usb->actual_frame->frame_num; + usb->sw_transaction_time += + save_transaction_time; + + if (usb->actual_frame->total_bytes >= + usb->max_bytes_per_frame) { + usb->actual_frame-> + frame_status = + FRAME_DATA_END_TRANSMISSION; + push_dummy_bd(usb->ep0); + ans = 0; + break; + } + if (usb->actual_frame->total_bytes >= + frame_part[list_type]) + break; + } + } + } + } + + /* be fair to each ED(move list head around) */ + move_head_to_tail(list); + usb->sw_transaction_time = save_transaction_time; + + return ans; +} + +static u32 rotate_frames(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (!list_empty(&usb->actual_frame->tds_list)) { + if ((((u16)(fhci->pram->frame_num & 0x07ff) - + usb->actual_frame->frame_num) & 0x7ff) > 5) + flush_actual_frame(usb); + else + return -EINVAL; + } + + usb->actual_frame->frame_status = FRAME_IS_PREPARED; + usb->actual_frame->frame_num = (u16) (fhci->pram->frame_num & 0x7ff); + usb->actual_frame->total_bytes = 0; + + return 0; +} + +/* + * This function schedule the USB transaction and will process the + * endpoint in the following order: iso, interrupt, control and bulk. + */ +static void schedule_transactions(struct fhci_usb *usb) +{ + int left = 1; + + if (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION) + if (rotate_frames(usb) != 0) + return; + + if (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION) + return; + + if (usb->actual_frame->total_bytes == 0) { + /* schedule the next available ISO transfer + *or next stage of the ISO transfer*/ + scan_ed_list(usb, &usb->hc_list->iso_list, FHCI_TF_ISO); + + /* + * schedule the next available interrupt transfer or + * the next stage of the interrupt transfer + */ + scan_ed_list(usb, &usb->hc_list->intr_list, FHCI_TF_INTR); + + /* + * schedule the next available control transfer + * or the next stage of the control transfer + */ + left = + scan_ed_list(usb, &usb->hc_list->ctrl_list, FHCI_TF_CTRL); + } + + /* + * schedule the next available bulk transfer or the next stage of the + * bulk transfer + */ + if (left > 0) + scan_ed_list(usb, &usb->hc_list->bulk_list, FHCI_TF_BULK); +} + +/* initialize the endpoint zero */ +u32 endpoint_zero_init(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + u32 rc; + + rc = create_endpoint(usb, data_mem, ring_len); + if (rc) + return rc; + + /* inilialize endpoint registers */ + init_endpoint_registers(usb, usb->ep0, data_mem); + + return 0; +} + +/* enable the USB interrupts */ +void fhci_usb_enable_interrupt(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (usb->intr_nesting_cnt == 1) { + /* initialize the USB interrupt */ + enable_irq(fhci_to_hcd(fhci)->irq); + + /* initialize the event register and mask register */ + usb->fhci->regs->usb_event = 0xffff; + usb->fhci->regs->usb_mask = usb->saved_msk; + + /* enable the timer interrupts */ + enable_irq(fhci->timer_irq); + } else if (usb->intr_nesting_cnt > 1) + fhci_info(fhci, "unbalanced USB interrupts nesting\n"); + usb->intr_nesting_cnt--; +} + +/* diable the usb interrupt */ +void fhci_usb_disable_interrupt(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (usb->intr_nesting_cnt == 0) { + /* diable the timer interrupt */ + disable_irq(fhci->timer_irq); + + /* disable the usb interrupt */ + disable_irq(fhci_to_hcd(fhci)->irq); + usb->fhci->regs->usb_mask = 0; + } + usb->intr_nesting_cnt++; +} + +/* enable the USB controller */ +u32 fhci_usb_enable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + u8 mode; + + /* configure IO ports for USB--in Linux kernel initialize */ + + usb->fhci->regs->usb_event = 0xffff; + usb->fhci->regs->usb_mask = usb->saved_msk; + + mode = usb->fhci->regs->usb_mod; + mode |= USB_MODE_EN; + usb->fhci->regs->usb_mod = mode; + + msleep(100); + + return 0; +} + +/* disable the USB controller */ +u32 fhci_usb_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + u8 mode; + + fhci_usb_disable_interrupt(usb); + usb_port_disable(fhci); + + /* disable the usb controller */ + if (usb->port_status == FHCI_PORT_FULL || + usb->port_status == FHCI_PORT_LOW) + device_disconnected_interrupt(fhci); + + mode = usb->fhci->regs->usb_mod; + mode &= ~USB_MODE_EN; + usb->fhci->regs->usb_mod = mode; + + return 0; +} + +/* check the bus state by polling the QE bit on the IO ports */ +int fhci_ioports_check_bus_state(struct fhci_hcd *fhci) +{ + u8 bits = 0; + + /* check USBOE,if transmitting,exit */ + if (!gpio_get_value(fhci->gpios[GPIO_USBOE])) + return -1; + + /* check USBRP */ + if (gpio_get_value(fhci->gpios[GPIO_USBRP])) + bits |= 0x2; + + /* check USBRN */ + if (gpio_get_value(fhci->gpios[GPIO_USBRN])) + bits |= 0x1; + + return bits; +} + +/* Handles SOF interrupt */ +static void sof_interrupt(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + if ((usb->port_status == FHCI_PORT_DISABLED) && + (usb->vroot_hub->port.wPortStatus & USB_PORT_STAT_CONNECTION) && + !(usb->vroot_hub->port.wPortChange & USB_PORT_STAT_C_CONNECTION)) { + if (usb->vroot_hub->port.wPortStatus & USB_PORT_STAT_LOW_SPEED) + usb->port_status = FHCI_PORT_LOW; + else + usb->port_status = FHCI_PORT_FULL; + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + usb->fhci->regs->usb_mask = usb->saved_msk; + } + + qe_reset_ref_timer_16(fhci->timer, 1000000, usb->max_frame_usage); + + host_transmit_actual_frame(usb); + usb->actual_frame->frame_status = FRAME_IS_TRANSMITTED; + + schedule_transactions(usb); +} + +/* Handles device disconnected interrupt on port */ +static void device_disconnected_interrupt(struct fhci_hcd *fhci) +{ + u8 mode; + struct fhci_usb *usb = fhci->usb_lld; + + fhci_usb_disable_interrupt(usb); + mode = usb->fhci->regs->usb_mod; + mode &= ~USB_MODE_LSS; + usb->fhci->regs->usb_mod = mode; + usb->port_status = FHCI_PORT_DISABLED; + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + usb->fhci->regs->usb_mask = usb->saved_msk; + + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_CONNECTION; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = 0; + fhci_usb_enable_interrupt(usb); +} + +/* detect a new device connected on the USB port*/ +static void device_connected_interrupt(struct fhci_hcd *fhci) +{ + + struct fhci_usb *usb = fhci->usb_lld; + int state; + int ret; + u8 mode; + + fhci_usb_disable_interrupt(usb); + state = fhci_ioports_check_bus_state(fhci); + + /* low-speed device was connected to the USB port */ + if (state == 1) { + ret = qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + if (ret) { + fhci_info(fhci, "Low-Speed device is not supported, " + "try use BRGx\n"); + return; + } + + mode = usb->fhci->regs->usb_mod; + mode |= USB_MODE_LSS; + usb->fhci->regs->usb_mod = mode; + usb->vroot_hub->port.wPortStatus |= + (USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_CONNECTION); + usb->vroot_hub->port.wPortChange |= + USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = + (MAX_BYTES_PER_FRAME >> 3) - 7; + usb_port_enable(usb); + } else if (state == 2) { + ret = qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + if (ret) { + fhci_info(fhci, "Full-Speed device is not supported, " + "try use CLKx\n"); + return; + } + + mode = usb->fhci->regs->usb_mod; + mode &= ~USB_MODE_LSS; + usb->fhci->regs->usb_mod = mode; + usb->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_LOW_SPEED; + usb->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_CONNECTION; + usb->vroot_hub->port.wPortChange |= + USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = (MAX_BYTES_PER_FRAME - 15); + usb_port_enable(usb); + } + fhci_usb_enable_interrupt(usb); +} + +irqreturn_t fhci_frame_limit_timer_irq(int irq, void *_hcd) +{ + struct usb_hcd *hcd = _hcd; + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb = fhci->usb_lld; + + spin_lock(&fhci->lock); + + qe_reset_ref_timer_16(fhci->timer, 1000000, 1000); + + if (usb->actual_frame->frame_status == FRAME_IS_TRANSMITTED) { + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + push_dummy_bd(usb->ep0); + } + + schedule_transactions(usb); + + spin_unlock(&fhci->lock); + + return IRQ_HANDLED; +} + + + +static irqreturn_t fhci_irq(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb; + register u16 usb_er = 0; + + spin_lock(&fhci->lock); + + usb = fhci->usb_lld; + + usb->intr_counter++; + do { + usb_er |= (u16) (usb->fhci->regs->usb_event & + usb->fhci->regs->usb_mask); + + /* clear event bits for next time */ + usb->fhci->regs->usb_event = usb_er; + + fhci_dbg_isr(fhci, usb_er); + + if (usb_er & USB_E_RESET_MASK) { + if ((usb->port_status == FHCI_PORT_FULL) || + (usb->port_status == FHCI_PORT_LOW)) { + device_disconnected_interrupt(fhci); + usb_er &= ~USB_E_IDLE_MASK; + } else if (usb->port_status == + FHCI_PORT_WAITING) { + usb->port_status = FHCI_PORT_DISCONNECTING; + + /* Turn on IDLE since we want to disconnect */ + usb->saved_msk |= USB_E_IDLE_MASK; + usb->fhci->regs->usb_event = usb->saved_msk; + } else if (usb->port_status == FHCI_PORT_DISABLED) { + if (fhci_ioports_check_bus_state(fhci) == 1) + device_connected_interrupt(fhci); + } + usb_er &= ~USB_E_RESET_MASK; + } + + if (usb_er & USB_E_MSF_MASK) { + abort_transmission(fhci->usb_lld); + usb_er &= ~USB_E_MSF_MASK; + } + + if (usb_er & (USB_E_SOF_MASK | USB_E_SFT_MASK)) { + sof_interrupt(fhci); + usb_er &= ~(USB_E_SOF_MASK | USB_E_SFT_MASK); + } +#ifdef CONFIG_FHCI_WITH_BDS + if (usb_er & USB_E_RXB_MASK) { + if (receive_packet_interrupt(fhci) == 0) + usb_er &= ~USB_E_RXB_MASK; + } +#endif /* CONFIG_FHCI_WITH_BDS */ + + if (usb_er & USB_E_TXB_MASK) { + tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXB_MASK; +#ifdef CONFIG_FHCI_WITH_BDS + continue; +#endif /* CONFIG_FHCI_WITH_BDS */ + } + + if (usb_er & USB_E_TXE1_MASK) { + tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXE1_MASK; +#ifdef CONFIG_FHCI_WITH_BDS + continue; +#endif /* CONFIG_FHCI_WITH_BDS */ + } + + if (usb_er & USB_E_IDLE_MASK) { + if (usb->port_status == FHCI_PORT_DISABLED) { + usb_er &= ~USB_E_RESET_MASK; + device_connected_interrupt(fhci); + } else if (usb->port_status == + FHCI_PORT_DISCONNECTING) { + usb->port_status = FHCI_PORT_WAITING; + + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + usb->fhci->regs->usb_mask = usb->saved_msk; + } else + fhci_dbg_isr(fhci, -1); + + usb_er &= ~USB_E_IDLE_MASK; + } + } while (usb_er); + + spin_unlock(&fhci->lock); + + return IRQ_HANDLED; +} + +static void fhci_mem_free(struct fhci_hcd *fhci) +{ + struct td *td; + struct ed *ed; + + while (!list_empty(&fhci->empty_eds)) { + ed = list_entry(fhci->empty_eds.next, struct ed, node); + list_del(fhci->empty_eds.next); + } + + while (!list_empty(&fhci->empty_tds)) { + td = list_entry(fhci->empty_tds.next, struct td, node); + list_del(fhci->empty_tds.next); + } + + kfree(fhci->vroot_hub); + kfree(fhci->hc_list); +} + +DECLARE_TASKLET(fhci_tasklet, process_done_list, 0); + +static int fhci_mem_init(struct fhci_hcd *fhci) +{ + int i, error = 0; + + fhci->hc_list = kzalloc(sizeof(*fhci->hc_list), GFP_KERNEL); + if (!fhci->hc_list) + return -ENOMEM; + + INIT_LIST_HEAD(&fhci->hc_list->ctrl_list); + INIT_LIST_HEAD(&fhci->hc_list->bulk_list); + INIT_LIST_HEAD(&fhci->hc_list->iso_list); + INIT_LIST_HEAD(&fhci->hc_list->intr_list); + INIT_LIST_HEAD(&fhci->hc_list->done_list); + + fhci->vroot_hub = kzalloc(sizeof(*fhci->vroot_hub), GFP_KERNEL); + if (!fhci->vroot_hub) + return -ENOMEM; + + + INIT_LIST_HEAD(&fhci->empty_eds); + INIT_LIST_HEAD(&fhci->empty_tds); + + /* initialize work queue to handle done list */ + fhci_tasklet.data = (unsigned long)fhci; + fhci->process_done_task = &fhci_tasklet; + + for (i = 0; i < MAX_TDS; i++) { + struct td *td = kmalloc(sizeof(*td), GFP_KERNEL); + + if (!td) { + error = 1; + break; + } + recycle_empty_td(fhci, td); + } + for (i = 0; i < MAX_EDS; i++) { + struct ed *ed = kmalloc(sizeof(*ed), GFP_KERNEL); + + if (!ed) { + error = 1; + break; + } + recycle_empty_ed(fhci, ed); + } + + if (error) { + fhci_mem_free(fhci); + return -ENOMEM; + } + + fhci->active_urbs = 0; + + return error; +} + +/* transfer complted callback*/ +static u32 transfer_confirm_callback(struct fhci_hcd *fhci) +{ + if (!fhci->process_done_task->state) + tasklet_schedule(fhci->process_done_task); + return 0; +} + +/* destroy the fhci_usb structure */ +void fhci_usb_free(void *lld) +{ + struct fhci_usb *usb = lld; + struct fhci_hcd *fhci = usb->fhci; + + if (usb) { + config_transceiver(fhci, FHCI_OP_POWER_OFF); + config_transceiver(fhci, FHCI_OP_DISCONNECT); + + endpoint_zero_free(usb); +#if defined(CONFIG_FHCI_HAS_NO_RT_SOF) + kfree(usb->sof_pkts); +#elif defined(CONFIG_FHCI_HAS_UC_RT_SOF) + u8 *tmp_crc5_pkts = phys_to_virt(usb->pram->sof_tbl); + kfree(tmp_crc5_pkts); +#endif + kfree(usb->actual_frame); + kfree(usb); + } +} + +/* initialize the USB*/ +u32 fhci_usb_init(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + u8 mode = 0; + + memset_io(usb->fhci->pram, 0, FHCI_PRAM_SIZE); + + usb->port_status = FHCI_PORT_DISABLED; + usb->max_frame_usage = FRAME_TIME_USAGE; +#ifdef CONFIG_FHCI_WITH_BDS + /* the predefined time limitation are for the driver with + * transcation level interface, so we have to take about 2 times + */ + usb->sw_transaction_time = SW_FIX_TIME_BETWEEN_TRANSACTION * 2; +#else + usb->sw_transaction_time = SW_FIX_TIME_BETWEEN_TRANSACTION; +#endif + + usb->actual_frame = kzalloc(sizeof(*usb->actual_frame), GFP_KERNEL); + if (!usb->actual_frame) { + fhci_usb_free(usb); + return -ENOMEM; + } + + INIT_LIST_HEAD(&usb->actual_frame->tds_list); + + /* initializing registers on chip clear frame number */ + fhci->pram->frame_num = 0; + + /* clear rx state */ + fhci->pram->rx_state = 0; + + /* set mask register */ + usb->saved_msk = (USB_E_TXB_MASK | + USB_E_TXE1_MASK | + USB_E_IDLE_MASK | + USB_E_RESET_MASK | USB_E_SFT_MASK | USB_E_MSF_MASK); + +#ifdef CONFIG_FHCI_WITH_BDS + usb->saved_msk |= USB_E_RXB_MASK; +#endif + + /* config the usb mode register */ + mode = USB_MODE_HOST | USB_MODE_EN; + usb->fhci->regs->usb_mod = mode; + + /* clearing the mask register */ + usb->fhci->regs->usb_mask = (u16) 0x0; + + /* initialing the event register */ + usb->fhci->regs->usb_event = (u16) 0xffff; + + if (endpoint_zero_init(usb, DEFAULT_DATA_MEM, DEFAULT_RING_LEN) != 0) { + fhci_usb_free(usb); + return -EINVAL; + } + + return 0; +} + +/* initialize the fhci_usb struct and the corresponding data staruct*/ +struct fhci_usb *fhci_create_lld(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb; + + /* allocate memory for SCC data structure */ + usb = kzalloc(sizeof(*usb), GFP_KERNEL); + if (!usb) { + fhci_err(fhci, "no memory for SCC data struct\n"); + return NULL; + } + + usb->fhci = fhci; + usb->hc_list = fhci->hc_list; + usb->vroot_hub = fhci->vroot_hub; + + usb->transfer_confirm = transfer_confirm_callback; + + return usb; +} + +static int fhci_start(struct usb_hcd *hcd) +{ + int ret; + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + ret = fhci_mem_init(fhci); + + fhci->usb_lld = fhci_create_lld(fhci); + if (!fhci->usb_lld) { + fhci_err(fhci, "low level driver config failed\n"); + fhci_mem_free(fhci); + return -ENODEV; + } + if (fhci_usb_init(fhci)) { + fhci_err(fhci, "low level driver initialize failed\n"); + fhci_mem_free(fhci); + return -ENODEV; + } + spin_lock_init(&fhci->lock); + + /* connect the virtual root hub */ + fhci->vroot_hub->dev_num = 1; /* this field may be needed to fix */ + fhci->vroot_hub->hub.wHubStatus = 0; + fhci->vroot_hub->hub.wHubChange = 0; + fhci->vroot_hub->port.wPortStatus = 0; + fhci->vroot_hub->port.wPortChange = 0; + + hcd->state = HC_STATE_RUNNING; + + /* + * From here on, khubd concurrently accesses the root + * hub; drivers will be talking to enumerated devices. + * (On restart paths, khubd already knows about the root + * hub and could find work as soon as we wrote FLAG_CF.) + * + * Before this point the HC was idle/ready. After, khubd + * and device drivers may start it running. + */ + fhci_usb_enable(fhci); + + return 0; +} + +static void fhci_stop(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + fhci_usb_disable_interrupt(fhci->usb_lld); + fhci_usb_disable(fhci); + + fhci_usb_free(fhci->usb_lld); + fhci->usb_lld = NULL; + fhci_mem_free(fhci); +} + +static int fhci_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + u32 pipe = urb->pipe; + int i, size = 0; + struct urb_priv *urb_priv; + + spin_lock(&fhci->lock); + + switch (usb_pipetype(pipe)) { + case PIPE_CONTROL: + /* 1 td fro setup,1 for ack */ + size = 2; + case PIPE_BULK: + /* one td for every 4096 bytes(can be upto 8k) */ + size += urb->transfer_buffer_length / 4096; + /* ...add for any remaining bytes... */ + if ((urb->transfer_buffer_length % 4096) != 0) + size++; + /* ..and maybe a zero length packet to wrap it up */ + if (size == 0) + size++; + else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 + && (urb->transfer_buffer_length + % usb_maxpacket(urb->dev, pipe, + usb_pipeout(pipe))) != 0) + size++; + break; + case PIPE_ISOCHRONOUS: + size = urb->number_of_packets; + if (size <= 0) + return -EINVAL; + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].actual_length = 0; + urb->iso_frame_desc[i].status = (u32) (-EXDEV); + } + break; + case PIPE_INTERRUPT: + size = 1; + } + + /* allocate the private part of the URB */ + urb_priv = kzalloc(sizeof(*urb_priv), mem_flags); + if (!urb_priv) + return -ENOMEM; + + /* allocate the private part of the URB */ + urb_priv->tds = kzalloc(size * sizeof(struct td), mem_flags); + if (!urb_priv->tds) { + kfree(urb_priv); + return -ENOMEM; + } + + /* fill the private part of the URB */ + urb_priv->num_of_tds = size; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->error_count = 0; + urb->hcpriv = urb_priv; + + queue_urb(fhci, urb); + + spin_unlock(&fhci->lock); + return 0; +} + +/* dequeue FHCI URB */ +static int fhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb = fhci->usb_lld; + + spin_lock(&fhci->lock); + + if (!urb || !urb->dev || !urb->dev->bus) + goto out; + + if (usb->port_status != FHCI_PORT_DISABLED) { + struct urb_priv *urb_priv; + + /* + * flag the urb's data for deletion in some upcoming + * SF interrupt's delete list processing + */ + urb_priv = urb->hcpriv; + + if (!urb_priv || (urb_priv->state == URB_DEL)) + goto out; + + urb_priv->state = URB_DEL; + + /* already pending? */ + urb_priv->ed->state = FHCI_ED_URB_DEL; + } else + urb_complete_free(fhci, urb); +out: + spin_unlock(&fhci->lock); + + return 0; +} + +static void fhci_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fhci_hcd *fhci; + struct ed *ed; + + fhci = hcd_to_fhci(hcd); + spin_lock(&fhci->lock); + ed = ep->hcpriv; + if (ed) { + while (ed->td_head != NULL) { + struct td *td = remove_td_from_ed(ed); + urb_complete_free(fhci, td->urb); + } + recycle_empty_ed(fhci, ed); + ep->hcpriv = NULL; + } + spin_unlock(&fhci->lock); +} + +static int fhci_get_frame_number(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + return get_frame_num(fhci); +} + +static const struct hc_driver fhci_driver = { + .description = "fsl,usb-fhci", + .product_desc = "FHCI HOST Controller", + .hcd_priv_size = sizeof(struct fhci_hcd), + + /* generic hardware linkage */ + .irq = fhci_irq, + .flags = HCD_USB11, + + /* basic lifecycle operation */ + .start = fhci_start, + .stop = fhci_stop, + + /* managing i/o requests and associated device resources */ + .urb_enqueue = fhci_urb_enqueue, + .urb_dequeue = fhci_urb_dequeue, + .endpoint_disable = fhci_endpoint_disable, + + /* scheduling support */ + .get_frame_number = fhci_get_frame_number, + + /* root hub support */ + .hub_status_data = fhci_hub_status_data, + .hub_control = fhci_hub_control, +}; + +struct fhci_probe_info { + struct resource regs; + unsigned long pram_addr; + struct resource usb_irq; + int gpios[NUM_GPIOS]; + enum qe_clock fullspeed_clk; + enum qe_clock lowspeed_clk; + unsigned int power_budget; +}; + +static int __devinit fhci_probe(struct device *dev, struct fhci_probe_info *pi) +{ + unsigned long ret; + int i; + struct usb_hcd *hcd = 0; + struct fhci_hcd *fhci; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&fhci_driver, dev, dev->bus_id); + if (!hcd) { + dev_dbg(dev, "could not create hcd\n"); + return -ENOMEM; + } + + dev_set_drvdata(dev, hcd); + fhci = hcd_to_fhci(hcd); + + hcd->self.controller = dev; + hcd->power_budget = pi->power_budget; + hcd->regs = ioremap(pi->regs.start, pi->regs.end - pi->regs.start + 1); + fhci->regs = hcd->regs; + memcpy(fhci->gpios, pi->gpios, sizeof(fhci->gpios)); + + ret = qe_muram_alloc_fixed(pi->pram_addr, FHCI_PRAM_SIZE); + if (IS_ERR_VALUE(ret) || ret != pi->pram_addr) { + dev_err(dev, "failed to allocate usb pram\n"); + goto err_pram_alloc; + } + fhci->pram = qe_muram_addr(pi->pram_addr); + + for (i = 0; i < NUM_GPIOS; i++) { + int gpio = fhci->gpios[i]; + + if (gpio < 0) { + if (gpio < GPIO_SPEED) { + dev_err(dev, "incorrect GPIO%d: %d\n", + i, gpio); + goto err_gpios; + } else { + dev_info(dev, "assuming board doesn't have " + "%s gpio\n", gpio == GPIO_SPEED ? + "speed" : "suspn"); + } + } + + ret = gpio_request(gpio, dev->bus_id); + if (ret) { + dev_err(dev, "failed to request gpio %d", i); + goto err_gpios; + } + } + + ret = fhci->timer = qe_get_timer(16, &fhci->timer_irq); + if (ret < 0) { + dev_err(dev, "failed to request qe timer: %li", ret); + goto err_get_timer; + } + + fhci->fullspeed_clk = pi->fullspeed_clk; + fhci->lowspeed_clk = pi->lowspeed_clk; + + ret = request_irq(fhci->timer_irq, fhci_frame_limit_timer_irq, + IRQF_DISABLED, "qe timer (usb)", hcd); + if (ret) { + dev_err(dev, "failed to request timer irq"); + goto err_timer_irq; + } + + dev_info(dev, "at 0x%p,irq %d\n", hcd->regs, pi->usb_irq.start); + + /* start with low-speed, if possible */ + if (fhci->lowspeed_clk != QE_CLK_NONE) + qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + else + qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + + config_transceiver(fhci, FHCI_OP_HOST); + + ret = usb_add_hcd(hcd, pi->usb_irq.start, IRQF_DISABLED); + if (ret < 0) + goto err_add_hcd; + + fhci_dfs_create(fhci); + + return 0; + +err_add_hcd: + free_irq(fhci->timer_irq, hcd); +err_timer_irq: + qe_put_timer(fhci->timer); +err_get_timer: +err_gpios: + while (--i >= 0) { + if (fhci->gpios[i] >= 0) + gpio_free(fhci->gpios[i]); + } +err_pram_alloc: + usb_put_hcd(hcd); + return ret; +} + +static int __devexit fhci_remove(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + fhci_dfs_destroy(fhci); + usb_remove_hcd(hcd); + free_irq(fhci->timer_irq, hcd); + qe_put_timer(fhci->timer); + qe_muram_free(qe_muram_offset(fhci->pram)); + usb_put_hcd(hcd); + + return 0; +} + +static int __devinit of_fhci_probe(struct of_device *ofdev, + const struct of_device_id *ofid) +{ + int ret; + struct device *dev = &ofdev->dev; + struct fhci_probe_info pi = { + .lowspeed_clk = QE_CLK_NONE, + .fullspeed_clk = QE_CLK_NONE, + }; + int size; + const unsigned long *pram_addr; + const char *clk; + const u32 *power_budget; + struct device_node *np; + int i; + + np = of_find_compatible_node(NULL, NULL, "fsl,qe-muram-usb-pram"); + if (!np) { + dev_err(dev, "can't find usb-pram node\n"); + return -ENOENT; + } + + pram_addr = of_get_property(np, "reg", &size); + if (!pram_addr || size < sizeof(*pram_addr)) { + dev_err(dev, "can't get pram offset\n"); + of_node_put(np); + return -EINVAL;; + } + pi.pram_addr = *pram_addr; + of_node_put(np); + + ret = of_address_to_resource(ofdev->node, 0, &pi.regs); + if (ret) { + dev_err(dev, "can't get regs\n"); + return -EINVAL; + } + + clk = of_get_property(ofdev->node, "fullspeed-clock", NULL); + if (clk) { + pi.fullspeed_clk = qe_clock_source(clk); + if (pi.fullspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong fullspeed-clock\n"); + return -EINVAL; + } + } + + clk = of_get_property(ofdev->node, "lowspeed-clock", NULL); + if (clk) { + pi.lowspeed_clk = qe_clock_source(clk); + if (pi.lowspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong lowspeed-clock\n"); + return -EINVAL; + } + } + + if (pi.fullspeed_clk == QE_CLK_NONE && + pi.lowspeed_clk == QE_CLK_NONE) { + dev_err(dev, "no clocks specified\n"); + return -EINVAL; + } + + power_budget = of_get_property(ofdev->node, "hub-power-budget", &size); + if (power_budget && size == sizeof(*power_budget)) + pi.power_budget = *power_budget; + + ret = of_irq_to_resource(ofdev->node, 0, &pi.usb_irq); + if (ret == NO_IRQ) { + dev_err(dev, "can't get usb irq\n"); + return ret; + } + + /* gpios error and sanity checks are in the fhci_probe() */ + for (i = 0; i < NUM_GPIOS; i++) + pi.gpios[i] = of_get_gpio(ofdev->node, i); + + return fhci_probe(dev, &pi); +} + +static int __devexit of_fhci_remove(struct of_device *ofdev) +{ + return fhci_remove(&ofdev->dev); +} + +static const struct of_device_id of_fhci_match[] = { + { .compatible = "fsl,usb-fhci", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_fhci_match); + +static struct of_platform_driver of_fhci_driver = { + .name = "fsl,usb-fhci", + .match_table = of_fhci_match, + .probe = of_fhci_probe, + .remove = __devexit_p(of_fhci_remove), +}; + +static int __init fhci_module_init(void) +{ + return of_register_platform_driver(&of_fhci_driver); +} +module_init(fhci_module_init); + +static void __exit fhci_module_exit(void) +{ + of_unregister_platform_driver(&of_fhci_driver); +} +module_exit(fhci_module_exit); + +MODULE_DESCRIPTION("USB Freescale Host Controller Interface Driver"); +MODULE_AUTHOR("Shlomi Gridish <gridish@freescale.com>, " + "Jerry Huang <Chang-Ming.Huang@freescale.com>"); +MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c
new file mode 100644
index 0000000..38b5cd3
--- /dev/null
+++ b/drivers/usb/host/fhci-hub.c@@ -0,0 +1,343 @@ +/* + * 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. + */ + +/* Virtual root hub specific descriptor */ +static u8 root_hub_des[] = { + 0x09, /* blength */ + 0x29, /* bDescriptorType;hub-descriptor */ + 0x01, /* bNbrPorts */ + 0x00, /* wHubCharacteristics */ + 0x00, + 0x01, /* bPwrOn2pwrGood;2ms */ + 0x00, /* bHubContrCurrent;0mA */ + 0x00, /* DeviceRemoveable */ + 0xff /* PortPwrCtrlMask */ +}; + +static void fhci_start_sof_timer(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + u8 mode; + + /* clear frame_n */ + fhci->pram->frame_num = 0; + +#ifdef CONFIG_FHCI_HAS_EOP_MISSING_BUG + usb->eop_missing_bug_indicator = 0; +#endif + + fhci->regs->usb_sof_tmr = 0; + mode = fhci->regs->usb_mod; + mode |= USB_MODE_SFTE; + fhci->regs->usb_mod = mode; + + qe_reset_ref_timer_16(fhci->timer, 1000000, usb->max_frame_usage); +} + +static void fhci_stop_sof_timer(struct fhci_hcd *fhci) +{ + u8 mode; + + mode = fhci->regs->usb_mod; + mode &= ~USB_MODE_SFTE; + fhci->regs->usb_mod = mode; + + qe_stop_timer(fhci->timer); +} + +static u16 get_sof_timer_count(struct fhci_usb *usb) +{ + return (u16) (usb->fhci->regs->usb_sof_tmr / 12); +} + +void config_transceiver(struct fhci_hcd *fhci, enum fhci_op_mode mode) +{ + fhci_dbg(fhci, "%s: %d\n", __func__, mode); + + switch (mode) { + case FHCI_OP_HOST: + if (fhci->gpios[GPIO_SPEED] >= 0) + gpio_set_value(fhci->gpios[GPIO_SPEED], 1); + if (fhci->gpios[GPIO_SUSPN] >= 0) + gpio_set_value(fhci->gpios[GPIO_SUSPN], 1); + if (fhci->gpios[GPIO_SPEED] >= 0 || + fhci->gpios[GPIO_SUSPN] >= 0) + msleep(100); + break; + case FHCI_OP_DISCONNECT: + if (fhci->gpios[GPIO_SPEED] >= 0) + gpio_set_value(fhci->gpios[GPIO_SPEED], 1); + if (fhci->gpios[GPIO_SUSPN] >= 0) + gpio_set_value(fhci->gpios[GPIO_SUSPN], 1); + break; + case FHCI_OP_POWER_ON: + /* vcc on */ + if (fhci->gpios[GPIO_SUSPN] >= 0) { + gpio_set_value(fhci->gpios[GPIO_SUSPN], 0); + msleep(100); + } + break; + case FHCI_OP_POWER_OFF: + /* vcc off */ + if (fhci->gpios[GPIO_SUSPN] >= 0) + gpio_set_value(fhci->gpios[GPIO_SUSPN], 1); + break; + default: + WARN_ON(1); + break; + } +} + +/* disable the USB port by clearing the EN bit in the USBMOD register */ +void usb_port_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = (struct fhci_usb *)fhci->usb_lld; + enum fhci_port_status port_status; + + fhci_stop_sof_timer(fhci); + + flush_all_transmissions(usb); + + config_transceiver(fhci, FHCI_OP_POWER_OFF); + + fhci_usb_disable_interrupt((struct fhci_usb *)fhci->usb_lld); + port_status = usb->port_status; + usb->port_status = FHCI_PORT_DISABLED; + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + usb->fhci->regs->usb_mask = usb->saved_msk; + + /* check if during the disconnection process attached new device */ + if (port_status == FHCI_PORT_WAITING) + device_connected_interrupt(fhci); + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_ENABLE; + fhci_usb_enable_interrupt((struct fhci_usb *)fhci->usb_lld); +} + +/* enable the USB port by setting the EN bit in the USBMOD register */ +void usb_port_enable(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + + if ((usb->port_status != FHCI_PORT_FULL) && + (usb->port_status != FHCI_PORT_LOW)) + fhci_start_sof_timer(fhci); + udelay(5000); + usb->vroot_hub->port.wPortStatus |= USB_PORT_STAT_ENABLE; +} + +static void io_port_generate_reset(struct fhci_hcd *fhci) +{ + gpio_direction_output(fhci->gpios[GPIO_USBOE], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTP], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTN], 0); + + udelay(5000); + + gpio_set_dedicated(fhci->gpios[GPIO_USBOE], 0); + gpio_set_dedicated(fhci->gpios[GPIO_USBTP], 0); + gpio_set_dedicated(fhci->gpios[GPIO_USBTN], 0); +} + +/* generate the RESET condition on the bus */ +void usb_port_reset(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + u8 mode; + u16 mask; + + fhci_stop_sof_timer(fhci); + /* disable the USB controller */ + mode = fhci->regs->usb_mod; + fhci->regs->usb_mod = mode & (~USB_MODE_EN); + + /* disable idle interrupts */ + mask = fhci->regs->usb_mask; + fhci->regs->usb_mask = mask & (~USB_E_IDLE_MASK); + + io_port_generate_reset(fhci); + + /* enable interrupt on this endpoint */ + fhci->regs->usb_mask = mask; + + /* enable the USB controller */ + mode = fhci->regs->usb_mod; + fhci->regs->usb_mod = (mode | USB_MODE_EN); + fhci_start_sof_timer(fhci); +} + +static int fhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int ret = 0; + char data; + + data = (fhci->vroot_hub->hub.wHubChange & + HUB_CHANGE_LOCAL_POWER) ? (char)1 : (char)0; + ret = data; + + if (fhci->vroot_hub->port.wPortChange & (USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_SUSPEND | + USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_OVERCURRENT)) { + data |= 1 << 1; + ret += data; + } + + if (ret > 0) { + *(char *)buf = data; + return 1; + } + + return 0; +} + +static int fhci_hub_control(struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int retval = 0; + int len = 0; + struct usb_hub_status *hub_status; + struct usb_port_status *port_status; + + spin_lock(&fhci->lock); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + break; + default: + goto error; + } + break; + case ClearPortFeature: + fhci->vroot_hub->feature &= (1 << wValue); + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_ENABLE; + usb_port_disable(fhci); + break; + case USB_PORT_FEAT_C_ENABLE: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_SUSPEND; + fhci_stop_sof_timer(fhci); + break; + case USB_PORT_FEAT_C_SUSPEND: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_POWER; + config_transceiver(fhci, FHCI_OP_POWER_OFF); + break; + case USB_PORT_FEAT_C_CONNECTION: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_OVERCURRENT; + break; + case USB_PORT_FEAT_C_RESET: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_RESET; + default: + goto error; + } + break; + case GetHubDescriptor: + memcpy(buf, root_hub_des, sizeof(root_hub_des)); + buf[3] = 0x11; /* per-port power, no ovrcrnt */ + len = (buf[0] < wLength) ? buf[0] : wLength; + break; + case GetHubStatus: + hub_status = (struct usb_hub_status *)buf; + hub_status->wHubStatus = + cpu_to_le16(fhci->vroot_hub->hub.wHubStatus); + hub_status->wHubChange = + cpu_to_le16(fhci->vroot_hub->hub.wHubChange); + len = 4; + break; + case GetPortStatus: + port_status = (struct usb_port_status *)buf; + port_status->wPortStatus = + cpu_to_le16(fhci->vroot_hub->port.wPortStatus); + port_status->wPortChange = + cpu_to_le16(fhci->vroot_hub->port.wPortChange); + len = 4; + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case SetPortFeature: + fhci->vroot_hub->feature |= (1 << wValue); + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_ENABLE; + usb_port_enable(fhci->usb_lld); + break; + case USB_PORT_FEAT_SUSPEND: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_SUSPEND; + fhci_stop_sof_timer(fhci); + break; + case USB_PORT_FEAT_RESET: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_RESET; + usb_port_reset(fhci->usb_lld); + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_ENABLE; + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_RESET; + break; + case USB_PORT_FEAT_POWER: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_POWER; + config_transceiver(fhci, FHCI_OP_POWER_ON); + break; + default: + goto error; + } + break; + default: +error: + retval = -EPIPE; + } + + spin_unlock(&fhci->lock); + return retval; +}
diff --git a/drivers/usb/host/fhci-mem.c b/drivers/usb/host/fhci-mem.c
new file mode 100644
index 0000000..b9e51bc
--- /dev/null
+++ b/drivers/usb/host/fhci-mem.c@@ -0,0 +1,95 @@ +/* + * 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. + */ + +static void init_td(struct td *td) +{ + memset(td, 0, sizeof(*td)); + INIT_LIST_HEAD(&td->node); + INIT_LIST_HEAD(&td->frame_lh); +} + +static void init_ed(struct ed *ed) +{ + memset(ed, 0, sizeof(*ed)); + INIT_LIST_HEAD(&ed->td_list); + INIT_LIST_HEAD(&ed->node); +} + +static struct td *get_empty_td(struct fhci_hcd *fhci) +{ + struct td *td; + + if (!list_empty(&fhci->empty_tds)) { + td = list_entry(fhci->empty_tds.next, struct td, node); + list_del(fhci->empty_tds.next); + } else { + td = kmalloc(sizeof(*td), GFP_ATOMIC); + if (!td) + fhci_err(fhci, "No memory to allocate to TD\n"); + else + init_td(td); + } + + return td; +} + +static void recycle_empty_td(struct fhci_hcd *fhci, struct td *td) +{ + init_td(td); + list_add(&td->node, &fhci->empty_tds); +} + +static struct ed *get_empty_ed(struct fhci_hcd *fhci) +{ + struct ed *ed; + + if (!list_empty(&fhci->empty_eds)) { + ed = list_entry(fhci->empty_eds.next, struct ed, node); + list_del(fhci->empty_eds.next); + } else { + ed = kmalloc(sizeof(*ed), GFP_ATOMIC); + if (!ed) + fhci_err(fhci, "No memory to allocate to ED\n"); + else + init_ed(ed); + } + + return ed; +} + +static void recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed) +{ + init_ed(ed); + list_add(&ed->node, &fhci->empty_eds); +} + +static struct td *td_alloc_fill(struct fhci_hcd *fhci, struct urb *urb, + struct urb_priv *urb_priv, struct ed *ed, u16 index, + enum fhci_ta_type type, int toggle, u8 *data, u32 len, + u16 interval, u16 start_frame, bool ioc) +{ + struct td *td = get_empty_td(fhci); + + if (!td) + return NULL; + + td_fill(urb, ed, td, index, type, toggle, data, len, interval, + start_frame, ioc); + urb_priv->tds[index] = td; + + return td; +}
diff --git a/drivers/usb/host/fhci-q.c b/drivers/usb/host/fhci-q.c
new file mode 100644
index 0000000..459c5e2
--- /dev/null
+++ b/drivers/usb/host/fhci-q.c@@ -0,0 +1,495 @@ +/* + * 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. + */ + +static void td_fill(struct urb *urb, struct ed *ed, struct td *td, + u16 index, enum fhci_ta_type type, int toggle, u8 *data, + u32 len, u16 interval, u16 start_frame, bool ioc) +{ + td->urb = urb; + td->ed = ed; + td->type = type; + td->toggle = toggle; + td->data = data; + td->len = len; + td->iso_index = index; + td->interval = interval; + td->start_frame = start_frame; + td->ioc = ioc; + td->status = USB_TD_OK; +} + +static void add_td_to_frame(struct fhci_time_frame *frame, struct td *td) +{ + list_add_tail(&td->frame_lh, &frame->tds_list); +} + +static void add_tds_to_ed(struct ed *ed, struct td **td_list, int number) +{ + int i; + + for (i = 0; i < number; i++) { + struct td *td = td_list[i]; + list_add_tail(&td->node, &ed->td_list); + } + if (ed->td_head == NULL) + ed->td_head = td_list[0]; +} + +static struct td *peek_td_from_ed(struct ed *ed) +{ + struct td *td; + + if (!list_empty(&ed->td_list)) + td = list_entry(ed->td_list.next, struct td, node); + else + td = NULL; + + return td; +} + +static struct td *remove_td_from_frame(struct fhci_time_frame *frame) +{ + struct td *td; + + if (!list_empty(&frame->tds_list)) { + td = list_entry(frame->tds_list.next, struct td, frame_lh); + list_del_init(frame->tds_list.next); + } else + td = NULL; + + return td; +} + +static struct td *peek_td_from_frame(struct fhci_time_frame *frame) +{ + struct td *td; + + if (!list_empty(&frame->tds_list)) + td = list_entry(frame->tds_list.next, struct td, frame_lh); + else + td = NULL; + + return td; +} +static struct td *remove_td_from_ed(struct ed *ed) +{ + struct td *td; + + if (!list_empty(&ed->td_list)) { + td = list_entry(ed->td_list.next, struct td, node); + list_del_init(ed->td_list.next); + + /* if this TD was the ED's head, find next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, + node); + else + ed->td_head = NULL; + } else + td = NULL; + + return td; +} + +static struct td *remove_td_from_done_list(struct fhci_controller_list *p_list) +{ + struct td *td; + + if (!list_empty(&p_list->done_list)) { + td = list_entry(p_list->done_list.next, struct td, node); + list_del_init(p_list->done_list.next); + } else + td = NULL; + + return td; +} + +static void move_td_from_ed_to_done_list(struct fhci_usb *usb, struct ed *ed) +{ + struct td *td; + + td = ed->td_head; + list_del_init(&td->node); + + /* If this TD was the ED's head,find next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, node); + else { + ed->td_head = NULL; + ed->state = FHCI_ED_SKIP; + } + ed->toggle_carry = td->toggle; + list_add_tail(&td->node, &usb->hc_list->done_list); + if (td->ioc) + usb->transfer_confirm(usb->fhci); +} + +/* free done FHCI URB resource such as ED and TD*/ +static void free_urb_priv(struct fhci_hcd *fhci, struct urb *urb) +{ + int i; + struct urb_priv *urb_priv = urb->hcpriv; + struct ed *ed = urb_priv->ed; + + for (i = 0; i < urb_priv->num_of_tds; i++) { + list_del_init(&urb_priv->tds[i]->node); + recycle_empty_td(fhci, urb_priv->tds[i]); + } + + /* if this TD was the ED's head,find the next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, node); + else + ed->td_head = NULL; + + kfree(urb_priv->tds); + kfree(urb_priv); + urb->hcpriv = NULL; + + /* if this TD was the ED's head,find next TD */ + if (ed->td_head == NULL) + list_del_init(&ed->node); + fhci->active_urbs--; +} + +/* this routine called to complete and free done URB */ +static void urb_complete_free(struct fhci_hcd *fhci, struct urb *urb) +{ + free_urb_priv(fhci, urb); + + if (urb->status == -EINPROGRESS) { + if (urb->actual_length != urb->transfer_buffer_length && + urb->transfer_flags & URB_SHORT_NOT_OK) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + spin_unlock(&fhci->lock); + usb_hcd_giveback_urb(fhci_to_hcd(fhci), urb, urb->status); + spin_lock(&fhci->lock); +} + +/* + * caculate transfer length/stats and update the urb + * Precondition: irqsafe(only for urb-?status locking) + */ +static void done_td(struct urb *urb, struct td *td) +{ + struct ed *ed = td->ed; + u32 cc = td->status; + + /* ISO...drivers see per-TD length/status */ + if (ed->mode == FHCI_TF_ISO) { + u32 len; + if (!(urb->transfer_flags & URB_SHORT_NOT_OK && + cc == USB_TD_RX_DATA_UNDERUN)) + cc = USB_TD_OK; + + if (usb_pipeout(urb->pipe)) + len = urb->iso_frame_desc[td->iso_index].length; + else + len = td->actual_len; + + urb->actual_length += len; + urb->iso_frame_desc[td->iso_index].actual_length = len; + urb->iso_frame_desc[td->iso_index].status = + status_to_error(cc); + } + + /* BULK,INT,CONTROL... drivers see aggregate length/status, + * except that "setup" bytes aren't counted and "short" transfers + * might not be reported as errors. + */ + else { + if (td->error_cnt >= 3) + urb->error_count = 3; + + /* control endpoint only have soft stalls */ + + /* update packet status if needed(short may be ok) */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK) && + cc == USB_TD_RX_DATA_UNDERUN) { + ed->state = FHCI_ED_OPER; + cc = USB_TD_OK; + } + if (cc != USB_TD_OK) { + if (urb->status == -EINPROGRESS) + urb->status = status_to_error(cc); + } + + /* count all non-empty packets except control SETUP packet */ + if (td->type != FHCI_TA_SETUP || td->iso_index != 0) + urb->actual_length += td->actual_len; + } +} + +/* there are some pedning request to unlink */ +static void del_ed_list(struct fhci_hcd *fhci, struct ed *ed) +{ + struct td *td = peek_td_from_ed(ed); + struct urb *urb = td->urb; + struct urb_priv *urb_priv = urb->hcpriv; + + if (urb_priv->state == URB_DEL) { + td = remove_td_from_ed(ed); + /* HC may have partly processed this TD */ + if (td->status != USB_TD_INPROGRESS) + done_td(urb, td); + + /* URB is done;clean up */ + if (++(urb_priv->tds_cnt) == urb_priv->num_of_tds) + urb_complete_free(fhci, urb); + } +} + +/* + * Process normal completions(error or sucess) and clean the schedule. + * + * This is the main path for handing urbs back to drivers. The only other patth + * is process_del_list(),which unlinks URBs by scanning EDs,instead of scanning + * the (re-reversed) done list as this does. + */ +static void process_done_list(unsigned long data) +{ + struct urb *urb; + struct ed *ed; + struct td *td; + struct urb_priv *urb_priv; + struct fhci_hcd *fhci = (struct fhci_hcd *)data; + + spin_lock(&fhci->lock); + td = remove_td_from_done_list(fhci->hc_list); + while (td != NULL) { + urb = td->urb; + urb_priv = urb->hcpriv; + ed = td->ed; + + /* update URB's length and status from TD */ + done_td(urb, td); + urb_priv->tds_cnt++; + + /* + * if all this urb's TDs are done, call complete() + * Interrupt transfers are the onley special case: + * they are reissued,until "deleted" by usb_unlink_urb + * (real work done in a SOF intr, by process_del_list) + */ + if (urb_priv->tds_cnt == urb_priv->num_of_tds) { + urb_complete_free(fhci, urb); + } else if (urb_priv->state == URB_DEL && + ed->state == FHCI_ED_SKIP) { + del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } else if (ed->state == FHCI_ED_HALTED) { + urb_priv->state = URB_DEL; + ed->state = FHCI_ED_URB_DEL; + del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } + + td = remove_td_from_done_list(fhci->hc_list); + } + spin_unlock(&fhci->lock); +} + +/* + * adds urb to the endpoint descriptor list + * arguments: + * fhci data structure for the Low level host controller + * ep USB Host endpoint data structure + * urb USB request block data structure + */ +static void queue_urb(struct fhci_hcd *fhci, struct urb *urb) +{ + struct ed *ed = urb->ep->hcpriv; + struct urb_priv *urb_priv = urb->hcpriv; + u32 data_len = urb->transfer_buffer_length; + int urb_state = 0; + int toggle = 0; + struct td *td; + u8 *data; + u16 cnt = 0; + + if (ed == NULL) { + ed = get_empty_ed(fhci); + ed->dev_addr = (u8) usb_pipedevice(urb->pipe); + ed->ep_addr = (u8) usb_pipeendpoint(urb->pipe); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ed->mode = FHCI_TF_CTRL; + break; + case PIPE_BULK: + ed->mode = FHCI_TF_BULK; + break; + case PIPE_INTERRUPT: + ed->mode = FHCI_TF_INTR; + break; + case PIPE_ISOCHRONOUS: + ed->mode = FHCI_TF_ISO; + break; + default: + break; + } + ed->speed = (urb->dev->speed == USB_SPEED_LOW) ? + FHCI_LOW_SPEED : FHCI_FULL_SPEED; + ed->max_pkt_size = usb_maxpacket(urb->dev, + urb->pipe, usb_pipeout(urb->pipe)); + urb->ep->hcpriv = ed; + } + + /* for ISO transfer calculate start frame index */ + if (ed->mode == FHCI_TF_ISO && urb->transfer_flags & URB_ISO_ASAP) + urb->start_frame = ed->td_head ? ed->last_iso + 1 : + get_frame_num(fhci); + + /* + * OHCI handles the DATA toggle itself,we just use the USB + * toggle bits + */ + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe))) + toggle = USB_TD_TOGGLE_CARRY; + else { + toggle = USB_TD_TOGGLE_DATA0; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 1); + } + + urb_priv->tds_cnt = 0; + urb_priv->ed = ed; + if (data_len > 0) + data = urb->transfer_buffer; + else + data = 0; + + switch (ed->mode) { + case FHCI_TF_BULK: + if (urb->transfer_flags & URB_ZERO_PACKET && + urb->transfer_buffer_length > 0 && + ((urb->transfer_buffer_length % + usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))) == 0)) + urb_state = US_BULK0; + while (data_len > 4096) { + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + cnt ? USB_TD_TOGGLE_CARRY : + toggle, + data, 4096, 0, 0, true); + data += 4096; + data_len -= 4096; + cnt++; + } + + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : FHCI_TA_IN, + cnt ? USB_TD_TOGGLE_CARRY : toggle, + data, data_len, 0, 0, true); + cnt++; + + if (urb->transfer_flags & URB_ZERO_PACKET && + cnt < urb_priv->num_of_tds) { + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_CARRY, NULL, 0, 0, 0, true); + cnt++; + } + break; + case FHCI_TF_INTR: + urb->start_frame = get_frame_num(fhci) + 1; + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : FHCI_TA_IN, + USB_TD_TOGGLE_DATA0, data, data_len, + urb->interval, urb->start_frame, true); + break; + case FHCI_TF_CTRL: + ed->dev_addr = usb_pipedevice(urb->pipe); + ed->max_pkt_size = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt++, + FHCI_TA_SETUP, USB_TD_TOGGLE_DATA0, urb->setup_packet, + 8, 0, 0, true); + + if (data_len > 0) { + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_DATA1, data, data_len, 0, 0, + true); + } + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_IN : FHCI_TA_OUT, + USB_TD_TOGGLE_DATA1, data, 0, 0, 0, true); + urb_state = US_CTRL_SETUP; + break; + case FHCI_TF_ISO: + for (cnt = 0; cnt < urb->number_of_packets; cnt++) { + u16 frame = (u16) urb->start_frame; + + /* + * FIXME scheduling should handle frame counter + * roll-around ... exotic case (and OHCI has + * a 2^16 iso range, vs other HCs max of 2^10) + */ + frame += cnt * urb->interval; + frame &= 0x07ff; + td = td_alloc_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_DATA0, + data + urb->iso_frame_desc[cnt].offset, + urb->iso_frame_desc[cnt].length, + urb->interval, frame, true); + } + default: + break; + } + + /* + * set the state of URB + * control pipe:3 states -- setup,data,status + * interrupt and bulk pipe:1 state -- data + */ + urb->pipe &= ~0x1f; + urb->pipe |= urb_state & 0x1f; + + urb_priv->state = URB_INPROGRESS; + + if (!ed->td_head) { + ed->state = FHCI_ED_OPER; + switch (ed->mode) { + case FHCI_TF_CTRL: + list_add(&ed->node, &fhci->hc_list->ctrl_list); + break; + case FHCI_TF_BULK: + list_add(&ed->node, &fhci->hc_list->bulk_list); + break; + case FHCI_TF_INTR: + list_add(&ed->node, &fhci->hc_list->intr_list); + break; + case FHCI_TF_ISO: + list_add(&ed->node, &fhci->hc_list->iso_list); + default: + break; + } + } + + add_tds_to_ed(ed, urb_priv->tds, urb_priv->num_of_tds); + fhci->active_urbs++; +}
diff --git a/drivers/usb/host/fhci-tds.c b/drivers/usb/host/fhci-tds.c
new file mode 100644
index 0000000..88922f8
--- /dev/null
+++ b/drivers/usb/host/fhci-tds.c@@ -0,0 +1,628 @@ +/* + * 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. + */ + +#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 recieved */ +#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 { + u16 status; + u16 length; + u32 buf_ptr; + u16 extra; + u16 reserved; +}; + +struct endpoint { + /* Pointer to ep parameter RAM */ + struct fhci_ep_pram *ep_pram_ptr; + + /* Host transactions */ + struct usb_td *td_base; /* first TD in the ring */ + struct usb_td *conf_td; /* next TD for confirm after transac */ + struct usb_td *empty_td;/* next TD for new transaction request */ + void *empty_frame_Q; /* Empty frames list to use */ + void *conf_frame_Q; /* frames passed to TDs,waiting for tx */ + void *dummy_packets_Q; /* dummy packets for the CRC overun */ + + bool already_pushed_dummy_bd; +}; + +struct usb_td *next_bd(struct usb_td *base, struct usb_td *td, u16 status) +{ + if (status & TD_W) + return base; + else + return ++td; +} + +static void push_dummy_bd(struct endpoint *ep) +{ + if (ep->already_pushed_dummy_bd == false) { + u16 td_status = ep->empty_td->status; + + 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 endpoint_zero_free(struct fhci_usb *usb) +{ + struct endpoint *ep; + int size; + + ep = usb->ep0; + if (ep) { + if (ep->td_base) + qe_muram_free(qe_muram_offset(ep->td_base)); + + if (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 (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 (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 create_endpoint(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + struct endpoint *ep; + struct usb_td *td; + unsigned long ep_offset; + char *err_for = "enpoint 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) + goto err; + + ep_mem_size = ring_len * sizeof(*td) + sizeof(struct fhci_ep_pram); + ep_offset = qe_muram_alloc(ep_mem_size, 32); + if (IS_ERR_VALUE(ep_offset)) + goto err; + ep->td_base = qe_muram_addr(ep_offset); + + /* zero all queue pointers */ + ep->conf_frame_Q = cq_new((u8) ring_len + 2); + ep->empty_frame_Q = cq_new((u8) ring_len + 2); + ep->dummy_packets_Q = cq_new((u8) ring_len + 2); + if (!ep->conf_frame_Q || !ep->empty_frame_Q || !ep->dummy_packets_Q) { + 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 *)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++) { + td->buf_ptr = 0; + td->status = 0; + td->length = 0; + td->extra = 0; + td++; + } + td--; + td->status = TD_W; /* for last TD set Wrap bit */ + td->length = 0; + + /* endpoint structure has been created */ + usb->ep0 = ep; + + return 0; +err: + kfree(ep); + endpoint_zero_free(usb); + 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 init_endpoint_registers(struct fhci_usb *usb, + struct endpoint *ep, enum fhci_mem_alloc data_mem) +{ + u8 rt; + + /* set the endpoint registers according to the endpoint */ + usb->fhci->regs->usb_ep[0] = (USB_TRANS_CTR | USB_EP_MF | USB_EP_RTE); + usb->fhci->pram->ep_ptr[0] = qe_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 + ep->ep_pram_ptr->rx_func_code = rt; + ep->ep_pram_ptr->tx_func_code = rt; + ep->ep_pram_ptr->rx_buff_len = 1028; + ep->ep_pram_ptr->rx_base = 0; + ep->ep_pram_ptr->tx_base = qe_muram_offset(ep->td_base); + ep->ep_pram_ptr->rx_bd_ptr = 0; + ep->ep_pram_ptr->tx_bd_ptr = qe_muram_offset(ep->td_base); + ep->ep_pram_ptr->tx_state = 0; + /* + * FIXME: what the is this? I presume it is needed for u-code + * patched FHCIs, but this needs checking. + */ + ep->ep_pram_ptr->reserved = (u16)((u32)qe_muram_addr(0) >> 16); +} + +/* Collect the submitted frames and inform the application about them + * It is also prepearing the TDs for new frames. If the Tx interrupts + * are diabled, the application should call that routine to get + * confirmation about the submitted frames. Otherwise, the routine is + * called frome the interrupt service routine during the Tx interrupt. + * In that case the application is informed by calling the application + * specific 'transaction_confirm' routine*/ +static void td_transaction_confirm(struct fhci_usb *usb) +{ + struct endpoint *ep = usb->ep0; + struct packet *pkt; + struct usb_td *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 + */ + do { + td = ep->conf_td; + td_status = td->status; + td_length = td->length; + buf = td->buf_ptr; + extra_data = 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 */ + td->status &= TD_W; + td->length = 0; + td->buf_ptr = 0; + 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 " + "occured\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 occured\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; + + transaction_confirm(usb, pkt); + } while (1); +} + +/* + * 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 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 *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 = td->status; + + if (td_status & TD_R && 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; + 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; + 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; + + td->status = td_status; + + /* set up buffer length */ + if (trans_type == FHCI_TA_IN) + td->length = pkt->len + CRC_SIZE; + else + 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) + usb->fhci->regs->usb_comm = USB_CMD_STR_FIFO; + + return 0; +} + +/* Reset the Tx BD ring*/ +void flush_bds(struct fhci_usb *usb) +{ + u16 extra_data; + u16 td_status; + u32 buf; + struct usb_td *td; + struct endpoint *ep = usb->ep0; + + td = ep->td_base; + do { + td_status = td->status; + buf = td->buf_ptr; + extra_data = td->extra; + + /* if the TD is not empty - we'll confirm it as Timeout */ + if (td_status & TD_R) + td->status = (td_status & ~TD_R) | TD_TO; + /* if this TD is dummy - let's skip this TD */ + else if (td->buf_ptr == DUMMY_BD_BUFFER) + td->buf_ptr = DUMMY2_BD_BUFFER; + /* if this is the last TD - break */ + if (td_status & TD_W) + break; + + td++; + } while (1); + + td_transaction_confirm(usb); + + td = ep->td_base; + do { + td->status = 0; + td->length = 0; + td->buf_ptr = 0; + td->extra = 0; + td++; + } while (!(td->status & TD_W)); + td->status = TD_W; /* for last TD set Wrap bit */ + td->length = 0; + td->buf_ptr = 0; + td->extra = 0; + + ep->ep_pram_ptr->tx_bd_ptr = ep->ep_pram_ptr->tx_base; + ep->ep_pram_ptr->tx_state = 0; + ep->ep_pram_ptr->tx_cnt = 0; + ep->conf_td = ep->empty_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 flush_actual_frame(struct fhci_usb *usb) +{ + u8 mode; + u16 tb_ptr; + u16 extra_data; + u16 td_status; + u32 buf_ptr; + struct usb_td *td; + struct endpoint *ep = usb->ep0; + + /* disable the USB controller */ + mode = usb->fhci->regs->usb_mod; + usb->fhci->regs->usb_mod = (u8) (mode & ~USB_MODE_EN); + + tb_ptr = ep->ep_pram_ptr->tx_bd_ptr; + td = qe_muram_addr(tb_ptr); + td_status = td->status; + buf_ptr = td->buf_ptr; + extra_data = td->extra; + do { + if (td_status & TD_R) { + td->status = (td_status & ~TD_R) | TD_TO; + } else { + 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 = td->status; + buf_ptr = td->buf_ptr; + extra_data = td->extra; + } while ((td_status & TD_R) || buf_ptr); + + td_transaction_confirm(usb); + + ep->ep_pram_ptr->tx_bd_ptr = ep->ep_pram_ptr->tx_base; + ep->ep_pram_ptr->tx_state = 0; + ep->ep_pram_ptr->tx_cnt = 0; + ep->conf_td = ep->empty_td = ep->td_base; + + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + + /* reset the event register */ + usb->fhci->regs->usb_event = 0xffff; + /* enable the USB controller */ + usb->fhci->regs->usb_mod = (u8) (mode | USB_MODE_EN); +} + +/* handles Tx confirm and Tx error interrupt */ +void tx_conf_interrupt(struct fhci_usb *usb) +{ + td_transaction_confirm(usb); + + /* + * Schedule another transaction to this frame only if we have + * already confirmed all transaction in the frame. + */ + if (((get_sof_timer_count(usb) < usb->max_frame_usage) || + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION)) && + (list_empty(&usb->actual_frame->tds_list))) + schedule_transactions(usb); +} + +void host_transmit_actual_frame(struct fhci_usb *usb) +{ + u16 tb_ptr; + u16 td_status; + struct usb_td *td; + struct endpoint *ep = usb->ep0; + + tb_ptr = ep->ep_pram_ptr->tx_bd_ptr; + td = qe_muram_addr(tb_ptr); + + if (td->buf_ptr == DUMMY_BD_BUFFER) { + struct usb_td *old_td = td; + + ep->already_pushed_dummy_bd = false; + td_status = td->status; + /* gets the next TD in the ring */ + td = next_bd(ep->td_base, td, td_status); + tb_ptr = qe_muram_offset(td); + ep->ep_pram_ptr->tx_bd_ptr = tb_ptr; + + /* start transmit only if we have something in the TDs */ + if (td->status & TD_R) + usb->fhci->regs->usb_comm = USB_CMD_STR_FIFO; + + if (ep->conf_td->buf_ptr == DUMMY_BD_BUFFER) { + old_td->buf_ptr = 0; + ep->conf_td = next_bd(ep->td_base, ep->conf_td, + td_status); + } else { + old_td->buf_ptr = DUMMY2_BD_BUFFER; + } + } +}
diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h
new file mode 100644
index 0000000..9eb454a
--- /dev/null
+++ b/drivers/usb/host/fhci.h@@ -0,0 +1,543 @@ +/* + * 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. + */ + +#ifndef __FHCI_H +#define __FHCI_H + +#if defined(CONFIG_FHCI_DEBUG) && !defined(DEBUG) +#define DEBUG +#endif + +#define USB_CLOCK 48000000 + +#define FHCI_PRAM_SIZE 0x100 + +#define MAX_EDS 32 +#define MAX_TDS 32 + + +/* CRC16 field size */ +#define CRC_SIZE 2 + +/* USB protocol overhead for each frame transmitted from the host */ +#define PROTOCOL_OVERHEAD 7 + +/* Packet structure, info field */ +#define PKT_PID_DATA0 0x80000000 /* PID - Data toggle zero */ +#define PKT_PID_DATA1 0x40000000 /* PID - Data toggle one */ +#define PKT_PID_SETUP 0x20000000 /* PID - Setup bit */ +#define PKT_SETUP_STATUS 0x10000000 /* Setup status bit */ +#define PKT_SETADDR_STATUS 0x08000000 /* Set address status bit */ +#define PKT_SET_HOST_LAST 0x04000000 /* Last data packet */ +#define PKT_HOST_DATA 0x02000000 /* Data packet */ +#define PKT_FIRST_IN_FRAME 0x01000000 /* First packet in the frame */ +#define PKT_TOKEN_FRAME 0x00800000 /* Token packet */ +#define PKT_ZLP 0x00400000 /* Zero length packet */ +#define PKT_IN_TOKEN_FRAME 0x00200000 /* IN token packet */ +#define PKT_OUT_TOKEN_FRAME 0x00100000 /* OUT token packet */ +#define PKT_SETUP_TOKEN_FRAME 0x00080000 /* SETUP token packet */ +#define PKT_STALL_FRAME 0x00040000 /* STALL packet */ +#define PKT_NACK_FRAME 0x00020000 /* NACK packet */ +#define PKT_NO_PID 0x00010000 /* No PID */ +#define PKT_NO_CRC 0x00008000 /* don't append CRC */ +#define PKT_HOST_COMMAND 0x00004000 /* Host command packet */ +#define PKT_DUMMY_PACKET 0x00002000 /* Dummy packet, used for mmm */ +#define PKT_LOW_SPEED_PACKET 0x00001000 /* Low-Speed packet */ + +#define TRANS_OK 0 +#define TRANS_INPROGRESS -1 +#define TRANS_DISCARD -2 +#define TRANS_FAIL -3 + +#define PS_INT 0 +#define PS_DISCONNECTED 1 +#define PS_CONNECTED 2 +#define PS_READY 3 +#define PS_MISSING 4 + +/* Transfer Descriptor status field */ +#define USB_TD_OK 0x00000000 /* TD transmited or received ok */ +#define USB_TD_INPROGRESS 0x80000000 /* TD is being transmitted */ +#define USB_TD_RX_ER_NONOCT 0x40000000 /* Tx Non Octet Aligned Packet */ +#define USB_TD_RX_ER_BITSTUFF 0x20000000 /* Frame Aborted-Received pkt */ +#define USB_TD_RX_ER_CRC 0x10000000 /* CRC error */ +#define USB_TD_RX_ER_OVERUN 0x08000000 /* Over - run occured */ +#define USB_TD_RX_ER_PID 0x04000000 /* wrong PID received */ +#define USB_TD_RX_DATA_UNDERUN 0x02000000 /* shorter than expected */ +#define USB_TD_RX_DATA_OVERUN 0x01000000 /* longer than expected */ +#define USB_TD_TX_ER_NAK 0x00800000 /* NAK handshake */ +#define USB_TD_TX_ER_STALL 0x00400000 /* STALL handshake */ +#define USB_TD_TX_ER_TIMEOUT 0x00200000 /* transmit time out */ +#define USB_TD_TX_ER_UNDERUN 0x00100000 /* transmit underrun */ + +#define USB_TD_ERROR (USB_TD_RX_ER_NONOCT | USB_TD_RX_ER_BITSTUFF | \ + USB_TD_RX_ER_CRC | USB_TD_RX_ER_OVERUN | USB_TD_RX_ER_PID | \ + USB_TD_RX_DATA_UNDERUN | USB_TD_RX_DATA_OVERUN | \ + USB_TD_TX_ER_NAK | USB_TD_TX_ER_STALL | \ + USB_TD_TX_ER_TIMEOUT | USB_TD_TX_ER_UNDERUN) + +/* Transfer Descriptor toggle field */ +#define USB_TD_TOGGLE_DATA0 0 +#define USB_TD_TOGGLE_DATA1 1 +#define USB_TD_TOGGLE_CARRY 2 + +/* #define MULTI_DATA_BUS */ + +/* Bus mode register RBMR/TBMR */ +#define BUS_MODE_GBL 0x20 /*Global snooping */ +#define BUS_MODE_BO 0x18 /*Byte ordering */ +#define BUS_MODE_BO_BE 0x10 /*Byte ordering - Big-endian */ +#define BUS_MODE_DTB 0x02 /*Data bus */ + +/* FHCI QE USB Register Description */ + +/* USB Mode Register bit define */ +#define USB_MODE_EN 0x01 +#define USB_MODE_HOST 0x02 +#define USB_MODE_TEST 0x04 +#define USB_MODE_SFTE 0x08 +#define USB_MODE_RESUME 0x40 +#define USB_MODE_LSS 0x80 + +/* USB Slave Address Register Mask */ +#define USB_SLVADDR_MASK 0x7F + +/* USB Endpoint register define */ +#define USB_EPNUM_MASK 0xF000 +#define USB_EPNUM_SHIFT 12 + +#define USB_TRANS_MODE_SHIFT 8 +#define USB_TRANS_CTR 0x0000 +#define USB_TRANS_INT 0x0100 +#define USB_TRANS_BULK 0x0200 +#define USB_TRANS_ISO 0x0300 + +#define USB_EP_MF 0x0020 +#define USB_EP_RTE 0x0010 + +#define USB_THS_SHIFT 2 +#define USB_THS_MASK 0x000c +#define USB_THS_NORMAL 0x0 +#define USB_THS_IGNORE_IN 0x0004 +#define USB_THS_NACK 0x0008 +#define USB_THS_STALL 0x000c + +#define USB_RHS_SHIFT 0 +#define USB_RHS_MASK 0x0003 +#define USB_RHS_NORMAL 0x0 +#define USB_RHS_IGNORE_OUT 0x0001 +#define USB_RHS_NACK 0x0002 +#define USB_RHS_STALL 0x0003 + +#define USB_RTHS_MASK 0x000f + +/* USB Command Register define */ +#define USB_CMD_STR_FIFO 0x80 +#define USB_CMD_FLUSH_FIFO 0x40 +#define USB_CMD_ISFT 0x20 +#define USB_CMD_DSFT 0x10 +#define USB_CMD_EP_MASK 0x03 + +/* USB Event and Mask Register define */ +#define USB_E_MSF_MASK 0x0800 +#define USB_E_SFT_MASK 0x0400 +#define USB_E_RESET_MASK 0x0200 +#define USB_E_IDLE_MASK 0x0100 +#define USB_E_TXE4_MASK 0x0080 +#define USB_E_TXE3_MASK 0x0040 +#define USB_E_TXE2_MASK 0x0020 +#define USB_E_TXE1_MASK 0x0010 +#define USB_E_SOF_MASK 0x0008 +#define USB_E_BSY_MASK 0x0004 +#define USB_E_TXB_MASK 0x0002 +#define USB_E_RXB_MASK 0x0001 + +/* operation mode */ +enum fhci_op_mode { + FHCI_OP_FS_DEVICE = 0, + FHCI_OP_LS_DEVICE, + FHCI_OP_HOST, + FHCI_OP_DISCONNECT, + FHCI_OP_POWER_ON, + FHCI_OP_POWER_OFF +}; + +/* Freescale USB Host controller registers */ +struct fhci_regs { + u8 usb_mod; /* mode register */ + u8 usb_addr; /* address register */ + u8 usb_comm; /* command register */ + u8 reserved1[1]; + u16 usb_ep[4]; /* endpoint register */ + u8 reserved2[4]; + u16 usb_event; /* event register */ + u8 reserved3[2]; + u16 usb_mask; /* mask register */ + u8 reserved4[1]; + u8 usb_status; /* status register */ + u16 usb_sof_tmr; /* Start Of Frame timer */ + u8 reserved5[2]; + u16 usb_frame_num; /* frame number register */ + u8 reserved6[1]; +}; + +/* Freescale USB HOST */ +struct fhci_pram { + u16 ep_ptr[4]; /* Endpoint porter reg */ + u32 rx_state; /* Rx internal state */ + u32 rx_ptr; /* Rx internal data pointer */ + u16 frame_num; /* Frame number */ + u16 rx_cnt; /* Rx byte count */ + u32 rx_temp; /* Rx temp */ + u32 rx_data_temp; /* Rx data temp */ + u16 rx_u_ptr; /* Rx microcode return address temp */ + u8 reserved1[2]; /* reserved area */ + u32 sof_tbl; /* SOF lookup table pointer */ + u8 sof_u_crc_temp; /* SOF micorcode CRC5 temp reg */ + u8 reserved2[0xdb]; +}; + +/* Freescale USB Endpoint*/ +struct fhci_ep_pram { + u16 rx_base; /* Rx BD base address */ + u16 tx_base; /* Tx BD base address */ + u8 rx_func_code; /* Rx function code */ + u8 tx_func_code; /* Tx function code */ + u16 rx_buff_len; /* Rx buffer length */ + u16 rx_bd_ptr; /* Rx BD pointer */ + u16 tx_bd_ptr; /* Tx BD pointer */ + u32 tx_state; /* Tx internal state */ + u32 tx_ptr; /* Tx internal data pointer */ + u16 tx_crc; /* temp transmit CRC */ + u16 tx_cnt; /* Tx byte count */ + u32 tx_temp; /* Tx temp */ + u16 tx_u_ptr; /* Tx microcode return address temp */ + u16 reserved; +}; + +struct fhci_controller_list { + struct list_head ctrl_list; /* control endpoints */ + struct list_head bulk_list; /* bulk endpoints */ + struct list_head iso_list; /* isochronous endpoints */ + struct list_head intr_list; /* interruput endpoints */ + struct list_head done_list; /* done transfers */ +}; + +struct virtual_root_hub { + int dev_num; /* USB address of the root hub */ + u32 feature; /* indicates what feature has been set */ + struct usb_hub_status hub; + struct usb_port_status port; +}; + +enum fhci_gpios { + GPIO_USBOE = 0, + GPIO_USBTP, + GPIO_USBTN, + GPIO_USBRP, + GPIO_USBRN, + /* these are optional */ + GPIO_SPEED, + GPIO_SUSPN, +}; + +#define NUM_GPIOS (GPIO_SUSPN + 1) + +struct fhci_hcd { + struct fhci_regs __iomem *regs; /* I/O memory used to communicate */ + struct fhci_pram __iomem *pram; /* Parameter RAM */ + int timer; + unsigned int timer_irq; + int gpios[NUM_GPIOS]; + enum qe_clock fullspeed_clk; + enum qe_clock lowspeed_clk; + + spinlock_t lock; + struct fhci_usb *usb_lld; /* Low-level driver */ + struct virtual_root_hub *vroot_hub; /* the virtual root hub */ + int active_urbs; + struct fhci_controller_list *hc_list; + struct tasklet_struct *process_done_task; /* tasklet for done list */ + + struct list_head empty_eds; + struct list_head empty_tds; + +#ifdef CONFIG_FHCI_DEBUG + int usb_irq_stat[13]; + struct dentry *dfs_root; + struct dentry *dfs_regs; + struct dentry *dfs_irq_stat; +#endif +}; + +#define USB_FRAME_USAGE 90 +#define FRAME_TIME_USAGE (USB_FRAME_USAGE*10) /* frame time usage */ +#define SW_FIX_TIME_BETWEEN_TRANSACTION 150 /* SW */ +#define MAX_BYTES_PER_FRAME (USB_FRAME_USAGE*15) +#define MAX_PERIODIC_FRAME_USAGE 90 + +/* transaction type */ +enum fhci_ta_type { + FHCI_TA_IN = 0, /* input transaction */ + FHCI_TA_OUT, /* output transaction */ + FHCI_TA_SETUP, /* setup transaction */ +}; + +/* transfer mode */ +enum fhci_tf_mode { + FHCI_TF_CTRL = 0, + FHCI_TF_ISO, + FHCI_TF_BULK, + FHCI_TF_INTR, +}; + +enum fhci_speed{ + FHCI_FULL_SPEED, + FHCI_LOW_SPEED, +}; + +/* endpoint state */ +enum fhci_ed_state { + FHCI_ED_NEW = 0,/* pipe is new */ + FHCI_ED_OPER, /* pipe is operating */ + FHCI_ED_URB_DEL,/* pipe is in hold because urb is being deleted */ + FHCI_ED_SKIP, /* skip this pipe */ + FHCI_ED_HALTED, /* pipe is halted */ +}; + +enum fhci_port_status { + FHCI_PORT_DISABLED = 0, + FHCI_PORT_DISCONNECTING, + FHCI_PORT_WAITING, /* waiting for connection */ + FHCI_PORT_FULL, /* full speed connected */ + FHCI_PORT_LOW, /* low speed connected */ +}; + +enum fhci_mem_alloc { + MEM_CACHABLE_SYS = 0x00000001, /* primary DDR,cachable */ + MEM_NOCACHE_SYS = 0x00000004, /* primary DDR,non-cachable */ + MEM_SECONDARY = 0x00000002, /* either secondary DDR or SDRAM */ + MEM_PRAM = 0x00000008, /* multi-user RAM identifier */ +}; + +/* USB default parameters*/ +#define DEFAULT_RING_LEN 8 +#define DEFAULT_DATA_MEM MEM_CACHABLE_SYS + +struct ed { + u8 dev_addr; /*device address */ + u8 ep_addr; /*endpoint address */ + enum fhci_tf_mode mode; /*USB transfer mode */ + enum fhci_speed speed; + unsigned int max_pkt_size; + enum fhci_ed_state state; + struct list_head td_list; /*a list of all queued TD to this pipe */ + struct list_head node; + + /*read only parameters, should be cleared upon initialization */ + u8 toggle_carry; /*toggle carry from the last TD submitted */ + u32 last_iso; /*time stamp of last queued ISO transfer */ + struct td *td_head; /*a pointer to the current TD handled */ +}; + +struct td { + void *data; /* a pointer to the data buffer */ + unsigned int len; /* length of the data to be submitted */ + unsigned int actual_len;/* actual bytes transfered on this td */ + enum fhci_ta_type type; /* transaction type */ + u8 toggle; /* toggle for the next transwithin this TD */ + u16 iso_index; /* ISO transaction index */ + u16 start_frame; /* start frame time stamp */ + u16 interval; /* interval between transaction (for ISO/Intr) */ + u32 status; /* status of the TD */ + struct ed *ed; /* a handle to the corresponding ED */ + struct urb *urb; /* a handle to the corresponding URB */ + bool ioc; /* Inform On Completion */ + struct list_head node; + + /*read only parameters should be cleared upon initialization */ + struct packet *pkt; + int nak_cnt; + int error_cnt; + struct list_head frame_lh; +}; + +struct packet { + u8 *data; /* packet data */ + u32 len; /* packet length */ + u32 status; /* status of the packet - equivalent to the status + * field for the corresponding structure td */ + u32 info; /* packet information */ + void *priv_data;/* private data of the driver */ +}; + +/* struct for each URB */ +#define URB_INPROGRESS 0 +#define URB_DEL 1 + +/* URB states (state field) */ +#define US_BULK 0 +#define US_BULK0 1 + +/* three setup states */ +#define US_CTRL_SETUP 2 +#define US_CTRL_DATA 1 +#define US_CTRL_ACK 0 + +#define EP_ZERO 0 + +struct urb_priv { + int num_of_tds; + int tds_cnt; + int state; + + struct td **tds; + struct ed *ed; + struct timer_list time_out; +}; + +/* struct for each 1mSec frame time */ +#define FRAME_IS_TRANSMITTED 0x00 +#define FRAME_TIMER_END_TRANSMISSION 0x01 +#define FRAME_DATA_END_TRANSMISSION 0x02 +#define FRAME_END_TRANSMISSION 0x03 +#define FRAME_IS_PREPARED 0x04 + +struct fhci_time_frame { + u16 frame_num; /*frame number */ + u16 total_bytes; /* total bytes submitted within this frame */ + u8 frame_status; /*flag that indicates to stop fill this frame */ + struct list_head tds_list; /*all tds of this frame */ +}; + +/* internal driver structure*/ +struct fhci_usb { + u16 saved_msk; /*saving of the USB mask register */ + struct endpoint *ep0; /*pointer for endpoint0 structure */ + u32 intr_counter; /*counter of the incoming USB Intr */ + int intr_nesting_cnt; /*interrupt nesting counter */ + u16 max_frame_usage; /*max frame time usage,in micro-sec */ + u16 max_bytes_per_frame;/*max byte can be tx in one time frame */ + u32 sw_transaction_time;/*sw complete trans time,in micro-sec */ + struct fhci_time_frame *actual_frame; + struct fhci_controller_list *hc_list; /* main structure for hc */ + struct virtual_root_hub *vroot_hub; + enum fhci_port_status port_status; /* v_rh port status */ + + u32 (*transfer_confirm)(struct fhci_hcd *fhci); + + struct fhci_hcd *fhci; +}; + +/* + * Various helpers and prototypes below. + */ + +static void move_head_to_tail(struct list_head *list) +{ + struct list_head *node = list->next; + + if (!list_empty(list)) { + list_del(node); + list_add_tail(node, list); + } +} + +/* maps the hardware error code to the USB error code */ +static int status_to_error(u32 status) +{ + if (status == USB_TD_OK) + return 0; + else if (status & USB_TD_RX_ER_CRC) + return -EILSEQ; + else if (status & USB_TD_RX_ER_NONOCT) + return -EPROTO; + else if (status & USB_TD_RX_ER_OVERUN) + return -ECOMM; + else if (status & USB_TD_RX_ER_BITSTUFF) + return -EPROTO; + else if (status & USB_TD_RX_ER_PID) + return -EILSEQ; + else if (status & (USB_TD_TX_ER_NAK | USB_TD_TX_ER_TIMEOUT)) + return -ETIMEDOUT; + else if (status & USB_TD_TX_ER_STALL) + return -EPIPE; + else if (status & USB_TD_TX_ER_UNDERUN) + return -ENOSR; + else if (status & USB_TD_RX_DATA_UNDERUN) + return -EREMOTEIO; + else if (status & USB_TD_RX_DATA_OVERUN) + return -EOVERFLOW; + else + return -EINVAL; +} + +static u16 get_frame_num(struct fhci_hcd *fhci) +{ + return fhci->pram->frame_num & 0x07ff; +} + + +#define fhci_dbg(fhci, fmt, args...) \ + dev_dbg(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_err(fhci, fmt, args...) \ + dev_err(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_info(fhci, fmt, args...) \ + dev_info(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_warn(fhci, fmt, args...) \ + dev_warn(fhci_to_hcd(fhci)->self.controller, fmt, ##args) + +static struct fhci_hcd *hcd_to_fhci(struct usb_hcd *hcd) +{ + return (struct fhci_hcd *)hcd->hcd_priv; +} + +static struct usb_hcd *fhci_to_hcd(struct fhci_hcd *fhci) +{ + return container_of((void *)fhci, struct usb_hcd, hcd_priv); +} + +static void fhci_usb_enable_interrupt(struct fhci_usb *usb); +static void fhci_usb_disable_interrupt(struct fhci_usb *usb); +static void flush_all_transmissions(struct fhci_usb *usb); +static void device_connected_interrupt(struct fhci_hcd *fhci); +static void recycle_empty_td(struct fhci_hcd *fhci, struct td *td); +static struct ed *get_empty_ed(struct fhci_hcd *fhci); + +static struct td *td_alloc_fill(struct fhci_hcd *fhci, + struct urb *urb, struct urb_priv *urb_priv, + struct ed *ed, u16 index, enum fhci_ta_type type, + int toggle, u8 *data, u32 len, u16 interval, + u16 start_frame, bool ioc); +static void flush_bds(struct fhci_usb *usb); +static void flush_actual_frame(struct fhci_usb *usb); +static u32 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); +static void push_dummy_bd(struct endpoint *ep); +static void device_disconnected_interrupt(struct fhci_hcd *fhci); +static int fhci_ioports_check_bus_state(struct fhci_hcd *fhci); +static void process_done_list(unsigned long data); + + +static void recycle_frame(struct fhci_usb *usb, struct packet *pkt); +static void transaction_confirm(struct fhci_usb *usb, struct packet *pkt); +static void qe_usb_restart_tx(u8 ep); +static void schedule_transactions(struct fhci_usb *usb); + +#endif /* __FHCI_H */
--
1.5.2.2